From nobody Sat Apr 18 06:56:30 2026 Received: from smtp01-ext2.udag.de (smtp01-ext2.udag.de [62.146.106.18]) (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 7DCD12D24B7; Tue, 10 Feb 2026 08:51:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713513; cv=none; b=Qt1JO7sHsWQft1MtrR+ozr7R6FeY8m4Rsknp2YUqQrFRVF0/9kxn4Ddx/xsCOd5YMaf3cT5O8iOAfID1L6cIi8BDiQM2KL2RBBQZEVYcqOFTr+o0Xiq4lYzQU16TIa92P/Frod2iSNp0+EXGvQ2FGriMW6Qo4pu4bAw01qKCkQE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713513; c=relaxed/simple; bh=eamksJLwn3NdPWRgX/TcGsfOhfuqEfYqR+/28ONubMc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=D61fKABoya5Wrq6+S+4PKcUGf14Jpn3ENN2CsfpCyGGsSzb41zp2wIHCiRm/XqN5veTf8mKrzLSzpTbw3OlYYo+MTAJgZKNbcMK3eXOuDvWU3q/6ZhwfC3L3ej/9yJ/dznVzIUA5uGz8Cd1X1XypTtZXjSEKxw2jOYYNnrL7goo= 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=rI0pibB1; arc=none smtp.client-ip=62.146.106.18 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="rI0pibB1" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp01-ext2.udag.de (Postfix) with ESMTPA id 16339E05D5; Tue, 10 Feb 2026 09:46:19 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1770713179; 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=HKLbAlwiBaOQebJPK8+oRchPH73Kl1tw6JGIQnimhNo=; b=rI0pibB1F1KGCADMHk5x052PnEG0tJv6hThy+fojJ6paDBeX8Y4RjlLoXpgvpbwl6z8d5j 2JN1YMJMFjhxyyNgYCobn1XrRSTtAmnO9iSUUnkNkFxZ4HYJiJrPN997OYxNyDSC4DP2bP 4jGHnciPohLxTmTBQVJy0N5f4s+KYrvLL0XVKtdMVg/n7O+24O6NbbY04wFkq+x4a9ik+S IWGP51BSQtUZP9FP18FF6waC0F1tdBIWGlJCXwmFn6yBSMP02l25okAlBpc1FJcDkXfEXE OA4/yMuwGDm34ianHdaRrKKBYkYzcDnJHEKPT5mjF13lTfJrzMf9CAdCnaKcmg== Authentication-Results: smtp01-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Tue, 10 Feb 2026 09:46:16 +0100 Subject: [PATCH v5 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: <20260210-fuse-compounds-upstream-v5-1-ea0585f62daa@ddn.com> References: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@ddn.com> In-Reply-To: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@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=1770713177; l=10262; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=T3CBnqA9xo98+6YVrLiAvlhkmt1v7VrLe7xe0RX+AcM=; b=WNhPXxLFSqf+BjWOyvvrMPVcLCMOsfTgaH1T3rIjsYapSI60MDQFlLhnTwLBISwZRYczvac00 VuN6Vlc2leABXKWzV8ZRoMqpRLwFHCjC1LHV9C0gZwFQ/h5Eeh24Qmm X-Developer-Key: i=hbirthelmer@ddn.com; a=ed25519; pk=v3BVDFoy16EzgHZ23ObqW+kbpURtjrwxgKu8YNDKjGg= From: Horst Birthelmer For a FUSE_COMPOUND we add a header that contains information about how many commands there are in the compound and about the size of the expected result. This will make the interpretation in libfuse easier, since we can preallocate the whole result. Then we append the requests that belong to this compound. The API for the compound command has: fuse_compound_alloc() fuse_compound_add() fuse_compound_send() Signed-off-by: Horst Birthelmer --- fs/fuse/Makefile | 2 +- fs/fuse/compound.c | 224 ++++++++++++++++++++++++++++++++++++++++++= ++++ fs/fuse/fuse_i.h | 11 +++ include/uapi/linux/fuse.h | 40 +++++++++ 4 files changed, 276 insertions(+), 1 deletion(-) diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 22ad9538dfc4b80c6d9b52235bdfead6a6567ae4..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..1a85209f4e99a0e5e54955bcd95= 1eaf395176c12 --- /dev/null +++ b/fs/fuse/compound.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 + * + * Compound operations for FUSE - batch multiple operations into a single + * request to reduce round trips between kernel and userspace. + */ + +#include "fuse_i.h" + +/* + * Compound request builder, state tracker, and args pointer storage + */ +struct fuse_compound_req { + struct fuse_mount *fm; + struct fuse_compound_in compound_header; + struct fuse_compound_out result_header; + int op_errors[FUSE_MAX_COMPOUND_OPS]; + struct fuse_args *op_args[FUSE_MAX_COMPOUND_OPS]; +}; + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 f= lags) +{ + struct fuse_compound_req *compound; + + compound =3D kzalloc(sizeof(*compound), GFP_KERNEL); + if (!compound) + return NULL; + + compound->fm =3D fm; + compound->compound_header.flags =3D flags; + + return compound; +} + +int fuse_compound_add(struct fuse_compound_req *compound, struct fuse_args= *args) +{ + if (!compound || + compound->compound_header.count >=3D FUSE_MAX_COMPOUND_OPS) + return -EINVAL; + + if (args->in_pages) + return -EINVAL; + + compound->op_args[compound->compound_header.count] =3D args; + compound->compound_header.count++; + return 0; +} + +static void *fuse_copy_resp_data_per_req(const struct fuse_args *args, + char *resp) +{ + const struct fuse_arg *arg; + int i; + + for (i =3D 0; i < args->out_numargs; i++) { + arg =3D &args->out_args[i]; + memcpy(arg->value, resp, arg->size); + resp +=3D arg->size; + } + + return resp; +} + +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx) +{ + return compound->op_errors[op_idx]; +} + +static void *fuse_compound_parse_one_op(struct fuse_compound_req *compound, + int op_index, char *response, + char *response_end) +{ + struct fuse_out_header *op_hdr =3D (struct fuse_out_header *)response; + struct fuse_args *args =3D compound->op_args[op_index]; + + if (op_hdr->len < sizeof(struct fuse_out_header)) + return NULL; + + if (response + op_hdr->len > response_end) + return NULL; + + if (op_hdr->error) + compound->op_errors[op_index] =3D op_hdr->error; + else + fuse_copy_resp_data_per_req(args, response + + sizeof(struct fuse_out_header)); + /* in case of error, we still need to advance to the next op */ + return response + op_hdr->len; +} + +static int fuse_compound_parse_resp(struct fuse_compound_req *compound, + char *response, size_t response_size) +{ + char *response_end =3D response + response_size; + int req_count; + int i; + + req_count =3D min(compound->compound_header.count, + compound->result_header.count); + + for (i =3D 0; i < req_count; i++) { + response =3D fuse_compound_parse_one_op(compound, i, response, + response_end); + if (!response) + return -EIO; + } + + return 0; +} + +/* + * Build a single operation request in the buffer + * + * Returns the new buffer position after writing the operation. + */ +static char *fuse_compound_build_one_op(struct fuse_conn *fc, + struct fuse_args *op_args, + char *buffer_pos) +{ + struct fuse_in_header *hdr; + size_t needed_size =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; + memset(hdr, 0, sizeof(*hdr)); + hdr->len =3D needed_size; + hdr->opcode =3D op_args->opcode; + hdr->nodeid =3D op_args->nodeid; + hdr->uid =3D from_kuid(fc->user_ns, current_fsuid()); + hdr->gid =3D from_kgid(fc->user_ns, current_fsgid()); + hdr->pid =3D pid_nr_ns(task_pid(current), fc->pid_ns); + 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; +} + +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->compound_header.count; + size_t total_expected_out_size =3D 0; + size_t actual_response_size; + size_t buffer_size =3D 0; + void *resp_payload_buffer; + char *buffer_pos; + void *buffer =3D NULL; + ssize_t ret; + 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 kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO); + if (!buffer) + return -ENOMEM; + + buffer_pos =3D buffer; + for (i =3D 0; i < req_count; i++) + buffer_pos =3D fuse_compound_build_one_op(fc, + compound->op_args[i], + buffer_pos); + + 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 kmalloc(buffer_size, GFP_KERNEL | __GFP_ZERO); + 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 out; + + actual_response_size =3D args.out_args[1].size; + + ret =3D fuse_compound_parse_resp(compound, (char *)resp_payload_buffer, + actual_response_size); +out: + kfree(resp_payload_buffer); +out_free_buffer: + kfree(buffer); + return ret; +} diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 7f16049387d15e869db4be23a93605098588eda9..9ebcd96b6b309d75c86a9c716cb= d88aaa55c57ef 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1273,6 +1273,17 @@ static inline ssize_t fuse_simple_idmap_request(stru= ct mnt_idmap *idmap, int fuse_simple_background(struct fuse_mount *fm, struct fuse_args *args, gfp_t gfp_flags); =20 +/** + * Compound request API + */ +struct fuse_compound_req; +ssize_t fuse_compound_send(struct fuse_compound_req *compound); + +struct fuse_compound_req *fuse_compound_alloc(struct fuse_mount *fm, u32 f= lags); +int fuse_compound_add(struct fuse_compound_req *compound, + struct fuse_args *args); +int fuse_compound_get_error(struct fuse_compound_req *compound, int op_idx= ); + /** * Assign a unique id to a fuse request */ diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index c13e1f9a2f12bd39f535188cb5466688eba42263..113583c4efb67268174dbe4f68e= 9ea1c21b45eb6 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,39 @@ struct fuse_supp_groups { uint32_t groups[]; }; =20 +#define FUSE_MAX_COMPOUND_OPS 16 /* Maximum operations per compou= nd */ + +#define FUSE_COMPOUND_SEPARABLE (1<<0) +#define FUSE_COMPOUND_ATOMIC (1<<1) + +/* + * Compound request header + * + * This header is followed by the fuse requests + */ +struct fuse_compound_in { + uint32_t count; /* Number of operations */ + uint32_t flags; /* Compound flags */ + + /* Total size of all results. + * This is needed for preallocating the whole result for all + * commands in this compound. + */ + uint32_t result_size; + uint64_t reserved; +}; + +/* + * Compound response header + * + * This header is followed by complete fuse responses + */ +struct fuse_compound_out { + uint32_t count; /* Number of results */ + uint32_t flags; /* Result flags */ + uint64_t reserved; +}; + /** * Size of the ring buffer header */ --=20 2.53.0 From nobody Sat Apr 18 06:56:30 2026 Received: from smtp01-ext2.udag.de (smtp01-ext2.udag.de [62.146.106.18]) (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 11DDA2857F1; Tue, 10 Feb 2026 08:46:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713190; cv=none; b=HX9pPdFGOtj+kDhMKjg3WKHYS0lv1VSgi5PYanwL6Smly1u4F3k5Vpq/F/3LBv34FhO4LRg5MAcjOIlbDXWxREiMrRRU4FCm1m+DNpjXHf8tq05Dw7Mv7KmEBNOFm+QH6SsiDCgtra3JcKTfcA8xY3zX5yyA9BdgxWj74AJ7xFo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713190; c=relaxed/simple; bh=thEj7m81zXaFkuH8PMc/UfoL5Qw2HhqcWZT10qucOYU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=SqpGxpcUis+nkpvjvRIIPkqJ03y6Wx5vbe59daKOnxk0owwhU1JnZMhCuOvK+bldm5svX64PUI16eBu3wKWjPsNrFORmmFnAvA4Uf+kNsICWIMDicjanaw2xnTM2AqNhmcvDJpvp6ndrvISD1JJlumKsngaZqiusRWdRu8Gt/No= 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=hXwJH3As; arc=none smtp.client-ip=62.146.106.18 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="hXwJH3As" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp01-ext2.udag.de (Postfix) with ESMTPA id BED38E05EB; Tue, 10 Feb 2026 09:46:19 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1770713180; 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=d4P5Rx+c3QyJ6fROuBa6MvqRG4jtnnEBOUbCvXMfpDk=; b=hXwJH3AsUbWH3+LadTkcFUUE7ZF0DF3Y5+5V9Ge4Y5pCQmfhNLdYA3+3JHJdzxcnm29xYf l8KQj4l9ShzKnbX+sQJsX39wTL8ULtrya9aLW3bSYSmCCPp+lpWdTOpLn8G/aJamjvaOwS 2d7JV13GODDl3TUVdoa/djX3ZtTtHmp72bsQcbps86C8wR6YhNC20ZJx3Xipco1DOAzcUn XJ2OwbNAPC+Xk64h5oSghM1KMjClv7QN7IIUP4C0u9AWR3VfkfSyoACEYjoiOaIIjSj38F gawId1PHjn0lPmEQuwCpG9InwZi1kvXEEeuVQkmbE+pNLEEA21Zd4ahVBF88IA== Authentication-Results: smtp01-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Tue, 10 Feb 2026 09:46:17 +0100 Subject: [PATCH v5 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: <20260210-fuse-compounds-upstream-v5-2-ea0585f62daa@ddn.com> References: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@ddn.com> In-Reply-To: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@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=1770713177; l=4228; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=pMlEWlzZop3CWtcz586lgLhHwSNGfNcjXvt2p/x52L4=; b=d2Zz5T428GTTqNXniY15TQm3TG6lOMwjjp9azKbNHVF4Q2IBr8Iyx/fdiXgpb2i3LkR3aAzHk YmKfsIi0AlOBs3p9nJhpyCw2ktova3TVosUHM2aWNqljUoHLNSwKSZw 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 4b6b3d2758ff225dc389016017753b09fadff9d1..33fa5fc97ff7f65db585ee13861= 56ef13cf330cf 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1472,6 +1472,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) { @@ -1493,14 +1510,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 9ebcd96b6b309d75c86a9c716cbd88aaa55c57ef..fba14f26b67888831fcba6e2ac7= 3399f3c95d5ad 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 Sat Apr 18 06:56:30 2026 Received: from smtp01-ext2.udag.de (smtp01-ext2.udag.de [62.146.106.18]) (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 11E47329E40; Tue, 10 Feb 2026 08:46:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=62.146.106.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713191; cv=none; b=u8CHoYQK1cJcF5UguQBnMnsHMfEw2m7w7zgd72Ay5w4yI7LDdUMlMk6zqQ8+80GS7spYU0e6P5ux/r4ioEPxdnUUu2mJ27ePojIT6MsLmxt2f73Y1CQ8+uGF02jeqg6tJhrMSFswzvoGnJyHfhZrAB3HapdaJIZ2arkTgk5Cis8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770713191; c=relaxed/simple; bh=KG8tijsylarx7CIhbgNevEW7115AEbdRWCKAEmkegyw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tLfPkczJmnR83izWzlX0hghYdEyBvmG/OdlzPpHKgGwuMW7unwvZgnbDB7ILD3XuiWSv0IfZ3OWEYvHo5QlYsM7ZgHy+adjxd8K4+LKOMhkU14QwNOePB0T/1JkCi2jhtgw89txj/UF4EnKGi/RiFATJBovaWFWfDhYrCUuGaIE= 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=rI8B7+yY; arc=none smtp.client-ip=62.146.106.18 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="rI8B7+yY" Received: from fedora.fritz.box (200-143-067-156.ip-addr.inexio.net [156.67.143.200]) by smtp01-ext2.udag.de (Postfix) with ESMTPA id 98B88E05DB; Tue, 10 Feb 2026 09:46:20 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=birthelmer.com; s=uddkim-202310; t=1770713180; 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=DjvIUg1kuDCvHNRWBW3dRsoYRtdIFdkW0a8PofdtL4g=; b=rI8B7+yYYbSZ+dLxsRD681BordQ/cFThYg1xc9/4v25H+lgFa8VntcKqI+fQuli3l/j/S7 l//HRrSv1bh4vbrxH7ENvxWsJH5rLhTyj0o8c9vWE2ZYZJ0e90LVQb79kejMuHYG1zPD8s V0lKDRX1HKGxT59gnKzziaTPLXdrUEZKWxj0Eh2dnTZm9DorocTvhQi5DlCo/IbBmh8F1K CvJqKtfwfcAkmZbTCDOnG9v95BKc1XEkGuI2S/gElKLC7Hv1fPMQevoKAet8ajE+k+oLnG v3PNxib0L5HE1r7JqJjGdKDUwgqWnoibsWhGS/TjUxWQKQs22T94cTnkLp0acw== Authentication-Results: smtp01-ext2.udag.de; auth=pass smtp.auth=birthelmercom-0001 smtp.mailfrom=horst@birthelmer.com From: Horst Birthelmer Date: Tue, 10 Feb 2026 09:46:18 +0100 Subject: [PATCH v5 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: <20260210-fuse-compounds-upstream-v5-3-ea0585f62daa@ddn.com> References: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@ddn.com> In-Reply-To: <20260210-fuse-compounds-upstream-v5-0-ea0585f62daa@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=1770713177; l=6724; i=hbirthelmer@ddn.com; s=20251006; h=from:subject:message-id; bh=ooas0wlSi3drdMEixNU7ad+moqNHFP2UUOOu9XNPpcY=; b=86C5G3ut1TZoKps1yzPkyJLBM5mlSnXjTmCfWxYSvLAEt+gElp0B//Aw9QZNXBp32EO7AkeJO zt5DGnm2f+VDixlFALn+ASIrjtSdTatzLwctF2qiKxuH2Ip9oZrf0ka 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 | 7 +++- fs/fuse/inode.c | 6 +++ fs/fuse/ioctl.c | 2 +- 4 files changed, 108 insertions(+), 18 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a408a9668abbb361e2c1e386ebab9dfcb0a7a573..73c6a214b29a11fd6341f704bea= 140b282bb8355 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, 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); + if (err) + goto out; + + fuse_getattr_args_fill(&getattr_args, nodeid, &getattr_in, outattrp); + + err =3D fuse_compound_add(compound, &getattr_args); + 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: + kfree(compound); + return err; +} + struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - unsigned int open_flags, bool isdir) + struct inode *inode, + unsigned int open_flags, bool isdir) { struct fuse_conn *fc =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 && fc->compound_open_getattr) { + 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 fba14f26b67888831fcba6e2ac73399f3c95d5ad..3184ef864cf0b7b8598251dbb9c= 814d772c04026 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -924,6 +924,9 @@ struct fuse_conn { /* Use io_uring for communication */ unsigned int io_uring; =20 + /* Does the filesystem support compound operations? */ + unsigned int compound_open_getattr:1; + /** Maximum stack depth for passthrough backing files */ int max_stack_depth; =20 @@ -1560,7 +1563,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/inode.c b/fs/fuse/inode.c index 819e50d666224a6201cfc7f450e0bd37bfe32810..a5fd721be96d2cb1f22d58d7451= 165d1b33f5a5a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -991,6 +991,12 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_= mount *fm, fc->blocked =3D 0; fc->initialized =3D 0; fc->connected =3D 1; + + /* pretend fuse server supports compound operations + * until it tells us otherwise. + */ + fc->compound_open_getattr =3D 1; + atomic64_set(&fc->attr_version, 1); atomic64_set(&fc->evict_ctr, 1); get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index fdc175e93f74743eb4d2e5a4bc688df1c62e64c4..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