From 6103f417d6acb5e1d86ee844b700de37fe2c505e Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Mon, 22 Dec 2025 10:09:39 +0100 Subject: [PATCH 1/4] fuse: improve readability and simplify code flow no functional change Signed-off-by: Horst Birthelmer --- fs/fuse/compound.c | 188 +++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 93 deletions(-) diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c index dc76dbe01bcaf0..5d950b52ebd32b 100644 --- a/fs/fuse/compound.c +++ b/fs/fuse/compound.c @@ -13,8 +13,7 @@ /* * Compound request */ -struct fuse_compound_req -{ +struct fuse_compound_req { struct fuse_mount *fm; struct fuse_compound_in compound_header; struct fuse_compound_out result_header; @@ -59,10 +58,11 @@ struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, */ void fuse_compound_free(struct fuse_compound_req *compound) { - if (compound) { - kvfree(compound->buffer); - kfree(compound); - } + if (!compound) + return; + + kvfree(compound->buffer); + kfree(compound); } /* @@ -149,23 +149,18 @@ int fuse_compound_add(struct fuse_compound_req *compound, 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; - } - } + /* Page-based payloads are not supported */ + if (args->in_pages) + return -EINVAL; + + /* Calculate input size */ + for (i = 0; i < args->in_numargs; i++) + args_size += args->in_args[i].size; /* Calculate expected output size */ for (i = 0; i < args->out_numargs; i++) @@ -177,10 +172,12 @@ int fuse_compound_add(struct fuse_compound_req *compound, if (compound->buffer_pos + needed_size > compound->buffer_size) { size_t new_size = max(compound->buffer_size * 2, compound->buffer_pos + needed_size); + char *new_buffer; + new_size = round_up(new_size, PAGE_SIZE); - char *new_buffer = kvrealloc(compound->buffer, - compound->buffer_size, - new_size, GFP_KERNEL); + new_buffer = kvrealloc(compound->buffer, + compound->buffer_size, + new_size, GFP_KERNEL); if (!new_buffer) return -ENOMEM; compound->buffer = new_buffer; @@ -199,12 +196,6 @@ int fuse_compound_add(struct fuse_compound_req *compound, 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, @@ -238,30 +229,63 @@ static void *fuse_copy_response_data(struct fuse_args *args, char *response_data /* 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) + /* + * 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; - } + } + + 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; + return (char *)response_data + copied; } -int fuse_compound_get_error(struct fuse_compound_req * compound, - int op_idx) +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx) { return compound->op_errors[op_idx]; } +/* + * Parse a single operation response from the compound response buffer + * + * Returns pointer to the next operation response on success, or NULL on error. + */ +static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound, + int op_index, void *op_out_data, + void *response_end) +{ + struct fuse_out_header *op_hdr = op_out_data; + struct fuse_args *args = compound->op_args[op_index]; + + /* Validate header length */ + if (op_hdr->len < sizeof(struct fuse_out_header)) + return NULL; + + /* Check if the entire operation response fits in the buffer */ + if ((char *)op_out_data + op_hdr->len > (char *)response_end) + return NULL; + + if (op_hdr->error != 0) + compound->op_errors[op_index] = op_hdr->error; + + /* Copy response data */ + if (args && op_hdr->len > sizeof(struct fuse_out_header)) + return fuse_copy_response_data(args, + op_out_data + sizeof(struct fuse_out_header)); + + /* No response data, just advance past the header */ + return (char *)op_out_data + op_hdr->len; +} + /* * Parse compound response * @@ -275,58 +299,35 @@ int fuse_compound_get_error(struct fuse_compound_req * compound, * 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) + uint32_t count, void *response, + size_t response_size) { + void *op_out_data = response; + void *response_end = (char *)response + response_size; int i; - int res = 0; - /* double parsing prevention will be important + /* + * Double parsing prevention will be important * for large responses most likely out pages. */ - if (compound->parsed) { + 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)) { + 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) { + for (i = 0; i < count && i < compound->result_header.count; i++) { + op_out_data = fuse_compound_parse_one_op(compound, i, + op_out_data, + response_end); + if (!op_out_data) return -EIO; - } - - 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; + return 0; } /* @@ -341,13 +342,11 @@ static int fuse_compound_parse_resp(struct fuse_compound_req *compound, * 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 0 on success. * 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, @@ -355,6 +354,11 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) .out_numargs = 2, .out_argvar = true, }; + size_t expected_response_size; + size_t total_buffer_size; + size_t actual_response_size; + void *resp_payload; + ssize_t ret; if (!compound) { pr_info_ratelimited("FUSE: compound request is NULL in fuse_compound_send\n"); @@ -367,16 +371,15 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) } /* Calculate response buffer size */ - expected_response_size = - compound->total_expected_out_size; - size_t total_buffer_size = expected_response_size + + expected_response_size = compound->total_expected_out_size; + 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); - + 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 */ + + /* 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); @@ -395,25 +398,24 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) goto out; ret = fuse_compound_request(compound->fm, &args); - if (ret == -ENOSYS) { + if (ret == -ENOSYS) goto out; - } - size_t actual_response_size = args.out_args[1].size; + 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)); + 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); + ret = fuse_compound_parse_resp(compound, compound->result_header.count, + (char *)resp_payload, + actual_response_size); out: kvfree(resp_payload); return ret; From 6785c82d764431ac5f0099cb387746afd371d0a8 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Mon, 22 Dec 2025 10:11:00 +0100 Subject: [PATCH 2/4] fuse: improve error handling in open+getattr compound Signed-off-by: Horst Birthelmer --- fs/fuse/file.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 92fd31afe947df..0cea41dc4afaf0 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -135,6 +135,7 @@ 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_conn *fc = fm->fc; struct fuse_compound_req *compound; struct fuse_args open_args = {}, getattr_args = {}; struct fuse_open_in open_in = {}; @@ -143,7 +144,7 @@ static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid, int fla struct fuse_attr_out attr_out; int err; - /* Build compound request with flag to execute in the given order */ + /* Build compound request */ compound = fuse_compound_alloc(fm, 0); if (IS_ERR(compound)) return PTR_ERR(compound); @@ -157,6 +158,7 @@ static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid, int fla (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; @@ -185,9 +187,32 @@ static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid, int fla goto out; err = fuse_compound_send(compound); + if (err == -ENOSYS) { + fc->compound_open_getattr = 0; + goto out; + } + + if (err) + goto out; + + /* Check individual operation errors */ + err = fuse_compound_get_error(compound, 0); if (err) goto out; + err = fuse_compound_get_error(compound, 1); + if (err) { + /* OPEN succeeded but GETATTR failed - need to release the handle */ + struct fuse_release_args *ra = ff->release_args; + + if (ra) { + ra->inarg.fh = open_out.fh; + ra->inarg.flags = open_in.flags; + fuse_file_put(ff, true); + } + goto out; + } + ff->fh = open_out.fh; ff->open_flags = open_out.open_flags; From 8fcce62604488f40e80d5931f4d8ba90a47fffe0 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Sat, 3 Jan 2026 14:05:18 +0100 Subject: [PATCH 3/4] fuse: fix error handling for compounds in case fuse_compound_request() returns some other error than ENOSYS. The returned error got overwritten. Signed-off-by: Horst Birthelmer --- fs/fuse/compound.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c index 5d950b52ebd32b..f06fd66764ad4f 100644 --- a/fs/fuse/compound.c +++ b/fs/fuse/compound.c @@ -398,7 +398,7 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) goto out; ret = fuse_compound_request(compound->fm, &args); - if (ret == -ENOSYS) + if (ret < 0) goto out; actual_response_size = args.out_args[1].size; From b97043e769aad3658affb84e8bc1c3d91cda59b8 Mon Sep 17 00:00:00 2001 From: Horst Birthelmer Date: Thu, 8 Jan 2026 20:22:06 +0100 Subject: [PATCH 4/4] fuse: simplify compound commands Simplify fuse_compound_req to hold only the pointers to the added fuse args and the request housekeeping. Simplify open+getattr call by using helper functions to fill out the fuse request parameters Signed-off-by: Horst Birthelmer --- fs/fuse/compound.c | 347 ++++++++++++--------------------------------- fs/fuse/dir.c | 9 +- fs/fuse/file.c | 117 ++++++++------- fs/fuse/fuse_i.h | 13 +- 4 files changed, 155 insertions(+), 331 deletions(-) diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c index f06fd66764ad4f..5d84e3558a06f8 100644 --- a/fs/fuse/compound.c +++ b/fs/fuse/compound.c @@ -11,30 +11,19 @@ #include "fuse_i.h" /* - * Compound request + * Compound request builder and state tracker and args pointer storage */ struct fuse_compound_req { struct fuse_mount *fm; struct fuse_compound_in compound_header; struct fuse_compound_out result_header; - 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 */ + /* Per-operation error codes */ 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 *fuse_compound_alloc(struct fuse_mount *fm, u32 flags) { struct fuse_compound_req *compound; @@ -44,209 +33,43 @@ struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, 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) - return; - - 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; + return compound; } -/* - * 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; - int i; - - if (!compound || compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS) + if (!compound || + compound->compound_header.count >= FUSE_MAX_COMPOUND_OPS) return -EINVAL; - /* Page-based payloads are not supported */ if (args->in_pages) return -EINVAL; - /* Calculate input size */ - for (i = 0; i < args->in_numargs; i++) - 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); - char *new_buffer; - - new_size = round_up(new_size, PAGE_SIZE); - 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); - - /* 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) +static void *fuse_copy_response_per_req(struct fuse_args *args, + char *resp) { + int i; 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; - } + for (i = 0; i < args->out_numargs; i++) { + struct fuse_arg current_arg = args->out_args[i]; size_t arg_size = current_arg.size; if (current_arg.value && arg_size > 0) { memcpy(current_arg.value, - (char *)response_data + copied, - arg_size); + (char *)resp + copied, arg_size); copied += arg_size; } } - return (char *)response_data + copied; + return (char *)resp + copied; } int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx) @@ -254,11 +77,6 @@ int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx) return compound->op_errors[op_idx]; } -/* - * Parse a single operation response from the compound response buffer - * - * Returns pointer to the next operation response on success, or NULL on error. - */ static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound, int op_index, void *op_out_data, void *response_end) @@ -266,7 +84,6 @@ static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound, struct fuse_out_header *op_hdr = op_out_data; struct fuse_args *args = compound->op_args[op_index]; - /* Validate header length */ if (op_hdr->len < sizeof(struct fuse_out_header)) return NULL; @@ -277,47 +94,25 @@ static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound, if (op_hdr->error != 0) compound->op_errors[op_index] = op_hdr->error; - /* Copy response data */ if (args && op_hdr->len > sizeof(struct fuse_out_header)) - return fuse_copy_response_data(args, - op_out_data + sizeof(struct fuse_out_header)); + return fuse_copy_response_per_req(args, op_out_data + + sizeof(struct fuse_out_header)); /* No response data, just advance past the header */ return (char *)op_out_data + op_hdr->len; } -/* - * 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, + u32 count, void *response, size_t response_size) { void *op_out_data = response; void *response_end = (char *)response + response_size; int i; - /* - * Double parsing prevention will be important - * for large responses most likely out pages. - */ - if (compound->parsed) - return 0; - - /* 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++) { op_out_data = fuse_compound_parse_one_op(compound, i, op_out_data, @@ -326,25 +121,9 @@ static int fuse_compound_parse_resp(struct fuse_compound_req *compound, return -EIO; } - compound->parsed = true; return 0; } -/* - * 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. - * Returns negative error code if the request itself fails. - */ ssize_t fuse_compound_send(struct fuse_compound_req *compound) { struct fuse_args args = { @@ -354,14 +133,18 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) .out_numargs = 2, .out_argvar = true, }; - size_t expected_response_size; - size_t total_buffer_size; + size_t resp_buffer_size; size_t actual_response_size; + size_t buffer_pos; + size_t total_expected_out_size; + void *buffer = NULL; void *resp_payload; ssize_t ret; + int i; if (!compound) { - pr_info_ratelimited("FUSE: compound request is NULL in fuse_compound_send\n"); + pr_info_ratelimited("FUSE: compound request is NULL in %s\n", + __func__); return -EINVAL; } @@ -370,40 +153,85 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) return -EINVAL; } - /* Calculate response buffer size */ - expected_response_size = compound->total_expected_out_size; - total_buffer_size = expected_response_size + - (compound->compound_header.count * sizeof(struct fuse_out_header)); + buffer_pos = 0; + total_expected_out_size = 0; + + for (i = 0; i < compound->compound_header.count; i++) { + struct fuse_args *op_args = compound->op_args[i]; + size_t needed_size = sizeof(struct fuse_in_header); + int j; - resp_payload = kvmalloc(total_buffer_size, GFP_KERNEL | __GFP_ZERO); - if (!resp_payload) + for (j = 0; j < op_args->in_numargs; j++) + needed_size += op_args->in_args[j].size; + + buffer_pos += needed_size; + + for (j = 0; j < op_args->out_numargs; j++) + total_expected_out_size += op_args->out_args[j].size; + } + + buffer = kvmalloc(buffer_pos, GFP_KERNEL); + if (!buffer) return -ENOMEM; - /* Tell the fuse server how much memory we have allocated */ - compound->compound_header.result_size = expected_response_size; + buffer_pos = 0; + for (i = 0; i < compound->compound_header.count; i++) { + struct fuse_args *op_args = compound->op_args[i]; + struct fuse_in_header *hdr; + size_t needed_size = sizeof(struct fuse_in_header); + int j; + + for (j = 0; j < op_args->in_numargs; j++) + needed_size += op_args->in_args[j].size; + + hdr = (struct fuse_in_header *)(buffer + buffer_pos); + memset(hdr, 0, sizeof(*hdr)); + hdr->len = needed_size; + hdr->opcode = op_args->opcode; + hdr->nodeid = op_args->nodeid; + hdr->uid = from_kuid(compound->fm->fc->user_ns, + current_fsuid()); + hdr->gid = from_kgid(compound->fm->fc->user_ns, + current_fsgid()); + hdr->pid = pid_nr_ns(task_pid(current), + compound->fm->fc->pid_ns); + buffer_pos += sizeof(*hdr); + + for (j = 0; j < op_args->in_numargs; j++) { + memcpy(buffer + buffer_pos, op_args->in_args[j].value, + op_args->in_args[j].size); + buffer_pos += op_args->in_args[j].size; + } + } + + resp_buffer_size = total_expected_out_size + + (compound->compound_header.count * + sizeof(struct fuse_out_header)); + + resp_payload = kvmalloc(resp_buffer_size, GFP_KERNEL | __GFP_ZERO); + if (!resp_payload) { + ret = -ENOMEM; + goto out_free_buffer; + } + + compound->compound_header.result_size = total_expected_out_size; args.in_args[0].size = sizeof(compound->compound_header); args.in_args[0].value = &compound->compound_header; - args.in_args[1].size = compound->buffer_pos; - args.in_args[1].value = compound->buffer; + args.in_args[1].size = buffer_pos; + args.in_args[1].value = buffer; args.out_args[0].size = sizeof(compound->result_header); args.out_args[0].value = &compound->result_header; - args.out_args[1].size = total_buffer_size; + args.out_args[1].size = resp_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); + ret = fuse_simple_request(compound->fm, &args); if (ret < 0) goto out; 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, @@ -412,11 +240,12 @@ ssize_t fuse_compound_send(struct fuse_compound_req *compound) 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); +out_free_buffer: + kvfree(buffer); return ret; } diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 597fb0d33ef7b2..db8046716077fa 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1265,14 +1265,7 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat, 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 0cea41dc4afaf0..55d018283f0201 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -22,6 +22,39 @@ #include #include +/* + * Helper function to initialize fuse_args for OPEN/OPENDIR operations + */ +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, + struct fuse_open_in *inarg, struct fuse_open_out *outarg) +{ + args->opcode = opcode; + args->nodeid = nodeid; + args->in_numargs = 1; + args->in_args[0].size = sizeof(*inarg); + args->in_args[0].value = inarg; + args->out_numargs = 1; + args->out_args[0].size = sizeof(*outarg); + args->out_args[0].value = outarg; +} + +/* + * Helper function to initialize fuse_args for GETATTR operations + */ +void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid, + struct fuse_getattr_in *inarg, + struct fuse_attr_out *outarg) +{ + args->opcode = FUSE_GETATTR; + args->nodeid = nodeid; + args->in_numargs = 1; + args->in_args[0].size = sizeof(*inarg); + args->in_args[0].value = inarg; + args->out_numargs = 1; + args->out_args[0].size = sizeof(*outarg); + args->out_args[0].value = outarg; +} + static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, unsigned int open_flags, int opcode, struct fuse_open_out *outargp) @@ -39,14 +72,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); } @@ -132,95 +158,60 @@ 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) +static int fuse_compound_open_getattr(struct fuse_mount *fm, u64 nodeid, + int flags, int opcode, + struct fuse_file *ff, + struct fuse_attr_out *outattrp, + struct fuse_open_out *outopenp) { - struct fuse_conn *fc = fm->fc; struct fuse_compound_req *compound; - struct fuse_args open_args = {}, getattr_args = {}; + struct fuse_args open_args = {}; + struct fuse_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 */ 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.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; + fuse_open_args_fill(&open_args, nodeid, opcode, &open_in, outopenp); 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; + 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 == -ENOSYS) { - fc->compound_open_getattr = 0; - goto out; - } - if (err) goto out; - /* Check individual operation errors */ err = fuse_compound_get_error(compound, 0); if (err) goto out; err = fuse_compound_get_error(compound, 1); - if (err) { - /* OPEN succeeded but GETATTR failed - need to release the handle */ - struct fuse_release_args *ra = ff->release_args; - - if (ra) { - ra->inarg.fh = open_out.fh; - ra->inarg.flags = open_in.flags; - fuse_file_put(ff, true); - } + if (err) goto out; - } - - ff->fh = open_out.fh; - ff->open_flags = open_out.open_flags; - if (out_attr) - *out_attr = attr_out; + ff->fh = outopenp->fh; + ff->open_flags = outopenp->open_flags; out: - fuse_compound_free(compound); + kfree(compound); return err; } @@ -246,16 +237,20 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, 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); + opcode, ff, + &attr_outarg, &outarg); + if (err == -ENOSYS) + fc->compound_open_getattr = 0; if (!err) - fuse_change_attributes(inode, &attr_outarg.attr, NULL, + 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); - if (!err) { ff->fh = outarg.fh; ff->open_flags = outarg.open_flags; @@ -263,7 +258,7 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, } if (err) { - if(err != -ENOSYS) { + if (err != -ENOSYS) { /* err is not ENOSYS */ fuse_file_free(ff); return ERR_PTR(err); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index c4f644e3d51bd4..4f0474e2e31def 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1122,6 +1122,14 @@ struct fuse_io_args { void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos, size_t count, int opcode); +/* + * Helper functions to initialize fuse_args for common operations + */ +void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int opcode, + struct fuse_open_in *inarg, struct fuse_open_out *outarg); +void fuse_getattr_args_fill(struct fuse_args *args, u64 nodeid, + struct fuse_getattr_in *inarg, + struct fuse_attr_out *outarg); struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release); void fuse_file_free(struct fuse_file *ff); @@ -1198,7 +1206,6 @@ 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); @@ -1213,7 +1220,6 @@ int fuse_compound_add(struct fuse_compound_req *compound, 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 @@ -1468,7 +1474,8 @@ 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, struct inode *inode, - unsigned int open_flags, bool isdir); + 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);