* [PATCH v5 0/3] fuse: compound commands
@ 2026-02-10 8:46 Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
` (2 more replies)
0 siblings, 3 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-10 8:46 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert, Joanne Koong, Luis Henriques
Cc: linux-kernel, linux-fsdevel, Horst Birthelmer
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).
The pull request for libfuse is here [2]
That pull request contains a patch for handling compounds
and a patch for passthrough_hp that demonstrates multiple ways of
handling a compound. Either calling the helper in libfuse to decode and
execute every request sequencially or decoding and handling it in the
fuse server itself.
[1] https://lore.kernel.org/linux-fsdevel/CAJfpegshcrjXJ0USZ8RRdBy=e0MxmBTJSCE0xnxG8LXgXy-xuQ@mail.gmail.com/
[2] https://github.com/libfuse/libfuse/pull/1418
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
Changes in v5:
- introduced the flag FUSE_COMPOUND_SEPARABLE as discussed here
- simplify result parsing and streamline the code
- simplify the result and error handling for open+getattr
- fixed a couple of issues pointed out by Joanne
- Link to v4: https://lore.kernel.org/r/20260109-fuse-compounds-upstream-v4-0-0d3b82a4666f@ddn.com
Changes in v4:
- removed RFC
- removed the unnecessary 'parsed' variable in fuse_compound_req, since
we parse the result only once
- reordered the patches about the helper functions to fill in the fuse
args for open and getattr calls
- Link to v3: https://lore.kernel.org/r/20260108-fuse-compounds-upstream-v3-0-8dc91ebf3740@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: create helper functions for filling in fuse args for open and getattr
fuse: add an implementation of open+getattr
fs/fuse/Makefile | 2 +-
fs/fuse/compound.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/dir.c | 26 ++++--
fs/fuse/file.c | 137 +++++++++++++++++++++++-----
fs/fuse/fuse_i.h | 24 ++++-
fs/fuse/inode.c | 6 ++
fs/fuse/ioctl.c | 2 +-
include/uapi/linux/fuse.h | 40 +++++++++
8 files changed, 426 insertions(+), 35 deletions(-)
---
base-commit: 63804fed149a6750ffd28610c5c1c98cce6bd377
change-id: 20251223-fuse-compounds-upstream-c85b4e39b3d3
Best regards,
--
Horst Birthelmer <hbirthelmer@ddn.com>
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-10 8:46 [PATCH v5 0/3] fuse: compound commands Horst Birthelmer
@ 2026-02-10 8:46 ` Horst Birthelmer
2026-02-11 14:59 ` Luis Henriques
2026-02-11 16:13 ` Miklos Szeredi
2026-02-10 8:46 ` [PATCH v5 2/3] fuse: create helper functions for filling in fuse args for open and getattr Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 3/3] fuse: add an implementation of open+getattr Horst Birthelmer
2 siblings, 2 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-10 8:46 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert, Joanne Koong, Luis Henriques
Cc: linux-kernel, linux-fsdevel, Horst Birthelmer
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()
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
fs/fuse/Makefile | 2 +-
fs/fuse/compound.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++
fs/fuse/fuse_i.h | 11 +++
include/uapi/linux/fuse.h | 40 +++++++++
4 files changed, 276 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 22ad9538dfc4b80c6d9b52235bdfead6a6567ae4..4c09038ef995d1b9133c2b6871b97b280a4693b0 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 0000000000000000000000000000000000000000..1a85209f4e99a0e5e54955bcd951eaf395176c12
--- /dev/null
+++ b/fs/fuse/compound.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FUSE: Filesystem in Userspace
+ * Copyright (C) 2025
+ *
+ * Compound operations for FUSE - batch multiple operations into a single
+ * request to reduce round trips between kernel and userspace.
+ */
+
+#include "fuse_i.h"
+
+/*
+ * Compound request builder, 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;
+ int op_errors[FUSE_MAX_COMPOUND_OPS];
+ struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
+};
+
+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 NULL;
+
+ compound->fm = fm;
+ compound->compound_header.flags = flags;
+
+ return 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_resp_data_per_req(const struct fuse_args *args,
+ char *resp)
+{
+ const struct fuse_arg *arg;
+ int i;
+
+ for (i = 0; i < args->out_numargs; i++) {
+ arg = &args->out_args[i];
+ memcpy(arg->value, resp, arg->size);
+ resp += arg->size;
+ }
+
+ return resp;
+}
+
+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, char *response,
+ char *response_end)
+{
+ struct fuse_out_header *op_hdr = (struct fuse_out_header *)response;
+ struct fuse_args *args = compound->op_args[op_index];
+
+ if (op_hdr->len < sizeof(struct fuse_out_header))
+ return NULL;
+
+ if (response + op_hdr->len > response_end)
+ return NULL;
+
+ if (op_hdr->error)
+ compound->op_errors[op_index] = op_hdr->error;
+ else
+ fuse_copy_resp_data_per_req(args, response +
+ sizeof(struct fuse_out_header));
+ /* in case of error, we still need to advance to the next op */
+ return response + op_hdr->len;
+}
+
+static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
+ char *response, size_t response_size)
+{
+ char *response_end = response + response_size;
+ int req_count;
+ int i;
+
+ req_count = min(compound->compound_header.count,
+ compound->result_header.count);
+
+ for (i = 0; i < req_count; i++) {
+ response = fuse_compound_parse_one_op(compound, i, response,
+ response_end);
+ if (!response)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * Build a single operation request in the buffer
+ *
+ * Returns the new buffer position after writing the operation.
+ */
+static char *fuse_compound_build_one_op(struct fuse_conn *fc,
+ struct fuse_args *op_args,
+ char *buffer_pos)
+{
+ 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_pos;
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->len = needed_size;
+ hdr->opcode = op_args->opcode;
+ hdr->nodeid = op_args->nodeid;
+ hdr->uid = from_kuid(fc->user_ns, current_fsuid());
+ hdr->gid = from_kgid(fc->user_ns, current_fsgid());
+ hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
+ buffer_pos += sizeof(*hdr);
+
+ for (j = 0; j < op_args->in_numargs; j++) {
+ memcpy(buffer_pos, op_args->in_args[j].value,
+ op_args->in_args[j].size);
+ buffer_pos += op_args->in_args[j].size;
+ }
+
+ return buffer_pos;
+}
+
+ssize_t fuse_compound_send(struct fuse_compound_req *compound)
+{
+ struct fuse_conn *fc = compound->fm->fc;
+ struct fuse_args args = {
+ .opcode = FUSE_COMPOUND,
+ .in_numargs = 2,
+ .out_numargs = 2,
+ .out_argvar = true,
+ };
+ unsigned int req_count = compound->compound_header.count;
+ size_t total_expected_out_size = 0;
+ size_t actual_response_size;
+ size_t buffer_size = 0;
+ void *resp_payload_buffer;
+ char *buffer_pos;
+ void *buffer = NULL;
+ ssize_t ret;
+ int i, j;
+
+ for (i = 0; i < req_count; i++) {
+ struct fuse_args *op_args = compound->op_args[i];
+ size_t needed_size = sizeof(struct fuse_in_header);
+
+ for (j = 0; j < op_args->in_numargs; j++)
+ needed_size += op_args->in_args[j].size;
+
+ buffer_size += needed_size;
+
+ for (j = 0; j < op_args->out_numargs; j++)
+ total_expected_out_size += op_args->out_args[j].size;
+ }
+
+ buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
+ if (!buffer)
+ return -ENOMEM;
+
+ buffer_pos = buffer;
+ for (i = 0; i < req_count; i++)
+ buffer_pos = fuse_compound_build_one_op(fc,
+ compound->op_args[i],
+ buffer_pos);
+
+ 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_size;
+ args.in_args[1].value = buffer;
+
+ buffer_size = total_expected_out_size +
+ (req_count * sizeof(struct fuse_out_header));
+
+ resp_payload_buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
+ if (!resp_payload_buffer) {
+ ret = -ENOMEM;
+ goto out_free_buffer;
+ }
+
+ args.out_args[0].size = sizeof(compound->result_header);
+ args.out_args[0].value = &compound->result_header;
+ args.out_args[1].size = buffer_size;
+ args.out_args[1].value = resp_payload_buffer;
+
+ ret = fuse_simple_request(compound->fm, &args);
+ if (ret < 0)
+ goto out;
+
+ actual_response_size = args.out_args[1].size;
+
+ ret = fuse_compound_parse_resp(compound, (char *)resp_payload_buffer,
+ actual_response_size);
+out:
+ kfree(resp_payload_buffer);
+out_free_buffer:
+ kfree(buffer);
+ return ret;
+}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 7f16049387d15e869db4be23a93605098588eda9..9ebcd96b6b309d75c86a9c716cbd88aaa55c57ef 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1273,6 +1273,17 @@ 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);
+
/**
* Assign a unique id to a fuse request
*/
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index c13e1f9a2f12bd39f535188cb5466688eba42263..113583c4efb67268174dbe4f68e9ea1c21b45eb6 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 is handled like a single request,
+ * but contains multiple requests as input.
+ * This can be used to signal to the fuse server that
+ * the requests can be combined atomically.
+ */
+ FUSE_COMPOUND = 54,
+
/* CUSE specific operations */
CUSE_INIT = 4096,
@@ -1245,6 +1252,39 @@ struct fuse_supp_groups {
uint32_t groups[];
};
+#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
+
+#define FUSE_COMPOUND_SEPARABLE (1<<0)
+#define FUSE_COMPOUND_ATOMIC (1<<1)
+
+/*
+ * 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.53.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v5 2/3] fuse: create helper functions for filling in fuse args for open and getattr
2026-02-10 8:46 [PATCH v5 0/3] fuse: compound commands Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
@ 2026-02-10 8:46 ` Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 3/3] fuse: add an implementation of open+getattr Horst Birthelmer
2 siblings, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-10 8:46 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert, Joanne Koong, Luis Henriques
Cc: linux-kernel, linux-fsdevel, Horst Birthelmer
From: Horst Birthelmer <hbirthelmer@ddn.com>
create fuse_getattr_args_fill() and fuse_open_args_fill() to fill in
the parameters for the open and getattr calls.
This is in preparation for implementing open+getattr and does not
represent any functional change.
Suggested-by: Joanne Koong <joannelkoong@gmail.com>
Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
---
fs/fuse/dir.c | 26 ++++++++++++++++++--------
fs/fuse/file.c | 26 ++++++++++++++++++--------
fs/fuse/fuse_i.h | 6 ++++++
3 files changed, 42 insertions(+), 16 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4b6b3d2758ff225dc389016017753b09fadff9d1..33fa5fc97ff7f65db585ee1386156ef13cf330cf 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1472,6 +1472,23 @@ static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode,
return 0;
}
+/*
+ * 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_do_getattr(struct mnt_idmap *idmap, struct inode *inode,
struct kstat *stat, struct file *file)
{
@@ -1493,14 +1510,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 3b2a171e652f0c9dd1c9e37253d3d3e88caab148..a408a9668abbb361e2c1e386ebab9dfcb0a7a573 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -23,6 +23,23 @@
#include <linux/task_io_accounting_ops.h>
#include <linux/iomap.h>
+/*
+ * Helper function to initialize fuse_args for OPEN/OPENDIR operations
+ */
+static 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;
+}
+
+
static int fuse_send_open(struct fuse_mount *fm, u64 nodeid,
unsigned int open_flags, int opcode,
struct fuse_open_out *outargp)
@@ -40,14 +57,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);
}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 9ebcd96b6b309d75c86a9c716cbd88aaa55c57ef..fba14f26b67888831fcba6e2ac73399f3c95d5ad 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1179,6 +1179,12 @@ 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_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);
--
2.53.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v5 3/3] fuse: add an implementation of open+getattr
2026-02-10 8:46 [PATCH v5 0/3] fuse: compound commands Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 2/3] fuse: create helper functions for filling in fuse args for open and getattr Horst Birthelmer
@ 2026-02-10 8:46 ` Horst Birthelmer
2 siblings, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-10 8:46 UTC (permalink / raw)
To: Miklos Szeredi, Bernd Schubert, Joanne Koong, Luis Henriques
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 | 111 +++++++++++++++++++++++++++++++++++++++++++++++--------
fs/fuse/fuse_i.h | 7 +++-
fs/fuse/inode.c | 6 +++
fs/fuse/ioctl.c | 2 +-
4 files changed, 108 insertions(+), 18 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index a408a9668abbb361e2c1e386ebab9dfcb0a7a573..73c6a214b29a11fd6341f704bea140b282bb8355 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -136,8 +136,71 @@ static void fuse_file_put(struct fuse_file *ff, bool sync)
}
}
+static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid,
+ struct inode *inode, int flags, int opcode,
+ struct fuse_file *ff,
+ struct fuse_attr_out *outattrp,
+ struct fuse_open_out *outopenp)
+{
+ struct fuse_conn *fc = fm->fc;
+ 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, FUSE_COMPOUND_SEPARABLE);
+ if (!compound)
+ return -ENOMEM;
+
+ 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;
+
+ ff->fh = outopenp->fh;
+ ff->open_flags = outopenp->open_flags;
+
+ err = fuse_compound_get_error(compound, 1);
+ if (err)
+ goto out;
+
+ fuse_change_attributes(inode, &outattrp->attr, NULL,
+ ATTR_TIMEOUT(outattrp),
+ fuse_get_attr_version(fc));
+
+out:
+ kfree(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;
@@ -163,23 +226,40 @@ 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;
- 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 (inode && fc->compound_open_getattr) {
+ struct fuse_attr_out attr_outarg;
+
+ err = fuse_compound_open_getattr(fm, nodeid, inode,
+ open_flags, opcode, ff,
+ &attr_outarg, outargp);
+ }
+
+ 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;
+ }
+ }
+
+ 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;
}
}
}
@@ -195,11 +275,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 fba14f26b67888831fcba6e2ac73399f3c95d5ad..3184ef864cf0b7b8598251dbb9c814d772c04026 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;
@@ -1560,7 +1563,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 819e50d666224a6201cfc7f450e0bd37bfe32810..a5fd721be96d2cb1f22d58d7451165d1b33f5a5a 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 fdc175e93f74743eb4d2e5a4bc688df1c62e64c4..07a02e47b2c3a68633d213675a8cc380a0cf31d8 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.53.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
@ 2026-02-11 14:59 ` Luis Henriques
2026-02-11 16:18 ` Horst Birthelmer
2026-02-11 16:13 ` Miklos Szeredi
1 sibling, 1 reply; 31+ messages in thread
From: Luis Henriques @ 2026-02-11 14:59 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Miklos Szeredi, Bernd Schubert, Joanne Koong, linux-kernel,
linux-fsdevel, Horst Birthelmer
Hi Horst,
On Tue, Feb 10 2026, Horst Birthelmer 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()
First of all, thanks a lot for this. It looks solid to me and in general,
I'm happy with this interface. (Note: I haven't yet tested this v5 rev --
I'll rebase my own code on top of it soon so that I can give it a try).
Please find below a few comments I have, most of them minor.
As for the two other patches in this series, they look mostly alright to
me, though I believe there's some extra clean-up work to be done in the
error paths for the actual open+getattr implementation. Is this
open+getattr code something you'd like to get merged as well, or is it
just an example on how to implement a compound operation? If the latter,
maybe it could be moved somewhere else (Documentation/filesystems/fuse/
maybe?).
> Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> ---
> fs/fuse/Makefile | 2 +-
> fs/fuse/compound.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++
> fs/fuse/fuse_i.h | 11 +++
> include/uapi/linux/fuse.h | 40 +++++++++
> 4 files changed, 276 insertions(+), 1 deletion(-)
>
> diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> index 22ad9538dfc4b80c6d9b52235bdfead6a6567ae4..4c09038ef995d1b9133c2b6871b97b280a4693b0 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 0000000000000000000000000000000000000000..1a85209f4e99a0e5e54955bcd951eaf395176c12
> --- /dev/null
> +++ b/fs/fuse/compound.c
> @@ -0,0 +1,224 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * FUSE: Filesystem in Userspace
> + * Copyright (C) 2025
Should this be 2026? Or 2025-2026, not sure.
> + *
> + * Compound operations for FUSE - batch multiple operations into a single
> + * request to reduce round trips between kernel and userspace.
> + */
> +
> +#include "fuse_i.h"
> +
> +/*
> + * Compound request builder, 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;
> + int op_errors[FUSE_MAX_COMPOUND_OPS];
> + struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
> +};
> +
> +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 NULL;
> +
> + compound->fm = fm;
> + compound->compound_header.flags = flags;
> +
> + return 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_resp_data_per_req(const struct fuse_args *args,
> + char *resp)
> +{
> + const struct fuse_arg *arg;
> + int i;
> +
> + for (i = 0; i < args->out_numargs; i++) {
> + arg = &args->out_args[i];
> + memcpy(arg->value, resp, arg->size);
> + resp += arg->size;
> + }
> +
> + return resp;
fuse_copy_resp_data_per_req() should probably be a void function, as it's
only caller doesn't seem to care about the return.
>
> +}
> +
> +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx)
> +{
> + return compound->op_errors[op_idx];
> +}
Since this isn't being used in this file, this function could probably be
moved into fuse_i.h as a static inline instead.
> +
> +static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound,
> + int op_index, char *response,
> + char *response_end)
> +{
> + struct fuse_out_header *op_hdr = (struct fuse_out_header *)response;
> + struct fuse_args *args = compound->op_args[op_index];
> +
> + if (op_hdr->len < sizeof(struct fuse_out_header))
> + return NULL;
> +
> + if (response + op_hdr->len > response_end)
> + return NULL;
> +
> + if (op_hdr->error)
> + compound->op_errors[op_index] = op_hdr->error;
> + else
> + fuse_copy_resp_data_per_req(args, response +
> + sizeof(struct fuse_out_header));
> + /* in case of error, we still need to advance to the next op */
> + return response + op_hdr->len;
> +}
> +
> +static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
> + char *response, size_t response_size)
> +{
> + char *response_end = response + response_size;
> + int req_count;
> + int i;
> +
> + req_count = min(compound->compound_header.count,
> + compound->result_header.count);
> +
> + for (i = 0; i < req_count; i++) {
> + response = fuse_compound_parse_one_op(compound, i, response,
> + response_end);
So, we allow the possibility of having some requests without out args.
But there seems to be something wrong here, right? What if the first
request is e.g. a FUSE_UNLINK, which doesn't have an out arg? In the
function above we'll be copying the arg into the first request. Or am I
misunderstanding the logic?
> + if (!response)
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Build a single operation request in the buffer
> + *
> + * Returns the new buffer position after writing the operation.
> + */
> +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
> + struct fuse_args *op_args,
> + char *buffer_pos)
> +{
> + 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_pos;
> + memset(hdr, 0, sizeof(*hdr));
Nit: since the buffer has kmalloc'ed using '__GFP_ZERO', this memset()
could probably be dropped.
> + hdr->len = needed_size;
> + hdr->opcode = op_args->opcode;
> + hdr->nodeid = op_args->nodeid;
> + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
> + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
> + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
> + buffer_pos += sizeof(*hdr);
> +
> + for (j = 0; j < op_args->in_numargs; j++) {
> + memcpy(buffer_pos, op_args->in_args[j].value,
> + op_args->in_args[j].size);
> + buffer_pos += op_args->in_args[j].size;
> + }
> +
> + return buffer_pos;
> +}
> +
> +ssize_t fuse_compound_send(struct fuse_compound_req *compound)
> +{
> + struct fuse_conn *fc = compound->fm->fc;
> + struct fuse_args args = {
> + .opcode = FUSE_COMPOUND,
> + .in_numargs = 2,
> + .out_numargs = 2,
> + .out_argvar = true,
> + };
> + unsigned int req_count = compound->compound_header.count;
> + size_t total_expected_out_size = 0;
> + size_t actual_response_size;
> + size_t buffer_size = 0;
> + void *resp_payload_buffer;
> + char *buffer_pos;
> + void *buffer = NULL;
> + ssize_t ret;
> + int i, j;
> +
> + for (i = 0; i < req_count; i++) {
> + struct fuse_args *op_args = compound->op_args[i];
> + size_t needed_size = sizeof(struct fuse_in_header);
> +
> + for (j = 0; j < op_args->in_numargs; j++)
> + needed_size += op_args->in_args[j].size;
> +
> + buffer_size += needed_size;
> +
> + for (j = 0; j < op_args->out_numargs; j++)
> + total_expected_out_size += op_args->out_args[j].size;
> + }
> +
> + buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
Nit: I find kzalloc() easier to read (and write!).
> + if (!buffer)
> + return -ENOMEM;
> +
> + buffer_pos = buffer;
> + for (i = 0; i < req_count; i++)
> + buffer_pos = fuse_compound_build_one_op(fc,
> + compound->op_args[i],
> + buffer_pos);
> +
> + 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_size;
> + args.in_args[1].value = buffer;
> +
> + buffer_size = total_expected_out_size +
> + (req_count * sizeof(struct fuse_out_header));
> +
> + resp_payload_buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
Same as above.
> + if (!resp_payload_buffer) {
> + ret = -ENOMEM;
> + goto out_free_buffer;
> + }
> +
> + args.out_args[0].size = sizeof(compound->result_header);
> + args.out_args[0].value = &compound->result_header;
> + args.out_args[1].size = buffer_size;
> + args.out_args[1].value = resp_payload_buffer;
> +
> + ret = fuse_simple_request(compound->fm, &args);
> + if (ret < 0)
> + goto out;
> +
> + actual_response_size = args.out_args[1].size;
> +
> + ret = fuse_compound_parse_resp(compound, (char *)resp_payload_buffer,
> + actual_response_size);
> +out:
> + kfree(resp_payload_buffer);
> +out_free_buffer:
> + kfree(buffer);
> + return ret;
> +}
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 7f16049387d15e869db4be23a93605098588eda9..9ebcd96b6b309d75c86a9c716cbd88aaa55c57ef 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1273,6 +1273,17 @@ 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);
> +
> /**
> * Assign a unique id to a fuse request
> */
> diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> index c13e1f9a2f12bd39f535188cb5466688eba42263..113583c4efb67268174dbe4f68e9ea1c21b45eb6 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 is handled like a single request,
> + * but contains multiple requests as input.
> + * This can be used to signal to the fuse server that
> + * the requests can be combined atomically.
> + */
> + FUSE_COMPOUND = 54,
> +
> /* CUSE specific operations */
> CUSE_INIT = 4096,
>
> @@ -1245,6 +1252,39 @@ struct fuse_supp_groups {
> uint32_t groups[];
> };
>
> +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
> +
> +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> +#define FUSE_COMPOUND_ATOMIC (1<<1)
Is there a difference between !ATOMIC and SEPARABLE? I.e can we set both
flags for the same request? If there is a difference, I think it should
be explicitly spelled out here.
Cheers,
--
Luís
> +/*
> + * 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.53.0
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
2026-02-11 14:59 ` Luis Henriques
@ 2026-02-11 16:13 ` Miklos Szeredi
2026-02-11 16:35 ` Horst Birthelmer
2026-02-11 20:36 ` Bernd Schubert
1 sibling, 2 replies; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-11 16:13 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Bernd Schubert, Joanne Koong, Luis Henriques, linux-kernel,
linux-fsdevel, Horst Birthelmer
On Tue, 10 Feb 2026 at 09:46, Horst Birthelmer <horst@birthelmer.com> wrote:
> +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
> + struct fuse_args *op_args,
> + char *buffer_pos)
> +{
> + 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_pos;
> + memset(hdr, 0, sizeof(*hdr));
> + hdr->len = needed_size;
> + hdr->opcode = op_args->opcode;
> + hdr->nodeid = op_args->nodeid;
hdr->unique is notably missing.
I don't know. Maybe just fill it with the index?
> + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
> + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
uid/gid are not needed except for creation ops, and those need idmap
to calculate the correct values. I don't think we want to keep legacy
behavior of always setting these.
> + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
This will be the same as the value in the compound header, so it's
redundant. That might not be bad, but I feel that we're better off
setting this to zero and letting the userspace server fetch the pid
value from the compound header if that's needed.
> +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
Don't see a good reason to declare this in the API. More sensible
would be to negotiate a max_request_size during INIT.
> +
> +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> +#define FUSE_COMPOUND_ATOMIC (1<<1)
What is the meaning of these flags?
> +
> +/*
> + * Compound request header
> + *
> + * This header is followed by the fuse requests
> + */
> +struct fuse_compound_in {
> + uint32_t count; /* Number of operations */
This is redundant, as the sum of the sub-request lengths is equal to
the compound request length, hence calculating the number of ops is
trivial.
> + 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;
I don't understand why this is needed. Preallocation by the userspace
server? Why is this different from a simple request?
> + uint64_t reserved;
> +};
> +
> +/*
> + * Compound response header
> + *
> + * This header is followed by complete fuse responses
> + */
> +struct fuse_compound_out {
> + uint32_t count; /* Number of results */
Again, redundant.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-11 14:59 ` Luis Henriques
@ 2026-02-11 16:18 ` Horst Birthelmer
0 siblings, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-11 16:18 UTC (permalink / raw)
To: Luis Henriques
Cc: Horst Birthelmer, Miklos Szeredi, Bernd Schubert, Joanne Koong,
linux-kernel, linux-fsdevel, Horst Birthelmer
Hi Luis,
On Wed, Feb 11, 2026 at 02:59:46PM +0000, Luis Henriques wrote:
> Hi Horst,
>
> On Tue, Feb 10 2026, Horst Birthelmer 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()
>
> First of all, thanks a lot for this. It looks solid to me and in general,
> I'm happy with this interface. (Note: I haven't yet tested this v5 rev --
> I'll rebase my own code on top of it soon so that I can give it a try).
>
> Please find below a few comments I have, most of them minor.
>
> As for the two other patches in this series, they look mostly alright to
> me, though I believe there's some extra clean-up work to be done in the
> error paths for the actual open+getattr implementation. Is this
> open+getattr code something you'd like to get merged as well, or is it
> just an example on how to implement a compound operation? If the latter,
> maybe it could be moved somewhere else (Documentation/filesystems/fuse/
> maybe?).
I actually thought that it would get merged in as well since it is a solution
to the problem described here:
https://lore.kernel.org/all/20240813212149.1909627-1-joannelkoong@gmail.com/
>
> > Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> > ---
> > fs/fuse/Makefile | 2 +-
> > fs/fuse/compound.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++
> > fs/fuse/fuse_i.h | 11 +++
> > include/uapi/linux/fuse.h | 40 +++++++++
> > 4 files changed, 276 insertions(+), 1 deletion(-)
> >
> > diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
> > index 22ad9538dfc4b80c6d9b52235bdfead6a6567ae4..4c09038ef995d1b9133c2b6871b97b280a4693b0 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 0000000000000000000000000000000000000000..1a85209f4e99a0e5e54955bcd951eaf395176c12
> > --- /dev/null
> > +++ b/fs/fuse/compound.c
> > @@ -0,0 +1,224 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * FUSE: Filesystem in Userspace
> > + * Copyright (C) 2025
>
> Should this be 2026? Or 2025-2026, not sure.
neither am I but I added the 2026 in the next version.
>
> > + *
> > + * Compound operations for FUSE - batch multiple operations into a single
> > + * request to reduce round trips between kernel and userspace.
> > + */
> > +
> > +#include "fuse_i.h"
> > +
> > +/*
> > + * Compound request builder, 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;
> > + int op_errors[FUSE_MAX_COMPOUND_OPS];
> > + struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS];
> > +};
> > +
> > +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 NULL;
> > +
> > + compound->fm = fm;
> > + compound->compound_header.flags = flags;
> > +
> > + return 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_resp_data_per_req(const struct fuse_args *args,
> > + char *resp)
> > +{
> > + const struct fuse_arg *arg;
> > + int i;
> > +
> > + for (i = 0; i < args->out_numargs; i++) {
> > + arg = &args->out_args[i];
> > + memcpy(arg->value, resp, arg->size);
> > + resp += arg->size;
> > + }
> > +
> > + return resp;
>
> fuse_copy_resp_data_per_req() should probably be a void function, as it's
> only caller doesn't seem to care about the return.
will be fixed in the next version
>
> >
> > +}
> > +
> > +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx)
> > +{
> > + return compound->op_errors[op_idx];
> > +}
>
> Since this isn't being used in this file, this function could probably be
> moved into fuse_i.h as a static inline instead.
>
good idea! Will fix in the next version.
> > +
> > +static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound,
> > + int op_index, char *response,
> > + char *response_end)
> > +{
> > + struct fuse_out_header *op_hdr = (struct fuse_out_header *)response;
> > + struct fuse_args *args = compound->op_args[op_index];
> > +
> > + if (op_hdr->len < sizeof(struct fuse_out_header))
> > + return NULL;
> > +
> > + if (response + op_hdr->len > response_end)
> > + return NULL;
> > +
> > + if (op_hdr->error)
> > + compound->op_errors[op_index] = op_hdr->error;
> > + else
> > + fuse_copy_resp_data_per_req(args, response +
> > + sizeof(struct fuse_out_header));
> > + /* in case of error, we still need to advance to the next op */
> > + return response + op_hdr->len;
> > +}
> > +
> > +static int fuse_compound_parse_resp(struct fuse_compound_req *compound,
> > + char *response, size_t response_size)
> > +{
> > + char *response_end = response + response_size;
> > + int req_count;
> > + int i;
> > +
> > + req_count = min(compound->compound_header.count,
> > + compound->result_header.count);
> > +
> > + for (i = 0; i < req_count; i++) {
> > + response = fuse_compound_parse_one_op(compound, i, response,
> > + response_end);
>
> So, we allow the possibility of having some requests without out args.
> But there seems to be something wrong here, right? What if the first
> request is e.g. a FUSE_UNLINK, which doesn't have an out arg? In the
> function above we'll be copying the arg into the first request. Or am I
> misunderstanding the logic?
Good catch ... This I have to think about and come up with a better solution.
>
> > + if (!response)
> > + return -EIO;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +/*
> > + * Build a single operation request in the buffer
> > + *
> > + * Returns the new buffer position after writing the operation.
> > + */
> > +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
> > + struct fuse_args *op_args,
> > + char *buffer_pos)
> > +{
> > + 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_pos;
> > + memset(hdr, 0, sizeof(*hdr));
>
> Nit: since the buffer has kmalloc'ed using '__GFP_ZERO', this memset()
> could probably be dropped.
Will change that, if it makes it easier to read.
>
> > + hdr->len = needed_size;
> > + hdr->opcode = op_args->opcode;
> > + hdr->nodeid = op_args->nodeid;
> > + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
> > + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
> > + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
> > + buffer_pos += sizeof(*hdr);
> > +
> > + for (j = 0; j < op_args->in_numargs; j++) {
> > + memcpy(buffer_pos, op_args->in_args[j].value,
> > + op_args->in_args[j].size);
> > + buffer_pos += op_args->in_args[j].size;
> > + }
> > +
> > + return buffer_pos;
> > +}
> > +
> > +ssize_t fuse_compound_send(struct fuse_compound_req *compound)
> > +{
> > + struct fuse_conn *fc = compound->fm->fc;
> > + struct fuse_args args = {
> > + .opcode = FUSE_COMPOUND,
> > + .in_numargs = 2,
> > + .out_numargs = 2,
> > + .out_argvar = true,
> > + };
> > + unsigned int req_count = compound->compound_header.count;
> > + size_t total_expected_out_size = 0;
> > + size_t actual_response_size;
> > + size_t buffer_size = 0;
> > + void *resp_payload_buffer;
> > + char *buffer_pos;
> > + void *buffer = NULL;
> > + ssize_t ret;
> > + int i, j;
> > +
> > + for (i = 0; i < req_count; i++) {
> > + struct fuse_args *op_args = compound->op_args[i];
> > + size_t needed_size = sizeof(struct fuse_in_header);
> > +
> > + for (j = 0; j < op_args->in_numargs; j++)
> > + needed_size += op_args->in_args[j].size;
> > +
> > + buffer_size += needed_size;
> > +
> > + for (j = 0; j < op_args->out_numargs; j++)
> > + total_expected_out_size += op_args->out_args[j].size;
> > + }
> > +
> > + buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
>
> Nit: I find kzalloc() easier to read (and write!).
>
> > + if (!buffer)
> > + return -ENOMEM;
> > +
> > + buffer_pos = buffer;
> > + for (i = 0; i < req_count; i++)
> > + buffer_pos = fuse_compound_build_one_op(fc,
> > + compound->op_args[i],
> > + buffer_pos);
> > +
> > + 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_size;
> > + args.in_args[1].value = buffer;
> > +
> > + buffer_size = total_expected_out_size +
> > + (req_count * sizeof(struct fuse_out_header));
> > +
> > + resp_payload_buffer = kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO);
>
> Same as above.
>
> > + if (!resp_payload_buffer) {
> > + ret = -ENOMEM;
> > + goto out_free_buffer;
> > + }
> > +
> > + args.out_args[0].size = sizeof(compound->result_header);
> > + args.out_args[0].value = &compound->result_header;
> > + args.out_args[1].size = buffer_size;
> > + args.out_args[1].value = resp_payload_buffer;
> > +
> > + ret = fuse_simple_request(compound->fm, &args);
> > + if (ret < 0)
> > + goto out;
> > +
> > + actual_response_size = args.out_args[1].size;
> > +
> > + ret = fuse_compound_parse_resp(compound, (char *)resp_payload_buffer,
> > + actual_response_size);
> > +out:
> > + kfree(resp_payload_buffer);
> > +out_free_buffer:
> > + kfree(buffer);
> > + return ret;
> > +}
> > diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> > index 7f16049387d15e869db4be23a93605098588eda9..9ebcd96b6b309d75c86a9c716cbd88aaa55c57ef 100644
> > --- a/fs/fuse/fuse_i.h
> > +++ b/fs/fuse/fuse_i.h
> > @@ -1273,6 +1273,17 @@ 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);
> > +
> > /**
> > * Assign a unique id to a fuse request
> > */
> > diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
> > index c13e1f9a2f12bd39f535188cb5466688eba42263..113583c4efb67268174dbe4f68e9ea1c21b45eb6 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 is handled like a single request,
> > + * but contains multiple requests as input.
> > + * This can be used to signal to the fuse server that
> > + * the requests can be combined atomically.
> > + */
> > + FUSE_COMPOUND = 54,
> > +
> > /* CUSE specific operations */
> > CUSE_INIT = 4096,
> >
> > @@ -1245,6 +1252,39 @@ struct fuse_supp_groups {
> > uint32_t groups[];
> > };
> >
> > +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
> > +
> > +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> > +#define FUSE_COMPOUND_ATOMIC (1<<1)
>
> Is there a difference between !ATOMIC and SEPARABLE? I.e can we set both
> flags for the same request? If there is a difference, I think it should
> be explicitly spelled out here.
I think this will change in the near future.
>
> Cheers,
> --
> Luís
>
> > +/*
> > + * 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.53.0
> >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-11 16:13 ` Miklos Szeredi
@ 2026-02-11 16:35 ` Horst Birthelmer
2026-02-12 9:38 ` Miklos Szeredi
2026-02-11 20:36 ` Bernd Schubert
1 sibling, 1 reply; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-11 16:35 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
Hi Miklos,
thanks for taking the time to look at this!
On Wed, Feb 11, 2026 at 05:13:21PM +0100, Miklos Szeredi wrote:
> On Tue, 10 Feb 2026 at 09:46, Horst Birthelmer <horst@birthelmer.com> wrote:
>
> > +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
> > + struct fuse_args *op_args,
> > + char *buffer_pos)
> > +{
> > + 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_pos;
> > + memset(hdr, 0, sizeof(*hdr));
> > + hdr->len = needed_size;
> > + hdr->opcode = op_args->opcode;
> > + hdr->nodeid = op_args->nodeid;
>
> hdr->unique is notably missing.
>
> I don't know. Maybe just fill it with the index?
OK, will do. Since it was never used in libfuse, I didn't notice.
>
> > + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
> > + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
>
> uid/gid are not needed except for creation ops, and those need idmap
> to calculate the correct values. I don't think we want to keep legacy
> behavior of always setting these.
>
> > + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
>
> This will be the same as the value in the compound header, so it's
> redundant. That might not be bad, but I feel that we're better off
> setting this to zero and letting the userspace server fetch the pid
> value from the compound header if that's needed.
>
> > +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
>
> Don't see a good reason to declare this in the API. More sensible
> would be to negotiate a max_request_size during INIT.
>
Wouldn't that make for a very complicated implementation of larger compounds.
If a fuse server negotiates something like 2?
> > +
> > +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> > +#define FUSE_COMPOUND_ATOMIC (1<<1)
>
> What is the meaning of these flags?
FUSE_COMPOUND_SEPARABLE is a hint for the fuse server that the requests are all
complete and there is no need to use the result of one request to complete the input
of another request further down the line.
Think of LOOKUP+MKNOD+OPEN ... that would never be 'separable'.
At the moment I use this flag to signal libfuse that it can decode the compund
and execute sequencially completely in the lib and just call the separate requests
of the fuse server.
FUSE_COMPOUND_ATOMIC was an idea to hint to the fuse server that the kernel treats
the compound as one atomic request. This can maybe save us some checks for some
compounds.
>
> > +
> > +/*
> > + * Compound request header
> > + *
> > + * This header is followed by the fuse requests
> > + */
> > +struct fuse_compound_in {
> > + uint32_t count; /* Number of operations */
>
> This is redundant, as the sum of the sub-request lengths is equal to
> the compound request length, hence calculating the number of ops is
> trivial.
>
> > + 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;
>
> I don't understand why this is needed. Preallocation by the userspace
> server? Why is this different from a simple request?
>
The reason is the implementation in libfuse.
It makes it a lot easier to know what the sum of the result buffers in the kernel is
and preallocate that in libfuse for compounds that will be decoded and handled in the
library.
> > + uint64_t reserved;
> > +};
> > +
> > +/*
> > + * Compound response header
> > + *
> > + * This header is followed by complete fuse responses
> > + */
> > +struct fuse_compound_out {
> > + uint32_t count; /* Number of results */
>
> Again, redundant.
>
> Thanks,
> Miklos
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-11 16:13 ` Miklos Szeredi
2026-02-11 16:35 ` Horst Birthelmer
@ 2026-02-11 20:36 ` Bernd Schubert
2026-02-12 9:07 ` Miklos Szeredi
1 sibling, 1 reply; 31+ messages in thread
From: Bernd Schubert @ 2026-02-11 20:36 UTC (permalink / raw)
To: Miklos Szeredi, Horst Birthelmer
Cc: Bernd Schubert, Joanne Koong, Luis Henriques, linux-kernel,
linux-fsdevel, Horst Birthelmer
On 2/11/26 17:13, Miklos Szeredi wrote:
> On Tue, 10 Feb 2026 at 09:46, Horst Birthelmer <horst@birthelmer.com> wrote:
>
>> +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
>> + struct fuse_args *op_args,
>> + char *buffer_pos)
>> +{
>> + 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_pos;
>> + memset(hdr, 0, sizeof(*hdr));
>> + hdr->len = needed_size;
>> + hdr->opcode = op_args->opcode;
>> + hdr->nodeid = op_args->nodeid;
>
> hdr->unique is notably missing.
>
> I don't know. Maybe just fill it with the index?
>
>> + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
>> + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
>
> uid/gid are not needed except for creation ops, and those need idmap
> to calculate the correct values. I don't think we want to keep legacy
> behavior of always setting these.
>
>> + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
>
> This will be the same as the value in the compound header, so it's
> redundant. That might not be bad, but I feel that we're better off
> setting this to zero and letting the userspace server fetch the pid
> value from the compound header if that's needed.
>
>> +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
>
> Don't see a good reason to declare this in the API. More sensible
> would be to negotiate a max_request_size during INIT.
>
>> +
>> +#define FUSE_COMPOUND_SEPARABLE (1<<0)
>> +#define FUSE_COMPOUND_ATOMIC (1<<1)
>
> What is the meaning of these flags?
>
>> +
>> +/*
>> + * Compound request header
>> + *
>> + * This header is followed by the fuse requests
>> + */
>> +struct fuse_compound_in {
>> + uint32_t count; /* Number of operations */
>
> This is redundant, as the sum of the sub-request lengths is equal to
> the compound request length, hence calculating the number of ops is
> trivial.
>
>> + 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;
>
> I don't understand why this is needed. Preallocation by the userspace
> server? Why is this different from a simple request?
With simple request and a single request per buffer, one can re-use the
existing buffer for the reply in fuse-server
- write: Do the write operation, then store the result into the io-buffer
- read: Copy the relatively small header, store the result into the
io-buffer
- Meta-operations: Same as read
Compound:
- iterate over each compound, store the result into a temporary buffer
- After completion, copy the temporary buffer back into the compound
Which then results in the choice of
- Make the temporary buffer as large as the IO buffer
- Reallocate for each compound, or something like 2x 1st buffer
- Allocate the exact size of what kernel nows the reply is supposed to
be on success
In this specific case I had especially asked to store the result size
into the compound to simplify code in libfuse.
Question, this is a just a uint32_t, does it hurt to leave it? Maybe
with the additional comment like "Optimization, not strictly needed, but
might simplify fuse-server".
Thanks,
Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-11 20:36 ` Bernd Schubert
@ 2026-02-12 9:07 ` Miklos Szeredi
2026-02-12 9:48 ` Bernd Schubert
0 siblings, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 9:07 UTC (permalink / raw)
To: Bernd Schubert
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
> With simple request and a single request per buffer, one can re-use the
> existing buffer for the reply in fuse-server
>
> - write: Do the write operation, then store the result into the io-buffer
> - read: Copy the relatively small header, store the result into the
> io-buffer
>
> - Meta-operations: Same as read
Reminds me of the header/payload separation in io-uring.
We could actually do that on the /dev/fuse interface as well, just
never got around to implementing it: first page reserved for
header(s), payload is stored at PAGE_SIZE offset in the supplied
buffer.
That doesn't solve the overwriting problem, since in theory we could
have a compound with a READ and a WRITE but in practice we can just
disallow such combinations.
In fact I'd argue that most/all practical compounds will not even have
a payload and can fit into a page sized buffer.
So as a first iteration can we just limit compounds to small in/out sizes?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-11 16:35 ` Horst Birthelmer
@ 2026-02-12 9:38 ` Miklos Szeredi
2026-02-12 9:53 ` Horst Birthelmer
0 siblings, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 9:38 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Wed, 11 Feb 2026 at 17:35, Horst Birthelmer <horst@birthelmer.de> wrote:
>
> Hi Miklos,
>
> thanks for taking the time to look at this!
>
> On Wed, Feb 11, 2026 at 05:13:21PM +0100, Miklos Szeredi wrote:
> > On Tue, 10 Feb 2026 at 09:46, Horst Birthelmer <horst@birthelmer.com> wrote:
> >
> > > +static char *fuse_compound_build_one_op(struct fuse_conn *fc,
> > > + struct fuse_args *op_args,
> > > + char *buffer_pos)
> > > +{
> > > + 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_pos;
> > > + memset(hdr, 0, sizeof(*hdr));
> > > + hdr->len = needed_size;
> > > + hdr->opcode = op_args->opcode;
> > > + hdr->nodeid = op_args->nodeid;
> >
> > hdr->unique is notably missing.
> >
> > I don't know. Maybe just fill it with the index?
>
> OK, will do. Since it was never used in libfuse, I didn't notice.
>
> >
> > > + hdr->uid = from_kuid(fc->user_ns, current_fsuid());
> > > + hdr->gid = from_kgid(fc->user_ns, current_fsgid());
> >
> > uid/gid are not needed except for creation ops, and those need idmap
> > to calculate the correct values. I don't think we want to keep legacy
> > behavior of always setting these.
> >
> > > + hdr->pid = pid_nr_ns(task_pid(current), fc->pid_ns);
> >
> > This will be the same as the value in the compound header, so it's
> > redundant. That might not be bad, but I feel that we're better off
> > setting this to zero and letting the userspace server fetch the pid
> > value from the compound header if that's needed.
> >
> > > +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
> >
> > Don't see a good reason to declare this in the API. More sensible
> > would be to negotiate a max_request_size during INIT.
> >
>
> Wouldn't that make for a very complicated implementation of larger compounds.
> If a fuse server negotiates something like 2?
I didn't mean negotiating the number of ops, rather the size of the
buffer for the compound.
But let's not overthink this. If compound doesn't fit in 4k, then it
probably not worth doing anyway.
> > > +
> > > +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> > > +#define FUSE_COMPOUND_ATOMIC (1<<1)
> >
> > What is the meaning of these flags?
>
> FUSE_COMPOUND_SEPARABLE is a hint for the fuse server that the requests are all
> complete and there is no need to use the result of one request to complete the input
> of another request further down the line.
Aha, so it means parallel execution is allowed.
> Think of LOOKUP+MKNOD+OPEN ... that would never be 'separable'.
Right. I think for the moment we don't need to think about parallel
execution within a compound.
> At the moment I use this flag to signal libfuse that it can decode the compund
> and execute sequencially completely in the lib and just call the separate requests
> of the fuse server.
I think decoding and executing the ops sequentially should always be
possible, and it would be one of the major advantages of the compound
architecture: kernel packs a number of requests that it would do
sequentially, sends to server, server decodes and calls individual
callbacks in filesystem, then replies with the compound result. This
reduces the number of syscalls/context switches which can be a win
even with an unchanged library API.
The trick in a case like MKNOD + OPEN is to let the server know how to
feed the output of one request to the input of the next.
> FUSE_COMPOUND_ATOMIC was an idea to hint to the fuse server that the kernel treats
> the compound as one atomic request. This can maybe save us some checks for some
> compounds.
Do you have an example?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 9:07 ` Miklos Szeredi
@ 2026-02-12 9:48 ` Bernd Schubert
2026-02-12 10:16 ` Miklos Szeredi
0 siblings, 1 reply; 31+ messages in thread
From: Bernd Schubert @ 2026-02-12 9:48 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On 2/12/26 10:07, Miklos Szeredi wrote:
> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
>
>> With simple request and a single request per buffer, one can re-use the
>> existing buffer for the reply in fuse-server
>>
>> - write: Do the write operation, then store the result into the io-buffer
>> - read: Copy the relatively small header, store the result into the
>> io-buffer
>>
>> - Meta-operations: Same as read
>
> Reminds me of the header/payload separation in io-uring.
>
> We could actually do that on the /dev/fuse interface as well, just
> never got around to implementing it: first page reserved for
> header(s), payload is stored at PAGE_SIZE offset in the supplied
> buffer.
Yeah, same here, I never came around to that during the past year.
>
> That doesn't solve the overwriting problem, since in theory we could
> have a compound with a READ and a WRITE but in practice we can just
> disallow such combinations.
>
> In fact I'd argue that most/all practical compounds will not even have
> a payload and can fit into a page sized buffer.
That is what Horst had said as well, until I came up with a use case -
write and immediately fetch updated attributes.
A bit side tracking background, I disabled auto-invalidate in the DDN
file system, because we use DLM anyway (additional patches for
background writes and mmap in our branches, should be published to the
list asap). And then that disabled auto-invalidation introduced another
xfstest failure, I think generic/323, but I would need to look it up. I
didn't have time for the details yet, but I think page read beyond EOF.
And that made me think that all operations that change file size and
time stamps could immediately return the updates attributes, if the file
system supports it - with our DLM we would support that.
>
> So as a first iteration can we just limit compounds to small in/out sizes?
Even without write payload, there is still FUSE_NAME_MAX, that can be up
to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
the buffer seems a bit overhead? Especially as libfuse needs to iterate
over each compound first and figure out the exact size.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 9:38 ` Miklos Szeredi
@ 2026-02-12 9:53 ` Horst Birthelmer
2026-02-12 10:23 ` Miklos Szeredi
0 siblings, 1 reply; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-12 9:53 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 10:38:25AM +0100, Miklos Szeredi wrote:
> On Wed, 11 Feb 2026 at 17:35, Horst Birthelmer <horst@birthelmer.de> wrote:
> >
> > >
> > > > +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
> > >
> > > Don't see a good reason to declare this in the API. More sensible
> > > would be to negotiate a max_request_size during INIT.
> > >
> >
> > Wouldn't that make for a very complicated implementation of larger compounds.
> > If a fuse server negotiates something like 2?
>
> I didn't mean negotiating the number of ops, rather the size of the
> buffer for the compound.
>
OK, so the current limit would be the size of the whole operation.
> But let's not overthink this. If compound doesn't fit in 4k, then it
> probably not worth doing anyway.
Got it!
Unless some people have the idea of doing some atomic operation with
WRITE+<some attribute operation> ;-)
>
> > > > +
> > > > +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> > > > +#define FUSE_COMPOUND_ATOMIC (1<<1)
> > >
> > > What is the meaning of these flags?
> >
> > FUSE_COMPOUND_SEPARABLE is a hint for the fuse server that the requests are all
> > complete and there is no need to use the result of one request to complete the input
> > of another request further down the line.
>
> Aha, so it means parallel execution is allowed.
Yes.
>
> > Think of LOOKUP+MKNOD+OPEN ... that would never be 'separable'.
>
> Right. I think for the moment we don't need to think about parallel
> execution within a compound.
Not only for parallel execution. You cannot craft the args for this to be
complete, independent of parallel execution.
You will need the result of LOOKUP to know what to do with MKNOD and to fill the args for OPEN.
>
> > At the moment I use this flag to signal libfuse that it can decode the compund
> > and execute sequencially completely in the lib and just call the separate requests
> > of the fuse server.
>
> I think decoding and executing the ops sequentially should always be
> possible, and it would be one of the major advantages of the compound
> architecture: kernel packs a number of requests that it would do
> sequentially, sends to server, server decodes and calls individual
> callbacks in filesystem, then replies with the compound result. This
> reduces the number of syscalls/context switches which can be a win
> even with an unchanged library API.
Yes, but some combinations are not complete when you have interdependencies
within the packed requests.
>
> The trick in a case like MKNOD + OPEN is to let the server know how to
> feed the output of one request to the input of the next.
>
Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
that we know that this is not done in this case, so the requests can be processed
'separately'.
If that is missing the fuse server has to look at the combination and decide wether it
will execute it as a 'compound' or return an error.
> > FUSE_COMPOUND_ATOMIC was an idea to hint to the fuse server that the kernel treats
> > the compound as one atomic request. This can maybe save us some checks for some
> > compounds.
>
> Do you have an example?
Yes, I have used it for my (not yet ready for scrutiny) implementation of atomic open.
If you know that LOOKUP got a valid result and the operation was atomic on fuse server side,
you don't have to check for MKNODs result.
>
> Thanks,
> Miklos
Thanks,
Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 9:48 ` Bernd Schubert
@ 2026-02-12 10:16 ` Miklos Szeredi
2026-02-12 10:43 ` Bernd Schubert
0 siblings, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 10:16 UTC (permalink / raw)
To: Bernd Schubert
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
>
>
>
> On 2/12/26 10:07, Miklos Szeredi wrote:
> > On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
> >
> >> With simple request and a single request per buffer, one can re-use the
> >> existing buffer for the reply in fuse-server
> >>
> >> - write: Do the write operation, then store the result into the io-buffer
> >> - read: Copy the relatively small header, store the result into the
> >> io-buffer
> >>
> >> - Meta-operations: Same as read
> >
> > Reminds me of the header/payload separation in io-uring.
> >
> > We could actually do that on the /dev/fuse interface as well, just
> > never got around to implementing it: first page reserved for
> > header(s), payload is stored at PAGE_SIZE offset in the supplied
> > buffer.
>
> Yeah, same here, I never came around to that during the past year.
>
> >
> > That doesn't solve the overwriting problem, since in theory we could
> > have a compound with a READ and a WRITE but in practice we can just
> > disallow such combinations.
> >
> > In fact I'd argue that most/all practical compounds will not even have
> > a payload and can fit into a page sized buffer.
>
> That is what Horst had said as well, until I came up with a use case -
> write and immediately fetch updated attributes.
Attributes definitely should fit in the reply header buffer.
> > So as a first iteration can we just limit compounds to small in/out sizes?
>
> Even without write payload, there is still FUSE_NAME_MAX, that can be up
> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
> the buffer seems a bit overhead? Especially as libfuse needs to iterate
> over each compound first and figure out the exact size.
Ah, huge filenames are a thing. Probably not worth doing
LOOKUP+CREATE as a compound since it duplicates the filename. We
already have LOOKUP_CREATE, which does both. Am I missing something?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 9:53 ` Horst Birthelmer
@ 2026-02-12 10:23 ` Miklos Szeredi
2026-02-12 10:48 ` Horst Birthelmer
0 siblings, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 10:23 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, 12 Feb 2026 at 10:53, Horst Birthelmer <horst@birthelmer.de> wrote:
>
> On Thu, Feb 12, 2026 at 10:38:25AM +0100, Miklos Szeredi wrote:
> > On Wed, 11 Feb 2026 at 17:35, Horst Birthelmer <horst@birthelmer.de> wrote:
> > >
> > > >
> > > > > +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */
> > > >
> > > > Don't see a good reason to declare this in the API. More sensible
> > > > would be to negotiate a max_request_size during INIT.
> > > >
> > >
> > > Wouldn't that make for a very complicated implementation of larger compounds.
> > > If a fuse server negotiates something like 2?
> >
> > I didn't mean negotiating the number of ops, rather the size of the
> > buffer for the compound.
> >
> OK, so the current limit would be the size of the whole operation.
>
> > But let's not overthink this. If compound doesn't fit in 4k, then it
> > probably not worth doing anyway.
>
> Got it!
> Unless some people have the idea of doing some atomic operation with
> WRITE+<some attribute operation> ;-)
>
> >
> > > > > +
> > > > > +#define FUSE_COMPOUND_SEPARABLE (1<<0)
> > > > > +#define FUSE_COMPOUND_ATOMIC (1<<1)
> > > >
> > > > What is the meaning of these flags?
> > >
> > > FUSE_COMPOUND_SEPARABLE is a hint for the fuse server that the requests are all
> > > complete and there is no need to use the result of one request to complete the input
> > > of another request further down the line.
> >
> > Aha, so it means parallel execution is allowed.
>
> Yes.
>
> >
> > > Think of LOOKUP+MKNOD+OPEN ... that would never be 'separable'.
> >
> > Right. I think for the moment we don't need to think about parallel
> > execution within a compound.
>
> Not only for parallel execution. You cannot craft the args for this to be
> complete, independent of parallel execution.
>
> You will need the result of LOOKUP to know what to do with MKNOD and to fill the args for OPEN.
>
> >
> > > At the moment I use this flag to signal libfuse that it can decode the compund
> > > and execute sequencially completely in the lib and just call the separate requests
> > > of the fuse server.
> >
> > I think decoding and executing the ops sequentially should always be
> > possible, and it would be one of the major advantages of the compound
> > architecture: kernel packs a number of requests that it would do
> > sequentially, sends to server, server decodes and calls individual
> > callbacks in filesystem, then replies with the compound result. This
> > reduces the number of syscalls/context switches which can be a win
> > even with an unchanged library API.
>
> Yes, but some combinations are not complete when you have interdependencies
> within the packed requests.
>
> >
> > The trick in a case like MKNOD + OPEN is to let the server know how to
> > feed the output of one request to the input of the next.
> >
>
> Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
> that we know that this is not done in this case, so the requests can be processed
> 'separately'.
> If that is missing the fuse server has to look at the combination and decide wether it
> will execute it as a 'compound' or return an error.
I'd rather add some sub-op header flag that how to fill the missing
input. E.g. use the nodeid from the previous op's result.
If there's no flag, then the op is "separable".
> > > FUSE_COMPOUND_ATOMIC was an idea to hint to the fuse server that the kernel treats
> > > the compound as one atomic request. This can maybe save us some checks for some
> > > compounds.
> >
> > Do you have an example?
>
> Yes, I have used it for my (not yet ready for scrutiny) implementation of atomic open.
> If you know that LOOKUP got a valid result and the operation was atomic on fuse server side,
> you don't have to check for MKNODs result.
As noted in my reply to Bernd the LOOKUP+MKNOD may not be the best way
to use compounds. May be better off sticking with an actual atomic op
for this.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 10:16 ` Miklos Szeredi
@ 2026-02-12 10:43 ` Bernd Schubert
2026-02-12 11:44 ` Horst Birthelmer
2026-02-12 11:55 ` Miklos Szeredi
0 siblings, 2 replies; 31+ messages in thread
From: Bernd Schubert @ 2026-02-12 10:43 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On 2/12/26 11:16, Miklos Szeredi wrote:
> On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
>>
>>
>>
>> On 2/12/26 10:07, Miklos Szeredi wrote:
>>> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
>>>
>>>> With simple request and a single request per buffer, one can re-use the
>>>> existing buffer for the reply in fuse-server
>>>>
>>>> - write: Do the write operation, then store the result into the io-buffer
>>>> - read: Copy the relatively small header, store the result into the
>>>> io-buffer
>>>>
>>>> - Meta-operations: Same as read
>>>
>>> Reminds me of the header/payload separation in io-uring.
>>>
>>> We could actually do that on the /dev/fuse interface as well, just
>>> never got around to implementing it: first page reserved for
>>> header(s), payload is stored at PAGE_SIZE offset in the supplied
>>> buffer.
>>
>> Yeah, same here, I never came around to that during the past year.
>>
>>>
>>> That doesn't solve the overwriting problem, since in theory we could
>>> have a compound with a READ and a WRITE but in practice we can just
>>> disallow such combinations.
>>>
>>> In fact I'd argue that most/all practical compounds will not even have
>>> a payload and can fit into a page sized buffer.
>>
>> That is what Horst had said as well, until I came up with a use case -
>> write and immediately fetch updated attributes.
>
> Attributes definitely should fit in the reply header buffer.
And now let's include something more complex, let's say a write
including a partial page write of an unknown page. With compounds
fetching the entire page or just the missing part + file attributes
becomes possible.
>
>>> So as a first iteration can we just limit compounds to small in/out sizes?
>>
>> Even without write payload, there is still FUSE_NAME_MAX, that can be up
>> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
>> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
>> the buffer seems a bit overhead? Especially as libfuse needs to iterate
>> over each compound first and figure out the exact size.
>
> Ah, huge filenames are a thing. Probably not worth doing
> LOOKUP+CREATE as a compound since it duplicates the filename. We
> already have LOOKUP_CREATE, which does both. Am I missing something?
I think you mean FUSE_CREATE? Which is create+getattr, but always
preceded by FUSE_LOOKUP is always sent first? Horst is currently working
on full atomic open based on compounds, i.e. a totally new patch set to
the earlier versions. With that LOOKUP
Yes, we could use the same file name for the entire compound, but then
individual requests of the compound rely on an uber info. This info
needs to be created, it needs to be handled on the other side as part of
the individual parts. Please correct me if I'm wrong, but this sounds
much more difficult than just adding an info how much space is needed to
hold the result?
Thanks,
Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 10:23 ` Miklos Szeredi
@ 2026-02-12 10:48 ` Horst Birthelmer
2026-02-12 12:10 ` Miklos Szeredi
0 siblings, 1 reply; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-12 10:48 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 11:23:56AM +0100, Miklos Szeredi wrote:
> On Thu, 12 Feb 2026 at 10:53, Horst Birthelmer <horst@birthelmer.de> wrote:
> >
> >
> > Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
> > that we know that this is not done in this case, so the requests can be processed
> > 'separately'.
> > If that is missing the fuse server has to look at the combination and decide wether it
> > will execute it as a 'compound' or return an error.
>
> I'd rather add some sub-op header flag that how to fill the missing
> input. E.g. use the nodeid from the previous op's result.
>
> If there's no flag, then the op is "separable".
>
This makes the handling on the fuse server side unnecessarily harder.
With the current way I can check the flag in the compound header and let libfuse handle the
compound by calling the request handlers separately, and not worry about a thing.
If the flag is not there, the fuse server itself
(passthrough_hp from the PR already demonstrates this) has to handle the whole compound
as a whole. I'm confident that this way we can handle pretty much every semantically
overloaded combination.
The other way would make the handling in libfuse or in the lowest level of the fuse server
(for fuse servers that don't use libfuse) almost impossible without parsing all the requests
and all the flags to know that we would have been able to get away with very little work.
I had thought of a hierarchical parsing of the compound.
The fuse server can decide
1. does it handle compounds at all
2. does it support this particular compound (based on the opcodes and the compound flags
and the particular capabilities of the fuse server)
3. if the particular compound can not be handled can libfuse handle it for us?
This way we can have real atomic operations in fuse server, where it supports it.
> > > > FUSE_COMPOUND_ATOMIC was an idea to hint to the fuse server that the kernel treats
> > > > the compound as one atomic request. This can maybe save us some checks for some
> > > > compounds.
> > >
> > > Do you have an example?
> >
> > Yes, I have used it for my (not yet ready for scrutiny) implementation of atomic open.
> > If you know that LOOKUP got a valid result and the operation was atomic on fuse server side,
> > you don't have to check for MKNODs result.
>
> As noted in my reply to Bernd the LOOKUP+MKNOD may not be the best way
> to use compounds. May be better off sticking with an actual atomic op
> for this.
>
I don't understand yet, why.
I think we could actually implement a real atomic open if we craft a compound for it and
the fuse server supports it. If not, we can go back to the way it is handled now.
What am I missing here?
> Thanks,
> Miklos
Thanks,
Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 10:43 ` Bernd Schubert
@ 2026-02-12 11:44 ` Horst Birthelmer
2026-02-14 1:35 ` Joanne Koong
2026-02-12 11:55 ` Miklos Szeredi
1 sibling, 1 reply; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-12 11:44 UTC (permalink / raw)
To: Bernd Schubert
Cc: Miklos Szeredi, Horst Birthelmer, Bernd Schubert, Joanne Koong,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 11:43:12AM +0100, Bernd Schubert wrote:
>
>
> On 2/12/26 11:16, Miklos Szeredi wrote:
> > On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
> >> On 2/12/26 10:07, Miklos Szeredi wrote:
> >>> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
> >>>
> >
> >>> So as a first iteration can we just limit compounds to small in/out sizes?
> >>
> >> Even without write payload, there is still FUSE_NAME_MAX, that can be up
> >> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
> >> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
> >> the buffer seems a bit overhead? Especially as libfuse needs to iterate
> >> over each compound first and figure out the exact size.
> >
> > Ah, huge filenames are a thing. Probably not worth doing
> > LOOKUP+CREATE as a compound since it duplicates the filename. We
> > already have LOOKUP_CREATE, which does both. Am I missing something?
>
> I think you mean FUSE_CREATE? Which is create+getattr, but always
> preceded by FUSE_LOOKUP is always sent first? Horst is currently working
> on full atomic open based on compounds, i.e. a totally new patch set to
> the earlier versions. With that LOOKUP
>
> Yes, we could use the same file name for the entire compound, but then
> individual requests of the compound rely on an uber info. This info
> needs to be created, it needs to be handled on the other side as part of
> the individual parts. Please correct me if I'm wrong, but this sounds
> much more difficult than just adding an info how much space is needed to
> hold the result?
I have a feeling we have different use cases in mind and misunderstand each other.
As I see it:
From the discussion a while ago that actually started the whole thing I understand
that we have combinations of requests that we want to bunch together for a
specific semantic effect. (see OPEN+GETATTR that started it all)
If that is true, then bunching together more commands to create 'compounds' that
semantically linked should not be a problem and we don't need any algorithm for
recosntructing the args. We know the semantics on both ends and craft the compounds
according to what is to be accomplished (the fuse server just provides the 'how')
From the newer discussion I have a feeling that there is the idea floating around
that we should bunch together arbitrary requests to have some performance advantage.
This was not my initial intention.
We could do that however if we can fill the args and the requests are not
interdependent.
If we can signal to the fuse server what we expect as result
(at least the allocated memory) I think we can do both, but I would like to have the
emphasis more on the semantic grouping for the moment.
Do you guys think that there will ever be a fuse server that doesn't support compounds
and all of them are handled by something like libfuse and the request handlers are just
called without having to handle not even one unseparatebale semantic 'group'?
>
> Thanks,
> Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 10:43 ` Bernd Schubert
2026-02-12 11:44 ` Horst Birthelmer
@ 2026-02-12 11:55 ` Miklos Szeredi
1 sibling, 0 replies; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 11:55 UTC (permalink / raw)
To: Bernd Schubert
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, 12 Feb 2026 at 11:43, Bernd Schubert <bernd@bsbernd.com> wrote:
> I think you mean FUSE_CREATE?
Yes.
> Which is create+getattr, but always
> preceded by FUSE_LOOKUP is always sent first?
Maybe LOOKUP is always sent because otherwise FMODE_CREATED may not be
(even approximately) correct. Even if LOOKUP is sent, the file might
have been created remotely and hence FMODE_CREATED be incorrect. The
only way to fix this is to return this flag from CREATE.
> Horst is currently working
> on full atomic open based on compounds, i.e. a totally new patch set to
> the earlier versions. With that LOOKUP
I'm saying that LOOKUP is just unnecessary. Maybe I'm missing
something, though.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 10:48 ` Horst Birthelmer
@ 2026-02-12 12:10 ` Miklos Szeredi
2026-02-12 12:33 ` Horst Birthelmer
0 siblings, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-12 12:10 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, 12 Feb 2026 at 11:48, Horst Birthelmer <horst@birthelmer.de> wrote:
>
> On Thu, Feb 12, 2026 at 11:23:56AM +0100, Miklos Szeredi wrote:
> > On Thu, 12 Feb 2026 at 10:53, Horst Birthelmer <horst@birthelmer.de> wrote:
> > >
> > >
> > > Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
> > > that we know that this is not done in this case, so the requests can be processed
> > > 'separately'.
> > > If that is missing the fuse server has to look at the combination and decide wether it
> > > will execute it as a 'compound' or return an error.
> >
> > I'd rather add some sub-op header flag that how to fill the missing
> > input. E.g. use the nodeid from the previous op's result.
> >
> > If there's no flag, then the op is "separable".
> >
>
> This makes the handling on the fuse server side unnecessarily harder.
> With the current way I can check the flag in the compound header and let libfuse handle the
> compound by calling the request handlers separately, and not worry about a thing.
>
> If the flag is not there, the fuse server itself
> (passthrough_hp from the PR already demonstrates this) has to handle the whole compound
> as a whole. I'm confident that this way we can handle pretty much every semantically
> overloaded combination.
Yeah, that's one strategy. I'm saying that supporting compounds that
are not "separable" within libfuse should be possible, given a few
constraints. Something very similar is done in io-uring. It adds
complexity, but I think it's worth it.
I also have the feeling that decoding requests should always be done
by the library, and if the server wants to handle compounds in special
way (because for example the network protocol also supports that),
then it should be done by bracketing the regular operation callbacks
with compound callbacks, that can allocate/free context which can be
used by the operation callbacks.
Not sure if I'm making sense.
>
> The other way would make the handling in libfuse or in the lowest level of the fuse server
> (for fuse servers that don't use libfuse) almost impossible without parsing all the requests
> and all the flags to know that we would have been able to get away with very little work.
>
> I had thought of a hierarchical parsing of the compound.
> The fuse server can decide
> 1. does it handle compounds at all
> 2. does it support this particular compound (based on the opcodes and the compound flags
> and the particular capabilities of the fuse server)
> 3. if the particular compound can not be handled can libfuse handle it for us?
>
> This way we can have real atomic operations in fuse server, where it supports it.
Yes, that's something definitely useful. But I also think that the
fuse *filesystem* code in the kernel should not have to worry about
whether a particular server supports a particular combination of
operations and fall back to calling ops sequentially if it doesn't.
This could be all handled transparently in the layers below
(fs/fuse/dev.c, lib/fuse_lowelevel.c).
> I don't understand yet, why.
> I think we could actually implement a real atomic open if we craft a compound for it and
> the fuse server supports it. If not, we can go back to the way it is handled now.
>
> What am I missing here?
I'm saying that there's no point in splitting FUSE_CREATE into
FUSE_LOOKUP + FUSE_MKNOD compound, since that would:
a) complicate the logic of joining the ops (which I was taking about above)
b) add redundant information (parent and name are the same in both ops)
c) we already have an atomic op that does both, so why make it larger
and less efficient?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 12:10 ` Miklos Szeredi
@ 2026-02-12 12:33 ` Horst Birthelmer
2026-02-14 1:04 ` Joanne Koong
0 siblings, 1 reply; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-12 12:33 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Horst Birthelmer, Bernd Schubert, Joanne Koong, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 01:10:19PM +0100, Miklos Szeredi wrote:
> On Thu, 12 Feb 2026 at 11:48, Horst Birthelmer <horst@birthelmer.de> wrote:
> > On Thu, Feb 12, 2026 at 11:23:56AM +0100, Miklos Szeredi wrote:
> > > On Thu, 12 Feb 2026 at 10:53, Horst Birthelmer <horst@birthelmer.de> wrote:
> > > >
> > > >
> > > > Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
> > > > that we know that this is not done in this case, so the requests can be processed
> > > > 'separately'.
> > > > If that is missing the fuse server has to look at the combination and decide wether it
> > > > will execute it as a 'compound' or return an error.
> > >
> > > I'd rather add some sub-op header flag that how to fill the missing
> > > input. E.g. use the nodeid from the previous op's result.
> > >
> > > If there's no flag, then the op is "separable".
> > >
> >
> > This makes the handling on the fuse server side unnecessarily harder.
> > With the current way I can check the flag in the compound header and let libfuse handle the
> > compound by calling the request handlers separately, and not worry about a thing.
> >
> > If the flag is not there, the fuse server itself
> > (passthrough_hp from the PR already demonstrates this) has to handle the whole compound
> > as a whole. I'm confident that this way we can handle pretty much every semantically
> > overloaded combination.
>
> Yeah, that's one strategy. I'm saying that supporting compounds that
> are not "separable" within libfuse should be possible, given a few
> constraints. Something very similar is done in io-uring. It adds
> complexity, but I think it's worth it.
>
> I also have the feeling that decoding requests should always be done
> by the library, and if the server wants to handle compounds in special
> way (because for example the network protocol also supports that),
> then it should be done by bracketing the regular operation callbacks
> with compound callbacks, that can allocate/free context which can be
> used by the operation callbacks.
>
Right now we don't have it completely like this but very similar.
The fuse server makes the final decision not the library.
If it doesn't support a combination it gives control back to libfuse
and if the FUSE_COMPOUND_SEPARABLE flag is set libfuse calls the handlers
sequencially.
The only drawback here is, (this actually makes handling a lot easier)
that we have to have valid and complete results for all requests
that are in the compound.
> Not sure if I'm making sense.
>
I think we're getting there that I understand your perspective better.
> >
> > The other way would make the handling in libfuse or in the lowest level of the fuse server
> > (for fuse servers that don't use libfuse) almost impossible without parsing all the requests
> > and all the flags to know that we would have been able to get away with very little work.
> >
> > I had thought of a hierarchical parsing of the compound.
> > The fuse server can decide
> > 1. does it handle compounds at all
> > 2. does it support this particular compound (based on the opcodes and the compound flags
> > and the particular capabilities of the fuse server)
> > 3. if the particular compound can not be handled can libfuse handle it for us?
> >
> > This way we can have real atomic operations in fuse server, where it supports it.
>
> Yes, that's something definitely useful. But I also think that the
> fuse *filesystem* code in the kernel should not have to worry about
> whether a particular server supports a particular combination of
> operations and fall back to calling ops sequentially if it doesn't.
> This could be all handled transparently in the layers below
> (fs/fuse/dev.c, lib/fuse_lowelevel.c).
I actually like this approach.
lib/fuse_lowlevel.c is already there ... the kernel part does not do this yet.
I'll have a look and come up with a new version.
> > I don't understand yet, why.
> > I think we could actually implement a real atomic open if we craft a compound for it and
> > the fuse server supports it. If not, we can go back to the way it is handled now.
> >
> > What am I missing here?
>
> I'm saying that there's no point in splitting FUSE_CREATE into
> FUSE_LOOKUP + FUSE_MKNOD compound, since that would:
>
> a) complicate the logic of joining the ops (which I was taking about above)
> b) add redundant information (parent and name are the same in both ops)
> c) we already have an atomic op that does both, so why make it larger
> and less efficient?
>
> Thanks,
> Miklos
Thanks,
Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 12:33 ` Horst Birthelmer
@ 2026-02-14 1:04 ` Joanne Koong
0 siblings, 0 replies; 31+ messages in thread
From: Joanne Koong @ 2026-02-14 1:04 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Miklos Szeredi, Horst Birthelmer, Bernd Schubert, Luis Henriques,
linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 4:34 AM Horst Birthelmer <horst@birthelmer.de> wrote:
>
> On Thu, Feb 12, 2026 at 01:10:19PM +0100, Miklos Szeredi wrote:
> > On Thu, 12 Feb 2026 at 11:48, Horst Birthelmer <horst@birthelmer.de> wrote:
> > > On Thu, Feb 12, 2026 at 11:23:56AM +0100, Miklos Szeredi wrote:
> > > > On Thu, 12 Feb 2026 at 10:53, Horst Birthelmer <horst@birthelmer.de> wrote:
> > > > >
> > > > >
> > > > > Exactly. And the FUSE_COMPOUND_SEPARABLE was actually there to tell the fuse server,
> > > > > that we know that this is not done in this case, so the requests can be processed
> > > > > 'separately'.
> > > > > If that is missing the fuse server has to look at the combination and decide wether it
> > > > > will execute it as a 'compound' or return an error.
> > > >
> > > > I'd rather add some sub-op header flag that how to fill the missing
> > > > input. E.g. use the nodeid from the previous op's result.
> > > >
> > > > If there's no flag, then the op is "separable".
> > > >
> > >
> > > This makes the handling on the fuse server side unnecessarily harder.
> > > With the current way I can check the flag in the compound header and let libfuse handle the
> > > compound by calling the request handlers separately, and not worry about a thing.
In my opinion, having the request marked at the individual request
header level as having a dependency on the request before it gives
more flexibility long term. With marking it at the compound header
level, all the requests will have to be assumed as sequentially
dependent whereas it might be the case that there's only a few
dependency chains between requests rather than all of the requests in
the compound being dependent on the request preceding it.
Thanks,
Joanne
> > >
> > > If the flag is not there, the fuse server itself
> > > (passthrough_hp from the PR already demonstrates this) has to handle the whole compound
> > > as a whole. I'm confident that this way we can handle pretty much every semantically
> > > overloaded combination.
> >
> > Yeah, that's one strategy. I'm saying that supporting compounds that
> > are not "separable" within libfuse should be possible, given a few
> > constraints. Something very similar is done in io-uring. It adds
> > complexity, but I think it's worth it.
> >
> > I also have the feeling that decoding requests should always be done
> > by the library, and if the server wants to handle compounds in special
> > way (because for example the network protocol also supports that),
> > then it should be done by bracketing the regular operation callbacks
> > with compound callbacks, that can allocate/free context which can be
> > used by the operation callbacks.
> >
>
> Right now we don't have it completely like this but very similar.
> The fuse server makes the final decision not the library.
>
> If it doesn't support a combination it gives control back to libfuse
> and if the FUSE_COMPOUND_SEPARABLE flag is set libfuse calls the handlers
> sequencially.
>
> The only drawback here is, (this actually makes handling a lot easier)
> that we have to have valid and complete results for all requests
> that are in the compound.
>
> > Not sure if I'm making sense.
> >
>
> I think we're getting there that I understand your perspective better.
>
> > >
> > > The other way would make the handling in libfuse or in the lowest level of the fuse server
> > > (for fuse servers that don't use libfuse) almost impossible without parsing all the requests
> > > and all the flags to know that we would have been able to get away with very little work.
> > >
> > > I had thought of a hierarchical parsing of the compound.
> > > The fuse server can decide
> > > 1. does it handle compounds at all
> > > 2. does it support this particular compound (based on the opcodes and the compound flags
> > > and the particular capabilities of the fuse server)
> > > 3. if the particular compound can not be handled can libfuse handle it for us?
> > >
> > > This way we can have real atomic operations in fuse server, where it supports it.
> >
> > Yes, that's something definitely useful. But I also think that the
> > fuse *filesystem* code in the kernel should not have to worry about
> > whether a particular server supports a particular combination of
> > operations and fall back to calling ops sequentially if it doesn't.
> > This could be all handled transparently in the layers below
> > (fs/fuse/dev.c, lib/fuse_lowelevel.c).
>
> I actually like this approach.
> lib/fuse_lowlevel.c is already there ... the kernel part does not do this yet.
> I'll have a look and come up with a new version.
>
> > > I don't understand yet, why.
> > > I think we could actually implement a real atomic open if we craft a compound for it and
> > > the fuse server supports it. If not, we can go back to the way it is handled now.
> > >
> > > What am I missing here?
> >
> > I'm saying that there's no point in splitting FUSE_CREATE into
> > FUSE_LOOKUP + FUSE_MKNOD compound, since that would:
> >
> > a) complicate the logic of joining the ops (which I was taking about above)
> > b) add redundant information (parent and name are the same in both ops)
> > c) we already have an atomic op that does both, so why make it larger
> > and less efficient?
> >
> > Thanks,
> > Miklos
>
> Thanks,
> Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-12 11:44 ` Horst Birthelmer
@ 2026-02-14 1:35 ` Joanne Koong
2026-02-14 12:54 ` Bernd Schubert
2026-02-14 17:50 ` Re: " Horst Birthelmer
0 siblings, 2 replies; 31+ messages in thread
From: Joanne Koong @ 2026-02-14 1:35 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Bernd Schubert, Miklos Szeredi, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Feb 12, 2026 at 3:44 AM Horst Birthelmer <horst@birthelmer.de> wrote:
>
> On Thu, Feb 12, 2026 at 11:43:12AM +0100, Bernd Schubert wrote:
> >
> >
> > On 2/12/26 11:16, Miklos Szeredi wrote:
> > > On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
> > >> On 2/12/26 10:07, Miklos Szeredi wrote:
> > >>> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
> > >>>
> > >
> > >>> So as a first iteration can we just limit compounds to small in/out sizes?
> > >>
> > >> Even without write payload, there is still FUSE_NAME_MAX, that can be up
> > >> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
> > >> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
> > >> the buffer seems a bit overhead? Especially as libfuse needs to iterate
> > >> over each compound first and figure out the exact size.
> > >
> > > Ah, huge filenames are a thing. Probably not worth doing
> > > LOOKUP+CREATE as a compound since it duplicates the filename. We
> > > already have LOOKUP_CREATE, which does both. Am I missing something?
> >
> > I think you mean FUSE_CREATE? Which is create+getattr, but always
> > preceded by FUSE_LOOKUP is always sent first? Horst is currently working
> > on full atomic open based on compounds, i.e. a totally new patch set to
> > the earlier versions. With that LOOKUP
> >
> > Yes, we could use the same file name for the entire compound, but then
> > individual requests of the compound rely on an uber info. This info
> > needs to be created, it needs to be handled on the other side as part of
> > the individual parts. Please correct me if I'm wrong, but this sounds
> > much more difficult than just adding an info how much space is needed to
> > hold the result?
>
> I have a feeling we have different use cases in mind and misunderstand each other.
>
> As I see it:
> From the discussion a while ago that actually started the whole thing I understand
> that we have combinations of requests that we want to bunch together for a
> specific semantic effect. (see OPEN+GETATTR that started it all)
>
> If that is true, then bunching together more commands to create 'compounds' that
> semantically linked should not be a problem and we don't need any algorithm for
> recosntructing the args. We know the semantics on both ends and craft the compounds
> according to what is to be accomplished (the fuse server just provides the 'how')
>
> From the newer discussion I have a feeling that there is the idea floating around
> that we should bunch together arbitrary requests to have some performance advantage.
> This was not my initial intention.
> We could do that however if we can fill the args and the requests are not
> interdependent.
I have a series of (very unpolished) patches from last year that does
basically this. When libfuse does a read on /dev/fuse, the kernel
crams in as many requests off the fiq list as it can fit into the
buffer. On the libfuse side, when it iterates through that buffer it
offloads each request to a worker thread to process/execute that
request. It worked the same way on the dev uring side. I put those
changes aside to work on the zero copy stuff, but if there's interest
I can go back to those patches and clean them up and put them through
some testing. I don't think the work overlaps with your compound
requests stuff though. The compound requests would be a request inside
the larger batch.
>
> If we can signal to the fuse server what we expect as result
> (at least the allocated memory) I think we can do both, but I would like to have the
> emphasis more on the semantic grouping for the moment.
>
> Do you guys think that there will ever be a fuse server that doesn't support compounds
> and all of them are handled by something like libfuse and the request handlers are just
> called without having to handle not even one unseparatebale semantic 'group'?
If I'm understanding the question correctly, yes imo this is likely.
But I think that's fine. In my opinion, the main benefit from this is
saving on the context switching cost. I don't really see the problem
if libfuse has to issue the requests separately and sequentially if
the requests have dependency chains and the server doesn't have a
special handler for that specific compound request combo (which imo I
don't think libfuse should even add, as I dont see what the purpose of
it is that can't be done by sending each request to each separate
handler sequentially).
Thanks,
Joanne
>
> >
> > Thanks,
> > Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-14 1:35 ` Joanne Koong
@ 2026-02-14 12:54 ` Bernd Schubert
2026-02-14 17:50 ` Re: " Horst Birthelmer
1 sibling, 0 replies; 31+ messages in thread
From: Bernd Schubert @ 2026-02-14 12:54 UTC (permalink / raw)
To: Joanne Koong, Horst Birthelmer
Cc: Bernd Schubert, Miklos Szeredi, Horst Birthelmer, Luis Henriques,
linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
Horst Birthelmer
On 2/14/26 02:35, Joanne Koong wrote:
> On Thu, Feb 12, 2026 at 3:44 AM Horst Birthelmer <horst@birthelmer.de> wrote:
>>
>> On Thu, Feb 12, 2026 at 11:43:12AM +0100, Bernd Schubert wrote:
>>>
>>>
>>> On 2/12/26 11:16, Miklos Szeredi wrote:
>>>> On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
>>>>> On 2/12/26 10:07, Miklos Szeredi wrote:
>>>>>> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
>>>>>>
>>>>
>>>>>> So as a first iteration can we just limit compounds to small in/out sizes?
>>>>>
>>>>> Even without write payload, there is still FUSE_NAME_MAX, that can be up
>>>>> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
>>>>> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
>>>>> the buffer seems a bit overhead? Especially as libfuse needs to iterate
>>>>> over each compound first and figure out the exact size.
>>>>
>>>> Ah, huge filenames are a thing. Probably not worth doing
>>>> LOOKUP+CREATE as a compound since it duplicates the filename. We
>>>> already have LOOKUP_CREATE, which does both. Am I missing something?
>>>
>>> I think you mean FUSE_CREATE? Which is create+getattr, but always
>>> preceded by FUSE_LOOKUP is always sent first? Horst is currently working
>>> on full atomic open based on compounds, i.e. a totally new patch set to
>>> the earlier versions. With that LOOKUP
>>>
>>> Yes, we could use the same file name for the entire compound, but then
>>> individual requests of the compound rely on an uber info. This info
>>> needs to be created, it needs to be handled on the other side as part of
>>> the individual parts. Please correct me if I'm wrong, but this sounds
>>> much more difficult than just adding an info how much space is needed to
>>> hold the result?
>>
>> I have a feeling we have different use cases in mind and misunderstand each other.
>>
>> As I see it:
>> From the discussion a while ago that actually started the whole thing I understand
>> that we have combinations of requests that we want to bunch together for a
>> specific semantic effect. (see OPEN+GETATTR that started it all)
>>
>> If that is true, then bunching together more commands to create 'compounds' that
>> semantically linked should not be a problem and we don't need any algorithm for
>> recosntructing the args. We know the semantics on both ends and craft the compounds
>> according to what is to be accomplished (the fuse server just provides the 'how')
>>
>> From the newer discussion I have a feeling that there is the idea floating around
>> that we should bunch together arbitrary requests to have some performance advantage.
>> This was not my initial intention.
>> We could do that however if we can fill the args and the requests are not
>> interdependent.
>
> I have a series of (very unpolished) patches from last year that does
> basically this. When libfuse does a read on /dev/fuse, the kernel
> crams in as many requests off the fiq list as it can fit into the
> buffer. On the libfuse side, when it iterates through that buffer it
> offloads each request to a worker thread to process/execute that
> request. It worked the same way on the dev uring side. I put those
> changes aside to work on the zero copy stuff, but if there's interest
> I can go back to those patches and clean them up and put them through
> some testing. I don't think the work overlaps with your compound
> requests stuff though. The compound requests would be a request inside
> the larger batch.
>
>>
>> If we can signal to the fuse server what we expect as result
>> (at least the allocated memory) I think we can do both, but I would like to have the
>> emphasis more on the semantic grouping for the moment.
>>
>> Do you guys think that there will ever be a fuse server that doesn't support compounds
>> and all of them are handled by something like libfuse and the request handlers are just
>> called without having to handle not even one unseparatebale semantic 'group'?
>
> If I'm understanding the question correctly, yes imo this is likely.
> But I think that's fine. In my opinion, the main benefit from this is
> saving on the context switching cost. I don't really see the problem
> if libfuse has to issue the requests separately and sequentially if
> the requests have dependency chains and the server doesn't have a
> special handler for that specific compound request combo (which imo I
> don't think libfuse should even add, as I dont see what the purpose of
> it is that can't be done by sending each request to each separate
> handler sequentially).
Think of sshfs and open+getattr and a latency of ~1s per request. I.e.
especially if you go over the wire, you want to bundle as much as
possible. I'm aware that sftp current does not handle open+getattr, but
once fuse would support it as compound, we would have a good use case.
And while the 1s latency is corner case, although not even too uncommon
with sshfs, it applies to any network file system - you always want to
send as much as possible over the wire in one buffer. The
kernel/userspace interface is likely not the limit here.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-14 1:35 ` Joanne Koong
2026-02-14 12:54 ` Bernd Schubert
@ 2026-02-14 17:50 ` Horst Birthelmer
2026-02-16 11:43 ` Miklos Szeredi
2026-03-06 0:52 ` Joanne Koong
1 sibling, 2 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-14 17:50 UTC (permalink / raw)
To: Joanne Koong
Cc: Bernd Schubert, Miklos Szeredi, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Fri, Feb 13, 2026 at 05:35:30PM -0800, Joanne Koong wrote:
> On Thu, Feb 12, 2026 at 3:44 AM Horst Birthelmer <horst@birthelmer.de> wrote:
> > On Thu, Feb 12, 2026 at 11:43:12AM +0100, Bernd Schubert wrote:
> > > On 2/12/26 11:16, Miklos Szeredi wrote:
> > > > On Thu, 12 Feb 2026 at 10:48, Bernd Schubert <bernd@bsbernd.com> wrote:
> > > >> On 2/12/26 10:07, Miklos Szeredi wrote:
> > > >>> On Wed, 11 Feb 2026 at 21:36, Bernd Schubert <bernd@bsbernd.com> wrote:
> > > >>>
> > > >
> > > >>> So as a first iteration can we just limit compounds to small in/out sizes?
> > > >>
> > > >> Even without write payload, there is still FUSE_NAME_MAX, that can be up
> > > >> to PATH_MAX -1. Let's say there is LOOKUP, CREATE/OPEN, GETATTR. Lookup
> > > >> could take >4K, CREATE/OPEN another 4K. Copying that pro-actively out of
> > > >> the buffer seems a bit overhead? Especially as libfuse needs to iterate
> > > >> over each compound first and figure out the exact size.
> > > >
> > > > Ah, huge filenames are a thing. Probably not worth doing
> > > > LOOKUP+CREATE as a compound since it duplicates the filename. We
> > > > already have LOOKUP_CREATE, which does both. Am I missing something?
> > >
> > > I think you mean FUSE_CREATE? Which is create+getattr, but always
> > > preceded by FUSE_LOOKUP is always sent first? Horst is currently working
> > > on full atomic open based on compounds, i.e. a totally new patch set to
> > > the earlier versions. With that LOOKUP
> > >
> > > Yes, we could use the same file name for the entire compound, but then
> > > individual requests of the compound rely on an uber info. This info
> > > needs to be created, it needs to be handled on the other side as part of
> > > the individual parts. Please correct me if I'm wrong, but this sounds
> > > much more difficult than just adding an info how much space is needed to
> > > hold the result?
> >
> > I have a feeling we have different use cases in mind and misunderstand each other.
> >
> > As I see it:
> > From the discussion a while ago that actually started the whole thing I understand
> > that we have combinations of requests that we want to bunch together for a
> > specific semantic effect. (see OPEN+GETATTR that started it all)
> >
> > If that is true, then bunching together more commands to create 'compounds' that
> > semantically linked should not be a problem and we don't need any algorithm for
> > recosntructing the args. We know the semantics on both ends and craft the compounds
> > according to what is to be accomplished (the fuse server just provides the 'how')
> >
> > From the newer discussion I have a feeling that there is the idea floating around
> > that we should bunch together arbitrary requests to have some performance advantage.
> > This was not my initial intention.
> > We could do that however if we can fill the args and the requests are not
> > interdependent.
>
> I have a series of (very unpolished) patches from last year that does
> basically this. When libfuse does a read on /dev/fuse, the kernel
> crams in as many requests off the fiq list as it can fit into the
> buffer. On the libfuse side, when it iterates through that buffer it
> offloads each request to a worker thread to process/execute that
> request. It worked the same way on the dev uring side. I put those
> changes aside to work on the zero copy stuff, but if there's interest
> I can go back to those patches and clean them up and put them through
> some testing. I don't think the work overlaps with your compound
> requests stuff though. The compound requests would be a request inside
> the larger batch.
I would like to have your patch for the processing of multiple requests
and the compound for handling semantically related requests.
> >
> > If we can signal to the fuse server what we expect as result
> > (at least the allocated memory) I think we can do both, but I would like to have the
> > emphasis more on the semantic grouping for the moment.
> >
> > Do you guys think that there will ever be a fuse server that doesn't support compounds
> > and all of them are handled by something like libfuse and the request handlers are just
> > called without having to handle not even one unseparatebale semantic 'group'?
>
> If I'm understanding the question correctly, yes imo this is likely.
> But I think that's fine. In my opinion, the main benefit from this is
> saving on the context switching cost. I don't really see the problem
> if libfuse has to issue the requests separately and sequentially if
> the requests have dependency chains and the server doesn't have a
> special handler for that specific compound request combo (which imo I
> don't think libfuse should even add, as I dont see what the purpose of
> it is that can't be done by sending each request to each separate
> handler sequentially).
Which part would process those interdependencies?
We have multiple options and I'm not entirely sure,
the kernel, libfuse, fuse server?
In the current version none would do any special interpretation and the
fuse server will have a specialized handler for a compound type, which
automatically 'knows' how to get the right args.
> Thanks,
> Joanne
>
> > >
> > > Thanks,
> > > Bernd
Thanks,
Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-14 17:50 ` Re: " Horst Birthelmer
@ 2026-02-16 11:43 ` Miklos Szeredi
2026-02-16 15:22 ` Miklos Szeredi
2026-02-17 7:28 ` Horst Birthelmer
2026-03-06 0:52 ` Joanne Koong
1 sibling, 2 replies; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-16 11:43 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Joanne Koong, Bernd Schubert, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Sat, 14 Feb 2026 at 18:51, Horst Birthelmer <horst@birthelmer.de> wrote:
> Which part would process those interdependencies?
> We have multiple options and I'm not entirely sure,
> the kernel, libfuse, fuse server?
Kernel: mandatory
Libfuse: new versions should handle it, but older versions continue to
work (because the kernel will deal with it)
Fuse server: optional
> In the current version none would do any special interpretation and the
> fuse server will have a specialized handler for a compound type, which
> automatically 'knows' how to get the right args.
The API should allow the server to deal with generic compounds, not
just combinations it actually knows about.
What we need to define is the types of dependencies, e.g.:
- default: wait for previous and stop on error
- copy nodeid in fuse_entry_out from the previous lookup/mk*
- copy fh in fuse_open_out from previous open
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-16 11:43 ` Miklos Szeredi
@ 2026-02-16 15:22 ` Miklos Szeredi
2026-02-17 7:26 ` Horst Birthelmer
2026-02-17 7:28 ` Horst Birthelmer
1 sibling, 1 reply; 31+ messages in thread
From: Miklos Szeredi @ 2026-02-16 15:22 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Joanne Koong, Bernd Schubert, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Mon, 16 Feb 2026 at 12:43, Miklos Szeredi <miklos@szeredi.hu> wrote:
>
> On Sat, 14 Feb 2026 at 18:51, Horst Birthelmer <horst@birthelmer.de> wrote:
>
> > Which part would process those interdependencies?
Another interesting question is which entity is responsible for
undoing a partial success?
E.g. if in a compound mknod succeeds while statx fails, then the
creation needs to be undone. Since this sort of partial failure
should be rare, my feeling is that this should be done by the kernel
to avoid adding complexity to all layers.
This could be a problem in a distributed fs, where the ephemeral
object might cause side effects. So in these cases the server needs
to deal with partial failures for maximum correctness.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-16 15:22 ` Miklos Szeredi
@ 2026-02-17 7:26 ` Horst Birthelmer
0 siblings, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-17 7:26 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Joanne Koong, Bernd Schubert, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Mon, Feb 16, 2026 at 04:22:13PM +0100, Miklos Szeredi wrote:
> On Mon, 16 Feb 2026 at 12:43, Miklos Szeredi <miklos@szeredi.hu> wrote:
> >
> > On Sat, 14 Feb 2026 at 18:51, Horst Birthelmer <horst@birthelmer.de> wrote:
> >
> > > Which part would process those interdependencies?
>
> Another interesting question is which entity is responsible for
> undoing a partial success?
>
> E.g. if in a compound mknod succeeds while statx fails, then the
> creation needs to be undone. Since this sort of partial failure
> should be rare, my feeling is that this should be done by the kernel
> to avoid adding complexity to all layers.
>
> This could be a problem in a distributed fs, where the ephemeral
> object might cause side effects. So in these cases the server needs
> to deal with partial failures for maximum correctness.
I completely agree, that the kernel has to handle this.
And I think that the implementation of the particular compound
(like the function fuse_open_and_getattr() in the current example
has to deal with this)
These are the cases where we cannot have an automatic decoding deal
with the error since we don't have the information and data of the
actual semantics.
>
> Thanks,
> Miklos
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-16 11:43 ` Miklos Szeredi
2026-02-16 15:22 ` Miklos Szeredi
@ 2026-02-17 7:28 ` Horst Birthelmer
1 sibling, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-02-17 7:28 UTC (permalink / raw)
To: Miklos Szeredi
Cc: Joanne Koong, Bernd Schubert, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Mon, Feb 16, 2026 at 12:43:18PM +0100, Miklos Szeredi wrote:
> On Sat, 14 Feb 2026 at 18:51, Horst Birthelmer <horst@birthelmer.de> wrote:
>
> > Which part would process those interdependencies?
> > We have multiple options and I'm not entirely sure,
> > the kernel, libfuse, fuse server?
>
> Kernel: mandatory
> Libfuse: new versions should handle it, but older versions continue to
> work (because the kernel will deal with it)
> Fuse server: optional
>
> > In the current version none would do any special interpretation and the
> > fuse server will have a specialized handler for a compound type, which
> > automatically 'knows' how to get the right args.
>
> The API should allow the server to deal with generic compounds, not
> just combinations it actually knows about.
>
> What we need to define is the types of dependencies, e.g.:
>
> - default: wait for previous and stop on error
> - copy nodeid in fuse_entry_out from the previous lookup/mk*
> - copy fh in fuse_open_out from previous open
I understand.
Will come up with a new version and we'll see how it works out.
>
> Thanks,
> Miklos
>
Thanks for taking the time,
Horst
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-02-14 17:50 ` Re: " Horst Birthelmer
2026-02-16 11:43 ` Miklos Szeredi
@ 2026-03-06 0:52 ` Joanne Koong
2026-03-06 8:17 ` Horst Birthelmer
1 sibling, 1 reply; 31+ messages in thread
From: Joanne Koong @ 2026-03-06 0:52 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Bernd Schubert, Miklos Szeredi, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Sat, Feb 14, 2026 at 9:51 AM Horst Birthelmer <horst@birthelmer.de> wrote:
>
> On Fri, Feb 13, 2026 at 05:35:30PM -0800, Joanne Koong wrote:
> > On Thu, Feb 12, 2026 at 3:44 AM Horst Birthelmer <horst@birthelmer.de> wrote:
> > > I have a feeling we have different use cases in mind and misunderstand each other.
> > >
> > > As I see it:
> > > From the discussion a while ago that actually started the whole thing I understand
> > > that we have combinations of requests that we want to bunch together for a
> > > specific semantic effect. (see OPEN+GETATTR that started it all)
> > >
> > > If that is true, then bunching together more commands to create 'compounds' that
> > > semantically linked should not be a problem and we don't need any algorithm for
> > > recosntructing the args. We know the semantics on both ends and craft the compounds
> > > according to what is to be accomplished (the fuse server just provides the 'how')
> > >
> > > From the newer discussion I have a feeling that there is the idea floating around
> > > that we should bunch together arbitrary requests to have some performance advantage.
> > > This was not my initial intention.
> > > We could do that however if we can fill the args and the requests are not
> > > interdependent.
> >
> > I have a series of (very unpolished) patches from last year that does
> > basically this. When libfuse does a read on /dev/fuse, the kernel
> > crams in as many requests off the fiq list as it can fit into the
> > buffer. On the libfuse side, when it iterates through that buffer it
> > offloads each request to a worker thread to process/execute that
> > request. It worked the same way on the dev uring side. I put those
> > changes aside to work on the zero copy stuff, but if there's interest
> > I can go back to those patches and clean them up and put them through
> > some testing. I don't think the work overlaps with your compound
> > requests stuff though. The compound requests would be a request inside
> > the larger batch.
>
> I would like to have your patch for the processing of multiple requests
> and the compound for handling semantically related requests.
>
the kernel-side changes for the /dev/fuse request batching are pretty
self-contained [1] but the libfuse changes are very ugly. The
benchmarks didn't look promising. I think it only really helps if the
server has metadata-heavy bursty behavior that saturates all the
libfuse threads, but I don't think that's typical. I dont think it's
worth pursuing further.
Thanks,
Joanne
[1] https://github.com/joannekoong/linux/commit/308ebbde134ac98d3b3d3e2f3abc2c52ef444759
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: Re: Re: Re: [PATCH v5 1/3] fuse: add compound command to combine multiple requests
2026-03-06 0:52 ` Joanne Koong
@ 2026-03-06 8:17 ` Horst Birthelmer
0 siblings, 0 replies; 31+ messages in thread
From: Horst Birthelmer @ 2026-03-06 8:17 UTC (permalink / raw)
To: Joanne Koong
Cc: Bernd Schubert, Miklos Szeredi, Horst Birthelmer, Bernd Schubert,
Luis Henriques, linux-kernel, linux-fsdevel, Horst Birthelmer
On Thu, Mar 05, 2026 at 04:52:01PM -0800, Joanne Koong wrote:
> On Sat, Feb 14, 2026 at 9:51 AM Horst Birthelmer <horst@birthelmer.de> wrote:
> >
> > On Fri, Feb 13, 2026 at 05:35:30PM -0800, Joanne Koong wrote:
> > > On Thu, Feb 12, 2026 at 3:44 AM Horst Birthelmer <horst@birthelmer.de> wrote:
> > > > I have a feeling we have different use cases in mind and misunderstand each other.
> > > >
> > > > As I see it:
> > > > From the discussion a while ago that actually started the whole thing I understand
> > > > that we have combinations of requests that we want to bunch together for a
> > > > specific semantic effect. (see OPEN+GETATTR that started it all)
> > > >
> > > > If that is true, then bunching together more commands to create 'compounds' that
> > > > semantically linked should not be a problem and we don't need any algorithm for
> > > > recosntructing the args. We know the semantics on both ends and craft the compounds
> > > > according to what is to be accomplished (the fuse server just provides the 'how')
> > > >
> > > > From the newer discussion I have a feeling that there is the idea floating around
> > > > that we should bunch together arbitrary requests to have some performance advantage.
> > > > This was not my initial intention.
> > > > We could do that however if we can fill the args and the requests are not
> > > > interdependent.
> > >
> > > I have a series of (very unpolished) patches from last year that does
> > > basically this. When libfuse does a read on /dev/fuse, the kernel
> > > crams in as many requests off the fiq list as it can fit into the
> > > buffer. On the libfuse side, when it iterates through that buffer it
> > > offloads each request to a worker thread to process/execute that
> > > request. It worked the same way on the dev uring side. I put those
> > > changes aside to work on the zero copy stuff, but if there's interest
> > > I can go back to those patches and clean them up and put them through
> > > some testing. I don't think the work overlaps with your compound
> > > requests stuff though. The compound requests would be a request inside
> > > the larger batch.
> >
> > I would like to have your patch for the processing of multiple requests
> > and the compound for handling semantically related requests.
> >
>
> the kernel-side changes for the /dev/fuse request batching are pretty
> self-contained [1] but the libfuse changes are very ugly. The
> benchmarks didn't look promising. I think it only really helps if the
> server has metadata-heavy bursty behavior that saturates all the
> libfuse threads, but I don't think that's typical. I dont think it's
> worth pursuing further.
Thanks for the patch. I agree completely. However I wrote that in the context
where I thought that people wanted to achieve something different with compound
requests, like processing parts of a queue depending on some or no criteria.
That's why I stressed the semantic relation of the requests in a compound.
I'm almost willing to bet that in the future we will see someone get
the idea of LOOKUP+OPEN+READ+CLOSE, which would practically
create exactly those bursts when not done as a compound.
Just as a side note ...
>
> Thanks,
> Joanne
>
> [1] https://github.com/joannekoong/linux/commit/308ebbde134ac98d3b3d3e2f3abc2c52ef444759
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2026-03-06 8:17 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-10 8:46 [PATCH v5 0/3] fuse: compound commands Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 1/3] fuse: add compound command to combine multiple requests Horst Birthelmer
2026-02-11 14:59 ` Luis Henriques
2026-02-11 16:18 ` Horst Birthelmer
2026-02-11 16:13 ` Miklos Szeredi
2026-02-11 16:35 ` Horst Birthelmer
2026-02-12 9:38 ` Miklos Szeredi
2026-02-12 9:53 ` Horst Birthelmer
2026-02-12 10:23 ` Miklos Szeredi
2026-02-12 10:48 ` Horst Birthelmer
2026-02-12 12:10 ` Miklos Szeredi
2026-02-12 12:33 ` Horst Birthelmer
2026-02-14 1:04 ` Joanne Koong
2026-02-11 20:36 ` Bernd Schubert
2026-02-12 9:07 ` Miklos Szeredi
2026-02-12 9:48 ` Bernd Schubert
2026-02-12 10:16 ` Miklos Szeredi
2026-02-12 10:43 ` Bernd Schubert
2026-02-12 11:44 ` Horst Birthelmer
2026-02-14 1:35 ` Joanne Koong
2026-02-14 12:54 ` Bernd Schubert
2026-02-14 17:50 ` Re: " Horst Birthelmer
2026-02-16 11:43 ` Miklos Szeredi
2026-02-16 15:22 ` Miklos Szeredi
2026-02-17 7:26 ` Horst Birthelmer
2026-02-17 7:28 ` Horst Birthelmer
2026-03-06 0:52 ` Joanne Koong
2026-03-06 8:17 ` Horst Birthelmer
2026-02-12 11:55 ` Miklos Szeredi
2026-02-10 8:46 ` [PATCH v5 2/3] fuse: create helper functions for filling in fuse args for open and getattr Horst Birthelmer
2026-02-10 8:46 ` [PATCH v5 3/3] fuse: add an implementation of open+getattr Horst Birthelmer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox