VFS: Fix the nfs sillyrename regression in kernel 2.6.38
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Tue, 15 Mar 2011 17:36:43 +0000 (13:36 -0400)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 15 Mar 2011 22:46:11 +0000 (15:46 -0700)
The new vfs locking scheme introduced in 2.6.38 breaks NFS sillyrename
because the latter relies on being able to determine the parent
directory of the dentry in the ->iput() callback in order to send the
appropriate unlink rpc call.

Looking at the code that cares about races with dput(), there doesn't
seem to be anything that specifically uses d_parent as a test for
whether or not there is a race:
  - __d_lookup_rcu(), __d_lookup() all test for d_hashed() after d_parent
  - shrink_dcache_for_umount() is safe since nothing else can rearrange
    the dentries in that super block.
  - have_submount(), select_parent() and d_genocide() can test for a
    deletion if we set the DCACHE_DISCONNECTED flag when the dentry
    is removed from the parent's d_subdirs list.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Cc: stable@kernel.org (2.6.38, needs commit c826cb7dfce8 "dcache.c:
create helper function for duplicated functionality" )
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/dcache.c

index 361882a14ccbce8885ca2759c6d9d2db2b419804..a39fe47c466f794cb4f794ccd2863f2e165fc03e 100644 (file)
@@ -296,8 +296,12 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)
        __releases(parent->d_lock)
        __releases(dentry->d_inode->i_lock)
 {
-       dentry->d_parent = NULL;
        list_del(&dentry->d_u.d_child);
+       /*
+        * Inform try_to_ascend() that we are no longer attached to the
+        * dentry tree
+        */
+       dentry->d_flags |= DCACHE_DISCONNECTED;
        if (parent)
                spin_unlock(&parent->d_lock);
        dentry_iput(dentry);
@@ -1030,6 +1034,7 @@ static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq
         * or deletion
         */
        if (new != old->d_parent ||
+                (old->d_flags & DCACHE_DISCONNECTED) ||
                 (!locked && read_seqretry(&rename_lock, seq))) {
                spin_unlock(&new->d_lock);
                new = NULL;