mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Luis Henriques <luis@igalia.com>
To: Miklos Szeredi <miklos@szeredi.hu>,
	Amir Goldstein <amir73il@gmail.com>,
	Bernd Schubert <bschubert@ddn.com>,
	Bernd Schubert <bernd@bsbernd.com>,
	"Darrick J. Wong" <djwong@kernel.org>,
	Horst Birthelmer <hbirthelmer@ddn.com>,
	Joanne Koong <joannelkoong@gmail.com>, Kevin Chen <kchen@ddn.com>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey <mharvey@jumptrading.com>,
	kernel-dev@igalia.com, Luis Henriques <luis@igalia.com>
Subject: [RFC PATCH v3 6/8] fuse: implementation of lookup_handle+statx compound operation
Date: Wed, 25 Feb 2026 11:24:37 +0000	[thread overview]
Message-ID: <20260225112439.27276-7-luis@igalia.com> (raw)
In-Reply-To: <20260225112439.27276-1-luis@igalia.com>

The implementation of lookup_handle+statx compound operation extends the
lookup operation so that a file handle is be passed into the kernel.  It
also needs to include an extra inarg, so that the parent directory file
handle can be sent to user-space.  This extra inarg is added as an extension
header to the request.

By having a separate statx including in a compound operation allows the
attr to be dropped from the lookup_handle request, simplifying the
traditional FUSE lookup operation.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/dir.c             | 294 +++++++++++++++++++++++++++++++++++---
 fs/fuse/fuse_i.h          |  23 ++-
 fs/fuse/inode.c           |  48 +++++--
 fs/fuse/readdir.c         |   2 +-
 include/uapi/linux/fuse.h |  23 ++-
 5 files changed, 355 insertions(+), 35 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 5c0f1364c392..7fa8c405f1a3 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -21,6 +21,7 @@
 #include <linux/security.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
+#include <linux/exportfs.h>
 
 static bool __read_mostly allow_sys_admin_access;
 module_param(allow_sys_admin_access, bool, 0644);
@@ -372,6 +373,47 @@ static void fuse_lookup_init(struct fuse_args *args, u64 nodeid,
 	args->out_args[0].value = outarg;
 }
 
+static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid,
+				  struct inode *parent_inode,
+				  const struct qstr *name,
+				  struct fuse_entry2_out *lookup_out,
+				  struct fuse_statx_out *statx_out,
+				  struct fuse_file_handle **fh);
+static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr);
+static int do_reval_lookup(struct fuse_mount *fm, u64 parent_nodeid,
+			   const struct qstr *name, u64 *nodeid,
+			   u64 *generation, u64 *attr_valid,
+			   struct fuse_attr *attr, struct fuse_file_handle **fh)
+{
+	struct fuse_entry_out entry_out;
+	struct fuse_entry2_out lookup_out;
+	struct fuse_statx_out statx_out;
+	FUSE_ARGS(lookup_args);
+	int ret = 0;
+
+	if (fm->fc->lookup_handle) {
+		ret = do_lookup_handle_statx(fm, parent_nodeid, NULL, name,
+					     &lookup_out, &statx_out, fh);
+		if (!ret) {
+			*nodeid = lookup_out.nodeid;
+			*generation = lookup_out.generation;
+			*attr_valid = fuse_time_to_jiffies(lookup_out.entry_valid,
+							   lookup_out.entry_valid_nsec);
+			fuse_statx_to_attr(&statx_out.stat, attr);
+		}
+	} else {
+		fuse_lookup_init(&lookup_args, parent_nodeid, name, &entry_out);
+		ret = fuse_simple_request(fm, &lookup_args);
+		if (!ret) {
+			*nodeid = entry_out.nodeid;
+			*generation = entry_out.generation;
+			*attr_valid = ATTR_TIMEOUT(&entry_out);
+			memcpy(attr, &entry_out.attr, sizeof(*attr));
+		}
+	}
+
+	return ret;
+}
 /*
  * Check whether the dentry is still valid
  *
@@ -399,10 +441,11 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 		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_forget_link *forget;
+		struct fuse_file_handle *fh = NULL;
 		u64 attr_version;
+		u64 nodeid, generation, attr_valid;
+		struct fuse_attr attr;
 
 		/* For negative dentries, always do a fresh lookup */
 		if (!inode)
