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>
This commit is contained in:
parent
946e51f2bf
commit
ca5358ef75
1 changed files with 16 additions and 15 deletions
31
fs/dcache.c
31
fs/dcache.c
|
@ -495,7 +495,7 @@ static void __dentry_kill(struct dentry *dentry)
|
||||||
}
|
}
|
||||||
/* if it was on the hash then remove it */
|
/* if it was on the hash then remove it */
|
||||||
__d_drop(dentry);
|
__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
|
* Inform d_walk() that we are no longer attached to the
|
||||||
* dentry tree
|
* dentry tree
|
||||||
|
@ -1081,33 +1081,31 @@ resume:
|
||||||
/*
|
/*
|
||||||
* All done at this level ... ascend and resume the search.
|
* All done at this level ... ascend and resume the search.
|
||||||
*/
|
*/
|
||||||
|
rcu_read_lock();
|
||||||
|
ascend:
|
||||||
if (this_parent != parent) {
|
if (this_parent != parent) {
|
||||||
struct dentry *child = this_parent;
|
struct dentry *child = this_parent;
|
||||||
this_parent = child->d_parent;
|
this_parent = child->d_parent;
|
||||||
|
|
||||||
rcu_read_lock();
|
|
||||||
spin_unlock(&child->d_lock);
|
spin_unlock(&child->d_lock);
|
||||||
spin_lock(&this_parent->d_lock);
|
spin_lock(&this_parent->d_lock);
|
||||||
|
|
||||||
/*
|
/* might go back up the wrong parent if we have had a rename. */
|
||||||
* might go back up the wrong parent if we have had a rename
|
if (need_seqretry(&rename_lock, seq))
|
||||||
* 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();
|
|
||||||
goto rename_retry;
|
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();
|
rcu_read_unlock();
|
||||||
next = child->d_child.next;
|
|
||||||
goto resume;
|
goto resume;
|
||||||
}
|
}
|
||||||
if (need_seqretry(&rename_lock, seq)) {
|
if (need_seqretry(&rename_lock, seq))
|
||||||
spin_unlock(&this_parent->d_lock);
|
|
||||||
goto rename_retry;
|
goto rename_retry;
|
||||||
}
|
rcu_read_unlock();
|
||||||
if (finish)
|
if (finish)
|
||||||
finish(data);
|
finish(data);
|
||||||
|
|
||||||
|
@ -1117,6 +1115,9 @@ out_unlock:
|
||||||
return;
|
return;
|
||||||
|
|
||||||
rename_retry:
|
rename_retry:
|
||||||
|
spin_unlock(&this_parent->d_lock);
|
||||||
|
rcu_read_unlock();
|
||||||
|
BUG_ON(seq & 1);
|
||||||
if (!retry)
|
if (!retry)
|
||||||
return;
|
return;
|
||||||
seq = 1;
|
seq = 1;
|
||||||
|
|
Loading…
Add table
Reference in a new issue