fix idiotic braino in d_alloc_parallel()
Check for d_unhashed() while searching in in-lookup hash was absolutely wrong. Worse, it masked a deadlock on dget() done under bitlock that nests inside ->d_lock. Thanks to J. R. Okajima for spotting it. Spotted-by: "J. R. Okajima" <hooanon05g@gmail.com> Wearing-brown-paperbag: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
parent
ea01a18494
commit
e7d6ef9790
17
fs/dcache.c
17
fs/dcache.c
|
@ -2503,7 +2503,6 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
goto retry;
|
goto retry;
|
||||||
}
|
}
|
||||||
rcu_read_unlock();
|
|
||||||
/*
|
/*
|
||||||
* No changes for the parent since the beginning of d_lookup().
|
* No changes for the parent since the beginning of d_lookup().
|
||||||
* Since all removals from the chain happen with hlist_bl_lock(),
|
* Since all removals from the chain happen with hlist_bl_lock(),
|
||||||
|
@ -2516,8 +2515,6 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
|
||||||
continue;
|
continue;
|
||||||
if (dentry->d_parent != parent)
|
if (dentry->d_parent != parent)
|
||||||
continue;
|
continue;
|
||||||
if (d_unhashed(dentry))
|
|
||||||
continue;
|
|
||||||
if (parent->d_flags & DCACHE_OP_COMPARE) {
|
if (parent->d_flags & DCACHE_OP_COMPARE) {
|
||||||
int tlen = dentry->d_name.len;
|
int tlen = dentry->d_name.len;
|
||||||
const char *tname = dentry->d_name.name;
|
const char *tname = dentry->d_name.name;
|
||||||
|
@ -2529,9 +2526,18 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
|
||||||
if (dentry_cmp(dentry, str, len))
|
if (dentry_cmp(dentry, str, len))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dget(dentry);
|
|
||||||
hlist_bl_unlock(b);
|
hlist_bl_unlock(b);
|
||||||
/* somebody is doing lookup for it right now; wait for it */
|
/* now we can try to grab a reference */
|
||||||
|
if (!lockref_get_not_dead(&dentry->d_lockref)) {
|
||||||
|
rcu_read_unlock();
|
||||||
|
goto retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
rcu_read_unlock();
|
||||||
|
/*
|
||||||
|
* somebody is likely to be still doing lookup for it;
|
||||||
|
* wait for them to finish
|
||||||
|
*/
|
||||||
spin_lock(&dentry->d_lock);
|
spin_lock(&dentry->d_lock);
|
||||||
d_wait_lookup(dentry);
|
d_wait_lookup(dentry);
|
||||||
/*
|
/*
|
||||||
|
@ -2562,6 +2568,7 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
|
||||||
dput(new);
|
dput(new);
|
||||||
return dentry;
|
return dentry;
|
||||||
}
|
}
|
||||||
|
rcu_read_unlock();
|
||||||
/* we can't take ->d_lock here; it's OK, though. */
|
/* we can't take ->d_lock here; it's OK, though. */
|
||||||
new->d_flags |= DCACHE_PAR_LOOKUP;
|
new->d_flags |= DCACHE_PAR_LOOKUP;
|
||||||
new->d_wait = wq;
|
new->d_wait = wq;
|
||||||
|
|
Loading…
Reference in New Issue