@@ -421,35 +464,36 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 
 		attr_version = fuse_get_attr_version(fm->fc);
 
-		fuse_lookup_init(&args, get_node_id(dir), name, &outarg);
-		ret = fuse_simple_request(fm, &args);
+		ret = do_reval_lookup(fm, get_node_id(dir), name, &nodeid,
+				      &generation, &attr_valid, &attr, &fh);
 		/* Zero nodeid is same as -ENOENT */
-		if (!ret && !outarg.nodeid)
+		if (!ret && !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)) {
-				fuse_queue_forget(fm->fc, forget,
-						  outarg.nodeid, 1);
+			if (!fuse_file_handle_is_equal(fm->fc, fi->fh, fh) ||
+			    nodeid != get_node_id(inode) ||
+			    (bool) IS_AUTOMOUNT(inode) != (bool) (attr.flags & FUSE_ATTR_SUBMOUNT)) {
+				fuse_queue_forget(fm->fc, forget, nodeid, 1);
+				kfree(fh);
 				goto invalid;
 			}
 			spin_lock(&fi->lock);
 			fi->nlookup++;
 			spin_unlock(&fi->lock);
 		}
+		kfree(fh);
 		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(&attr) ||
+		    fuse_stale_inode(inode, generation, &attr))
 			goto invalid;
 
 		forget_all_cached_acls(inode);
