Add .gitattributes and .gitignore files and normalize line endings in the repository...
[reactos.git] / base / services / nfsd / name_cache.c
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 }