]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
d_alloc_parallel(): set DCACHE_PAR_LOOKUP earlier
authorAl Viro <viro@zeniv.linux.org.uk>
Mon, 20 Oct 2025 17:28:58 +0000 (13:28 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 23 Oct 2025 14:20:43 +0000 (16:20 +0200)
[ Upstream commit e95db51c81f54dd12ea465b5127e4786f62a1095 ]

Do that before new dentry is visible anywhere.  It does create
a new possible state for dentries present in ->d_children/->d_sib -
DCACHE_PAR_LOOKUP present, negative, unhashed, not in in-lookup
hash chains, refcount positive.  Those are going to be skipped
by all tree-walkers (both d_walk() callbacks in fs/dcache.c and
explicit loops over children/sibling lists elsewhere) and
dput() is fine with those.

NOTE: dropping the final reference to a "normal" in-lookup dentry
(in in-lookup hash) is a bug - somebody must've forgotten to
call d_lookup_done() on it and bad things will happen.  With those
it's OK; if/when we get around to making __dentry_kill() complain
about such breakage, remember that predicate to check should
*not* be just d_in_lookup(victim) but rather a combination of that
with !hlist_bl_unhashed(&victim->d_u.d_in_lookup_hash).  Might
be worth considering later...

Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Stable-dep-of: 56094ad3eaa2 ("vfs: Don't leak disconnected dentries on umount")
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/dcache.c

index 0f6b16ba30d082b7613b084a23f6a01ed6436b1f..d81765352cf819024b51a1c7cf2c63b438db10e3 100644 (file)
@@ -2475,13 +2475,19 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
        unsigned int hash = name->hash;
        struct hlist_bl_head *b = in_lookup_hash(parent, hash);
        struct hlist_bl_node *node;
-       struct dentry *new = d_alloc(parent, name);
+       struct dentry *new = __d_alloc(parent->d_sb, name);
        struct dentry *dentry;
        unsigned seq, r_seq, d_seq;
 
        if (unlikely(!new))
                return ERR_PTR(-ENOMEM);
 
+       new->d_flags |= DCACHE_PAR_LOOKUP;
+       spin_lock(&parent->d_lock);
+       new->d_parent = dget_dlock(parent);
+       hlist_add_head(&new->d_sib, &parent->d_children);
+       spin_unlock(&parent->d_lock);
+
 retry:
        rcu_read_lock();
        seq = smp_load_acquire(&parent->d_inode->i_dir_seq);
@@ -2565,8 +2571,6 @@ retry:
                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;
        hlist_bl_add_head(&new->d_u.d_in_lookup_hash, b);
        hlist_bl_unlock(b);