-		fuse_change_attributes(inode, &outarg.attr, NULL,
-				       ATTR_TIMEOUT(&outarg),
+		fuse_change_attributes(inode, &attr, NULL, attr_valid,
 				       attr_version);
-		fuse_change_entry_timeout(entry, &outarg);
+		fuse_dentry_settime(entry, attr_valid);
 	} else if (inode) {
 		fi = get_fuse_inode(inode);
 		if (flags & LOOKUP_RCU) {
@@ -546,8 +590,215 @@ bool fuse_invalid_attr(struct fuse_attr *attr)
 	return !fuse_valid_type(attr->mode) || !fuse_valid_size(attr->size);
 }
 
-int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
-		     u64 *time, struct inode **inode)
+static int create_ext_handle(struct fuse_in_arg *ext, struct fuse_inode *fi)
+{
+	struct fuse_ext_header *xh;
+	struct fuse_file_handle *fh;
+	u32 len;
+
+	len = fuse_ext_size(sizeof(*fi->fh) + fi->fh->size);
+	xh = fuse_extend_arg(ext, len);
+	if (!xh)
+		return -ENOMEM;
+
+	xh->size = len;
+	xh->type = FUSE_EXT_HANDLE;
+	fh = (struct fuse_file_handle *)&xh[1];
+	fh->size = fi->fh->size;
+	memcpy(fh->handle, fi->fh->handle, fh->size);
+
+	return 0;
+}
+
+static int fuse_lookup_handle_init(struct fuse_args *args, u64 nodeid,
+				   struct fuse_inode *fi,
+				   const struct qstr *name,
+				   struct fuse_entry2_out *outarg)
+{
+	struct fuse_file_handle *fh;
+	size_t fh_size = sizeof(*fh) + MAX_HANDLE_SZ;
+	int ret = -ENOMEM;
+
+	fh = kzalloc(fh_size, GFP_KERNEL);
+	if (!fh)
+		return ret;
+
+	memset(outarg, 0, sizeof(struct fuse_entry2_out));
+	args->opcode = FUSE_LOOKUP_HANDLE;
+	args->nodeid = nodeid;
+	args->in_numargs = 3;
+	fuse_set_zero_arg0(args);
+	args->in_args[1].size = name->len;
+	args->in_args[1].value = name->name;
+	args->in_args[2].size = 1;
+	args->in_args[2].value = "";
+	if (fi && fi->fh) {
+		args->is_ext = true;
+		args->ext_idx = args->in_numargs++;
+		args->in_args[args->ext_idx].size = 0;
+		ret = create_ext_handle(&args->in_args[args->ext_idx], fi);
+		if (ret) {
+			kfree(fh);
+			return ret;
+		}
+	}
+	args->out_numargs = 2;
+	args->out_argvar = true;
+	args->out_argvar_idx = 1;
+	args->out_args[0].size = sizeof(struct fuse_entry2_out);
+	args->out_args[0].value = outarg;
+
+	/* XXX do allocation to the actual size of the handle */
+	args->out_args[1].size = fh_size;
+	args->out_args[1].value = fh;
+
+	return 0;
+}
+
+static void fuse_req_free_argvar_ext(struct fuse_args *args)
+{
+	if (args->out_argvar)
+		kfree(args->out_args[args->out_argvar_idx].value);
+	if (args->is_ext)
+		kfree(args->in_args[args->ext_idx].value);
+}
+
+static void fuse_statx_init(struct fuse_args *args, struct fuse_statx_in *inarg,
+			    u64 nodeid, struct fuse_file *ff,
+			    struct fuse_statx_out *outarg);
+static int fuse_statx_update(struct mnt_idmap *idmap,
+			     struct fuse_statx_out *outarg, struct inode *inode,
+			     u64 attr_version, struct kstat *stat);
+static int do_lookup_handle_statx(struct fuse_mount *fm, u64 parent_nodeid,
+				  struct inode *parent_inode,
+				  const struct qstr *name,
+				  struct fuse_entry2_out *lookup_out,
+				  struct fuse_statx_out *statx_out,
+				  struct fuse_file_handle **fh)
+{
+	struct fuse_compound_req *compound;
+	struct fuse_inode *fi = NULL;
+	struct fuse_statx_in statx_in;
+	FUSE_ARGS(lookup_args);
+	FUSE_ARGS(statx_args);
+	int ret;
+
+	if (parent_inode)
+		fi = get_fuse_inode(parent_inode);
+
+	compound = fuse_compound_alloc(fm, 0);
+	if (!compound)
+		return -ENOMEM;
+
+	ret = fuse_lookup_handle_init(&lookup_args, parent_nodeid, fi,
+				      name, lookup_out);
+	if (ret)
+		goto out_compound;
+
+	ret = fuse_compound_add(compound, &lookup_args);
+	if (ret)
+		goto out_args;
+
+	/*
+	 * XXX nodeid is the parent of the inode we want! At this point
+	 * we still don't have the nodeid.  Using FUSE_ROOT_ID for now.
+	 */
+	fuse_statx_init(&statx_args, &statx_in, FUSE_ROOT_ID, NULL, statx_out);
+	ret = fuse_compound_add(compound, &statx_args);
+	if (ret)
+		goto out_args;
+
+	ret = fuse_compound_send(compound);
+	if (ret)
+		goto out_args;
+
+	ret = fuse_compound_get_error(compound, 0);
+	if (ret || !lookup_out->nodeid)
+		goto out_args;
+	if (lookup_out->nodeid == FUSE_ROOT_ID &&
+	    lookup_out->generation != 0) {
+		pr_warn_once("root generation should be zero\n");
+		lookup_out->generation = 0;
+	}
+	if ((lookup_args.out_args[1].size > 0) &&
+	    (lookup_args.out_args[1].value)) {
+		struct fuse_file_handle *h = lookup_args.out_args[1].value;
+
+		*fh = kzalloc(sizeof(*fh) + h->size, GFP_KERNEL);
+		if (!*fh) {
+			ret = -ENOMEM;
+			goto out_args;
+		}
+		(*fh)->size = h->size;
+		memcpy((*fh)->handle, h->handle, (*fh)->size);
+	}
+
+	ret = fuse_compound_get_error(compound, 1);
+	if (ret) {
+		kfree(*fh);
+		*fh = NULL;
+	}
+
+out_args:
+	fuse_req_free_argvar_ext(&lookup_args);
+out_compound:
+	kfree(compound);
+
+	return ret;
+}
+
+static int fuse_lookup_handle_name(struct super_block *sb, u64 nodeid,
+				   struct inode *dir, const struct qstr *name,
+				   u64 *time, struct inode **inode)
+{
+	struct fuse_mount *fm = get_fuse_mount_super(sb);
+	struct fuse_file_handle *fh = NULL;
+	struct fuse_entry2_out lookup_out;
+	struct fuse_statx_out statx_out;
+	struct fuse_attr attr;
+	struct fuse_forget_link *forget;
+	u64 attr_version, evict_ctr;
+	int ret;
+
+	forget = fuse_alloc_forget();
+	if (!forget)
+		return -ENOMEM;
+
+	attr_version = fuse_get_attr_version(fm->fc);
+	evict_ctr = fuse_get_evict_ctr(fm->fc);
+
+	ret = do_lookup_handle_statx(fm, nodeid, dir, name, &lookup_out,
+				     &statx_out, &fh);
+	if (ret)
+		goto out_forget;
+
+	fuse_statx_to_attr(&statx_out.stat, &attr);
+	WARN_ON(fuse_invalid_attr(&attr));
+
+	*inode = fuse_iget(sb, lookup_out.nodeid, lookup_out.generation,
+			   &attr, ATTR_TIMEOUT(&statx_out),
+			   attr_version, evict_ctr, fh);
+	ret = -ENOMEM;
+	if (!*inode) {
+		fuse_queue_forget(fm->fc, forget, lookup_out.nodeid, 1);
+		goto out_forget;
+	}
+	if (time)
+		*time = fuse_time_to_jiffies(lookup_out.entry_valid,
+					     lookup_out.entry_valid_nsec);
+
+	/* XXX idmap? */
+	ret = fuse_statx_update(&nop_mnt_idmap, &statx_out, *inode,
+				attr_version, NULL);
+
+out_forget:
+	kfree(forget);
+
+	return ret;
+}
+
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, u64 *time, struct inode **inode)
 {
 	struct fuse_mount *fm = get_fuse_mount_super(sb);
 	FUSE_ARGS(args);
@@ -561,6 +812,9 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 	if (name->len > fm->fc->name_max)
 		goto out;
 
+	if (fm->fc->lookup_handle)
+		return fuse_lookup_handle_name(sb, nodeid, dir, name, time,
+					       inode);
 
 	forget = fuse_alloc_forget();
 	err = -ENOMEM;
@@ -586,7 +840,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 
 	*inode = fuse_iget(sb, outarg.nodeid, outarg.generation,
 			   &outarg.attr, ATTR_TIMEOUT(&outarg),
-			   attr_version, evict_ctr);
+			   attr_version, evict_ctr, NULL);
 	err = -ENOMEM;
 	if (!*inode) {
 		fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
@@ -621,7 +875,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 	epoch = atomic_read(&fc->epoch);
 
 	locked = fuse_lock_inode(dir);
-	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
+	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), dir, &entry->d_name,
 			       &time, &inode);
 	fuse_unlock_inode(dir, locked);
 	if (err == -ENOENT)
