xref: /reactos/base/services/nfsd/name_cache.c (revision 84ccccab)
1 /* NFSv4.1 client for Windows
2  * Copyright � 2012 The Regents of the University of Michigan
3  *
4  * Olga Kornievskaia <aglo@umich.edu>
5  * Casey Bodley <cbodley@umich.edu>
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * without any warranty; without even the implied warranty of merchantability
14  * or fitness for a particular purpose.  See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  */
21 
22 #include <windows.h>
23 #include <strsafe.h>
24 #include <time.h>
25 #include <assert.h>
26 
27 #include "nfs41_ops.h"
28 #include "nfs41_compound.h"
29 #include "name_cache.h"
30 #include "util.h"
31 #include "tree.h"
32 #include "daemon_debug.h"
33 
34 
35 /* dprintf levels for name cache logging */
36 enum {
37     NCLVL1 = 2,
38     NCLVL2
39 };
40 
41 
42 #define NAME_CACHE_EXPIRATION 20 /* TODO: get from configuration */
43 
44 /* allow up to 256K of memory for name and attribute cache entries */
45 #define NAME_CACHE_MAX_SIZE 262144
46 
47 /* negative lookup caching
48  *
49  * by caching lookups that result in NOENT, we can avoid sending subsequent
50  * lookups over the wire.  a name cache entry is negative when its attributes
51  * pointer is NULL.  negative entries are created by three functions:
52  * nfs41_name_cache_remove(), _insert() when called with NULL for the fh and
53  * attributes, and _rename() for the source entry */
54 
55 /* delegations and cache feedback
56  *
57  *   delegations provide a guarantee that no links or attributes will change
58  * without notice.  the name cache takes advantage of this by preventing
59  * delegated entries from being removed on NAME_CACHE_EXPIRATION, though
60  * they're still removed when a parent is invalidated.  the attribute cache
61  * holds an extra reference on delegated entries to prevent their removal
62  * entirely, until the delegation is returned.
63  *   this extra reference presents a problem when the number of delegations
64  * approaches the maximum number of attribute cache entries.  when there are
65  * not enough available entries to store the parent directories, every lookup
66  * results in a name cache miss, and cache performance degrades significantly.
67  *   the solution is to provide feedback via nfs41_name_cache_insert() when
68  * delegations reach a certain percent of the cache capacity.  the error code
69  * ERROR_TOO_MANY_OPEN_FILES, chosen arbitrarily for this case, instructs the
70  * caller to return an outstanding delegation before caching a new one.
71  */
72 static __inline bool_t is_delegation(
73     IN enum open_delegation_type4 type)
74 {
75     return type == OPEN_DELEGATE_READ || type == OPEN_DELEGATE_WRITE;
76 }
77 
78 
79 /* attribute cache */
80 struct attr_cache_entry {
81     RB_ENTRY(attr_cache_entry) rbnode;
82     struct list_entry       free_entry;
83     uint64_t                change;
84     uint64_t                size;
85     uint64_t                fileid;
86     int64_t                 time_access_s;
87     int64_t                 time_create_s;
88     int64_t                 time_modify_s;
89     uint32_t                time_access_ns;
90     uint32_t                time_create_ns;
91     uint32_t                time_modify_ns;
92     uint32_t                numlinks;
93     unsigned                mode : 30;
94     unsigned                hidden : 1;
95     unsigned                system : 1;
96     unsigned                archive : 1;
97     time_t                  expiration;
98     unsigned                ref_count : 26;
99     unsigned                type : 4;
100     unsigned                invalidated : 1;
101     unsigned                delegated : 1;
102 };
103 #define ATTR_ENTRY_SIZE sizeof(struct attr_cache_entry)
104 
105 RB_HEAD(attr_tree, attr_cache_entry);
106 
107 struct attr_cache {
108     struct attr_tree        head;
109     struct attr_cache_entry *pool;
110     struct list_entry       free_entries;
111 };
112 
113 int attr_cmp(struct attr_cache_entry *lhs, struct attr_cache_entry *rhs)
114 {
115     return lhs->fileid < rhs->fileid ? -1 : lhs->fileid > rhs->fileid;
116 }
117 RB_GENERATE(attr_tree, attr_cache_entry, rbnode, attr_cmp)
118 
119 
120 /* attr_cache_entry */
121 #define attr_entry(pos) list_container(pos, struct attr_cache_entry, free_entry)
122 
123 static int attr_cache_entry_create(
124     IN struct attr_cache *cache,
125     IN uint64_t fileid,
126     OUT struct attr_cache_entry **entry_out)
127 {
128     struct attr_cache_entry *entry;
129     int status = NO_ERROR;
130 
131     /* get the next entry from free_entries and remove it */
132     if (list_empty(&cache->free_entries)) {
133         status = ERROR_OUTOFMEMORY;
134         goto out;
135     }
136     entry = attr_entry(cache->free_entries.next);
137     list_remove(&entry->free_entry);
138 
139     entry->fileid = fileid;
140     entry->invalidated = FALSE;
141     entry->delegated = FALSE;
142     *entry_out = entry;
143 out:
144     return status;
145 }
146 
147 static __inline void attr_cache_entry_free(
148     IN struct attr_cache *cache,
149     IN struct attr_cache_entry *entry)
150 {
151     dprintf(NCLVL1, "attr_cache_entry_free(%llu)\n", entry->fileid);
152     RB_REMOVE(attr_tree, &cache->head, entry);
153     /* add it back to free_entries */
154     list_add_tail(&cache->free_entries, &entry->free_entry);
155 }
156 
157 static __inline void attr_cache_entry_ref(
158     IN struct attr_cache *cache,
159     IN struct attr_cache_entry *entry)
160 {
161     const uint32_t previous = entry->ref_count++;
162     dprintf(NCLVL2, "attr_cache_entry_ref(%llu) %u -> %u\n",
163         entry->fileid, previous, entry->ref_count);
164 }
165 
166 static __inline void attr_cache_entry_deref(
167     IN struct attr_cache *cache,
168     IN struct attr_cache_entry *entry)
169 {
170     const uint32_t previous = entry->ref_count--;
171     dprintf(NCLVL2, "attr_cache_entry_deref(%llu) %u -> %u\n",
172         entry->fileid, previous, entry->ref_count);
173 
174     if (entry->ref_count == 0)
175         attr_cache_entry_free(cache, entry);
176 }
177 
178 static __inline int attr_cache_entry_expired(
179     IN const struct attr_cache_entry *entry)
180 {
181     return entry->invalidated ||
182         (!entry->delegated && time(NULL) > entry->expiration);
183 }
184 
185 /* attr_cache */
186 static int attr_cache_init(
187     IN struct attr_cache *cache,
188     IN uint32_t max_entries)
189 {
190     uint32_t i;
191     int status = NO_ERROR;
192 
193     /* allocate a pool of entries */
194     cache->pool = calloc(max_entries, ATTR_ENTRY_SIZE);
195     if (cache->pool == NULL) {
196         status = GetLastError();
197         goto out;
198     }
199 
200     /* initialize the list of free entries */
201     list_init(&cache->free_entries);
202     for (i = 0; i < max_entries; i++) {
203         list_init(&cache->pool[i].free_entry);
204         list_add_tail(&cache->free_entries, &cache->pool[i].free_entry);
205     }
206 out:
207     return status;
208 }
209 
210 static void attr_cache_free(
211     IN struct attr_cache *cache)
212 {
213     /* free the pool */
214     free(cache->pool);
215     cache->pool = NULL;
216     list_init(&cache->free_entries);
217 }
218 
219 static struct attr_cache_entry* attr_cache_search(
220     IN struct attr_cache *cache,
221     IN uint64_t fileid)
222 {
223     /* find an entry that matches fileid */
224     struct attr_cache_entry tmp;
225     tmp.fileid = fileid;
226     return RB_FIND(attr_tree, &cache->head, &tmp);
227 }
228 
229 static int attr_cache_insert(
230     IN struct attr_cache *cache,
231     IN struct attr_cache_entry *entry)
232 {
233     int status = NO_ERROR;
234 
235     dprintf(NCLVL2, "--> attr_cache_insert(%llu)\n", entry->fileid);
236 
237     if (RB_INSERT(attr_tree, &cache->head, entry))
238         status = ERROR_FILE_EXISTS;
239 
240     dprintf(NCLVL2, "<-- attr_cache_insert() returning %d\n", status);
241     return status;
242 }
243 
244 static int attr_cache_find_or_create(
245     IN struct attr_cache *cache,
246     IN uint64_t fileid,
247     OUT struct attr_cache_entry **entry_out)
248 {
249     struct attr_cache_entry *entry;
250     int status = NO_ERROR;
251 
252     dprintf(NCLVL1, "--> attr_cache_find_or_create(%llu)\n", fileid);
253 
254     /* look for an existing entry */
255     entry = attr_cache_search(cache, fileid);
256     if (entry == NULL) {
257         /* create and insert */
258         status = attr_cache_entry_create(cache, fileid, &entry);
259         if (status)
260             goto out;
261 
262         status = attr_cache_insert(cache, entry);
263         if (status)
264             goto out_err_free;
265     }
266 
267     /* take a reference on success */
268     attr_cache_entry_ref(cache, entry);
269 
270 out:
271     *entry_out = entry;
272     dprintf(NCLVL1, "<-- attr_cache_find_or_create() returning %d\n",
273         status);
274     return status;
275 
276 out_err_free:
277     attr_cache_entry_free(cache, entry);
278     entry = NULL;
279     goto out;
280 }
281 
282 static void attr_cache_update(
283     IN struct attr_cache_entry *entry,
284     IN const nfs41_file_info *info,
285     IN enum open_delegation_type4 delegation)
286 {
287     /* update the attributes present in mask */
288     if (info->attrmask.count >= 1) {
289         if (info->attrmask.arr[0] & FATTR4_WORD0_TYPE)
290             entry->type = (unsigned char)(info->type & NFS_FTYPE_MASK);
291         if (info->attrmask.arr[0] & FATTR4_WORD0_CHANGE) {
292             entry->change = info->change;
293             /* revalidate whenever we get a change attribute */
294             entry->invalidated = 0;
295             entry->expiration = time(NULL) + NAME_CACHE_EXPIRATION;
296         }
297         if (info->attrmask.arr[0] & FATTR4_WORD0_SIZE)
298             entry->size = info->size;
299         if (info->attrmask.arr[0] & FATTR4_WORD0_HIDDEN)
300             entry->hidden = info->hidden;
301         if (info->attrmask.arr[0] & FATTR4_WORD0_ARCHIVE)
302             entry->archive = info->archive;
303     }
304     if (info->attrmask.count >= 2) {
305         if (info->attrmask.arr[1] & FATTR4_WORD1_MODE)
306             entry->mode = info->mode;
307         if (info->attrmask.arr[1] & FATTR4_WORD1_NUMLINKS)
308             entry->numlinks = info->numlinks;
309         if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_ACCESS) {
310             entry->time_access_s = info->time_access.seconds;
311             entry->time_access_ns = info->time_access.nseconds;
312         }
313         if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_CREATE) {
314             entry->time_create_s = info->time_create.seconds;
315             entry->time_create_ns = info->time_create.nseconds;
316         }
317         if (info->attrmask.arr[1] & FATTR4_WORD1_TIME_MODIFY) {
318             entry->time_modify_s = info->time_modify.seconds;
319             entry->time_modify_ns = info->time_modify.nseconds;
320         }
321         if (info->attrmask.arr[1] & FATTR4_WORD1_SYSTEM)
322             entry->system = info->system;
323     }
324 
325     if (is_delegation(delegation))
326         entry->delegated = TRUE;
327 }
328 
329 static void copy_attrs(
330     OUT nfs41_file_info *dst,
331     IN const struct attr_cache_entry *src)
332 {
333     dst->change = src->change;
334     dst->size = src->size;
335     dst->time_access.seconds = src->time_access_s;
336     dst->time_access.nseconds = src->time_access_ns;
337     dst->time_create.seconds = src->time_create_s;
338     dst->time_create.nseconds = src->time_create_ns;
339     dst->time_modify.seconds = src->time_modify_s;
340     dst->time_modify.nseconds = src->time_modify_ns;
341     dst->type = src->type;
342     dst->numlinks = src->numlinks;
343     dst->mode = src->mode;
344     dst->fileid = src->fileid;
345     dst->hidden = src->hidden;
346     dst->system = src->system;
347     dst->archive = src->archive;
348 
349     dst->attrmask.count = 2;
350     dst->attrmask.arr[0] = FATTR4_WORD0_TYPE | FATTR4_WORD0_CHANGE
351         | FATTR4_WORD0_SIZE | FATTR4_WORD0_FILEID
352         | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE;
353     dst->attrmask.arr[1] = FATTR4_WORD1_MODE
354         | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_TIME_ACCESS
355         | FATTR4_WORD1_TIME_CREATE | FATTR4_WORD1_TIME_MODIFY
356         | FATTR4_WORD1_SYSTEM;
357 }
358 
359 
360 /* name cache */
361 RB_HEAD(name_tree, name_cache_entry);
362 struct name_cache_entry {
363     char                    component[NFS41_MAX_COMPONENT_LEN];
364     nfs41_fh                fh;
365     RB_ENTRY(name_cache_entry) rbnode;
366     struct name_tree        rbchildren;
367     struct attr_cache_entry *attributes;
368     struct name_cache_entry *parent;
369     struct list_entry       exp_entry;
370     time_t                  expiration;
371     unsigned short          component_len;
372 };
373 #define NAME_ENTRY_SIZE sizeof(struct name_cache_entry)
374 
375 int name_cmp(struct name_cache_entry *lhs, struct name_cache_entry *rhs)
376 {
377     const int diff = rhs->component_len - lhs->component_len;
378     return diff ? diff : strncmp(lhs->component, rhs->component, lhs->component_len);
379 }
380 RB_GENERATE(name_tree, name_cache_entry, rbnode, name_cmp)
381 
382 struct nfs41_name_cache {
383     struct name_cache_entry *root;
384     struct name_cache_entry *pool;
385     struct attr_cache       attributes;
386     struct list_entry       exp_entries; /* list of entries by expiry */
387     uint32_t                expiration;
388     uint32_t                entries;
389     uint32_t                max_entries;
390     uint32_t                delegations;
391     uint32_t                max_delegations;
392     SRWLOCK                 lock;
393 };
394 
395 
396 /* internal name cache functions used by the public name cache interface;
397  * these functions expect the caller to hold a lock on the cache */
398 
399 #define name_entry(pos) list_container(pos, struct name_cache_entry, exp_entry)
400 
401 static __inline bool_t name_cache_enabled(
402     IN struct nfs41_name_cache *cache)
403 {
404     return cache->expiration > 0;
405 }
406 
407 static __inline void name_cache_entry_rename(
408     OUT struct name_cache_entry *entry,
409     IN const nfs41_component *component)
410 {
411     StringCchCopyNA(entry->component, NFS41_MAX_COMPONENT_LEN,
412         component->name, component->len);
413     entry->component_len = component->len;
414 }
415 
416 static __inline void name_cache_remove(
417     IN struct name_cache_entry *entry,
418     IN struct name_cache_entry *parent)
419 {
420     RB_REMOVE(name_tree, &parent->rbchildren, entry);
421     entry->parent = NULL;
422 }
423 
424 static void name_cache_unlink_children_recursive(
425     IN struct nfs41_name_cache *cache,
426     IN struct name_cache_entry *parent);
427 
428 static __inline void name_cache_unlink(
429     IN struct nfs41_name_cache *cache,
430     IN struct name_cache_entry *entry)
431 {
432     /* remove the entry from the tree */
433     if (entry->parent)
434         name_cache_remove(entry, entry->parent);
435     else if (entry == cache->root)
436         cache->root = NULL;
437 
438     /* unlink all of its children */
439     name_cache_unlink_children_recursive(cache, entry);
440     /* release the cached attributes */
441     if (entry->attributes) {
442         attr_cache_entry_deref(&cache->attributes, entry->attributes);
443         entry->attributes = NULL;
444     }
445     /* move it to the end of exp_entries for scavenging */
446     list_remove(&entry->exp_entry);
447     list_add_tail(&cache->exp_entries, &entry->exp_entry);
448 }
449 
450 static void name_cache_unlink_children_recursive(
451     IN struct nfs41_name_cache *cache,
452     IN struct name_cache_entry *parent)
453 {
454     struct name_cache_entry *entry, *tmp;
455     RB_FOREACH_SAFE(entry, name_tree, &parent->rbchildren, tmp)
456         name_cache_unlink(cache, entry);
457 }
458 
459 static int name_cache_entry_create(
460     IN struct nfs41_name_cache *cache,
461     IN const nfs41_component *component,
462     OUT struct name_cache_entry **entry_out)
463 {
464     int status = NO_ERROR;
465     struct name_cache_entry *entry;
466 
467     if (cache->entries >= cache->max_entries) {
468         /* scavenge the oldest entry */
469         if (list_empty(&cache->exp_entries)) {
470             status = ERROR_OUTOFMEMORY;
471             goto out;
472         }
473         entry = name_entry(cache->exp_entries.prev);
474         name_cache_unlink(cache, entry);
475 
476         dprintf(NCLVL2, "name_cache_entry_create('%s') scavenged 0x%p\n",
477             component->name, entry);
478     } else {
479         /* take the next entry in the pool and add it to exp_entries */
480         entry = &cache->pool[cache->entries++];
481         list_init(&entry->exp_entry);
482         list_add_tail(&cache->exp_entries, &entry->exp_entry);
483     }
484 
485     name_cache_entry_rename(entry, component);
486 
487     *entry_out = entry;
488 out:
489     return status;
490 }
491 
492 static void name_cache_entry_accessed(
493     IN struct nfs41_name_cache *cache,
494     IN struct name_cache_entry *entry)
495 {
496     /* move the entry to the front of cache->exp_entries, then do
497      * the same for its parents, which are more costly to evict */
498     while (entry) {
499         /* if entry is delegated, it won't be in the list */
500         if (!list_empty(&entry->exp_entry)) {
501             list_remove(&entry->exp_entry);
502             list_add_head(&cache->exp_entries, &entry->exp_entry);
503         }
504         if (entry == entry->parent)
505             break;
506         entry = entry->parent;
507     }
508 }
509 
510 static void name_cache_entry_updated(
511     IN struct nfs41_name_cache *cache,
512     IN struct name_cache_entry *entry)
513 {
514     /* update the expiration timer */
515     entry->expiration = time(NULL) + cache->expiration;
516     name_cache_entry_accessed(cache, entry);
517 }
518 
519 static int name_cache_entry_update(
520     IN struct nfs41_name_cache *cache,
521     IN struct name_cache_entry *entry,
522     IN OPTIONAL const nfs41_fh *fh,
523     IN OPTIONAL const nfs41_file_info *info,
524     IN enum open_delegation_type4 delegation)
525 {
526     int status = NO_ERROR;
527 
528     if (fh)
529         fh_copy(&entry->fh, fh);
530     else
531         entry->fh.len = 0;
532 
533     if (info) {
534         if (entry->attributes == NULL) {
535             /* negative -> positive entry, create the attributes */
536             status = attr_cache_find_or_create(&cache->attributes,
537                 info->fileid, &entry->attributes);
538             if (status)
539                 goto out;
540         }
541 
542         attr_cache_update(entry->attributes, info, delegation);
543 
544         /* hold a reference as long as we have the delegation */
545         if (is_delegation(delegation)) {
546             attr_cache_entry_ref(&cache->attributes, entry->attributes);
547             cache->delegations++;
548         }
549 
550         /* keep the entry from expiring */
551         if (entry->attributes->delegated)
552             list_remove(&entry->exp_entry);
553     } else if (entry->attributes) {
554         /* positive -> negative entry, deref the attributes */
555         attr_cache_entry_deref(&cache->attributes, entry->attributes);
556         entry->attributes = NULL;
557     }
558     name_cache_entry_updated(cache, entry);
559 out:
560     return status;
561 }
562 
563 static int name_cache_entry_changed(
564     IN struct nfs41_name_cache *cache,
565     IN struct name_cache_entry *entry,
566     IN const change_info4 *cinfo)
567 {
568     if (entry->attributes == NULL)
569         return FALSE;
570 
571     if (cinfo->after == entry->attributes->change ||
572             (cinfo->atomic && cinfo->before == entry->attributes->change)) {
573         entry->attributes->change = cinfo->after;
574         name_cache_entry_updated(cache, entry);
575         dprintf(NCLVL1, "name_cache_entry_changed('%s') has not changed. "
576             "updated change=%llu\n", entry->component,
577             entry->attributes->change);
578         return FALSE;
579     } else {
580         dprintf(NCLVL1, "name_cache_entry_changed('%s') has changed: was %llu, "
581             "got before=%llu\n", entry->component,
582             entry->attributes->change, cinfo->before);
583         return TRUE;
584     }
585 }
586 
587 static void name_cache_entry_invalidate(
588     IN struct nfs41_name_cache *cache,
589     IN struct name_cache_entry *entry)
590 {
591     dprintf(NCLVL1, "name_cache_entry_invalidate('%s')\n", entry->component);
592 
593     if (entry->attributes) {
594         /* flag attributes so that entry_invis() will return true
595          * if another entry attempts to use them */
596         entry->attributes->invalidated = 1;
597     }
598     name_cache_unlink(cache, entry);
599 }
600 
601 static struct name_cache_entry* name_cache_search(
602     IN struct nfs41_name_cache *cache,
603     IN struct name_cache_entry *parent,
604     IN const nfs41_component *component)
605 {
606     struct name_cache_entry tmp, *entry;
607 
608     dprintf(NCLVL2, "--> name_cache_search('%.*s' under '%s')\n",
609         component->len, component->name, parent->component);
610 
611     StringCchCopyNA(tmp.component, NFS41_MAX_COMPONENT_LEN,
612         component->name, component->len);
613     tmp.component_len = component->len;
614 
615     entry = RB_FIND(name_tree, &parent->rbchildren, &tmp);
616     if (entry)
617         dprintf(NCLVL2, "<-- name_cache_search() "
618             "found existing entry 0x%p\n", entry);
619     else
620         dprintf(NCLVL2, "<-- name_cache_search() returning NULL\n");
621     return entry;
622 }
623 
624 static int entry_invis(
625     IN struct name_cache_entry *entry,
626     OUT OPTIONAL bool_t *is_negative)
627 {
628     /* name entry timer expired? */
629     if (!list_empty(&entry->exp_entry) && time(NULL) > entry->expiration) {
630         dprintf(NCLVL2, "name_entry_expired('%s')\n", entry->component);
631         return 1;
632     }
633     /* negative lookup entry? */
634     if (entry->attributes == NULL) {
635         if (is_negative) *is_negative = 1;
636         dprintf(NCLVL2, "name_entry_negative('%s')\n", entry->component);
637         return 1;
638     }
639     /* attribute entry expired? */
640     if (attr_cache_entry_expired(entry->attributes)) {
641         dprintf(NCLVL2, "attr_entry_expired(%llu)\n",
642             entry->attributes->fileid);
643         return 1;
644     }
645     return 0;
646 }
647 
648 static int name_cache_lookup(
649     IN struct nfs41_name_cache *cache,
650     IN bool_t skip_invis,
651     IN const char *path,
652     IN const char *path_end,
653     OUT OPTIONAL const char **remaining_path_out,
654     OUT OPTIONAL struct name_cache_entry **parent_out,
655     OUT OPTIONAL struct name_cache_entry **target_out,
656     OUT OPTIONAL bool_t *is_negative)
657 {
658     struct name_cache_entry *parent, *target;
659     nfs41_component component;
660     const char *path_pos;
661     int status = NO_ERROR;
662 
663     dprintf(NCLVL1, "--> name_cache_lookup('%s')\n", path);
664 
665     parent = NULL;
666     target = cache->root;
667     component.name = path_pos = path;
668 
669     if (target == NULL || (skip_invis && entry_invis(target, is_negative))) {
670         target = NULL;
671         status = ERROR_PATH_NOT_FOUND;
672         goto out;
673     }
674 
675     while (next_component(path_pos, path_end, &component)) {
676         parent = target;
677         target = name_cache_search(cache, parent, &component);
678         path_pos = component.name + component.len;
679         if (target == NULL || (skip_invis && entry_invis(target, is_negative))) {
680             target = NULL;
681             if (is_last_component(component.name, path_end))
682                 status = ERROR_FILE_NOT_FOUND;
683             else
684                 status = ERROR_PATH_NOT_FOUND;
685             break;
686         }
687     }
688 out:
689     if (remaining_path_out) *remaining_path_out = component.name;
690     if (parent_out) *parent_out = parent;
691     if (target_out) *target_out = target;
692     dprintf(NCLVL1, "<-- name_cache_lookup() returning %d\n", status);
693     return status;
694 }
695 
696 static int name_cache_insert(
697     IN struct name_cache_entry *entry,
698     IN struct name_cache_entry *parent)
699 {
700     int status = NO_ERROR;
701 
702     dprintf(NCLVL2, "--> name_cache_insert('%s')\n", entry->component);
703 
704     if (RB_INSERT(name_tree, &parent->rbchildren, entry))
705         status = ERROR_FILE_EXISTS;
706     entry->parent = parent;
707 
708     dprintf(NCLVL2, "<-- name_cache_insert() returning %u\n", status);
709     return status;
710 }
711 
712 static int name_cache_find_or_create(
713     IN struct nfs41_name_cache *cache,
714     IN struct name_cache_entry *parent,
715     IN const nfs41_component *component,
716     OUT struct name_cache_entry **target_out)
717 {
718     int status = NO_ERROR;
719 
720     dprintf(NCLVL1, "--> name_cache_find_or_create('%.*s' under '%s')\n",
721         component->len, component->name, parent->component);
722 
723     *target_out = name_cache_search(cache, parent, component);
724     if (*target_out)
725         goto out;
726 
727     status = name_cache_entry_create(cache, component, target_out);
728     if (status)
729         goto out;
730 
731     status = name_cache_insert(*target_out, parent);
732     if (status)
733         goto out_err;
734 
735 out:
736     dprintf(NCLVL1, "<-- name_cache_find_or_create() returning %d\n",
737         status);
738     return status;
739 
740 out_err:
741     *target_out = NULL;
742     goto out;
743 }
744 
745 
746 /* public name cache interface, declared in name_cache.h */
747 
748 /* assuming no hard links, calculate how many entries will fit in the cache */
749 #define SIZE_PER_ENTRY (ATTR_ENTRY_SIZE + NAME_ENTRY_SIZE)
750 #define NAME_CACHE_MAX_ENTRIES (NAME_CACHE_MAX_SIZE / SIZE_PER_ENTRY)
751 
752 int nfs41_name_cache_create(
753     OUT struct nfs41_name_cache **cache_out)
754 {
755     struct nfs41_name_cache *cache;
756     int status = NO_ERROR;
757 
758     dprintf(NCLVL1, "nfs41_name_cache_create()\n");
759 
760     /* allocate the cache */
761     cache = calloc(1, sizeof(struct nfs41_name_cache));
762     if (cache == NULL) {
763         status = GetLastError();
764         goto out;
765     }
766 
767     list_init(&cache->exp_entries);
768     cache->expiration = NAME_CACHE_EXPIRATION;
769     cache->max_entries = NAME_CACHE_MAX_ENTRIES;
770     cache->max_delegations = NAME_CACHE_MAX_ENTRIES / 2;
771     InitializeSRWLock(&cache->lock);
772 
773     /* allocate a pool of entries */
774     cache->pool = calloc(cache->max_entries, NAME_ENTRY_SIZE);
775     if (cache->pool == NULL) {
776         status = GetLastError();
777         goto out_err_cache;
778     }
779 
780     /* initialize the attribute cache */
781     status = attr_cache_init(&cache->attributes, cache->max_entries);
782     if (status)
783         goto out_err_pool;
784 
785     *cache_out = cache;
786 out:
787     return status;
788 
789 out_err_pool:
790     free(cache->pool);
791 out_err_cache:
792     free(cache);
793     goto out;
794 }
795 
796 int nfs41_name_cache_free(
797     IN struct nfs41_name_cache **cache_out)
798 {
799     struct nfs41_name_cache *cache = *cache_out;
800     int status = NO_ERROR;
801 
802     dprintf(NCLVL1, "nfs41_name_cache_free()\n");
803 
804     /* free the attribute cache */
805     attr_cache_free(&cache->attributes);
806 
807     /* free the name entry pool */
808     free(cache->pool);
809     free(cache);
810     *cache_out = NULL;
811     return status;
812 }
813 
814 static __inline void copy_fh(
815     OUT nfs41_fh *dst,
816     IN OPTIONAL const struct name_cache_entry *src)
817 {
818     if (src)
819         fh_copy(dst, &src->fh);
820     else
821         dst->len = 0;
822 }
823 
824 int nfs41_name_cache_lookup(
825     IN struct nfs41_name_cache *cache,
826     IN const char *path,
827     IN const char *path_end,
828     OUT OPTIONAL const char **remaining_path_out,
829     OUT OPTIONAL nfs41_fh *parent_out,
830     OUT OPTIONAL nfs41_fh *target_out,
831     OUT OPTIONAL nfs41_file_info *info_out,
832     OUT OPTIONAL bool_t *is_negative)
833 {
834     struct name_cache_entry *parent, *target;
835     const char *path_pos = path;
836     int status;
837 
838     AcquireSRWLockShared(&cache->lock);
839 
840     if (!name_cache_enabled(cache)) {
841         status = ERROR_NOT_SUPPORTED;
842         goto out_unlock;
843     }
844 
845     status = name_cache_lookup(cache, 1, path, path_end,
846         &path_pos, &parent, &target, is_negative);
847 
848     if (parent_out) copy_fh(parent_out, parent);
849     if (target_out) copy_fh(target_out, target);
850     if (info_out && target && target->attributes)
851         copy_attrs(info_out, target->attributes);
852 
853 out_unlock:
854     ReleaseSRWLockShared(&cache->lock);
855     if (remaining_path_out) *remaining_path_out = path_pos;
856     return status;
857 }
858 
859 int nfs41_attr_cache_lookup(
860     IN struct nfs41_name_cache *cache,
861     IN uint64_t fileid,
862     OUT nfs41_file_info *info_out)
863 {
864     struct attr_cache_entry *entry;
865     int status = NO_ERROR;
866 
867     dprintf(NCLVL1, "--> nfs41_attr_cache_lookup(%llu)\n", fileid);
868 
869     AcquireSRWLockShared(&cache->lock);
870 
871     if (!name_cache_enabled(cache)) {
872         status = ERROR_NOT_SUPPORTED;
873         goto out_unlock;
874     }
875 
876     entry = attr_cache_search(&cache->attributes, fileid);
877     if (entry == NULL || attr_cache_entry_expired(entry)) {
878         status = ERROR_FILE_NOT_FOUND;
879         goto out_unlock;
880     }
881 
882     copy_attrs(info_out, entry);
883 
884 out_unlock:
885     ReleaseSRWLockShared(&cache->lock);
886 
887     dprintf(NCLVL1, "<-- nfs41_attr_cache_lookup() returning %d\n", status);
888     return status;
889 }
890 
891 int nfs41_attr_cache_update(
892     IN struct nfs41_name_cache *cache,
893     IN uint64_t fileid,
894     IN const nfs41_file_info *info)
895 {
896     struct attr_cache_entry *entry;
897     int status = NO_ERROR;
898 
899     dprintf(NCLVL1, "--> nfs41_attr_cache_update(%llu)\n", fileid);
900 
901     AcquireSRWLockExclusive(&cache->lock);
902 
903     if (!name_cache_enabled(cache)) {
904         status = ERROR_NOT_SUPPORTED;
905         goto out_unlock;
906     }
907 
908     entry = attr_cache_search(&cache->attributes, fileid);
909     if (entry == NULL) {
910         status = ERROR_FILE_NOT_FOUND;
911         goto out_unlock;
912     }
913 
914     attr_cache_update(entry, info, OPEN_DELEGATE_NONE);
915 
916 out_unlock:
917     ReleaseSRWLockExclusive(&cache->lock);
918 
919     dprintf(NCLVL1, "<-- nfs41_attr_cache_update() returning %d\n", status);
920     return status;
921 }
922 
923 int nfs41_name_cache_insert(
924     IN struct nfs41_name_cache *cache,
925     IN const char *path,
926     IN const nfs41_component *name,
927     IN OPTIONAL const nfs41_fh *fh,
928     IN OPTIONAL const nfs41_file_info *info,
929     IN OPTIONAL const change_info4 *cinfo,
930     IN enum open_delegation_type4 delegation)
931 {
932     struct name_cache_entry *parent, *target;
933     int status;
934 
935     dprintf(NCLVL1, "--> nfs41_name_cache_insert('%.*s')\n",
936         name->name + name->len - path, path);
937 
938     AcquireSRWLockExclusive(&cache->lock);
939 
940     if (!name_cache_enabled(cache)) {
941         status = ERROR_NOT_SUPPORTED;
942         goto out_unlock;
943     }
944 
945     /* limit the number of delegations to prevent attr cache starvation */
946     if (is_delegation(delegation) &&
947         cache->delegations >= cache->max_delegations) {
948         status = ERROR_TOO_MANY_OPEN_FILES;
949         goto out_unlock;
950     }
951 
952     /* an empty path or component implies the root entry */
953     if (path == NULL || name == NULL || name->len == 0) {
954         /* create the root entry if it doesn't exist */
955         if (cache->root == NULL) {
956             const nfs41_component name = { "ROOT", 4 };
957             status = name_cache_entry_create(cache, &name, &cache->root);
958             if (status)
959                 goto out_err_deleg;
960         }
961         target = cache->root;
962     } else {
963         /* find/create an entry under its parent */
964         status = name_cache_lookup(cache, 0, path,
965             name->name, NULL, NULL, &parent, NULL);
966         if (status)
967             goto out_err_deleg;
968 
969         if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) {
970             name_cache_entry_invalidate(cache, parent);
971             goto out_err_deleg;
972         }
973 
974         status = name_cache_find_or_create(cache, parent, name, &target);
975         if (status)
976             goto out_err_deleg;
977     }
978 
979     /* pass in the new fh/attributes */
980     status = name_cache_entry_update(cache, target, fh, info, delegation);
981     if (status)
982         goto out_err_update;
983 
984 out_unlock:
985     ReleaseSRWLockExclusive(&cache->lock);
986 
987     dprintf(NCLVL1, "<-- nfs41_name_cache_insert() returning %d\n",
988         status);
989     return status;
990 
991 out_err_update:
992     /* a failure in name_cache_entry_update() leaves a negative entry
993      * where there shouldn't be one; remove it from the cache */
994     name_cache_entry_invalidate(cache, target);
995 
996 out_err_deleg:
997     if (is_delegation(delegation)) {
998         /* we still need a reference to the attributes for the delegation */
999         struct attr_cache_entry *attributes;
1000         status = attr_cache_find_or_create(&cache->attributes,
1001             info->fileid, &attributes);
1002         if (status == NO_ERROR) {
1003             attr_cache_update(attributes, info, delegation);
1004             cache->delegations++;
1005         }
1006         else
1007             status = ERROR_TOO_MANY_OPEN_FILES;
1008     }
1009     goto out_unlock;
1010 }
1011 
1012 int nfs41_name_cache_delegreturn(
1013     IN struct nfs41_name_cache *cache,
1014     IN uint64_t fileid,
1015     IN const char *path,
1016     IN const nfs41_component *name)
1017 {
1018     struct name_cache_entry *parent, *target;
1019     struct attr_cache_entry *attributes;
1020     int status;
1021 
1022     dprintf(NCLVL1, "--> nfs41_name_cache_delegreturn(%llu, '%s')\n",
1023         fileid, path);
1024 
1025     AcquireSRWLockExclusive(&cache->lock);
1026 
1027     if (!name_cache_enabled(cache)) {
1028         status = ERROR_NOT_SUPPORTED;
1029         goto out_unlock;
1030     }
1031 
1032     status = name_cache_lookup(cache, 0, path,
1033         name->name + name->len, NULL, &parent, &target, NULL);
1034     if (status == NO_ERROR) {
1035         /* put the name cache entry back on the exp_entries list */
1036         list_add_head(&cache->exp_entries, &target->exp_entry);
1037         name_cache_entry_updated(cache, target);
1038 
1039         attributes = target->attributes;
1040     } else {
1041         /* should still have an attr cache entry */
1042         attributes = attr_cache_search(&cache->attributes, fileid);
1043     }
1044 
1045     if (attributes == NULL) {
1046         status = ERROR_FILE_NOT_FOUND;
1047         goto out_unlock;
1048     }
1049 
1050     /* release the reference from name_cache_entry_update() */
1051     if (attributes->delegated) {
1052         attributes->delegated = FALSE;
1053         attr_cache_entry_deref(&cache->attributes, attributes);
1054         assert(cache->delegations > 0);
1055         cache->delegations--;
1056     }
1057     status = NO_ERROR;
1058 
1059 out_unlock:
1060     ReleaseSRWLockExclusive(&cache->lock);
1061 
1062     dprintf(NCLVL1, "<-- nfs41_name_cache_delegreturn() returning %d\n", status);
1063     return status;
1064 }
1065 
1066 int nfs41_name_cache_remove(
1067     IN struct nfs41_name_cache *cache,
1068     IN const char *path,
1069     IN const nfs41_component *name,
1070     IN uint64_t fileid,
1071     IN const change_info4 *cinfo)
1072 {
1073     struct name_cache_entry *parent, *target;
1074     struct attr_cache_entry *attributes = NULL;
1075     int status;
1076 
1077     dprintf(NCLVL1, "--> nfs41_name_cache_remove('%s')\n", path);
1078 
1079     AcquireSRWLockExclusive(&cache->lock);
1080 
1081     if (!name_cache_enabled(cache)) {
1082         status = ERROR_NOT_SUPPORTED;
1083         goto out_unlock;
1084     }
1085 
1086     status = name_cache_lookup(cache, 0, path,
1087         name->name + name->len, NULL, &parent, &target, NULL);
1088     if (status == ERROR_PATH_NOT_FOUND)
1089         goto out_attributes;
1090 
1091     if (cinfo && name_cache_entry_changed(cache, parent, cinfo)) {
1092         name_cache_entry_invalidate(cache, parent);
1093         goto out_attributes;
1094     }
1095 
1096     if (status == ERROR_FILE_NOT_FOUND)
1097         goto out_attributes;
1098 
1099     if (target->attributes)
1100         target->attributes->numlinks--;
1101 
1102     /* make this a negative entry and unlink children */
1103     name_cache_entry_update(cache, target, NULL, NULL, OPEN_DELEGATE_NONE);
1104     name_cache_unlink_children_recursive(cache, target);
1105 
1106 out_unlock:
1107     ReleaseSRWLockExclusive(&cache->lock);
1108 
1109     dprintf(NCLVL1, "<-- nfs41_name_cache_remove() returning %d\n", status);
1110     return status;
1111 
1112 out_attributes:
1113     /* in the presence of other links, we need to update numlinks
1114      * regardless of a failure to find the target entry */
1115     dprintf(NCLVL1, "nfs41_name_cache_remove: need to find attributes for %s\n", path);
1116     attributes = attr_cache_search(&cache->attributes, fileid);
1117     if (attributes)
1118         attributes->numlinks--;
1119     goto out_unlock;
1120 }
1121 
1122 int nfs41_name_cache_rename(
1123     IN struct nfs41_name_cache *cache,
1124     IN const char *src_path,
1125     IN const nfs41_component *src_name,
1126     IN const change_info4 *src_cinfo,
1127     IN const char *dst_path,
1128     IN const nfs41_component *dst_name,
1129     IN const change_info4 *dst_cinfo)
1130 {
1131     struct name_cache_entry *src_parent, *src;
1132     struct name_cache_entry *dst_parent;
1133     int status = NO_ERROR;
1134 
1135     dprintf(NCLVL1, "--> nfs41_name_cache_rename('%s' to '%s')\n",
1136         src_path, dst_path);
1137 
1138     AcquireSRWLockExclusive(&cache->lock);
1139 
1140     if (!name_cache_enabled(cache)) {
1141         status = ERROR_NOT_SUPPORTED;
1142         goto out_unlock;
1143     }
1144 
1145     /* look up dst_parent */
1146     status = name_cache_lookup(cache, 0, dst_path,
1147         dst_name->name, NULL, NULL, &dst_parent, NULL);
1148     /* we can't create the dst entry without a parent */
1149     if (status || dst_parent->attributes == NULL) {
1150         /* if src exists, make it negative */
1151         dprintf(NCLVL1, "nfs41_name_cache_rename: adding negative cache "
1152             "entry for %.*s\n", src_name->len, src_name->name);
1153         status = name_cache_lookup(cache, 0, src_path,
1154             src_name->name + src_name->len, NULL, NULL, &src, NULL);
1155         if (status == NO_ERROR) {
1156             name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE);
1157             name_cache_unlink_children_recursive(cache, src);
1158         }
1159         status = ERROR_PATH_NOT_FOUND;
1160         goto out_unlock;
1161     }
1162 
1163     /* look up src_parent and src */
1164     status = name_cache_lookup(cache, 0, src_path,
1165         src_name->name + src_name->len, NULL, &src_parent, &src, NULL);
1166     /* we can't create the dst entry without valid attributes */
1167     if (status || src->attributes == NULL) {
1168         /* remove dst if it exists */
1169         struct name_cache_entry *dst;
1170         dprintf(NCLVL1, "nfs41_name_cache_rename: removing negative cache "
1171             "entry for %.*s\n", dst_name->len, dst_name->name);
1172         dst = name_cache_search(cache, dst_parent, dst_name);
1173         if (dst) name_cache_unlink(cache, dst);
1174         goto out_unlock;
1175     }
1176 
1177     if (name_cache_entry_changed(cache, dst_parent, dst_cinfo)) {
1178         name_cache_entry_invalidate(cache, dst_parent);
1179         /* if dst_parent and src_parent are both gone,
1180          * we no longer have an entry to rename */
1181         if (dst_parent == src_parent)
1182             goto out_unlock;
1183     } else {
1184         struct name_cache_entry *existing;
1185         existing = name_cache_search(cache, dst_parent, dst_name);
1186         if (existing) {
1187             if (existing == src)
1188                 goto out_unlock;
1189             /* remove the existing entry, but don't unlink it yet;
1190              * we may reuse it for a negative entry */
1191             name_cache_remove(existing, dst_parent);
1192         }
1193 
1194         /* move the src entry under dst_parent */
1195         name_cache_remove(src, src_parent);
1196         name_cache_entry_rename(src, dst_name);
1197         name_cache_insert(src, dst_parent);
1198 
1199         if (existing) {
1200             /* recycle 'existing' as the negative entry 'src' */
1201             name_cache_entry_rename(existing, src_name);
1202             name_cache_insert(existing, src_parent);
1203         }
1204         src = existing;
1205     }
1206 
1207     if (name_cache_entry_changed(cache, src_parent, src_cinfo)) {
1208         name_cache_entry_invalidate(cache, src_parent);
1209         goto out_unlock;
1210     }
1211 
1212     /* leave a negative entry where the file used to be */
1213     if (src == NULL) {
1214         /* src was moved, create a new entry in its place */
1215         status = name_cache_find_or_create(cache, src_parent, src_name, &src);
1216         if (status)
1217             goto out_unlock;
1218     }
1219     name_cache_entry_update(cache, src, NULL, NULL, OPEN_DELEGATE_NONE);
1220     name_cache_unlink_children_recursive(cache, src);
1221 
1222 out_unlock:
1223     ReleaseSRWLockExclusive(&cache->lock);
1224 
1225     dprintf(NCLVL1, "<-- nfs41_name_cache_rename() returning %d\n", status);
1226     return status;
1227 }
1228 
1229 /* nfs41_name_cache_resolve_fh() */
1230 
1231 #define MAX_PUTFH_PER_COMPOUND 16
1232 
1233 static bool_t get_path_fhs(
1234     IN struct nfs41_name_cache *cache,
1235     IN nfs41_abs_path *path,
1236     IN OUT const char **path_pos,
1237     IN uint32_t max_components,
1238     OUT nfs41_path_fh *files,
1239     OUT uint32_t *count)
1240 {
1241     struct name_cache_entry *target;
1242     const char *path_end = path->path + path->len;
1243     nfs41_component *name;
1244     uint32_t i;
1245     int status;
1246 
1247     *count = 0;
1248 
1249     AcquireSRWLockShared(&cache->lock);
1250 
1251     /* look up the parent of the first component */
1252     status = name_cache_lookup(cache, 1, path->path,
1253         *path_pos, NULL, NULL, &target, NULL);
1254     if (status)
1255         goto out_unlock;
1256 
1257     for (i = 0; i < max_components; i++) {
1258         files[i].path = path;
1259         name = &files[i].name;
1260 
1261         if (!next_component(*path_pos, path_end, name))
1262             break;
1263         *path_pos = name->name + name->len;
1264 
1265         target = name_cache_search(cache, target, name);
1266         if (target == NULL || entry_invis(target, NULL)) {
1267             if (is_last_component(name->name, path_end))
1268                 status = ERROR_FILE_NOT_FOUND;
1269             else
1270                 status = ERROR_PATH_NOT_FOUND;
1271             goto out_unlock;
1272         }
1273         /* make copies for use outside of cache->lock */
1274         fh_copy(&files[i].fh, &target->fh);
1275         (*count)++;
1276     }
1277 
1278 out_unlock:
1279     ReleaseSRWLockShared(&cache->lock);
1280     return *count && status == 0;
1281 }
1282 
1283 static int rpc_array_putfh(
1284     IN nfs41_session *session,
1285     IN nfs41_path_fh *files,
1286     IN uint32_t count,
1287     OUT uint32_t *valid_out)
1288 {
1289     nfs41_compound compound;
1290     nfs_argop4 argops[1+MAX_PUTFH_PER_COMPOUND];
1291     nfs_resop4 resops[1+MAX_PUTFH_PER_COMPOUND];
1292     nfs41_sequence_args sequence_args;
1293     nfs41_sequence_res sequence_res = { 0 };
1294     nfs41_putfh_args putfh_args[MAX_PUTFH_PER_COMPOUND];
1295     nfs41_putfh_res putfh_res[MAX_PUTFH_PER_COMPOUND] = { 0 };
1296     uint32_t i;
1297     int status;
1298 
1299     *valid_out = 0;
1300 
1301     compound_init(&compound, argops, resops, "array_putfh");
1302 
1303     compound_add_op(&compound, OP_SEQUENCE, &sequence_args, &sequence_res);
1304     nfs41_session_sequence(&sequence_args, session, 0);
1305 
1306     for (i = 0; i < count; i++){
1307         compound_add_op(&compound, OP_PUTFH, &putfh_args[i], &putfh_res[i]);
1308         putfh_args[i].file = &files[i];
1309         putfh_args[i].in_recovery = 1;
1310     }
1311 
1312     status = compound_encode_send_decode(session, &compound, TRUE);
1313     if (status) goto out;
1314 
1315     status = sequence_res.sr_status;
1316     if (status) goto out;
1317 
1318     for (i = 0; i < count; i++) {
1319         status = putfh_res[i].status;
1320         if (status) break;
1321     }
1322     *valid_out = i;
1323 out:
1324     return status;
1325 }
1326 
1327 static int delete_stale_component(
1328     IN struct nfs41_name_cache *cache,
1329     IN nfs41_session *session,
1330     IN const nfs41_abs_path *path,
1331     IN const nfs41_component *component)
1332 {
1333     struct name_cache_entry *target;
1334     int status;
1335 
1336     dprintf(NCLVL1, "--> delete_stale_component('%s')\n",
1337         component->name);
1338 
1339     AcquireSRWLockExclusive(&cache->lock);
1340 
1341     status = name_cache_lookup(cache, 0, path->path,
1342         component->name + component->len, NULL, NULL, &target, NULL);
1343     if (status == NO_ERROR)
1344         name_cache_unlink(cache, target);
1345 
1346     ReleaseSRWLockExclusive(&cache->lock);
1347 
1348     dprintf(NCLVL1, "<-- delete_stale_component() returning %d\n", status);
1349     return status;
1350 }
1351 
1352 static __inline uint32_t max_putfh_components(
1353     IN const nfs41_session *session)
1354 {
1355     const uint32_t comps = session->fore_chan_attrs.ca_maxoperations - 1;
1356     return min(comps, MAX_PUTFH_PER_COMPOUND);
1357 }
1358 
1359 int nfs41_name_cache_remove_stale(
1360     IN struct nfs41_name_cache *cache,
1361     IN nfs41_session *session,
1362     IN nfs41_abs_path *path)
1363 {
1364     nfs41_path_fh files[MAX_PUTFH_PER_COMPOUND];
1365     const char *path_pos = path->path;
1366     const char* const path_end = path->path + path->len;
1367     const uint32_t max_components = max_putfh_components(session);
1368     uint32_t count, index;
1369     int status = NO_ERROR;
1370 
1371     AcquireSRWLockShared(&cache->lock);
1372 
1373     /* if there's no cache, don't check any components */
1374     if (!name_cache_enabled(cache))
1375         path_pos = path_end;
1376 
1377     ReleaseSRWLockShared(&cache->lock);
1378 
1379     /* hold a lock on the path to protect against rename */
1380     AcquireSRWLockShared(&path->lock);
1381 
1382     while (get_path_fhs(cache, path, &path_pos, max_components, files, &count)) {
1383         status = rpc_array_putfh(session, files, count, &index);
1384 
1385         if (status == NFS4ERR_STALE || status == NFS4ERR_FHEXPIRED) {
1386             status = delete_stale_component(cache,
1387                 session, path, &files[index].name);
1388             break;
1389         }
1390         if (status) {
1391             status = nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
1392             break;
1393         }
1394     }
1395 
1396     ReleaseSRWLockShared(&path->lock);
1397 
1398     return status;
1399 }
1400