aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfs
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2013-02-22 12:53:43 -0500
committerTrond Myklebust <Trond.Myklebust@netapp.com>2013-02-22 14:55:34 -0500
commit5a7a613a47a715711b3f2d3322a0eac21d459166 (patch)
treedbc096fb09319a2f04dc5a1c0ae65cdcce0ed135 /fs/nfs
parent666b3d803a511fbc9bc5e5ea8ce66010cf03ea13 (diff)
NFS: Don't allow NFS silly-renamed files to be deleted, no signal
Commit 73ca100 broke the code that prevents the client from deleting a silly renamed dentry. This affected "delete on last close" semantics as after that commit, nothing prevented removal of silly-renamed files. As a result, a process holding a file open could easily get an ESTALE on the file in a directory where some other process issued 'rm -rf some_dir_containing_the_file' twice. Before the commit, any attempt at unlinking silly renamed files would fail inside may_delete() with -EBUSY because of the DCACHE_NFSFS_RENAMED flag. The following testcase demonstrates the problem: tail -f /nfsmnt/dir/file & rm -rf /nfsmnt/dir rm -rf /nfsmnt/dir # second removal does not fail, 'tail' process receives ESTALE The problem with the above commit is that it unhashes the old and new dentries from the lookup path, even in the normal case when a signal is not encountered and it would have been safe to call d_move. Unfortunately the old dentry has the special DCACHE_NFSFS_RENAMED flag set on it. Unhashing has the side-effect that future lookups call d_alloc(), allocating a new dentry without the special flag for any silly-renamed files. As a result, subsequent calls to unlink silly renamed files do not fail but allow the removal to go through. This will result in ESTALE errors for any other process doing operations on the file. To fix this, go back to using d_move on success. For the signal case, it's unclear what we may safely do beyond d_drop. Reported-by: Dave Wysochanski <dwysocha@redhat.com> Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com> Acked-by: Jeff Layton <jlayton@redhat.com> Cc: stable@vger.kernel.org
Diffstat (limited to 'fs/nfs')
-rw-r--r--fs/nfs/unlink.c20
1 files changed, 13 insertions, 7 deletions
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index d26a32f5b53b..1f1f38f0c5d5 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -335,20 +335,14 @@ static void nfs_async_rename_done(struct rpc_task *task, void *calldata)
335 struct inode *old_dir = data->old_dir; 335 struct inode *old_dir = data->old_dir;
336 struct inode *new_dir = data->new_dir; 336 struct inode *new_dir = data->new_dir;
337 struct dentry *old_dentry = data->old_dentry; 337 struct dentry *old_dentry = data->old_dentry;
338 struct dentry *new_dentry = data->new_dentry;
339 338
340 if (!NFS_PROTO(old_dir)->rename_done(task, old_dir, new_dir)) { 339 if (!NFS_PROTO(old_dir)->rename_done(task, old_dir, new_dir)) {
341 rpc_restart_call_prepare(task); 340 rpc_restart_call_prepare(task);
342 return; 341 return;
343 } 342 }
344 343
345 if (task->tk_status != 0) { 344 if (task->tk_status != 0)
346 nfs_cancel_async_unlink(old_dentry); 345 nfs_cancel_async_unlink(old_dentry);
347 return;
348 }
349
350 d_drop(old_dentry);
351 d_drop(new_dentry);
352} 346}
353 347
354/** 348/**
@@ -549,6 +543,18 @@ nfs_sillyrename(struct inode *dir, struct dentry *dentry)
549 error = rpc_wait_for_completion_task(task); 543 error = rpc_wait_for_completion_task(task);
550 if (error == 0) 544 if (error == 0)
551 error = task->tk_status; 545 error = task->tk_status;
546 switch (error) {
547 case 0:
548 /* The rename succeeded */
549 nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
550 d_move(dentry, sdentry);
551 break;
552 case -ERESTARTSYS:
553 /* The result of the rename is unknown. Play it safe by
554 * forcing a new lookup */
555 d_drop(dentry);
556 d_drop(sdentry);
557 }
552 rpc_put_task(task); 558 rpc_put_task(task);
553out_dput: 559out_dput:
554 dput(sdentry); 560 dput(sdentry);