@@ -882,7 +1136,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	ff->nodeid = outentry.nodeid;
 	ff->open_flags = outopenp->open_flags;
 	inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
-			  &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0);
+			  &outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0, NULL);
 	if (!inode) {
 		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
 		fuse_sync_release(NULL, ff, flags);
@@ -1009,7 +1263,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 		goto out_put_forget_req;
 
 	inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
-			  &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0);
+			  &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0, NULL);
 	if (!inode) {
 		fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
 		return ERR_PTR(-ENOMEM);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 04f09e2ccfd0..173032887fc2 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -223,6 +223,8 @@ struct fuse_inode {
 	 * so preserve the blocksize specified by the server.
 	 */
 	u8 cached_i_blkbits;
+
+	struct fuse_file_handle *fh;
 };
 
 /** FUSE inode state bits */
@@ -923,6 +925,9 @@ struct fuse_conn {
 	/* Is synchronous FUSE_INIT allowed? */
 	unsigned int sync_init:1;
 
+	/** Is LOOKUP_HANDLE implemented by the fs? */
+	unsigned int lookup_handle:1;
+
 	/* Use io_uring for communication */
 	unsigned int io_uring;
 
@@ -1072,6 +1077,18 @@ static inline int invalid_nodeid(u64 nodeid)
 	return !nodeid || nodeid == FUSE_ROOT_ID;
 }
 
+static inline bool fuse_file_handle_is_equal(struct fuse_conn *fc,
+					     struct fuse_file_handle *fh1,
+					     struct fuse_file_handle *fh2)
+{
+	if (!fc->lookup_handle ||
+	    ((fh1 == fh2) && !fh1) || /* both NULL */
+	    (fh1 && fh2 && (fh1->size == fh2->size) &&
+	     (!memcmp(fh1->handle, fh2->handle, fh1->size))))
+		return true;
+	return false;
+}
+
 static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
 {
 	return atomic64_read(&fc->attr_version);
@@ -1148,10 +1165,10 @@ extern const struct dentry_operations fuse_dentry_operations;
 struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			int generation, struct fuse_attr *attr,
 			u64 attr_valid, u64 attr_version,
-			u64 evict_ctr);
+			u64 evict_ctr, struct fuse_file_handle *fh);
 
