mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC v3 0/3] fuse: compound commands
@ 2026-01-08 14:23 horst
  2026-01-08 14:23 ` [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests horst
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: horst @ 2026-01-08 14:23 UTC (permalink / raw)
  To: Miklos Szeredi, Bernd Schubert, Joanne Koong
  Cc: linux-kernel, linux-fsdevel, Horst Birthelmer, syzbot

In the discussion about open+getattr here [1] Bernd and Miklos talked
about the need for a compound command in fuse that could send multiple
commands to a fuse server.
    
Here's a propsal for exactly that compound command with an example
(the mentioned open+getattr).
    
[1] https://lore.kernel.org/linux-fsdevel/CAJfpegshcrjXJ0USZ8RRdBy=e0MxmBTJSCE0xnxG8LXgXy-xuQ@mail.gmail.com/

Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
Changes in v3:
- simplified the data handling for compound commands
- remove the validating functionality, since it was only a helper for
  development
- remove fuse_compound_request() and use fuse_simple_request()
- add helper functions for creating args for open and attr
- use the newly createn helper functions for arg creation for open and
  getattr
- Link to v2: https://lore.kernel.org/r/20251223-fuse-compounds-upstream-v2-0-0f7b4451c85e@ddn.com

Changes in v2:
- fixed issues with error handling in the compounds as well as in the
  open+getattr
- Link to v1: https://lore.kernel.org/r/20251223-fuse-compounds-upstream-v1-0-7bade663947b@ddn.com

---
Horst Birthelmer (3):
      fuse: add compound command to combine multiple requests
      fuse: add an implementation of open+getattr
      fuse: use the newly created helper functions

 fs/fuse/Makefile          |   2 +-
 fs/fuse/compound.c        | 276 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/fuse/dir.c             |   9 +-
 fs/fuse/file.c            | 152 +++++++++++++++++++++----
 fs/fuse/fuse_i.h          |  27 ++++-
 fs/fuse/inode.c           |   6 +
 fs/fuse/ioctl.c           |   2 +-
 include/uapi/linux/fuse.h |  37 +++++++
 8 files changed, 476 insertions(+), 35 deletions(-)
---
base-commit: 9448598b22c50c8a5bb77a9103e2d49f134c9578
change-id: 20251223-fuse-compounds-upstream-c85b4e39b3d3

Best regards,
-- 
Horst Birthelmer <hbirthelmer@ddn.com>


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

* [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests
  2026-01-08 14:23 [PATCH RFC v3 0/3] fuse: compound commands horst
@ 2026-01-08 14:23 ` horst
  2026-01-08 22:01   ` Bernd Schubert
  2026-01-08 14:23 ` [PATCH RFC v3 2/3] fuse: add an implementation of open+getattr horst
  2026-01-08 14:23 ` [PATCH RFC v3 3/3] fuse: use the newly created helper functions horst
  2 siblings, 1 reply; 8+ messages in thread
From: horst @ 2026-01-08 14:23 UTC (permalink / raw)
  To: Miklos Szeredi, Bernd Schubert, Joanne Koong
  Cc: linux-kernel, linux-fsdevel, Horst Birthelmer, syzbot

From: Horst Birthelmer <hbirthelmer@ddn.com>

For a FUSE_COMPOUND we add a header that contains information
about how many commands there are in the compound and about the
size of the expected result. This will make the interpretation
in libfuse easier, since we can preallocate the whole result.
Then we append the requests that belong to this compound.

The API for the compound command has:
  fuse_compound_alloc()
  fuse_compound_add()
  fuse_compound_send()
  fuse_compound_free()

Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
Tested-by: syzbot@syzkaller.appspotmail.com
---
 fs/fuse/Makefile          |   2 +-
 fs/fuse/compound.c        | 276 ++++++++++++++++++++++++++++++++++++++++++++++
 fs/fuse/fuse_i.h          |  12 ++
 include/uapi/linux/fuse.h |  37 +++++++
 4 files changed, 326 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 22ad9538dfc4..4c09038ef995 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o
 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 += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o compound.o
 fuse-y += iomode.o
 fuse-$(CONFIG_FUSE_DAX) += dax.o
 fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c
new file mode 100644
index 000000000000..2f292ae3e816
--- /dev/null
+++ b/fs/fuse/compound.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025
+ *
+ * This file implements compound operations for FUSE, allowing multiple
+ * operations to be batched into a single request to reduce round trips
+ * between kernel and userspace.
+ */
+
+#include "fuse_i.h"
+
+/*
+ * Compound request builder and state tracker and args pointer storage
+ */
+struct fuse_compound_req {
+	struct fuse_mount *fm;
+	struct fuse_compound_in compound_header;
+	struct fuse_compound_out result_header;
+
+	/* Per-operation error codes */
+	int op_errors[FUSE_MAX_COMPOUND_OPS];
+	/* Original fuse_args for response parsing */
+	struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
+
+	bool parsed;				/* Prevent double-parsing of response */
+};
+
+struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 flags)
+{
+	struct fuse_compound_req *compound;
+
+	compound = kzalloc(sizeof(*compound), GFP_KERNEL);
+	if (!compound)
+		return ERR_PTR(-ENOMEM);
+
+	compound->fm = fm;
+	compound->compound_header.flags = flags;
+
+	return compound;
+}
+
+void fuse_compound_free(struct fuse_compound_req *compound)
+{
+	if (!compound)
+		return;
+
+	kfree(compound);
+}
+
+int fuse_compound_add(struct fuse_compound_req *compound,
+		      struct fuse_args *args)
+{
+	if (!compound ||
+	    compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS)
+		return -EINVAL;
+
+	if (args->in_pages)
+		return -EINVAL;
+
+	compound->op_args[compound->compound_header.count] = args;
+	compound->compound_header.count++;
+	return 0;
+}
+
+static void *fuse_copy_response_data(struct fuse_args *args,
+				     char *response_data)
+{
+	size_t copied = 0;
+	int i;
+
+	for (i = 0; i < args->out_numargs; i++) {
+		struct fuse_arg current_arg = args->out_args[i];
+		size_t arg_size;
+
+		/*
+		 * Last argument with out_pages: copy to pages
+		 * External payload (in the last out arg) is not supported
+		 * at the moment
+		 */
+		if (i == args->out_numargs - 1 && args->out_pages)
+			return response_data;
+
+		arg_size = current_arg.size;
+
+		if (current_arg.value && arg_size > 0) {
+			memcpy(current_arg.value,
+			       (char *)response_data + copied, arg_size);
+			copied += arg_size;
+		}
+	}
+
+	return (char *)response_data + copied;
+}
+
+int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx)
+{
+	return compound->op_errors[op_idx];
+}
+
+static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound,
+					int op_index, void *op_out_data,
+					void *response_end)
+{
+	struct fuse_out_header *op_hdr = op_out_data;
+	struct fuse_args *args = compound->op_args[op_index];
+
+	if (op_hdr->len < sizeof(struct fuse_out_header))
+		return NULL;
+
+	/* Check if the entire operation response fits in the buffer */
+	if ((char *)op_out_data + op_hdr->len > (char *)response_end)
+		return NULL;
+
+	if (op_hdr->error != 0)
+		compound->op_errors[op_index] = op_hdr->error;
+
+	if (args && op_hdr->len > sizeof(struct fuse_out_header))
+		return fuse_copy_response_data(args, op_out_data +
+					       sizeof(struct fuse_out_header));
+
+	/* No response data, just advance past the header */
+	return (char *)op_out_data + op_hdr->len;
+}
+
+static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
+				    u32 count, void *response,
+				    size_t response_size)
+{
+	void *op_out_data = response;
+	void *response_end = (char *)response + response_size;
+	int i;
+
+	if (compound->parsed)
+		return 0;
+
+	if (!response || response_size < sizeof(struct fuse_out_header))
+		return -EIO;
+
+	for (i = 0; i < count && i < compound->result_header.count; i++) {
+		op_out_data = fuse_compound_parse_one_op(compound, i,
+							 op_out_data,
+							 response_end);
+		if (!op_out_data)
+			return -EIO;
+	}
+
+	compound->parsed = true;
+	return 0;
+}
+
+ssize_t fuse_compound_send(struct fuse_compound_req *compound)
+{
+	struct fuse_args args = {
+		.opcode = FUSE_COMPOUND,
+		.nodeid = 0,
+		.in_numargs = 2,
+		.out_numargs = 2,
+		.out_argvar = true,
+	};
+	size_t resp_buffer_size;
+	size_t actual_response_size;
+	size_t buffer_pos;
+	size_t total_expected_out_size;
+	void *buffer = NULL;
+	void *resp_payload;
+	ssize_t ret;
+	int i;
+
+	if (!compound) {
+		pr_info_ratelimited("FUSE: compound request is NULL in %s\n",
+				    __func__);
+		return -EINVAL;
+	}
+
+	if (compound->compound_header.count == 0) {
+		pr_info_ratelimited("FUSE: compound request contains no operations\n");
+		return -EINVAL;
+	}
+
+	buffer_pos = 0;
+	total_expected_out_size = 0;
+
+	for (i = 0; i < compound->compound_header.count; i++) {
+		struct fuse_args *op_args = compound->op_args[i];
+		size_t needed_size = sizeof(struct fuse_in_header);
+		int j;
+
+		for (j = 0; j < op_args->in_numargs; j++)
+			needed_size += op_args->in_args[j].size;
+
+		buffer_pos += needed_size;
+
+		for (j = 0; j < op_args->out_numargs; j++)
+			total_expected_out_size += op_args->out_args[j].size;
+	}
+
+	buffer = kvmalloc(buffer_pos, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer_pos = 0;
+	for (i = 0; i < compound->compound_header.count; i++) {
+		struct fuse_args *op_args = compound->op_args[i];
+		struct fuse_in_header *hdr;
+		size_t needed_size = sizeof(struct fuse_in_header);
+		int j;
+
+		for (j = 0; j < op_args->in_numargs; j++)
+			needed_size += op_args->in_args[j].size;
+
+		hdr = (struct fuse_in_header *)(buffer + buffer_pos);
+		memset(hdr, 0, sizeof(*hdr));
+		hdr->len = needed_size;
+		hdr->opcode = op_args->opcode;
+		hdr->nodeid = op_args->nodeid;
+		hdr->uid = from_kuid(compound->fm->fc->user_ns,
+				     current_fsuid());
+		hdr->gid = from_kgid(compound->fm->fc->user_ns,
+				     current_fsgid());
+		hdr->pid = pid_nr_ns(task_pid(current),
+				     compound->fm->fc->pid_ns);
+		buffer_pos += sizeof(*hdr);
+
+		for (j = 0; j < op_args->in_numargs; j++) {
+			memcpy(buffer + buffer_pos, op_args->in_args[j].value,
+			       op_args->in_args[j].size);
+			buffer_pos += op_args->in_args[j].size;
+		}
+	}
+
+	resp_buffer_size = total_expected_out_size +
+			   (compound->compound_header.count *
+			    sizeof(struct fuse_out_header));
+
+	resp_payload = kvmalloc(resp_buffer_size, GFP_KERNEL | __GFP_ZERO);
+	if (!resp_payload) {
+		ret = -ENOMEM;
+		goto out_free_buffer;
+	}
+
+	compound->compound_header.result_size = total_expected_out_size;
+
+	args.in_args[0].size = sizeof(compound->compound_header);
+	args.in_args[0].value = &compound->compound_header;
+	args.in_args[1].size = buffer_pos;
+	args.in_args[1].value = buffer;
+
+	args.out_args[0].size = sizeof(compound->result_header);
+	args.out_args[0].value = &compound->result_header;
+	args.out_args[1].size = resp_buffer_size;
+	args.out_args[1].value = resp_payload;
+
+	ret = fuse_simple_request(compound->fm, &args);
+	if (ret < 0)
+		goto out;
+
+	actual_response_size = args.out_args[1].size;
+
+	if (actual_response_size < sizeof(struct fuse_compound_out)) {
+		pr_info_ratelimited("FUSE: compound response too small (%zu bytes, minimum %zu bytes)\n",
+				    actual_response_size,
+				    sizeof(struct fuse_compound_out));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = fuse_compound_parse_resp(compound, compound->result_header.count,
+				       (char *)resp_payload,
+				       actual_response_size);
+out:
+	kvfree(resp_payload);
+out_free_buffer:
+	kvfree(buffer);
+	return ret;
+}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 7f16049387d1..6dddbe2b027b 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1273,6 +1273,18 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap,
 int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args,
 			   gfp_t gfp_flags);
 
+/**
+ * Compound request API
+ */
+struct fuse_compound_req;
+ssize_t fuse_compound_send(struct fuse_compound_req *compound);
+
+struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 flags);
+int fuse_compound_add(struct fuse_compound_req *compound,
+		      struct fuse_args *args);
+int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx);
+void fuse_compound_free(struct fuse_compound_req *compound);
+
 /**
  * Assign a unique id to a fuse request
  */
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index c13e1f9a2f12..848323acecdc 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -664,6 +664,13 @@ enum fuse_opcode {
 	FUSE_STATX		= 52,
 	FUSE_COPY_FILE_RANGE_64	= 53,
 
+	/* A compound request works like multiple simple requests.
+	 * This is a special case for calls that can be combined atomic on the
+	 * fuse server. If the server actually does atomically execute the command is
+	 * left to the fuse server implementation.
+	 */
+	FUSE_COMPOUND		= 101,
+
 	/* CUSE specific operations */
 	CUSE_INIT		= 4096,
 
@@ -1245,6 +1252,36 @@ struct fuse_supp_groups {
 	uint32_t	groups[];
 };
 
+#define FUSE_MAX_COMPOUND_OPS   16        /* Maximum operations per compound */
+
+/*
+ * Compound request header
+ *
+ * This header is followed by the fuse requests
+ */
+struct fuse_compound_in {
+	uint32_t	count;			/* Number of operations */
+	uint32_t	flags;			/* Compound flags */
+
+	/* Total size of all results.
+	 * This is needed for preallocating the whole result for all
+	 * commands in this compound.
+	 */
+	uint32_t	result_size;
+	uint64_t	reserved;
+};
+
+/*
+ * Compound response header
+ *
+ * This header is followed by complete fuse responses
+ */
+struct fuse_compound_out {
+	uint32_t	count;     /* Number of results */
+	uint32_t	flags;     /* Result flags */
+	uint64_t	reserved;
+};
+
 /**
  * Size of the ring buffer header
  */

-- 
2.51.0


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

* [PATCH RFC v3 2/3] fuse: add an implementation of open+getattr
  2026-01-08 14:23 [PATCH RFC v3 0/3] fuse: compound commands horst
  2026-01-08 14:23 ` [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests horst
@ 2026-01-08 14:23 ` horst
  2026-01-08 14:23 ` [PATCH RFC v3 3/3] fuse: use the newly created helper functions horst
  2 siblings, 0 replies; 8+ messages in thread
From: horst @ 2026-01-08 14:23 UTC (permalink / raw)
  To: Miklos Szeredi, Bernd Schubert, Joanne Koong
  Cc: linux-kernel, linux-fsdevel, Horst Birthelmer

From: Horst Birthelmer <hbirthelmer@ddn.com>

The discussion about compound commands in fuse was
started over an argument to add a new operation that
will open a file and return its attributes in the same operation.

Here is a demonstration of that use case with compound commands.

Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
 fs/fuse/file.c   | 143 ++++++++++++++++++++++++++++++++++++++++++++++++-------
 fs/fuse/fuse_i.h |  15 +++++-
 fs/fuse/inode.c  |   6 +++
 fs/fuse/ioctl.c  |   2 +-
 4 files changed, 148 insertions(+), 18 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 01bc894e9c2b..676f6bfde9f8 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -23,6 +23,39 @@
 #include <linux/task_io_accounting_ops.h>
 #include <linux/iomap.h>
 
+/*
+ * Helper function to initialize fuse_args for OPEN/OPENDIR operations
+ */
+void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode,
+			 struct fuse_open_in *inarg, struct fuse_open_out *outarg)
+{
+	args->opcode = opcode;
+	args->nodeid = nodeid;
+	args->in_numargs = 1;
+	args->in_args[0].size = sizeof(*inarg);
+	args->in_args[0].value = inarg;
+	args->out_numargs = 1;
+	args->out_args[0].size = sizeof(*outarg);
+	args->out_args[0].value = outarg;
+}
+
+/*
+ * Helper function to initialize fuse_args for GETATTR operations
+ */
+void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid,
+			     struct fuse_getattr_in *inarg,
+			     struct fuse_attr_out *outarg)
+{
+	args->opcode = FUSE_GETATTR;
+	args->nodeid = nodeid;
+	args->in_numargs = 1;
+	args->in_args[0].size = sizeof(*inarg);
+	args->in_args[0].value = inarg;
+	args->out_numargs = 1;
+	args->out_args[0].size = sizeof(*outarg);
+	args->out_args[0].value = outarg;
+}
+
 static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 			  unsigned int open_flags, int opcode,
 			  struct fuse_open_out *outargp)
