Creating a new file for shared code between vfs_inode.c and vfs_inode_dotl.c. Same change for non-.L will be added in a following commit. Signed-off-by: Tingmao Wang <m@xxxxxxxxxx> --- fs/9p/Makefile | 3 +- fs/9p/ino_path.c | 114 +++++++++++++++++++++++++++++++++++++++++ fs/9p/v9fs.h | 54 +++++++++++++------ fs/9p/vfs_inode.c | 20 ++++++-- fs/9p/vfs_inode_dotl.c | 91 +++++++++++++++++++++++++++++--- fs/9p/vfs_super.c | 10 +++- 6 files changed, 262 insertions(+), 30 deletions(-) create mode 100644 fs/9p/ino_path.c diff --git a/fs/9p/Makefile b/fs/9p/Makefile index e7800a5c7395..38c3ceb26274 100644 --- a/fs/9p/Makefile +++ b/fs/9p/Makefile @@ -11,7 +11,8 @@ obj-$(CONFIG_9P_FS) := 9p.o vfs_dentry.o \ v9fs.o \ fid.o \ - xattr.o + xattr.o \ + ino_path.o 9p-$(CONFIG_9P_FSCACHE) += cache.o 9p-$(CONFIG_9P_FS_POSIX_ACL) += acl.o diff --git a/fs/9p/ino_path.c b/fs/9p/ino_path.c new file mode 100644 index 000000000000..a4e0aef81618 --- /dev/null +++ b/fs/9p/ino_path.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Specific operations on the v9fs_ino_path structure. + * + * Copyright (C) 2025 by Tingmao Wang <m@xxxxxxxxxx> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/dcache.h> + +#include <linux/posix_acl.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> +#include "v9fs.h" + +/* + * Must hold rename_sem due to traversing parents + */ +struct v9fs_ino_path *make_ino_path(struct dentry *dentry) +{ + struct v9fs_ino_path *path; + size_t path_components = 0; + struct dentry *curr = dentry; + ssize_t i; + + lockdep_assert_held_read(&v9fs_dentry2v9ses(dentry)->rename_sem); + + rcu_read_lock(); + + /* Don't include the root dentry */ + while (curr->d_parent != curr) { + path_components++; + curr = curr->d_parent; + } + if (WARN_ON(path_components > SSIZE_MAX)) { + rcu_read_unlock(); + return NULL; + } + + path = kmalloc(struct_size(path, names, path_components), + GFP_KERNEL); + if (!path) { + rcu_read_unlock(); + return NULL; + } + + path->nr_components = path_components; + curr = dentry; + for (i = path_components - 1; i >= 0; i--) { + take_dentry_name_snapshot(&path->names[i], curr); + curr = curr->d_parent; + } + WARN_ON(curr != curr->d_parent); + rcu_read_unlock(); + return path; +} + +void free_ino_path(struct v9fs_ino_path *path) +{ + if (path) { + for (size_t i = 0; i < path->nr_components; i++) + release_dentry_name_snapshot(&path->names[i]); + kfree(path); + } +} + +/* + * Must hold rename_sem due to traversing parents + */ +bool ino_path_compare(struct v9fs_ino_path *ino_path, + struct dentry *dentry) +{ + struct dentry *curr = dentry; + struct qstr *curr_name; + struct name_snapshot *compare; + ssize_t i; + + lockdep_assert_held_read(&v9fs_dentry2v9ses(dentry)->rename_sem); + + rcu_read_lock(); + for (i = ino_path->nr_components - 1; i >= 0; i--) { + if (curr->d_parent == curr) { + /* We're supposed to have more components to walk */ + rcu_read_unlock(); + return false; + } + curr_name = &curr->d_name; + compare = &ino_path->names[i]; + /* + * We can't use hash_len because it is salted with the parent + * dentry pointer. We could make this faster by pre-computing our + * own hashlen for compare and ino_path outside, probably. + */ + if (curr_name->len != compare->name.len) { + rcu_read_unlock(); + return false; + } + if (strncmp(curr_name->name, compare->name.name, + curr_name->len) != 0) { + rcu_read_unlock(); + return false; + } + curr = curr->d_parent; + } + rcu_read_unlock(); + if (curr != curr->d_parent) { + /* dentry is deeper than ino_path */ + return false; + } + return true; +} diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index f28bc763847a..5c85923aa2dd 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -10,6 +10,7 @@ #include <linux/backing-dev.h> #include <linux/netfs.h> +#include <linux/dcache.h> /** * enum p9_session_flags - option flags for each 9P session @@ -31,16 +32,17 @@ #define V9FS_ACL_MASK V9FS_POSIX_ACL enum p9_session_flags { - V9FS_PROTO_2000U = 0x01, - V9FS_PROTO_2000L = 0x02, - V9FS_ACCESS_SINGLE = 0x04, - V9FS_ACCESS_USER = 0x08, - V9FS_ACCESS_CLIENT = 0x10, - V9FS_POSIX_ACL = 0x20, - V9FS_NO_XATTR = 0x40, - V9FS_IGNORE_QV = 0x80, /* ignore qid.version for cache hints */ - V9FS_DIRECT_IO = 0x100, - V9FS_SYNC = 0x200 + V9FS_PROTO_2000U = 0x01, + V9FS_PROTO_2000L = 0x02, + V9FS_ACCESS_SINGLE = 0x04, + V9FS_ACCESS_USER = 0x08, + V9FS_ACCESS_CLIENT = 0x10, + V9FS_POSIX_ACL = 0x20, + V9FS_NO_XATTR = 0x40, + V9FS_IGNORE_QV = 0x80, /* ignore qid.version for cache hints */ + V9FS_DIRECT_IO = 0x100, + V9FS_SYNC = 0x200, + V9FS_INODE_IDENT_PATH = 0x400, }; /** @@ -133,11 +135,27 @@ struct v9fs_session_info { /* cache_validity flags */ #define V9FS_INO_INVALID_ATTR 0x01 +struct v9fs_ino_path { + size_t nr_components; + struct name_snapshot names[] __counted_by(nr_components); +}; + +extern struct v9fs_ino_path *make_ino_path(struct dentry *dentry); +extern void free_ino_path(struct v9fs_ino_path *path); +extern bool ino_path_compare(struct v9fs_ino_path *ino_path, + struct dentry *dentry); + struct v9fs_inode { struct netfs_inode netfs; /* Netfslib context and vfs inode */ struct p9_qid qid; unsigned int cache_validity; struct mutex v_mutex; + + /* + * Only for filesystems with inode_ident=path. Lifetime is the same as + * this inode + */ + struct v9fs_ino_path *path; }; static inline struct v9fs_inode *V9FS_I(const struct inode *inode) @@ -188,7 +206,8 @@ extern const struct inode_operations v9fs_symlink_inode_operations_dotl; extern const struct netfs_request_ops v9fs_req_ops; extern struct inode *v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid, - struct super_block *sb, int new); + struct super_block *sb, + struct dentry *dentry, int new); /* other default globals */ #define V9FS_PORT 564 @@ -217,6 +236,11 @@ static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses) return v9ses->flags & V9FS_PROTO_2000L; } +static inline int v9fs_inode_ident_path(struct v9fs_session_info *v9ses) +{ + return v9ses->flags & V9FS_INODE_IDENT_PATH; +} + /** * v9fs_get_inode_from_fid - Helper routine to populate an inode by * issuing a attribute request @@ -227,10 +251,10 @@ static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses) */ static inline struct inode * v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, - struct super_block *sb) + struct super_block *sb, struct dentry *dentry) { if (v9fs_proto_dotl(v9ses)) - return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 0); + return v9fs_inode_from_fid_dotl(v9ses, fid, sb, dentry, 0); else return v9fs_inode_from_fid(v9ses, fid, sb, 0); } @@ -245,10 +269,10 @@ v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, */ static inline struct inode * v9fs_get_new_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, - struct super_block *sb) + struct super_block *sb, struct dentry *dentry) { if (v9fs_proto_dotl(v9ses)) - return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 1); + return v9fs_inode_from_fid_dotl(v9ses, fid, sb, dentry, 1); else return v9fs_inode_from_fid(v9ses, fid, sb, 1); } diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 1640765563e9..72fd72a2ff06 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -243,6 +243,7 @@ struct inode *v9fs_alloc_inode(struct super_block *sb) void v9fs_free_inode(struct inode *inode) { + free_ino_path(V9FS_I(inode)->path); kmem_cache_free(v9fs_inode_cache, V9FS_I(inode)); } @@ -607,15 +608,17 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir, goto error; } /* - * instantiate inode and assign the unopened fid to the dentry + * Instantiate inode. On .L fs, pass in dentry for inodeident=path. */ - inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb, + v9fs_proto_dotl(v9ses) ? dentry : NULL); if (IS_ERR(inode)) { err = PTR_ERR(inode); p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err); goto error; } + /* Assign the unopened fid to the dentry */ v9fs_fid_add(dentry, &fid); d_instantiate(dentry, inode); } @@ -733,14 +736,21 @@ struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, name = dentry->d_name.name; fid = p9_client_walk(dfid, 1, &name, 1); p9_fid_put(dfid); + + /* + * On .L fs, pass in dentry to v9fs_get_inode_from_fid in case it is + * needed by inodeident=path + */ if (fid == ERR_PTR(-ENOENT)) inode = NULL; else if (IS_ERR(fid)) inode = ERR_CAST(fid); - else if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) - inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb); + else if (v9ses->cache & (CACHE_META | CACHE_LOOSE)) + inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb, + v9fs_proto_dotl(v9ses) ? dentry : NULL); else - inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb, + v9fs_proto_dotl(v9ses) ? dentry : NULL); /* * If we had a rename on the server and a parallel lookup * for the new name, then make sure we instantiate with diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 2d025e561ba1..c1cc3553f2fb 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -52,10 +52,17 @@ static kgid_t v9fs_get_fsgid_for_create(struct inode *dir_inode) return current_fsgid(); } +struct iget_data { + struct p9_stat_dotl *st; + struct dentry *dentry; +}; + static int v9fs_test_inode_dotl(struct inode *inode, void *data) { struct v9fs_inode *v9inode = V9FS_I(inode); - struct p9_stat_dotl *st = (struct p9_stat_dotl *)data; + struct p9_stat_dotl *st = ((struct iget_data *)data)->st; + struct dentry *dentry = ((struct iget_data *)data)->dentry; + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); /* don't match inode of different type */ if (inode_wrong_type(inode, st->st_mode)) @@ -74,22 +81,74 @@ static int v9fs_test_inode_dotl(struct inode *inode, void *data) if (v9inode->qid.path != st->qid.path) return 0; + + if (v9fs_inode_ident_path(v9ses)) { + if (!ino_path_compare(v9inode->path, dentry)) { + p9_debug(P9_DEBUG_VFS, "Refusing to reuse inode %p based on path mismatch", + inode); + return 0; + } + } return 1; } /* Always get a new inode */ static int v9fs_test_new_inode_dotl(struct inode *inode, void *data) { + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_stat_dotl *st = ((struct iget_data *)data)->st; + struct dentry *dentry = ((struct iget_data *)data)->dentry; + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); + + /* + * Don't reuse inode of different type, even if we have + * inodeident=path and path matches. + */ + if (inode_wrong_type(inode, st->st_mode)) + return 0; + + /* + * We're only getting here if QID2INO stays the same anyway, so + * mirroring the qid checks in v9fs_test_inode_dotl + * (but maybe that check is unnecessary anyway? at least on 64bit) + */ + + if (v9inode->qid.type != st->qid.type) + return 0; + + if (v9inode->qid.path != st->qid.path) + return 0; + + if (v9fs_inode_ident_path(v9ses) && dentry && v9inode->path) { + if (ino_path_compare(V9FS_I(inode)->path, dentry)) { + p9_debug(P9_DEBUG_VFS, + "Reusing inode %p based on path match", inode); + return 1; + } + } + return 0; } static int v9fs_set_inode_dotl(struct inode *inode, void *data) { struct v9fs_inode *v9inode = V9FS_I(inode); - struct p9_stat_dotl *st = (struct p9_stat_dotl *)data; + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); + struct iget_data *idata = data; + struct p9_stat_dotl *st = idata->st; + struct dentry *dentry = idata->dentry; memcpy(&v9inode->qid, &st->qid, sizeof(st->qid)); inode->i_generation = st->st_gen; + if (v9fs_inode_ident_path(v9ses)) { + if (dentry) { + v9inode->path = make_ino_path(dentry); + if (!v9inode->path) + return -ENOMEM; + } else { + v9inode->path = NULL; + } + } return 0; } @@ -97,19 +156,35 @@ static struct inode *v9fs_qid_iget_dotl(struct super_block *sb, struct p9_qid *qid, struct p9_fid *fid, struct p9_stat_dotl *st, + struct dentry *dentry, int new) { int retval; struct inode *inode; struct v9fs_session_info *v9ses = sb->s_fs_info; int (*test)(struct inode *inode, void *data); + struct iget_data data = { + .st = st, + .dentry = dentry, + }; + if (new) test = v9fs_test_new_inode_dotl; else test = v9fs_test_inode_dotl; - inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode_dotl, st); + if (v9fs_inode_ident_path(v9ses) && dentry) { + /* + * We have to take the rename_sem lock here as iget5_locked has + * spinlock in it (inode_hash_lock) + */ + down_read(&v9ses->rename_sem); + } + inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode_dotl, &data); + if (v9fs_inode_ident_path(v9ses) && dentry) + up_read(&v9ses->rename_sem); + if (!inode) return ERR_PTR(-ENOMEM); if (!(inode->i_state & I_NEW)) @@ -142,7 +217,7 @@ static struct inode *v9fs_qid_iget_dotl(struct super_block *sb, struct inode * v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid, - struct super_block *sb, int new) + struct super_block *sb, struct dentry *dentry, int new) { struct p9_stat_dotl *st; struct inode *inode = NULL; @@ -151,7 +226,7 @@ v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid, if (IS_ERR(st)) return ERR_CAST(st); - inode = v9fs_qid_iget_dotl(sb, &st->qid, fid, st, new); + inode = v9fs_qid_iget_dotl(sb, &st->qid, fid, st, dentry, new); kfree(st); return inode; } @@ -305,7 +380,7 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); goto out; } - inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb, dentry); if (IS_ERR(inode)) { err = PTR_ERR(inode); p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err); @@ -400,7 +475,7 @@ static int v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap, } /* instantiate inode and assign the unopened fid to the dentry */ - inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb, dentry); if (IS_ERR(inode)) { err = PTR_ERR(inode); p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", @@ -838,7 +913,7 @@ v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir, err); goto error; } - inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb, dentry); if (IS_ERR(inode)) { err = PTR_ERR(inode); p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c index 489db161abc9..566d9ae6255f 100644 --- a/fs/9p/vfs_super.c +++ b/fs/9p/vfs_super.c @@ -139,7 +139,7 @@ static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags, else sb->s_d_op = &v9fs_dentry_operations; - inode = v9fs_get_new_inode_from_fid(v9ses, fid, sb); + inode = v9fs_get_new_inode_from_fid(v9ses, fid, sb, NULL); if (IS_ERR(inode)) { retval = PTR_ERR(inode); goto release_sb; @@ -151,6 +151,14 @@ static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags, goto release_sb; } sb->s_root = root; + + if (v9fs_inode_ident_path(v9ses)) { + /* Probably not necessary, just to satisfy lockdep_assert */ + down_read(&v9ses->rename_sem); + V9FS_I(inode)->path = make_ino_path(root); + up_read(&v9ses->rename_sem); + } + retval = v9fs_get_acl(inode, fid); if (retval) goto release_sb; -- 2.39.5