diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2016-06-20 08:35:59 +0300 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2016-06-20 17:07:42 +0300 |
commit | e7d6ef9790bc281f5c29d0132b68031248523fe8 (patch) | |
tree | 28ce8447fcc8269544d576de196df8efa335abd2 | |
parent | ea01a18494b3d7a91b2f1f2a6a5aaef4741bc294 (diff) | |
download | linux-e7d6ef9790bc281f5c29d0132b68031248523fe8.tar.xz |
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>
-rw-r--r-- | fs/dcache.c | 17 |
1 files changed, 12 insertions, 5 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index b7eddfd35aa5..d6847d7b123d 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -2503,7 +2503,6 @@ retry: rcu_read_unlock(); goto retry; } - rcu_read_unlock(); /* * No changes for the parent since the beginning of d_lookup(). * Since all removals from the chain happen with hlist_bl_lock(), @@ -2516,8 +2515,6 @@ retry: continue; if (dentry->d_parent != parent) continue; - if (d_unhashed(dentry)) - continue; if (parent->d_flags & DCACHE_OP_COMPARE) { int tlen = dentry->d_name.len; const char *tname = dentry->d_name.name; @@ -2529,9 +2526,18 @@ retry: if (dentry_cmp(dentry, str, len)) continue; } - dget(dentry); 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); d_wait_lookup(dentry); /* @@ -2562,6 +2568,7 @@ retry: dput(new); return dentry; } + rcu_read_unlock(); /* we can't take ->d_lock here; it's OK, though. */ new->d_flags |= DCACHE_PAR_LOOKUP; new->d_wait = wq; |