@@ -126,8 +159,66 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
 	}
 }
 
+static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid,
+				      int flags, int opcode,
+				      struct fuse_file *ff,
+				      struct fuse_attr_out *outattrp,
+				      struct fuse_open_out *outopenp)
+{
+	struct fuse_compound_req *compound;
+	struct fuse_args open_args = {};
+	struct fuse_args getattr_args = {};
+	struct fuse_open_in open_in = {};
+	struct fuse_getattr_in getattr_in = {};
+	int err;
+
+	compound = fuse_compound_alloc(fm, 0);
+	if (IS_ERR(compound))
+		return PTR_ERR(compound);
+
+	open_in.flags = flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
+	if (!fm->fc->atomic_o_trunc)
+		open_in.flags &= ~O_TRUNC;
+
+	if (fm->fc->handle_killpriv_v2 &&
+	    (open_in.flags & O_TRUNC) && !capable(CAP_FSETID))
+		open_in.open_flags |= FUSE_OPEN_KILL_SUIDGID;
+
+	fuse_open_args_fill(&open_args, nodeid, opcode, &open_in, outopenp);
+
+	err = fuse_compound_add(compound, &open_args);
+	if (err)
+		goto out;
+
+	fuse_getattr_args_fill(&getattr_args, nodeid, &getattr_in, outattrp);
+
+	err = fuse_compound_add(compound, &getattr_args);
+	if (err)
+		goto out;
+
+	err = fuse_compound_send(compound);
+	if (err)
+		goto out;
+
+	err = fuse_compound_get_error(compound, 0);
+	if (err)
+		goto out;
+
+	err = fuse_compound_get_error(compound, 1);
+	if (err)
+		goto out;
+
+	ff->fh = outopenp->fh;
+	ff->open_flags = outopenp->open_flags;
+
+out:
+	fuse_compound_free(compound);
+	return err;
+}
+
 struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
