From nobody Tue Apr 7 17:15:01 2026 Received: from smtp03-ext2.udag.de (smtp03-ext2.udag.de [62.146.106.30]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 67FF53603CC; Thu, 26 Feb 2026 16:44:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.30 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124246; cv=none; b=AcPTiWj0n/ypZD+WuA5fQQhvD6Mh668hljFHnQ0orOAGvHjZ0gS83Q1cQTP6ch2u7SYGMAeldB7TZ7Y5j2C4JdjlTAVLj3xPzsDv0hihmo2wu/Q53Jf7cKJFWYAWibY/6tRRyxhyuew5fRRNU2spr3zScbZ0NIDXl293ZOy7X9Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124246; c=relaxed/simple; bh=EjAzSgzjObUM5dhOlxitVf8/fAu+sdE0ybuv8LpQfm4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=gqf+tYugqJ1xg7H/CFNEFKrXAQol5GxZApLaiqZG1hCc2rfnUYGpVftPOmEnqRsAU/xDnP792lxRyoKPkoR6hpfnxk0ozuQ7WqXoHPggPGCL9fnOVPqlR22pbsfz0pTXQb6F6BR+Ahqxyr8lOnf8GusCnGInMbodhAdRRxd5AQE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com; spf=pass smtp.mailfrom=birthelmer.com; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b=dNdryYmp; arc=none smtp.client-ip=62.146.106.30 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b="dNdryYmp" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp03-ext2.udag.de (Postfix) with ESMTPA id 28FD8E0354; Thu, 26 Feb 2026 17:43:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1772124237; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=BUEBsyMed4N11PbJMO0GR1OSz5oxIcmpiXGAsRr+jQI=; b=dNdryYmpF799ZJC+I1Wa7W1FMBlSkAkjuyn5PVhaDkzcl6iCvBcXIvIeIf1PvMsv5NId6y ec1/x/Bn46e6aqUYZcjHIiMaeuXmCHav38nvXq+K+dIoqkqx8W6S/E715fv0dyE6pEytb2 gNj6KkHpIwvpcQZ0RZE0c518XX2rY8h0lBhfuwvj0J9P85Q1mXQKho10kP3R/oqbxsRPTe a7R/ncsF4n6b82gKz/p8YUZMnUD3o1/E8ktgtKYQ5B/rXO/rwqGMNgDsV5VG8eL50yPpXO D15R607dHBwMqOrQd7PvFQn1U9M61DSBU0o5prmhKjjKkad9QhbOFLL2zm9rAg== Authentication-Results: smtp03-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Thu, 26 Feb 2026 17:43:53 +0100 Subject: [PATCH v6 1/3] fuse: add compound command to combine multiple requests Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260226-fuse-compounds-upstream-v6-1-8585c5fcd2fc@ddn.com> References: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> In-Reply-To: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> To: Miklos Szeredi , Bernd Schubert , Joanne Koong , Luis Henriques Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Horst Birthelmer X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1772124235; l=13452; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=zB9dxl9WuMwc9QRTsSIuZYa0kBA+Bipx0HoYFgbETZc=; b=RgsgynSd4s4SFAIfwl12bTondWfPKABtUjJptOiUSr1RkBU11zyZU8Mi/HhyHMc+hh9RkfJFG 0I+E6o3LPtPCwJ2Ve8WZOo9RTTpNMLhasnj79U1eLNwRcOq8SICbVBo X-Developer-Key: i=hbirthelmer@ddn.com; a=ed25519; pk=v3BVDFoy16EzgHZ23ObqW+kbpURtjrwxgKu8YNDKjGg= From: Horst Birthelmer For a FUSE_COMPOUND we add a small header that informs the fuse server how much buffer memory the kernel has for the result. This will make the interpretation in libfuse easier, since we can preallocate the whole result and work on the return buffer. Then we append the requests that belong to this compound. The API for the compound command has: fuse_compound_alloc() fuse_compound_add() fuse_compound_send() fuse_compound_free() Signed-off-by: Horst Birthelmer --- fs/fuse/Makefile | 2 +- fs/fuse/compound.c | 308 ++++++++++++++++++++++++++++++++++++++++++= ++++ fs/fuse/fuse_i.h | 39 ++++++ include/uapi/linux/fuse.h | 52 ++++++++ 4 files changed, 400 insertions(+), 1 deletion(-) diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 22ad9538dfc4b80c6d9b52235bdfead6a6567ae4..4c09038ef995d1b9133c2b6871b= 97b280a4693b0 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) +=3D cuse.o obj-$(CONFIG_VIRTIO_FS) +=3D virtiofs.o =20 fuse-y :=3D trace.o # put trace.o first so we see ftrace errors sooner -fuse-y +=3D dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o i= octl.o +fuse-y +=3D dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o i= octl.o compound.o fuse-y +=3D iomode.o fuse-$(CONFIG_FUSE_DAX) +=3D dax.o fuse-$(CONFIG_FUSE_PASSTHROUGH) +=3D passthrough.o backing.o diff --git a/fs/fuse/compound.c b/fs/fuse/compound.c new file mode 100644 index 0000000000000000000000000000000000000000..68f30123f39b244dd82b8357170= 77cc271518e14 --- /dev/null +++ b/fs/fuse/compound.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025-2026 + * + * Compound operations for FUSE - batch multiple operations into a single + * request to reduce round trips between kernel and userspace. + */ + +#include "fuse_i.h" + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, + u32 max_count, u32 flags) +{ + struct fuse_compound_req *compound; + + if (max_count =3D=3D 0) + return NULL; + + compound =3D kzalloc(sizeof(*compound), GFP_KERNEL); + if (!compound) + return NULL; + + compound->max_count =3D max_count; + compound->count =3D 0; + compound->fm =3D fm; + compound->compound_header.flags =3D flags; + + compound->op_errors =3D kcalloc(max_count, sizeof(int), GFP_KERNEL); + if (!compound->op_errors) + goto out_free_compound; + + compound->op_args =3D kcalloc(max_count, sizeof(struct fuse_args *), + GFP_KERNEL); + if (!compound->op_args) + goto out_free_op_errors; + + compound->op_converters =3D kcalloc(max_count, + sizeof(int (*)(struct fuse_compound_req *, unsigned int)), + GFP_KERNEL); + if (!compound->op_converters) + goto out_free_op_args; + + return compound; + +out_free_op_args: + kfree(compound->op_args); +out_free_op_errors: + kfree(compound->op_errors); +out_free_compound: + kfree(compound); + return NULL; +} + +void fuse_compound_free(struct fuse_compound_req *compound) +{ + kfree(compound->op_errors); + kfree(compound->op_args); + kfree(compound->op_converters); + kfree(compound); +} + +int fuse_compound_add(struct fuse_compound_req *compound, + struct fuse_args *args, + int (*converter)(struct fuse_compound_req *compound, + unsigned int index)) +{ + if (!compound || compound->count >=3D compound->max_count) + return -EINVAL; + + if (args->in_pages) + return -EINVAL; + + compound->op_args[compound->count] =3D args; + compound->op_converters[compound->count] =3D converter; + compound->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 =3D 0; i < args->out_numargs; i++) { + arg =3D &args->out_args[i]; + memcpy(arg->value, resp, arg->size); + resp +=3D arg->size; + } +} + +static char *fuse_compound_parse_one_op(struct fuse_compound_req *compound, + char *response, + char *response_end, + int op_count) +{ + struct fuse_out_header *op_hdr =3D (struct fuse_out_header *)response; + struct fuse_args *args; + + if (op_hdr->len < sizeof(struct fuse_out_header)) + return NULL; + + if (response + op_hdr->len > response_end) + return NULL; + + if (op_count >=3D compound->max_count) + return NULL; + + if (op_hdr->error) { + compound->op_errors[op_count] =3D op_hdr->error; + } else { + args =3D compound->op_args[op_count]; + 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, char *response_end) +{ + int op_count =3D 0; + + while (response < response_end) { + response =3D fuse_compound_parse_one_op(compound, response, + response_end, op_count); + if (!response) + return -EIO; + op_count++; + } + + return 0; +} + +static int fuse_handle_compound_results(struct fuse_compound_req *compound, + struct fuse_args *args) +{ + size_t actual_response_size; + size_t buffer_size; + char *resp_payload_buffer; + int ret; + + buffer_size =3D compound->compound_header.result_size + + compound->count * sizeof(struct fuse_out_header); + + resp_payload_buffer =3D args->out_args[1].value; + actual_response_size =3D args->out_args[1].size; + + if (actual_response_size <=3D buffer_size) { + ret =3D fuse_compound_parse_resp(compound, + (char *)resp_payload_buffer, + resp_payload_buffer + + actual_response_size); + } else { + /* FUSE server sent more data than expected */ + ret =3D -EIO; + } + + return ret; +} + +/* + * 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, + unsigned int index) +{ + struct fuse_in_header *hdr; + size_t needed_size =3D sizeof(struct fuse_in_header); + int j; + + for (j =3D 0; j < op_args->in_numargs; j++) + needed_size +=3D op_args->in_args[j].size; + + hdr =3D (struct fuse_in_header *)buffer_pos; + hdr->unique =3D index; + hdr->len =3D needed_size; + hdr->opcode =3D op_args->opcode; + hdr->nodeid =3D op_args->nodeid; + buffer_pos +=3D sizeof(*hdr); + + for (j =3D 0; j < op_args->in_numargs; j++) { + memcpy(buffer_pos, op_args->in_args[j].value, + op_args->in_args[j].size); + buffer_pos +=3D op_args->in_args[j].size; + } + + return buffer_pos; +} + +static ssize_t fuse_compound_fallback_separate(struct fuse_compound_req *c= ompound) +{ + unsigned int req_count =3D compound->count; + ssize_t ret =3D 0; + unsigned int i; + + /* Try separate requests */ + for (i =3D 0; i < req_count; i++) { + /* fill the current args from the already received responses */ + if (compound->op_converters[i]) + ret =3D compound->op_converters[i](compound, i); + + ret =3D fuse_simple_request(compound->fm, compound->op_args[i]); + if (ret < 0) { + compound->op_errors[i] =3D ret; + if (!(compound->compound_header.flags & FUSE_COMPOUND_CONTINUE)) + break; + } + } + + return ret; +} + +ssize_t fuse_compound_send(struct fuse_compound_req *compound) +{ + struct fuse_conn *fc =3D compound->fm->fc; + struct fuse_args args =3D { + .opcode =3D FUSE_COMPOUND, + .in_numargs =3D 2, + .out_numargs =3D 2, + .out_argvar =3D true, + }; + unsigned int req_count =3D compound->count; + size_t total_expected_out_size =3D 0; + size_t buffer_size =3D 0; + void *resp_payload_buffer; + char *buffer_pos; + void *buffer =3D NULL; + ssize_t ret; + unsigned int i, j; + + for (i =3D 0; i < req_count; i++) { + struct fuse_args *op_args =3D compound->op_args[i]; + size_t needed_size =3D sizeof(struct fuse_in_header); + + for (j =3D 0; j < op_args->in_numargs; j++) + needed_size +=3D op_args->in_args[j].size; + + buffer_size +=3D needed_size; + + for (j =3D 0; j < op_args->out_numargs; j++) + total_expected_out_size +=3D op_args->out_args[j].size; + } + + buffer =3D kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer_pos =3D buffer; + for (i =3D 0; i < req_count; i++) { + if (compound->op_converters[i]) { + ret =3D compound->op_converters[i](compound, i); + if (ret < 0) + goto out_free_buffer; + } + + buffer_pos =3D fuse_compound_build_one_op(fc, + compound->op_args[i], + buffer_pos, i); + } + + compound->compound_header.result_size =3D total_expected_out_size; + + args.in_args[0].size =3D sizeof(compound->compound_header); + args.in_args[0].value =3D &compound->compound_header; + args.in_args[1].size =3D buffer_size; + args.in_args[1].value =3D buffer; + + buffer_size =3D total_expected_out_size + + req_count * sizeof(struct fuse_out_header); + + resp_payload_buffer =3D kzalloc(buffer_size, GFP_KERNEL); + if (!resp_payload_buffer) { + ret =3D -ENOMEM; + goto out_free_buffer; + } + + args.out_args[0].size =3D sizeof(compound->result_header); + args.out_args[0].value =3D &compound->result_header; + args.out_args[1].size =3D buffer_size; + args.out_args[1].value =3D resp_payload_buffer; + + ret =3D fuse_simple_request(compound->fm, &args); + if (ret < 0) + goto fallback_separate; + + ret =3D fuse_handle_compound_results(compound, &args); + if (ret =3D=3D 0) + goto out; + +fallback_separate: + /* Kernel tries to fallback to separate requests */ + if (!(compound->compound_header.flags & FUSE_COMPOUND_ATOMIC)) + ret =3D fuse_compound_fallback_separate(compound); + +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..e46315aa428c9d0e704c62a0b80= 811172c5ec9c1 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1273,6 +1273,45 @@ static inline ssize_t fuse_simple_idmap_request(stru= ct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); =20 +/* + * 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; + + struct fuse_args **op_args; + + /* + * Every op can add a converter function to construct the ops args from + * the already received responses. + */ + int (**op_converters)(struct fuse_compound_req *compound, + unsigned int index); + int *op_errors; + + unsigned int max_count; + unsigned int count; +}; + +/* + * Compound request API + */ +ssize_t fuse_compound_send(struct fuse_compound_req *compound); + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, + u32 max_count, u32 flags); +int fuse_compound_add(struct fuse_compound_req *compound, + struct fuse_args *args, + int (*converter)(struct fuse_compound_req *compound, + unsigned int index)); +void fuse_compound_free(struct fuse_compound_req *compound); +static inline int fuse_compound_get_error(struct fuse_compound_req *compou= nd, int op_idx) +{ + return compound->op_errors[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..d43bffd1ccbe2b3d144864407d6= 0ff7a48db53ed 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -664,6 +664,13 @@ enum fuse_opcode { FUSE_STATX =3D 52, FUSE_COPY_FILE_RANGE_64 =3D 53, =20 + /* 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 =3D 54, + /* CUSE specific operations */ CUSE_INIT =3D 4096, =20 @@ -1245,6 +1252,51 @@ struct fuse_supp_groups { uint32_t groups[]; }; =20 +/* + * This is a hint to the fuse server that all requests are complete and it= can + * use automatic decoding and sequential processing from libfuse. + */ +#define FUSE_COMPOUND_SEPARABLE (1 << 0) +/* + * This will be used by the kernel to continue on + * even after one of the requests fail. + */ +#define FUSE_COMPOUND_CONTINUE (1 << 1) +/* + * This flags the compound as atomic, which + * means that the operation has to be interpreted + * atomically and be directly supported by the fuse server + * itself. + */ +#define FUSE_COMPOUND_ATOMIC (1 << 2) + +/* + * Compound request header + * + * This header is followed by the fuse requests + */ +struct fuse_compound_in { + uint32_t flags; /* Compound flags */ + + /* Total size of all results expected from the fuse server. + * This is needed for preallocating the whole result for all + * commands in the fuse server. + */ + uint32_t result_size; + uint64_t reserved; +}; + +/* + * Compound response header + * + * This header is followed by complete fuse responses + */ +struct fuse_compound_out { + uint32_t flags; /* Result flags */ + uint32_t padding; + uint64_t reserved; +}; + /** * Size of the ring buffer header */ --=20 2.53.0 From nobody Tue Apr 7 17:15:01 2026 Received: from smtp03-ext2.udag.de (smtp03-ext2.udag.de [62.146.106.30]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AF0A736B04B; Thu, 26 Feb 2026 16:44:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.30 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124249; cv=none; b=m513UhwYUpAnmr58cXJnibdKsiykYLo1mmpIKEhSWrTg13Z7Q79ECs1HvoQE53F5KWXONamITGmrWj27waEl/73AkdW3Pr+SYHh13pW6g3UmBxX6Ifzo+30jcWBDkczuO7LdaEhptgFYANPdjSeFRHtDBbofBbpj2j6XJO5ePgU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124249; c=relaxed/simple; bh=0kzc9erjXODarwfpNMQnQ3iZ7a8L3MxgsLdlgnpU7Zo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=R4lX6qdg13DpFafV8Jljsd6j5A0R7q07LA/rz8sEpulEjSPX2oKOqaIajffuhYiaIqZxZZa2wC5Plgrr9rUsNbDLOWqQOR/QK14s5VHjBvFJYw/L8OYO9BtfVxSLMTPi6BsF7hEy7wmXSfoBNz+A4egC11h+elC7LlCzZJyN0z4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com; spf=pass smtp.mailfrom=birthelmer.com; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b=DWRS2aar; arc=none smtp.client-ip=62.146.106.30 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b="DWRS2aar" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp03-ext2.udag.de (Postfix) with ESMTPA id BE3BAE0360; Thu, 26 Feb 2026 17:43:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1772124238; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=eGydO6FwFqLLphmOSbDH/70Uh6txyr6vYs0h8O6bVb4=; b=DWRS2aarxCP7nUIvSpxjjkPMTtrJwZ5L8DydvWWkF0t0luLakoY82ZWQ4HY1teetm4GQCp JRN7gKy2fO4c6PBG6QrGJUnY+IuWwfBjz0KZjQUHEcurpoUkhB/3VyhrT48ldmMF8e0agl 1HddZcdwqtD1CYmauU6G4kHrRsxTw75eNwNDAMp6iO+eGvUZeMGp7HjyhAb/Bnl75ZgGbB Y3y6ORHf6ym60vdwk0JvSsvex0CtxyM0vQOSF0psXFMubJ5if6K8nSZEeHgJz/W8afZAS1 ZByQqJW5NNKqiiDQawZ37Oszy2SBbzuGuEVSIaVhEHE8DmZhh9N9PdIA4seQVQ== Authentication-Results: smtp03-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Thu, 26 Feb 2026 17:43:54 +0100 Subject: [PATCH v6 2/3] fuse: create helper functions for filling in fuse args for open and getattr Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260226-fuse-compounds-upstream-v6-2-8585c5fcd2fc@ddn.com> References: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> In-Reply-To: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> To: Miklos Szeredi , Bernd Schubert , Joanne Koong , Luis Henriques Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Horst Birthelmer X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1772124235; l=4228; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=fIrksBFbOfxzwmxEToaZUQ6D6sUY/NeffJ6fTnRgRhQ=; b=U7QVwGjdVV5qIwsDoRFOKdclptpzWtGK6qECtVOdXFZde0Vgzi2b95NZoOmTbqIdvlwZessfV VwZet4OemXwAQ5xQVEdVkJMx8Pc0SPZK4NwWPjvjGJ6GP4QLal35jT4 X-Developer-Key: i=hbirthelmer@ddn.com; a=ed25519; pk=v3BVDFoy16EzgHZ23ObqW+kbpURtjrwxgKu8YNDKjGg= From: Horst Birthelmer 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 Signed-off-by: Horst Birthelmer --- 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 3927cb069236e9c52674301831c6d655397f24c5..e5ae033a15e85757a10a38b5e7d= 03dac86067c2a 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1471,6 +1471,23 @@ static int fuse_do_statx(struct mnt_idmap *idmap, st= ruct inode *inode, return 0; } =20 +/* + * 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 =3D FUSE_GETATTR; + args->nodeid =3D nodeid; + args->in_numargs =3D 1; + args->in_args[0].size =3D sizeof(*inarg); + args->in_args[0].value =3D inarg; + args->out_numargs =3D 1; + args->out_args[0].size =3D sizeof(*outarg); + args->out_args[0].value =3D outarg; +} + static int fuse_do_getattr(struct mnt_idmap *idmap, struct inode *inode, struct kstat *stat, struct file *file) { @@ -1492,14 +1509,7 @@ static int fuse_do_getattr(struct mnt_idmap *idmap, = struct inode *inode, inarg.getattr_flags |=3D FUSE_GETATTR_FH; inarg.fh =3D ff->fh; } - args.opcode =3D FUSE_GETATTR; - args.nodeid =3D get_node_id(inode); - args.in_numargs =3D 1; - args.in_args[0].size =3D sizeof(inarg); - args.in_args[0].value =3D &inarg; - args.out_numargs =3D 1; - args.out_args[0].size =3D sizeof(outarg); - args.out_args[0].value =3D &outarg; + fuse_getattr_args_fill(&args, get_node_id(inode), &inarg, &outarg); err =3D 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..a408a9668abbb361e2c1e386eba= b9dfcb0a7a573 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -23,6 +23,23 @@ #include #include =20 +/* + * Helper function to initialize fuse_args for OPEN/OPENDIR operations + */ +static void fuse_open_args_fill(struct fuse_args *args, u64 nodeid, int op= code, + struct fuse_open_in *inarg, struct fuse_open_out *outarg) +{ + args->opcode =3D opcode; + args->nodeid =3D nodeid; + args->in_numargs =3D 1; + args->in_args[0].size =3D sizeof(*inarg); + args->in_args[0].value =3D inarg; + args->out_numargs =3D 1; + args->out_args[0].size =3D sizeof(*outarg); + args->out_args[0].value =3D 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 nod= eid, inarg.open_flags |=3D FUSE_OPEN_KILL_SUIDGID; } =20 - args.opcode =3D opcode; - args.nodeid =3D nodeid; - args.in_numargs =3D 1; - args.in_args[0].size =3D sizeof(inarg); - args.in_args[0].value =3D &inarg; - args.out_numargs =3D 1; - args.out_args[0].size =3D sizeof(*outargp); - args.out_args[0].value =3D outargp; + fuse_open_args_fill(&args, nodeid, opcode, &inarg, outargp); =20 return fuse_simple_request(fm, &args); } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e46315aa428c9d0e704c62a0b80811172c5ec9c1..ff8222b66c4f7b04c0671a98023= 7a43871affd0a 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); =20 +/* + * 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); =20 struct fuse_file *fuse_file_alloc(struct fuse_mount *fm, bool release); void fuse_file_free(struct fuse_file *ff); --=20 2.53.0 From nobody Tue Apr 7 17:15:01 2026 Received: from smtp03-ext2.udag.de (smtp03-ext2.udag.de [62.146.106.30]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8BCB0364955; Thu, 26 Feb 2026 16:44:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.30 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124248; cv=none; b=L2FHKzjCFh3wIoNSe2AmKIjcxgcVNmsyxTkIndeM6ivvjYoMRzwUFnIzPaC38Is0U+NvRKGQ/di/P+sFN0C6/O2GU+LgoffyekFCGzrJFtj2zdA6SvNNnMH83fo2IY2D0sXqwBc1JNGL6xANlVnmesfmehKn7DasuhpyWtT5Fks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1772124248; c=relaxed/simple; bh=Wzo8W6GuKhl9trl7/UuvnQRqTZeOq9FOfR9/Ey+Sf6w=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FUmIop3cLrvq84f0bmOQ1irkTd/ek3tcvsAEAxQXP+/8YpsqKQGrk8ZYO+dJnHihvaa4LgUjll2I7/JtUZhaLIBDIrsbIrnl8j+ai9JmdOIILiAw/zpXZcYuM2N2uPsI4EKY8z6JhROWSrX+kiAvycbHs0XIqrUXJWf8+WUzJNE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com; spf=pass smtp.mailfrom=birthelmer.com; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b=rznISr6Z; arc=none smtp.client-ip=62.146.106.30 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=birthelmer.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=birthelmer.com header.i=@birthelmer.com header.b="rznISr6Z" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp03-ext2.udag.de (Postfix) with ESMTPA id 4C816E0364; Thu, 26 Feb 2026 17:43:58 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1772124238; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ZUv8gt5mgSFguSaafivJElSBGQrkctKXF01agrTnXwU=; b=rznISr6Zinc4+u8jBbEkPV7mkk74xRegmqz+8JD/uYC+JJbOCm8eM1Ttf3ro50qkzfdFKE 64Bno29lHWMTHdLLsN0kymt5fewhYW5Fwbuo103lKRHq21ZvFRBSL1O7nRwvCBDwRNYtGY exmwKO/O5KUlCLTmS/WMy3tUcRbjv0tAVY1RFEzVSHwlLzUaRqvJEXU9SPQKAlJfp/nEoC UdQ8ycGzLst7x3o5SOF0H+hq2YKgPNOSTbWXiXcBpL6U1uUZlfgg0nSfDRZJwdtrHxcyyN R3q23Jo0oRbTR9e1NTN67P07b+UCZ1UEs66GFfmiWPm898jgnGrT9iAP1auo+g== Authentication-Results: smtp03-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Thu, 26 Feb 2026 17:43:55 +0100 Subject: [PATCH v6 3/3] fuse: add an implementation of open+getattr Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260226-fuse-compounds-upstream-v6-3-8585c5fcd2fc@ddn.com> References: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> In-Reply-To: <20260226-fuse-compounds-upstream-v6-0-8585c5fcd2fc@ddn.com> To: Miklos Szeredi , Bernd Schubert , Joanne Koong , Luis Henriques Cc: linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Horst Birthelmer X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1772124235; l=5771; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=a/RBWGdWP//IlsEU/HBmKi3NpsJuJaZ2v6GDMv26sQE=; b=VwkcEOCotSgofOiok7Lax5jHbZWcNIHNNoHv5zEEmeNUYY1hXUiGUoPxSgfYsnUI0N+HxfFxQ RrMyXFLpASSAgCv2fDBoLyeeCqWPEgISCx5/35GTvV+7yUNoO0Y9JL7 X-Developer-Key: i=hbirthelmer@ddn.com; a=ed25519; pk=v3BVDFoy16EzgHZ23ObqW+kbpURtjrwxgKu8YNDKjGg= From: Horst Birthelmer 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 --- fs/fuse/file.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++----= ---- fs/fuse/fuse_i.h | 4 +- fs/fuse/ioctl.c | 2 +- 3 files changed, 99 insertions(+), 18 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a408a9668abbb361e2c1e386ebab9dfcb0a7a573..daa95a640c311fc393241bdf727= e00a2bc714f35 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 s= ync) } } =20 +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 =3D fm->fc; + struct fuse_compound_req *compound; + struct fuse_args open_args =3D {}; + struct fuse_args getattr_args =3D {}; + struct fuse_open_in open_in =3D {}; + struct fuse_getattr_in getattr_in =3D {}; + int err; + + compound =3D fuse_compound_alloc(fm, 2, FUSE_COMPOUND_SEPARABLE); + if (!compound) + return -ENOMEM; + + open_in.flags =3D flags & ~(O_CREAT | O_EXCL | O_NOCTTY); + if (!fm->fc->atomic_o_trunc) + open_in.flags &=3D ~O_TRUNC; + + if (fm->fc->handle_killpriv_v2 && + (open_in.flags & O_TRUNC) && !capable(CAP_FSETID)) + open_in.open_flags |=3D FUSE_OPEN_KILL_SUIDGID; + + fuse_open_args_fill(&open_args, nodeid, opcode, &open_in, outopenp); + + err =3D fuse_compound_add(compound, &open_args, NULL); + if (err) + goto out; + + fuse_getattr_args_fill(&getattr_args, nodeid, &getattr_in, outattrp); + + err =3D fuse_compound_add(compound, &getattr_args, NULL); + if (err) + goto out; + + err =3D fuse_compound_send(compound); + if (err) + goto out; + + err =3D fuse_compound_get_error(compound, 0); + if (err) + goto out; + + ff->fh =3D outopenp->fh; + ff->open_flags =3D outopenp->open_flags; + + err =3D 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: + 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 =3D fm->fc; struct fuse_file *ff; @@ -163,23 +226,40 @@ struct fuse_file *fuse_file_open(struct fuse_mount *f= m, u64 nodeid, if (open) { /* Store outarg for fuse_finish_open() */ struct fuse_open_out *outargp =3D &ff->args->open_outarg; - int err; + int err =3D -ENOSYS; =20 - err =3D fuse_send_open(fm, nodeid, open_flags, opcode, outargp); - if (!err) { - ff->fh =3D outargp->fh; - ff->open_flags =3D outargp->open_flags; - } else if (err !=3D -ENOSYS) { - fuse_file_free(ff); - return ERR_PTR(err); - } else { - if (isdir) { + if (inode) { + struct fuse_attr_out attr_outarg; + + err =3D fuse_compound_open_getattr(fm, nodeid, inode, + open_flags, opcode, ff, + &attr_outarg, outargp); + } + + if (err =3D=3D -ENOSYS) { + err =3D fuse_send_open(fm, nodeid, open_flags, opcode, + outargp); + if (!err) { + ff->fh =3D outargp->fh; + ff->open_flags =3D outargp->open_flags; + } + } + + if (err) { + if (err !=3D -ENOSYS) { + /* err is not ENOSYS */ + fuse_file_free(ff); + return ERR_PTR(err); + } else { /* No release needed */ kfree(ff->args); ff->args =3D NULL; - fc->no_opendir =3D 1; - } else { - fc->no_open =3D 1; + + /* we don't have open */ + if (isdir) + fc->no_opendir =3D 1; + else + fc->no_open =3D 1; } } } @@ -195,11 +275,10 @@ struct fuse_file *fuse_file_open(struct fuse_mount *f= m, u64 nodeid, int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file, bool isdir) { - struct fuse_file *ff =3D fuse_file_open(fm, nodeid, file->f_flags, isdir); + struct fuse_file *ff =3D fuse_file_open(fm, nodeid, file_inode(file), fil= e->f_flags, isdir); =20 if (!IS_ERR(ff)) file->private_data =3D 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 ff8222b66c4f7b04c0671a980237a43871affd0a..40409a4ab016a061eea20afee76= c8a7fe9c15adb 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1588,7 +1588,9 @@ void fuse_file_io_release(struct fuse_file *ff, struc= t inode *inode); =20 /* 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); =20 diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index fdc175e93f74743eb4d2e5a4bc688df1c62e64c4..07a02e47b2c3a68633d213675a8= cc380a0cf31d8 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); =20 - 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); } =20 static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file = *ff) --=20 2.53.0