-int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
-		     u64 *time, struct inode **inode);
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, u64 *time, struct inode **inode);
 
 /**
  * Send FORGET command
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 006436a3ad06..9f2c0c9e877c 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -120,6 +120,8 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_inode_backing_set(fi, NULL);
 
+	fi->fh = NULL;
+
 	return &fi->inode;
 
 out_free_forget:
@@ -141,6 +143,8 @@ static void fuse_free_inode(struct inode *inode)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_backing_put(fuse_inode_backing(fi));
 
+	kfree(fi->fh);
+
 	kmem_cache_free(fuse_inode_cachep, fi);
 }
 
@@ -465,7 +469,7 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp)
 struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			int generation, struct fuse_attr *attr,
 			u64 attr_valid, u64 attr_version,
-			u64 evict_ctr)
+			u64 evict_ctr, struct fuse_file_handle *fh)
 {
 	struct inode *inode;
 	struct fuse_inode *fi;
@@ -505,6 +509,26 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 	if (!inode)
 		return NULL;
 
+	fi = get_fuse_inode(inode);
+	if (fc->lookup_handle && !fi->fh) {
+		if (!fh && (nodeid != FUSE_ROOT_ID)) {
+			pr_err("NULL file handle for nodeid %llu\n", nodeid);
+			WARN_ON_ONCE(1);
+		} else {
+			size_t sz = sizeof(struct fuse_file_handle);
+
+			if (fh)
+				sz += fh->size;
+
+			fi->fh = kzalloc(sz, GFP_KERNEL);
+			if (!fi->fh) {
+				iput(inode);
+				return NULL; // ENOMEM
+			}
+			if (fh)
+				memcpy(fi->fh, fh, sz);
+		}
+	}
 	if ((inode_state_read_once(inode) & I_NEW)) {
 		inode->i_flags |= S_NOATIME;
 		if (!fc->writeback_cache || !S_ISREG(attr->mode))
@@ -512,7 +536,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 		inode->i_generation = generation;
 		fuse_init_inode(inode, attr, fc);
 		unlock_new_inode(inode);
-	} else if (fuse_stale_inode(inode, generation, attr)) {
+	} else if (fuse_stale_inode(inode, generation, attr) ||
+		   (!fuse_file_handle_is_equal(fc, fi->fh, fh))) {
 		/* nodeid was reused, any I/O on the old inode should fail */
 		fuse_make_bad(inode);
 		if (inode != d_inode(sb->s_root)) {
@@ -521,7 +546,6 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 			goto retry;
 		}
 	}
-	fi = get_fuse_inode(inode);
 	spin_lock(&fi->lock);
 	fi->nlookup++;
 	spin_unlock(&fi->lock);
@@ -1068,7 +1092,7 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mo
 	attr.mode = mode;
 	attr.ino = FUSE_ROOT_ID;
 	attr.nlink = 1;
-	return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0);
+	return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0, NULL);
 }
 
 struct fuse_inode_handle {
@@ -1094,7 +1118,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 		if (!fc->export_support)
 			goto out_err;
 
-		err = fuse_lookup_name(sb, handle->nodeid, &name, NULL, &inode);
+		err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, NULL,
+				       &inode);
 		if (err && err != -ENOENT)
 			goto out_err;
 		if (err || !inode) {
@@ -1115,9 +1140,9 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 
 	return entry;
 
- out_iput:
+out_iput:
 	iput(inode);
- out_err:
+out_err:
 	return ERR_PTR(err);
 }
 
