[PATCH v2 12/21] fuse: add struct fuse_entry2_out and helpers for extended entry replies

Joanne Koong <[email protected]> Fri, 15 May 2026 17:39:55 -0700
Newsgroups org.kernel.vger.linux-unionfs,dev.linux.lists.fuse-devel
Message-ID <[email protected]>
Add struct fuse_entry2_out, which is a new extended entry reply struct
that carries a backing_id and statx attributes. This will be necessary
for setting fuse passthrough on inodes.

Add helpers that subsequent commits will use to process fuse_entry2_out
for passthrough support for lookup, revalidate, and create.
fuse_statx_to_attr() is also moved to earlier in the file to avoid
forward declaring.

Signed-off-by: Joanne Koong <[email protected]>
---
 fs/fuse/dir.c             | 116 +++++++++++++++++++++++++++++---------
 fs/fuse/fuse_i.h          |   5 ++
 include/uapi/linux/fuse.h |  14 +++++
 3 files changed, 107 insertions(+), 28 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index a338f2a06b50..b7a9d2b0476a 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -355,11 +355,18 @@ static void fuse_invalidate_entry(struct dentry *entry)
 	fuse_invalidate_entry_cache(entry);
 }
 
-static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
-			     const struct qstr *name,
-			     struct fuse_entry_out *outarg)
+static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
+			     u64 nodeid, const struct qstr *name,
+			     struct fuse_entry_out *outarg,
+			     struct fuse_entry2_out *outarg2)
 {
-	memset(outarg, 0, sizeof(struct fuse_entry_out));
+	bool use_entry2 = fuse_use_entry2(fc);
+
+	if (use_entry2)
+		memset(outarg2, 0, sizeof(struct fuse_entry2_out));
+	else
+		memset(outarg, 0, sizeof(struct fuse_entry_out));
+
 	args->opcode = FUSE_LOOKUP;
 	args->nodeid = nodeid;
 	args->in_numargs = 3;
@@ -369,8 +376,79 @@ static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
 	args->in_args[2].size = 1;
 	args->in_args[2].value = "";
 	args->out_numargs = 1;
-	args->out_args[0].size = sizeof(struct fuse_entry_out);
-	args->out_args[0].value = outarg;
+
+	if (use_entry2) {
+		args->out_args[0].size = sizeof(struct fuse_entry2_out);
+		args->out_args[0].value = outarg2;
+	} else {
+		args->out_args[0].size = sizeof(struct fuse_entry_out);
+		args->out_args[0].value = outarg;
+	}
+}
+
+static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+	attr->ino = sx->ino;
+	attr->size = sx->size;
+	attr->blocks = sx->blocks;
+	attr->atime = sx->atime.tv_sec;
+	attr->mtime = sx->mtime.tv_sec;
+	attr->ctime = sx->ctime.tv_sec;
+	attr->atimensec = sx->atime.tv_nsec;
+	attr->mtimensec = sx->mtime.tv_nsec;
+	attr->ctimensec = sx->ctime.tv_nsec;
+	attr->mode = sx->mode;
+	attr->nlink = sx->nlink;
+	attr->uid = sx->uid;
+	attr->gid = sx->gid;
+	attr->rdev = new_encode_dev(MKDEV(sx->rdev_major, sx->rdev_minor));
+	attr->blksize = sx->blksize;
+}
+
+static void fuse_entry2_to_entry(struct fuse_entry2_out *outarg2,
+				 struct fuse_entry_out *outarg)
+{
+	memset(outarg, 0, sizeof(struct fuse_entry_out));
+	outarg->nodeid = outarg2->nodeid;
+	outarg->generation = outarg2->generation;
+	outarg->entry_valid = outarg2->entry_valid;
+	outarg->attr_valid = outarg2->attr_valid;
+	outarg->entry_valid_nsec = outarg2->entry_valid_nsec;
+	outarg->attr_valid_nsec = outarg2->attr_valid_nsec;
+	fuse_statx_to_attr(&outarg2->statx, &outarg->attr);
+	outarg->attr.flags = outarg2->flags;
+}
+
+static __maybe_unused int fuse_process_entry2(struct fuse_conn *fc,
+					      struct fuse_entry2_out *outarg2,
+					      struct fuse_entry_out *outarg,
+					      struct fuse_statx **sxp)
+{
+	if (!fuse_use_entry2(fc))
+		return 0;
+
+	/*
+	 * Convert entry2 format to entry format before error checking since the
+	 * caller needs outarg->nodeid field even on error
+	 */
+	fuse_entry2_to_entry(outarg2, outarg);
+
+	if (outarg2->reserved)
+		return -EINVAL;
+
+	/* error */
+	if (outarg2->backing_id < 0)
+		return outarg2->backing_id;
+
+	/*
+	 * If passthrough is enabled (backing_id > 0), statx attributes are not
+	 * cached because passthrough getattr fetches them directly from the
+	 * backing inode
+	 */
+	if (!outarg2->backing_id)
+		*sxp = &outarg2->statx;
+	return outarg2->backing_id;
 }
 
 /*
@@ -389,6 +467,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 	struct fuse_conn *fc;
 	struct fuse_inode *fi;
 	struct fuse_entry_out outarg;
+	struct fuse_entry2_out outarg2;
 	struct fuse_forget_link *forget;
 	FUSE_ARGS(args);
 	u64 attr_version;
@@ -431,7 +510,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 
 	attr_version = fuse_get_attr_version(fc);
 
-	fuse_lookup_init(&args, get_node_id(dir), name, &outarg);
+	fuse_lookup_init(fc, &args, get_node_id(dir), name, &outarg, &outarg2);
 	ret = fuse_simple_request(get_fuse_mount(inode), &args);
 	if (ret || !outarg.nodeid) {
 		kfree(forget);
@@ -551,6 +630,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 	FUSE_ARGS(args);
 	struct fuse_forget_link *forget;
 	u64 attr_version, evict_ctr;
+	struct fuse_entry2_out outarg2;
 	int err;
 
 	*inode = NULL;
@@ -567,7 +647,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 	attr_version = fuse_get_attr_version(fm->fc);
 	evict_ctr = fuse_get_evict_ctr(fm->fc);
 
-	fuse_lookup_init(&args, nodeid, name, outarg);
+	fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, &outarg2);
 	err = fuse_simple_request(fm, &args);
 	/* Zero nodeid is same as -ENOENT, but with valid timeout */
 	if (err || !outarg->nodeid)
