From c4e8aee7097e8d3c9272181c88327b0a27cda288 Mon Sep 17 00:00:00 2001 From: Cheng Ding Date: Thu, 21 May 2026 09:06:00 +0000 Subject: [PATCH] fuse: add lookupx support This is an extended version of lookup that supports additional flags and returns attributes with valid masks via fuse_reply_lookupx. --- fs/fuse/dir.c | 120 +++++++++++++++++++++++++++++++++----- fs/fuse/fuse_i.h | 3 + fs/fuse/inode.c | 3 +- include/uapi/linux/fuse.h | 18 ++++++ 4 files changed, 129 insertions(+), 15 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 67581ce277b560..1bd18259a822a8 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -28,6 +28,8 @@ module_param(allow_sys_admin_access, bool, 0644); MODULE_PARM_DESC(allow_sys_admin_access, "Allow users with CAP_SYS_ADMIN in initial userns to bypass allow_other access check"); +static void fuse_attr_to_statx(struct fuse_attr *attr, struct fuse_statx *sx, uint32_t mask); + static void fuse_advise_use_readdirplus(struct inode *dir) { struct fuse_inode *fi = get_fuse_inode(dir); @@ -185,6 +187,72 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, args->out_args[0].value = outarg; } +/** + * fuse_do_lookupx - Perform a FUSE_LOOKUPX operation + * + * @ext_out: extended output argument structure + * @lookup_flags: lookup flags (e.g., FUSE_LOOKUPX_REVALIDATE) + */ +static int fuse_do_lookupx(struct fuse_mount *fm, u64 nodeid, + const struct qstr *name, + struct fuse_lookupx_out *ext_out, + uint32_t lookup_flags) +{ + struct fuse_conn *fc = fm->fc; + FUSE_ARGS(args); + struct fuse_lookupx_in inarg; + int err; + + memset(ext_out, 0, sizeof(*ext_out)); + args.nodeid = nodeid; + + if (!fc->lookupx) + goto fallback; + + args.opcode = FUSE_LOOKUPX; + memset(&inarg, 0, sizeof(inarg)); + inarg.lookup_flags = lookup_flags; + + args.in_numargs = 3; + args.in_args[0].size = sizeof(inarg); + args.in_args[0].value = &inarg; + args.in_args[1].size = 0; + args.in_args[1].value = NULL; + args.in_args[2].size = name->len + 1; + args.in_args[2].value = name->name; + args.out_numargs = 1; + args.out_args[0].size = sizeof(struct fuse_lookupx_out); + args.out_args[0].value = ext_out; + + err = fuse_simple_request(fm, &args); + if (err) { + if (err == -ENOSYS) { + fc->lookupx = 0; + goto fallback; + } + return err; + } + + return 0; + +fallback: + args.opcode = FUSE_LOOKUP; + args.in_numargs = 2; + fuse_set_zero_arg0(&args); + args.in_args[1].size = name->len + 1; + args.in_args[1].value = name->name; + args.out_numargs = 1; + args.out_args[0].size = sizeof(struct fuse_entry_out); + args.out_args[0].value = &ext_out->entry; + + err = fuse_simple_request(fm, &args); + if (err) + return err; + + ext_out->mask = STATX_BASIC_STATS; + return 0; +} + /* * Check whether the dentry is still valid * @@ -207,8 +275,8 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) goto invalid; else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { - struct fuse_entry_out outarg; - FUSE_ARGS(args); + struct fuse_lookupx_out ext_out; + struct fuse_statx sx; struct fuse_forget_link *forget; u64 attr_version; @@ -230,19 +298,19 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) attr_version = fuse_get_attr_version(fm->fc); parent = dget_parent(entry); - fuse_lookup_init(fm->fc, &args, get_node_id(d_inode(parent)), - &entry->d_name, &outarg); - ret = fuse_simple_request(fm, &args); + ret = fuse_do_lookupx(fm, get_node_id(d_inode(parent)), + &entry->d_name, &ext_out, + FUSE_LOOKUPX_REVALIDATE); dput(parent); /* Zero nodeid is same as -ENOENT */ - if (!ret && !outarg.nodeid) + if (!ret && !ext_out.entry.nodeid) ret = -ENOENT; if (!ret) { fi = get_fuse_inode(inode); - if (outarg.nodeid != get_node_id(inode) || - (bool) IS_AUTOMOUNT(inode) != (bool) (outarg.attr.flags & FUSE_ATTR_SUBMOUNT)) { + if (ext_out.entry.nodeid != get_node_id(inode) || + (bool) IS_AUTOMOUNT(inode) != (bool) (ext_out.entry.attr.flags & FUSE_ATTR_SUBMOUNT)) { fuse_queue_forget(fm->fc, forget, - outarg.nodeid, 1); + ext_out.entry.nodeid, 1); goto invalid; } spin_lock(&fi->lock); @@ -252,15 +320,17 @@ static int fuse_dentry_revalidate(struct dentry *entry, unsigned int flags) kfree(forget); if (ret == -ENOMEM || ret == -EINTR) goto out; - if (ret || fuse_invalid_attr(&outarg.attr) || - fuse_stale_inode(inode, outarg.generation, &outarg.attr)) + if (ret || fuse_invalid_attr(&ext_out.entry.attr) || + fuse_stale_inode(inode, ext_out.entry.generation, &ext_out.entry.attr)) goto invalid; forget_all_cached_acls(inode); - fuse_change_attributes(inode, &outarg.attr, NULL, - ATTR_TIMEOUT(&outarg), + fuse_attr_to_statx(&ext_out.entry.attr, &sx, ext_out.mask); + fuse_change_attributes(inode, &ext_out.entry.attr, &sx, + ATTR_TIMEOUT(&ext_out.entry), attr_version); - fuse_change_entry_timeout(entry, &outarg); + if ((ext_out.mask & STATX_BASIC_STATS) == STATX_BASIC_STATS) + fuse_change_entry_timeout(entry, &ext_out.entry); } else if (inode) { fi = get_fuse_inode(inode); if (flags & LOOKUP_RCU) { @@ -1183,6 +1253,28 @@ static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr) attr->blksize = sx->blksize; } +static void fuse_attr_to_statx(struct fuse_attr *attr, struct fuse_statx *sx, uint32_t mask) +{ + memset(sx, 0, sizeof(*sx)); + sx->mask = mask; + sx->ino = attr->ino; + sx->size = attr->size; + sx->blocks = attr->blocks; + sx->atime.tv_sec = attr->atime; + sx->mtime.tv_sec = attr->mtime; + sx->ctime.tv_sec = attr->ctime; + sx->atime.tv_nsec = attr->atimensec; + sx->mtime.tv_nsec = attr->mtimensec; + sx->ctime.tv_nsec = attr->ctimensec; + sx->mode = attr->mode; + sx->nlink = attr->nlink; + sx->uid = attr->uid; + sx->gid = attr->gid; + sx->rdev_major = MAJOR(attr->rdev); + sx->rdev_minor = MINOR(attr->rdev); + sx->blksize = attr->blksize; +} + static int fuse_do_statx(struct inode *inode, struct file *file, struct kstat *stat) { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 654b5890c24c18..7014a2a2d76848 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -864,6 +864,9 @@ struct fuse_conn { /* do we have support for dlm in the fs? */ unsigned int dlm:1; + /* Is extended lookup implemented by fs? */ + unsigned int lookupx:1; + /* Is synchronous FUSE_INIT allowed? */ unsigned int sync_init:1; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 6cfe436aa93d2c..1947f9f630d72f 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -611,7 +611,7 @@ int fuse_reverse_inval_inode(struct fuse_conn *fc, u64 nodeid, fi->attr_version = atomic64_inc_return(&fc->attr_version); spin_unlock(&fi->lock); - + if (fc->inval_inode_entries) fuse_invalidate_inode_entry(inode); else if (fc->expire_inode_entries) @@ -1043,6 +1043,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, fc->initialized = 0; fc->connected = 1; fc->dlm = 1; + fc->lookupx = 1; /* module option for now */ fc->compound_open_getattr = enable_compound; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 52ac7a3d266d46..a4122a83e91b1b 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -575,6 +575,12 @@ struct fuse_file_lock { */ #define FUSE_OPEN_KILL_SUIDGID (1 << 0) +/** + * Lookup flags + * FUSE_LOOKUPX_REVALIDATE: lookup called from revalidate + */ +#define FUSE_LOOKUPX_REVALIDATE (1 << 0) + /** * setxattr flags * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set @@ -660,6 +666,9 @@ enum fuse_opcode { */ FUSE_COMPOUND = 101, + /* Extented lookup operation */ + FUSE_LOOKUPX = 102, + /* CUSE specific operations */ CUSE_INIT = 4096, @@ -694,6 +703,15 @@ struct fuse_entry_out { struct fuse_attr attr; }; +struct fuse_lookupx_in { + uint32_t lookup_flags; +}; + +struct fuse_lookupx_out { + struct fuse_entry_out entry; + uint32_t mask; /* Mask of valid attributes in statx format */ +}; + struct fuse_forget_in { uint64_t nlookup; };