public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
@ 2025-12-12 18:12 Luis Henriques
  2025-12-12 18:12 ` [RFC PATCH v2 1/6] fuse: store index of the variable length argument Luis Henriques
                   ` (6 more replies)
  0 siblings, 7 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

Hi!

As I mentioned in the v1 cover letter, I've been working on implementing the
FUSE_LOOKUP_HANDLE operation.  As I also mentioned, this is being done in
the scope of a wider project, which is to be able to restart FUSE servers
without the need to unmount the file systems.  For context, here are the
links again: [0] [1].

This v2 tries to address (most of) the comments from Amir to v1[2].  I don't
think I'm addressing them all, but since a few weeks have already passed, I
decided it's time to send a new version anyway.

Here's what changed since v1:

- Handle assertion gracefully in create_new_entry() (Amir)
- Don't truncate handle in fuse_iget() if size is too large (Amir)
- Move NFS-related changes to a different patch (Amir)
  In fact, I ended-up moving all the NFS-related code to a different file
- Handle compat (still WIP)
- Fix out_argvar handling: variable length arguments are not always the last
  arg now, so a new patch is handling this
- Re-implemented NFS-related changes
  Still only lightly tested, and as Amir hinted, it should probably include
  an extra init flag to select between old vs new NFS handles format
- The usual bug fixes found during more testing

[0] https://lore.kernel.org/all/8734afp0ct.fsf@igalia.com
[1] https://lore.kernel.org/all/CAJfpegvNZ6Z7uhuTdQ6quBaTOYNkAP8W_4yUY4L2JRAEKxEwOQ@mail.gmail.com
[2] https://lore.kernel.org/all/20251120105535.13374-1-luis@igalia.com

Cheers,
--
Luis

Luis Henriques (6):
  fuse: store index of the variable length argument
  fuse: move fuse_entry_out structs out of the stack
  fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  fuse: factor out NFS export related code
  fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE

 fs/fuse/Makefile          |   2 +-
 fs/fuse/cuse.c            |   1 +
 fs/fuse/dev.c             |  20 ++-
 fs/fuse/dir.c             | 216 ++++++++++++++++++--------
 fs/fuse/export.c          | 318 ++++++++++++++++++++++++++++++++++++++
 fs/fuse/file.c            |   1 +
 fs/fuse/fuse_i.h          |  53 ++++++-
 fs/fuse/inode.c           | 230 +++++++++------------------
 fs/fuse/ioctl.c           |   1 +
 fs/fuse/readdir.c         |  10 +-
 fs/fuse/virtio_fs.c       |   6 +-
 fs/fuse/xattr.c           |   2 +
 include/linux/exportfs.h  |   7 +
 include/uapi/linux/fuse.h |  16 +-
 14 files changed, 645 insertions(+), 238 deletions(-)
 create mode 100644 fs/fuse/export.c


^ permalink raw reply	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 1/6] fuse: store index of the variable length argument
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-12 18:12 ` [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack Luis Henriques
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

Operations that have a variable length argument assume that it will always
be the last argument on the list.  This patch allows this assumption to be
removed by keeping track of the index of variable length argument.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/cuse.c      | 1 +
 fs/fuse/dev.c       | 4 ++--
 fs/fuse/dir.c       | 1 +
 fs/fuse/file.c      | 1 +
 fs/fuse/fuse_i.h    | 2 ++
 fs/fuse/inode.c     | 1 +
 fs/fuse/ioctl.c     | 1 +
 fs/fuse/virtio_fs.c | 6 +++---
 fs/fuse/xattr.c     | 2 ++
 9 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index 28c96961e85d..9d93a3023b19 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -460,6 +460,7 @@ static int cuse_send_init(struct cuse_conn *cc)
 	ap->args.out_args[0].value = &ia->out;
 	ap->args.out_args[1].size = CUSE_INIT_INFO_MAX;
 	ap->args.out_argvar = true;
+	ap->args.out_argvar = 1;
 	ap->args.out_pages = true;
 	ap->num_folios = 1;
 	ap->folios = &ia->folio;
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 132f38619d70..629e8a043079 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -694,7 +694,7 @@ ssize_t __fuse_simple_request(struct mnt_idmap *idmap,
 	ret = req->out.h.error;
 	if (!ret && args->out_argvar) {
 		BUG_ON(args->out_numargs == 0);
-		ret = args->out_args[args->out_numargs - 1].size;
+		ret = args->out_args[args->out_argvar_idx].size;
 	}
 	fuse_put_request(req);
 
@@ -2157,7 +2157,7 @@ int fuse_copy_out_args(struct fuse_copy_state *cs, struct fuse_args *args,
 	if (reqsize < nbytes || (reqsize > nbytes && !args->out_argvar))
 		return -EINVAL;
 	else if (reqsize > nbytes) {
-		struct fuse_arg *lastarg = &args->out_args[args->out_numargs-1];
+		struct fuse_arg *lastarg = &args->out_args[args->out_argvar_idx];
 		unsigned diffsize = reqsize - nbytes;
 
 		if (diffsize > lastarg->size)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index ecaec0fea3a1..4dfe964a491c 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1629,6 +1629,7 @@ static int fuse_readlink_folio(struct inode *inode, struct folio *folio)
 	ap.args.nodeid = get_node_id(inode);
 	ap.args.out_pages = true;
 	ap.args.out_argvar = true;
+	ap.args.out_argvar_idx = 0;
 	ap.args.page_zeroing = true;
 	ap.args.out_numargs = 1;
 	ap.args.out_args[0].size = desc.length;
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index f1ef77a0be05..0fef3da1585c 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -581,6 +581,7 @@ void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
 	args->in_args[0].size = sizeof(ia->read.in);
 	args->in_args[0].value = &ia->read.in;
 	args->out_argvar = true;
+	args->out_argvar_idx = 0;
 	args->out_numargs = 1;
 	args->out_args[0].size = count;
 }
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index c2f2a48156d6..a5714bae4c45 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -319,6 +319,8 @@ struct fuse_args {
 	uint32_t opcode;
 	uint8_t in_numargs;
 	uint8_t out_numargs;
+	/* The index of the variable lenght out arg */
+	uint8_t out_argvar_idx;
 	uint8_t ext_idx;
 	bool force:1;
 	bool noreply:1;
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d1babf56f254..8917e5b7a009 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1534,6 +1534,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 	   with interface version < 7.5.  Rest of init_out is zeroed
 	   by do_get_request(), so a short reply is not a problem */
 	ia->args.out_argvar = true;
+	ia->args.out_argvar_idx = 0;
 	ia->args.out_args[0].size = sizeof(ia->out);
 	ia->args.out_args[0].value = &ia->out;
 	ia->args.force = true;
diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c
index fdc175e93f74..03a2b321e611 100644
--- a/fs/fuse/ioctl.c
+++ b/fs/fuse/ioctl.c
@@ -337,6 +337,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
 	ap.args.out_args[1].size = out_size;
 	ap.args.out_pages = true;
 	ap.args.out_argvar = true;
+	ap.args.out_argvar_idx = 1;
 
 	transferred = fuse_send_ioctl(fm, &ap.args, &outarg);
 	err = transferred;
diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c
index b2f6486fe1d5..26fb9c29f935 100644
--- a/fs/fuse/virtio_fs.c
+++ b/fs/fuse/virtio_fs.c
@@ -738,7 +738,7 @@ static void copy_args_from_argbuf(struct fuse_args *args, struct fuse_req *req)
 		unsigned int argsize = args->out_args[i].size;
 
 		if (args->out_argvar &&
-		    i == args->out_numargs - 1 &&
+		    i == args->out_argvar_idx &&
 		    argsize > remaining) {
 			argsize = remaining;
 		}
@@ -746,13 +746,13 @@ static void copy_args_from_argbuf(struct fuse_args *args, struct fuse_req *req)
 		memcpy(args->out_args[i].value, req->argbuf + offset, argsize);
 		offset += argsize;
 
-		if (i != args->out_numargs - 1)
+		if (i != args->out_argvar_idx)
 			remaining -= argsize;
 	}
 
 	/* Store the actual size of the variable-length arg */
 	if (args->out_argvar)
-		args->out_args[args->out_numargs - 1].size = remaining;
+		args->out_args[args->out_argvar_idx].size = remaining;
 
 	kfree(req->argbuf);
 	req->argbuf = NULL;
diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c
index 93dfb06b6cea..f123446fe537 100644
--- a/fs/fuse/xattr.c
+++ b/fs/fuse/xattr.c
@@ -73,6 +73,7 @@ ssize_t fuse_getxattr(struct inode *inode, const char *name, void *value,
 	args.out_numargs = 1;
 	if (size) {
 		args.out_argvar = true;
+		args.out_argvar_idx = 0;
 		args.out_args[0].size = size;
 		args.out_args[0].value = value;
 	} else {
@@ -135,6 +136,7 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list, size_t size)
 	args.out_numargs = 1;
 	if (size) {
 		args.out_argvar = true;
+		args.out_argvar_idx = 0;
 		args.out_args[0].size = size;
 		args.out_args[0].value = list;
 	} else {

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
  2025-12-12 18:12 ` [RFC PATCH v2 1/6] fuse: store index of the variable length argument Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-15 14:03   ` Bernd Schubert
  2025-12-12 18:12 ` [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support Luis Henriques
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

This patch simply turns all struct fuse_entry_out instances that are
allocated in the stack into dynamically allocated structs.  This is a
preparation patch for further changes, including the extra helper function
used to actually allocate the memory.

Also, remove all the memset()s that are used to zero-out these structures,
as kzalloc() is being used.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/dir.c    | 139 ++++++++++++++++++++++++++++++-----------------
 fs/fuse/fuse_i.h |   9 +++
 fs/fuse/inode.c  |  20 +++++--
 3 files changed, 114 insertions(+), 54 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4dfe964a491c..e3fd5d148741 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -172,7 +172,6 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
 			     u64 nodeid, const struct qstr *name,
 			     struct fuse_entry_out *outarg)
 {
-	memset(outarg, 0, sizeof(struct fuse_entry_out));
 	args->opcode = FUSE_LOOKUP;
 	args->nodeid = nodeid;
 	args->in_numargs = 3;
@@ -213,7 +212,7 @@ 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;
+		struct fuse_entry_out *outarg;
 		FUSE_ARGS(args);
 		struct fuse_forget_link *forget;
 		u64 attr_version;
@@ -233,20 +232,27 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 		if (!forget)
 			goto out;
 
+		outarg = fuse_entry_out_alloc(fc);
+		if (!outarg) {
+			kfree(forget);
+			goto out;
+		}
+
 		attr_version = fuse_get_attr_version(fm->fc);
 
 		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
-				 name, &outarg);
+				 name, outarg);
 		ret = fuse_simple_request(fm, &args);
 		/* Zero nodeid is same as -ENOENT */
-		if (!ret && !outarg.nodeid)
+		if (!ret && !outarg->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 (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);
+						  outarg->nodeid, 1);
+				kfree(outarg);
 				goto invalid;
 			}
 			spin_lock(&fi->lock);
@@ -254,17 +260,22 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 			spin_unlock(&fi->lock);
 		}
 		kfree(forget);
-		if (ret == -ENOMEM || ret == -EINTR)
+		if (ret == -ENOMEM || ret == -EINTR) {
+			kfree(outarg);
 			goto out;
-		if (ret || fuse_invalid_attr(&outarg.attr) ||
-		    fuse_stale_inode(inode, outarg.generation, &outarg.attr))
+		}
+		if (ret || fuse_invalid_attr(&outarg->attr) ||
+		    fuse_stale_inode(inode, outarg->generation, &outarg->attr)) {
+			kfree(outarg);
 			goto invalid;
+		}
 
 		forget_all_cached_acls(inode);
-		fuse_change_attributes(inode, &outarg.attr, NULL,
-				       ATTR_TIMEOUT(&outarg),
+		fuse_change_attributes(inode, &outarg->attr, NULL,
+				       ATTR_TIMEOUT(outarg),
 				       attr_version);
-		fuse_change_entry_timeout(entry, &outarg);
+		fuse_change_entry_timeout(entry, outarg);
+		kfree(outarg);
 	} else if (inode) {
 		fi = get_fuse_inode(inode);
 		if (flags & LOOKUP_RCU) {
@@ -410,7 +421,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
 static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 				  unsigned int flags)
 {
-	struct fuse_entry_out outarg;
+	struct fuse_entry_out *outarg;
 	struct fuse_conn *fc;
 	struct inode *inode;
 	struct dentry *newent;
@@ -424,9 +435,13 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 	fc = get_fuse_conn_super(dir->i_sb);
 	epoch = atomic_read(&fc->epoch);
 
+	outarg = fuse_entry_out_alloc(fc);
+	if (!outarg)
+		return ERR_PTR(-ENOMEM);
+
 	locked = fuse_lock_inode(dir);
 	err = fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name,
-			       &outarg, &inode);
+			       outarg, &inode);
 	fuse_unlock_inode(dir, locked);
 	if (err == -ENOENT) {
 		outarg_valid = false;
@@ -447,17 +462,21 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 	entry = newent ? newent : entry;
 	entry->d_time = epoch;
 	if (outarg_valid)
-		fuse_change_entry_timeout(entry, &outarg);
+		fuse_change_entry_timeout(entry, outarg);
 	else
 		fuse_invalidate_entry_cache(entry);
 
 	if (inode)
 		fuse_advise_use_readdirplus(dir);
+
+	kfree(outarg);
+
 	return newent;
 
- out_iput:
+out_iput:
 	iput(inode);
- out_err:
+out_err:
+	kfree(outarg);
 	return ERR_PTR(err);
 }
 
@@ -625,7 +644,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	struct fuse_forget_link *forget;
 	struct fuse_create_in inarg;
 	struct fuse_open_out *outopenp;
-	struct fuse_entry_out outentry;
+	struct fuse_entry_out *outentry;
 	struct fuse_inode *fi;
 	struct fuse_file *ff;
 	int epoch, err;
@@ -640,17 +659,19 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	if (!forget)
 		goto out_err;
 
-	err = -ENOMEM;
 	ff = fuse_file_alloc(fm, true);
 	if (!ff)
 		goto out_put_forget_req;
 
+	outentry = fuse_entry_out_alloc(fm->fc);
+	if (!outentry)
+		goto out_free_ff;
+
 	if (!fm->fc->dont_mask)
 		mode &= ~current_umask();
 
 	flags &= ~O_NOCTTY;
 	memset(&inarg, 0, sizeof(inarg));
-	memset(&outentry, 0, sizeof(outentry));
 	inarg.flags = flags;
 	inarg.mode = mode;
 	inarg.umask = current_umask();
@@ -668,8 +689,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	args.in_args[1].size = entry->d_name.len + 1;
 	args.in_args[1].value = entry->d_name.name;
 	args.out_numargs = 2;
-	args.out_args[0].size = sizeof(outentry);
-	args.out_args[0].value = &outentry;
+	args.out_args[0].size = sizeof(*outentry);
+	args.out_args[0].value = outentry;
 	/* Store outarg for fuse_finish_open() */
 	outopenp = &ff->args->open_outarg;
 	args.out_args[1].size = sizeof(*outopenp);
@@ -677,34 +698,35 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 
 	err = get_create_ext(idmap, &args, dir, entry, mode);
 	if (err)
-		goto out_free_ff;
+		goto out_free_outentry;
 
 	err = fuse_simple_idmap_request(idmap, fm, &args);
 	free_ext_value(&args);
 	if (err)
-		goto out_free_ff;
+		goto out_free_outentry;
 
 	err = -EIO;
-	if (!S_ISREG(outentry.attr.mode) || invalid_nodeid(outentry.nodeid) ||
-	    fuse_invalid_attr(&outentry.attr))
-		goto out_free_ff;
+	if (!S_ISREG(outentry->attr.mode) || invalid_nodeid(outentry->nodeid) ||
+	    fuse_invalid_attr(&outentry->attr))
+		goto out_free_outentry;
 
 	ff->fh = outopenp->fh;
-	ff->nodeid = outentry.nodeid;
+	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);
+	inode = fuse_iget(dir->i_sb, outentry->nodeid, outentry->generation,
+			  &outentry->attr, ATTR_TIMEOUT(outentry), 0, 0);
 	if (!inode) {
 		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
 		fuse_sync_release(NULL, ff, flags);
-		fuse_queue_forget(fm->fc, forget, outentry.nodeid, 1);
+		fuse_queue_forget(fm->fc, forget, outentry->nodeid, 1);
 		err = -ENOMEM;
+		kfree(outentry);
 		goto out_err;
 	}
 	kfree(forget);
 	d_instantiate(entry, inode);
 	entry->d_time = epoch;
-	fuse_change_entry_timeout(entry, &outentry);
+	fuse_change_entry_timeout(entry, outentry);
 	fuse_dir_changed(dir);
 	err = generic_file_open(inode, file);
 	if (!err) {
@@ -720,8 +742,13 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 		else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
 			invalidate_inode_pages2(inode->i_mapping);
 	}
+
+	kfree(outentry);
+
 	return err;
 
+out_free_outentry:
+	kfree(outentry);
 out_free_ff:
 	fuse_file_free(ff);
 out_put_forget_req:
@@ -780,7 +807,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 				       struct fuse_args *args, struct inode *dir,
 				       struct dentry *entry, umode_t mode)
 {
-	struct fuse_entry_out outarg;
+	struct fuse_entry_out *outarg;
 	struct inode *inode;
 	struct dentry *d;
 	struct fuse_forget_link *forget;
@@ -795,54 +822,66 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 	if (!forget)
 		return ERR_PTR(-ENOMEM);
 
-	memset(&outarg, 0, sizeof(outarg));
+	outarg = fuse_entry_out_alloc(fm->fc);
+	if (!outarg) {
+		err = -ENOMEM;
+		goto out_put_forget_req;
+	}
+
 	args->nodeid = get_node_id(dir);
 	args->out_numargs = 1;
-	args->out_args[0].size = sizeof(outarg);
-	args->out_args[0].value = &outarg;
+	args->out_args[0].size = sizeof(*outarg);
+	args->out_args[0].value = outarg;
 
 	if (args->opcode != FUSE_LINK) {
 		err = get_create_ext(idmap, args, dir, entry, mode);
 		if (err)
-			goto out_put_forget_req;
+			goto out_free_outarg;
 	}
 
 	err = fuse_simple_idmap_request(idmap, fm, args);
 	free_ext_value(args);
 	if (err)
-		goto out_put_forget_req;
+		goto out_free_outarg;
 
 	err = -EIO;
-	if (invalid_nodeid(outarg.nodeid) || fuse_invalid_attr(&outarg.attr))
-		goto out_put_forget_req;
+	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr))
+		goto out_free_outarg;
 
-	if ((outarg.attr.mode ^ mode) & S_IFMT)
-		goto out_put_forget_req;
+	if ((outarg->attr.mode ^ mode) & S_IFMT)
+		goto out_free_outarg;
 
-	inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
-			  &outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0);
+	inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
+			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0);
 	if (!inode) {
-		fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
+		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
+		kfree(outarg);
 		return ERR_PTR(-ENOMEM);
 	}
 	kfree(forget);
 
 	d_drop(entry);
 	d = d_splice_alias(inode, entry);
-	if (IS_ERR(d))
+	if (IS_ERR(d)) {
+		kfree(outarg);
 		return d;
+	}
 
 	if (d) {
 		d->d_time = epoch;
-		fuse_change_entry_timeout(d, &outarg);
+		fuse_change_entry_timeout(d, outarg);
 	} else {
 		entry->d_time = epoch;
-		fuse_change_entry_timeout(entry, &outarg);
+		fuse_change_entry_timeout(entry, outarg);
 	}
 	fuse_dir_changed(dir);
+	kfree(outarg);
+
 	return d;
 
- out_put_forget_req:
+out_free_outarg:
+	kfree(outarg);
+out_put_forget_req:
 	if (err == -EEXIST)
 		fuse_invalidate_entry(entry);
 	kfree(forget);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index a5714bae4c45..1792ee6f5da6 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1090,6 +1090,15 @@ static inline bool fuse_is_bad(struct inode *inode)
 	return unlikely(test_bit(FUSE_I_BAD, &get_fuse_inode(inode)->state));
 }
 
