[PATCH 04/11] VFS: use d_alloc_parallel() in lookup_one_qstr_excl()

NeilBrown
2024-12-20T03:09:07Z

lookup_one_qstr_excl() is used for lookups prior to directory
modifications, whether create, unlink, rename, or whatever.

To prepare for allowing modification to happen in parallel, change
lookup_one_qstr_excl() to use d_alloc_parallel().

If any for the "intent" LOOKUP flags are passed, the caller must ensure
d_lookup_done() is called at an appropriate time. If none are passed
then we can be sure ->lookup() will do a real lookup and d_lookup_done()
is called internally.

Signed-off-by: NeilBrown <neilb@suse.de>
---
fs/namei.c | 21 ++++++++++++++-------
fs/smb/server/vfs.c | 1 +
include/linux/namei.h | 3 +++
3 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/fs/namei.c b/fs/namei.c
index 174e6693304e..395bfbc8fc92 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1664,11 +1664,9 @@ static struct dentry *lookup_dcache(const struct qstr *name,
}

/*
- * Parent directory has inode locked exclusive. This is one
- * and only case when ->lookup() gets called on non in-lookup
- * dentries - as the matter of fact, this only gets called
- * when directory is guaranteed to have no in-lookup children
- * at all.
+ * Parent directory has inode locked exclusive.
+ * If @flags contains any LOOKUP_INTENT_FLAGS then d_lookup_done()
+ * must be called after the intended operation is performed - or aborted.
*/
struct dentry *lookup_one_qstr_excl(const struct qstr *name,
struct dentry *base,
@@ -1685,15 +1683,22 @@ struct dentry *lookup_one_qstr_excl(const struct qstr *name,
if (unlikely(IS_DEADDIR(dir)))
return ERR_PTR(-ENOENT);

- dentry = d_alloc(base, name);
- if (unlikely(!dentry))
+ dentry = d_alloc_parallel(base, name);
+ if (unlikely(IS_ERR_OR_NULL(dentry)))
return ERR_PTR(-ENOMEM);
+ if (!d_in_lookup(dentry))
+ /* Raced with another thread which did the lookup */
+ return dentry;

old = dir->i_op->lookup(dir, dentry, flags);
if (unlikely(old)) {
+ d_lookup_done(dentry);
dput(dentry);
dentry = old;
}
+ if ((flags & LOOKUP_INTENT_FLAGS) == 0)
+ /* ->lookup must have given final answer */
+ d_lookup_done(dentry);
return dentry;
}
EXPORT_SYMBOL(lookup_one_qstr_excl);
@@ -4112,6 +4117,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
}
return dentry;
fail:
+ d_lookup_done(dentry);
dput(dentry);
dentry = ERR_PTR(error);
unlock:
@@ -5340,6 +5346,7 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
rd.flags = flags;
error = vfs_rename(&rd);
exit5:
+ d_lookup_done(new_dentry);
dput(new_dentry);
exit4:
dput(old_dentry);
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index dfb0eee5f5f3..83131f08bfb4 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -772,6 +772,7 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
ksmbd_debug(VFS, "vfs_rename failed err %d\n", err);

out4:
+ d_lookup_done(new_dentry);
dput(new_dentry);
out3:
dput(old_parent);
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 8ec8fed3bce8..15118992f745 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -34,6 +34,9 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT};
#define LOOKUP_EXCL 0x0400 /* ... in exclusive creation */
#define LOOKUP_RENAME_TARGET 0x0800 /* ... in destination of rename() */

+#define LOOKUP_INTENT_FLAGS (LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_EXCL | \
+ LOOKUP_RENAME_TARGET)
+
/* internal use only */
#define LOOKUP_PARENT 0x0010

--
2.47.0