-				 unsigned int open_flags, bool isdir)
+				struct inode *inode,
+				unsigned int open_flags, bool isdir)
 {
 	struct fuse_conn *fc = fm->fc;
 	struct fuse_file *ff;
@@ -153,23 +244,44 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
 	if (open) {
 		/* Store outarg for fuse_finish_open() */
 		struct fuse_open_out *outargp = &ff->args->open_outarg;
-		int err;
+		int err = -ENOSYS;
+
+		if (inode && fc->compound_open_getattr) {
+			struct fuse_attr_out attr_outarg;
+
+			err = fuse_compound_open_getattr(fm, nodeid, open_flags,
+							 opcode, ff,
+							 &attr_outarg, outargp);
+			if (!err)
+				fuse_change_attributes(inode, &attr_outarg.attr,
+						       NULL,
+						       ATTR_TIMEOUT(&attr_outarg),
+						       fuse_get_attr_version(fc));
+		}
+		if (err == -ENOSYS) {
+			err = fuse_send_open(fm, nodeid, open_flags, opcode,
+					     outargp);
+			if (!err) {
+				ff->fh = outargp->fh;
+				ff->open_flags = outargp->open_flags;
+			}
+		}
 
-		err = fuse_send_open(fm, nodeid, open_flags, opcode, outargp);
-		if (!err) {
-			ff->fh = outargp->fh;
-			ff->open_flags = outargp->open_flags;
-		} else if (err != -ENOSYS) {
-			fuse_file_free(ff);
-			return ERR_PTR(err);
-		} else {
-			if (isdir) {
+		if (err) {
+			if (err != -ENOSYS) {
+				/* err is not ENOSYS */
+				fuse_file_free(ff);
+				return ERR_PTR(err);
+			} else {
 				/* No release needed */
 				kfree(ff->args);
 				ff->args = NULL;
-				fc->no_opendir = 1;
-			} else {
-				fc->no_open = 1;
+
+				/* we don't have open */
+				if (isdir)
+					fc->no_opendir = 1;
+				else
+					fc->no_open = 1;
 			}
 		}
 	}
@@ -185,11 +297,10 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
 int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
 		 bool isdir)
 {
-	struct fuse_file *ff = fuse_file_open(fm, nodeid, file->f_flags, isdir);
+	struct fuse_file *ff = fuse_file_open(fm, nodeid, file_inode(file), file->f_flags, isdir);
 
 	if (!IS_ERR(ff))
 		file->private_data = ff;
-
 	return PTR_ERR_OR_ZERO(ff);
 }
 EXPORT_SYMBOL_GPL(fuse_do_open);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 6dddbe2b027b..e7828405e262 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -924,6 +924,9 @@ struct fuse_conn {
 	/* Use io_uring for communication */
 	unsigned int io_uring;
 
+	/* Does the filesystem support compound operations? */
+	unsigned int compound_open_getattr:1;
+
 	/** Maximum stack depth for passthrough backing files */
 	int max_stack_depth;
 
@@ -1179,6 +1182,14 @@ struct fuse_io_args {
 void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
 			 size_t count, int opcode);
 
+/*
+ * Helper functions to initialize fuse_args for common operations
+ */
+void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode,
+			 struct fuse_open_in *inarg, struct fuse_open_out *outarg);
+void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid,
+			    struct fuse_getattr_in *inarg,
+			    struct fuse_attr_out *outarg);
 
 struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release);
 void fuse_file_free(struct fuse_file *ff);
