deal with deadlock in d_walk()
authorAl Viro <viro@zeniv.linux.org.uk>
Sun, 26 Oct 2014 23:31:10 +0000 (19:31 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Mon, 3 Nov 2014 20:22:16 +0000 (15:22 -0500)
... by not hitting rename_retry for reasons other than rename having
happened.  In other words, do _not_ restart when finding that
between unlocking the child and locking the parent the former got
into __dentry_kill().  Skip the killed siblings instead...

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/dcache.c

index 8b4c45e408343fade33350e4a1c6987fb21b1a0d..e90aa825cc03a6180c31a4350fd3b322695e6234 100644 (file)
@@ -495,7 +495,7 @@ static void __dentry_kill(struct dentry *dentry)
        }
        /* if it was on the hash then remove it */
        __d_drop(dentry);
-       list_del(&dentry->d_child);
+       __list_del_entry(&dentry->d_child);
        /*
         * Inform d_walk() that we are no longer attached to the
         * dentry tree
@@ -1081,33 +1081,31 @@ resume:
        /*
         * All done at this level ... ascend and resume the search.
         */
+       rcu_read_lock();
+ascend:
        if (this_parent != parent) {
                struct dentry *child = this_parent;
                this_parent = child->d_parent;
 
-               rcu_read_lock();
                spin_unlock(&child->d_lock);
                spin_lock(&this_parent->d_lock);
 
-               /*
-                * might go back up the wrong parent if we have had a rename
-                * or deletion
-                */
-               if (this_parent != child->d_parent ||
-                        (child->d_flags & DCACHE_DENTRY_KILLED) ||
-                        need_seqretry(&rename_lock, seq)) {
-                       spin_unlock(&this_parent->d_lock);
-                       rcu_read_unlock();
+               /* might go back up the wrong parent if we have had a rename. */
+               if (need_seqretry(&rename_lock, seq))
                        goto rename_retry;
+               next = child->d_child.next;
+               while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED)) {
+                       if (next == &this_parent->d_subdirs)
+                               goto ascend;
+                       child = list_entry(next, struct dentry, d_child);
+                       next = next->next;
                }
                rcu_read_unlock();
-               next = child->d_child.next;
                goto resume;
        }
-       if (need_seqretry(&rename_lock, seq)) {
-               spin_unlock(&this_parent->d_lock);
+       if (need_seqretry(&rename_lock, seq))
                goto rename_retry;
-       }
+       rcu_read_unlock();
        if (finish)
                finish(data);
 
@@ -1117,6 +1115,9 @@ out_unlock:
        return;
 
 rename_retry:
+       spin_unlock(&this_parent->d_lock);
+       rcu_read_unlock();
+       BUG_ON(seq & 1);
        if (!retry)
                return;
        seq = 1;