@@ -1386,26 +1466,6 @@ static void fuse_fillattr(struct mnt_idmap *idmap, struct inode *inode,
 	stat->blksize = 1 << blkbits;
 }
 
-static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr)
-{
-	memset(attr, 0, sizeof(*attr));
-	attr->ino = sx->ino;
-	attr->size = sx->size;
-	attr->blocks = sx->blocks;
-	attr->atime = sx->atime.tv_sec;
-	attr->mtime = sx->mtime.tv_sec;
-	attr->ctime = sx->ctime.tv_sec;
-	attr->atimensec = sx->atime.tv_nsec;
-	attr->mtimensec = sx->mtime.tv_nsec;
-	attr->ctimensec = sx->ctime.tv_nsec;
-	attr->mode = sx->mode;
-	attr->nlink = sx->nlink;
-	attr->uid = sx->uid;
-	attr->gid = sx->gid;
-	attr->rdev = new_encode_dev(MKDEV(sx->rdev_major, sx->rdev_minor));
-	attr->blksize = sx->blksize;
-}
-
 void fuse_kstat_to_attr(struct fuse_conn *fc, const struct kstat *stat,
 			struct fuse_attr *attr)
 {
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 1f2c849ea4e3..89c9333e9702 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1368,6 +1368,11 @@ static inline bool fuse_passthrough_op(struct inode *inode, enum fuse_opcode op)
 int fuse_passthrough_getattr(struct inode *inode, struct kstat *stat,
 			     u32 request_mask, unsigned int flags);
 
+static inline bool fuse_use_entry2(struct fuse_conn *fc)
+{
+	return fc->passthrough_ino;
+}
+
 #ifdef CONFIG_SYSCTL
 extern int fuse_sysctl_register(void);
 extern void fuse_sysctl_unregister(void);
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 6404ed95c758..3963631558f9 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -244,6 +244,7 @@
  *  7.46
  *  - add FUSE_PASSTHROUGH_INO
  *  - add ops_mask field to struct fuse_backing_map
+ *  - add fuse_entry2_out
  */
 
 #ifndef _LINUX_FUSE_H
@@ -705,6 +706,19 @@ struct fuse_entry_out {
 	struct fuse_attr attr;
 };
 
+struct fuse_entry2_out {
+	uint64_t	nodeid;
+	uint64_t	generation;
+	uint64_t	entry_valid;
+	uint64_t	attr_valid;
+	uint32_t	entry_valid_nsec;
+	uint32_t	attr_valid_nsec;
+	int32_t		backing_id;
+	uint32_t	flags;
+	uint64_t	reserved;
+	struct fuse_statx statx;
+};
+
 struct fuse_forget_in {
 	uint64_t	nlookup;
 };
-- 
2.52.0