@@ -1194,7 +1219,7 @@ static struct dentry *fuse_get_parent(struct dentry *child)
 		return ERR_PTR(-ESTALE);
 
 	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
-			       &dotdot_name, NULL, &inode);
+			       child_inode, &dotdot_name, NULL, &inode);
 	if (err) {
 		if (err == -ENOENT)
 			return ERR_PTR(-ESTALE);
@@ -1459,6 +1484,9 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
 
 			if (flags & FUSE_REQUEST_TIMEOUT)
 				timeout = arg->request_timeout;
+
+			if (flags & FUSE_HAS_LOOKUP_HANDLE)
+				fc->lookup_handle = 1;
 		} else {
 			ra_pages = fc->max_read / PAGE_SIZE;
 			fc->no_lock = 1;
@@ -1509,7 +1537,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 		FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP |
 		FUSE_HAS_EXPIRE_ONLY | FUSE_DIRECT_IO_ALLOW_MMAP |
 		FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP |
-		FUSE_REQUEST_TIMEOUT;
+		FUSE_REQUEST_TIMEOUT | FUSE_LOOKUP_HANDLE;
 #ifdef CONFIG_FUSE_DAX
 	if (fm->fc->dax)
 		flags |= FUSE_MAP_ALIGNMENT;
@@ -1745,7 +1773,7 @@ static int fuse_fill_super_submount(struct super_block *sb,
 
 	fuse_fill_attr_from_inode(&root_attr, parent_fi);
 	root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0,
-			 fuse_get_evict_ctr(fm->fc));
+			 fuse_get_evict_ctr(fm->fc), NULL);
 	/*
 	 * This inode is just a duplicate, so it is not looked up and
 	 * its nlookup should not be incremented.  fuse_iget() does
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index c2aae2eef086..2b59a196bcbf 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -235,7 +235,7 @@ static int fuse_direntplus_link(struct file *file,
 	} else {
 		inode = fuse_iget(dir->i_sb, o->nodeid, o->generation,
 				  &o->attr, ATTR_TIMEOUT(o),
-				  attr_version, evict_ctr);
+				  attr_version, evict_ctr, NULL);
 		if (!inode)
 			inode = ERR_PTR(-ENOMEM);
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 113583c4efb6..89e6176abe25 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -240,6 +240,9 @@
  *  - add FUSE_COPY_FILE_RANGE_64
  *  - add struct fuse_copy_file_range_out
  *  - add FUSE_NOTIFY_PRUNE
+ *
+ *  7.46
+ *  - add FUSE_LOOKUP_HANDLE
  */
 
 #ifndef _LINUX_FUSE_H