@@ -1555,7 +1566,9 @@ void fuse_file_io_release(struct fuse_file *ff, struct inode *inode);
 
 /* file.c */
 struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid,
-				 unsigned int open_flags, bool isdir);
+								struct inode *inode,
+								unsigned int open_flags,
+								bool isdir);
 void fuse_file_release(struct inode *inode, struct fuse_file *ff,
 		       unsigned int open_flags, fl_owner_t id, bool isdir);
 
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 819e50d66622..a5fd721be96d 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -991,6 +991,12 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
 	fc->blocked = 0;
 	fc->initialized = 0;
 	fc->connected = 1;
+
+	/* pretend fuse server supports compound operations
+	 * until it tells us otherwise.
+	 */
+	fc->compound_open_getattr = 1;
+
 	atomic64_set(&fc->attr_version, 1);
 	atomic64_set(&fc->evict_ctr, 1);
 	get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c
index fdc175e93f74..07a02e47b2c3 100644
--- a/fs/fuse/ioctl.c
+++ b/fs/fuse/ioctl.c
@@ -494,7 +494,7 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct inode *inode)
 	if (!S_ISREG(inode->i_mode) && !isdir)
 		return ERR_PTR(-ENOTTY);
 
-	return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir);
+	return fuse_file_open(fm, get_node_id(inode), NULL, O_RDONLY, isdir);
 }
 
 static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file *ff)

-- 
2.51.0


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

* [PATCH RFC v3 3/3] fuse: use the newly created helper functions
  2026-01-08 14:23 [PATCH RFC v3 0/3] fuse: compound commands horst
  2026-01-08 14:23 ` [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests horst
  2026-01-08 14:23 ` [PATCH RFC v3 2/3] fuse: add an implementation of open+getattr horst
@ 2026-01-08 14:23 ` horst
  2026-01-08 22:19   ` Joanne Koong
  2 siblings, 1 reply; 8+ messages in thread
From: horst @ 2026-01-08 14:23 UTC (permalink / raw)
  To: Miklos Szeredi, Bernd Schubert, Joanne Koong
  Cc: linux-kernel, linux-fsdevel, Horst Birthelmer

From: Horst Birthelmer <hbirthelmer@ddn.com>

new helper functions are:
- fuse_getattr_args_fill()
- fuse_open_args_fill()

Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
 fs/fuse/dir.c  | 9 +--------
 fs/fuse/file.c | 9 +--------
 2 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4b6b3d2758ff..ca8b69282c60 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1493,14 +1493,7 @@ static int fuse_do_getattr(struct mnt_idmap *idmap, struct inode *inode,
 		inarg.getattr_flags |= FUSE_GETATTR_FH;
 		inarg.fh = ff->fh;
 	}
