diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-07-30 12:45:35 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2011-07-30 14:34:50 -0400 |
commit | 0c0308066ca53fdf1423895f3a42838b67b3a5a8 (patch) | |
tree | 4c6f0d852456581a7ca17286601d18d7bb030632 | |
parent | ed1e6211a0a134ff23592c6f057af982ad5dab52 (diff) |
NFS: Fix spurious readdir cookie loop messages
If the directory contents change, then we have to accept that the
file->f_pos value may shrink if we do a 'search-by-cookie'. In that
case, we should turn off the loop detection and let the NFS client
try to recover.
The patch also fixes a second loop detection bug by ensuring
that after turning on the ctx->duped flag, we read at least one new
cookie into ctx->dir_cookie before attempting to match with
ctx->dup_cookie.
Reported-by: Petr Vandrovec <petr@vandrovec.name>
Cc: stable@kernel.org [2.6.39+]
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
-rw-r--r-- | fs/nfs/dir.c | 56 | ||||
-rw-r--r-- | include/linux/nfs_fs.h | 3 |
2 files changed, 35 insertions, 24 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 57f578e2560a..d23108b1e338 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c | |||
@@ -134,18 +134,19 @@ const struct inode_operations nfs4_dir_inode_operations = { | |||
134 | 134 | ||
135 | #endif /* CONFIG_NFS_V4 */ | 135 | #endif /* CONFIG_NFS_V4 */ |
136 | 136 | ||
137 | static struct nfs_open_dir_context *alloc_nfs_open_dir_context(struct rpc_cred *cred) | 137 | static struct nfs_open_dir_context *alloc_nfs_open_dir_context(struct inode *dir, struct rpc_cred *cred) |
138 | { | 138 | { |
139 | struct nfs_open_dir_context *ctx; | 139 | struct nfs_open_dir_context *ctx; |
140 | ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); | 140 | ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); |
141 | if (ctx != NULL) { | 141 | if (ctx != NULL) { |
142 | ctx->duped = 0; | 142 | ctx->duped = 0; |
143 | ctx->attr_gencount = NFS_I(dir)->attr_gencount; | ||
143 | ctx->dir_cookie = 0; | 144 | ctx->dir_cookie = 0; |
144 | ctx->dup_cookie = 0; | 145 | ctx->dup_cookie = 0; |
145 | ctx->cred = get_rpccred(cred); | 146 | ctx->cred = get_rpccred(cred); |
146 | } else | 147 | return ctx; |
147 | ctx = ERR_PTR(-ENOMEM); | 148 | } |
148 | return ctx; | 149 | return ERR_PTR(-ENOMEM); |
149 | } | 150 | } |
150 | 151 | ||
151 | static void put_nfs_open_dir_context(struct nfs_open_dir_context *ctx) | 152 | static void put_nfs_open_dir_context(struct nfs_open_dir_context *ctx) |
@@ -173,7 +174,7 @@ nfs_opendir(struct inode *inode, struct file *filp) | |||
173 | cred = rpc_lookup_cred(); | 174 | cred = rpc_lookup_cred(); |
174 | if (IS_ERR(cred)) | 175 | if (IS_ERR(cred)) |
175 | return PTR_ERR(cred); | 176 | return PTR_ERR(cred); |
176 | ctx = alloc_nfs_open_dir_context(cred); | 177 | ctx = alloc_nfs_open_dir_context(inode, cred); |
177 | if (IS_ERR(ctx)) { | 178 | if (IS_ERR(ctx)) { |
178 | res = PTR_ERR(ctx); | 179 | res = PTR_ERR(ctx); |
179 | goto out; | 180 | goto out; |
@@ -323,7 +324,6 @@ int nfs_readdir_search_for_pos(struct nfs_cache_array *array, nfs_readdir_descri | |||
323 | { | 324 | { |
324 | loff_t diff = desc->file->f_pos - desc->current_index; | 325 | loff_t diff = desc->file->f_pos - desc->current_index; |
325 | unsigned int index; | 326 | unsigned int index; |
326 | struct nfs_open_dir_context *ctx = desc->file->private_data; | ||
327 | 327 | ||
328 | if (diff < 0) | 328 | if (diff < 0) |
329 | goto out_eof; | 329 | goto out_eof; |
@@ -336,7 +336,6 @@ int nfs_readdir_search_for_pos(struct nfs_cache_array *array, nfs_readdir_descri | |||
336 | index = (unsigned int)diff; | 336 | index = (unsigned int)diff; |
337 | *desc->dir_cookie = array->array[index].cookie; | 337 | *desc->dir_cookie = array->array[index].cookie; |
338 | desc->cache_entry_index = index; | 338 | desc->cache_entry_index = index; |
339 | ctx->duped = 0; | ||
340 | return 0; | 339 | return 0; |
341 | out_eof: | 340 | out_eof: |
342 | desc->eof = 1; | 341 | desc->eof = 1; |
@@ -349,14 +348,33 @@ int nfs_readdir_search_for_cookie(struct nfs_cache_array *array, nfs_readdir_des | |||
349 | int i; | 348 | int i; |
350 | loff_t new_pos; | 349 | loff_t new_pos; |
351 | int status = -EAGAIN; | 350 | int status = -EAGAIN; |
352 | struct nfs_open_dir_context *ctx = desc->file->private_data; | ||
353 | 351 | ||
354 | for (i = 0; i < array->size; i++) { | 352 | for (i = 0; i < array->size; i++) { |
355 | if (array->array[i].cookie == *desc->dir_cookie) { | 353 | if (array->array[i].cookie == *desc->dir_cookie) { |
354 | struct nfs_inode *nfsi = NFS_I(desc->file->f_path.dentry->d_inode); | ||
355 | struct nfs_open_dir_context *ctx = desc->file->private_data; | ||
356 | |||
356 | new_pos = desc->current_index + i; | 357 | new_pos = desc->current_index + i; |
357 | if (new_pos < desc->file->f_pos) { | 358 | if (ctx->attr_gencount != nfsi->attr_gencount |
359 | || (nfsi->cache_validity & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA))) { | ||
360 | ctx->duped = 0; | ||
361 | ctx->attr_gencount = nfsi->attr_gencount; | ||
362 | } else if (new_pos < desc->file->f_pos) { | ||
363 | if (ctx->duped > 0 | ||
364 | && ctx->dup_cookie == *desc->dir_cookie) { | ||
365 | if (printk_ratelimit()) { | ||
366 | pr_notice("NFS: directory %s/%s contains a readdir loop." | ||
367 | "Please contact your server vendor. " | ||
368 | "Offending cookie: %llu\n", | ||
369 | desc->file->f_dentry->d_parent->d_name.name, | ||
370 | desc->file->f_dentry->d_name.name, | ||
371 | *desc->dir_cookie); | ||
372 | } | ||
373 | status = -ELOOP; | ||
374 | goto out; | ||
375 | } | ||
358 | ctx->dup_cookie = *desc->dir_cookie; | 376 | ctx->dup_cookie = *desc->dir_cookie; |
359 | ctx->duped = 1; | 377 | ctx->duped = -1; |
360 | } | 378 | } |
361 | desc->file->f_pos = new_pos; | 379 | desc->file->f_pos = new_pos; |
362 | desc->cache_entry_index = i; | 380 | desc->cache_entry_index = i; |
@@ -368,6 +386,7 @@ int nfs_readdir_search_for_cookie(struct nfs_cache_array *array, nfs_readdir_des | |||
368 | if (*desc->dir_cookie == array->last_cookie) | 386 | if (*desc->dir_cookie == array->last_cookie) |
369 | desc->eof = 1; | 387 | desc->eof = 1; |
370 | } | 388 | } |
389 | out: | ||
371 | return status; | 390 | return status; |
372 | } | 391 | } |
373 | 392 | ||
@@ -740,19 +759,6 @@ int nfs_do_filldir(nfs_readdir_descriptor_t *desc, void *dirent, | |||
740 | struct nfs_cache_array *array = NULL; | 759 | struct nfs_cache_array *array = NULL; |
741 | struct nfs_open_dir_context *ctx = file->private_data; | 760 | struct nfs_open_dir_context *ctx = file->private_data; |
742 | 761 | ||
743 | if (ctx->duped != 0 && ctx->dup_cookie == *desc->dir_cookie) { | ||
744 | if (printk_ratelimit()) { | ||
745 | pr_notice("NFS: directory %s/%s contains a readdir loop. " | ||
746 | "Please contact your server vendor. " | ||
747 | "Offending cookie: %llu\n", | ||
748 | file->f_dentry->d_parent->d_name.name, | ||
749 | file->f_dentry->d_name.name, | ||
750 | *desc->dir_cookie); | ||
751 | } | ||
752 | res = -ELOOP; | ||
753 | goto out; | ||
754 | } | ||
755 | |||
756 | array = nfs_readdir_get_array(desc->page); | 762 | array = nfs_readdir_get_array(desc->page); |
757 | if (IS_ERR(array)) { | 763 | if (IS_ERR(array)) { |
758 | res = PTR_ERR(array); | 764 | res = PTR_ERR(array); |
@@ -774,6 +780,8 @@ int nfs_do_filldir(nfs_readdir_descriptor_t *desc, void *dirent, | |||
774 | *desc->dir_cookie = array->array[i+1].cookie; | 780 | *desc->dir_cookie = array->array[i+1].cookie; |
775 | else | 781 | else |
776 | *desc->dir_cookie = array->last_cookie; | 782 | *desc->dir_cookie = array->last_cookie; |
783 | if (ctx->duped != 0) | ||
784 | ctx->duped = 1; | ||
777 | } | 785 | } |
778 | if (array->eof_index >= 0) | 786 | if (array->eof_index >= 0) |
779 | desc->eof = 1; | 787 | desc->eof = 1; |
@@ -805,6 +813,7 @@ int uncached_readdir(nfs_readdir_descriptor_t *desc, void *dirent, | |||
805 | struct page *page = NULL; | 813 | struct page *page = NULL; |
806 | int status; | 814 | int status; |
807 | struct inode *inode = desc->file->f_path.dentry->d_inode; | 815 | struct inode *inode = desc->file->f_path.dentry->d_inode; |
816 | struct nfs_open_dir_context *ctx = desc->file->private_data; | ||
808 | 817 | ||
809 | dfprintk(DIRCACHE, "NFS: uncached_readdir() searching for cookie %Lu\n", | 818 | dfprintk(DIRCACHE, "NFS: uncached_readdir() searching for cookie %Lu\n", |
810 | (unsigned long long)*desc->dir_cookie); | 819 | (unsigned long long)*desc->dir_cookie); |
@@ -818,6 +827,7 @@ int uncached_readdir(nfs_readdir_descriptor_t *desc, void *dirent, | |||
818 | desc->page_index = 0; | 827 | desc->page_index = 0; |
819 | desc->last_cookie = *desc->dir_cookie; | 828 | desc->last_cookie = *desc->dir_cookie; |
820 | desc->page = page; | 829 | desc->page = page; |
830 | ctx->duped = 0; | ||
821 | 831 | ||
822 | status = nfs_readdir_xdr_to_array(desc, page, inode); | 832 | status = nfs_readdir_xdr_to_array(desc, page, inode); |
823 | if (status < 0) | 833 | if (status < 0) |
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 8b579beb6358..b96fb99072ff 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h | |||
@@ -99,9 +99,10 @@ struct nfs_open_context { | |||
99 | 99 | ||
100 | struct nfs_open_dir_context { | 100 | struct nfs_open_dir_context { |
101 | struct rpc_cred *cred; | 101 | struct rpc_cred *cred; |
102 | unsigned long attr_gencount; | ||
102 | __u64 dir_cookie; | 103 | __u64 dir_cookie; |
103 | __u64 dup_cookie; | 104 | __u64 dup_cookie; |
104 | int duped; | 105 | signed char duped; |
105 | }; | 106 | }; |
106 | 107 | ||
107 | /* | 108 | /* |