All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.