+static inline struct fuse_entry_out *fuse_entry_out_alloc(struct fuse_conn *fc)
+{
+	struct fuse_entry_out *entryout;
+
+	entryout = kzalloc(sizeof(*entryout), GFP_KERNEL_ACCOUNT);
+
+	return entryout;
+}
+
 static inline struct folio **fuse_folios_alloc(unsigned int nfolios, gfp_t flags,
 					       struct fuse_folio_desc **desc)
 {
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 8917e5b7a009..ef63300c634f 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1080,14 +1080,21 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 
 	inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid);
 	if (!inode) {
-		struct fuse_entry_out outarg;
+		struct fuse_entry_out *outarg;
 		const struct qstr name = QSTR_INIT(".", 1);
 
 		if (!fc->export_support)
 			goto out_err;
 
-		err = fuse_lookup_name(sb, handle->nodeid, &name, &outarg,
+		outarg = fuse_entry_out_alloc(fc);
+		if (!outarg) {
+			err = -ENOMEM;
+			goto out_err;
+		}
+
+		err = fuse_lookup_name(sb, handle->nodeid, &name, outarg,
 				       &inode);
+		kfree(outarg);
 		if (err && err != -ENOENT)
 			goto out_err;
 		if (err || !inode) {
@@ -1181,14 +1188,19 @@ static struct dentry *fuse_get_parent(struct dentry *child)
 	struct fuse_conn *fc = get_fuse_conn(child_inode);
 	struct inode *inode;
 	struct dentry *parent;
-	struct fuse_entry_out outarg;
+	struct fuse_entry_out *outarg;
 	int err;
 
 	if (!fc->export_support)
 		return ERR_PTR(-ESTALE);
 
+	outarg = fuse_entry_out_alloc(fc);
+	if (!outarg)
+		return ERR_PTR(-ENOMEM);
+
 	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
-			       &dotdot_name, &outarg, &inode);
+			       &dotdot_name, outarg, &inode);
+	kfree(outarg);
 	if (err) {
 		if (err == -ENOENT)
 			return ERR_PTR(-ESTALE);

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
  2025-12-12 18:12 ` [RFC PATCH v2 1/6] fuse: store index of the variable length argument Luis Henriques
  2025-12-12 18:12 ` [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-15 13:36   ` Bernd Schubert
  2025-12-16 10:19   ` Miklos Szeredi
  2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
                   ` (3 subsequent siblings)
  6 siblings, 2 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
operation.  It simply defines the new operation and the extra fuse_init_out
field to set the maximum handle size.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/fuse_i.h          | 4 ++++
 fs/fuse/inode.c           | 9 ++++++++-
 include/uapi/linux/fuse.h | 8 +++++++-
 3 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 1792ee6f5da6..fad05fae7e54 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -909,6 +909,10 @@ struct fuse_conn {
 	/* Is synchronous FUSE_INIT allowed? */
 	unsigned int sync_init:1;
 
+	/** Is LOOKUP_HANDLE implemented by fs? */
+	unsigned int lookup_handle:1;
+	unsigned int max_handle_sz;
+
 	/* Use io_uring for communication */
 	unsigned int io_uring;
 
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index ef63300c634f..bc84e7ed1e3d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1465,6 +1465,13 @@ 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) &&
+			    (arg->max_handle_sz > 0) &&
+			    (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
+				fc->lookup_handle = 1;
+				fc->max_handle_sz = arg->max_handle_sz;
+			}
 		} else {
 			ra_pages = fc->max_read / PAGE_SIZE;
 			fc->no_lock = 1;
@@ -1515,7 +1522,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;
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index c13e1f9a2f12..4acf71b407c9 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -495,6 +495,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
@@ -663,6 +664,7 @@ enum fuse_opcode {
 	FUSE_TMPFILE		= 51,
 	FUSE_STATX		= 52,
 	FUSE_COPY_FILE_RANGE_64	= 53,
+	FUSE_LOOKUP_HANDLE	= 54,
 
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
@@ -908,6 +910,9 @@ struct fuse_init_in {
 	uint32_t	unused[11];
 };
 
+/* Same value as MAX_HANDLE_SZ */
+#define FUSE_MAX_HANDLE_SZ 128
+
 #define FUSE_COMPAT_INIT_OUT_SIZE 8
 #define FUSE_COMPAT_22_INIT_OUT_SIZE 24
 
@@ -925,7 +930,8 @@ struct fuse_init_out {
 	uint32_t	flags2;
 	uint32_t	max_stack_depth;
 	uint16_t	request_timeout;
-	uint16_t	unused[11];
+	uint16_t	max_handle_sz;
+	uint16_t	unused[10];
 };
 
 #define CUSE_INIT_INFO_MAX 4096

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
                   ` (2 preceding siblings ...)
  2025-12-12 18:12 ` [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-15 17:39   ` Bernd Schubert
                     ` (2 more replies)
  2025-12-12 18:12 ` [RFC PATCH v2 5/6] fuse: factor out NFS export related code Luis Henriques
                   ` (2 subsequent siblings)
  6 siblings, 3 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
an extra inarg: the file handle for the parent directory (if it is
available).  Also, because fuse_entry_out now has a extra variable size
struct (the actual handle), it also sets the out_argvar flag to true.

Most of the other modifications in this patch are a fallout from these
changes: because fuse_entry_out has been modified to include a variable size
struct, every operation that receives such a parameter have to take this
into account:

  CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/dev.c             | 16 +++++++
 fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
 fs/fuse/fuse_i.h          | 34 +++++++++++++--
 fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
 fs/fuse/readdir.c         | 10 ++---
 include/uapi/linux/fuse.h |  8 ++++
 6 files changed, 189 insertions(+), 35 deletions(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 629e8a043079..fc6acf45ae27 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
 	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
 		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
 
+	if (fc->minor < 45) {
+		switch (args->opcode) {
+		case FUSE_CREATE:
+		case FUSE_LINK:
+		case FUSE_LOOKUP:
+		case FUSE_MKDIR:
+		case FUSE_MKNOD:
+		/* XXX case FUSE_READDIRPLUS: */
+		case FUSE_SYMLINK:
+		case FUSE_TMPFILE:
+			if (!WARN_ON_ONCE(args->in_numargs == 0))
+				args->in_numargs--;
+			args->out_args[0].size = FUSE_COMPAT_45_ENTRY_OUT_SIZE;
+			break;
+		}
+	}
 	if (fc->minor < 9) {
 		switch (args->opcode) {
 		case FUSE_LOOKUP:
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index e3fd5d148741..a6edb444180f 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -169,7 +169,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
 }
 
 static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
-			     u64 nodeid, const struct qstr *name,
+			     u64 nodeid, struct inode *dir,
+			     const struct qstr *name,
 			     struct fuse_entry_out *outarg)
 {
 	args->opcode = FUSE_LOOKUP;
@@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
 	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].size = sizeof(*outarg) + outarg->fh.size;
 	args->out_args[0].value = outarg;
+
+	if (fc->lookup_handle) {
+		struct fuse_inode *fi = NULL;
+
+		args->opcode = FUSE_LOOKUP_HANDLE;
+		args->out_argvar = true;
+
+		if (dir)
+			fi = get_fuse_inode(dir);
+
+		if (fi && fi->fh) {
+			args->in_numargs = 4;
+			args->in_args[3].size = sizeof(*fi->fh) + fi->fh->size;
+			args->in_args[3].value = fi->fh;
+		}
+	}
 }
 
 /*
@@ -240,7 +257,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 
 		attr_version = fuse_get_attr_version(fm->fc);
 
-		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
+		fuse_lookup_init(fm->fc, &args, get_node_id(dir), dir,
 				 name, outarg);
 		ret = fuse_simple_request(fm, &args);
 		/* Zero nodeid is same as -ENOENT */
@@ -248,7 +265,8 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
 			ret = -ENOENT;
 		if (!ret) {
 			fi = get_fuse_inode(inode);
-			if (outarg->nodeid != get_node_id(inode) ||
+			if (!fuse_file_handle_is_equal(fm->fc, fi->fh, &outarg->fh) ||
+			    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);
@@ -365,8 +383,9 @@ 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,
-		     struct fuse_entry_out *outarg, struct inode **inode)
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, struct fuse_entry_out *outarg,
+		     struct inode **inode)
 {
 	struct fuse_mount *fm = get_fuse_mount_super(sb);
 	FUSE_ARGS(args);
@@ -388,14 +407,15 @@ 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(fm->fc, &args, nodeid, name, outarg);
+	fuse_lookup_init(fm->fc, &args, nodeid, dir, name, outarg);
 	err = fuse_simple_request(fm, &args);
 	/* Zero nodeid is same as -ENOENT, but with valid timeout */
-	if (err || !outarg->nodeid)
+	if (err < 0 || !outarg->nodeid) // XXX err = size if args->out_argvar = true
 		goto out_put_forget;
 
 	err = -EIO;
-	if (fuse_invalid_attr(&outarg->attr))
+	if (fuse_invalid_attr(&outarg->attr) ||
+	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
 		goto out_put_forget;
 	if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) {
 		pr_warn_once("root generation should be zero\n");
@@ -404,7 +424,8 @@ 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,
+			   &outarg->fh);
 	err = -ENOMEM;
 	if (!*inode) {
 		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
@@ -440,14 +461,14 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 		return ERR_PTR(-ENOMEM);
 
 	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,
 			       outarg, &inode);
 	fuse_unlock_inode(dir, locked);
 	if (err == -ENOENT) {
 		outarg_valid = false;
 		err = 0;
 	}
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_err;
 
 	err = -EIO;
@@ -689,24 +710,36 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 	args.in_args[1].size = entry->d_name.len + 1;
 	args.in_args[1].value = entry->d_name.name;
 	args.out_numargs = 2;
-	args.out_args[0].size = sizeof(*outentry);
+	args.out_args[0].size = sizeof(*outentry) + outentry->fh.size;
 	args.out_args[0].value = outentry;
 	/* Store outarg for fuse_finish_open() */
 	outopenp = &ff->args->open_outarg;
 	args.out_args[1].size = sizeof(*outopenp);
 	args.out_args[1].value = outopenp;
 
+	if (fm->fc->lookup_handle) {
+		fi = get_fuse_inode(dir);
+		args.out_argvar = true;
+		args.out_argvar_idx = 0;
+		if (fi->fh) {
+			args.in_numargs = 3;
+			args.in_args[2].size = sizeof(*fi->fh) + fi->fh->size;
+			args.in_args[2].value = fi->fh;
+		}
+	}
+
 	err = get_create_ext(idmap, &args, dir, entry, mode);
 	if (err)
 		goto out_free_outentry;
 
 	err = fuse_simple_idmap_request(idmap, fm, &args);
 	free_ext_value(&args);
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_free_outentry;
 
 	err = -EIO;
 	if (!S_ISREG(outentry->attr.mode) || invalid_nodeid(outentry->nodeid) ||
+	    fuse_invalid_file_handle(fm->fc, &outentry->fh) ||
 	    fuse_invalid_attr(&outentry->attr))
 		goto out_free_outentry;
 
@@ -714,7 +747,8 @@ 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,
+			  &outentry->fh);
 	if (!inode) {
 		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
 		fuse_sync_release(NULL, ff, flags);
@@ -830,9 +864,22 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 
 	args->nodeid = get_node_id(dir);
 	args->out_numargs = 1;
-	args->out_args[0].size = sizeof(*outarg);
+	args->out_args[0].size = sizeof(*outarg) + outarg->fh.size;
 	args->out_args[0].value = outarg;
 
+	if (fm->fc->lookup_handle) {
+		struct fuse_inode *fi = get_fuse_inode(dir);
+		int idx = args->in_numargs;
+
+		args->out_argvar = true;
+		args->out_argvar_idx = 0;
+		if (fi->fh && !WARN_ON_ONCE(idx >= 4)) {		
+			args->in_args[idx].size = sizeof(*fi->fh) + fi->fh->size;
+			args->in_args[idx].value = fi->fh;
+			args->in_numargs++;
+		}
+	}
+
 	if (args->opcode != FUSE_LINK) {
 		err = get_create_ext(idmap, args, dir, entry, mode);
 		if (err)
@@ -841,18 +888,20 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 
 	err = fuse_simple_idmap_request(idmap, fm, args);
 	free_ext_value(args);
-	if (err)
+	if (err < 0) // XXX err = size if args->out_argvar = true
 		goto out_free_outarg;
 
 	err = -EIO;
-	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr))
+	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr) ||
+	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
 		goto out_free_outarg;
 
 	if ((outarg->attr.mode ^ mode) & S_IFMT)
 		goto out_free_outarg;
 
 	inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
-			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0);
+			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0,
+			  &outarg->fh);
 	if (!inode) {
 		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
 		kfree(outarg);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index fad05fae7e54..d0f3c81b5612 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -216,6 +216,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 */
@@ -1067,6 +1069,26 @@ static inline int invalid_nodeid(u64 nodeid)
 	return !nodeid || nodeid == FUSE_ROOT_ID;
 }
 
+static inline bool fuse_invalid_file_handle(struct fuse_conn *fc,
+					    struct fuse_file_handle *handle)
+{
+	if (!fc->lookup_handle)
+		return false;
+
+	return !handle->size || (handle->size >= FUSE_MAX_HANDLE_SZ);
+}
+
+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 || !fh2->size || // XXX more OPs without handle
+	    ((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);
@@ -1098,7 +1120,10 @@ static inline struct fuse_entry_out *fuse_entry_out_alloc(struct fuse_conn *fc)
 {
 	struct fuse_entry_out *entryout;
 
-	entryout = kzalloc(sizeof(*entryout), GFP_KERNEL_ACCOUNT);
+	entryout = kzalloc(sizeof(*entryout) + fc->max_handle_sz,
+			   GFP_KERNEL_ACCOUNT);
+	if (entryout)
+		entryout->fh.size = fc->max_handle_sz;
 
 	return entryout;
 }
@@ -1145,10 +1170,11 @@ 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,
-		     struct fuse_entry_out *outarg, struct inode **inode);
+int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
+		     const struct qstr *name, struct fuse_entry_out *outarg,
+		     struct inode **inode);
 
 /**
  * Send FORGET command
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index bc84e7ed1e3d..f565f7e8118d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -95,6 +95,25 @@ static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
 	return NULL;
 }
 
+/*
+ * XXX postpone this allocation and later use the real size instead of max
+ */
+static bool fuse_inode_handle_alloc(struct super_block *sb,
+				    struct fuse_inode *fi)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+
+	fi->fh = NULL;
+	if (fc->lookup_handle) {
+		fi->fh = kzalloc(sizeof(*fi->fh) + fc->max_handle_sz,
+				 GFP_KERNEL_ACCOUNT);
+		if (!fi->fh)
+			return false;
+	}
+
+	return true;
+}
+
 static struct inode *fuse_alloc_inode(struct super_block *sb)
 {
 	struct fuse_inode *fi;
@@ -120,8 +139,15 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_inode_backing_set(fi, NULL);
 
+	if (!fuse_inode_handle_alloc(sb, fi))
+		goto out_free_dax;
+
 	return &fi->inode;
 
+out_free_dax:
+#ifdef CONFIG_FUSE_DAX
+	kfree(fi->dax);
+#endif
 out_free_forget:
 	kfree(fi->forget);
 out_free:
@@ -132,6 +158,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
 static void fuse_free_inode(struct inode *inode)
 {
 	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_conn *fc = get_fuse_conn(inode);
 
 	mutex_destroy(&fi->mutex);
 	kfree(fi->forget);
@@ -141,6 +168,9 @@ static void fuse_free_inode(struct inode *inode)
 	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
 		fuse_backing_put(fuse_inode_backing(fi));
 
+	if (fc->lookup_handle)
+		kfree(fi->fh);
+
 	kmem_cache_free(fuse_inode_cachep, fi);
 }
 
@@ -465,7 +495,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 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
 	if (!inode)
 		return NULL;
 
+	fi = get_fuse_inode(inode);
+	if (fc->lookup_handle) {
+		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
+			pr_err("NULL file handle for nodeid %llu\n", nodeid);
+			iput(inode);
+			return NULL;
+		}
+		if (fi->fh->size)
+			pr_warn_ratelimited(
+				"Handle already set for nodeid %llu (size: %u)\n",
+				nodeid, fi->fh->size);
+		if (fh) {
+			if (fh->size >= fc->max_handle_sz) {
+				pr_err("File handle too big (%u)\n", fh->size);
+				iput(inode);
+				return NULL;
+			}
+			fi->fh->size = fh->size;
+			memcpy(fi->fh->handle, fh->handle, fi->fh->size);
+		} else {
+			fi->fh->size = 0;
+			memset(fi->fh, 0, fc->max_handle_sz);
+		}
+	}
 	if ((inode->i_state & I_NEW)) {
 		inode->i_flags |= S_NOATIME;
 		if (!fc->writeback_cache || !S_ISREG(attr->mode))
@@ -512,7 +566,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 +576,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);
@@ -1059,7 +1113,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); // XXX
 }
 
 struct fuse_inode_handle {
@@ -1092,7 +1146,7 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 			goto out_err;
 		}
 
-		err = fuse_lookup_name(sb, handle->nodeid, &name, outarg,
+		err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, outarg,
 				       &inode);
 		kfree(outarg);
 		if (err && err != -ENOENT)
@@ -1199,7 +1253,7 @@ static struct dentry *fuse_get_parent(struct dentry *child)
 		return ERR_PTR(-ENOMEM);
 
 	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
-			       &dotdot_name, outarg, &inode);
+			       child_inode, &dotdot_name, outarg, &inode);
 	kfree(outarg);
 	if (err) {
 		if (err == -ENOENT)
@@ -1757,8 +1811,9 @@ static int fuse_fill_super_submount(struct super_block *sb,
 		return -ENOMEM;
 
 	fuse_fill_attr_from_inode(&root_attr, parent_fi);
+	/* XXX using parent fh */
 	root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0,
-			 fuse_get_evict_ctr(fm->fc));
+			 fuse_get_evict_ctr(fm->fc), parent_fi->fh);
 	/*
 	 * 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..04fb6636c4c0 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -185,12 +185,12 @@ static int fuse_direntplus_link(struct file *file,
 			return 0;
 	}
 
-	if (invalid_nodeid(o->nodeid))
-		return -EIO;
-	if (fuse_invalid_attr(&o->attr))
+	fc = get_fuse_conn(dir);
+
+	if (invalid_nodeid(o->nodeid) || fuse_invalid_attr(&o->attr) ||
+	    fuse_invalid_file_handle(fc, &o->fh))
 		return -EIO;
 
-	fc = get_fuse_conn(dir);
 	epoch = atomic_read(&fc->epoch);
 
 	name.hash = full_name_hash(parent, name.name, name.len);
@@ -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, &o->fh);
 		if (!inode)
 			inode = ERR_PTR(-ENOMEM);
 
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 4acf71b407c9..b75744d2d75d 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -690,6 +690,13 @@ enum fuse_notify_code {
 #define FUSE_MIN_READ_BUFFER 8192
 
 #define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+#define FUSE_COMPAT_45_ENTRY_OUT_SIZE 128
+
+struct fuse_file_handle {
+	uint32_t	size;
+	uint32_t	type;
+	char		handle[0];
+};
 
 struct fuse_entry_out {
 	uint64_t	nodeid;		/* Inode ID */
@@ -700,6 +707,7 @@ struct fuse_entry_out {
 	uint32_t	entry_valid_nsec;
 	uint32_t	attr_valid_nsec;
 	struct fuse_attr attr;
+	struct fuse_file_handle fh;
 };
 
 struct fuse_forget_in {

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 5/6] fuse: factor out NFS export related code
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
                   ` (3 preceding siblings ...)
  2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-14 15:13   ` Amir Goldstein
  2025-12-12 18:12 ` [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE Luis Henriques
  2025-12-14 17:02 ` [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Askar Safin
  6 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

Move all the NFS-related code into a different file.  This is just
preparatory work to be able to use the LOOKUP_HANDLE file handles as the NFS
handles.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/Makefile |   2 +-
 fs/fuse/dir.c    |   1 +
 fs/fuse/export.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h |   6 ++
 fs/fuse/inode.c  | 167 +--------------------------------------------
 5 files changed, 183 insertions(+), 167 deletions(-)
 create mode 100644 fs/fuse/export.c

diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 22ad9538dfc4..1d1401658278 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -12,7 +12,7 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
 
 fuse-y := trace.o	# put trace.o first so we see ftrace errors sooner
 fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
-fuse-y += iomode.o
+fuse-y += iomode.o export.o
 fuse-$(CONFIG_FUSE_DAX) += dax.o
 fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
 fuse-$(CONFIG_SYSCTL) += sysctl.o
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index a6edb444180f..a885f1dc61eb 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -190,6 +190,7 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
 
 		args->opcode = FUSE_LOOKUP_HANDLE;
 		args->out_argvar = true;
+		args->out_argvar_idx = 0;
 
 		if (dir)
 			fi = get_fuse_inode(dir);
diff --git a/fs/fuse/export.c b/fs/fuse/export.c
new file mode 100644
index 000000000000..4a9c95fe578e
--- /dev/null
+++ b/fs/fuse/export.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE NFS export support.
+ *
+ * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+ */
+
+#include "fuse_i.h"
+#include <linux/exportfs.h>
+
+struct fuse_inode_handle {
+	u64 nodeid;
+	u32 generation;
+};
+
+static struct dentry *fuse_get_dentry(struct super_block *sb,
+				      struct fuse_inode_handle *handle)
+{
+	struct fuse_conn *fc = get_fuse_conn_super(sb);
+	struct inode *inode;
+	struct dentry *entry;
+	int err = -ESTALE;
+
+	if (handle->nodeid == 0)
+		goto out_err;
+
+	inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid);
+	if (!inode) {
+		struct fuse_entry_out *outarg;
+		const struct qstr name = QSTR_INIT(".", 1);
+
+		if (!fc->export_support)
+			goto out_err;
+
+		outarg = fuse_entry_out_alloc(fc);
+		if (!outarg) {
+			err = -ENOMEM;
+			goto out_err;
+		}
+
+		err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, outarg,
+				       &inode);
+		kfree(outarg);
+		if (err && err != -ENOENT)
+			goto out_err;
+		if (err || !inode) {
+			err = -ESTALE;
+			goto out_err;
+		}
+		err = -EIO;
+		if (get_node_id(inode) != handle->nodeid)
+			goto out_iput;
+	}
+	err = -ESTALE;
+	if (inode->i_generation != handle->generation)
+		goto out_iput;
+
+	entry = d_obtain_alias(inode);
+	if (!IS_ERR(entry) && get_node_id(inode) != FUSE_ROOT_ID)
+		fuse_invalidate_entry_cache(entry);
+
+	return entry;
+
+ out_iput:
+	iput(inode);
+ out_err:
+	return ERR_PTR(err);
+}
+
+static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
+			   struct inode *parent)
+{
+	int len = parent ? 6 : 3;
+	u64 nodeid;
+	u32 generation;
+
+	if (*max_len < len) {
+		*max_len = len;
+		return  FILEID_INVALID;
+	}
+
+	nodeid = get_fuse_inode(inode)->nodeid;
+	generation = inode->i_generation;
+
+	fh[0] = (u32)(nodeid >> 32);
+	fh[1] = (u32)(nodeid & 0xffffffff);
+	fh[2] = generation;
+
+	if (parent) {
+		nodeid = get_fuse_inode(parent)->nodeid;
+		generation = parent->i_generation;
+
+		fh[3] = (u32)(nodeid >> 32);
+		fh[4] = (u32)(nodeid & 0xffffffff);
+		fh[5] = generation;
+	}
+
+	*max_len = len;
+	return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
+}
+
+static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
+		struct fid *fid, int fh_len, int fh_type)
+{
+	struct fuse_inode_handle handle;
+
+	if ((fh_type != FILEID_INO64_GEN &&
+	     fh_type != FILEID_INO64_GEN_PARENT) || fh_len < 3)
+		return NULL;
+
+	handle.nodeid = (u64) fid->raw[0] << 32;
+	handle.nodeid |= (u64) fid->raw[1];
+	handle.generation = fid->raw[2];
+	return fuse_get_dentry(sb, &handle);
+}
+
+static struct dentry *fuse_fh_to_parent(struct super_block *sb,
+		struct fid *fid, int fh_len, int fh_type)
+{
+	struct fuse_inode_handle parent;
+
+	if (fh_type != FILEID_INO64_GEN_PARENT || fh_len < 6)
+		return NULL;
+
+	parent.nodeid = (u64) fid->raw[3] << 32;
+	parent.nodeid |= (u64) fid->raw[4];
+	parent.generation = fid->raw[5];
+	return fuse_get_dentry(sb, &parent);
+}
+
+static struct dentry *fuse_get_parent(struct dentry *child)
+{
+	struct inode *child_inode = d_inode(child);
+	struct fuse_conn *fc = get_fuse_conn(child_inode);
+	struct inode *inode;
+	struct dentry *parent;
+	struct fuse_entry_out *outarg;
+	int err;
+
+	if (!fc->export_support)
+		return ERR_PTR(-ESTALE);
+
+	outarg = fuse_entry_out_alloc(fc);
+	if (!outarg)
+		return ERR_PTR(-ENOMEM);
+
+	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
+			       child_inode, &dotdot_name, outarg, &inode);
+	kfree(outarg);
+	if (err) {
+		if (err == -ENOENT)
+			return ERR_PTR(-ESTALE);
+		return ERR_PTR(err);
+	}
+
+	parent = d_obtain_alias(inode);
+	if (!IS_ERR(parent) && get_node_id(inode) != FUSE_ROOT_ID)
+		fuse_invalidate_entry_cache(parent);
+
+	return parent;
+}
+
+/* only for fid encoding; no support for file handle */
+const struct export_operations fuse_export_fid_operations = {
+	.encode_fh	= fuse_encode_fh,
+};
+
+const struct export_operations fuse_export_operations = {
+	.fh_to_dentry	= fuse_fh_to_dentry,
+	.fh_to_parent	= fuse_fh_to_parent,
+	.encode_fh	= fuse_encode_fh,
+	.get_parent	= fuse_get_parent,
+};
+
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d0f3c81b5612..b6afd909c857 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1164,6 +1164,8 @@ extern const struct file_operations fuse_dev_operations;
 
 extern const struct dentry_operations fuse_dentry_operations;
 
+extern int fuse_inode_eq(struct inode *inode, void *_nodeidp);
+
 /**
  * Get a filled in inode
  */
@@ -1647,4 +1649,8 @@ extern void fuse_sysctl_unregister(void);
 #define fuse_sysctl_unregister()	do { } while (0)
 #endif /* CONFIG_SYSCTL */
 
+/* export.c */
+extern const struct export_operations fuse_export_operations;
+extern const struct export_operations fuse_export_fid_operations;
+
 #endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index f565f7e8118d..60715d6476c9 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -23,7 +23,6 @@
 #include <linux/statfs.h>
 #include <linux/random.h>
 #include <linux/sched.h>
-#include <linux/exportfs.h>
 #include <linux/posix_acl.h>
 #include <linux/pid_namespace.h>
 #include <uapi/linux/magic.h>
@@ -476,7 +475,7 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
 		inode->i_acl = inode->i_default_acl = ACL_DONT_CACHE;
 }
 
-static int fuse_inode_eq(struct inode *inode, void *_nodeidp)
+int fuse_inode_eq(struct inode *inode, void *_nodeidp)
 {
 	u64 nodeid = *(u64 *) _nodeidp;
 	if (get_node_id(inode) == nodeid)
@@ -1116,170 +1115,6 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mo
 	return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0, NULL); // XXX
 }
 
-struct fuse_inode_handle {
-	u64 nodeid;
-	u32 generation;
-};
-
-static struct dentry *fuse_get_dentry(struct super_block *sb,
-				      struct fuse_inode_handle *handle)
-{
-	struct fuse_conn *fc = get_fuse_conn_super(sb);
-	struct inode *inode;
-	struct dentry *entry;
-	int err = -ESTALE;
-
-	if (handle->nodeid == 0)
-		goto out_err;
-
-	inode = ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid);
-	if (!inode) {
-		struct fuse_entry_out *outarg;
-		const struct qstr name = QSTR_INIT(".", 1);
-
-		if (!fc->export_support)
-			goto out_err;
-
-		outarg = fuse_entry_out_alloc(fc);
-		if (!outarg) {
-			err = -ENOMEM;
-			goto out_err;
-		}
-
-		err = fuse_lookup_name(sb, handle->nodeid, NULL, &name, outarg,
-				       &inode);
-		kfree(outarg);
-		if (err && err != -ENOENT)
-			goto out_err;
-		if (err || !inode) {
-			err = -ESTALE;
-			goto out_err;
-		}
-		err = -EIO;
-		if (get_node_id(inode) != handle->nodeid)
-			goto out_iput;
-	}
-	err = -ESTALE;
-	if (inode->i_generation != handle->generation)
-		goto out_iput;
-
-	entry = d_obtain_alias(inode);
-	if (!IS_ERR(entry) && get_node_id(inode) != FUSE_ROOT_ID)
-		fuse_invalidate_entry_cache(entry);
-
-	return entry;
-
- out_iput:
-	iput(inode);
- out_err:
-	return ERR_PTR(err);
-}
-
-static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
-			   struct inode *parent)
-{
-	int len = parent ? 6 : 3;
-	u64 nodeid;
-	u32 generation;
-
-	if (*max_len < len) {
-		*max_len = len;
-		return  FILEID_INVALID;
-	}
-
-	nodeid = get_fuse_inode(inode)->nodeid;
-	generation = inode->i_generation;
-
-	fh[0] = (u32)(nodeid >> 32);
-	fh[1] = (u32)(nodeid & 0xffffffff);
-	fh[2] = generation;
-
-	if (parent) {
-		nodeid = get_fuse_inode(parent)->nodeid;
-		generation = parent->i_generation;
-
-		fh[3] = (u32)(nodeid >> 32);
-		fh[4] = (u32)(nodeid & 0xffffffff);
-		fh[5] = generation;
-	}
-
-	*max_len = len;
-	return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
-}
-
-static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
-		struct fid *fid, int fh_len, int fh_type)
-{
-	struct fuse_inode_handle handle;
-
-	if ((fh_type != FILEID_INO64_GEN &&
-	     fh_type != FILEID_INO64_GEN_PARENT) || fh_len < 3)
-		return NULL;
-
-	handle.nodeid = (u64) fid->raw[0] << 32;
-	handle.nodeid |= (u64) fid->raw[1];
-	handle.generation = fid->raw[2];
-	return fuse_get_dentry(sb, &handle);
-}
-
-static struct dentry *fuse_fh_to_parent(struct super_block *sb,
-		struct fid *fid, int fh_len, int fh_type)
-{
-	struct fuse_inode_handle parent;
-
-	if (fh_type != FILEID_INO64_GEN_PARENT || fh_len < 6)
-		return NULL;
-
-	parent.nodeid = (u64) fid->raw[3] << 32;
-	parent.nodeid |= (u64) fid->raw[4];
-	parent.generation = fid->raw[5];
-	return fuse_get_dentry(sb, &parent);
-}
-
-static struct dentry *fuse_get_parent(struct dentry *child)
-{
-	struct inode *child_inode = d_inode(child);
-	struct fuse_conn *fc = get_fuse_conn(child_inode);
-	struct inode *inode;
-	struct dentry *parent;
-	struct fuse_entry_out *outarg;
-	int err;
-
-	if (!fc->export_support)
-		return ERR_PTR(-ESTALE);
-
-	outarg = fuse_entry_out_alloc(fc);
-	if (!outarg)
-		return ERR_PTR(-ENOMEM);
-
-	err = fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode),
-			       child_inode, &dotdot_name, outarg, &inode);
-	kfree(outarg);
-	if (err) {
-		if (err == -ENOENT)
-			return ERR_PTR(-ESTALE);
-		return ERR_PTR(err);
-	}
-
-	parent = d_obtain_alias(inode);
-	if (!IS_ERR(parent) && get_node_id(inode) != FUSE_ROOT_ID)
-		fuse_invalidate_entry_cache(parent);
-
-	return parent;
-}
-
-/* only for fid encoding; no support for file handle */
-static const struct export_operations fuse_export_fid_operations = {
-	.encode_fh	= fuse_encode_fh,
-};
-
-static const struct export_operations fuse_export_operations = {
-	.fh_to_dentry	= fuse_fh_to_dentry,
-	.fh_to_parent	= fuse_fh_to_parent,
-	.encode_fh	= fuse_encode_fh,
-	.get_parent	= fuse_get_parent,
-};
-
 static const struct super_operations fuse_super_operations = {
 	.alloc_inode    = fuse_alloc_inode,
 	.free_inode     = fuse_free_inode,

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
                   ` (4 preceding siblings ...)
  2025-12-12 18:12 ` [RFC PATCH v2 5/6] fuse: factor out NFS export related code Luis Henriques
@ 2025-12-12 18:12 ` Luis Henriques
  2025-12-16 10:58   ` Miklos Szeredi
  2025-12-16 11:01   ` Amir Goldstein
  2025-12-14 17:02 ` [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Askar Safin
  6 siblings, 2 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-12 18:12 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev, Luis Henriques

This patch allows the NFS handle to use the new file handle provided by the
LOOKUP_HANDLE operation.  It still allows the usage of nodeid+generation as
an handle if this operation is not supported by the FUSE server or if no
handle is available for a specific inode.  I.e. it can mix both file handle
types FILEID_INO64_GEN{_PARENT} and FILEID_FUSE_WITH{OUT}_PARENT.

Signed-off-by: Luis Henriques <luis@igalia.com>
---
 fs/fuse/export.c         | 162 ++++++++++++++++++++++++++++++++++++---
 include/linux/exportfs.h |   7 ++
 2 files changed, 160 insertions(+), 9 deletions(-)

diff --git a/fs/fuse/export.c b/fs/fuse/export.c
index 4a9c95fe578e..b40d146a32f2 100644
--- a/fs/fuse/export.c
+++ b/fs/fuse/export.c
@@ -3,6 +3,7 @@
  * FUSE NFS export support.
  *
  * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+ * Copyright (C) 2025 Jump Trading LLC, author: Luis Henriques <luis@igalia.com>
  */
 
 #include "fuse_i.h"
@@ -10,7 +11,8 @@
 
 struct fuse_inode_handle {
 	u64 nodeid;
-	u32 generation;
+	u32 generation; /* XXX change to u64, and use fid->i64.ino in encode/decode? */
+	struct fuse_file_handle fh;
 };
 
 static struct dentry *fuse_get_dentry(struct super_block *sb,
@@ -67,8 +69,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
 	return ERR_PTR(err);
 }
 
-static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
-			   struct inode *parent)
+static int fuse_encode_gen_fh(struct inode *inode, u32 *fh, int *max_len,
+			      struct inode *parent)
 {
 	int len = parent ? 6 : 3;
 	u64 nodeid;
@@ -96,38 +98,180 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
 	}
 
 	*max_len = len;
+
 	return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
 }
 
-static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
-		struct fid *fid, int fh_len, int fh_type)
+static int fuse_encode_fuse_fh(struct inode *inode, u32 *fh, int *max_len,
+			       struct inode *parent)
+{
+	struct fuse_inode *fi = get_fuse_inode(inode);
+	struct fuse_inode *fip = NULL;
+	struct fuse_inode_handle *handle = (void *)fh;
+	int type = FILEID_FUSE_WITHOUT_PARENT;
+	int len, lenp = 0;
+	int buflen = *max_len << 2; /* max_len: number of words */
+
+	len = sizeof(struct fuse_inode_handle) + fi->fh->size;
+	if (parent) {
+		fip = get_fuse_inode(parent);
+		if (fip->fh && fip->fh->size) {
+			lenp = sizeof(struct fuse_inode_handle) +
+				fip->fh->size;
+			type = FILEID_FUSE_WITH_PARENT;
+		}
+	}
+
+	if (buflen < (len + lenp)) {
+		*max_len = (len + lenp) >> 2;
+		return  FILEID_INVALID;
+	}
+
+	handle[0].nodeid = fi->nodeid;
+	handle[0].generation = inode->i_generation;
+	memcpy(&handle[0].fh, fi->fh, len);
+	if (lenp) {
+		handle[1].nodeid = fip->nodeid;
+		handle[1].generation = parent->i_generation;
+		memcpy(&handle[1].fh, fip->fh, lenp);
+	}
+
+	*max_len = (len + lenp) >> 2;
+
+	return type;
+}
+
+static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
+			   struct inode *parent)
+{
+	struct fuse_conn *fc = get_fuse_conn(inode);
+	struct fuse_inode *fi = get_fuse_inode(inode);
+
+	if (fc->lookup_handle && fi->fh && fi->fh->size)
+		return fuse_encode_fuse_fh(inode, fh, max_len, parent);
+
+	return fuse_encode_gen_fh(inode, fh, max_len, parent);
+}
+
+static struct dentry *fuse_fh_gen_to_dentry(struct super_block *sb,
+					    struct fid *fid, int fh_len)
 {
 	struct fuse_inode_handle handle;
 
-	if ((fh_type != FILEID_INO64_GEN &&
-	     fh_type != FILEID_INO64_GEN_PARENT) || fh_len < 3)
+	if (fh_len < 3)
 		return NULL;
 
 	handle.nodeid = (u64) fid->raw[0] << 32;
 	handle.nodeid |= (u64) fid->raw[1];
 	handle.generation = fid->raw[2];
+
 	return fuse_get_dentry(sb, &handle);
 }
 
-static struct dentry *fuse_fh_to_parent(struct super_block *sb,
+static struct dentry *fuse_fh_fuse_to_dentry(struct super_block *sb,
+					     struct fid *fid, int fh_len)
+{
+	struct fuse_inode_handle *handle;
+	struct dentry *dentry;
+	int len = sizeof(struct fuse_file_handle);
+
+	handle = (void *)fid;
+	len += handle->fh.size;
+
+	if ((fh_len << 2) < len)
+		return NULL;
+
+	handle = kzalloc(len, GFP_KERNEL);
+	if (!handle)
+		return NULL;
+
+	memcpy(handle, fid, len);
+
+	dentry = fuse_get_dentry(sb, handle);
+	kfree(handle);
+
+	return dentry;
+}
+
+static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
 		struct fid *fid, int fh_len, int fh_type)
+{
+	switch (fh_type) {
+	case FILEID_INO64_GEN:
+	case FILEID_INO64_GEN_PARENT:
+		return fuse_fh_gen_to_dentry(sb, fid, fh_len);
+	case FILEID_FUSE_WITHOUT_PARENT:
+	case FILEID_FUSE_WITH_PARENT:
+		return fuse_fh_fuse_to_dentry(sb, fid, fh_len);
+	}
+
+	return NULL;
+
+}
+
+static struct dentry *fuse_fh_gen_to_parent(struct super_block *sb,
+					    struct fid *fid, int fh_len)
 {
 	struct fuse_inode_handle parent;
 
-	if (fh_type != FILEID_INO64_GEN_PARENT || fh_len < 6)
+	if (fh_len < 6)
 		return NULL;
 
 	parent.nodeid = (u64) fid->raw[3] << 32;
 	parent.nodeid |= (u64) fid->raw[4];
 	parent.generation = fid->raw[5];
+
 	return fuse_get_dentry(sb, &parent);
 }
 
+static struct dentry *fuse_fh_fuse_to_parent(struct super_block *sb,
+					     struct fid *fid, int fh_len)
+{
+	struct fuse_inode_handle *handle;
+	struct dentry *dentry;
+	int total_len;
+	int len;
+
+	handle = (void *)fid;
+	total_len = len = sizeof(struct fuse_inode_handle) + handle->fh.size;
+
+	if ((fh_len << 2) < total_len)
+		return NULL;
+
+	handle = (void *)(fid + len);
+	len = sizeof(struct fuse_file_handle) + handle->fh.size;
+	total_len += len;
+
+	if ((fh_len << 2) < total_len)
+		return NULL;
+	
+	handle = kzalloc(len, GFP_KERNEL);
+	if (!handle)
+		return NULL;
+
+	memcpy(handle, fid, len);
+
+	dentry = fuse_get_dentry(sb, handle);
+	kfree(handle);
+
+	return dentry;
+}
+
+static struct dentry *fuse_fh_to_parent(struct super_block *sb,
+		struct fid *fid, int fh_len, int fh_type)
+{
+	switch (fh_type) {
+	case FILEID_INO64_GEN:
+	case FILEID_INO64_GEN_PARENT:
+		return fuse_fh_gen_to_parent(sb, fid, fh_len);
+	case FILEID_FUSE_WITHOUT_PARENT:
+	case FILEID_FUSE_WITH_PARENT:
+		return fuse_fh_fuse_to_parent(sb, fid, fh_len);
+	}
+
+	return NULL;
+}
+
 static struct dentry *fuse_get_parent(struct dentry *child)
 {
 	struct inode *child_inode = d_inode(child);
diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h
index f0cf2714ec52..db783f6b28bc 100644
--- a/include/linux/exportfs.h
+++ b/include/linux/exportfs.h
@@ -110,6 +110,13 @@ enum fid_type {
 	 */
 	FILEID_INO64_GEN_PARENT = 0x82,
 
+	/*
+	 * 64 bit nodeid number, 32 bit generation number,
+	 * variable length handle.
+	 */
+	FILEID_FUSE_WITHOUT_PARENT = 0x91,
+	FILEID_FUSE_WITH_PARENT = 0x92,
+
 	/*
 	 * 128 bit child FID (struct lu_fid)
 	 * 128 bit parent FID (struct lu_fid)

^ permalink raw reply related	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 5/6] fuse: factor out NFS export related code
  2025-12-12 18:12 ` [RFC PATCH v2 5/6] fuse: factor out NFS export related code Luis Henriques
@ 2025-12-14 15:13   ` Amir Goldstein
  2025-12-15 12:05     ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2025-12-14 15:13 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Dec 12, 2025 at 7:13 PM Luis Henriques <luis@igalia.com> wrote:
>
> Move all the NFS-related code into a different file.  This is just
> preparatory work to be able to use the LOOKUP_HANDLE file handles as the NFS
> handles.
>
> Signed-off-by: Luis Henriques <luis@igalia.com>

Very nice.
Apart from minor nit below, feel free to add:

Reviewed-by: Amir Goldstein <amir73il@gmail.com>

> ---
>  fs/fuse/Makefile |   2 +-
>  fs/fuse/dir.c    |   1 +
>  fs/fuse/export.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++
>  fs/fuse/fuse_i.h |   6 ++
>  fs/fuse/inode.c  | 167 +--------------------------------------------
>  5 files changed, 183 insertions(+), 167 deletions(-)
>  create mode 100644 fs/fuse/export.c
>
> diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> index 22ad9538dfc4..1d1401658278 100644
> --- a/fs/fuse/Makefile
> +++ b/fs/fuse/Makefile
> @@ -12,7 +12,7 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
>
>  fuse-y := trace.o      # put trace.o first so we see ftrace errors sooner
>  fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
> -fuse-y += iomode.o
> +fuse-y += iomode.o export.o
>  fuse-$(CONFIG_FUSE_DAX) += dax.o
>  fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
>  fuse-$(CONFIG_SYSCTL) += sysctl.o
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index a6edb444180f..a885f1dc61eb 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -190,6 +190,7 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>
>                 args->opcode = FUSE_LOOKUP_HANDLE;
>                 args->out_argvar = true;
> +               args->out_argvar_idx = 0;
>

This change looks out of place.

Keep in mind that it may take me some time to get to the rest of the patches,
but this one was a low hanging review.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
                   ` (5 preceding siblings ...)
  2025-12-12 18:12 ` [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE Luis Henriques
@ 2025-12-14 17:02 ` Askar Safin
  2025-12-15 12:08   ` Luis Henriques
  6 siblings, 1 reply; 86+ messages in thread
From: Askar Safin @ 2025-12-14 17:02 UTC (permalink / raw)
  To: luis
  Cc: amir73il, bschubert, djwong, hbirthelmer, kchen, kernel-dev,
	linux-fsdevel, linux-kernel, mharvey, miklos

Luis Henriques <luis@igalia.com>:
> As I mentioned in the v1 cover letter, I've been working on implementing the
> FUSE_LOOKUP_HANDLE operation.  As I also mentioned, this is being done in
> the scope of a wider project, which is to be able to restart FUSE servers
> without the need to unmount the file systems.  For context, here are the
> links again: [0] [1].

Will this fix long-standing fuse+suspend problem, described here
https://lore.kernel.org/all/20250720205839.2919-1-safinaskar@zohomail.com/ ?

-- 
Askar Safin

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 5/6] fuse: factor out NFS export related code
  2025-12-14 15:13   ` Amir Goldstein
@ 2025-12-15 12:05     ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-15 12:05 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Miklos Szeredi, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Sun, Dec 14 2025, Amir Goldstein wrote:

> On Fri, Dec 12, 2025 at 7:13 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> Move all the NFS-related code into a different file.  This is just
>> preparatory work to be able to use the LOOKUP_HANDLE file handles as the NFS
>> handles.
>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>
> Very nice.
> Apart from minor nit below, feel free to add:

Thanks!

> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
>
>> ---
>>  fs/fuse/Makefile |   2 +-
>>  fs/fuse/dir.c    |   1 +
>>  fs/fuse/export.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++
>>  fs/fuse/fuse_i.h |   6 ++
>>  fs/fuse/inode.c  | 167 +--------------------------------------------
>>  5 files changed, 183 insertions(+), 167 deletions(-)
>>  create mode 100644 fs/fuse/export.c
>>
>> diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
>> index 22ad9538dfc4..1d1401658278 100644
>> --- a/fs/fuse/Makefile
>> +++ b/fs/fuse/Makefile
>> @@ -12,7 +12,7 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
>>
>>  fuse-y := trace.o      # put trace.o first so we see ftrace errors sooner
>>  fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
>> -fuse-y += iomode.o
>> +fuse-y += iomode.o export.o
>>  fuse-$(CONFIG_FUSE_DAX) += dax.o
>>  fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
>>  fuse-$(CONFIG_SYSCTL) += sysctl.o
>> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
>> index a6edb444180f..a885f1dc61eb 100644
>> --- a/fs/fuse/dir.c
>> +++ b/fs/fuse/dir.c
>> @@ -190,6 +190,7 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>>
>>                 args->opcode = FUSE_LOOKUP_HANDLE;
>>                 args->out_argvar = true;
>> +               args->out_argvar_idx = 0;
>>
>
> This change looks out of place.

Oops! Indeed, not sure how that happen.  This change belongs to patch

  fuse: store index of the variable length argument

> Keep in mind that it may take me some time to get to the rest of the patches,
> but this one was a low hanging review.

Sure, no problem.  I just wanted to send a new rev before everyone goes
off for EOY break.  But I understand  it'll probably take some time before
anyone has a look into it.

[ And maybe this -- FUSE restartability -- is even a topic for LSFMM. ]

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-14 17:02 ` [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Askar Safin
@ 2025-12-15 12:08   ` Luis Henriques
  2025-12-16  0:33     ` Askar Safin
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-15 12:08 UTC (permalink / raw)
  To: Askar Safin
  Cc: amir73il, bschubert, djwong, hbirthelmer, kchen, kernel-dev,
	linux-fsdevel, linux-kernel, mharvey, miklos

On Sun, Dec 14 2025, Askar Safin wrote:

> Luis Henriques <luis@igalia.com>:
>> As I mentioned in the v1 cover letter, I've been working on implementing the
>> FUSE_LOOKUP_HANDLE operation.  As I also mentioned, this is being done in
>> the scope of a wider project, which is to be able to restart FUSE servers
>> without the need to unmount the file systems.  For context, here are the
>> links again: [0] [1].
>
> Will this fix long-standing fuse+suspend problem, described here
> https://lore.kernel.org/all/20250720205839.2919-1-safinaskar@zohomail.com/ ?

No, this won't fix that.  This patchset is just an attempt to be a step
closer to be able to restart a FUSE server.  But other things will be
needed (including changes in the user-space server).

Cheers,
-- 
Luís


^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-12 18:12 ` [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support Luis Henriques
@ 2025-12-15 13:36   ` Bernd Schubert
  2025-12-15 17:06     ` Amir Goldstein
  2025-12-16 10:19   ` Miklos Szeredi
  1 sibling, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-15 13:36 UTC (permalink / raw)
  To: Luis Henriques, Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

Hi Luis,

I'm really sorry for late review.

On 12/12/25 19:12, Luis Henriques wrote:
> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
> operation.  It simply defines the new operation and the extra fuse_init_out
> field to set the maximum handle size.
> 
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>   fs/fuse/fuse_i.h          | 4 ++++
>   fs/fuse/inode.c           | 9 ++++++++-
>   include/uapi/linux/fuse.h | 8 +++++++-
>   3 files changed, 19 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 1792ee6f5da6..fad05fae7e54 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -909,6 +909,10 @@ struct fuse_conn {
>   	/* Is synchronous FUSE_INIT allowed? */
>   	unsigned int sync_init:1;
>   
> +	/** Is LOOKUP_HANDLE implemented by fs? */
> +	unsigned int lookup_handle:1;
> +	unsigned int max_handle_sz;
> +
>   	/* Use io_uring for communication */
>   	unsigned int io_uring;
>   
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index ef63300c634f..bc84e7ed1e3d 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -1465,6 +1465,13 @@ 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) &&
> +			    (arg->max_handle_sz > 0) &&
> +			    (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
> +				fc->lookup_handle = 1;
> +				fc->max_handle_sz = arg->max_handle_sz;

I don't have a strong opinion on it, maybe

if (flags & FUSE_HAS_LOOKUP_HANDLE) {
	if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
		pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
	} else {
		fc->lookup_handle = 1;
		fc->max_handle_sz = arg->max_handle_sz;
	}
}


I.e. give developers a warning what is wrong?


> +			}
>   		} else {
>   			ra_pages = fc->max_read / PAGE_SIZE;
>   			fc->no_lock = 1;
> @@ -1515,7 +1522,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;
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index c13e1f9a2f12..4acf71b407c9 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h

I forget to do that all the time myself, I think it should also increase the
minor version here and add add a comment for it.

> @@ -495,6 +495,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
> @@ -663,6 +664,7 @@ enum fuse_opcode {
>   	FUSE_TMPFILE		= 51,
>   	FUSE_STATX		= 52,
>   	FUSE_COPY_FILE_RANGE_64	= 53,
> +	FUSE_LOOKUP_HANDLE	= 54,
>   
>   	/* CUSE specific operations */
>   	CUSE_INIT		= 4096,
> @@ -908,6 +910,9 @@ struct fuse_init_in {
>   	uint32_t	unused[11];
>   };
>   
> +/* Same value as MAX_HANDLE_SZ */
> +#define FUSE_MAX_HANDLE_SZ 128
> +
>   #define FUSE_COMPAT_INIT_OUT_SIZE 8
>   #define FUSE_COMPAT_22_INIT_OUT_SIZE 24
>   
> @@ -925,7 +930,8 @@ struct fuse_init_out {
>   	uint32_t	flags2;
>   	uint32_t	max_stack_depth;
>   	uint16_t	request_timeout;
> -	uint16_t	unused[11];
> +	uint16_t	max_handle_sz;
> +	uint16_t	unused[10];
>   };

No strong opinion either and just given we are slowly running out of
available space. If we never expect to need more than 256 bytes,
maybe uint8_t?



Thanks,
Bernd


^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack
  2025-12-12 18:12 ` [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack Luis Henriques
@ 2025-12-15 14:03   ` Bernd Schubert
  2025-12-16 10:30     ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-15 14:03 UTC (permalink / raw)
  To: Luis Henriques, Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On 12/12/25 19:12, Luis Henriques wrote:
> This patch simply turns all struct fuse_entry_out instances that are
> allocated in the stack into dynamically allocated structs.  This is a
> preparation patch for further changes, including the extra helper function
> used to actually allocate the memory.
> 
> Also, remove all the memset()s that are used to zero-out these structures,
> as kzalloc() is being used.
> 
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>   fs/fuse/dir.c    | 139 ++++++++++++++++++++++++++++++-----------------
>   fs/fuse/fuse_i.h |   9 +++
>   fs/fuse/inode.c  |  20 +++++--
>   3 files changed, 114 insertions(+), 54 deletions(-)
> 
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 4dfe964a491c..e3fd5d148741 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -172,7 +172,6 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>   			     u64 nodeid, const struct qstr *name,
>   			     struct fuse_entry_out *outarg)
>   {
> -	memset(outarg, 0, sizeof(struct fuse_entry_out));
>   	args->opcode = FUSE_LOOKUP;
>   	args->nodeid = nodeid;
>   	args->in_numargs = 3;
> @@ -213,7 +212,7 @@ 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;
> +		struct fuse_entry_out *outarg;


How about using __free(kfree) for outarg? I think it would simplify
error handling a bit?
(Still reviewing other parts.)


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-15 13:36   ` Bernd Schubert
@ 2025-12-15 17:06     ` Amir Goldstein
  2025-12-15 17:11       ` Bernd Schubert
  0 siblings, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2025-12-15 17:06 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Luis Henriques, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Mon, Dec 15, 2025 at 2:36 PM Bernd Schubert <bschubert@ddn.com> wrote:
>
> Hi Luis,
>
> I'm really sorry for late review.
>
> On 12/12/25 19:12, Luis Henriques wrote:
> > This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
> > operation.  It simply defines the new operation and the extra fuse_init_out
> > field to set the maximum handle size.
> >
> > Signed-off-by: Luis Henriques <luis@igalia.com>
> > ---
> >   fs/fuse/fuse_i.h          | 4 ++++
> >   fs/fuse/inode.c           | 9 ++++++++-
> >   include/uapi/linux/fuse.h | 8 +++++++-
> >   3 files changed, 19 insertions(+), 2 deletions(-)
> >
> > diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> > index 1792ee6f5da6..fad05fae7e54 100644
> > --- a/fs/fuse/fuse_i.h
> > +++ b/fs/fuse/fuse_i.h
> > @@ -909,6 +909,10 @@ struct fuse_conn {
> >       /* Is synchronous FUSE_INIT allowed? */
> >       unsigned int sync_init:1;
> >
> > +     /** Is LOOKUP_HANDLE implemented by fs? */
> > +     unsigned int lookup_handle:1;
> > +     unsigned int max_handle_sz;
> > +
> >       /* Use io_uring for communication */
> >       unsigned int io_uring;
> >
> > diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> > index ef63300c634f..bc84e7ed1e3d 100644
> > --- a/fs/fuse/inode.c
> > +++ b/fs/fuse/inode.c
> > @@ -1465,6 +1465,13 @@ 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) &&
> > +                         (arg->max_handle_sz > 0) &&
> > +                         (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
> > +                             fc->lookup_handle = 1;
> > +                             fc->max_handle_sz = arg->max_handle_sz;
>
> I don't have a strong opinion on it, maybe
>
> if (flags & FUSE_HAS_LOOKUP_HANDLE) {
>         if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
>                 pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
>         } else {
>                 fc->lookup_handle = 1;
>                 fc->max_handle_sz = arg->max_handle_sz;

Why do we need both?
This seems redundant.
fc->max_handle_sz != 0 is equivalent to fc->lookup_handle
isnt it?

Thanks,
Amir.

>         }
> }
>
>
> I.e. give developers a warning what is wrong?
>
>
> > +                     }
> >               } else {
> >                       ra_pages = fc->max_read / PAGE_SIZE;
> >                       fc->no_lock = 1;
> > @@ -1515,7 +1522,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;
> > diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> > index c13e1f9a2f12..4acf71b407c9 100644
> > --- a/include/uapi/linux/fuse.h
> > +++ b/include/uapi/linux/fuse.h
>
> I forget to do that all the time myself, I think it should also increase the
> minor version here and add add a comment for it.
>
> > @@ -495,6 +495,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
> > @@ -663,6 +664,7 @@ enum fuse_opcode {
> >       FUSE_TMPFILE            = 51,
> >       FUSE_STATX              = 52,
> >       FUSE_COPY_FILE_RANGE_64 = 53,
> > +     FUSE_LOOKUP_HANDLE      = 54,
> >
> >       /* CUSE specific operations */
> >       CUSE_INIT               = 4096,
> > @@ -908,6 +910,9 @@ struct fuse_init_in {
> >       uint32_t        unused[11];
> >   };
> >
> > +/* Same value as MAX_HANDLE_SZ */
> > +#define FUSE_MAX_HANDLE_SZ 128
> > +
> >   #define FUSE_COMPAT_INIT_OUT_SIZE 8
> >   #define FUSE_COMPAT_22_INIT_OUT_SIZE 24
> >
> > @@ -925,7 +930,8 @@ struct fuse_init_out {
> >       uint32_t        flags2;
> >       uint32_t        max_stack_depth;
> >       uint16_t        request_timeout;
> > -     uint16_t        unused[11];
> > +     uint16_t        max_handle_sz;
> > +     uint16_t        unused[10];
> >   };
>
> No strong opinion either and just given we are slowly running out of
> available space. If we never expect to need more than 256 bytes,
> maybe uint8_t?
>
>
>
> Thanks,
> Bernd
>

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-15 17:06     ` Amir Goldstein
@ 2025-12-15 17:11       ` Bernd Schubert
  2025-12-15 18:09         ` Amir Goldstein
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-15 17:11 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Luis Henriques, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On 12/15/25 18:06, Amir Goldstein wrote:
> On Mon, Dec 15, 2025 at 2:36 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>
>> Hi Luis,
>>
>> I'm really sorry for late review.
>>
>> On 12/12/25 19:12, Luis Henriques wrote:
>>> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
>>> operation.  It simply defines the new operation and the extra fuse_init_out
>>> field to set the maximum handle size.
>>>
>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>> ---
>>>    fs/fuse/fuse_i.h          | 4 ++++
>>>    fs/fuse/inode.c           | 9 ++++++++-
>>>    include/uapi/linux/fuse.h | 8 +++++++-
>>>    3 files changed, 19 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
>>> index 1792ee6f5da6..fad05fae7e54 100644
>>> --- a/fs/fuse/fuse_i.h
>>> +++ b/fs/fuse/fuse_i.h
>>> @@ -909,6 +909,10 @@ struct fuse_conn {
>>>        /* Is synchronous FUSE_INIT allowed? */
>>>        unsigned int sync_init:1;
>>>
>>> +     /** Is LOOKUP_HANDLE implemented by fs? */
>>> +     unsigned int lookup_handle:1;
>>> +     unsigned int max_handle_sz;
>>> +
>>>        /* Use io_uring for communication */
>>>        unsigned int io_uring;
>>>
>>> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
>>> index ef63300c634f..bc84e7ed1e3d 100644
>>> --- a/fs/fuse/inode.c
>>> +++ b/fs/fuse/inode.c
>>> @@ -1465,6 +1465,13 @@ 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) &&
>>> +                         (arg->max_handle_sz > 0) &&
>>> +                         (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
>>> +                             fc->lookup_handle = 1;
>>> +                             fc->max_handle_sz = arg->max_handle_sz;
>>
>> I don't have a strong opinion on it, maybe
>>
>> if (flags & FUSE_HAS_LOOKUP_HANDLE) {
>>          if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
>>                  pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
>>          } else {
>>                  fc->lookup_handle = 1;
>>                  fc->max_handle_sz = arg->max_handle_sz;
> 
> Why do we need both?
> This seems redundant.
> fc->max_handle_sz != 0 is equivalent to fc->lookup_handle
> isnt it?

I'm personally always worried that some fuse server implementations just
don't zero the entire buffer. I.e. areas they don't know about.
If all servers are guaranteed to do that the flag would not be needed.

Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
@ 2025-12-15 17:39   ` Bernd Schubert
  2025-12-16 11:48     ` Luis Henriques
  2025-12-16  8:49   ` Joanne Koong
  2025-12-16 10:39   ` Miklos Szeredi
  2 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-15 17:39 UTC (permalink / raw)
  To: Luis Henriques, Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On 12/12/25 19:12, Luis Henriques wrote:
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.
> 
> Most of the other modifications in this patch are a fallout from these
> changes: because fuse_entry_out has been modified to include a variable size
> struct, every operation that receives such a parameter have to take this
> into account:
> 
>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> 
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>   fs/fuse/dev.c             | 16 +++++++
>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>   fs/fuse/readdir.c         | 10 ++---
>   include/uapi/linux/fuse.h |  8 ++++
>   6 files changed, 189 insertions(+), 35 deletions(-)
> 
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 629e8a043079..fc6acf45ae27 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>   
> +	if (fc->minor < 45) {

Could we use fc->lookup_handle here? Numbers are hard with backports

> +		switch (args->opcode) {
> +		case FUSE_CREATE:
> +		case FUSE_LINK:
> +		case FUSE_LOOKUP:
> +		case FUSE_MKDIR:
> +		case FUSE_MKNOD:
> +		/* XXX case FUSE_READDIRPLUS: */
> +		case FUSE_SYMLINK:
> +		case FUSE_TMPFILE:
> +			if (!WARN_ON_ONCE(args->in_numargs == 0))
> +				args->in_numargs--;
> +			args->out_args[0].size = FUSE_COMPAT_45_ENTRY_OUT_SIZE;
> +			break;
> +		}
> +	}
>   	if (fc->minor < 9) {
>   		switch (args->opcode) {
>   		case FUSE_LOOKUP:
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index e3fd5d148741..a6edb444180f 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -169,7 +169,8 @@ static void fuse_invalidate_entry(struct dentry *entry)
>   }
>   
>   static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
> -			     u64 nodeid, const struct qstr *name,
> +			     u64 nodeid, struct inode *dir,
> +			     const struct qstr *name,
>   			     struct fuse_entry_out *outarg)
>   {
>   	args->opcode = FUSE_LOOKUP;
> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>   	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].size = sizeof(*outarg) + outarg->fh.size;
>   	args->out_args[0].value = outarg;
> +
> +	if (fc->lookup_handle) {
> +		struct fuse_inode *fi = NULL;
> +
> +		args->opcode = FUSE_LOOKUP_HANDLE;
> +		args->out_argvar = true;
> +
> +		if (dir)
> +			fi = get_fuse_inode(dir);
> +
> +		if (fi && fi->fh) {
> +			args->in_numargs = 4;
> +			args->in_args[3].size = sizeof(*fi->fh) + fi->fh->size;
> +			args->in_args[3].value = fi->fh;
> +		}
> +	}
>   }
>   
>   /*
> @@ -240,7 +257,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
>   
>   		attr_version = fuse_get_attr_version(fm->fc);
>   
> -		fuse_lookup_init(fm->fc, &args, get_node_id(dir),
> +		fuse_lookup_init(fm->fc, &args, get_node_id(dir), dir,
>   				 name, outarg);
>   		ret = fuse_simple_request(fm, &args);
>   		/* Zero nodeid is same as -ENOENT */
> @@ -248,7 +265,8 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
>   			ret = -ENOENT;
>   		if (!ret) {
>   			fi = get_fuse_inode(inode);
> -			if (outarg->nodeid != get_node_id(inode) ||
> +			if (!fuse_file_handle_is_equal(fm->fc, fi->fh, &outarg->fh) ||
> +			    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);
> @@ -365,8 +383,9 @@ 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,
> -		     struct fuse_entry_out *outarg, struct inode **inode)
> +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
> +		     const struct qstr *name, struct fuse_entry_out *outarg,
> +		     struct inode **inode)
>   {
>   	struct fuse_mount *fm = get_fuse_mount_super(sb);
>   	FUSE_ARGS(args);
> @@ -388,14 +407,15 @@ 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(fm->fc, &args, nodeid, name, outarg);
> +	fuse_lookup_init(fm->fc, &args, nodeid, dir, name, outarg);
>   	err = fuse_simple_request(fm, &args);
>   	/* Zero nodeid is same as -ENOENT, but with valid timeout */
> -	if (err || !outarg->nodeid)
> +	if (err < 0 || !outarg->nodeid) // XXX err = size if args->out_argvar = true
>   		goto out_put_forget;
>   
>   	err = -EIO;
> -	if (fuse_invalid_attr(&outarg->attr))
> +	if (fuse_invalid_attr(&outarg->attr) ||
> +	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
>   		goto out_put_forget;
>   	if (outarg->nodeid == FUSE_ROOT_ID && outarg->generation != 0) {
>   		pr_warn_once("root generation should be zero\n");
> @@ -404,7 +424,8 @@ 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,
> +			   &outarg->fh);
>   	err = -ENOMEM;
>   	if (!*inode) {
>   		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
> @@ -440,14 +461,14 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
>   		return ERR_PTR(-ENOMEM);
>   
>   	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,
>   			       outarg, &inode);
>   	fuse_unlock_inode(dir, locked);
>   	if (err == -ENOENT) {
>   		outarg_valid = false;
>   		err = 0;
>   	}
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_err;
>   
>   	err = -EIO;
> @@ -689,24 +710,36 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
>   	args.in_args[1].size = entry->d_name.len + 1;
>   	args.in_args[1].value = entry->d_name.name;
>   	args.out_numargs = 2;
> -	args.out_args[0].size = sizeof(*outentry);
> +	args.out_args[0].size = sizeof(*outentry) + outentry->fh.size;
>   	args.out_args[0].value = outentry;
>   	/* Store outarg for fuse_finish_open() */
>   	outopenp = &ff->args->open_outarg;
>   	args.out_args[1].size = sizeof(*outopenp);
>   	args.out_args[1].value = outopenp;
>   
> +	if (fm->fc->lookup_handle) {
> +		fi = get_fuse_inode(dir);
> +		args.out_argvar = true;
> +		args.out_argvar_idx = 0;
> +		if (fi->fh) {
> +			args.in_numargs = 3;
> +			args.in_args[2].size = sizeof(*fi->fh) + fi->fh->size;
> +			args.in_args[2].value = fi->fh;
> +		}
> +	}
> +
>   	err = get_create_ext(idmap, &args, dir, entry, mode);
>   	if (err)
>   		goto out_free_outentry;
>   
>   	err = fuse_simple_idmap_request(idmap, fm, &args);
>   	free_ext_value(&args);
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_free_outentry;
>   
>   	err = -EIO;
>   	if (!S_ISREG(outentry->attr.mode) || invalid_nodeid(outentry->nodeid) ||
> +	    fuse_invalid_file_handle(fm->fc, &outentry->fh) ||
>   	    fuse_invalid_attr(&outentry->attr))
>   		goto out_free_outentry;
>   
> @@ -714,7 +747,8 @@ 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,
> +			  &outentry->fh);
>   	if (!inode) {
>   		flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
>   		fuse_sync_release(NULL, ff, flags);
> @@ -830,9 +864,22 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
>   
>   	args->nodeid = get_node_id(dir);
>   	args->out_numargs = 1;
> -	args->out_args[0].size = sizeof(*outarg);
> +	args->out_args[0].size = sizeof(*outarg) + outarg->fh.size;
>   	args->out_args[0].value = outarg;
>   
> +	if (fm->fc->lookup_handle) {
> +		struct fuse_inode *fi = get_fuse_inode(dir);
> +		int idx = args->in_numargs;
> +
> +		args->out_argvar = true;
> +		args->out_argvar_idx = 0;
> +		if (fi->fh && !WARN_ON_ONCE(idx >= 4)) {		
> +			args->in_args[idx].size = sizeof(*fi->fh) + fi->fh->size;
> +			args->in_args[idx].value = fi->fh;
> +			args->in_numargs++;
> +		}
> +	}
> +
>   	if (args->opcode != FUSE_LINK) {
>   		err = get_create_ext(idmap, args, dir, entry, mode);
>   		if (err)
> @@ -841,18 +888,20 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
>   
>   	err = fuse_simple_idmap_request(idmap, fm, args);
>   	free_ext_value(args);
> -	if (err)
> +	if (err < 0) // XXX err = size if args->out_argvar = true
>   		goto out_free_outarg;
>   
>   	err = -EIO;
> -	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr))
> +	if (invalid_nodeid(outarg->nodeid) || fuse_invalid_attr(&outarg->attr) ||
> +	    fuse_invalid_file_handle(fm->fc, &outarg->fh))
>   		goto out_free_outarg;
>   
>   	if ((outarg->attr.mode ^ mode) & S_IFMT)
>   		goto out_free_outarg;
>   
>   	inode = fuse_iget(dir->i_sb, outarg->nodeid, outarg->generation,
> -			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0);
> +			  &outarg->attr, ATTR_TIMEOUT(outarg), 0, 0,
> +			  &outarg->fh);
>   	if (!inode) {
>   		fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
>   		kfree(outarg);
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index fad05fae7e54..d0f3c81b5612 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -216,6 +216,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 */
> @@ -1067,6 +1069,26 @@ static inline int invalid_nodeid(u64 nodeid)
>   	return !nodeid || nodeid == FUSE_ROOT_ID;
>   }
>   
> +static inline bool fuse_invalid_file_handle(struct fuse_conn *fc,
> +					    struct fuse_file_handle *handle)
> +{
> +	if (!fc->lookup_handle)
> +		return false;
> +
> +	return !handle->size || (handle->size >= FUSE_MAX_HANDLE_SZ);
> +}
> +
> +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 || !fh2->size || // XXX more OPs without handle
> +	    ((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);
> @@ -1098,7 +1120,10 @@ static inline struct fuse_entry_out *fuse_entry_out_alloc(struct fuse_conn *fc)
>   {
>   	struct fuse_entry_out *entryout;
>   
> -	entryout = kzalloc(sizeof(*entryout), GFP_KERNEL_ACCOUNT);
> +	entryout = kzalloc(sizeof(*entryout) + fc->max_handle_sz,
> +			   GFP_KERNEL_ACCOUNT);
> +	if (entryout)
> +		entryout->fh.size = fc->max_handle_sz;
>   
>   	return entryout;
>   }
> @@ -1145,10 +1170,11 @@ 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,
> -		     struct fuse_entry_out *outarg, struct inode **inode);
> +int fuse_lookup_name(struct super_block *sb, u64 nodeid, struct inode *dir,
> +		     const struct qstr *name, struct fuse_entry_out *outarg,
> +		     struct inode **inode);
>   
>   /**
>    * Send FORGET command
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index bc84e7ed1e3d..f565f7e8118d 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -95,6 +95,25 @@ static struct fuse_submount_lookup *fuse_alloc_submount_lookup(void)
>   	return NULL;
>   }
>   
> +/*
> + * XXX postpone this allocation and later use the real size instead of max
> + */
> +static bool fuse_inode_handle_alloc(struct super_block *sb,
> +				    struct fuse_inode *fi)
> +{
> +	struct fuse_conn *fc = get_fuse_conn_super(sb);
> +
> +	fi->fh = NULL;
> +	if (fc->lookup_handle) {
> +		fi->fh = kzalloc(sizeof(*fi->fh) + fc->max_handle_sz,
> +				 GFP_KERNEL_ACCOUNT);
> +		if (!fi->fh)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
>   static struct inode *fuse_alloc_inode(struct super_block *sb)
>   {
>   	struct fuse_inode *fi;
> @@ -120,8 +139,15 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
>   	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
>   		fuse_inode_backing_set(fi, NULL);
>   
> +	if (!fuse_inode_handle_alloc(sb, fi))
> +		goto out_free_dax;
> +
>   	return &fi->inode;
>   
> +out_free_dax:
> +#ifdef CONFIG_FUSE_DAX
> +	kfree(fi->dax);
> +#endif
>   out_free_forget:
>   	kfree(fi->forget);
>   out_free:
> @@ -132,6 +158,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
>   static void fuse_free_inode(struct inode *inode)
>   {
>   	struct fuse_inode *fi = get_fuse_inode(inode);
> +	struct fuse_conn *fc = get_fuse_conn(inode);
>   
>   	mutex_destroy(&fi->mutex);
>   	kfree(fi->forget);
> @@ -141,6 +168,9 @@ static void fuse_free_inode(struct inode *inode)
>   	if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
>   		fuse_backing_put(fuse_inode_backing(fi));
>   
> +	if (fc->lookup_handle)
> +		kfree(fi->fh);
> +
>   	kmem_cache_free(fuse_inode_cachep, fi);
>   }
>   
> @@ -465,7 +495,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 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>   	if (!inode)
>   		return NULL;
>   
> +	fi = get_fuse_inode(inode);
> +	if (fc->lookup_handle) {
> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
> +			iput(inode);
> +			return NULL;

Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
was thinking "nice, fuse-server can decide to skip the fh for some
inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
with the other comment in fuse_inode_handle_alloc(), could be allocate
here to the needed size and allow fuse-server to not send the handle
for some files?

Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-15 17:11       ` Bernd Schubert
@ 2025-12-15 18:09         ` Amir Goldstein
  2025-12-15 18:23           ` Bernd Schubert
  2025-12-16 10:36           ` Luis Henriques
  0 siblings, 2 replies; 86+ messages in thread
From: Amir Goldstein @ 2025-12-15 18:09 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Luis Henriques, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Mon, Dec 15, 2025 at 6:11 PM Bernd Schubert <bschubert@ddn.com> wrote:
>
> On 12/15/25 18:06, Amir Goldstein wrote:
> > On Mon, Dec 15, 2025 at 2:36 PM Bernd Schubert <bschubert@ddn.com> wrote:
> >>
> >> Hi Luis,
> >>
> >> I'm really sorry for late review.
> >>
> >> On 12/12/25 19:12, Luis Henriques wrote:
> >>> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
> >>> operation.  It simply defines the new operation and the extra fuse_init_out
> >>> field to set the maximum handle size.
> >>>
> >>> Signed-off-by: Luis Henriques <luis@igalia.com>
> >>> ---
> >>>    fs/fuse/fuse_i.h          | 4 ++++
> >>>    fs/fuse/inode.c           | 9 ++++++++-
> >>>    include/uapi/linux/fuse.h | 8 +++++++-
> >>>    3 files changed, 19 insertions(+), 2 deletions(-)
> >>>
> >>> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> >>> index 1792ee6f5da6..fad05fae7e54 100644
> >>> --- a/fs/fuse/fuse_i.h
> >>> +++ b/fs/fuse/fuse_i.h
> >>> @@ -909,6 +909,10 @@ struct fuse_conn {
> >>>        /* Is synchronous FUSE_INIT allowed? */
> >>>        unsigned int sync_init:1;
> >>>
> >>> +     /** Is LOOKUP_HANDLE implemented by fs? */
> >>> +     unsigned int lookup_handle:1;
> >>> +     unsigned int max_handle_sz;
> >>> +

The bitwise section better be clearly separated from the non bitwise section,
but as I wrote, the bitwise one is not needed anyway.

> >>>        /* Use io_uring for communication */
> >>>        unsigned int io_uring;
> >>>
> >>> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> >>> index ef63300c634f..bc84e7ed1e3d 100644
> >>> --- a/fs/fuse/inode.c
> >>> +++ b/fs/fuse/inode.c
> >>> @@ -1465,6 +1465,13 @@ 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) &&
> >>> +                         (arg->max_handle_sz > 0) &&
> >>> +                         (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
> >>> +                             fc->lookup_handle = 1;
> >>> +                             fc->max_handle_sz = arg->max_handle_sz;
> >>
> >> I don't have a strong opinion on it, maybe
> >>
> >> if (flags & FUSE_HAS_LOOKUP_HANDLE) {
> >>          if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
> >>                  pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
> >>          } else {
> >>                  fc->lookup_handle = 1;
> >>                  fc->max_handle_sz = arg->max_handle_sz;
> >
> > Why do we need both?
> > This seems redundant.
> > fc->max_handle_sz != 0 is equivalent to fc->lookup_handle
> > isnt it?
>
> I'm personally always worried that some fuse server implementations just
> don't zero the entire buffer. I.e. areas they don't know about.
> If all servers are guaranteed to do that the flag would not be needed.
>

I did not mean that we should not use the flag FUSE_HAS_LOOKUP_HANDLE
we should definitely use it, but why do we need both
bool fc->lookup_handle and unsigned fc->max_handle_sz in fuse_conn?
The first one seems redundant.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-15 18:09         ` Amir Goldstein
@ 2025-12-15 18:23           ` Bernd Schubert
  2025-12-16 10:36           ` Luis Henriques
  1 sibling, 0 replies; 86+ messages in thread
From: Bernd Schubert @ 2025-12-15 18:23 UTC (permalink / raw)
  To: Amir Goldstein, Bernd Schubert
  Cc: Luis Henriques, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 12/15/25 19:09, Amir Goldstein wrote:
> On Mon, Dec 15, 2025 at 6:11 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>
>> On 12/15/25 18:06, Amir Goldstein wrote:
>>> On Mon, Dec 15, 2025 at 2:36 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>>>
>>>> Hi Luis,
>>>>
>>>> I'm really sorry for late review.
>>>>
>>>> On 12/12/25 19:12, Luis Henriques wrote:
>>>>> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
>>>>> operation.  It simply defines the new operation and the extra fuse_init_out
>>>>> field to set the maximum handle size.
>>>>>
>>>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>>>> ---
>>>>>    fs/fuse/fuse_i.h          | 4 ++++
>>>>>    fs/fuse/inode.c           | 9 ++++++++-
>>>>>    include/uapi/linux/fuse.h | 8 +++++++-
>>>>>    3 files changed, 19 insertions(+), 2 deletions(-)
>>>>>
>>>>> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
>>>>> index 1792ee6f5da6..fad05fae7e54 100644
>>>>> --- a/fs/fuse/fuse_i.h
>>>>> +++ b/fs/fuse/fuse_i.h
>>>>> @@ -909,6 +909,10 @@ struct fuse_conn {
>>>>>        /* Is synchronous FUSE_INIT allowed? */
>>>>>        unsigned int sync_init:1;
>>>>>
>>>>> +     /** Is LOOKUP_HANDLE implemented by fs? */
>>>>> +     unsigned int lookup_handle:1;
>>>>> +     unsigned int max_handle_sz;
>>>>> +
> 
> The bitwise section better be clearly separated from the non bitwise section,
> but as I wrote, the bitwise one is not needed anyway.
> 
>>>>>        /* Use io_uring for communication */
>>>>>        unsigned int io_uring;
>>>>>
>>>>> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
>>>>> index ef63300c634f..bc84e7ed1e3d 100644
>>>>> --- a/fs/fuse/inode.c
>>>>> +++ b/fs/fuse/inode.c
>>>>> @@ -1465,6 +1465,13 @@ 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) &&
>>>>> +                         (arg->max_handle_sz > 0) &&
>>>>> +                         (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
>>>>> +                             fc->lookup_handle = 1;
>>>>> +                             fc->max_handle_sz = arg->max_handle_sz;
>>>>
>>>> I don't have a strong opinion on it, maybe
>>>>
>>>> if (flags & FUSE_HAS_LOOKUP_HANDLE) {
>>>>          if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
>>>>                  pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
>>>>          } else {
>>>>                  fc->lookup_handle = 1;
>>>>                  fc->max_handle_sz = arg->max_handle_sz;
>>>
>>> Why do we need both?
>>> This seems redundant.
>>> fc->max_handle_sz != 0 is equivalent to fc->lookup_handle
>>> isnt it?
>>
>> I'm personally always worried that some fuse server implementations just
>> don't zero the entire buffer. I.e. areas they don't know about.
>> If all servers are guaranteed to do that the flag would not be needed.
>>
> 
> I did not mean that we should not use the flag FUSE_HAS_LOOKUP_HANDLE
> we should definitely use it, but why do we need both
> bool fc->lookup_handle and unsigned fc->max_handle_sz in fuse_conn?
> The first one seems redundant.

Ah sorry, you are absolutely right.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-15 12:08   ` Luis Henriques
@ 2025-12-16  0:33     ` Askar Safin
  2025-12-16 17:36       ` Luis Henriques
  2025-12-16 18:49       ` Bernd Schubert
  0 siblings, 2 replies; 86+ messages in thread
From: Askar Safin @ 2025-12-16  0:33 UTC (permalink / raw)
  To: Luis Henriques
  Cc: amir73il, bschubert, djwong, hbirthelmer, kchen, kernel-dev,
	linux-fsdevel, linux-kernel, mharvey, miklos

On Mon, Dec 15, 2025 at 3:08 PM Luis Henriques <luis@igalia.com> wrote:
> No, this won't fix that.  This patchset is just an attempt to be a step
> closer to be able to restart a FUSE server.  But other things will be
> needed (including changes in the user-space server).

So, fix for fuse+suspend is planned?

--
Askar Safin

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
  2025-12-15 17:39   ` Bernd Schubert
@ 2025-12-16  8:49   ` Joanne Koong
  2025-12-16  8:54     ` Bernd Schubert
  2025-12-16 10:39   ` Miklos Szeredi
  2 siblings, 1 reply; 86+ messages in thread
From: Joanne Koong @ 2025-12-16  8:49 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
>
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.
>
> Most of the other modifications in this patch are a fallout from these
> changes: because fuse_entry_out has been modified to include a variable size
> struct, every operation that receives such a parameter have to take this
> into account:
>
>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>  fs/fuse/dev.c             | 16 +++++++
>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>  fs/fuse/readdir.c         | 10 ++---
>  include/uapi/linux/fuse.h |  8 ++++
>  6 files changed, 189 insertions(+), 35 deletions(-)
>

Could you explain why the file handle size needs to be dynamically set
by the server instead of just from the kernel-side stipulating that
the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
like that would simplify a lot of the code logic here.

Thanks,
Joanne

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16  8:49   ` Joanne Koong
@ 2025-12-16  8:54     ` Bernd Schubert
  2025-12-17  0:32       ` Joanne Koong
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-16  8:54 UTC (permalink / raw)
  To: Joanne Koong, Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On 12/16/25 09:49, Joanne Koong wrote:
> On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
>>
>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> an extra inarg: the file handle for the parent directory (if it is
>> available).  Also, because fuse_entry_out now has a extra variable size
>> struct (the actual handle), it also sets the out_argvar flag to true.
>>
>> Most of the other modifications in this patch are a fallout from these
>> changes: because fuse_entry_out has been modified to include a variable size
>> struct, every operation that receives such a parameter have to take this
>> into account:
>>
>>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>  fs/fuse/dev.c             | 16 +++++++
>>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>  fs/fuse/readdir.c         | 10 ++---
>>  include/uapi/linux/fuse.h |  8 ++++
>>  6 files changed, 189 insertions(+), 35 deletions(-)
>>
> 
> Could you explain why the file handle size needs to be dynamically set
> by the server instead of just from the kernel-side stipulating that
> the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
> like that would simplify a lot of the code logic here.

It would be quite a waste if one only needs something like 12 or 16
bytes, wouldn't it? 128 is the upper limit, but most file systems won't
need that much.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-12 18:12 ` [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support Luis Henriques
  2025-12-15 13:36   ` Bernd Schubert
@ 2025-12-16 10:19   ` Miklos Szeredi
  2025-12-16 11:33     ` Luis Henriques
  1 sibling, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-16 10:19 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>
> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
> operation.  It simply defines the new operation and the extra fuse_init_out
> field to set the maximum handle size.

Since we are introducing a new op, I'd consider switching to
fuse_statx for the attributes.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack
  2025-12-15 14:03   ` Bernd Schubert
@ 2025-12-16 10:30     ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 10:30 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Mon, Dec 15 2025, Bernd Schubert wrote:

> On 12/12/25 19:12, Luis Henriques wrote:
>> This patch simply turns all struct fuse_entry_out instances that are
>> allocated in the stack into dynamically allocated structs.  This is a
>> preparation patch for further changes, including the extra helper function
>> used to actually allocate the memory.
>> 
>> Also, remove all the memset()s that are used to zero-out these structures,
>> as kzalloc() is being used.
>> 
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>   fs/fuse/dir.c    | 139 ++++++++++++++++++++++++++++++-----------------
>>   fs/fuse/fuse_i.h |   9 +++
>>   fs/fuse/inode.c  |  20 +++++--
>>   3 files changed, 114 insertions(+), 54 deletions(-)
>> 
>> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
>> index 4dfe964a491c..e3fd5d148741 100644
>> --- a/fs/fuse/dir.c
>> +++ b/fs/fuse/dir.c
>> @@ -172,7 +172,6 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>>   			     u64 nodeid, const struct qstr *name,
>>   			     struct fuse_entry_out *outarg)
>>   {
>> -	memset(outarg, 0, sizeof(struct fuse_entry_out));
>>   	args->opcode = FUSE_LOOKUP;
>>   	args->nodeid = nodeid;
>>   	args->in_numargs = 3;
>> @@ -213,7 +212,7 @@ 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;
>> +		struct fuse_entry_out *outarg;
>
>
> How about using __free(kfree) for outarg? I think it would simplify
> error handling a bit?
> (Still reviewing other parts.)

Ah, good suggestion.  I'll try to include the usage of that that clean-up
construct in the code.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-15 18:09         ` Amir Goldstein
  2025-12-15 18:23           ` Bernd Schubert
@ 2025-12-16 10:36           ` Luis Henriques
  1 sibling, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 10:36 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Bernd Schubert, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Mon, Dec 15 2025, Amir Goldstein wrote:

> On Mon, Dec 15, 2025 at 6:11 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>
>> On 12/15/25 18:06, Amir Goldstein wrote:
>> > On Mon, Dec 15, 2025 at 2:36 PM Bernd Schubert <bschubert@ddn.com> wrote:
>> >>
>> >> Hi Luis,
>> >>
>> >> I'm really sorry for late review.
>> >>
>> >> On 12/12/25 19:12, Luis Henriques wrote:
>> >>> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
>> >>> operation.  It simply defines the new operation and the extra fuse_init_out
>> >>> field to set the maximum handle size.
>> >>>
>> >>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> >>> ---
>> >>>    fs/fuse/fuse_i.h          | 4 ++++
>> >>>    fs/fuse/inode.c           | 9 ++++++++-
>> >>>    include/uapi/linux/fuse.h | 8 +++++++-
>> >>>    3 files changed, 19 insertions(+), 2 deletions(-)
>> >>>
>> >>> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
>> >>> index 1792ee6f5da6..fad05fae7e54 100644
>> >>> --- a/fs/fuse/fuse_i.h
>> >>> +++ b/fs/fuse/fuse_i.h
>> >>> @@ -909,6 +909,10 @@ struct fuse_conn {
>> >>>        /* Is synchronous FUSE_INIT allowed? */
>> >>>        unsigned int sync_init:1;
>> >>>
>> >>> +     /** Is LOOKUP_HANDLE implemented by fs? */
>> >>> +     unsigned int lookup_handle:1;
>> >>> +     unsigned int max_handle_sz;
>> >>> +
>
> The bitwise section better be clearly separated from the non bitwise section,
> but as I wrote, the bitwise one is not needed anyway.
>
>> >>>        /* Use io_uring for communication */
>> >>>        unsigned int io_uring;
>> >>>
>> >>> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
>> >>> index ef63300c634f..bc84e7ed1e3d 100644
>> >>> --- a/fs/fuse/inode.c
>> >>> +++ b/fs/fuse/inode.c
>> >>> @@ -1465,6 +1465,13 @@ 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) &&
>> >>> +                         (arg->max_handle_sz > 0) &&
>> >>> +                         (arg->max_handle_sz <= FUSE_MAX_HANDLE_SZ)) {
>> >>> +                             fc->lookup_handle = 1;
>> >>> +                             fc->max_handle_sz = arg->max_handle_sz;
>> >>
>> >> I don't have a strong opinion on it, maybe
>> >>
>> >> if (flags & FUSE_HAS_LOOKUP_HANDLE) {
>> >>          if (!arg->max_handle_sz || arg->max_handle_sz > FUSE_MAX_HANDLE_SZ) {
>> >>                  pr_info_ratelimited("Invalid fuse handle size %d\n, arg->max_handle_sz)
>> >>          } else {
>> >>                  fc->lookup_handle = 1;
>> >>                  fc->max_handle_sz = arg->max_handle_sz;

Right, having some warning here also makes sense.

>> >
>> > Why do we need both?
>> > This seems redundant.
>> > fc->max_handle_sz != 0 is equivalent to fc->lookup_handle
>> > isnt it?
>>
>> I'm personally always worried that some fuse server implementations just
>> don't zero the entire buffer. I.e. areas they don't know about.
>> If all servers are guaranteed to do that the flag would not be needed.
>>
>
> I did not mean that we should not use the flag FUSE_HAS_LOOKUP_HANDLE
> we should definitely use it, but why do we need both
> bool fc->lookup_handle and unsigned fc->max_handle_sz in fuse_conn?
> The first one seems redundant.

OK, I'll drop the ->lookup_handle.  At some point it seemed to make sense
to have both, but it doesn't anymore (maybe I had max_handle_sz stored
somewhere else, not sure).  Thank you for your comments.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
  2025-12-15 17:39   ` Bernd Schubert
  2025-12-16  8:49   ` Joanne Koong
@ 2025-12-16 10:39   ` Miklos Szeredi
  2025-12-16 10:51     ` Amir Goldstein
  2026-01-09 11:57     ` Luis Henriques
  2 siblings, 2 replies; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-16 10:39 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>
> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> an extra inarg: the file handle for the parent directory (if it is
> available).  Also, because fuse_entry_out now has a extra variable size
> struct (the actual handle), it also sets the out_argvar flag to true.

How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
would allow any operation to take a handle instead of a nodeid.

Yeah, the infrastructure for adding extensions is inadequate, but I
think the API is ready for this.

> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>         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].size = sizeof(*outarg) + outarg->fh.size;
> +
> +       if (fc->lookup_handle) {
> +               struct fuse_inode *fi = NULL;
> +
> +               args->opcode = FUSE_LOOKUP_HANDLE;
> +               args->out_argvar = true;

How about allocating variable length arguments on demand?  That would
allow getting rid of max_handle_size negotiation.

        args->out_var_alloc  = true;
        args->out_args[1].size = MAX_HANDLE_SZ;
        args->out_args[1].value = NULL; /* Will be allocated to the
actual size of the handle */

> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index 4acf71b407c9..b75744d2d75d 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -690,6 +690,13 @@ enum fuse_notify_code {
>  #define FUSE_MIN_READ_BUFFER 8192
>
>  #define FUSE_COMPAT_ENTRY_OUT_SIZE 120
> +#define FUSE_COMPAT_45_ENTRY_OUT_SIZE 128
> +
> +struct fuse_file_handle {
> +       uint32_t        size;

uint16_t should be enough for everyone ;)

> +       uint32_t        type;

Please make "type" just be a part of the opaque handle.  Makes
conversion from struct file_handle to struct fuse_file_handle slightly
more complex, but api clarity is more important imo.

> +       char            handle[0];

uint8_t handle[];

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16 10:39   ` Miklos Szeredi
@ 2025-12-16 10:51     ` Amir Goldstein
  2025-12-16 11:07       ` Miklos Szeredi
  2026-01-09 11:57     ` Luis Henriques
  1 sibling, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2025-12-16 10:51 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Luis Henriques, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, Dec 16, 2025 at 11:40 AM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
> >
> > The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> > an extra inarg: the file handle for the parent directory (if it is
> > available).  Also, because fuse_entry_out now has a extra variable size
> > struct (the actual handle), it also sets the out_argvar flag to true.
>
> How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
> would allow any operation to take a handle instead of a nodeid.
>
> Yeah, the infrastructure for adding extensions is inadequate, but I
> think the API is ready for this.
>
> > @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
> >         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].size = sizeof(*outarg) + outarg->fh.size;
> > +
> > +       if (fc->lookup_handle) {
> > +               struct fuse_inode *fi = NULL;
> > +
> > +               args->opcode = FUSE_LOOKUP_HANDLE;
> > +               args->out_argvar = true;
>
> How about allocating variable length arguments on demand?  That would
> allow getting rid of max_handle_size negotiation.
>
>         args->out_var_alloc  = true;
>         args->out_args[1].size = MAX_HANDLE_SZ;
>         args->out_args[1].value = NULL; /* Will be allocated to the
> actual size of the handle */
>

Keep in mind that we will need to store the file handle in the fuse_inode.
Don't you think that it is better to negotiate the max_handle_size even
if only as an upper limit?

Note that MAX_HANDLE_SZ is not even UAPI.
It is the upper limit of the moment for the open_by_handle_at() syscall.
FUSE protocol is by no means obligated to it, but sure we can use that
as the default upper limit.

From man open_by_handle_at.2:
       It  is  the caller's responsibility to allocate the structure
with a size large enough to hold the handle returned in f_handle.
       ...(The constant MAX_HANDLE_SZ, defined in <fcntl.h>, specifies
the maximum expected size for a file handle.
       It is not a guaranteed upper limit as future filesystems may
require more space.)

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-12 18:12 ` [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE Luis Henriques
@ 2025-12-16 10:58   ` Miklos Szeredi
  2025-12-16 17:06     ` Luis Henriques
  2025-12-16 11:01   ` Amir Goldstein
  1 sibling, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-16 10:58 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 12 Dec 2025 at 19:13, Luis Henriques <luis@igalia.com> wrote:
>
> This patch allows the NFS handle to use the new file handle provided by the
> LOOKUP_HANDLE operation.  It still allows the usage of nodeid+generation as
> an handle if this operation is not supported by the FUSE server or if no
> handle is available for a specific inode.  I.e. it can mix both file handle
> types FILEID_INO64_GEN{_PARENT} and FILEID_FUSE_WITH{OUT}_PARENT.
>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>  fs/fuse/export.c         | 162 ++++++++++++++++++++++++++++++++++++---
>  include/linux/exportfs.h |   7 ++
>  2 files changed, 160 insertions(+), 9 deletions(-)
>
> diff --git a/fs/fuse/export.c b/fs/fuse/export.c
> index 4a9c95fe578e..b40d146a32f2 100644
> --- a/fs/fuse/export.c
> +++ b/fs/fuse/export.c
> @@ -3,6 +3,7 @@
>   * FUSE NFS export support.
>   *
>   * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
> + * Copyright (C) 2025 Jump Trading LLC, author: Luis Henriques <luis@igalia.com>
>   */
>
>  #include "fuse_i.h"
> @@ -10,7 +11,8 @@
>
>  struct fuse_inode_handle {
>         u64 nodeid;
> -       u32 generation;
> +       u32 generation; /* XXX change to u64, and use fid->i64.ino in encode/decode? */
> +       struct fuse_file_handle fh;
>  };
>
>  static struct dentry *fuse_get_dentry(struct super_block *sb,
> @@ -67,8 +69,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
>         return ERR_PTR(err);
>  }
>
> -static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
> -                          struct inode *parent)
> +static int fuse_encode_gen_fh(struct inode *inode, u32 *fh, int *max_len,
> +                             struct inode *parent)
>  {
>         int len = parent ? 6 : 3;
>         u64 nodeid;
> @@ -96,38 +98,180 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>         }
>
>         *max_len = len;
> +
>         return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
>  }
>
> -static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
> -               struct fid *fid, int fh_len, int fh_type)
> +static int fuse_encode_fuse_fh(struct inode *inode, u32 *fh, int *max_len,
> +                              struct inode *parent)
> +{
> +       struct fuse_inode *fi = get_fuse_inode(inode);
> +       struct fuse_inode *fip = NULL;
> +       struct fuse_inode_handle *handle = (void *)fh;
> +       int type = FILEID_FUSE_WITHOUT_PARENT;
> +       int len, lenp = 0;
> +       int buflen = *max_len << 2; /* max_len: number of words */
> +
> +       len = sizeof(struct fuse_inode_handle) + fi->fh->size;
> +       if (parent) {
> +               fip = get_fuse_inode(parent);
> +               if (fip->fh && fip->fh->size) {
> +                       lenp = sizeof(struct fuse_inode_handle) +
> +                               fip->fh->size;
> +                       type = FILEID_FUSE_WITH_PARENT;
> +               }
> +       }
> +
> +       if (buflen < (len + lenp)) {
> +               *max_len = (len + lenp) >> 2;
> +               return  FILEID_INVALID;
> +       }
> +
> +       handle[0].nodeid = fi->nodeid;
> +       handle[0].generation = inode->i_generation;

I think it should be either

  - encode nodeid + generation (backward compatibility),

  - or encode file handle for servers that support it

but not both.

Which means that fuse_iget() must be able to search the cache based on
the handle as well, but that should not be too difficult to implement
(need to hash the file handle).

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-12 18:12 ` [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE Luis Henriques
  2025-12-16 10:58   ` Miklos Szeredi
@ 2025-12-16 11:01   ` Amir Goldstein
  2025-12-16 17:26     ` Luis Henriques
  1 sibling, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2025-12-16 11:01 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Dec 12, 2025 at 7:13 PM Luis Henriques <luis@igalia.com> wrote:
>
> This patch allows the NFS handle to use the new file handle provided by the
> LOOKUP_HANDLE operation.  It still allows the usage of nodeid+generation as
> an handle if this operation is not supported by the FUSE server or if no
> handle is available for a specific inode.  I.e. it can mix both file handle
> types FILEID_INO64_GEN{_PARENT} and FILEID_FUSE_WITH{OUT}_PARENT.

Why the mix? I dont get it.

>
> Signed-off-by: Luis Henriques <luis@igalia.com>
> ---
>  fs/fuse/export.c         | 162 ++++++++++++++++++++++++++++++++++++---
>  include/linux/exportfs.h |   7 ++
>  2 files changed, 160 insertions(+), 9 deletions(-)
>
> diff --git a/fs/fuse/export.c b/fs/fuse/export.c
> index 4a9c95fe578e..b40d146a32f2 100644
> --- a/fs/fuse/export.c
> +++ b/fs/fuse/export.c
> @@ -3,6 +3,7 @@
>   * FUSE NFS export support.
>   *
>   * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
> + * Copyright (C) 2025 Jump Trading LLC, author: Luis Henriques <luis@igalia.com>
>   */
>
>  #include "fuse_i.h"
> @@ -10,7 +11,8 @@
>
>  struct fuse_inode_handle {
>         u64 nodeid;
> -       u32 generation;
> +       u32 generation; /* XXX change to u64, and use fid->i64.ino in encode/decode? */
> +       struct fuse_file_handle fh;

If anything this should be a union
or maybe I don't understand what you were trying to accomplish.
Please try to explain the design better in the commit message.

>  };
>
>  static struct dentry *fuse_get_dentry(struct super_block *sb,
> @@ -67,8 +69,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
>         return ERR_PTR(err);
>  }
>
> -static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
> -                          struct inode *parent)
> +static int fuse_encode_gen_fh(struct inode *inode, u32 *fh, int *max_len,
> +                             struct inode *parent)
>  {
>         int len = parent ? 6 : 3;
>         u64 nodeid;
> @@ -96,38 +98,180 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>         }
>
>         *max_len = len;
> +
>         return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
>  }
>
> -static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
> -               struct fid *fid, int fh_len, int fh_type)
> +static int fuse_encode_fuse_fh(struct inode *inode, u32 *fh, int *max_len,
> +                              struct inode *parent)
> +{
> +       struct fuse_inode *fi = get_fuse_inode(inode);
> +       struct fuse_inode *fip = NULL;
> +       struct fuse_inode_handle *handle = (void *)fh;
> +       int type = FILEID_FUSE_WITHOUT_PARENT;
> +       int len, lenp = 0;
> +       int buflen = *max_len << 2; /* max_len: number of words */
> +
> +       len = sizeof(struct fuse_inode_handle) + fi->fh->size;
> +       if (parent) {
> +               fip = get_fuse_inode(parent);
> +               if (fip->fh && fip->fh->size) {
> +                       lenp = sizeof(struct fuse_inode_handle) +
> +                               fip->fh->size;
> +                       type = FILEID_FUSE_WITH_PARENT;
> +               }
> +       }
> +
> +       if (buflen < (len + lenp)) {
> +               *max_len = (len + lenp) >> 2;
> +               return  FILEID_INVALID;
> +       }
> +
> +       handle[0].nodeid = fi->nodeid;
> +       handle[0].generation = inode->i_generation;
> +       memcpy(&handle[0].fh, fi->fh, len);
> +       if (lenp) {
> +               handle[1].nodeid = fip->nodeid;
> +               handle[1].generation = parent->i_generation;
> +               memcpy(&handle[1].fh, fip->fh, lenp);
> +       }
> +
> +       *max_len = (len + lenp) >> 2;
> +
> +       return type;
> +}
> +
> +static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
> +                          struct inode *parent)
> +{
> +       struct fuse_conn *fc = get_fuse_conn(inode);
> +       struct fuse_inode *fi = get_fuse_inode(inode);
> +
> +       if (fc->lookup_handle && fi->fh && fi->fh->size)
> +               return fuse_encode_fuse_fh(inode, fh, max_len, parent);
> +
> +       return fuse_encode_gen_fh(inode, fh, max_len, parent);
> +}
> +
> +static struct dentry *fuse_fh_gen_to_dentry(struct super_block *sb,
> +                                           struct fid *fid, int fh_len)
>  {
>         struct fuse_inode_handle handle;
>
> -       if ((fh_type != FILEID_INO64_GEN &&
> -            fh_type != FILEID_INO64_GEN_PARENT) || fh_len < 3)
> +       if (fh_len < 3)

I dont understand why this was changed.

>                 return NULL;
>
>         handle.nodeid = (u64) fid->raw[0] << 32;
>         handle.nodeid |= (u64) fid->raw[1];
>         handle.generation = fid->raw[2];
> +
>         return fuse_get_dentry(sb, &handle);
>  }
>
> -static struct dentry *fuse_fh_to_parent(struct super_block *sb,
> +static struct dentry *fuse_fh_fuse_to_dentry(struct super_block *sb,
> +                                            struct fid *fid, int fh_len)
> +{
> +       struct fuse_inode_handle *handle;
> +       struct dentry *dentry;
> +       int len = sizeof(struct fuse_file_handle);
> +
> +       handle = (void *)fid;
> +       len += handle->fh.size;
> +
> +       if ((fh_len << 2) < len)
> +               return NULL;
> +
> +       handle = kzalloc(len, GFP_KERNEL);
> +       if (!handle)
> +               return NULL;
> +
> +       memcpy(handle, fid, len);
> +
> +       dentry = fuse_get_dentry(sb, handle);
> +       kfree(handle);
> +
> +       return dentry;
> +}
> +
> +static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
>                 struct fid *fid, int fh_len, int fh_type)
> +{
> +       switch (fh_type) {
> +       case FILEID_INO64_GEN:
> +       case FILEID_INO64_GEN_PARENT:
> +               return fuse_fh_gen_to_dentry(sb, fid, fh_len);
> +       case FILEID_FUSE_WITHOUT_PARENT:
> +       case FILEID_FUSE_WITH_PARENT:
> +               return fuse_fh_fuse_to_dentry(sb, fid, fh_len);
> +       }
> +
> +       return NULL;
> +
> +}
> +
> +static struct dentry *fuse_fh_gen_to_parent(struct super_block *sb,
> +                                           struct fid *fid, int fh_len)
>  {
>         struct fuse_inode_handle parent;
>
> -       if (fh_type != FILEID_INO64_GEN_PARENT || fh_len < 6)
> +       if (fh_len < 6)
>                 return NULL;
>
>         parent.nodeid = (u64) fid->raw[3] << 32;
>         parent.nodeid |= (u64) fid->raw[4];
>         parent.generation = fid->raw[5];
> +
>         return fuse_get_dentry(sb, &parent);
>  }
>
> +static struct dentry *fuse_fh_fuse_to_parent(struct super_block *sb,
> +                                            struct fid *fid, int fh_len)
> +{
> +       struct fuse_inode_handle *handle;
> +       struct dentry *dentry;
> +       int total_len;
> +       int len;
> +
> +       handle = (void *)fid;
> +       total_len = len = sizeof(struct fuse_inode_handle) + handle->fh.size;
> +
> +       if ((fh_len << 2) < total_len)
> +               return NULL;
> +
> +       handle = (void *)(fid + len);
> +       len = sizeof(struct fuse_file_handle) + handle->fh.size;
> +       total_len += len;
> +
> +       if ((fh_len << 2) < total_len)
> +               return NULL;
> +
> +       handle = kzalloc(len, GFP_KERNEL);
> +       if (!handle)
> +               return NULL;
> +
> +       memcpy(handle, fid, len);
> +
> +       dentry = fuse_get_dentry(sb, handle);
> +       kfree(handle);
> +
> +       return dentry;
> +}
> +
> +static struct dentry *fuse_fh_to_parent(struct super_block *sb,
> +               struct fid *fid, int fh_len, int fh_type)
> +{
> +       switch (fh_type) {
> +       case FILEID_INO64_GEN:
> +       case FILEID_INO64_GEN_PARENT:
> +               return fuse_fh_gen_to_parent(sb, fid, fh_len);
> +       case FILEID_FUSE_WITHOUT_PARENT:
> +       case FILEID_FUSE_WITH_PARENT:
> +               return fuse_fh_fuse_to_parent(sb, fid, fh_len);
> +       }
> +
> +       return NULL;
> +}
> +
>  static struct dentry *fuse_get_parent(struct dentry *child)
>  {
>         struct inode *child_inode = d_inode(child);
> diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h
> index f0cf2714ec52..db783f6b28bc 100644
> --- a/include/linux/exportfs.h
> +++ b/include/linux/exportfs.h
> @@ -110,6 +110,13 @@ enum fid_type {
>          */
>         FILEID_INO64_GEN_PARENT = 0x82,
>
> +       /*
> +        * 64 bit nodeid number, 32 bit generation number,
> +        * variable length handle.
> +        */
> +       FILEID_FUSE_WITHOUT_PARENT = 0x91,
> +       FILEID_FUSE_WITH_PARENT = 0x92,
> +


Do we even need a handle with inode+gen+server specific handle?
I didn't think we would. If we do, please explain why.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16 10:51     ` Amir Goldstein
@ 2025-12-16 11:07       ` Miklos Szeredi
  0 siblings, 0 replies; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-16 11:07 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Luis Henriques, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, 16 Dec 2025 at 11:52, Amir Goldstein <amir73il@gmail.com> wrote:

> Keep in mind that we will need to store the file handle in the fuse_inode.
> Don't you think that it is better to negotiate the max_handle_size even
> if only as an upper limit?

I don't see the point.  The handle will be allocated after the lookup
has completed, and by that time will will know the exact size, so the
maximum is irrelevant.  What am I missing?

> Note that MAX_HANDLE_SZ is not even UAPI.
> It is the upper limit of the moment for the open_by_handle_at() syscall.
> FUSE protocol is by no means obligated to it, but sure we can use that
> as the default upper limit.

Yeah, but even that is excessive, since this will be a non-connectable
one, and need to fit two of them plus a header into a connectable fuse
file handle.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-16 10:19   ` Miklos Szeredi
@ 2025-12-16 11:33     ` Luis Henriques
  2025-12-16 11:46       ` Miklos Szeredi
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 11:33 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, Dec 16 2025, Miklos Szeredi wrote:

> On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>>
>> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
>> operation.  It simply defines the new operation and the extra fuse_init_out
>> field to set the maximum handle size.
>
> Since we are introducing a new op, I'd consider switching to
> fuse_statx for the attributes.

So, just to clarify: you're suggesting that the maximum handle size should
instead be set using statx.  Which means that the first time the client
(kernel) needs to use this value it would emit a FUSE_STATX, and cache
that value for future use.  IIUC, this would also require a new mask
(STATX_MAX_HANDLE_SZ) to be added.  Did I got it right?

What would be the advantages of using statx?  Keeping the unused bytes in
struct fuse_init_out untouched?

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-16 11:33     ` Luis Henriques
@ 2025-12-16 11:46       ` Miklos Szeredi
  2025-12-16 12:02         ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-16 11:46 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, 16 Dec 2025 at 12:33, Luis Henriques <luis@igalia.com> wrote:
>
> On Tue, Dec 16 2025, Miklos Szeredi wrote:
>
> > On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
> >>
> >> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
> >> operation.  It simply defines the new operation and the extra fuse_init_out
> >> field to set the maximum handle size.
> >
> > Since we are introducing a new op, I'd consider switching to
> > fuse_statx for the attributes.
>
> So, just to clarify: you're suggesting that the maximum handle size should
> instead be set using statx.  Which means that the first time the client
> (kernel) needs to use this value it would emit a FUSE_STATX, and cache
> that value for future use.  IIUC, this would also require a new mask
> (STATX_MAX_HANDLE_SZ) to be added.  Did I got it right?

No, using statx as the output of LOOKUP_HANDLE is independent from the
other suggestion.

> What would be the advantages of using statx?  Keeping the unused bytes in
> struct fuse_init_out untouched?

Using fuse_statx instead of fuse_attr would allow btime (and other
attributes added to statx in the future) to be initialized on lookup.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-15 17:39   ` Bernd Schubert
@ 2025-12-16 11:48     ` Luis Henriques
  2025-12-17 10:18       ` Amir Goldstein
  2025-12-17 15:02       ` Bernd Schubert
  0 siblings, 2 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 11:48 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Mon, Dec 15 2025, Bernd Schubert wrote:

> On 12/12/25 19:12, Luis Henriques wrote:
>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> an extra inarg: the file handle for the parent directory (if it is
>> available).  Also, because fuse_entry_out now has a extra variable size
>> struct (the actual handle), it also sets the out_argvar flag to true.
>> 
>> Most of the other modifications in this patch are a fallout from these
>> changes: because fuse_entry_out has been modified to include a variable size
>> struct, every operation that receives such a parameter have to take this
>> into account:
>> 
>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>> 
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>   fs/fuse/dev.c             | 16 +++++++
>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>   fs/fuse/readdir.c         | 10 ++---
>>   include/uapi/linux/fuse.h |  8 ++++
>>   6 files changed, 189 insertions(+), 35 deletions(-)
>> 
>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>> index 629e8a043079..fc6acf45ae27 100644
>> --- a/fs/fuse/dev.c
>> +++ b/fs/fuse/dev.c
>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>   
>> +	if (fc->minor < 45) {
>
> Could we use fc->lookup_handle here? Numbers are hard with backports

To be honest, I'm not sure this code is correct.  I just followed the
pattern.  I'll need to dedicate some more time looking into this,
specially because the READDIRPLUS op handling is still TBD.

<snip>

>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>   	if (!inode)
>>   		return NULL;
>>   
>> +	fi = get_fuse_inode(inode);
>> +	if (fc->lookup_handle) {
>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>> +			iput(inode);
>> +			return NULL;
>
> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
> was thinking "nice, fuse-server can decide to skip the fh for some
> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
> with the other comment in fuse_inode_handle_alloc(), could be allocate
> here to the needed size and allow fuse-server to not send the handle
> for some files?

I'm not sure the code is consistent with this regard, but here I'm doing
exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
misunderstood your comment?

Regarding the comment in fuse_inode_handle_alloc(), I believe I'll need to
rethink about it anyway, specially after some of the comments I've already
seen from Miklos (which I'm still going through).

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support
  2025-12-16 11:46       ` Miklos Szeredi
@ 2025-12-16 12:02         ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 12:02 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, Dec 16 2025, Miklos Szeredi wrote:

> On Tue, 16 Dec 2025 at 12:33, Luis Henriques <luis@igalia.com> wrote:
>>
>> On Tue, Dec 16 2025, Miklos Szeredi wrote:
>>
>> > On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>> >>
>> >> This patch adds the initial infrastructure to implement the LOOKUP_HANDLE
>> >> operation.  It simply defines the new operation and the extra fuse_init_out
>> >> field to set the maximum handle size.
>> >
>> > Since we are introducing a new op, I'd consider switching to
>> > fuse_statx for the attributes.
>>
>> So, just to clarify: you're suggesting that the maximum handle size should
>> instead be set using statx.  Which means that the first time the client
>> (kernel) needs to use this value it would emit a FUSE_STATX, and cache
>> that value for future use.  IIUC, this would also require a new mask
>> (STATX_MAX_HANDLE_SZ) to be added.  Did I got it right?
>
> No, using statx as the output of LOOKUP_HANDLE is independent from the
> other suggestion.
>
>> What would be the advantages of using statx?  Keeping the unused bytes in
>> struct fuse_init_out untouched?
>
> Using fuse_statx instead of fuse_attr would allow btime (and other
> attributes added to statx in the future) to be initialized on lookup.

Oh! Of course, I totally misunderstood your suggestion.  Right, creating a
new *_out struct probably makes sense.  Something like a mix between
fuse_entry_out and fuse_statx_out.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-16 10:58   ` Miklos Szeredi
@ 2025-12-16 17:06     ` Luis Henriques
  2025-12-16 20:12       ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 17:06 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, Dec 16 2025, Miklos Szeredi wrote:

> On Fri, 12 Dec 2025 at 19:13, Luis Henriques <luis@igalia.com> wrote:
>>
>> This patch allows the NFS handle to use the new file handle provided by the
>> LOOKUP_HANDLE operation.  It still allows the usage of nodeid+generation as
>> an handle if this operation is not supported by the FUSE server or if no
>> handle is available for a specific inode.  I.e. it can mix both file handle
>> types FILEID_INO64_GEN{_PARENT} and FILEID_FUSE_WITH{OUT}_PARENT.
>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>  fs/fuse/export.c         | 162 ++++++++++++++++++++++++++++++++++++---
>>  include/linux/exportfs.h |   7 ++
>>  2 files changed, 160 insertions(+), 9 deletions(-)
>>
>> diff --git a/fs/fuse/export.c b/fs/fuse/export.c
>> index 4a9c95fe578e..b40d146a32f2 100644
>> --- a/fs/fuse/export.c
>> +++ b/fs/fuse/export.c
>> @@ -3,6 +3,7 @@
>>   * FUSE NFS export support.
>>   *
>>   * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
>> + * Copyright (C) 2025 Jump Trading LLC, author: Luis Henriques <luis@igalia.com>
>>   */
>>
>>  #include "fuse_i.h"
>> @@ -10,7 +11,8 @@
>>
>>  struct fuse_inode_handle {
>>         u64 nodeid;
>> -       u32 generation;
>> +       u32 generation; /* XXX change to u64, and use fid->i64.ino in encode/decode? */
>> +       struct fuse_file_handle fh;
>>  };
>>
>>  static struct dentry *fuse_get_dentry(struct super_block *sb,
>> @@ -67,8 +69,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
>>         return ERR_PTR(err);
>>  }
>>
>> -static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>> -                          struct inode *parent)
>> +static int fuse_encode_gen_fh(struct inode *inode, u32 *fh, int *max_len,
>> +                             struct inode *parent)
>>  {
>>         int len = parent ? 6 : 3;
>>         u64 nodeid;
>> @@ -96,38 +98,180 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>>         }
>>
>>         *max_len = len;
>> +
>>         return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
>>  }
>>
>> -static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
>> -               struct fid *fid, int fh_len, int fh_type)
>> +static int fuse_encode_fuse_fh(struct inode *inode, u32 *fh, int *max_len,
>> +                              struct inode *parent)
>> +{
>> +       struct fuse_inode *fi = get_fuse_inode(inode);
>> +       struct fuse_inode *fip = NULL;
>> +       struct fuse_inode_handle *handle = (void *)fh;
>> +       int type = FILEID_FUSE_WITHOUT_PARENT;
>> +       int len, lenp = 0;
>> +       int buflen = *max_len << 2; /* max_len: number of words */
>> +
>> +       len = sizeof(struct fuse_inode_handle) + fi->fh->size;
>> +       if (parent) {
>> +               fip = get_fuse_inode(parent);
>> +               if (fip->fh && fip->fh->size) {
>> +                       lenp = sizeof(struct fuse_inode_handle) +
>> +                               fip->fh->size;
>> +                       type = FILEID_FUSE_WITH_PARENT;
>> +               }
>> +       }
>> +
>> +       if (buflen < (len + lenp)) {
>> +               *max_len = (len + lenp) >> 2;
>> +               return  FILEID_INVALID;
>> +       }
>> +
>> +       handle[0].nodeid = fi->nodeid;
>> +       handle[0].generation = inode->i_generation;
>
> I think it should be either
>
>   - encode nodeid + generation (backward compatibility),
>
>   - or encode file handle for servers that support it
>
> but not both.

OK, in fact v1 was trying to do something like that, by defining the
handle with this:

struct fuse_inode_handle {
	u32 type;
	union {
		struct {
			u64 nodeid;
			u32 generation;
		};
		struct fuse_file_handle fh;
	};
};

(The 'type' is likely to be useless, as we know if the server supports fh
or not.)

> Which means that fuse_iget() must be able to search the cache based on
> the handle as well, but that should not be too difficult to implement
> (need to hash the file handle).

Right, I didn't got that far in v1.  I'll see what I can come up to.
Doing memcmp()s would definitely be too expensive, so using hashes is the
only way I guess.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-16 11:01   ` Amir Goldstein
@ 2025-12-16 17:26     ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 17:26 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Miklos Szeredi, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Tue, Dec 16 2025, Amir Goldstein wrote:

> On Fri, Dec 12, 2025 at 7:13 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> This patch allows the NFS handle to use the new file handle provided by the
>> LOOKUP_HANDLE operation.  It still allows the usage of nodeid+generation as
>> an handle if this operation is not supported by the FUSE server or if no
>> handle is available for a specific inode.  I.e. it can mix both file handle
>> types FILEID_INO64_GEN{_PARENT} and FILEID_FUSE_WITH{OUT}_PARENT.
>
> Why the mix? I dont get it.

Well, my thought was that if we have a nodeid for which we don't have a
file handle (FUSE_NODE_ID is the only example I can think of), we still
need to encode it.  And the idea was to use the old FILEID_INO64_GEN in
those cases, even if all other nodeids use the new FILEID.  But I guess we
can simply use the nodeid+gen file handle with the new FILEID anyway.

>>
>> Signed-off-by: Luis Henriques <luis@igalia.com>
>> ---
>>  fs/fuse/export.c         | 162 ++++++++++++++++++++++++++++++++++++---
>>  include/linux/exportfs.h |   7 ++
>>  2 files changed, 160 insertions(+), 9 deletions(-)
>>
>> diff --git a/fs/fuse/export.c b/fs/fuse/export.c
>> index 4a9c95fe578e..b40d146a32f2 100644
>> --- a/fs/fuse/export.c
>> +++ b/fs/fuse/export.c
>> @@ -3,6 +3,7 @@
>>   * FUSE NFS export support.
>>   *
>>   * Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
>> + * Copyright (C) 2025 Jump Trading LLC, author: Luis Henriques <luis@igalia.com>
>>   */
>>
>>  #include "fuse_i.h"
>> @@ -10,7 +11,8 @@
>>
>>  struct fuse_inode_handle {
>>         u64 nodeid;
>> -       u32 generation;
>> +       u32 generation; /* XXX change to u64, and use fid->i64.ino in encode/decode? */
>> +       struct fuse_file_handle fh;
>
> If anything this should be a union
> or maybe I don't understand what you were trying to accomplish.
> Please try to explain the design better in the commit message.

As Miklos also suggested using either nodeid+gen or the new fh, I guess it
makes sense to use a union here.

>>  };
>>
>>  static struct dentry *fuse_get_dentry(struct super_block *sb,
>> @@ -67,8 +69,8 @@ static struct dentry *fuse_get_dentry(struct super_block *sb,
>>         return ERR_PTR(err);
>>  }
>>
>> -static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>> -                          struct inode *parent)
>> +static int fuse_encode_gen_fh(struct inode *inode, u32 *fh, int *max_len,
>> +                             struct inode *parent)
>>  {
>>         int len = parent ? 6 : 3;
>>         u64 nodeid;
>> @@ -96,38 +98,180 @@ static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>>         }
>>
>>         *max_len = len;
>> +
>>         return parent ? FILEID_INO64_GEN_PARENT : FILEID_INO64_GEN;
>>  }
>>
>> -static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
>> -               struct fid *fid, int fh_len, int fh_type)
>> +static int fuse_encode_fuse_fh(struct inode *inode, u32 *fh, int *max_len,
>> +                              struct inode *parent)
>> +{
>> +       struct fuse_inode *fi = get_fuse_inode(inode);
>> +       struct fuse_inode *fip = NULL;
>> +       struct fuse_inode_handle *handle = (void *)fh;
>> +       int type = FILEID_FUSE_WITHOUT_PARENT;
>> +       int len, lenp = 0;
>> +       int buflen = *max_len << 2; /* max_len: number of words */
>> +
>> +       len = sizeof(struct fuse_inode_handle) + fi->fh->size;
>> +       if (parent) {
>> +               fip = get_fuse_inode(parent);
>> +               if (fip->fh && fip->fh->size) {
>> +                       lenp = sizeof(struct fuse_inode_handle) +
>> +                               fip->fh->size;
>> +                       type = FILEID_FUSE_WITH_PARENT;
>> +               }
>> +       }
>> +
>> +       if (buflen < (len + lenp)) {
>> +               *max_len = (len + lenp) >> 2;
>> +               return  FILEID_INVALID;
>> +       }
>> +
>> +       handle[0].nodeid = fi->nodeid;
>> +       handle[0].generation = inode->i_generation;
>> +       memcpy(&handle[0].fh, fi->fh, len);
>> +       if (lenp) {
>> +               handle[1].nodeid = fip->nodeid;
>> +               handle[1].generation = parent->i_generation;
>> +               memcpy(&handle[1].fh, fip->fh, lenp);
>> +       }
>> +
>> +       *max_len = (len + lenp) >> 2;
>> +
>> +       return type;
>> +}
>> +
>> +static int fuse_encode_fh(struct inode *inode, u32 *fh, int *max_len,
>> +                          struct inode *parent)
>> +{
>> +       struct fuse_conn *fc = get_fuse_conn(inode);
>> +       struct fuse_inode *fi = get_fuse_inode(inode);
>> +
>> +       if (fc->lookup_handle && fi->fh && fi->fh->size)
>> +               return fuse_encode_fuse_fh(inode, fh, max_len, parent);
>> +
>> +       return fuse_encode_gen_fh(inode, fh, max_len, parent);
>> +}
>> +
>> +static struct dentry *fuse_fh_gen_to_dentry(struct super_block *sb,
>> +                                           struct fid *fid, int fh_len)
>>  {
>>         struct fuse_inode_handle handle;
>>
>> -       if ((fh_type != FILEID_INO64_GEN &&
>> -            fh_type != FILEID_INO64_GEN_PARENT) || fh_len < 3)
>> +       if (fh_len < 3)
>
> I dont understand why this was changed.

The reason is because fuse_fh_to_dentry() is the only caller of this
function.  Since we already know the type is correct (fuse_fh_to_dentry()
checks it), there's not point checking it again here.

>>                 return NULL;
>>
>>         handle.nodeid = (u64) fid->raw[0] << 32;
>>         handle.nodeid |= (u64) fid->raw[1];
>>         handle.generation = fid->raw[2];
>> +
>>         return fuse_get_dentry(sb, &handle);
>>  }
>>
>> -static struct dentry *fuse_fh_to_parent(struct super_block *sb,
>> +static struct dentry *fuse_fh_fuse_to_dentry(struct super_block *sb,
>> +                                            struct fid *fid, int fh_len)
>> +{
>> +       struct fuse_inode_handle *handle;
>> +       struct dentry *dentry;
>> +       int len = sizeof(struct fuse_file_handle);
>> +
>> +       handle = (void *)fid;
>> +       len += handle->fh.size;
>> +
>> +       if ((fh_len << 2) < len)
>> +               return NULL;
>> +
>> +       handle = kzalloc(len, GFP_KERNEL);
>> +       if (!handle)
>> +               return NULL;
>> +
>> +       memcpy(handle, fid, len);
>> +
>> +       dentry = fuse_get_dentry(sb, handle);
>> +       kfree(handle);
>> +
>> +       return dentry;
>> +}
>> +
>> +static struct dentry *fuse_fh_to_dentry(struct super_block *sb,
>>                 struct fid *fid, int fh_len, int fh_type)
>> +{
>> +       switch (fh_type) {
>> +       case FILEID_INO64_GEN:
>> +       case FILEID_INO64_GEN_PARENT:
>> +               return fuse_fh_gen_to_dentry(sb, fid, fh_len);
>> +       case FILEID_FUSE_WITHOUT_PARENT:
>> +       case FILEID_FUSE_WITH_PARENT:
>> +               return fuse_fh_fuse_to_dentry(sb, fid, fh_len);
>> +       }
>> +
>> +       return NULL;
>> +
>> +}
>> +
>> +static struct dentry *fuse_fh_gen_to_parent(struct super_block *sb,
>> +                                           struct fid *fid, int fh_len)
>>  {
>>         struct fuse_inode_handle parent;
>>
>> -       if (fh_type != FILEID_INO64_GEN_PARENT || fh_len < 6)
>> +       if (fh_len < 6)
>>                 return NULL;
>>
>>         parent.nodeid = (u64) fid->raw[3] << 32;
>>         parent.nodeid |= (u64) fid->raw[4];
>>         parent.generation = fid->raw[5];
>> +
>>         return fuse_get_dentry(sb, &parent);
>>  }
>>
>> +static struct dentry *fuse_fh_fuse_to_parent(struct super_block *sb,
>> +                                            struct fid *fid, int fh_len)
>> +{
>> +       struct fuse_inode_handle *handle;
>> +       struct dentry *dentry;
>> +       int total_len;
>> +       int len;
>> +
>> +       handle = (void *)fid;
>> +       total_len = len = sizeof(struct fuse_inode_handle) + handle->fh.size;
>> +
>> +       if ((fh_len << 2) < total_len)
>> +               return NULL;
>> +
>> +       handle = (void *)(fid + len);
>> +       len = sizeof(struct fuse_file_handle) + handle->fh.size;
>> +       total_len += len;
>> +
>> +       if ((fh_len << 2) < total_len)
>> +               return NULL;
>> +
>> +       handle = kzalloc(len, GFP_KERNEL);
>> +       if (!handle)
>> +               return NULL;
>> +
>> +       memcpy(handle, fid, len);
>> +
>> +       dentry = fuse_get_dentry(sb, handle);
>> +       kfree(handle);
>> +
>> +       return dentry;
>> +}
>> +
>> +static struct dentry *fuse_fh_to_parent(struct super_block *sb,
>> +               struct fid *fid, int fh_len, int fh_type)
>> +{
>> +       switch (fh_type) {
>> +       case FILEID_INO64_GEN:
>> +       case FILEID_INO64_GEN_PARENT:
>> +               return fuse_fh_gen_to_parent(sb, fid, fh_len);
>> +       case FILEID_FUSE_WITHOUT_PARENT:
>> +       case FILEID_FUSE_WITH_PARENT:
>> +               return fuse_fh_fuse_to_parent(sb, fid, fh_len);
>> +       }
>> +
>> +       return NULL;
>> +}
>> +
>>  static struct dentry *fuse_get_parent(struct dentry *child)
>>  {
>>         struct inode *child_inode = d_inode(child);
>> diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h
>> index f0cf2714ec52..db783f6b28bc 100644
>> --- a/include/linux/exportfs.h
>> +++ b/include/linux/exportfs.h
>> @@ -110,6 +110,13 @@ enum fid_type {
>>          */
>>         FILEID_INO64_GEN_PARENT = 0x82,
>>
>> +       /*
>> +        * 64 bit nodeid number, 32 bit generation number,
>> +        * variable length handle.
>> +        */
>> +       FILEID_FUSE_WITHOUT_PARENT = 0x91,
>> +       FILEID_FUSE_WITH_PARENT = 0x92,
>> +
>
>
> Do we even need a handle with inode+gen+server specific handle?
> I didn't think we would. If we do, please explain why.

I *think* I've addressed this my other comments above.  Basically, I agree
that we don't need it.  FUSE_ROOT_ID would be the only exception, but
there's no need for this nodeid+gen+fh just because of that.  (Or maybe I
misunderstood this comment?)

(Oh, and thanks a lot everyone for the comments!  Much appreciated.)

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-16  0:33     ` Askar Safin
@ 2025-12-16 17:36       ` Luis Henriques
  2025-12-16 18:49       ` Bernd Schubert
  1 sibling, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-16 17:36 UTC (permalink / raw)
  To: Askar Safin
  Cc: amir73il, bschubert, djwong, hbirthelmer, kchen, kernel-dev,
	linux-fsdevel, linux-kernel, mharvey, miklos

On Tue, Dec 16 2025, Askar Safin wrote:

> On Mon, Dec 15, 2025 at 3:08 PM Luis Henriques <luis@igalia.com> wrote:
>> No, this won't fix that.  This patchset is just an attempt to be a step
>> closer to be able to restart a FUSE server.  But other things will be
>> needed (including changes in the user-space server).
>
> So, fix for fuse+suspend is planned?

To be honest, I really don't know.  I haven't looked closely into that
issue (time is scarce) but I'm not sure if the real problem you're
reporting in your link is a kernel issue or a problem in the user-space
implementation.  Have you tried to report it upstream (to the sshfs
maintainers)?

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-16  0:33     ` Askar Safin
  2025-12-16 17:36       ` Luis Henriques
@ 2025-12-16 18:49       ` Bernd Schubert
  2025-12-16 22:45         ` Askar Safin
                           ` (2 more replies)
  1 sibling, 3 replies; 86+ messages in thread
From: Bernd Schubert @ 2025-12-16 18:49 UTC (permalink / raw)
  To: Askar Safin, Luis Henriques
  Cc: amir73il, bschubert, djwong, hbirthelmer, kchen, kernel-dev,
	linux-fsdevel, linux-kernel, mharvey, miklos



On 12/16/25 01:33, Askar Safin wrote:
> On Mon, Dec 15, 2025 at 3:08 PM Luis Henriques <luis@igalia.com> wrote:
>> No, this won't fix that.  This patchset is just an attempt to be a step
>> closer to be able to restart a FUSE server.  But other things will be
>> needed (including changes in the user-space server).
> 
> So, fix for fuse+suspend is planned?

I have an idea about this, but this is not a one-liner and might not
work either. The hard part is wait_event() in request_wait_answer().
Probably also needs libfuse support, because libraries might complain
when they eventually reply, but the request is not there anymore. We can
work on this during my x-mas holidays (feel free to ping from next week
on), but please avoid posting about this in unrelated threads.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-16 17:06     ` Luis Henriques
@ 2025-12-16 20:12       ` Horst Birthelmer
  2025-12-17 17:02         ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2025-12-16 20:12 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Tue, Dec 16, 2025 at 05:06:25PM +0000, Luis Henriques wrote:
> On Tue, Dec 16 2025, Miklos Szeredi wrote:
...
> >
> > I think it should be either
> >
> >   - encode nodeid + generation (backward compatibility),
> >
> >   - or encode file handle for servers that support it
> >
> > but not both.
> 
> OK, in fact v1 was trying to do something like that, by defining the
> handle with this:
> 
> struct fuse_inode_handle {
> 	u32 type;
> 	union {
> 		struct {
> 			u64 nodeid;
> 			u32 generation;
> 		};
> 		struct fuse_file_handle fh;
> 	};
> };
> 
> (The 'type' is likely to be useless, as we know if the server supports fh
> or not.)
> 
> > Which means that fuse_iget() must be able to search the cache based on
> > the handle as well, but that should not be too difficult to implement
> > (need to hash the file handle).
> 
> Right, I didn't got that far in v1.  I'll see what I can come up to.
> Doing memcmp()s would definitely be too expensive, so using hashes is the
> only way I guess.
> 
Please excuse my ignorance, but why would memcmp() be too expensive for a proof of concept?
Inode handles are limited and the cache is somewhat limited.

Cheers,
Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-16 18:49       ` Bernd Schubert
@ 2025-12-16 22:45         ` Askar Safin
  2025-12-25  7:42         ` Askar Safin
  2026-01-04 22:38         ` Askar Safin
  2 siblings, 0 replies; 86+ messages in thread
From: Askar Safin @ 2025-12-16 22:45 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Luis Henriques, amir73il, bschubert, djwong, hbirthelmer, kchen,
	kernel-dev, linux-fsdevel, linux-kernel, mharvey, miklos

On Tue, Dec 16, 2025 at 9:49 PM Bernd Schubert <bernd@bsbernd.com> wrote:
> I have an idea about this, but this is not a one-liner and might not
> work either. The hard part is wait_event() in request_wait_answer().
> Probably also needs libfuse support, because libraries might complain
> when they eventually reply, but the request is not there anymore. We can
> work on this during my x-mas holidays (feel free to ping from next week
> on), but please avoid posting about this in unrelated threads.

Thank you!!! Please, CC me when you have some patches. I will test them.



-- 
Askar Safin

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16  8:54     ` Bernd Schubert
@ 2025-12-17  0:32       ` Joanne Koong
  2025-12-17  1:00         ` Darrick J. Wong
  0 siblings, 1 reply; 86+ messages in thread
From: Joanne Koong @ 2025-12-17  0:32 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Luis Henriques, Miklos Szeredi, Amir Goldstein, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Tue, Dec 16, 2025 at 4:54 PM Bernd Schubert <bschubert@ddn.com> wrote:
>
> On 12/16/25 09:49, Joanne Koong wrote:
> > On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
> >>
> >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> >> an extra inarg: the file handle for the parent directory (if it is
> >> available).  Also, because fuse_entry_out now has a extra variable size
> >> struct (the actual handle), it also sets the out_argvar flag to true.
> >>
> >> Most of the other modifications in this patch are a fallout from these
> >> changes: because fuse_entry_out has been modified to include a variable size
> >> struct, every operation that receives such a parameter have to take this
> >> into account:
> >>
> >>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> >>
> >> Signed-off-by: Luis Henriques <luis@igalia.com>
> >> ---
> >>  fs/fuse/dev.c             | 16 +++++++
> >>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
> >>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
> >>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
> >>  fs/fuse/readdir.c         | 10 ++---
> >>  include/uapi/linux/fuse.h |  8 ++++
> >>  6 files changed, 189 insertions(+), 35 deletions(-)
> >>
> >
> > Could you explain why the file handle size needs to be dynamically set
> > by the server instead of just from the kernel-side stipulating that
> > the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
> > like that would simplify a lot of the code logic here.
>
> It would be quite a waste if one only needs something like 12 or 16
> bytes, wouldn't it? 128 is the upper limit, but most file systems won't
> need that much.

Ah, I was looking at patch 5 + 6 and thought the use of the lookup
handle was for servers that want to pass it to NFS. But just read
through the previous threads and see now it's for adding server
restart. That makes sense, thanks for clarifying.

Thanks,
Joanne

>
>
> Thanks,
> Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17  0:32       ` Joanne Koong
@ 2025-12-17  1:00         ` Darrick J. Wong
  2025-12-17  2:48           ` Joanne Koong
  0 siblings, 1 reply; 86+ messages in thread
From: Darrick J. Wong @ 2025-12-17  1:00 UTC (permalink / raw)
  To: Joanne Koong
  Cc: Bernd Schubert, Luis Henriques, Miklos Szeredi, Amir Goldstein,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17, 2025 at 08:32:02AM +0800, Joanne Koong wrote:
> On Tue, Dec 16, 2025 at 4:54 PM Bernd Schubert <bschubert@ddn.com> wrote:
> >
> > On 12/16/25 09:49, Joanne Koong wrote:
> > > On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
> > >>
> > >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> > >> an extra inarg: the file handle for the parent directory (if it is
> > >> available).  Also, because fuse_entry_out now has a extra variable size
> > >> struct (the actual handle), it also sets the out_argvar flag to true.
> > >>
> > >> Most of the other modifications in this patch are a fallout from these
> > >> changes: because fuse_entry_out has been modified to include a variable size
> > >> struct, every operation that receives such a parameter have to take this
> > >> into account:
> > >>
> > >>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> > >>
> > >> Signed-off-by: Luis Henriques <luis@igalia.com>
> > >> ---
> > >>  fs/fuse/dev.c             | 16 +++++++
> > >>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
> > >>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
> > >>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
> > >>  fs/fuse/readdir.c         | 10 ++---
> > >>  include/uapi/linux/fuse.h |  8 ++++
> > >>  6 files changed, 189 insertions(+), 35 deletions(-)
> > >>
> > >
> > > Could you explain why the file handle size needs to be dynamically set
> > > by the server instead of just from the kernel-side stipulating that
> > > the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
> > > like that would simplify a lot of the code logic here.
> >
> > It would be quite a waste if one only needs something like 12 or 16
> > bytes, wouldn't it? 128 is the upper limit, but most file systems won't
> > need that much.
> 
> Ah, I was looking at patch 5 + 6 and thought the use of the lookup
> handle was for servers that want to pass it to NFS. But just read
> through the previous threads and see now it's for adding server
> restart. That makes sense, thanks for clarifying.

<-- wakes up from his long slumber

Why wouldn't you use the same handle format for NFS and for fuse server
restarts?  I would think that having separate formats would cause type
confusion and friction.

But that said, the fs implementation (fuse server) gets to decide the
handle format it uses, because they're just binary blobcookies to the
clients.  I think that's why the size is variable.

(Also I might be missing some context, if fuse handles aren't used in
the same places as nfs handles...)

--D

> Thanks,
> Joanne
> 
> >
> >
> > Thanks,
> > Bernd
> 

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17  1:00         ` Darrick J. Wong
@ 2025-12-17  2:48           ` Joanne Koong
  2025-12-17  9:38             ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Joanne Koong @ 2025-12-17  2:48 UTC (permalink / raw)
  To: Darrick J. Wong
  Cc: Bernd Schubert, Luis Henriques, Miklos Szeredi, Amir Goldstein,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17, 2025 at 9:00 AM Darrick J. Wong <djwong@kernel.org> wrote:
>
> On Wed, Dec 17, 2025 at 08:32:02AM +0800, Joanne Koong wrote:
> > On Tue, Dec 16, 2025 at 4:54 PM Bernd Schubert <bschubert@ddn.com> wrote:
> > >
> > > On 12/16/25 09:49, Joanne Koong wrote:
> > > > On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
> > > >>
> > > >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> > > >> an extra inarg: the file handle for the parent directory (if it is
> > > >> available).  Also, because fuse_entry_out now has a extra variable size
> > > >> struct (the actual handle), it also sets the out_argvar flag to true.
> > > >>
> > > >> Most of the other modifications in this patch are a fallout from these
> > > >> changes: because fuse_entry_out has been modified to include a variable size
> > > >> struct, every operation that receives such a parameter have to take this
> > > >> into account:
> > > >>
> > > >>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> > > >>
> > > >> Signed-off-by: Luis Henriques <luis@igalia.com>
> > > >> ---
> > > >>  fs/fuse/dev.c             | 16 +++++++
> > > >>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
> > > >>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
> > > >>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
> > > >>  fs/fuse/readdir.c         | 10 ++---
> > > >>  include/uapi/linux/fuse.h |  8 ++++
> > > >>  6 files changed, 189 insertions(+), 35 deletions(-)
> > > >>
> > > >
> > > > Could you explain why the file handle size needs to be dynamically set
> > > > by the server instead of just from the kernel-side stipulating that
> > > > the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
> > > > like that would simplify a lot of the code logic here.
> > >
> > > It would be quite a waste if one only needs something like 12 or 16
> > > bytes, wouldn't it? 128 is the upper limit, but most file systems won't
> > > need that much.
> >
> > Ah, I was looking at patch 5 + 6 and thought the use of the lookup
> > handle was for servers that want to pass it to NFS. But just read
> > through the previous threads and see now it's for adding server
> > restart. That makes sense, thanks for clarifying.
>
> <-- wakes up from his long slumber
>
> Why wouldn't you use the same handle format for NFS and for fuse server
> restarts?  I would think that having separate formats would cause type
> confusion and friction.
>
> But that said, the fs implementation (fuse server) gets to decide the
> handle format it uses, because they're just binary blobcookies to the
> clients.  I think that's why the size is variable.
>
> (Also I might be missing some context, if fuse handles aren't used in
> the same places as nfs handles...)

I think the fuse server would use the same NFS handle format if it
needs to pass it to NFS but with the server restart stuff, the handle
will also be used generically by servers that don't need to interact
with NFS (or at least that's my understanding of it though I might be
missing some context here too).

Thanks,
Joanne

>
> --D
>
> > Thanks,
> > Joanne
> >
> > >
> > >
> > > Thanks,
> > > Bernd
> >

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17  2:48           ` Joanne Koong
@ 2025-12-17  9:38             ` Luis Henriques
  2025-12-17 10:08               ` Miklos Szeredi
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-17  9:38 UTC (permalink / raw)
  To: Joanne Koong
  Cc: Darrick J. Wong, Bernd Schubert, Miklos Szeredi, Amir Goldstein,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17 2025, Joanne Koong wrote:

> On Wed, Dec 17, 2025 at 9:00 AM Darrick J. Wong <djwong@kernel.org> wrote:
>>
>> On Wed, Dec 17, 2025 at 08:32:02AM +0800, Joanne Koong wrote:
>> > On Tue, Dec 16, 2025 at 4:54 PM Bernd Schubert <bschubert@ddn.com> wrote:
>> > >
>> > > On 12/16/25 09:49, Joanne Koong wrote:
>> > > > On Sat, Dec 13, 2025 at 2:14 AM Luis Henriques <luis@igalia.com> wrote:
>> > > >>
>> > > >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> > > >> an extra inarg: the file handle for the parent directory (if it is
>> > > >> available).  Also, because fuse_entry_out now has a extra variable size
>> > > >> struct (the actual handle), it also sets the out_argvar flag to true.
>> > > >>
>> > > >> Most of the other modifications in this patch are a fallout from these
>> > > >> changes: because fuse_entry_out has been modified to include a variable size
>> > > >> struct, every operation that receives such a parameter have to take this
>> > > >> into account:
>> > > >>
>> > > >>   CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>> > > >>
>> > > >> Signed-off-by: Luis Henriques <luis@igalia.com>
>> > > >> ---
>> > > >>  fs/fuse/dev.c             | 16 +++++++
>> > > >>  fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>> > > >>  fs/fuse/fuse_i.h          | 34 +++++++++++++--
>> > > >>  fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>> > > >>  fs/fuse/readdir.c         | 10 ++---
>> > > >>  include/uapi/linux/fuse.h |  8 ++++
>> > > >>  6 files changed, 189 insertions(+), 35 deletions(-)
>> > > >>
>> > > >
>> > > > Could you explain why the file handle size needs to be dynamically set
>> > > > by the server instead of just from the kernel-side stipulating that
>> > > > the file handle size is FUSE_HANDLE_SZ (eg 128 bytes)? It seems to me
>> > > > like that would simplify a lot of the code logic here.
>> > >
>> > > It would be quite a waste if one only needs something like 12 or 16
>> > > bytes, wouldn't it? 128 is the upper limit, but most file systems won't
>> > > need that much.
>> >
>> > Ah, I was looking at patch 5 + 6 and thought the use of the lookup
>> > handle was for servers that want to pass it to NFS. But just read
>> > through the previous threads and see now it's for adding server
>> > restart. That makes sense, thanks for clarifying.
>>
>> <-- wakes up from his long slumber
>>
>> Why wouldn't you use the same handle format for NFS and for fuse server
>> restarts?  I would think that having separate formats would cause type
>> confusion and friction.
>>
>> But that said, the fs implementation (fuse server) gets to decide the
>> handle format it uses, because they're just binary blobcookies to the
>> clients.  I think that's why the size is variable.
>>
>> (Also I might be missing some context, if fuse handles aren't used in
>> the same places as nfs handles...)
>
> I think the fuse server would use the same NFS handle format if it
> needs to pass it to NFS but with the server restart stuff, the handle
> will also be used generically by servers that don't need to interact
> with NFS (or at least that's my understanding of it though I might be
> missing some context here too).

That is correct: the handle is to be used both by new FUSE lookup
operation, and by the NFS.  If the FUSE server does not implement this
LOOKUP_HANDLE operation (only the LOOKUP), then the old NFS handle
(nodeid+gen) is used instead.

(A question that just appeared in my mind is whether the two lookup
operations should be exclusive, i.e. if the kernel should explicitly avoid
sending a LOOKUP to a server that implements LOOKUP_HANDLE and vice-versa.
I _think_ the current implementation currently does this, but that was
mostly by accident.)

The relation of all this to the server restartability is that this new
handle will (eventually!) allow a server to recover a connection/mount
because it has to be a unique identifier (as opposed to the nodeid, which
can be reused).  But other use-cases have been mentioned, such as the
usage of open_by_handle_at() for example.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17  9:38             ` Luis Henriques
@ 2025-12-17 10:08               ` Miklos Szeredi
  2025-12-17 16:17                 ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2025-12-17 10:08 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Joanne Koong, Darrick J. Wong, Bernd Schubert, Amir Goldstein,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, 17 Dec 2025 at 10:38, Luis Henriques <luis@igalia.com> wrote:

> (A question that just appeared in my mind is whether the two lookup
> operations should be exclusive, i.e. if the kernel should explicitly avoid
> sending a LOOKUP to a server that implements LOOKUP_HANDLE and vice-versa.
> I _think_ the current implementation currently does this, but that was
> mostly by accident.)

Yes, I think LOOKUP_HANDLE should supersede LOOKUP.

Which begs the question: do we need nodeid and generation if file
handles are used by the server?

The generation is for guaranteeing uniqueness, and a file handle must
also provide that property, so it is clearly superfluous.

The nodeid is different.  It can be used as a temporary tag for easy
lookup of a cached object (e.g. cast to a pointer).  Since it's
temporary, it can't be embedded in the file handle.

The direct cache reference can be replaced with a hash table lookup
based on the file handle.  This would have an additional advantage,
namely that the lifetime of objects in the user cache are not strictly
synchronized with the kernel cache (FORGET completely omitted, or just
a hint).

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16 11:48     ` Luis Henriques
@ 2025-12-17 10:18       ` Amir Goldstein
  2025-12-17 14:45         ` Luis Henriques
  2025-12-17 15:02       ` Bernd Schubert
  1 sibling, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2025-12-17 10:18 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Bernd Schubert, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Tue, Dec 16, 2025 at 12:48 PM Luis Henriques <luis@igalia.com> wrote:
>
> On Mon, Dec 15 2025, Bernd Schubert wrote:
>
> > On 12/12/25 19:12, Luis Henriques wrote:
> >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
> >> an extra inarg: the file handle for the parent directory (if it is
> >> available).  Also, because fuse_entry_out now has a extra variable size
> >> struct (the actual handle), it also sets the out_argvar flag to true.
> >>
> >> Most of the other modifications in this patch are a fallout from these
> >> changes: because fuse_entry_out has been modified to include a variable size
> >> struct, every operation that receives such a parameter have to take this
> >> into account:
> >>
> >>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
> >>
> >> Signed-off-by: Luis Henriques <luis@igalia.com>
> >> ---
> >>   fs/fuse/dev.c             | 16 +++++++
> >>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
> >>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
> >>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
> >>   fs/fuse/readdir.c         | 10 ++---
> >>   include/uapi/linux/fuse.h |  8 ++++
> >>   6 files changed, 189 insertions(+), 35 deletions(-)
> >>
> >> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> >> index 629e8a043079..fc6acf45ae27 100644
> >> --- a/fs/fuse/dev.c
> >> +++ b/fs/fuse/dev.c
> >> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
> >>      if (fc->minor < 4 && args->opcode == FUSE_STATFS)
> >>              args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
> >>
> >> +    if (fc->minor < 45) {
> >
> > Could we use fc->lookup_handle here? Numbers are hard with backports
>
> To be honest, I'm not sure this code is correct.  I just followed the
> pattern.  I'll need to dedicate some more time looking into this,
> specially because the READDIRPLUS op handling is still TBD.
>
> <snip>
>
> >> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
> >>      if (!inode)
> >>              return NULL;
> >>
> >> +    fi = get_fuse_inode(inode);
> >> +    if (fc->lookup_handle) {
> >> +            if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
> >> +                    pr_err("NULL file handle for nodeid %llu\n", nodeid);
> >> +                    iput(inode);
> >> +                    return NULL;
> >
> > Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
> > was thinking "nice, fuse-server can decide to skip the fh for some
> > inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
> > with the other comment in fuse_inode_handle_alloc(), could be allocate
> > here to the needed size and allow fuse-server to not send the handle
> > for some files?
>
> I'm not sure the code is consistent with this regard, but here I'm doing
> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
> misunderstood your comment?
>

root inode is a special case.
The NFS server also does not encode the file handle for export root as
far as a I know
it just sends the special file handle type FILEID_ROOT to describe the
root inode
without any blob unique, so FUSE can do the same.

There is not much point in "looking up" the root inode neither by nodeid
nor by handle. unless is for making the code more generic.

I am not sure if FUSE server restart is supposed to revalidate the
root inode by file handle. That's kind of an administrative question about
the feature. My feeling is that it is not needed.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17 10:18       ` Amir Goldstein
@ 2025-12-17 14:45         ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-17 14:45 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Bernd Schubert, Miklos Szeredi, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17 2025, Amir Goldstein wrote:

> On Tue, Dec 16, 2025 at 12:48 PM Luis Henriques <luis@igalia.com> wrote:
>>
>> On Mon, Dec 15 2025, Bernd Schubert wrote:
>>
>> > On 12/12/25 19:12, Luis Henriques wrote:
>> >> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> >> an extra inarg: the file handle for the parent directory (if it is
>> >> available).  Also, because fuse_entry_out now has a extra variable size
>> >> struct (the actual handle), it also sets the out_argvar flag to true.
>> >>
>> >> Most of the other modifications in this patch are a fallout from these
>> >> changes: because fuse_entry_out has been modified to include a variable size
>> >> struct, every operation that receives such a parameter have to take this
>> >> into account:
>> >>
>> >>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>> >>
>> >> Signed-off-by: Luis Henriques <luis@igalia.com>
>> >> ---
>> >>   fs/fuse/dev.c             | 16 +++++++
>> >>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>> >>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>> >>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>> >>   fs/fuse/readdir.c         | 10 ++---
>> >>   include/uapi/linux/fuse.h |  8 ++++
>> >>   6 files changed, 189 insertions(+), 35 deletions(-)
>> >>
>> >> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>> >> index 629e8a043079..fc6acf45ae27 100644
>> >> --- a/fs/fuse/dev.c
>> >> +++ b/fs/fuse/dev.c
>> >> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>> >>      if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>> >>              args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>> >>
>> >> +    if (fc->minor < 45) {
>> >
>> > Could we use fc->lookup_handle here? Numbers are hard with backports
>>
>> To be honest, I'm not sure this code is correct.  I just followed the
>> pattern.  I'll need to dedicate some more time looking into this,
>> specially because the READDIRPLUS op handling is still TBD.
>>
>> <snip>
>>
>> >> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>> >>      if (!inode)
>> >>              return NULL;
>> >>
>> >> +    fi = get_fuse_inode(inode);
>> >> +    if (fc->lookup_handle) {
>> >> +            if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>> >> +                    pr_err("NULL file handle for nodeid %llu\n", nodeid);
>> >> +                    iput(inode);
>> >> +                    return NULL;
>> >
>> > Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>> > was thinking "nice, fuse-server can decide to skip the fh for some
>> > inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>> > with the other comment in fuse_inode_handle_alloc(), could be allocate
>> > here to the needed size and allow fuse-server to not send the handle
>> > for some files?
>>
>> I'm not sure the code is consistent with this regard, but here I'm doing
>> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
>> misunderstood your comment?
>>
>
> root inode is a special case.
> The NFS server also does not encode the file handle for export root as
> far as a I know
> it just sends the special file handle type FILEID_ROOT to describe the
> root inode
> without any blob unique, so FUSE can do the same.

OK, that makes sense.

> There is not much point in "looking up" the root inode neither by nodeid
> nor by handle. unless is for making the code more generic.
>
> I am not sure if FUSE server restart is supposed to revalidate the
> root inode by file handle. That's kind of an administrative question about
> the feature. My feeling is that it is not needed.

Thanks, Amir.  Looks like there's a lot in these v2 review comments that
I'll need to go through.  I'll try to put everything together and see what
I can cook for v3.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16 11:48     ` Luis Henriques
  2025-12-17 10:18       ` Amir Goldstein
@ 2025-12-17 15:02       ` Bernd Schubert
  2025-12-17 16:53         ` Luis Henriques
  1 sibling, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2025-12-17 15:02 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 12/16/25 12:48, Luis Henriques wrote:
> On Mon, Dec 15 2025, Bernd Schubert wrote:
> 
>> On 12/12/25 19:12, Luis Henriques wrote:
>>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>>> an extra inarg: the file handle for the parent directory (if it is
>>> available).  Also, because fuse_entry_out now has a extra variable size
>>> struct (the actual handle), it also sets the out_argvar flag to true.
>>>
>>> Most of the other modifications in this patch are a fallout from these
>>> changes: because fuse_entry_out has been modified to include a variable size
>>> struct, every operation that receives such a parameter have to take this
>>> into account:
>>>
>>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>>>
>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>> ---
>>>   fs/fuse/dev.c             | 16 +++++++
>>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>>   fs/fuse/readdir.c         | 10 ++---
>>>   include/uapi/linux/fuse.h |  8 ++++
>>>   6 files changed, 189 insertions(+), 35 deletions(-)
>>>
>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>> index 629e8a043079..fc6acf45ae27 100644
>>> --- a/fs/fuse/dev.c
>>> +++ b/fs/fuse/dev.c
>>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>>   
>>> +	if (fc->minor < 45) {
>>
>> Could we use fc->lookup_handle here? Numbers are hard with backports
> 
> To be honest, I'm not sure this code is correct.  I just followed the
> pattern.  I'll need to dedicate some more time looking into this,
> specially because the READDIRPLUS op handling is still TBD.
> 
> <snip>
> 
>>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>>   	if (!inode)
>>>   		return NULL;
>>>   
>>> +	fi = get_fuse_inode(inode);
>>> +	if (fc->lookup_handle) {
>>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>>> +			iput(inode);
>>> +			return NULL;
>>
>> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>> was thinking "nice, fuse-server can decide to skip the fh for some
>> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>> with the other comment in fuse_inode_handle_alloc(), could be allocate
>> here to the needed size and allow fuse-server to not send the handle
>> for some files?
> 
> I'm not sure the code is consistent with this regard, but here I'm doing
> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
> misunderstood your comment?

Sorry for late reply.

Yeah sorry, what I meant is that the file handle size might be different
for any of the inodes, in between 0 and max-size for any of the inodes?


Thanks,
Bernd



^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17 10:08               ` Miklos Szeredi
@ 2025-12-17 16:17                 ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-17 16:17 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Joanne Koong, Darrick J. Wong, Bernd Schubert, Amir Goldstein,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17 2025, Miklos Szeredi wrote:

> On Wed, 17 Dec 2025 at 10:38, Luis Henriques <luis@igalia.com> wrote:
>
>> (A question that just appeared in my mind is whether the two lookup
>> operations should be exclusive, i.e. if the kernel should explicitly avoid
>> sending a LOOKUP to a server that implements LOOKUP_HANDLE and vice-versa.
>> I _think_ the current implementation currently does this, but that was
>> mostly by accident.)
>
> Yes, I think LOOKUP_HANDLE should supersede LOOKUP.

Ack.

> Which begs the question: do we need nodeid and generation if file
> handles are used by the server?
>
> The generation is for guaranteeing uniqueness, and a file handle must
> also provide that property, so it is clearly superfluous.
>
> The nodeid is different.  It can be used as a temporary tag for easy
> lookup of a cached object (e.g. cast to a pointer).  Since it's
> temporary, it can't be embedded in the file handle.
>
> The direct cache reference can be replaced with a hash table lookup
> based on the file handle.  This would have an additional advantage,
> namely that the lifetime of objects in the user cache are not strictly
> synchronized with the kernel cache (FORGET completely omitted, or just
> a hint).

OK, this will require some more (or a lot more!) thinking from my side.
There are already several big(ish) suggestions I've started looking into,
and I need to go through them again.  Slowly ;-)

It's not clear to me at this point how to keep using nodeid+gen for
backward compatibility and replacing it by an hash table for the new
operation.  At first, it looks like a lot of code complexity will be
required for that.  But as I said, I'll need to go back and start
experimenting.

Other big change is to use fuse_ext_header instead of a variable size arg:
sounds interesting but will require more experimentation.  So, time for
going back to the drawing board!

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-17 15:02       ` Bernd Schubert
@ 2025-12-17 16:53         ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2025-12-17 16:53 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Wed, Dec 17 2025, Bernd Schubert wrote:

> On 12/16/25 12:48, Luis Henriques wrote:
>> On Mon, Dec 15 2025, Bernd Schubert wrote:
>> 
>>> On 12/12/25 19:12, Luis Henriques wrote:
>>>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>>>> an extra inarg: the file handle for the parent directory (if it is
>>>> available).  Also, because fuse_entry_out now has a extra variable size
>>>> struct (the actual handle), it also sets the out_argvar flag to true.
>>>>
>>>> Most of the other modifications in this patch are a fallout from these
>>>> changes: because fuse_entry_out has been modified to include a variable size
>>>> struct, every operation that receives such a parameter have to take this
>>>> into account:
>>>>
>>>>    CREATE, LINK, LOOKUP, MKDIR, MKNOD, READDIRPLUS, SYMLINK, TMPFILE
>>>>
>>>> Signed-off-by: Luis Henriques <luis@igalia.com>
>>>> ---
>>>>   fs/fuse/dev.c             | 16 +++++++
>>>>   fs/fuse/dir.c             | 87 ++++++++++++++++++++++++++++++---------
>>>>   fs/fuse/fuse_i.h          | 34 +++++++++++++--
>>>>   fs/fuse/inode.c           | 69 +++++++++++++++++++++++++++----
>>>>   fs/fuse/readdir.c         | 10 ++---
>>>>   include/uapi/linux/fuse.h |  8 ++++
>>>>   6 files changed, 189 insertions(+), 35 deletions(-)
>>>>
>>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>>> index 629e8a043079..fc6acf45ae27 100644
>>>> --- a/fs/fuse/dev.c
>>>> +++ b/fs/fuse/dev.c
>>>> @@ -606,6 +606,22 @@ static void fuse_adjust_compat(struct fuse_conn *fc, struct fuse_args *args)
>>>>   	if (fc->minor < 4 && args->opcode == FUSE_STATFS)
>>>>   		args->out_args[0].size = FUSE_COMPAT_STATFS_SIZE;
>>>>   
>>>> +	if (fc->minor < 45) {
>>>
>>> Could we use fc->lookup_handle here? Numbers are hard with backports
>> 
>> To be honest, I'm not sure this code is correct.  I just followed the
>> pattern.  I'll need to dedicate some more time looking into this,
>> specially because the READDIRPLUS op handling is still TBD.
>> 
>> <snip>
>> 
>>>> @@ -505,6 +535,30 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
>>>>   	if (!inode)
>>>>   		return NULL;
>>>>   
>>>> +	fi = get_fuse_inode(inode);
>>>> +	if (fc->lookup_handle) {
>>>> +		if ((fh == NULL) && (nodeid != FUSE_ROOT_ID)) {
>>>> +			pr_err("NULL file handle for nodeid %llu\n", nodeid);
>>>> +			iput(inode);
>>>> +			return NULL;
>>>
>>> Hmm, so there are conditions like "if (fi && fi->fh) {" in lookup and I
>>> was thinking "nice, fuse-server can decide to skip the fh for some
>>> inodes like FUSE_ROOT_ID. But now it gets forbidden here. In combination
>>> with the other comment in fuse_inode_handle_alloc(), could be allocate
>>> here to the needed size and allow fuse-server to not send the handle
>>> for some files?
>> 
>> I'm not sure the code is consistent with this regard, but here I'm doing
>> exactly that: allowing the fh to be NULL only for FUSE_ROOT_ID.  Or did I
>> misunderstood your comment?
>
> Sorry for late reply.
>
> Yeah sorry, what I meant is that the file handle size might be different
> for any of the inodes, in between 0 and max-size for any of the inodes?

So, as per the other discussion of this patch, Miklos was suggesting the
maximum size negotiation could be totally removed, and the allocation
would be done on-demand [1].  (But probably still keeping an hard limit on
MAX_HANDLE_SZ.)

In that case, different inodes could indeed have different file handle
sizes, defined by the server.  Which would be nice, I guess.

But as I also mentioned in other places, there are already a bunch of
changes, and I need going back to the drawing board :-)

[1] https://lore.kernel.org/all/CAJfpegszP+2XA=vADK4r09KU30BQd-r9sNu2Dog88yLG8iV7WQ@mail.gmail.com

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-16 20:12       ` Horst Birthelmer
@ 2025-12-17 17:02         ` Luis Henriques
  2025-12-17 18:02           ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2025-12-17 17:02 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Tue, Dec 16 2025, Horst Birthelmer wrote:

> On Tue, Dec 16, 2025 at 05:06:25PM +0000, Luis Henriques wrote:
>> On Tue, Dec 16 2025, Miklos Szeredi wrote:
> ...
>> >
>> > I think it should be either
>> >
>> >   - encode nodeid + generation (backward compatibility),
>> >
>> >   - or encode file handle for servers that support it
>> >
>> > but not both.
>> 
>> OK, in fact v1 was trying to do something like that, by defining the
>> handle with this:
>> 
>> struct fuse_inode_handle {
>> 	u32 type;
>> 	union {
>> 		struct {
>> 			u64 nodeid;
>> 			u32 generation;
>> 		};
>> 		struct fuse_file_handle fh;
>> 	};
>> };
>> 
>> (The 'type' is likely to be useless, as we know if the server supports fh
>> or not.)
>> 
>> > Which means that fuse_iget() must be able to search the cache based on
>> > the handle as well, but that should not be too difficult to implement
>> > (need to hash the file handle).
>> 
>> Right, I didn't got that far in v1.  I'll see what I can come up to.
>> Doing memcmp()s would definitely be too expensive, so using hashes is the
>> only way I guess.
>> 
> Please excuse my ignorance, but why would memcmp() be too expensive for a proof of concept?
> Inode handles are limited and the cache is somewhat limited.

(Oops, looks like I missed your email.)

So, if every time we're looking for a file handle we need to memcmp() it
with all the handles until we find it (or not!), that would easily be very
expensive if we have a lot of handles cached.  That's what I meant in my
reply, comparing this with an hash-based lookup.

(Not sure I answered your question, as I may have also misunderstood
Miklos suggestions.  It happens quite often!  Just read my replies in this
patchset :-) )

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE
  2025-12-17 17:02         ` Luis Henriques
@ 2025-12-17 18:02           ` Horst Birthelmer
  0 siblings, 0 replies; 86+ messages in thread
From: Horst Birthelmer @ 2025-12-17 18:02 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Wed, Dec 17, 2025 at 05:02:59PM +0000, Luis Henriques wrote:
> On Tue, Dec 16 2025, Horst Birthelmer wrote:
> 
> > On Tue, Dec 16, 2025 at 05:06:25PM +0000, Luis Henriques wrote:
> >> On Tue, Dec 16 2025, Miklos Szeredi wrote:
> > ...
> >> >
> >> > I think it should be either
> >> >
> >> >   - encode nodeid + generation (backward compatibility),
> >> >
> >> >   - or encode file handle for servers that support it
> >> >
> >> > but not both.
> >> 
> >> OK, in fact v1 was trying to do something like that, by defining the
> >> handle with this:
> >> 
> >> struct fuse_inode_handle {
> >> 	u32 type;
> >> 	union {
> >> 		struct {
> >> 			u64 nodeid;
> >> 			u32 generation;
> >> 		};
> >> 		struct fuse_file_handle fh;
> >> 	};
> >> };
> >> 
> >> (The 'type' is likely to be useless, as we know if the server supports fh
> >> or not.)
> >> 
> >> > Which means that fuse_iget() must be able to search the cache based on
> >> > the handle as well, but that should not be too difficult to implement
> >> > (need to hash the file handle).
> >> 
> >> Right, I didn't got that far in v1.  I'll see what I can come up to.
> >> Doing memcmp()s would definitely be too expensive, so using hashes is the
> >> only way I guess.
> >> 
> > Please excuse my ignorance, but why would memcmp() be too expensive for a proof of concept?
> > Inode handles are limited and the cache is somewhat limited.
> 
> (Oops, looks like I missed your email.)

Don't worry about it.

> So, if every time we're looking for a file handle we need to memcmp() it
> with all the handles until we find it (or not!), that would easily be very
> expensive if we have a lot of handles cached.  That's what I meant in my
> reply, comparing this with an hash-based lookup.

I get all that. My point was more of a suggestion to not worry too much about
that comparison, since the data size is really limited.
If you know and/or have figured out all the rest, you can think about what exactly
to hash if necessary.

BTW, I really liked your approach, that's why I said that. 
It was more of a general comment than a technical one.

> 
> (Not sure I answered your question, as I may have also misunderstood
> Miklos suggestions.  It happens quite often!  Just read my replies in this
> patchset :-) )
I have read the replies ;-)

Cheers,
Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-16 18:49       ` Bernd Schubert
  2025-12-16 22:45         ` Askar Safin
@ 2025-12-25  7:42         ` Askar Safin
  2026-01-04 22:38         ` Askar Safin
  2 siblings, 0 replies; 86+ messages in thread
From: Askar Safin @ 2025-12-25  7:42 UTC (permalink / raw)
  To: bernd; +Cc: bschubert, linux-fsdevel, linux-kernel

Bernd Schubert <bernd@bsbernd.com>:
> work on this during my x-mas holidays (feel free to ping from next week
> on), but please avoid posting about this in unrelated threads.

Here is ping.

-- 
Askar Safin

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation
  2025-12-16 18:49       ` Bernd Schubert
  2025-12-16 22:45         ` Askar Safin
  2025-12-25  7:42         ` Askar Safin
@ 2026-01-04 22:38         ` Askar Safin
  2 siblings, 0 replies; 86+ messages in thread
From: Askar Safin @ 2026-01-04 22:38 UTC (permalink / raw)
  To: bernd; +Cc: bschubert, linux-fsdevel, linux-kernel

Bernd Schubert <bernd@bsbernd.com>:
> work on this during my x-mas holidays (feel free to ping from next week
> on), but please avoid posting about this in unrelated threads.

Here is another ping.

-- 
Askar Safin

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2025-12-16 10:39   ` Miklos Szeredi
  2025-12-16 10:51     ` Amir Goldstein
@ 2026-01-09 11:57     ` Luis Henriques
  2026-01-09 12:38       ` Miklos Szeredi
  1 sibling, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2026-01-09 11:57 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

Hi Miklos,

On Tue, Dec 16 2025, Miklos Szeredi wrote:

> On Fri, 12 Dec 2025 at 19:12, Luis Henriques <luis@igalia.com> wrote:
>>
>> The implementation of LOOKUP_HANDLE modifies the LOOKUP operation to include
>> an extra inarg: the file handle for the parent directory (if it is
>> available).  Also, because fuse_entry_out now has a extra variable size
>> struct (the actual handle), it also sets the out_argvar flag to true.
>
> How about adding this as an extension header (FUSE_EXT_HANDLE)?  That
> would allow any operation to take a handle instead of a nodeid.
>
> Yeah, the infrastructure for adding extensions is inadequate, but I
> think the API is ready for this.
>
>> @@ -181,8 +182,24 @@ static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args,
>>         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].size = sizeof(*outarg) + outarg->fh.size;
>> +
>> +       if (fc->lookup_handle) {
>> +               struct fuse_inode *fi = NULL;
>> +
>> +               args->opcode = FUSE_LOOKUP_HANDLE;
>> +               args->out_argvar = true;
>
> How about allocating variable length arguments on demand?  That would
> allow getting rid of max_handle_size negotiation.
>
>         args->out_var_alloc  = true;
>         args->out_args[1].size = MAX_HANDLE_SZ;
>         args->out_args[1].value = NULL; /* Will be allocated to the actual size of the handle */

I've been trying to wrap my head around all the suggested changes, and
experimenting with a few options.  Since there are some major things that
need to be modified, I'd like to confirm that I got them right:

1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
   struct fuse_entry_out, which won't be changed and will continue to have
   a static size.

2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
   allocated (using your suggestion: 'args->out_var_alloc').  This will be
   a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
   replacing the struct fuse_attr by a struct fuse_statx, and adding the
   file handle struct.

3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
   (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
   create_new_entry()) will actually need to *add* an extra extension
   header, as extension headers are already being used there.
   This extension header will use the new struct fuse_entry_handle_out.

The above items seem to require some heavy changes on my current design.
That's why I'd like to make sure I got those right so that v3 is on the
right path.

Thanks in advance for any feedback!

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 11:57     ` Luis Henriques
@ 2026-01-09 12:38       ` Miklos Szeredi
  2026-01-09 14:45         ` Luis Henriques
  2026-01-09 15:03         ` Amir Goldstein
  0 siblings, 2 replies; 86+ messages in thread
From: Miklos Szeredi @ 2026-01-09 12:38 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:

> I've been trying to wrap my head around all the suggested changes, and
> experimenting with a few options.  Since there are some major things that
> need to be modified, I'd like to confirm that I got them right:
>
> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>    struct fuse_entry_out, which won't be changed and will continue to have
>    a static size.

Yes.

> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>    file handle struct.

Another idea: let's simplify the interface by removing the attributes
from the lookup reply entirely.  To get back the previous
functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

> 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header

No, extensions go after the regular request data: headers, payload,
extension(s).

We could think about changing that for uring, where it would make
sense to put the extensions after the regular headers, but currently
it doesn't work that way and goes into the payload section.

In any case LOOKUP_HANDLE should follow the existing practice.

>    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
>    create_new_entry()) will actually need to *add* an extra extension
>    header, as extension headers are already being used there.

Right.

>    This extension header will use the new struct fuse_entry_handle_out.

Why _out?

It should just be a struct fuse_ext_header followed by a struct
fuse_file_handle.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 12:38       ` Miklos Szeredi
@ 2026-01-09 14:45         ` Luis Henriques
  2026-01-09 14:56           ` Horst Birthelmer
  2026-01-09 15:20           ` Miklos Szeredi
  2026-01-09 15:03         ` Amir Goldstein
  1 sibling, 2 replies; 86+ messages in thread
From: Luis Henriques @ 2026-01-09 14:45 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Jan 09 2026, Miklos Szeredi wrote:

> On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>
>> I've been trying to wrap my head around all the suggested changes, and
>> experimenting with a few options.  Since there are some major things that
>> need to be modified, I'd like to confirm that I got them right:
>>
>> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>>    struct fuse_entry_out, which won't be changed and will continue to have
>>    a static size.
>
> Yes.
>
>> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>>    file handle struct.
>
> Another idea: let's simplify the interface by removing the attributes
> from the lookup reply entirely.  To get back the previous
> functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

OK, interesting idea.  So, in that case we would have:

struct fuse_entry_handle_out {
	uint64_t nodeid;
	uint64_t generation;
	uint64_t entry_valid;
	struct fuse_file_handle fh;
}

I'll then need to have a look at the compound requests closely. (I had
previously skimmed through the patches that add open+getattr but didn't
gone too deep into it.)

>> 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
>
> No, extensions go after the regular request data: headers, payload,
> extension(s).
>
> We could think about changing that for uring, where it would make
> sense to put the extensions after the regular headers, but currently
> it doesn't work that way and goes into the payload section.
>
> In any case LOOKUP_HANDLE should follow the existing practice.

Hmm... I _think_ I had it right in my head, but totally messed up my text.
English is hard.  What I meant was that args->ext_idx would be set to the
in_args[] index that would describe the extension (for the lookup
operation, that would be '3', not '0' as I mentioned above).

And then the extension header would be created similarly to what's being
done for FUSE_EXT_GROUPS, using the same helper extend_arg().  That way, I
think we would have: headers - payload - extensions.

>>    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
>>    create_new_entry()) will actually need to *add* an extra extension
>>    header, as extension headers are already being used there.
>
> Right.
>
>>    This extension header will use the new struct fuse_entry_handle_out.
>
> Why _out?
>
> It should just be a struct fuse_ext_header followed by a struct
> fuse_file_handle.

Yes, of course.  My English was totally messed-up.  And I meant
'fuse_file_handle', not 'fuse_entry_handle_out'.

Cheer,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 14:45         ` Luis Henriques
@ 2026-01-09 14:56           ` Horst Birthelmer
  2026-01-09 17:07             ` Luis Henriques
  2026-01-09 15:20           ` Miklos Szeredi
  1 sibling, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-09 14:56 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
> On Fri, Jan 09 2026, Miklos Szeredi wrote:
> 
> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
> >
> >> I've been trying to wrap my head around all the suggested changes, and
> >> experimenting with a few options.  Since there are some major things that
> >> need to be modified, I'd like to confirm that I got them right:
> >>
> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >>    struct fuse_entry_out, which won't be changed and will continue to have
> >>    a static size.
> >
> > Yes.
> >
> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >>    file handle struct.
> >
> > Another idea: let's simplify the interface by removing the attributes
> > from the lookup reply entirely.  To get back the previous
> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
> 
> OK, interesting idea.  So, in that case we would have:
> 
> struct fuse_entry_handle_out {
> 	uint64_t nodeid;
> 	uint64_t generation;
> 	uint64_t entry_valid;
> 	struct fuse_file_handle fh;
> }
> 
> I'll then need to have a look at the compound requests closely. (I had
> previously skimmed through the patches that add open+getattr but didn't
> gone too deep into it.)
> 

I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
side. 
That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.

> 
> Cheer,
> -- 
> Luís
> 

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 12:38       ` Miklos Szeredi
  2026-01-09 14:45         ` Luis Henriques
@ 2026-01-09 15:03         ` Amir Goldstein
  2026-01-09 15:37           ` Miklos Szeredi
  1 sibling, 1 reply; 86+ messages in thread
From: Amir Goldstein @ 2026-01-09 15:03 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Luis Henriques, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Jan 9, 2026 at 1:38 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>
> > I've been trying to wrap my head around all the suggested changes, and
> > experimenting with a few options.  Since there are some major things that
> > need to be modified, I'd like to confirm that I got them right:
> >
> > 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >    struct fuse_entry_out, which won't be changed and will continue to have
> >    a static size.
>
> Yes.
>
> > 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >    file handle struct.
>
> Another idea: let's simplify the interface by removing the attributes
> from the lookup reply entirely.  To get back the previous
> functionality, compound requests can be used: LOOKUP_HANDLE + STATX.

What about FUSE_CREATE? FUSE_TMPFILE?
and more importantly READDIRPLUS dirents?

How do you envision the protocol extension for those if fuse_entry_handle
does not contain fuse_statx?

Thanks,
Amir.

>
> > 3. FUSE_LOOKUP_HANDLE will use the args->in_args[0] as an extension header
>
> No, extensions go after the regular request data: headers, payload,
> extension(s).
>
> We could think about changing that for uring, where it would make
> sense to put the extensions after the regular headers, but currently
> it doesn't work that way and goes into the payload section.
>
> In any case LOOKUP_HANDLE should follow the existing practice.
>
> >    (FUSE_EXT_HANDLE).  Note that other operations (e.g. those in function
> >    create_new_entry()) will actually need to *add* an extra extension
> >    header, as extension headers are already being used there.
>
> Right.
>
> >    This extension header will use the new struct fuse_entry_handle_out.
>
> Why _out?
>
> It should just be a struct fuse_ext_header followed by a struct
> fuse_file_handle.
>
> Thanks,
> Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 14:45         ` Luis Henriques
  2026-01-09 14:56           ` Horst Birthelmer
@ 2026-01-09 15:20           ` Miklos Szeredi
  1 sibling, 0 replies; 86+ messages in thread
From: Miklos Szeredi @ 2026-01-09 15:20 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Amir Goldstein, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 9 Jan 2026 at 15:45, Luis Henriques <luis@igalia.com> wrote:

> struct fuse_entry_handle_out {
>         uint64_t nodeid;
>         uint64_t generation;
>         uint64_t entry_valid;
>         struct fuse_file_handle fh;
> }

I'd do it this way:

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;
};

and the file handle would be placed in out_args[1].

> I'll then need to have a look at the compound requests closely. (I had
> previously skimmed through the patches that add open+getattr but didn't
> gone too deep into it.)

It should work as two separate requests, just not as optimal.

> And then the extension header would be created similarly to what's being
> done for FUSE_EXT_GROUPS, using the same helper extend_arg().  That way, I
> think we would have: headers - payload - extensions.

Right.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 15:03         ` Amir Goldstein
@ 2026-01-09 15:37           ` Miklos Szeredi
  2026-01-09 15:56             ` Bernd Schubert
  0 siblings, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2026-01-09 15:37 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Luis Henriques, Darrick J. Wong, Bernd Schubert, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:

> What about FUSE_CREATE? FUSE_TMPFILE?

FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.

FUSE_TMPFILE is special, the create and open needs to be atomic.   So
the best we can do is FUSE_TMPFILE_H + FUSE_STATX.

> and more importantly READDIRPLUS dirents?

I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
flexible, where policy is moved from the kernel to the fuse server.

How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
dentry and the inode?

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 15:37           ` Miklos Szeredi
@ 2026-01-09 15:56             ` Bernd Schubert
  2026-01-09 16:28               ` Miklos Szeredi
  2026-01-09 18:29               ` Amir Goldstein
  0 siblings, 2 replies; 86+ messages in thread
From: Bernd Schubert @ 2026-01-09 15:56 UTC (permalink / raw)
  To: Miklos Szeredi, Amir Goldstein
  Cc: Luis Henriques, Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel, linux-kernel, Matt Harvey, kernel-dev



On 1/9/26 16:37, Miklos Szeredi wrote:
> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> 
>> What about FUSE_CREATE? FUSE_TMPFILE?
> 
> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> 
> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> 
>> and more importantly READDIRPLUS dirents?
> 
> I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
> flexible, where policy is moved from the kernel to the fuse server.
> 
> How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
> dentry and the inode?

Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
this will have a painful overhead.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 15:56             ` Bernd Schubert
@ 2026-01-09 16:28               ` Miklos Szeredi
  2026-01-09 17:16                 ` Luis Henriques
  2026-01-09 18:29               ` Amir Goldstein
  1 sibling, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2026-01-09 16:28 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Amir Goldstein, Luis Henriques, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 9 Jan 2026 at 16:56, Bernd Schubert <bschubert@ddn.com> wrote:

> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
> this will have a painful overhead.

We don't want to do the lock/add/unlock for individual dentries
anyway, so might as well make this FUSE_NOTIFY_ENTRIES.  That would
lock the directory, add a bunch of dentries, then unlock.

The fun part is that locking the directory must not be done from the
READDIR context, as rwsem read locks are apparently not nestable.
This would make the interface a pain to use.  We could work around
that by checking if a READDIR is currently in progress and then
synchronizing the two such that the entries are added with the
directory lock held.   It's a bit of complexity, but maybe worth it as
it's going to be the common usage.

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 14:56           ` Horst Birthelmer
@ 2026-01-09 17:07             ` Luis Henriques
  2026-01-12  7:43               ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2026-01-09 17:07 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Fri, Jan 09 2026, Horst Birthelmer wrote:

> On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
>> On Fri, Jan 09 2026, Miklos Szeredi wrote:
>> 
>> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
>> >
>> >> I've been trying to wrap my head around all the suggested changes, and
>> >> experimenting with a few options.  Since there are some major things that
>> >> need to be modified, I'd like to confirm that I got them right:
>> >>
>> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
>> >>    struct fuse_entry_out, which won't be changed and will continue to have
>> >>    a static size.
>> >
>> > Yes.
>> >
>> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
>> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
>> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
>> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
>> >>    file handle struct.
>> >
>> > Another idea: let's simplify the interface by removing the attributes
>> > from the lookup reply entirely.  To get back the previous
>> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
>> 
>> OK, interesting idea.  So, in that case we would have:
>> 
>> struct fuse_entry_handle_out {
>> 	uint64_t nodeid;
>> 	uint64_t generation;
>> 	uint64_t entry_valid;
>> 	struct fuse_file_handle fh;
>> }
>> 
>> I'll then need to have a look at the compound requests closely. (I had
>> previously skimmed through the patches that add open+getattr but didn't
>> gone too deep into it.)
>> 
>
> I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
> side. 
> That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.

Awesome, thanks for the hint, Horst.  I'll definitely have a look into it.

Cheer,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 16:28               ` Miklos Szeredi
@ 2026-01-09 17:16                 ` Luis Henriques
  0 siblings, 0 replies; 86+ messages in thread
From: Luis Henriques @ 2026-01-09 17:16 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Bernd Schubert, Amir Goldstein, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Jan 09 2026, Miklos Szeredi wrote:

> On Fri, 9 Jan 2026 at 16:56, Bernd Schubert <bschubert@ddn.com> wrote:
>
>> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
>> this will have a painful overhead.
>
> We don't want to do the lock/add/unlock for individual dentries
> anyway, so might as well make this FUSE_NOTIFY_ENTRIES.  That would
> lock the directory, add a bunch of dentries, then unlock.
>
> The fun part is that locking the directory must not be done from the
> READDIR context, as rwsem read locks are apparently not nestable.
> This would make the interface a pain to use.  We could work around
> that by checking if a READDIR is currently in progress and then
> synchronizing the two such that the entries are added with the
> directory lock held.   It's a bit of complexity, but maybe worth it as
> it's going to be the common usage.

Yikes!  Things are not getting any simpler :-(

OK, looks like there's a lot of things I'll need to figure out before
proceeding.

/me schedules some time to learn and play a bit with all this.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 15:56             ` Bernd Schubert
  2026-01-09 16:28               ` Miklos Szeredi
@ 2026-01-09 18:29               ` Amir Goldstein
  2026-01-09 19:01                 ` Miklos Szeredi
  2026-01-09 19:12                 ` Bernd Schubert
  1 sibling, 2 replies; 86+ messages in thread
From: Amir Goldstein @ 2026-01-09 18:29 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Luis Henriques, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>
>
>
> On 1/9/26 16:37, Miklos Szeredi wrote:
> > On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> >
> >> What about FUSE_CREATE? FUSE_TMPFILE?
> >
> > FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> >
> > FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> > the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> >

I thought that the idea of FUSE_CREATE is that it is atomic_open()
is it not?
If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
it won't be atomic on the server, would it?

> >> and more importantly READDIRPLUS dirents?
> >
> > I was never satisfied with FUSE_READDIRPLUS, I'd prefer something more
> > flexible, where policy is moved from the kernel to the fuse server.
> >
> > How about a push style interface with FUSE_NOTIFY_ENTRY setting up the
> > dentry and the inode?
>
> Feasible, but we should extend io-uring to FUSE_NOTIFY first, otherwise
> this will have a painful overhead.
>
>

I admit that the guesswork with readdirplus auto is not always
what serves users the best, but why change to push?
If the server had actually written the dirents with some header
it could just as well decide per dirent if it wants to return
dirent or direntplus or direntplus_handle.

What is the expected benefit of using push in this scenario?

My own take on READDIRPLUS is that it cries for a user API
so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.

I hacked my own server to use open(O_SYNC) indication for
directories as a signal to choose between readdirplus and
kernel readdir passthrough (not plus) and I have applications
that opt-out of readdirplus.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 18:29               ` Amir Goldstein
@ 2026-01-09 19:01                 ` Miklos Szeredi
  2026-01-09 19:28                   ` Amir Goldstein
  2026-01-09 19:12                 ` Bernd Schubert
  1 sibling, 1 reply; 86+ messages in thread
From: Miklos Szeredi @ 2026-01-09 19:01 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Bernd Schubert, Luis Henriques, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, 9 Jan 2026 at 19:29, Amir Goldstein <amir73il@gmail.com> wrote:

> I thought that the idea of FUSE_CREATE is that it is atomic_open()
> is it not?
> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> it won't be atomic on the server, would it?

This won't change anything wrt atomicity, since the request and the
reply are still a single blob.  The reason to do this is that the
interface itself is cleaner and we can swap out one part (like we want
now with the file handle stuff) and reuse the other parts.

So I mostly look on this as a trick to make the interface more
modular, not something that the servers will care about.

> I admit that the guesswork with readdirplus auto is not always
> what serves users the best, but why change to push?
> If the server had actually written the dirents with some header
> it could just as well decide per dirent if it wants to return
> dirent or direntplus or direntplus_handle.
>
> What is the expected benefit of using push in this scenario?

I was thinking of detaching the lookup + getattr from the directory data.

Now I realize that doing it with a FUSE_NOTIFY is not really useful,
we can just attach the array of entries at the end of the readdir
reply as a separate blob.

> My own take on READDIRPLUS is that it cries for a user API
> so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.

Yeah, that would be nice.  What about fadvise flag?

Thanks,
Miklos

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 18:29               ` Amir Goldstein
  2026-01-09 19:01                 ` Miklos Szeredi
@ 2026-01-09 19:12                 ` Bernd Schubert
  2026-01-09 19:55                   ` Horst Birthelmer
  1 sibling, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2026-01-09 19:12 UTC (permalink / raw)
  To: Amir Goldstein
  Cc: Miklos Szeredi, Luis Henriques, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On 1/9/26 19:29, Amir Goldstein wrote:
> On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>
>>
>>
>> On 1/9/26 16:37, Miklos Szeredi wrote:
>>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
>>>
>>>> What about FUSE_CREATE? FUSE_TMPFILE?
>>>
>>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
>>>
>>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
>>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
>>>
> 
> I thought that the idea of FUSE_CREATE is that it is atomic_open()
> is it not?
> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> it won't be atomic on the server, would it?

Horst just posted the libfuse PR for compounds
https://github.com/libfuse/libfuse/pull/1418

You can make it atomic on the libfuse side with the compound
implementation. I.e. you have the option leave it to libfuse to handle
compound by compound as individual requests, or you handle the compound
yourself as one request.

I think we need to create an example with self handling of the compound,
even if it is just to ensure that we didn't miss anything in design.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 19:01                 ` Miklos Szeredi
@ 2026-01-09 19:28                   ` Amir Goldstein
  0 siblings, 0 replies; 86+ messages in thread
From: Amir Goldstein @ 2026-01-09 19:28 UTC (permalink / raw)
  To: Miklos Szeredi
  Cc: Bernd Schubert, Luis Henriques, Darrick J. Wong, Kevin Chen,
	Horst Birthelmer, linux-fsdevel, linux-kernel, Matt Harvey,
	kernel-dev

On Fri, Jan 9, 2026 at 8:01 PM Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Fri, 9 Jan 2026 at 19:29, Amir Goldstein <amir73il@gmail.com> wrote:
>
> > I thought that the idea of FUSE_CREATE is that it is atomic_open()
> > is it not?
> > If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> > it won't be atomic on the server, would it?
>
> This won't change anything wrt atomicity, since the request and the
> reply are still a single blob.  The reason to do this is that the
> interface itself is cleaner and we can swap out one part (like we want
> now with the file handle stuff) and reuse the other parts.
>
> So I mostly look on this as a trick to make the interface more
> modular, not something that the servers will care about.
>
> > I admit that the guesswork with readdirplus auto is not always
> > what serves users the best, but why change to push?
> > If the server had actually written the dirents with some header
> > it could just as well decide per dirent if it wants to return
> > dirent or direntplus or direntplus_handle.
> >
> > What is the expected benefit of using push in this scenario?
>
> I was thinking of detaching the lookup + getattr from the directory data.
>
> Now I realize that doing it with a FUSE_NOTIFY is not really useful,
> we can just attach the array of entries at the end of the readdir
> reply as a separate blob.
>

Sure, that works, as long as there is some header to the array,
it could contain the new entry_handle and statx payloads.

> > My own take on READDIRPLUS is that it cries for a user API
> > so that "ls" could opt-out and "ls -l" could opt-in to readdirplus.
>
> Yeah, that would be nice.  What about fadvise flag?
>

Definitely, like readahead we would need to support
NO (RANDOM), YES (SEQUENTIAL) and AUTO (NORMAL).

Honestly, I got discouraged from completing readdir passthrough
because of the complexity involved wrt readdirplus, but
maybe I will just post the patches I have for readdir(not plus)
passthrough with the fadvise to facilitate it.

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 19:12                 ` Bernd Schubert
@ 2026-01-09 19:55                   ` Horst Birthelmer
  2026-01-21 17:56                     ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-09 19:55 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Amir Goldstein, Miklos Szeredi, Luis Henriques, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com

On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
> On 1/9/26 19:29, Amir Goldstein wrote:
> > On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
> >>
> >>
> >>
> >> On 1/9/26 16:37, Miklos Szeredi wrote:
> >>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> >>>
> >>>> What about FUSE_CREATE? FUSE_TMPFILE?
> >>>
> >>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> >>>
> >>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> >>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> >>>
> > 
> > I thought that the idea of FUSE_CREATE is that it is atomic_open()
> > is it not?
> > If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> > it won't be atomic on the server, would it?
> 
> Horst just posted the libfuse PR for compounds
> https://github.com/libfuse/libfuse/pull/1418
> 
> You can make it atomic on the libfuse side with the compound
> implementation. I.e. you have the option leave it to libfuse to handle
> compound by compound as individual requests, or you handle the compound
> yourself as one request.
> 
> I think we need to create an example with self handling of the compound,
> even if it is just to ensure that we didn't miss anything in design.

I actually do have an example that would be suitable.
I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.

> 
> 
> Thanks,
> Bernd

Cheers,
Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 17:07             ` Luis Henriques
@ 2026-01-12  7:43               ` Horst Birthelmer
  0 siblings, 0 replies; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-12  7:43 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Miklos Szeredi, Amir Goldstein, Darrick J. Wong, Bernd Schubert,
	Kevin Chen, Horst Birthelmer, linux-fsdevel, linux-kernel,
	Matt Harvey, kernel-dev

On Fri, Jan 09, 2026 at 05:07:10PM +0000, Luis Henriques wrote:
> On Fri, Jan 09 2026, Horst Birthelmer wrote:
> 
> > On Fri, Jan 09, 2026 at 02:45:21PM +0000, Luis Henriques wrote:
> >> On Fri, Jan 09 2026, Miklos Szeredi wrote:
> >> 
> >> > On Fri, 9 Jan 2026 at 12:57, Luis Henriques <luis@igalia.com> wrote:
> >> >
> >> >> I've been trying to wrap my head around all the suggested changes, and
> >> >> experimenting with a few options.  Since there are some major things that
> >> >> need to be modified, I'd like to confirm that I got them right:
> >> >>
> >> >> 1. In the old FUSE_LOOKUP, the args->in_args[0] will continue to use the
> >> >>    struct fuse_entry_out, which won't be changed and will continue to have
> >> >>    a static size.
> >> >
> >> > Yes.
> >> >
> >> >> 2. FUSE_LOOKUP_HANDLE will add a new out_arg, which will be dynamically
> >> >>    allocated (using your suggestion: 'args->out_var_alloc').  This will be
> >> >>    a new struct fuse_entry_handle_out, similar to fuse_entry_out, but
> >> >>    replacing the struct fuse_attr by a struct fuse_statx, and adding the
> >> >>    file handle struct.
> >> >
> >> > Another idea: let's simplify the interface by removing the attributes
> >> > from the lookup reply entirely.  To get back the previous
> >> > functionality, compound requests can be used: LOOKUP_HANDLE + STATX.
> >> 
> >> OK, interesting idea.  So, in that case we would have:
> >> 
> >> struct fuse_entry_handle_out {
> >> 	uint64_t nodeid;
> >> 	uint64_t generation;
> >> 	uint64_t entry_valid;
> >> 	struct fuse_file_handle fh;
> >> }
> >> 
> >> I'll then need to have a look at the compound requests closely. (I had
> >> previously skimmed through the patches that add open+getattr but didn't
> >> gone too deep into it.)
> >> 
> >
> > I am preparing the pull request for libfuse today, so you can have a look at how it will be handled on the libfuse
> > side. 
> > That contains a patch to passthrough_hp as well so it supports compounds and you will have something to test, if you want to go that way.
> 
> Awesome, thanks for the hint, Horst.  I'll definitely have a look into it.
> 
FWIW, I have updated the PR for libfuse so that passthrough_hp now demoes the two ways we can handle compounds.
The one where we just call the requests in the compound sequencially (here we have a helper in the lib now) and
the  way where we create a special function to take care of atomic combinations of requests.
I hope that helps to understand the idea better.

> Cheer,
> -- 
> Luís

Thanks,
Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-09 19:55                   ` Horst Birthelmer
@ 2026-01-21 17:56                     ` Luis Henriques
  2026-01-21 18:16                       ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2026-01-21 17:56 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

Hi Horst!

On Fri, Jan 09 2026, Horst Birthelmer wrote:

> On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
>> On 1/9/26 19:29, Amir Goldstein wrote:
>> > On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>> >>
>> >>
>> >>
>> >> On 1/9/26 16:37, Miklos Szeredi wrote:
>> >>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
>> >>>
>> >>>> What about FUSE_CREATE? FUSE_TMPFILE?
>> >>>
>> >>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
>> >>>
>> >>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
>> >>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
>> >>>
>> > 
>> > I thought that the idea of FUSE_CREATE is that it is atomic_open()
>> > is it not?
>> > If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
>> > it won't be atomic on the server, would it?
>> 
>> Horst just posted the libfuse PR for compounds
>> https://github.com/libfuse/libfuse/pull/1418
>> 
>> You can make it atomic on the libfuse side with the compound
>> implementation. I.e. you have the option leave it to libfuse to handle
>> compound by compound as individual requests, or you handle the compound
>> yourself as one request.
>> 
>> I think we need to create an example with self handling of the compound,
>> even if it is just to ensure that we didn't miss anything in design.
>
> I actually do have an example that would be suitable.
> I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.

So, I've been working on getting an implementation of LOOKUP_HANDLE+STATX.
And I would like to hear your opinion on a problem I found:

If the kernel is doing a LOOKUP, you'll send the parent directory nodeid
in the request args.  On the other hand, the nodeid for a STATX will be
the nodeid will be for the actual inode being statx'ed.

The problem is that when merging both requests into a compound request,
you don't have the nodeid for the STATX.  I've "fixed" this by passing in
FUSE_ROOT_ID and hacking user-space to work around it: if the lookup
succeeds, we have the correct nodeid for the STATX.  That seems to work
fine for my case, where the server handles the compound request itself.
But from what I understand libfuse can also handle it as individual
requests, and in this case the server wouldn't know the right nodeid for
the STATX.

Obviously, the same problem will need to be solved for other operations
(for example for FUSE_CREATE where we'll need to do a FUSE_MKOBJ_H +
FUSE_STATX + FUSE_OPEN).

I guess this can eventually be fixed in libfuse, by updating the nodeid in
this case.  Another solution is to not allow these sort of operations to
be handled individually.  But maybe I'm just being dense and there's a
better solution for this.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 17:56                     ` Luis Henriques
@ 2026-01-21 18:16                       ` Horst Birthelmer
  2026-01-21 18:28                         ` Bernd Schubert
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-21 18:16 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

Hi Luis,

On Wed, Jan 21, 2026 at 05:56:12PM +0000, Luis Henriques wrote:
> Hi Horst!
> 
> On Fri, Jan 09 2026, Horst Birthelmer wrote:
> 
> > On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
> >> On 1/9/26 19:29, Amir Goldstein wrote:
> >> > On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
> >> >>
> >> >>
> >> >>
> >> >> On 1/9/26 16:37, Miklos Szeredi wrote:
> >> >>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> >> >>>
> >> >>>> What about FUSE_CREATE? FUSE_TMPFILE?
> >> >>>
> >> >>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> >> >>>
> >> >>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> >> >>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> >> >>>
> >> > 
> >> > I thought that the idea of FUSE_CREATE is that it is atomic_open()
> >> > is it not?
> >> > If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> >> > it won't be atomic on the server, would it?
> >> 
> >> Horst just posted the libfuse PR for compounds
> >> https://github.com/libfuse/libfuse/pull/1418
> >> 
> >> You can make it atomic on the libfuse side with the compound
> >> implementation. I.e. you have the option leave it to libfuse to handle
> >> compound by compound as individual requests, or you handle the compound
> >> yourself as one request.
> >> 
> >> I think we need to create an example with self handling of the compound,
> >> even if it is just to ensure that we didn't miss anything in design.
> >
> > I actually do have an example that would be suitable.
> > I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.
> 
> So, I've been working on getting an implementation of LOOKUP_HANDLE+STATX.
> And I would like to hear your opinion on a problem I found:
> 
> If the kernel is doing a LOOKUP, you'll send the parent directory nodeid
> in the request args.  On the other hand, the nodeid for a STATX will be
> the nodeid will be for the actual inode being statx'ed.
> 
> The problem is that when merging both requests into a compound request,
> you don't have the nodeid for the STATX.  I've "fixed" this by passing in
> FUSE_ROOT_ID and hacking user-space to work around it: if the lookup
> succeeds, we have the correct nodeid for the STATX.  That seems to work
> fine for my case, where the server handles the compound request itself.
> But from what I understand libfuse can also handle it as individual
> requests, and in this case the server wouldn't know the right nodeid for
> the STATX.
> 
> Obviously, the same problem will need to be solved for other operations
> (for example for FUSE_CREATE where we'll need to do a FUSE_MKOBJ_H +
> FUSE_STATX + FUSE_OPEN).
> 
> I guess this can eventually be fixed in libfuse, by updating the nodeid in
> this case.  Another solution is to not allow these sort of operations to
> be handled individually.  But maybe I'm just being dense and there's a
> better solution for this.
> 

You have come across a problem, that I have come across, too, during my experiments. 
I think that makes it a rather common problem when creating compounds.

This can only be solved by convention and it is the reason why I have disabled the default
handling of compounds in libfuse. Bernd actually wanted to do that automatically, but I think
that is too risky for exactly the reason you have found.

The fuse server has to decide if it wants to handle the compound as such or as a
bunch of single requests.

At the moment I think it is best to just not use the libfuse single request handling 
of the compound where it is not possible. 
As my passthrough_hp shows, you can handle certain compounds as a compound where you know all the information
(like some lookup, you just did in the fuse server) and leave the 'trivial' ones to the lib.

We could actually pass 'one' id in the 'in header' of the compound as some sort of global parent 
but that would be another convention that the fuse server has to know and keep.

> Cheers,
> -- 
> Luís

Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 18:16                       ` Horst Birthelmer
@ 2026-01-21 18:28                         ` Bernd Schubert
  2026-01-21 18:36                           ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2026-01-21 18:28 UTC (permalink / raw)
  To: Horst Birthelmer, Luis Henriques
  Cc: Bernd Schubert, Amir Goldstein, Miklos Szeredi, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 1/21/26 19:16, Horst Birthelmer wrote:
> Hi Luis,
> 
> On Wed, Jan 21, 2026 at 05:56:12PM +0000, Luis Henriques wrote:
>> Hi Horst!
>>
>> On Fri, Jan 09 2026, Horst Birthelmer wrote:
>>
>>> On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
>>>> On 1/9/26 19:29, Amir Goldstein wrote:
>>>>> On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 1/9/26 16:37, Miklos Szeredi wrote:
>>>>>>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
>>>>>>>
>>>>>>>> What about FUSE_CREATE? FUSE_TMPFILE?
>>>>>>>
>>>>>>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
>>>>>>>
>>>>>>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
>>>>>>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
>>>>>>>
>>>>>
>>>>> I thought that the idea of FUSE_CREATE is that it is atomic_open()
>>>>> is it not?
>>>>> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
>>>>> it won't be atomic on the server, would it?
>>>>
>>>> Horst just posted the libfuse PR for compounds
>>>> https://github.com/libfuse/libfuse/pull/1418
>>>>
>>>> You can make it atomic on the libfuse side with the compound
>>>> implementation. I.e. you have the option leave it to libfuse to handle
>>>> compound by compound as individual requests, or you handle the compound
>>>> yourself as one request.
>>>>
>>>> I think we need to create an example with self handling of the compound,
>>>> even if it is just to ensure that we didn't miss anything in design.
>>>
>>> I actually do have an example that would be suitable.
>>> I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.
>>
>> So, I've been working on getting an implementation of LOOKUP_HANDLE+STATX.
>> And I would like to hear your opinion on a problem I found:
>>
>> If the kernel is doing a LOOKUP, you'll send the parent directory nodeid
>> in the request args.  On the other hand, the nodeid for a STATX will be
>> the nodeid will be for the actual inode being statx'ed.
>>
>> The problem is that when merging both requests into a compound request,
>> you don't have the nodeid for the STATX.  I've "fixed" this by passing in
>> FUSE_ROOT_ID and hacking user-space to work around it: if the lookup
>> succeeds, we have the correct nodeid for the STATX.  That seems to work
>> fine for my case, where the server handles the compound request itself.
>> But from what I understand libfuse can also handle it as individual
>> requests, and in this case the server wouldn't know the right nodeid for
>> the STATX.
>>
>> Obviously, the same problem will need to be solved for other operations
>> (for example for FUSE_CREATE where we'll need to do a FUSE_MKOBJ_H +
>> FUSE_STATX + FUSE_OPEN).
>>
>> I guess this can eventually be fixed in libfuse, by updating the nodeid in
>> this case.  Another solution is to not allow these sort of operations to
>> be handled individually.  But maybe I'm just being dense and there's a
>> better solution for this.
>>
> 
> You have come across a problem, that I have come across, too, during my experiments. 
> I think that makes it a rather common problem when creating compounds.
> 
> This can only be solved by convention and it is the reason why I have disabled the default
> handling of compounds in libfuse. Bernd actually wanted to do that automatically, but I think
> that is too risky for exactly the reason you have found.
> 
> The fuse server has to decide if it wants to handle the compound as such or as a
> bunch of single requests.

Idea was actually to pass compounds to the daemon if it has a compound
handler, if not to handle it automatically. Now for open+getattr
fuse-server actually not want the additional getattr - cannot be handle
automatically. But for lookup+stat and others like this, if fuse server
server does not know how to handle the entire compound, libfuse could
still do it correctly, i.e. have its own handler for known compounds?

> 
> At the moment I think it is best to just not use the libfuse single request handling 
> of the compound where it is not possible. 
> As my passthrough_hp shows, you can handle certain compounds as a compound where you know all the information
> (like some lookup, you just did in the fuse server) and leave the 'trivial' ones to the lib.
> 
> We could actually pass 'one' id in the 'in header' of the compound as some sort of global parent 
> but that would be another convention that the fuse server has to know and keep.
> 


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 18:28                         ` Bernd Schubert
@ 2026-01-21 18:36                           ` Horst Birthelmer
  2026-01-21 18:49                             ` Bernd Schubert
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-21 18:36 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Luis Henriques, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Wed, Jan 21, 2026 at 07:28:43PM +0100, Bernd Schubert wrote:
> On 1/21/26 19:16, Horst Birthelmer wrote:
> > Hi Luis,
> > 
> > On Wed, Jan 21, 2026 at 05:56:12PM +0000, Luis Henriques wrote:
> >> Hi Horst!
> >>
> >> On Fri, Jan 09 2026, Horst Birthelmer wrote:
> >>
> >>> On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
> >>>> On 1/9/26 19:29, Amir Goldstein wrote:
> >>>>> On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
> >>>>>>
> >>>>>>
> >>>>>>
> >>>>>> On 1/9/26 16:37, Miklos Szeredi wrote:
> >>>>>>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
> >>>>>>>
> >>>>>>>> What about FUSE_CREATE? FUSE_TMPFILE?
> >>>>>>>
> >>>>>>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
> >>>>>>>
> >>>>>>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
> >>>>>>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
> >>>>>>>
> >>>>>
> >>>>> I thought that the idea of FUSE_CREATE is that it is atomic_open()
> >>>>> is it not?
> >>>>> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
> >>>>> it won't be atomic on the server, would it?
> >>>>
> >>>> Horst just posted the libfuse PR for compounds
> >>>> https://github.com/libfuse/libfuse/pull/1418
> >>>>
> >>>> You can make it atomic on the libfuse side with the compound
> >>>> implementation. I.e. you have the option leave it to libfuse to handle
> >>>> compound by compound as individual requests, or you handle the compound
> >>>> yourself as one request.
> >>>>
> >>>> I think we need to create an example with self handling of the compound,
> >>>> even if it is just to ensure that we didn't miss anything in design.
> >>>
> >>> I actually do have an example that would be suitable.
> >>> I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.
> >>
> >> So, I've been working on getting an implementation of LOOKUP_HANDLE+STATX.
> >> And I would like to hear your opinion on a problem I found:
> >>
> >> If the kernel is doing a LOOKUP, you'll send the parent directory nodeid
> >> in the request args.  On the other hand, the nodeid for a STATX will be
> >> the nodeid will be for the actual inode being statx'ed.
> >>
> >> The problem is that when merging both requests into a compound request,
> >> you don't have the nodeid for the STATX.  I've "fixed" this by passing in
> >> FUSE_ROOT_ID and hacking user-space to work around it: if the lookup
> >> succeeds, we have the correct nodeid for the STATX.  That seems to work
> >> fine for my case, where the server handles the compound request itself.
> >> But from what I understand libfuse can also handle it as individual
> >> requests, and in this case the server wouldn't know the right nodeid for
> >> the STATX.
> >>
> >> Obviously, the same problem will need to be solved for other operations
> >> (for example for FUSE_CREATE where we'll need to do a FUSE_MKOBJ_H +
> >> FUSE_STATX + FUSE_OPEN).
> >>
> >> I guess this can eventually be fixed in libfuse, by updating the nodeid in
> >> this case.  Another solution is to not allow these sort of operations to
> >> be handled individually.  But maybe I'm just being dense and there's a
> >> better solution for this.
> >>
> > 
> > You have come across a problem, that I have come across, too, during my experiments. 
> > I think that makes it a rather common problem when creating compounds.
> > 
> > This can only be solved by convention and it is the reason why I have disabled the default
> > handling of compounds in libfuse. Bernd actually wanted to do that automatically, but I think
> > that is too risky for exactly the reason you have found.
> > 
> > The fuse server has to decide if it wants to handle the compound as such or as a
> > bunch of single requests.
> 
> Idea was actually to pass compounds to the daemon if it has a compound
> handler, if not to handle it automatically. Now for open+getattr
> fuse-server actually not want the additional getattr - cannot be handle
> automatically. But for lookup+stat and others like this, if fuse server
> server does not know how to handle the entire compound, libfuse could
> still do it correctly, i.e. have its own handler for known compounds?

We could definitely provide a library of compounds that we 'know' in libfuse,
of course. No argument there.

The problem Luis had was that he cannot construct the second request in the compound correctly
since he does not have all the in parameters to write complete request.

BTW, I forgot in my last mail to say that we have another problem, where we need 
some sort of convention where we will bang our heads sooner or later.
If the fuse server does not support the given compound it should 
return EOPNOTSUP in my opinion.
IIRC this is not implemented correctly so far.

> 
> > 
> > At the moment I think it is best to just not use the libfuse single request handling 
> > of the compound where it is not possible. 
> > As my passthrough_hp shows, you can handle certain compounds as a compound where you know all the information
> > (like some lookup, you just did in the fuse server) and leave the 'trivial' ones to the lib.
> > 
> > We could actually pass 'one' id in the 'in header' of the compound as some sort of global parent 
> > but that would be another convention that the fuse server has to know and keep.
> > 
> 
> 
> Thanks,
> Bernd

Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 18:36                           ` Horst Birthelmer
@ 2026-01-21 18:49                             ` Bernd Schubert
  2026-01-21 19:00                               ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2026-01-21 18:49 UTC (permalink / raw)
  To: Horst Birthelmer, Bernd Schubert
  Cc: Luis Henriques, Amir Goldstein, Miklos Szeredi, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 1/21/26 19:36, Horst Birthelmer wrote:
> On Wed, Jan 21, 2026 at 07:28:43PM +0100, Bernd Schubert wrote:
>> On 1/21/26 19:16, Horst Birthelmer wrote:
>>> Hi Luis,
>>>
>>> On Wed, Jan 21, 2026 at 05:56:12PM +0000, Luis Henriques wrote:
>>>> Hi Horst!
>>>>
>>>> On Fri, Jan 09 2026, Horst Birthelmer wrote:
>>>>
>>>>> On Fri, Jan 09, 2026 at 07:12:41PM +0000, Bernd Schubert wrote:
>>>>>> On 1/9/26 19:29, Amir Goldstein wrote:
>>>>>>> On Fri, Jan 9, 2026 at 4:56 PM Bernd Schubert <bschubert@ddn.com> wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On 1/9/26 16:37, Miklos Szeredi wrote:
>>>>>>>>> On Fri, 9 Jan 2026 at 16:03, Amir Goldstein <amir73il@gmail.com> wrote:
>>>>>>>>>
>>>>>>>>>> What about FUSE_CREATE? FUSE_TMPFILE?
>>>>>>>>>
>>>>>>>>> FUSE_CREATE could be decomposed to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN.
>>>>>>>>>
>>>>>>>>> FUSE_TMPFILE is special, the create and open needs to be atomic.   So
>>>>>>>>> the best we can do is FUSE_TMPFILE_H + FUSE_STATX.
>>>>>>>>>
>>>>>>>
>>>>>>> I thought that the idea of FUSE_CREATE is that it is atomic_open()
>>>>>>> is it not?
>>>>>>> If we decompose that to FUSE_MKOBJ_H + FUSE_STATX + FUSE_OPEN
>>>>>>> it won't be atomic on the server, would it?
>>>>>>
>>>>>> Horst just posted the libfuse PR for compounds
>>>>>> https://github.com/libfuse/libfuse/pull/1418
>>>>>>
>>>>>> You can make it atomic on the libfuse side with the compound
>>>>>> implementation. I.e. you have the option leave it to libfuse to handle
>>>>>> compound by compound as individual requests, or you handle the compound
>>>>>> yourself as one request.
>>>>>>
>>>>>> I think we need to create an example with self handling of the compound,
>>>>>> even if it is just to ensure that we didn't miss anything in design.
>>>>>
>>>>> I actually do have an example that would be suitable.
>>>>> I could implement the LOOKUP+CREATE as a pseudo atomic operation in passthrough_hp.
>>>>
>>>> So, I've been working on getting an implementation of LOOKUP_HANDLE+STATX.
>>>> And I would like to hear your opinion on a problem I found:
>>>>
>>>> If the kernel is doing a LOOKUP, you'll send the parent directory nodeid
>>>> in the request args.  On the other hand, the nodeid for a STATX will be
>>>> the nodeid will be for the actual inode being statx'ed.
>>>>
>>>> The problem is that when merging both requests into a compound request,
>>>> you don't have the nodeid for the STATX.  I've "fixed" this by passing in
>>>> FUSE_ROOT_ID and hacking user-space to work around it: if the lookup
>>>> succeeds, we have the correct nodeid for the STATX.  That seems to work
>>>> fine for my case, where the server handles the compound request itself.
>>>> But from what I understand libfuse can also handle it as individual
>>>> requests, and in this case the server wouldn't know the right nodeid for
>>>> the STATX.
>>>>
>>>> Obviously, the same problem will need to be solved for other operations
>>>> (for example for FUSE_CREATE where we'll need to do a FUSE_MKOBJ_H +
>>>> FUSE_STATX + FUSE_OPEN).
>>>>
>>>> I guess this can eventually be fixed in libfuse, by updating the nodeid in
>>>> this case.  Another solution is to not allow these sort of operations to
>>>> be handled individually.  But maybe I'm just being dense and there's a
>>>> better solution for this.
>>>>
>>>
>>> You have come across a problem, that I have come across, too, during my experiments. 
>>> I think that makes it a rather common problem when creating compounds.
>>>
>>> This can only be solved by convention and it is the reason why I have disabled the default
>>> handling of compounds in libfuse. Bernd actually wanted to do that automatically, but I think
>>> that is too risky for exactly the reason you have found.
>>>
>>> The fuse server has to decide if it wants to handle the compound as such or as a
>>> bunch of single requests.
>>
>> Idea was actually to pass compounds to the daemon if it has a compound
>> handler, if not to handle it automatically. Now for open+getattr
>> fuse-server actually not want the additional getattr - cannot be handle
>> automatically. But for lookup+stat and others like this, if fuse server
>> server does not know how to handle the entire compound, libfuse could
>> still do it correctly, i.e. have its own handler for known compounds?
> 
> We could definitely provide a library of compounds that we 'know' in libfuse,
> of course. No argument there.
> 
> The problem Luis had was that he cannot construct the second request in the compound correctly
> since he does not have all the in parameters to write complete request.

What I mean is, the auto-handler of libfuse could complete requests of
the 2nd compound request with those of the 1st request?

> 
> BTW, I forgot in my last mail to say that we have another problem, where we need 
> some sort of convention where we will bang our heads sooner or later.
> If the fuse server does not support the given compound it should 
> return EOPNOTSUP in my opinion.
> IIRC this is not implemented correctly so far.
> 
>>
>>>
>>> At the moment I think it is best to just not use the libfuse single request handling 
>>> of the compound where it is not possible. 
>>> As my passthrough_hp shows, you can handle certain compounds as a compound where you know all the information
>>> (like some lookup, you just did in the fuse server) and leave the 'trivial' ones to the lib.
>>>
>>> We could actually pass 'one' id in the 'in header' of the compound as some sort of global parent 
>>> but that would be another convention that the fuse server has to know and keep.
>>>
>>
>>
>> Thanks,
>> Bernd
> 
> Horst


^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 18:49                             ` Bernd Schubert
@ 2026-01-21 19:00                               ` Horst Birthelmer
  2026-01-21 19:03                                 ` Bernd Schubert
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-21 19:00 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Bernd Schubert, Luis Henriques, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
> 
> 
...
> > The problem Luis had was that he cannot construct the second request in the compound correctly
> > since he does not have all the in parameters to write complete request.
> 
> What I mean is, the auto-handler of libfuse could complete requests of
> the 2nd compound request with those of the 1st request?
> 
With a crazy bunch of flags, we could probably do it, yes.
It is way easier that the fuse server treats certain compounds
(combination of operations) as a single request and handles
those accordingly.

> >> Thanks,
> >> Bernd
> > 
> > Horst
> 

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 19:00                               ` Horst Birthelmer
@ 2026-01-21 19:03                                 ` Bernd Schubert
  2026-01-21 19:12                                   ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Bernd Schubert @ 2026-01-21 19:03 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Bernd Schubert, Luis Henriques, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com



On 1/21/26 20:00, Horst Birthelmer wrote:
> On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
>>
>>
> ...
>>> The problem Luis had was that he cannot construct the second request in the compound correctly
>>> since he does not have all the in parameters to write complete request.
>>
>> What I mean is, the auto-handler of libfuse could complete requests of
>> the 2nd compound request with those of the 1st request?
>>
> With a crazy bunch of flags, we could probably do it, yes.
> It is way easier that the fuse server treats certain compounds
> (combination of operations) as a single request and handles
> those accordingly.

Hmm, isn't the problem that each fuse server then needs to know those
common compound combinations? And that makes me wonder, what is the
difference to an op code then?


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 19:03                                 ` Bernd Schubert
@ 2026-01-21 19:12                                   ` Horst Birthelmer
  2026-01-22  9:52                                     ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-21 19:12 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Bernd Schubert, Luis Henriques, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Wed, Jan 21, 2026 at 08:03:32PM +0100, Bernd Schubert wrote:
> 
> 
> On 1/21/26 20:00, Horst Birthelmer wrote:
> > On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
> >>
> >>
> > ...
> >>> The problem Luis had was that he cannot construct the second request in the compound correctly
> >>> since he does not have all the in parameters to write complete request.
> >>
> >> What I mean is, the auto-handler of libfuse could complete requests of
> >> the 2nd compound request with those of the 1st request?
> >>
> > With a crazy bunch of flags, we could probably do it, yes.
> > It is way easier that the fuse server treats certain compounds
> > (combination of operations) as a single request and handles
> > those accordingly.
> 
> Hmm, isn't the problem that each fuse server then needs to know those
> common compound combinations? And that makes me wonder, what is the
> difference to an op code then?

I'm pretty sure we both have some examples and counter examples in mind.

Let's implement a couple of the suggested compounds and we will see 
if we can make generic rules. I'm not convinced yet, that we want to
have a generic implementation in libfuse.

The advantage to the 'add an opcode' for every combination 
(and there are already a couple of those) approach is that
you don't need more opcodes, so no changes to the kernel.
You need some code in the fuse server, though, which to me is
fine, since if you have atomic operations implemented there,
why not actually use them.

The big advantage is, choice.

There will be some examples (like the one from Luis)
where you don't actually have a generic choice,
or you create some convention, like you just had in mind.
(put the result of the first operation into the input
of the next ... or into some fields ... etc.)

> 
> 
> Thanks,
> Bernd

Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-21 19:12                                   ` Horst Birthelmer
@ 2026-01-22  9:52                                     ` Luis Henriques
  2026-01-22 10:20                                       ` Horst Birthelmer
  0 siblings, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2026-01-22  9:52 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

Hi!

On Wed, Jan 21 2026, Horst Birthelmer wrote:

> On Wed, Jan 21, 2026 at 08:03:32PM +0100, Bernd Schubert wrote:
>> 
>> 
>> On 1/21/26 20:00, Horst Birthelmer wrote:
>> > On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
>> >>
>> >>
>> > ...
>> >>> The problem Luis had was that he cannot construct the second request in the compound correctly
>> >>> since he does not have all the in parameters to write complete request.
>> >>
>> >> What I mean is, the auto-handler of libfuse could complete requests of
>> >> the 2nd compound request with those of the 1st request?
>> >>
>> > With a crazy bunch of flags, we could probably do it, yes.
>> > It is way easier that the fuse server treats certain compounds
>> > (combination of operations) as a single request and handles
>> > those accordingly.

Right, I think that at least the compound requests that can not be
serialised (i.e. those that can not be executed using the libfuse helper
function fuse_execute_compound_sequential()) should be flagged as such.
An extra flag to be set in the request should do the job.

This way, if this flag isn't set in a compound request and the FUSE server
doesn't have a compound handle, libfuse could serialise the requests.
Otherwise, it would return -ENOTSUPP.

>> Hmm, isn't the problem that each fuse server then needs to know those
>> common compound combinations? And that makes me wonder, what is the
>> difference to an op code then?
>
> I'm pretty sure we both have some examples and counter examples in mind.
>
> Let's implement a couple of the suggested compounds and we will see 
> if we can make generic rules. I'm not convinced yet, that we want to
> have a generic implementation in libfuse.
>
> The advantage to the 'add an opcode' for every combination 
> (and there are already a couple of those) approach is that
> you don't need more opcodes, so no changes to the kernel.
> You need some code in the fuse server, though, which to me is
> fine, since if you have atomic operations implemented there,
> why not actually use them.
>
> The big advantage is, choice.
>
> There will be some examples (like the one from Luis)
> where you don't actually have a generic choice,
> or you create some convention, like you just had in mind.
> (put the result of the first operation into the input
> of the next ... or into some fields ... etc.)

So, to summarise:

In the end, even FUSE servers that do support compound operations will
need to check the operations within a request, and act accordingly.  There
will be new combinations that will not be possible to be handle by servers
in a generic way: they'll need to return -EOPNOTSUPP if the combination of
operations is unknown.  libfuse may then be able to support the
serialisation of that specific operation compound.  But that'll require
flagging the request as "serialisable".

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22  9:52                                     ` Luis Henriques
@ 2026-01-22 10:20                                       ` Horst Birthelmer
  2026-01-22 10:35                                         ` Bernd Schubert
  2026-01-22 10:53                                         ` Luis Henriques
  0 siblings, 2 replies; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-22 10:20 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Thu, Jan 22, 2026 at 09:52:23AM +0000, Luis Henriques wrote:
> Hi!
> 
> On Wed, Jan 21 2026, Horst Birthelmer wrote:
> 
> > On Wed, Jan 21, 2026 at 08:03:32PM +0100, Bernd Schubert wrote:
> >> 
> >> 
> >> On 1/21/26 20:00, Horst Birthelmer wrote:
> >> > On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
> >> >>
> >> >>
> >> > ...
> >> >>> The problem Luis had was that he cannot construct the second request in the compound correctly
> >> >>> since he does not have all the in parameters to write complete request.
> >> >>
> >> >> What I mean is, the auto-handler of libfuse could complete requests of
> >> >> the 2nd compound request with those of the 1st request?
> >> >>
> >> > With a crazy bunch of flags, we could probably do it, yes.
> >> > It is way easier that the fuse server treats certain compounds
> >> > (combination of operations) as a single request and handles
> >> > those accordingly.
> 
> Right, I think that at least the compound requests that can not be
> serialised (i.e. those that can not be executed using the libfuse helper
> function fuse_execute_compound_sequential()) should be flagged as such.
> An extra flag to be set in the request should do the job.
> 
> This way, if this flag isn't set in a compound request and the FUSE server
> doesn't have a compound handle, libfuse could serialise the requests.
> Otherwise, it would return -ENOTSUPP.
> 
> >> Hmm, isn't the problem that each fuse server then needs to know those
> >> common compound combinations? And that makes me wonder, what is the
> >> difference to an op code then?
> >
> > I'm pretty sure we both have some examples and counter examples in mind.
> >
> > Let's implement a couple of the suggested compounds and we will see 
> > if we can make generic rules. I'm not convinced yet, that we want to
> > have a generic implementation in libfuse.
> >
> > The advantage to the 'add an opcode' for every combination 
> > (and there are already a couple of those) approach is that
> > you don't need more opcodes, so no changes to the kernel.
> > You need some code in the fuse server, though, which to me is
> > fine, since if you have atomic operations implemented there,
> > why not actually use them.
> >
> > The big advantage is, choice.
> >
> > There will be some examples (like the one from Luis)
> > where you don't actually have a generic choice,
> > or you create some convention, like you just had in mind.
> > (put the result of the first operation into the input
> > of the next ... or into some fields ... etc.)
> 
> So, to summarise:
> 
> In the end, even FUSE servers that do support compound operations will
> need to check the operations within a request, and act accordingly.  There
> will be new combinations that will not be possible to be handle by servers
> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
> operations is unknown.  libfuse may then be able to support the
> serialisation of that specific operation compound.  But that'll require
> flagging the request as "serialisable".

OK, so this boils down to libfuse trying a bit harder than it does at the moment.
After it calls the compound handler it should check for EOPNOTSUP and the flag
and then execute the single requests itself.

At the moment the fuse server implementation itself has to do this.
Actually the patched passthrough_hp does exactly that.

I think I can live with that.

> 
> Cheers,
> -- 
> Luís

Thanks,
Horst

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 10:20                                       ` Horst Birthelmer
@ 2026-01-22 10:35                                         ` Bernd Schubert
  2026-01-22 10:53                                         ` Luis Henriques
  1 sibling, 0 replies; 86+ messages in thread
From: Bernd Schubert @ 2026-01-22 10:35 UTC (permalink / raw)
  To: Horst Birthelmer, Luis Henriques
  Cc: Bernd Schubert, Amir Goldstein, Miklos Szeredi, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 1/22/26 11:20, Horst Birthelmer wrote:
> On Thu, Jan 22, 2026 at 09:52:23AM +0000, Luis Henriques wrote:
>> Hi!
>>
>> On Wed, Jan 21 2026, Horst Birthelmer wrote:
>>
>>> On Wed, Jan 21, 2026 at 08:03:32PM +0100, Bernd Schubert wrote:
>>>>
>>>>
>>>> On 1/21/26 20:00, Horst Birthelmer wrote:
>>>>> On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
>>>>>>
>>>>>>
>>>>> ...
>>>>>>> The problem Luis had was that he cannot construct the second request in the compound correctly
>>>>>>> since he does not have all the in parameters to write complete request.
>>>>>>
>>>>>> What I mean is, the auto-handler of libfuse could complete requests of
>>>>>> the 2nd compound request with those of the 1st request?
>>>>>>
>>>>> With a crazy bunch of flags, we could probably do it, yes.
>>>>> It is way easier that the fuse server treats certain compounds
>>>>> (combination of operations) as a single request and handles
>>>>> those accordingly.
>>
>> Right, I think that at least the compound requests that can not be
>> serialised (i.e. those that can not be executed using the libfuse helper
>> function fuse_execute_compound_sequential()) should be flagged as such.
>> An extra flag to be set in the request should do the job.
>>
>> This way, if this flag isn't set in a compound request and the FUSE server
>> doesn't have a compound handle, libfuse could serialise the requests.
>> Otherwise, it would return -ENOTSUPP.
>>
>>>> Hmm, isn't the problem that each fuse server then needs to know those
>>>> common compound combinations? And that makes me wonder, what is the
>>>> difference to an op code then?
>>>
>>> I'm pretty sure we both have some examples and counter examples in mind.
>>>
>>> Let's implement a couple of the suggested compounds and we will see 
>>> if we can make generic rules. I'm not convinced yet, that we want to
>>> have a generic implementation in libfuse.
>>>
>>> The advantage to the 'add an opcode' for every combination 
>>> (and there are already a couple of those) approach is that
>>> you don't need more opcodes, so no changes to the kernel.
>>> You need some code in the fuse server, though, which to me is
>>> fine, since if you have atomic operations implemented there,
>>> why not actually use them.
>>>
>>> The big advantage is, choice.
>>>
>>> There will be some examples (like the one from Luis)
>>> where you don't actually have a generic choice,
>>> or you create some convention, like you just had in mind.
>>> (put the result of the first operation into the input
>>> of the next ... or into some fields ... etc.)
>>
>> So, to summarise:
>>
>> In the end, even FUSE servers that do support compound operations will
>> need to check the operations within a request, and act accordingly.  There
>> will be new combinations that will not be possible to be handle by servers
>> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
>> operations is unknown.  libfuse may then be able to support the
>> serialisation of that specific operation compound.  But that'll require
>> flagging the request as "serialisable".
> 
> OK, so this boils down to libfuse trying a bit harder than it does at the moment.
> After it calls the compound handler it should check for EOPNOTSUP and the flag
> and then execute the single requests itself.

I'm not sure. I think the server compound handler should call into
libfuse as it does right now and ask libfuse to handle it, if it doesn't
have its own handler. But then it needs to be safe. I.e. open+getattr -
cannot be enabled by default, at least not the getattr part, because not
all servers will need that. I.e. server needs to set bits to libfuse to
enable some compound operations. Things like atomic open probably could
be enabled by default.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 10:20                                       ` Horst Birthelmer
  2026-01-22 10:35                                         ` Bernd Schubert
@ 2026-01-22 10:53                                         ` Luis Henriques
  2026-01-22 10:59                                           ` Horst Birthelmer
  1 sibling, 1 reply; 86+ messages in thread
From: Luis Henriques @ 2026-01-22 10:53 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Thu, Jan 22 2026, Horst Birthelmer wrote:

> On Thu, Jan 22, 2026 at 09:52:23AM +0000, Luis Henriques wrote:
>> Hi!
>> 
>> On Wed, Jan 21 2026, Horst Birthelmer wrote:
>> 
>> > On Wed, Jan 21, 2026 at 08:03:32PM +0100, Bernd Schubert wrote:
>> >> 
>> >> 
>> >> On 1/21/26 20:00, Horst Birthelmer wrote:
>> >> > On Wed, Jan 21, 2026 at 07:49:25PM +0100, Bernd Schubert wrote:
>> >> >>
>> >> >>
>> >> > ...
>> >> >>> The problem Luis had was that he cannot construct the second request in the compound correctly
>> >> >>> since he does not have all the in parameters to write complete request.
>> >> >>
>> >> >> What I mean is, the auto-handler of libfuse could complete requests of
>> >> >> the 2nd compound request with those of the 1st request?
>> >> >>
>> >> > With a crazy bunch of flags, we could probably do it, yes.
>> >> > It is way easier that the fuse server treats certain compounds
>> >> > (combination of operations) as a single request and handles
>> >> > those accordingly.
>> 
>> Right, I think that at least the compound requests that can not be
>> serialised (i.e. those that can not be executed using the libfuse helper
>> function fuse_execute_compound_sequential()) should be flagged as such.
>> An extra flag to be set in the request should do the job.
>> 
>> This way, if this flag isn't set in a compound request and the FUSE server
>> doesn't have a compound handle, libfuse could serialise the requests.
>> Otherwise, it would return -ENOTSUPP.
>> 
>> >> Hmm, isn't the problem that each fuse server then needs to know those
>> >> common compound combinations? And that makes me wonder, what is the
>> >> difference to an op code then?
>> >
>> > I'm pretty sure we both have some examples and counter examples in mind.
>> >
>> > Let's implement a couple of the suggested compounds and we will see 
>> > if we can make generic rules. I'm not convinced yet, that we want to
>> > have a generic implementation in libfuse.
>> >
>> > The advantage to the 'add an opcode' for every combination 
>> > (and there are already a couple of those) approach is that
>> > you don't need more opcodes, so no changes to the kernel.
>> > You need some code in the fuse server, though, which to me is
>> > fine, since if you have atomic operations implemented there,
>> > why not actually use them.
>> >
>> > The big advantage is, choice.
>> >
>> > There will be some examples (like the one from Luis)
>> > where you don't actually have a generic choice,
>> > or you create some convention, like you just had in mind.
>> > (put the result of the first operation into the input
>> > of the next ... or into some fields ... etc.)
>> 
>> So, to summarise:
>> 
>> In the end, even FUSE servers that do support compound operations will
>> need to check the operations within a request, and act accordingly.  There
>> will be new combinations that will not be possible to be handle by servers
>> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
>> operations is unknown.  libfuse may then be able to support the
>> serialisation of that specific operation compound.  But that'll require
>> flagging the request as "serialisable".
>
> OK, so this boils down to libfuse trying a bit harder than it does at the moment.
> After it calls the compound handler it should check for EOPNOTSUP and the flag
> and then execute the single requests itself.
>
> At the moment the fuse server implementation itself has to do this.
> Actually the patched passthrough_hp does exactly that.
>
> I think I can live with that.

Well, I was trying to suggest to have, at least for now, as little changes
to libfuse as possible.  Something like this:

	if (req->se->op.compound)
		req->se->op.compound(req, arg->count, arg->flags, in_payload);
	else if (arg->flags & FUSE_COMPOUND_SERIALISABLE)
		fuse_execute_compound_sequential(req);
	else
		fuse_reply_err(req, ENOSYS);

Eventually, support for specific non-serialisable operations could be
added, but that would have to be done for each individual compound.
Obviously, the server itself could also try to serialise the individual
operations in the compound handle, and use the same helper.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 10:53                                         ` Luis Henriques
@ 2026-01-22 10:59                                           ` Horst Birthelmer
  2026-01-22 11:25                                             ` Luis Henriques
  0 siblings, 1 reply; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-22 10:59 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Thu, Jan 22, 2026 at 10:53:24AM +0000, Luis Henriques wrote:
> On Thu, Jan 22 2026, Horst Birthelmer wrote:
...
> >> 
> >> So, to summarise:
> >> 
> >> In the end, even FUSE servers that do support compound operations will
> >> need to check the operations within a request, and act accordingly.  There
> >> will be new combinations that will not be possible to be handle by servers
> >> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
> >> operations is unknown.  libfuse may then be able to support the
> >> serialisation of that specific operation compound.  But that'll require
> >> flagging the request as "serialisable".
> >
> > OK, so this boils down to libfuse trying a bit harder than it does at the moment.
> > After it calls the compound handler it should check for EOPNOTSUP and the flag
> > and then execute the single requests itself.
> >
> > At the moment the fuse server implementation itself has to do this.
> > Actually the patched passthrough_hp does exactly that.
> >
> > I think I can live with that.
> 
> Well, I was trying to suggest to have, at least for now, as little changes
> to libfuse as possible.  Something like this:
> 
> 	if (req->se->op.compound)
> 		req->se->op.compound(req, arg->count, arg->flags, in_payload);
> 	else if (arg->flags & FUSE_COMPOUND_SERIALISABLE)
> 		fuse_execute_compound_sequential(req);
> 	else
> 		fuse_reply_err(req, ENOSYS);
> 
> Eventually, support for specific non-serialisable operations could be
> added, but that would have to be done for each individual compound.
> Obviously, the server itself could also try to serialise the individual
> operations in the compound handle, and use the same helper.
> 

Is there a specific reason why you want that change in lowlevel.c?
The patched passthrouhg_hp does this implicitly, actually without the flag.
It handles what it knows as 'atomic' compound and uses the helper for the rest.
If you don't want to handle specific combinations, just check for them 
and return an error.

> Cheers,
> -- 
> Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 10:59                                           ` Horst Birthelmer
@ 2026-01-22 11:25                                             ` Luis Henriques
  2026-01-22 11:32                                               ` Bernd Schubert
  2026-01-22 12:34                                               ` Horst Birthelmer
  0 siblings, 2 replies; 86+ messages in thread
From: Luis Henriques @ 2026-01-22 11:25 UTC (permalink / raw)
  To: Horst Birthelmer
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Thu, Jan 22 2026, Horst Birthelmer wrote:

> On Thu, Jan 22, 2026 at 10:53:24AM +0000, Luis Henriques wrote:
>> On Thu, Jan 22 2026, Horst Birthelmer wrote:
> ...
>> >> 
>> >> So, to summarise:
>> >> 
>> >> In the end, even FUSE servers that do support compound operations will
>> >> need to check the operations within a request, and act accordingly.  There
>> >> will be new combinations that will not be possible to be handle by servers
>> >> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
>> >> operations is unknown.  libfuse may then be able to support the
>> >> serialisation of that specific operation compound.  But that'll require
>> >> flagging the request as "serialisable".
>> >
>> > OK, so this boils down to libfuse trying a bit harder than it does at the moment.
>> > After it calls the compound handler it should check for EOPNOTSUP and the flag
>> > and then execute the single requests itself.
>> >
>> > At the moment the fuse server implementation itself has to do this.
>> > Actually the patched passthrough_hp does exactly that.
>> >
>> > I think I can live with that.
>> 
>> Well, I was trying to suggest to have, at least for now, as little changes
>> to libfuse as possible.  Something like this:
>> 
>> 	if (req->se->op.compound)
>> 		req->se->op.compound(req, arg->count, arg->flags, in_payload);
>> 	else if (arg->flags & FUSE_COMPOUND_SERIALISABLE)
>> 		fuse_execute_compound_sequential(req);
>> 	else
>> 		fuse_reply_err(req, ENOSYS);
>> 
>> Eventually, support for specific non-serialisable operations could be
>> added, but that would have to be done for each individual compound.
>> Obviously, the server itself could also try to serialise the individual
>> operations in the compound handle, and use the same helper.
>> 
>
> Is there a specific reason why you want that change in lowlevel.c?
> The patched passthrouhg_hp does this implicitly, actually without the flag.
> It handles what it knows as 'atomic' compound and uses the helper for the rest.
> If you don't want to handle specific combinations, just check for them 
> and return an error.

Sorry, I have the feeling that I'm starting to bikeshed a bit...

Anyway, I saw the passthrough_hp code, and that's why I thought it would
be easy to just move that into the lowlevel API.  I assumed this would be
a very small change to your current code that would also allow to safely
handle "serialisable" requests in servers that do not have the
->compound() handler.  Obviously, the *big* difference from your code
would be that the kernel would need to flag the non-serialisable requests,
so that user-space would know whether they could handle requests
individually or not.

And another thought I just had (more bikeshedding!) is that if the server
will be allowed to call fuse_execute_compound_sequential(), then this
function would also need to check that flag and return an error if the
request can't be serialisable.

Anyway, I'll stop bothering you now :-)  These comments should probably
have been done in the libfuse PR anyway.

Cheers,
-- 
Luís

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 11:25                                             ` Luis Henriques
@ 2026-01-22 11:32                                               ` Bernd Schubert
  2026-01-22 12:34                                               ` Horst Birthelmer
  1 sibling, 0 replies; 86+ messages in thread
From: Bernd Schubert @ 2026-01-22 11:32 UTC (permalink / raw)
  To: Luis Henriques, Horst Birthelmer
  Cc: Bernd Schubert, Amir Goldstein, Miklos Szeredi, Darrick J. Wong,
	Kevin Chen, Horst Birthelmer, linux-fsdevel@vger.kernel.org,
	linux-kernel@vger.kernel.org, Matt Harvey, kernel-dev@igalia.com



On 1/22/26 12:25, Luis Henriques wrote:
> On Thu, Jan 22 2026, Horst Birthelmer wrote:
> 
>> On Thu, Jan 22, 2026 at 10:53:24AM +0000, Luis Henriques wrote:
>>> On Thu, Jan 22 2026, Horst Birthelmer wrote:
>> ...
>>>>>
>>>>> So, to summarise:
>>>>>
>>>>> In the end, even FUSE servers that do support compound operations will
>>>>> need to check the operations within a request, and act accordingly.  There
>>>>> will be new combinations that will not be possible to be handle by servers
>>>>> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
>>>>> operations is unknown.  libfuse may then be able to support the
>>>>> serialisation of that specific operation compound.  But that'll require
>>>>> flagging the request as "serialisable".
>>>>
>>>> OK, so this boils down to libfuse trying a bit harder than it does at the moment.
>>>> After it calls the compound handler it should check for EOPNOTSUP and the flag
>>>> and then execute the single requests itself.
>>>>
>>>> At the moment the fuse server implementation itself has to do this.
>>>> Actually the patched passthrough_hp does exactly that.
>>>>
>>>> I think I can live with that.
>>>
>>> Well, I was trying to suggest to have, at least for now, as little changes
>>> to libfuse as possible.  Something like this:
>>>
>>> 	if (req->se->op.compound)
>>> 		req->se->op.compound(req, arg->count, arg->flags, in_payload);
>>> 	else if (arg->flags & FUSE_COMPOUND_SERIALISABLE)
>>> 		fuse_execute_compound_sequential(req);
>>> 	else
>>> 		fuse_reply_err(req, ENOSYS);
>>>
>>> Eventually, support for specific non-serialisable operations could be
>>> added, but that would have to be done for each individual compound.
>>> Obviously, the server itself could also try to serialise the individual
>>> operations in the compound handle, and use the same helper.
>>>
>>
>> Is there a specific reason why you want that change in lowlevel.c?
>> The patched passthrouhg_hp does this implicitly, actually without the flag.
>> It handles what it knows as 'atomic' compound and uses the helper for the rest.
>> If you don't want to handle specific combinations, just check for them 
>> and return an error.
> 
> Sorry, I have the feeling that I'm starting to bikeshed a bit...
> 
> Anyway, I saw the passthrough_hp code, and that's why I thought it would
> be easy to just move that into the lowlevel API.  I assumed this would be
> a very small change to your current code that would also allow to safely
> handle "serialisable" requests in servers that do not have the
> ->compound() handler.  Obviously, the *big* difference from your code
> would be that the kernel would need to flag the non-serialisable requests,
> so that user-space would know whether they could handle requests
> individually or not.
> 
> And another thought I just had (more bikeshedding!) is that if the server
> will be allowed to call fuse_execute_compound_sequential(), then this
> function would also need to check that flag and return an error if the
> request can't be serialisable.
> 
> Anyway, I'll stop bothering you now :-)  These comments should probably
> have been done in the libfuse PR anyway.

Kind of, we have an organization github account as well - the PRs went
through that first. We need to add more documentation why it cannot
be done from fuse directly in all cases. Well, the kernel could indeed
add the flags when it is safe. Though the first user of compounds is
open+getattr and there it should not be handled automatically, at
least not without introducing change behavior.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 86+ messages in thread

* Re: Re: [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation
  2026-01-22 11:25                                             ` Luis Henriques
  2026-01-22 11:32                                               ` Bernd Schubert
@ 2026-01-22 12:34                                               ` Horst Birthelmer
  1 sibling, 0 replies; 86+ messages in thread
From: Horst Birthelmer @ 2026-01-22 12:34 UTC (permalink / raw)
  To: Luis Henriques
  Cc: Bernd Schubert, Bernd Schubert, Amir Goldstein, Miklos Szeredi,
	Darrick J. Wong, Kevin Chen, Horst Birthelmer,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	Matt Harvey, kernel-dev@igalia.com

On Thu, Jan 22, 2026 at 11:25:22AM +0000, Luis Henriques wrote:
> On Thu, Jan 22 2026, Horst Birthelmer wrote:
> 
> > On Thu, Jan 22, 2026 at 10:53:24AM +0000, Luis Henriques wrote:
> >> On Thu, Jan 22 2026, Horst Birthelmer wrote:
> > ...
> >> >> 
> >> >> So, to summarise:
> >> >> 
> >> >> In the end, even FUSE servers that do support compound operations will
> >> >> need to check the operations within a request, and act accordingly.  There
> >> >> will be new combinations that will not be possible to be handle by servers
> >> >> in a generic way: they'll need to return -EOPNOTSUPP if the combination of
> >> >> operations is unknown.  libfuse may then be able to support the
> >> >> serialisation of that specific operation compound.  But that'll require
> >> >> flagging the request as "serialisable".
> >> >
> >> > OK, so this boils down to libfuse trying a bit harder than it does at the moment.
> >> > After it calls the compound handler it should check for EOPNOTSUP and the flag
> >> > and then execute the single requests itself.
> >> >
> >> > At the moment the fuse server implementation itself has to do this.
> >> > Actually the patched passthrough_hp does exactly that.
> >> >
> >> > I think I can live with that.
> >> 
> >> Well, I was trying to suggest to have, at least for now, as little changes
> >> to libfuse as possible.  Something like this:
> >> 
> >> 	if (req->se->op.compound)
> >> 		req->se->op.compound(req, arg->count, arg->flags, in_payload);
> >> 	else if (arg->flags & FUSE_COMPOUND_SERIALISABLE)
> >> 		fuse_execute_compound_sequential(req);
> >> 	else
> >> 		fuse_reply_err(req, ENOSYS);
> >> 
> >> Eventually, support for specific non-serialisable operations could be
> >> added, but that would have to be done for each individual compound.
> >> Obviously, the server itself could also try to serialise the individual
> >> operations in the compound handle, and use the same helper.
> >> 
> >
> > Is there a specific reason why you want that change in lowlevel.c?
> > The patched passthrouhg_hp does this implicitly, actually without the flag.
> > It handles what it knows as 'atomic' compound and uses the helper for the rest.
> > If you don't want to handle specific combinations, just check for them 
> > and return an error.
> 
> Sorry, I have the feeling that I'm starting to bikeshed a bit...
> 
> Anyway, I saw the passthrough_hp code, and that's why I thought it would
> be easy to just move that into the lowlevel API.  I assumed this would be
> a very small change to your current code that would also allow to safely
> handle "serialisable" requests in servers that do not have the
> ->compound() handler.  Obviously, the *big* difference from your code
> would be that the kernel would need to flag the non-serialisable requests,
> so that user-space would know whether they could handle requests
> individually or not.
> 
> And another thought I just had (more bikeshedding!) is that if the server
> will be allowed to call fuse_execute_compound_sequential(), then this
> function would also need to check that flag and return an error if the
> request can't be serialisable.
> 
> Anyway, I'll stop bothering you now :-)  These comments should probably
> have been done in the libfuse PR anyway.

You are not bothering me at all. I am actually very greatful for those comments
since you are the first user of compounds and that is a very important part.
All the scenarios we clarify now will not bite us later.

I'm still a bit in doubt, that adding that to libfuse will help for all
cases.

> 
> Cheers,
> -- 
> Luís
> 

^ permalink raw reply	[flat|nested] 86+ messages in thread

end of thread, other threads:[~2026-01-22 12:35 UTC | newest]

Thread overview: 86+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-12 18:12 [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Luis Henriques
2025-12-12 18:12 ` [RFC PATCH v2 1/6] fuse: store index of the variable length argument Luis Henriques
2025-12-12 18:12 ` [RFC PATCH v2 2/6] fuse: move fuse_entry_out structs out of the stack Luis Henriques
2025-12-15 14:03   ` Bernd Schubert
2025-12-16 10:30     ` Luis Henriques
2025-12-12 18:12 ` [RFC PATCH v2 3/6] fuse: initial infrastructure for FUSE_LOOKUP_HANDLE support Luis Henriques
2025-12-15 13:36   ` Bernd Schubert
2025-12-15 17:06     ` Amir Goldstein
2025-12-15 17:11       ` Bernd Schubert
2025-12-15 18:09         ` Amir Goldstein
2025-12-15 18:23           ` Bernd Schubert
2025-12-16 10:36           ` Luis Henriques
2025-12-16 10:19   ` Miklos Szeredi
2025-12-16 11:33     ` Luis Henriques
2025-12-16 11:46       ` Miklos Szeredi
2025-12-16 12:02         ` Luis Henriques
2025-12-12 18:12 ` [RFC PATCH v2 4/6] fuse: implementation of the FUSE_LOOKUP_HANDLE operation Luis Henriques
2025-12-15 17:39   ` Bernd Schubert
2025-12-16 11:48     ` Luis Henriques
2025-12-17 10:18       ` Amir Goldstein
2025-12-17 14:45         ` Luis Henriques
2025-12-17 15:02       ` Bernd Schubert
2025-12-17 16:53         ` Luis Henriques
2025-12-16  8:49   ` Joanne Koong
2025-12-16  8:54     ` Bernd Schubert
2025-12-17  0:32       ` Joanne Koong
2025-12-17  1:00         ` Darrick J. Wong
2025-12-17  2:48           ` Joanne Koong
2025-12-17  9:38             ` Luis Henriques
2025-12-17 10:08               ` Miklos Szeredi
2025-12-17 16:17                 ` Luis Henriques
2025-12-16 10:39   ` Miklos Szeredi
2025-12-16 10:51     ` Amir Goldstein
2025-12-16 11:07       ` Miklos Szeredi
2026-01-09 11:57     ` Luis Henriques
2026-01-09 12:38       ` Miklos Szeredi
2026-01-09 14:45         ` Luis Henriques
2026-01-09 14:56           ` Horst Birthelmer
2026-01-09 17:07             ` Luis Henriques
2026-01-12  7:43               ` Horst Birthelmer
2026-01-09 15:20           ` Miklos Szeredi
2026-01-09 15:03         ` Amir Goldstein
2026-01-09 15:37           ` Miklos Szeredi
2026-01-09 15:56             ` Bernd Schubert
2026-01-09 16:28               ` Miklos Szeredi
2026-01-09 17:16                 ` Luis Henriques
2026-01-09 18:29               ` Amir Goldstein
2026-01-09 19:01                 ` Miklos Szeredi
2026-01-09 19:28                   ` Amir Goldstein
2026-01-09 19:12                 ` Bernd Schubert
2026-01-09 19:55                   ` Horst Birthelmer
2026-01-21 17:56                     ` Luis Henriques
2026-01-21 18:16                       ` Horst Birthelmer
2026-01-21 18:28                         ` Bernd Schubert
2026-01-21 18:36                           ` Horst Birthelmer
2026-01-21 18:49                             ` Bernd Schubert
2026-01-21 19:00                               ` Horst Birthelmer
2026-01-21 19:03                                 ` Bernd Schubert
2026-01-21 19:12                                   ` Horst Birthelmer
2026-01-22  9:52                                     ` Luis Henriques
2026-01-22 10:20                                       ` Horst Birthelmer
2026-01-22 10:35                                         ` Bernd Schubert
2026-01-22 10:53                                         ` Luis Henriques
2026-01-22 10:59                                           ` Horst Birthelmer
2026-01-22 11:25                                             ` Luis Henriques
2026-01-22 11:32                                               ` Bernd Schubert
2026-01-22 12:34                                               ` Horst Birthelmer
2025-12-12 18:12 ` [RFC PATCH v2 5/6] fuse: factor out NFS export related code Luis Henriques
2025-12-14 15:13   ` Amir Goldstein
2025-12-15 12:05     ` Luis Henriques
2025-12-12 18:12 ` [RFC PATCH v2 6/6] fuse: implementation of export_operations with FUSE_LOOKUP_HANDLE Luis Henriques
2025-12-16 10:58   ` Miklos Szeredi
2025-12-16 17:06     ` Luis Henriques
2025-12-16 20:12       ` Horst Birthelmer
2025-12-17 17:02         ` Luis Henriques
2025-12-17 18:02           ` Horst Birthelmer
2025-12-16 11:01   ` Amir Goldstein
2025-12-16 17:26     ` Luis Henriques
2025-12-14 17:02 ` [RFC PATCH v2 0/6] fuse: LOOKUP_HANDLE operation Askar Safin
2025-12-15 12:08   ` Luis Henriques
2025-12-16  0:33     ` Askar Safin
2025-12-16 17:36       ` Luis Henriques
2025-12-16 18:49       ` Bernd Schubert
2025-12-16 22:45         ` Askar Safin
2025-12-25  7:42         ` Askar Safin
2026-01-04 22:38         ` Askar Safin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox