Re: [PATCH v3 08/19] VFS/xfs/ntfs: drop parent lock across d_alloc_parallel() in d_add_ci()

Amir Goldstein
2026-04-27T07:49:51Z

On Mon, Apr 27, 2026 at 6:07 AM NeilBrown <neilb@ownmail.net> wrote:
>
> From: NeilBrown <neil@brown.name>
>
> A proposed change will invert the lock ordering between
> d_alloc_parallel() and inode_lock() on the parent.
> When that happens it will not be safe to call d_alloc_parallel() while
> holding the parent lock - even shared.
>
> We don't need to keep the parent lock held when d_add_ci() is run - the
> VFS doesn't need it as dentry is exclusively held due to
> DCACHE_PAR_LOOKUP and the filesystem has finished its work.
>
> So drop and reclaim the lock (shared or exclusive as determined by
> LOOKUP_SHARED) to avoid future deadlock.
>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
> fs/dcache.c | 18 +++++++++++++++++-
> fs/ntfs/namei.c | 4 +++-
> fs/xfs/xfs_iops.c | 3 ++-
> include/linux/dcache.h | 3 ++-
> 4 files changed, 24 insertions(+), 4 deletions(-)
>
> diff --git a/fs/dcache.c b/fs/dcache.c
> index 569a8ddf4c0d..a2ddfe811df3 100644
> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -2294,6 +2294,7 @@ EXPORT_SYMBOL(d_obtain_root);
> * @dentry: the negative dentry that was passed to the parent's lookup func
> * @inode: the inode case-insensitive lookup has found
> * @name: the case-exact name to be associated with the returned dentry
> + * @bool: %true if lookup was performed with LOOKUP_SHARED

I do not understand the choice of API.
Seems better to pass the lookup flags and avoid exposing this very internal
logic in the call sites...

> *
> * This is to avoid filling the dcache with case-insensitive names to the
> * same inode, only the actual correct case is stored in the dcache for
> @@ -2306,7 +2307,7 @@ EXPORT_SYMBOL(d_obtain_root);
> * the exact case, and return the spliced entry.
> */
> struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
> - struct qstr *name)
> + struct qstr *name, bool shared)
> {
> struct dentry *found, *res;
>
> @@ -2319,6 +2320,17 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
> iput(inode);
> return found;
> }
> + /*
> + * We are holding parent lock and so don't want to wait for a
> + * d_in_lookup() dentry. We can safely drop the parent lock and
> + * reclaim it as we have exclusive access to dentry as it is
> + * d_in_lookup() (so ->d_parent is stable) and we are near the
> + * end ->lookup() and will shortly drop the lock anyway.
> + */
> + if (shared)
> + inode_unlock_shared(d_inode(dentry->d_parent));
> + else
> + inode_unlock(d_inode(dentry->d_parent));
> if (d_in_lookup(dentry)) {
> found = d_alloc_parallel(dentry->d_parent, name);
> if (IS_ERR(found) || !d_in_lookup(found)) {
> @@ -2332,6 +2344,10 @@ struct dentry *d_add_ci(struct dentry *dentry, struct inode *inode,
> return ERR_PTR(-ENOMEM);
> }
> }
> + if (shared)
> + inode_lock_shared(d_inode(dentry->d_parent));
> + else
> + inode_lock_nested(d_inode(dentry->d_parent), I_MUTEX_PARENT);
> res = d_splice_alias(inode, found);
> if (res) {
> d_lookup_done(found);
> diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
> index 10894de519c3..30dddef43300 100644
> --- a/fs/ntfs/namei.c
> +++ b/fs/ntfs/namei.c
> @@ -8,6 +8,7 @@
>
> #include <linux/exportfs.h>
> #include <linux/iversion.h>
> +#include <linux/namei.h> // for LOOKUP_SHARED

... this won't be needed

>
> #include "ntfs.h"
> #include "time.h"
> @@ -310,7 +311,8 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent,
> }
> nls_name.hash = full_name_hash(dent, nls_name.name, nls_name.len);
>
> - dent = d_add_ci(dent, dent_inode, &nls_name);
> + dent = d_add_ci(dent, dent_inode, &nls_name,
> + !!(flags & LOOKUP_SHARED));
> kfree(nls_name.name);
> return dent;
>
> diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
> index 325c2200c501..f03d832f1468 100644
> --- a/fs/xfs/xfs_iops.c
> +++ b/fs/xfs/xfs_iops.c
> @@ -35,6 +35,7 @@
> #include <linux/security.h>
> #include <linux/iversion.h>
> #include <linux/fiemap.h>
> +#include <linux/namei.h> // for LOOKUP_SHARED
>
> /*
> * Directories have different lock order w.r.t. mmap_lock compared to regular
> @@ -369,7 +370,7 @@ xfs_vn_ci_lookup(
> /* else case-insensitive match... */
> dname.name = ci_name.name;
> dname.len = ci_name.len;
> - dentry = d_add_ci(dentry, VFS_I(ip), &dname);
> + dentry = d_add_ci(dentry, VFS_I(ip), &dname, !!(flags & LOOKUP_SHARED));

... and this ugliness could be avoided.

Thanks,
Amir.