-	args.opcode = FUSE_GETATTR;
-	args.nodeid = get_node_id(inode);
-	args.in_numargs = 1;
-	args.in_args[0].size = sizeof(inarg);
-	args.in_args[0].value = &inarg;
-	args.out_numargs = 1;
-	args.out_args[0].size = sizeof(outarg);
-	args.out_args[0].value = &outarg;
+	fuse_getattr_args_fill(&args, get_node_id(inode), &inarg, &outarg);
 	err = fuse_simple_request(fm, &args);
 	if (!err) {
 		if (fuse_invalid_attr(&outarg.attr) ||
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 676f6bfde9f8..c0375b32967d 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -73,14 +73,7 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
 		inarg.open_flags |= FUSE_OPEN_KILL_SUIDGID;
 	}
 
-	args.opcode = opcode;
-	args.nodeid = nodeid;
-	args.in_numargs = 1;
-	args.in_args[0].size = sizeof(inarg);
-	args.in_args[0].value = &inarg;
-	args.out_numargs = 1;
-	args.out_args[0].size = sizeof(*outargp);
-	args.out_args[0].value = outargp;
+	fuse_open_args_fill(&args, nodeid, opcode, &inarg, outargp);
 
 	return fuse_simple_request(fm, &args);
 }

-- 
2.51.0


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

* Re: [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests
  2026-01-08 14:23 ` [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests horst
@ 2026-01-08 22:01   ` Bernd Schubert
  2026-01-08 22:12     ` Joanne Koong
  0 siblings, 1 reply; 8+ messages in thread
From: Bernd Schubert @ 2026-01-08 22:01 UTC (permalink / raw)
  To: horst, Miklos Szeredi, Bernd Schubert, Joanne Koong
  Cc: linux-kernel, linux-fsdevel, Horst Birthelmer, syzbot



On 1/8/26 15:23, horst@birthelmer.com wrote:
> From: Horst Birthelmer <hbirthelmer@ddn.com>
> 
> For a FUSE_COMPOUND we add a header that contains information
> about how many commands there are in the compound and about the
> size of the expected result. This will make the interpretation
> in libfuse easier, since we can preallocate the whole result.
> Then we append the requests that belong to this compound.
> 
> The API for the compound command has:
>   fuse_compound_alloc()
>   fuse_compound_add()
>   fuse_compound_send()
>   fuse_compound_free()
> 
> Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> Tested-by: syzbot@syzkaller.appspotmail.com
> ---
>  fs/fuse/Makefile          |   2 +-
>  fs/fuse/compound.c        | 276 ++++++++++++++++++++++++++++++++++++++++++++++
>  fs/fuse/fuse_i.h          |  12 ++
>  include/uapi/linux/fuse.h |  37 +++++++
>  4 files changed, 326 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> index 22ad9538dfc4..4c09038ef995 100644
> --- a/fs/fuse/Makefile
> +++ b/fs/fuse/Makefile
> @@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o
>  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 += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o compound.o
>  fuse-y += iomode.o
>  fuse-$(CONFIG_FUSE_DAX) += dax.o
>  fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
> diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c
> new file mode 100644
> index 000000000000..2f292ae3e816
> --- /dev/null
> +++ b/fs/fuse/compound.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025
> + *
> + * This file implements compound operations for FUSE, allowing multiple
> + * operations to be batched into a single request to reduce round trips
> + * between kernel and userspace.
> + */
> +
> +#include "fuse_i.h"
> +
> +/*
> + * Compound request builder and state tracker and args pointer storage
> + */
> +struct fuse_compound_req {
> +	struct fuse_mount *fm;
> +	struct fuse_compound_in compound_header;
> +	struct fuse_compound_out result_header;
> +
> +	/* Per-operation error codes */
> +	int op_errors[FUSE_MAX_COMPOUND_OPS];
> +	/* Original fuse_args for response parsing */
> +	struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
> +
> +	bool parsed;				/* Prevent double-parsing of response */

Just for Joanne and other reviewers, Horst is preparing the next
version, this 'parsed' is also going to be removed.

> +};
> +
> +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 flags)
> +{
> +	struct fuse_compound_req *compound;
> +
> +	compound = kzalloc(sizeof(*compound), GFP_KERNEL);
> +	if (!compound)
> +		return ERR_PTR(-ENOMEM);
> +
> +	compound->fm = fm;
> +	compound->compound_header.flags = flags;
> +
> +	return compound;
> +}
> +
> +void fuse_compound_free(struct fuse_compound_req *compound)
> +{
> +	if (!compound)
> +		return;
> +
> +	kfree(compound);
> +}
> +
> +int fuse_compound_add(struct fuse_compound_req *compound,
> +		      struct fuse_args *args)
> +{
> +	if (!compound ||
> +	    compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS)
> +		return -EINVAL;
> +
> +	if (args->in_pages)
> +		return -EINVAL;
> +
> +	compound->op_args[compound->compound_header.count] = args;
> +	compound->compound_header.count++;
> +	return 0;
> +}
> +
> +static void *fuse_copy_response_data(struct fuse_args *args,
> +				     char *response_data)
> +{
> +	size_t copied = 0;
> +	int i;
> +
> +	for (i = 0; i < args->out_numargs; i++) {
> +		struct fuse_arg current_arg = args->out_args[i];
> +		size_t arg_size;
> +
> +		/*
> +		 * Last argument with out_pages: copy to pages
> +		 * External payload (in the last out arg) is not supported
> +		 * at the moment
> +		 */
> +		if (i == args->out_numargs - 1 && args->out_pages)
> +			return response_data;
> +
> +		arg_size = current_arg.size;
> +
> +		if (current_arg.value && arg_size > 0) {
> +			memcpy(current_arg.value,
> +			       (char *)response_data + copied, arg_size);
> +			copied += arg_size;
> +		}
> +	}
> +
> +	return (char *)response_data + copied;
> +}
> +
> +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx)
> +{
> +	return compound->op_errors[op_idx];
> +}
> +
> +static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound,
> +					int op_index, void *op_out_data,
> +					void *response_end)
> +{
> +	struct fuse_out_header *op_hdr = op_out_data;
> +	struct fuse_args *args = compound->op_args[op_index];
> +
> +	if (op_hdr->len < sizeof(struct fuse_out_header))
> +		return NULL;
> +
> +	/* Check if the entire operation response fits in the buffer */
> +	if ((char *)op_out_data + op_hdr->len > (char *)response_end)
> +		return NULL;
> +
> +	if (op_hdr->error != 0)
> +		compound->op_errors[op_index] = op_hdr->error;
> +
> +	if (args && op_hdr->len > sizeof(struct fuse_out_header))
> +		return fuse_copy_response_data(args, op_out_data +
> +					       sizeof(struct fuse_out_header));
> +
> +	/* No response data, just advance past the header */
> +	return (char *)op_out_data + op_hdr->len;
> +}
> +
> +static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
> +				    u32 count, void *response,
> +				    size_t response_size)
> +{
> +	void *op_out_data = response;
> +	void *response_end = (char *)response + response_size;
> +	int i;
> +
> +	if (compound->parsed)
> +		return 0;
> +
> +	if (!response || response_size < sizeof(struct fuse_out_header))
> +		return -EIO;
> +
> +	for (i = 0; i < count && i < compound->result_header.count; i++) {
> +		op_out_data = fuse_compound_parse_one_op(compound, i,
> +							 op_out_data,
> +							 response_end);
> +		if (!op_out_data)
> +			return -EIO;
> +	}
> +
> +	compound->parsed = true;
> +	return 0;
> +}
> +
> +ssize_t fuse_compound_send(struct fuse_compound_req *compound)
> +{
> +	struct fuse_args args = {
> +		.opcode = FUSE_COMPOUND,
> +		.nodeid = 0,
> +		.in_numargs = 2,
> +		.out_numargs = 2,
> +		.out_argvar = true,
> +	};
> +	size_t resp_buffer_size;
> +	size_t actual_response_size;
> +	size_t buffer_pos;
> +	size_t total_expected_out_size;
> +	void *buffer = NULL;
> +	void *resp_payload;
> +	ssize_t ret;
> +	int i;
> +
> +	if (!compound) {
> +		pr_info_ratelimited("FUSE: compound request is NULL in %s\n",
> +				    __func__);
> +		return -EINVAL;
> +	}
> +
> +	if (compound->compound_header.count == 0) {
> +		pr_info_ratelimited("FUSE: compound request contains no operations\n");
> +		return -EINVAL;
> +	}
> +
> +	buffer_pos = 0;
> +	total_expected_out_size = 0;
> +
> +	for (i = 0; i < compound->compound_header.count; i++) {
> +		struct fuse_args *op_args = compound->op_args[i];
> +		size_t needed_size = sizeof(struct fuse_in_header);
> +		int j;
> +
> +		for (j = 0; j < op_args->in_numargs; j++)
> +			needed_size += op_args->in_args[j].size;
> +
> +		buffer_pos += needed_size;
> +
> +		for (j = 0; j < op_args->out_numargs; j++)
> +			total_expected_out_size += op_args->out_args[j].size;
> +	}
> +
> +	buffer = kvmalloc(buffer_pos, GFP_KERNEL);
> +	if (!buffer)
> +		return -ENOMEM;
> +
> +	buffer_pos = 0;
> +	for (i = 0; i < compound->compound_header.count; i++) {
> +		struct fuse_args *op_args = compound->op_args[i];
> +		struct fuse_in_header *hdr;
> +		size_t needed_size = sizeof(struct fuse_in_header);
> +		int j;
> +
> +		for (j = 0; j < op_args->in_numargs; j++)
> +			needed_size += op_args->in_args[j].size;
> +
> +		hdr = (struct fuse_in_header *)(buffer + buffer_pos);
> +		memset(hdr, 0, sizeof(*hdr));
> +		hdr->len = needed_size;
> +		hdr->opcode = op_args->opcode;
> +		hdr->nodeid = op_args->nodeid;
> +		hdr->uid = from_kuid(compound->fm->fc->user_ns,
> +				     current_fsuid());
> +		hdr->gid = from_kgid(compound->fm->fc->user_ns,
> +				     current_fsgid());
> +		hdr->pid = pid_nr_ns(task_pid(current),
> +				     compound->fm->fc->pid_ns);
> +		buffer_pos += sizeof(*hdr);
> +
> +		for (j = 0; j < op_args->in_numargs; j++) {
> +			memcpy(buffer + buffer_pos, op_args->in_args[j].value,
> +			       op_args->in_args[j].size);
> +			buffer_pos += op_args->in_args[j].size;
> +		}
> +	}
> +
> +	resp_buffer_size = total_expected_out_size +
> +			   (compound->compound_header.count *
> +			    sizeof(struct fuse_out_header));
> +
> +	resp_payload = kvmalloc(resp_buffer_size, GFP_KERNEL | __GFP_ZERO);
> +	if (!resp_payload) {
> +		ret = -ENOMEM;
> +		goto out_free_buffer;
> +	}
> +
> +	compound->compound_header.result_size = total_expected_out_size;
> +
> +	args.in_args[0].size = sizeof(compound->compound_header);
> +	args.in_args[0].value = &compound->compound_header;
> +	args.in_args[1].size = buffer_pos;
> +	args.in_args[1].value = buffer;
> +
> +	args.out_args[0].size = sizeof(compound->result_header);
> +	args.out_args[0].value = &compound->result_header;
> +	args.out_args[1].size = resp_buffer_size;
> +	args.out_args[1].value = resp_payload;
> +
> +	ret = fuse_simple_request(compound->fm, &args);
> +	if (ret < 0)
> +		goto out;
> +
> +	actual_response_size = args.out_args[1].size;
> +
> +	if (actual_response_size < sizeof(struct fuse_compound_out)) {
> +		pr_info_ratelimited("FUSE: compound response too small (%zu bytes, minimum %zu bytes)\n",
> +				    actual_response_size,
> +				    sizeof(struct fuse_compound_out));
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	ret = fuse_compound_parse_resp(compound, compound->result_header.count,
> +				       (char *)resp_payload,
> +				       actual_response_size);
> +out:
> +	kvfree(resp_payload);
> +out_free_buffer:
> +	kvfree(buffer);
> +	return ret;
> +}
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 7f16049387d1..6dddbe2b027b 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1273,6 +1273,18 @@ static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap,
>  int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args,
>  			   gfp_t gfp_flags);
>  
> +/**
> + * Compound request API
> + */
> +struct fuse_compound_req;
> +ssize_t fuse_compound_send(struct fuse_compound_req *compound);
> +
> +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 flags);
> +int fuse_compound_add(struct fuse_compound_req *compound,
> +		      struct fuse_args *args);
> +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx);
> +void fuse_compound_free(struct fuse_compound_req *compound);
> +
>  /**
>   * Assign a unique id to a fuse request
>   */
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index c13e1f9a2f12..848323acecdc 100644
> --- a/include/uapi/linux/fuse.h
> +++ b/include/uapi/linux/fuse.h
> @@ -664,6 +664,13 @@ enum fuse_opcode {
>  	FUSE_STATX		= 52,
>  	FUSE_COPY_FILE_RANGE_64	= 53,
>  
> +	/* A compound request works like multiple simple requests.
> +	 * This is a special case for calls that can be combined atomic on the
> +	 * fuse server. If the server actually does atomically execute the command is
> +	 * left to the fuse server implementation.
> +	 */
> +	FUSE_COMPOUND		= 101,

This is our internal value, next round will have set to the right value.

> +
>  	/* CUSE specific operations */
>  	CUSE_INIT		= 4096,
>  
> @@ -1245,6 +1252,36 @@ struct fuse_supp_groups {
>  	uint32_t	groups[];
>  };
>  
> +#define FUSE_MAX_COMPOUND_OPS   16        /* Maximum operations per compound */
> +
> +/*
> + * Compound request header
> + *
> + * This header is followed by the fuse requests
> + */
> +struct fuse_compound_in {
> +	uint32_t	count;			/* Number of operations */
> +	uint32_t	flags;			/* Compound flags */
> +
> +	/* Total size of all results.
> +	 * This is needed for preallocating the whole result for all
> +	 * commands in this compound.
> +	 */
> +	uint32_t	result_size;
> +	uint64_t	reserved;
> +};
> +
> +/*
> + * Compound response header
> + *
> + * This header is followed by complete fuse responses
> + */
> +struct fuse_compound_out {
> +	uint32_t	count;     /* Number of results */
> +	uint32_t	flags;     /* Result flags */
> +	uint64_t	reserved;
> +};
> +
>  /**
>   * Size of the ring buffer header
>   */
> 

Thanks,
Bernd

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

* Re: [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests
  2026-01-08 22:01   ` Bernd Schubert
@ 2026-01-08 22:12     ` Joanne Koong
  0 siblings, 0 replies; 8+ messages in thread
From: Joanne Koong @ 2026-01-08 22:12 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: horst, Miklos Szeredi, Bernd Schubert, linux-kernel,
	linux-fsdevel, Horst Birthelmer, syzbot

On Thu, Jan 8, 2026 at 2:01 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
> On 1/8/26 15:23, horst@birthelmer.com wrote:
> > From: Horst Birthelmer <hbirthelmer@ddn.com>
> >
> > For a FUSE_COMPOUND we add a header that contains information
> > about how many commands there are in the compound and about the
> > size of the expected result. This will make the interpretation
> > in libfuse easier, since we can preallocate the whole result.
> > Then we append the requests that belong to this compound.
> >
> > The API for the compound command has:
> >   fuse_compound_alloc()
> >   fuse_compound_add()
> >   fuse_compound_send()
> >   fuse_compound_free()
> >
> > Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> > Tested-by: syzbot@syzkaller.appspotmail.com
> > ---
> >  fs/fuse/Makefile          |   2 +-
> >  fs/fuse/compound.c        | 276 ++++++++++++++++++++++++++++++++++++++++++++++
> >  fs/fuse/fuse_i.h          |  12 ++
> >  include/uapi/linux/fuse.h |  37 +++++++
> >  4 files changed, 326 insertions(+), 1 deletion(-)
> >
> > diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> > index 22ad9538dfc4..4c09038ef995 100644
> > --- a/fs/fuse/Makefile
> > +++ b/fs/fuse/Makefile
> > @@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o
> >  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 += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o compound.o
> >  fuse-y += iomode.o
> >  fuse-$(CONFIG_FUSE_DAX) += dax.o
> >  fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o
> > diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c
> > new file mode 100644
> > index 000000000000..2f292ae3e816
> > --- /dev/null
> > +++ b/fs/fuse/compound.c
> > @@ -0,0 +1,276 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * FUSE: Filesystem in Userspace
> > + * Copyright (C) 2025
> > + *
> > + * This file implements compound operations for FUSE, allowing multiple
> > + * operations to be batched into a single request to reduce round trips
> > + * between kernel and userspace.
> > + */
> > +
> > +#include "fuse_i.h"
> > +
> > +/*
> > + * Compound request builder and state tracker and args pointer storage
> > + */
> > +struct fuse_compound_req {
> > +     struct fuse_mount *fm;
> > +     struct fuse_compound_in compound_header;
> > +     struct fuse_compound_out result_header;
> > +
> > +     /* Per-operation error codes */
> > +     int op_errors[FUSE_MAX_COMPOUND_OPS];
> > +     /* Original fuse_args for response parsing */
> > +     struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
> > +
> > +     bool parsed;                            /* Prevent double-parsing of response */
>
> Just for Joanne and other reviewers, Horst is preparing the next
> version, this 'parsed' is also going to be removed.

Sounds great, thanks for the update. I'll wait for the next version
before reviewing.

Thanks,
Joanne
>
> > +};

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

* Re: [PATCH RFC v3 3/3] fuse: use the newly created helper functions
  2026-01-08 14:23 ` [PATCH RFC v3 3/3] fuse: use the newly created helper functions horst
@ 2026-01-08 22:19   ` Joanne Koong
  2026-01-09  7:20     ` Horst Birthelmer
  0 siblings, 1 reply; 8+ messages in thread
From: Joanne Koong @ 2026-01-08 22:19 UTC (permalink / raw)
  To: horst
  Cc: Miklos Szeredi, Bernd Schubert, linux-kernel, linux-fsdevel,
	Horst Birthelmer

On Thu, Jan 8, 2026 at 6:23 AM <horst@birthelmer.com> wrote:
>
> From: Horst Birthelmer <hbirthelmer@ddn.com>
>
> new helper functions are:
> - fuse_getattr_args_fill()
> - fuse_open_args_fill()
>
> Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> ---
>  fs/fuse/dir.c  | 9 +--------
>  fs/fuse/file.c | 9 +--------
>  2 files changed, 2 insertions(+), 16 deletions(-)
>
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 4b6b3d2758ff..ca8b69282c60 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -1493,14 +1493,7 @@ static int fuse_do_getattr(struct mnt_idmap *idmap, struct inode *inode,
>                 inarg.getattr_flags |= FUSE_GETATTR_FH;
>                 inarg.fh = ff->fh;
>         }
> -       args.opcode = FUSE_GETATTR;
> -       args.nodeid = get_node_id(inode);
> -       args.in_numargs = 1;
> -       args.in_args[0].size = sizeof(inarg);
> -       args.in_args[0].value = &inarg;
> -       args.out_numargs = 1;
> -       args.out_args[0].size = sizeof(outarg);
> -       args.out_args[0].value = &outarg;
> +       fuse_getattr_args_fill(&args, get_node_id(inode), &inarg, &outarg);
>         err = fuse_simple_request(fm, &args);
>         if (!err) {
>                 if (fuse_invalid_attr(&outarg.attr) ||
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index 676f6bfde9f8..c0375b32967d 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -73,14 +73,7 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
>                 inarg.open_flags |= FUSE_OPEN_KILL_SUIDGID;
>         }
>
> -       args.opcode = opcode;
> -       args.nodeid = nodeid;
> -       args.in_numargs = 1;
> -       args.in_args[0].size = sizeof(inarg);
> -       args.in_args[0].value = &inarg;
> -       args.out_numargs = 1;
> -       args.out_args[0].size = sizeof(*outargp);
> -       args.out_args[0].value = outargp;
> +       fuse_open_args_fill(&args, nodeid, opcode, &inarg, outargp);
>
>         return fuse_simple_request(fm, &args);
>  }
>

This is a very minor nit but imo the split is a bit nicer if patch 2/3
is this patch with your helper changes:

+/*
+ * Helper function to initialize fuse_args for OPEN/OPENDIR operations
+ */
+void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode,
+ struct fuse_open_in *inarg, struct fuse_open_out *outarg)
+{
+ args->opcode = opcode;
...
+}
+
+/*
+ * Helper function to initialize fuse_args for GETATTR operations
+ */
+void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid,
+     struct fuse_getattr_in *inarg,
+     struct fuse_attr_out *outarg)
+{
+ args->opcode = FUSE_GETATTR;
...
+}
+