@@ -275,7 +278,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 45
+#define FUSE_KERNEL_MINOR_VERSION 46
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -495,6 +498,7 @@ struct fuse_file_lock {
 #define FUSE_ALLOW_IDMAP	(1ULL << 40)
 #define FUSE_OVER_IO_URING	(1ULL << 41)
 #define FUSE_REQUEST_TIMEOUT	(1ULL << 42)
+#define FUSE_HAS_LOOKUP_HANDLE	(1ULL << 43)
 
 /**
  * CUSE INIT request/reply flags
@@ -609,6 +613,7 @@ enum fuse_ext_type {
 	/* Types 0..31 are reserved for fuse_secctx_header */
 	FUSE_MAX_NR_SECCTX	= 31,
 	FUSE_EXT_GROUPS		= 32,
+	FUSE_EXT_HANDLE		= 33,
 };
 
 enum fuse_opcode {
@@ -671,6 +676,8 @@ enum fuse_opcode {
 	 */
 	FUSE_COMPOUND		= 54,
 
+	FUSE_LOOKUP_HANDLE	= 55,
+
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
 
@@ -707,6 +714,20 @@ struct fuse_entry_out {
 	struct fuse_attr attr;
 };
 
+struct fuse_entry2_out {
+	uint64_t	nodeid;
+	uint64_t	generation;
+	uint64_t	entry_valid;
+	uint32_t	entry_valid_nsec;
+	uint32_t	flags;
+	uint64_t	spare;
+};
+
+struct fuse_file_handle {
+	uint16_t	size;
+	uint8_t		handle[];
+};
+
 struct fuse_forget_in {
 	uint64_t	nlookup;
 };

  parent reply	other threads:[~2026-02-25 11:25 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-25 11:24 [RFC PATCH v3 0/8] fuse: LOOKUP_HANDLE operation Luis Henriques
2026-02-25 11:24 ` [RFC PATCH v3 1/8] fuse: simplify fuse_lookup_name() interface Luis Henriques
2026-02-27 15:46   ` Miklos Szeredi
2026-02-28 14:42     ` Luis Henriques
2026-02-25 11:24 ` [RFC PATCH v3 2/8] fuse: export extend_arg() and factor out fuse_ext_size() Luis Henriques
2026-02-25 11:24 ` [RFC PATCH v3 3/8] fuse: store index of the variable length argument Luis Henriques
2026-02-27 15:41   ` Miklos Szeredi
2026-02-28 14:50     ` Luis Henriques
2026-02-25 11:24 ` [RFC PATCH v3 4/8] fuse: drop unnecessary argument from fuse_lookup_init() Luis Henriques
2026-02-27 15:57   ` Miklos Szeredi
2026-02-25 11:24 ` [RFC PATCH v3 5/8] fuse: extract helper functions from fuse_do_statx() Luis Henriques
2026-02-25 11:24 ` Luis Henriques [this message]
2026-02-25 18:06   ` [RFC PATCH v3 6/8] fuse: implementation of lookup_handle+statx compound operation Amir Goldstein
2026-02-26  9:54     ` Luis Henriques
2026-02-26 10:08       ` Amir Goldstein
2026-02-26 10:29         ` Miklos Szeredi
2026-02-26 15:06           ` Luis Henriques
2026-02-26 15:44             ` Miklos Szeredi
2026-02-26 16:17               ` Luis Henriques
2026-02-26 10:33         ` Luis Henriques
2026-04-07 17:43   ` Joanne Koong
2026-04-07 21:20     ` Luis Henriques
2026-04-07 23:06       ` Joanne Koong
2026-04-07 23:24         ` Joanne Koong
2026-04-07 23:38           ` Joanne Koong
2026-04-08 10:22           ` Luis Henriques
2026-04-08 15:15             ` Joanne Koong
2026-04-08 10:16         ` Luis Henriques
2026-04-08 15:05           ` Joanne Koong
2026-04-09 10:46             ` Luis Henriques
2026-04-09  2:27           ` Jingbo Xu
2026-04-09 11:10             ` Luis Henriques
2026-04-10  2:18               ` Jingbo Xu
2026-04-09 14:03             ` Amir Goldstein
2026-04-10  2:17               ` Jingbo Xu
2026-02-25 11:24 ` [RFC PATCH v3 7/8] fuse: export fuse_open_args_fill() helper function Luis Henriques
2026-02-25 11:24 ` [RFC PATCH v3 8/8] fuse: implementation of mkobj_handle+statx+open compound operation Luis Henriques
2026-02-25 15:08   ` Horst Birthelmer
2026-02-25 17:26     ` Luis Henriques
2026-02-25 15:14 ` [RFC PATCH v3 0/8] fuse: LOOKUP_HANDLE operation Horst Birthelmer
2026-02-25 17:06   ` Luis Henriques

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260225112439.27276-7-luis@igalia.com \
    --to=luis@igalia.com \
    --cc=amir73il@gmail.com \
    --cc=bernd@bsbernd.com \
    --cc=bschubert@ddn.com \
    --cc=djwong@kernel.org \
    --cc=hbirthelmer@ddn.com \
    --cc=joannelkoong@gmail.com \
    --cc=kchen@ddn.com \
    --cc=kernel-dev@igalia.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mharvey@jumptrading.com \
    --cc=miklos@szeredi.hu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

Powered by JetHome