From 58d6c6228d228b205b341512309de70c254be102 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Tue, 16 Sep 2025 13:31:45 +0200 Subject: [PATCH] fuse: add compound command to combine multiple requests fuse.h: add new opcode FUSE_COMPOUND fuse_compound.c: add new functionality to pack multiple fuse operations into one compound command file.c: add an implementation of open+getattr (cherry picked from commit d9e735140a3faccbe5786a7e75a4ad9a6a9aa2e0) --- fs/fuse/Makefile | 2 +- fs/fuse/compound.c | 432 ++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 24 +++ fs/fuse/file.c | 124 +++++++++-- fs/fuse/fuse_i.h | 27 ++- fs/fuse/inode.c | 6 + fs/fuse/ioctl.c | 2 +- include/uapi/linux/fuse.h | 38 ++++ 8 files changed, 633 insertions(+), 22 deletions(-) create mode 100644 fs/fuse/compound.c diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index f6952d826bcb33..64dad526c0b8ce 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_FUSE_FS) += fuse.o obj-$(CONFIG_CUSE) += cuse.o obj-$(CONFIG_VIRTIO_FS) += virtiofs.o -fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o fuse_dlm_cache.o +fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o fuse_dlm_cache.o compound.o fuse-y += iomode.o fuse-$(CONFIG_FUSE_DAX) += dax.o fuse-$(CONFIG_FUSE_IO_URING) += dev_uring.o diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c new file mode 100644 index 00000000000000..3758b4666366ea --- /dev/null +++ b/fs/fuse/compound.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 + * + * This file implements compound operations for FUSE, allowing multiple + * operations to be batched into a single request to reduce round trips + * between kernel and userspace. + */ + +#include "fuse_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Compound request + */ +struct fuse_compound_req +{ + struct fuse_mount *fm; + struct fuse_compound_in compound_header; + struct fuse_compound_out result_header; + + size_t total_size; + char *buffer; + size_t buffer_pos; + size_t buffer_size; + + size_t total_expected_out_size; + + /* Operation results for error tracking */ + int op_errors[FUSE_MAX_COMPOUND_OPS]; + struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS]; + + /* Parsing state to avoid double processing */ + bool parsed; +}; + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, + uint32_t flags) +{ + struct fuse_compound_req *compound; + + compound = kzalloc(sizeof(*compound), GFP_KERNEL); + if (!compound) + return ERR_PTR(-ENOMEM); + + compound->fm = fm; + compound->compound_header.flags = flags; + compound->buffer_size = PAGE_SIZE; + compound->buffer = kvmalloc(compound->buffer_size, GFP_KERNEL); + if (!compound->buffer) { + kfree(compound); + return ERR_PTR(-ENOMEM); + } + return compound; +} + +/* + * Free compound request resources + */ +void fuse_compound_free(struct fuse_compound_req *compound) +{ + if (compound) { + kvfree(compound->buffer); + kfree(compound); + } +} + +/* + * Validate compound request structure before sending it out. + * Returns 0 on success, negative error code on failure. + */ +static int fuse_compound_validate_header(struct fuse_compound_req *compound) +{ + struct fuse_compound_in *in_header = &compound->compound_header; + size_t offset = 0; + int i; + + if (compound->buffer_pos > compound->buffer_size) + return -EINVAL; + + if (!compound || !compound->buffer) + return -EINVAL; + + if (compound->buffer_pos < sizeof(struct fuse_in_header)) + return -EINVAL; + + if (in_header->count == 0 || in_header->count > FUSE_MAX_COMPOUND_OPS) + return -EINVAL; + + for (i = 0; i < in_header->count; i++) { + const struct fuse_in_header *op_hdr; + + if (offset + sizeof(struct fuse_in_header) > compound->buffer_pos) { + pr_info_ratelimited("FUSE: compound operation %d header extends beyond buffer (offset %zu + header size %zu > buffer pos %zu)\n", + i, offset, sizeof(struct fuse_in_header), compound->buffer_pos); + return -EINVAL; + } + + op_hdr = (const struct fuse_in_header *)(compound->buffer + offset); + + if (op_hdr->len < sizeof(struct fuse_in_header)) { + pr_info_ratelimited("FUSE: compound operation %d has invalid length %u (minimum %zu bytes)\n", + i, op_hdr->len, sizeof(struct fuse_in_header)); + return -EINVAL; + } + + if (offset + op_hdr->len > compound->buffer_pos) { + pr_info_ratelimited("FUSE: compound operation %d extends beyond buffer (offset %zu + length %u > buffer pos %zu)\n", + i, offset, op_hdr->len, compound->buffer_pos); + return -EINVAL; + } + + if (op_hdr->opcode == 0 || op_hdr->opcode == FUSE_COMPOUND) { + pr_info_ratelimited("FUSE: compound operation %d has invalid opcode %u (cannot be 0 or FUSE_COMPOUND)\n", + i, op_hdr->opcode); + return -EINVAL; + } + + if (op_hdr->nodeid == 0) { + pr_info_ratelimited("FUSE: compound operation %d has invalid node ID 0\n", i); + return -EINVAL; + } + + offset += op_hdr->len; + } + + if (offset != compound->buffer_pos) { + pr_info_ratelimited("FUSE: compound buffer size mismatch (calculated %zu bytes, actual %zu bytes)\n", + offset, compound->buffer_pos); + return -EINVAL; + } + + return 0; +} + +/* + * Adds a single operation to the compound request. The operation is serialized + * into the request buffer with its own fuse_in_header. + * + * For operations with page-based payloads (in_pages=true), the page data is + * ignored at the moment. + * + * Returns 0 on success, negative error code on failure. + */ +int fuse_compound_add(struct fuse_compound_req *compound, + struct fuse_args *args) +{ + struct fuse_in_header *hdr; + size_t args_size = 0; + size_t needed_size; + size_t expected_out_size = 0; + size_t page_payload_size = 0; + int i; + + if (!compound || compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS) + return -EINVAL; + + /* Calculate input size - handle page-based arguments separately */ + for (i = 0; i < args->in_numargs; i++) { + /* Last argument with in_pages flag gets data from pages */ + if (unlikely(i == args->in_numargs - 1 && args->in_pages)) { + /* the data handling is not supported at the moment */ + page_payload_size = args->in_args[i].size; + args_size += page_payload_size; + } else { + args_size += args->in_args[i].size; + } + } + + /* Calculate expected output size */ + for (i = 0; i < args->out_numargs; i++) + expected_out_size += args->out_args[i].size; + + needed_size = sizeof(struct fuse_in_header) + args_size; + + /* Expand buffer if needed */ + if (compound->buffer_pos + needed_size > compound->buffer_size) { + size_t new_size = max(compound->buffer_size * 2, + compound->buffer_pos + needed_size); + new_size = round_up(new_size, PAGE_SIZE); + char *new_buffer = kvrealloc(compound->buffer, + compound->buffer_size, + new_size, GFP_KERNEL); + if (!new_buffer) + return -ENOMEM; + compound->buffer = new_buffer; + compound->buffer_size = new_size; + } + + /* Build request header */ + hdr = (struct fuse_in_header *)(compound->buffer + compound->buffer_pos); + memset(hdr, 0, sizeof(*hdr)); + hdr->len = needed_size; + hdr->opcode = args->opcode; + hdr->nodeid = args->nodeid; + hdr->uid = from_kuid(compound->fm->fc->user_ns, current_fsuid()); + hdr->gid = from_kgid(compound->fm->fc->user_ns, current_fsgid()); + hdr->pid = pid_nr_ns(task_pid(current), compound->fm->fc->pid_ns); + hdr->unique = fuse_get_unique(&compound->fm->fc->iq); + compound->buffer_pos += sizeof(*hdr); + + if (args->in_pages) { + /* we have external payload, + * this is not supported at the moment */ + return -EINVAL; + } + + /* Copy operation arguments */ + for (i = 0; i < args->in_numargs; i++) { + memcpy(compound->buffer + compound->buffer_pos, + args->in_args[i].value, args->in_args[i].size); + compound->buffer_pos += args->in_args[i].size; + } + + compound->total_expected_out_size += expected_out_size; + + /* Store args for response parsing */ + compound->op_args[compound->compound_header.count] = args; + + compound->compound_header.count++; + compound->total_size += needed_size; + + return 0; +} + +/* + * Copy response data to fuse_args structure + * + * Returns 0 on success, negative error code on failure. + */ +static void *fuse_copy_response_data(struct fuse_args *args, char *response_data) +{ + size_t copied = 0; + int arg_idx; + + for (arg_idx = 0; arg_idx < args->out_numargs; arg_idx++) { + struct fuse_arg current_arg = args->out_args[arg_idx]; + + /* Last argument with out_pages: copy to pages */ + if (arg_idx == args->out_numargs - 1 && args->out_pages) { + /* external payload (in the last out arg) + * is not supported at the moment + */ + return response_data; + } else { + size_t arg_size = current_arg.size; + if (current_arg.value && arg_size > 0) { + memcpy(current_arg.value, + (char *)response_data + copied, + arg_size); + copied += arg_size; + } + } + } + + return (char*)response_data + copied; +} + +int fuse_compound_get_error(struct fuse_compound_req * compound, + int op_idx) +{ + return compound->op_errors[op_idx]; +} + +/* + * Parse compound response + * + * Parses the compound response and populates the original + * fuse_args structures with the response data. This function is idempotent + * and can be called multiple times safely. + * + * For operations with page-based output (out_pages=true), the response data + * is ignored at the moment. + * + * Returns 0 on success, negative error code on failure. + */ +static int fuse_compound_parse_resp(struct fuse_compound_req *compound, + uint32_t count, void *response, size_t response_size) +{ + int i; + int res = 0; + + /* double parsing prevention will be important + * for large responses most likely out pages. + */ + if (compound->parsed) { + return 0; + } + + void *op_out_data = response; + void *response_end = (char *)response + response_size; + + /* Basic validation */ + if (!response || response_size < sizeof(struct fuse_out_header)) { + return -EIO; + } + + /* Parse each operation response */ + for (i = 0; + i < count && i < compound->result_header.count; i++) { + struct fuse_out_header *op_hdr = op_out_data; + struct fuse_args *args = compound->op_args[i]; + + /* Validate header length */ + if (op_hdr->len < sizeof(struct fuse_out_header)) { + return -EIO; + } + + /* Check if the entire operation response fits in the buffer */ + if ((char *)op_out_data + op_hdr->len > (char *)response_end) { + return -EIO; + } + + if (op_hdr->error != 0) { + compound->op_errors[i] = op_hdr->error; + } + + /* Copy response data */ + if (args && op_hdr->len > sizeof(struct fuse_out_header)) { + op_out_data = fuse_copy_response_data(args, + op_out_data + sizeof(struct fuse_out_header)); + } else { + /* No response data, just advance past the header */ + op_out_data = (char *)op_out_data + op_hdr->len; + } + } + + compound->parsed = true; + return res; +} + +/* + * Send compound request to userspace + * + * Sends the compound request out and parses the response. + * + * -> in_arg[0] -> fuse_compound_in (containing mainly count and flags) + * -> in_arg[1] -> payload + * (containing the serialized requests created by fuse_compound_add) + * + * On success, the response data is copied to the original fuse_args + * structures for each operation. + * + * Returns 0 on success, or the first error code from any operation. + * Returns negative error code if the request itself fails. + */ +ssize_t fuse_compound_send(struct fuse_compound_req *compound) +{ + size_t expected_response_size; + ssize_t ret; + struct fuse_args args = { + .opcode = FUSE_COMPOUND, + .nodeid = 0, + .in_numargs = 2, + .out_numargs = 2, + .out_argvar = true, + }; + + if (!compound) { + pr_info_ratelimited("FUSE: compound request is NULL in fuse_compound_send\n"); + return -EINVAL; + } + + if (compound->compound_header.count == 0) { + pr_info_ratelimited("FUSE: compound request contains no operations\n"); + return -EINVAL; + } + + /* Calculate response buffer size */ + expected_response_size = + compound->total_expected_out_size; + size_t total_buffer_size = expected_response_size + + (compound->compound_header.count * sizeof(struct fuse_out_header)); + + void *resp_payload = kvmalloc(total_buffer_size, GFP_KERNEL | __GFP_ZERO); + + if (!resp_payload) + return -ENOMEM; + /* tell the fuse server how much memory we have allocated */ + compound->compound_header.result_size = expected_response_size; + + args.in_args[0].size = sizeof(compound->compound_header); + args.in_args[0].value = &compound->compound_header; + args.in_args[1].size = compound->buffer_pos; + args.in_args[1].value = compound->buffer; + + args.out_args[0].size = sizeof(compound->result_header); + args.out_args[0].value = &compound->result_header; + args.out_args[1].size = total_buffer_size; + args.out_args[1].value = resp_payload; + + /* Validate request */ + ret = fuse_compound_validate_header(compound); + if (ret) + goto out; + + ret = fuse_compound_request(compound->fm, &args); + if (ret == -ENOSYS) { + goto out; + } + + size_t actual_response_size = args.out_args[1].size; + + /* Validate response size */ + if (actual_response_size < sizeof(struct fuse_compound_out)) { + pr_info_ratelimited("FUSE: compound response too small (%zu bytes, minimum %zu bytes)\n", + actual_response_size, sizeof(struct fuse_compound_out)); + ret = -EINVAL; + goto out; + } + + /* Parse response using actual size */ + ret = fuse_compound_parse_resp(compound, + compound->result_header.count, + ((char *)resp_payload), + actual_response_size); +out: + kvfree(resp_payload); + return ret; +} diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 8c27a8827b1d16..4ba6bb15c90ee8 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -504,6 +504,30 @@ static void fuse_args_to_req(struct fuse_req *req, struct fuse_args *args) req->in.h.unique = fuse_get_unique(&req->fm->fc->iq); } +ssize_t fuse_compound_request(struct fuse_mount *fm, struct fuse_args *args) +{ + struct fuse_req *req; + ssize_t ret; + + req = fuse_get_req(fm, false); + if (IS_ERR(req)) + return PTR_ERR(req); + + fuse_args_to_req(req, args); + + if (!args->noreply) + __set_bit(FR_ISREPLY, &req->flags); + + __fuse_request_send(req); + ret = req->out.h.error; + if (!ret && args->out_argvar) { + BUG_ON(args->out_numargs == 0); + ret = args->out_args[args->out_numargs - 1].size; + } + fuse_put_request(req); + return ret; +} + ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args) { struct fuse_conn *fc = fm->fc; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 0288d626e62d5c..96894003be6a28 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -130,8 +130,76 @@ static void fuse_file_put(struct fuse_file *ff, bool sync) } } +static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid, int flags, + int opcode, struct fuse_file *ff, struct fuse_attr_out *out_attr) +{ + struct fuse_compound_req *compound; + struct fuse_args open_args = {}, getattr_args = {}; + struct fuse_open_in open_in = {}; + struct fuse_getattr_in getattr_in = {}; + struct fuse_open_out open_out; + struct fuse_attr_out attr_out; + int err; + + /* Build compound request with flag to execute in the given order */ + compound = fuse_compound_alloc(fm, 0); + if (IS_ERR(compound)) + return PTR_ERR(compound); + + /* Add OPEN */ + 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; + } + open_args.opcode = opcode; + open_args.nodeid = nodeid; + open_args.in_numargs = 1; + open_args.in_args[0].size = sizeof(open_in); + open_args.in_args[0].value = &open_in; + open_args.out_numargs = 1; + open_args.out_args[0].size = sizeof(struct fuse_open_out); + open_args.out_args[0].value = &open_out; + + err = fuse_compound_add(compound, &open_args); + if (err) + goto out; + + /* Add GETATTR */ + getattr_args.opcode = FUSE_GETATTR; + getattr_args.nodeid = nodeid; + getattr_args.in_numargs = 1; + getattr_args.in_args[0].size = sizeof(getattr_in); + getattr_args.in_args[0].value = &getattr_in; + getattr_args.out_numargs = 1; + getattr_args.out_args[0].size = sizeof(struct fuse_attr_out); + getattr_args.out_args[0].value = &attr_out; + + err = fuse_compound_add(compound, &getattr_args); + if (err) + goto out; + + err = fuse_compound_send(compound); + if (err) + goto out; + + ff->fh = open_out.fh; + ff->open_flags = open_out.open_flags; + + if (out_attr) + *out_attr = attr_out; + +out: + fuse_compound_free(compound); + return err; +} + struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - unsigned int open_flags, bool isdir) + struct inode *inode, + unsigned int open_flags, bool isdir) { struct fuse_conn *fc = fm->fc; struct fuse_file *ff; @@ -147,23 +215,42 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, ff->open_flags = FOPEN_KEEP_CACHE | (isdir ? FOPEN_CACHE_DIR : 0); if (open) { struct fuse_open_out outarg; - int err; + int err = -ENOSYS; + + if (inode && fc->compound_open_getattr) { + struct fuse_attr_out attr_outarg; + err = fuse_compound_open_getattr(fm, nodeid, open_flags, + opcode, ff, &attr_outarg); + if (!err) + fuse_change_attributes(inode, &attr_outarg.attr, NULL, + ATTR_TIMEOUT(&attr_outarg), + fuse_get_attr_version(fc)); + } + if (err == -ENOSYS) { + err = fuse_send_open(fm, nodeid, open_flags, opcode, &outarg); - err = fuse_send_open(fm, nodeid, open_flags, opcode, &outarg); - if (!err) { - ff->fh = outarg.fh; - ff->open_flags = outarg.open_flags; - } else if (err != -ENOSYS) { - fuse_file_free(ff); - return ERR_PTR(err); - } else { - /* No release needed */ - kfree(ff->release_args); - ff->release_args = NULL; - if (isdir) - fc->no_opendir = 1; - else - fc->no_open = 1; + if (!err) { + ff->fh = outarg.fh; + ff->open_flags = outarg.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->release_args); + ff->release_args = NULL; + + /* we don't have open */ + if (isdir) + fc->no_opendir = 1; + else + fc->no_open = 1; + } } } @@ -178,11 +265,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 c71ddee5150606..f3d320a11ad0b9 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -865,6 +865,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; + /** The number of requests waiting for completion */ atomic_t num_waiting; @@ -1187,9 +1190,23 @@ void __exit fuse_ctl_cleanup(void); * Simple request sending that does request allocation and freeing */ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args); +ssize_t fuse_compound_request(struct fuse_mount *fm, struct fuse_args *args); int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); +/** + * Compound request API + */ +struct fuse_compound_req; + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, uint32_t flags); +int fuse_compound_add(struct fuse_compound_req *compound, + struct fuse_args *args); +ssize_t fuse_compound_send(struct fuse_compound_req *compound); +int fuse_compound_get_error(struct fuse_compound_req * compound, + int op_idx); +void fuse_compound_free(struct fuse_compound_req *compound); + /** * End a finished request */ @@ -1442,10 +1459,18 @@ 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); +#ifdef CONFIG_MIGRATION +int fuse_migrate_folio(struct address_space *mapping, struct folio *dst, + struct folio *src, enum migrate_mode mode); +#else +#define fuse_migrate_folio NULL +#endif + #ifdef CONFIG_SYSCTL extern int fuse_sysctl_register(void); extern void fuse_sysctl_unregister(void); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 1d65600d32b444..3393e7087bfb2b 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1024,6 +1024,12 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm, fc->initialized = 0; fc->connected = 1; fc->dlm = 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 4ebabebaa89866..d6bbb090a99a7f 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -435,7 +435,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) diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 437f349076aed8..c7bfb89acbc49a 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -645,6 +645,13 @@ enum fuse_opcode { /* Operations which have not been merged into upstream */ FUSE_DLM_WB_LOCK = 100, + /* A compound request works like multiple simple requests. + * This is a special case for calls that can be combined atomic on the + * fuse server. If the server actually does atomically execute the command is + * left to the fuse server implementation. + */ + FUSE_COMPOUND = 101, + /* CUSE specific operations */ CUSE_INIT = 4096, @@ -1203,6 +1210,7 @@ struct fuse_dlm_lock_in { uint32_t reserved; }; + /** * struct fuse_dlm_lock_out - Lock response * @locksize: how many bytes where locked by the call @@ -1215,6 +1223,36 @@ struct fuse_dlm_lock_out { uint64_t reserved; }; +/* + * 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; +}; + +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compound */ + /** * Size of the ring buffer header */