and then patch 3 is your open+getattr changes. That way, it's easier
to see that the changes in this patch to fuse_do_getattr() and
fuse_send_open() have no functional changes in logic.


Thanks,
Joanne
> --
> 2.51.0
>

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

* Re: Re: [PATCH RFC v3 3/3] fuse: use the newly created helper functions
  2026-01-08 22:19   ` Joanne Koong
@ 2026-01-09  7:20     ` Horst Birthelmer
  0 siblings, 0 replies; 8+ messages in thread
From: Horst Birthelmer @ 2026-01-09  7:20 UTC (permalink / raw)
  To: Joanne Koong
  Cc: horst, Miklos Szeredi, Bernd Schubert, linux-kernel,
	linux-fsdevel, Horst Birthelmer

On Thu, Jan 08, 2026 at 02:19:10PM -0800, Joanne Koong wrote:
> On Thu, Jan 8, 2026 at 6:23 AM <horst@birthelmer.com> wrote:
> >
> > From: Horst Birthelmer <hbirthelmer@ddn.com>
> >
> > new helper functions are:
> > - fuse_getattr_args_fill()
> > - fuse_open_args_fill()
> >
> > Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> > ---
> >  fs/fuse/dir.c  | 9 +--------
> >  fs/fuse/file.c | 9 +--------
> >  2 files changed, 2 insertions(+), 16 deletions(-)
> >
> > diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> > index 4b6b3d2758ff..ca8b69282c60 100644
> > --- a/fs/fuse/dir.c
> > +++ b/fs/fuse/dir.c
> > @@ -1493,14 +1493,7 @@ static int fuse_do_getattr(struct mnt_idmap *idmap, struct inode *inode,
> >                 inarg.getattr_flags |= FUSE_GETATTR_FH;
> >                 inarg.fh = ff->fh;
> >         }
> > -       args.opcode = FUSE_GETATTR;
> > -       args.nodeid = get_node_id(inode);
> > -       args.in_numargs = 1;
> > -       args.in_args[0].size = sizeof(inarg);
> > -       args.in_args[0].value = &inarg;
> > -       args.out_numargs = 1;
> > -       args.out_args[0].size = sizeof(outarg);
> > -       args.out_args[0].value = &outarg;
> > +       fuse_getattr_args_fill(&args, get_node_id(inode), &inarg, &outarg);
> >         err = fuse_simple_request(fm, &args);
> >         if (!err) {
> >                 if (fuse_invalid_attr(&outarg.attr) ||
> > diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> > index 676f6bfde9f8..c0375b32967d 100644
> > --- a/fs/fuse/file.c
> > +++ b/fs/fuse/file.c
> > @@ -73,14 +73,7 @@ static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
> >                 inarg.open_flags |= FUSE_OPEN_KILL_SUIDGID;
> >         }
> >
> > -       args.opcode = opcode;
> > -       args.nodeid = nodeid;
> > -       args.in_numargs = 1;
> > -       args.in_args[0].size = sizeof(inarg);
> > -       args.in_args[0].value = &inarg;
> > -       args.out_numargs = 1;
> > -       args.out_args[0].size = sizeof(*outargp);
> > -       args.out_args[0].value = outargp;
> > +       fuse_open_args_fill(&args, nodeid, opcode, &inarg, outargp);
> >
> >         return fuse_simple_request(fm, &args);
> >  }
> >
> 
> This is a very minor nit but imo the split is a bit nicer if patch 2/3
> is this patch with your helper changes:
> 
> +/*
> + * Helper function to initialize fuse_args for OPEN/OPENDIR operations
> + */
> +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode,
> + struct fuse_open_in *inarg, struct fuse_open_out *outarg)
> +{
> + args->opcode = opcode;
> ...
> +}
> +
> +/*
> + * Helper function to initialize fuse_args for GETATTR operations
> + */
> +void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid,
> +     struct fuse_getattr_in *inarg,
> +     struct fuse_attr_out *outarg)
> +{
> + args->opcode = FUSE_GETATTR;
> ...
> +}
> +
> 
> and then patch 3 is your open+getattr changes. That way, it's easier
> to see that the changes in this patch to fuse_do_getattr() and
> fuse_send_open() have no functional changes in logic.

My rational here was, that if people don't like the changes to those functions
I can easily backtrack by just not providing this patch, so basically laziness.

I can easily change that.

Thanks for your explanation.

> 
> 
> Thanks,
> Joanne
> > --
> > 2.51.0
> >

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

end of thread, other threads:[~2026-01-09  7:26 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-08 14:23 [PATCH RFC v3 0/3] fuse: compound commands horst
2026-01-08 14:23 ` [PATCH RFC v3 1/3] fuse: add compound command to combine multiple requests horst
2026-01-08 22:01   ` Bernd Schubert
2026-01-08 22:12     ` Joanne Koong
2026-01-08 14:23 ` [PATCH RFC v3 2/3] fuse: add an implementation of open+getattr horst
2026-01-08 14:23 ` [PATCH RFC v3 3/3] fuse: use the newly created helper functions horst
2026-01-08 22:19   ` Joanne Koong
2026-01-09  7:20     ` Horst Birthelmer

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

all inboxes | Powered by JetHome®