Commit a91da0b3 authored by Al Viro's avatar Al Viro Committed by Zefan Li

deal with deadlock in d_walk()

commit ca5358ef upstream.

... 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: default avatarAl Viro <viro@zeniv.linux.org.uk>
[bwh: Backported to 3.2:
 - As we only have try_to_ascend() and not d_walk(), apply this
   change to all callers of try_to_ascend()
 - Adjust context to make __dentry_kill() apply to d_kill()]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
[lizf: Backported to 3.4: fold the fix 2d5a2e67 in 3.2.y into this patch]
Signed-off-by: default avatarZefan Li <lizefan@huawei.com>
parent 6fd17def
...@@ -368,9 +368,9 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent) ...@@ -368,9 +368,9 @@ static struct dentry *d_kill(struct dentry *dentry, struct dentry *parent)
__releases(parent->d_lock) __releases(parent->d_lock)
__releases(dentry->d_inode->i_lock) __releases(dentry->d_inode->i_lock)
{ {
list_del(&dentry->d_child); __list_del_entry(&dentry->d_child);
/* /*
* Inform try_to_ascend() that we are no longer attached to the * Inform ascending readers that we are no longer attached to the
* dentry tree * dentry tree
*/ */
dentry->d_flags |= DCACHE_DENTRY_KILLED; dentry->d_flags |= DCACHE_DENTRY_KILLED;
...@@ -1011,34 +1011,6 @@ void shrink_dcache_for_umount(struct super_block *sb) ...@@ -1011,34 +1011,6 @@ void shrink_dcache_for_umount(struct super_block *sb)
} }
} }
/*
* This tries to ascend one level of parenthood, but
* we can race with renaming, so we need to re-check
* the parenthood after dropping the lock and check
* that the sequence number still matches.
*/
static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq)
{
struct dentry *new = old->d_parent;
rcu_read_lock();
spin_unlock(&old->d_lock);
spin_lock(&new->d_lock);
/*
* might go back up the wrong parent if we have had a rename
* or deletion
*/
if (new != old->d_parent ||
(old->d_flags & DCACHE_DENTRY_KILLED) ||
(!locked && read_seqretry(&rename_lock, seq))) {
spin_unlock(&new->d_lock);
new = NULL;
}
rcu_read_unlock();
return new;
}
/* /*
* Search for at least 1 mount point in the dentry's subdirs. * Search for at least 1 mount point in the dentry's subdirs.
...@@ -1094,30 +1066,48 @@ int have_submounts(struct dentry *parent) ...@@ -1094,30 +1066,48 @@ int have_submounts(struct dentry *parent)
/* /*
* 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 = try_to_ascend(this_parent, locked, seq); this_parent = child->d_parent;
if (!this_parent)
spin_unlock(&child->d_lock);
spin_lock(&this_parent->d_lock);
/* might go back up the wrong parent if we have had a rename */
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
next = child->d_child.next; 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();
goto resume; goto resume;
} }
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq)) if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (locked) if (locked)
write_sequnlock(&rename_lock); write_sequnlock(&rename_lock);
return 0; /* No mount points found in tree */ return 0; /* No mount points found in tree */
positive: positive:
if (!locked && read_seqretry(&rename_lock, seq)) if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry_unlocked;
if (locked) if (locked)
write_sequnlock(&rename_lock); write_sequnlock(&rename_lock);
return 1; return 1;
rename_retry: rename_retry:
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (locked) if (locked)
goto again; goto again;
rename_retry_unlocked:
locked = 1; locked = 1;
write_seqlock(&rename_lock); write_seqlock(&rename_lock);
goto again; goto again;
...@@ -1182,6 +1172,7 @@ static int select_parent(struct dentry *parent, struct list_head *dispose) ...@@ -1182,6 +1172,7 @@ static int select_parent(struct dentry *parent, struct list_head *dispose)
*/ */
if (found && need_resched()) { if (found && need_resched()) {
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
rcu_read_lock();
goto out; goto out;
} }
...@@ -1201,23 +1192,40 @@ static int select_parent(struct dentry *parent, struct list_head *dispose) ...@@ -1201,23 +1192,40 @@ static int select_parent(struct dentry *parent, struct list_head *dispose)
/* /*
* 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 = try_to_ascend(this_parent, locked, seq); this_parent = child->d_parent;
if (!this_parent)
spin_unlock(&child->d_lock);
spin_lock(&this_parent->d_lock);
/* might go back up the wrong parent if we have had a rename */
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
next = child->d_child.next; 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();
goto resume; goto resume;
} }
out: out:
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq)) if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (locked) if (locked)
write_sequnlock(&rename_lock); write_sequnlock(&rename_lock);
return found; return found;
rename_retry: rename_retry:
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (found) if (found)
return found; return found;
if (locked) if (locked)
...@@ -2955,26 +2963,43 @@ void d_genocide(struct dentry *root) ...@@ -2955,26 +2963,43 @@ void d_genocide(struct dentry *root)
} }
spin_unlock(&dentry->d_lock); spin_unlock(&dentry->d_lock);
} }
rcu_read_lock();
ascend:
if (this_parent != root) { if (this_parent != root) {
struct dentry *child = this_parent; struct dentry *child = this_parent;
if (!(this_parent->d_flags & DCACHE_GENOCIDE)) { if (!(this_parent->d_flags & DCACHE_GENOCIDE)) {
this_parent->d_flags |= DCACHE_GENOCIDE; this_parent->d_flags |= DCACHE_GENOCIDE;
this_parent->d_count--; this_parent->d_count--;
} }
this_parent = try_to_ascend(this_parent, locked, seq); this_parent = child->d_parent;
if (!this_parent)
spin_unlock(&child->d_lock);
spin_lock(&this_parent->d_lock);
/* might go back up the wrong parent if we have had a rename */
if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
next = child->d_child.next; 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();
goto resume; goto resume;
} }
spin_unlock(&this_parent->d_lock);
if (!locked && read_seqretry(&rename_lock, seq)) if (!locked && read_seqretry(&rename_lock, seq))
goto rename_retry; goto rename_retry;
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (locked) if (locked)
write_sequnlock(&rename_lock); write_sequnlock(&rename_lock);
return; return;
rename_retry: rename_retry:
spin_unlock(&this_parent->d_lock);
rcu_read_unlock();
if (locked) if (locked)
goto again; goto again;
locked = 1; locked = 1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment