87 lines
2.3 KiB
Diff
87 lines
2.3 KiB
Diff
|
From: Al Viro <viro@zeniv.linux.org.uk>
|
||
|
Date: Sun, 26 Oct 2014 19:31:10 -0400
|
||
|
Subject: [PATCH] deal with deadlock in d_walk()
|
||
|
|
||
|
... 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 | 31 ++++++++++++++++---------------
|
||
|
1 file changed, 16 insertions(+), 15 deletions(-)
|
||
|
|
||
|
diff --git a/fs/dcache.c b/fs/dcache.c
|
||
|
index c3ea5b765f6a..71acf8d6f2be 100644
|
||
|
--- a/fs/dcache.c
|
||
|
+++ b/fs/dcache.c
|
||
|
@@ -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
|
||
|
@@ -1082,33 +1082,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);
|
||
|
|
||
|
@@ -1118,6 +1116,9 @@ out_unlock:
|
||
|
return;
|
||
|
|
||
|
rename_retry:
|
||
|
+ spin_unlock(&this_parent->d_lock);
|
||
|
+ rcu_read_unlock();
|
||
|
+ BUG_ON(seq & 1);
|
||
|
if (!retry)
|
||
|
return;
|
||
|
seq = 1;
|
||
|
--
|
||
|
2.1.0
|
||
|
|