From nobody Mon May 25 04:56:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1778079390957653.4114006771647; Wed, 6 May 2026 07:56:30 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKdev-0006mS-EP; Wed, 06 May 2026 10:55:33 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeK-0006Rx-5n; Wed, 06 May 2026 10:54:57 -0400 Received: from proxmox-new.maurer-it.com ([94.136.29.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeG-0005s9-Ua; Wed, 06 May 2026 10:54:55 -0400 Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 8FC61444F3; Wed, 06 May 2026 16:54:39 +0200 (CEST) From: Fiona Ebner To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, hreitz@redhat.com, kwolf@redhat.com, qemu-stable@nongnu.org Subject: [PATCH 1/3] block/export/fuse: use struct fuse_init_in Date: Wed, 6 May 2026 16:49:56 +0200 Message-ID: <20260506145424.10249-2-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260506145424.10249-1-f.ebner@proxmox.com> References: <20260506145424.10249-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778079171148 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=94.136.29.106; envelope-from=f.ebner@proxmox.com; helo=proxmox-new.maurer-it.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1778079394879154100 Content-Type: text/plain; charset="utf-8" The code is switched to use the current 'struct fuse_init_in' in preparation to use the FUSE_DIRECT_IO_ALLOW_MMAP feature, which is part of the flags2 member that got added in protocol version 5.36. To not break compatibility with older kernels, the check for whether the full header of an operation was read in co_read_from_fuse_fd() needs to be adapted. In particular, for a FUSE_INIT operation, the protocol version must be considered, because the length of the header changed with protocol version 7.36. Always using the length of the old, shorter struct was inaccurate, since for newer protocol versions this might mean accepting a truncated read for FUSE_INIT. Users of the init header that want to use parts of the extended structure must check with the using_old_fuse_init_in() helper function if they may do so. Cc: qemu-stable@nongnu.org Fixes: a94a1d7699 ("fuse: Manually process requests (without libfuse)") Signed-off-by: Fiona Ebner --- block/export/fuse.c | 56 +++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/block/export/fuse.c b/block/export/fuse.c index a2a478d293..35218e3197 100644 --- a/block/export/fuse.c +++ b/block/export/fuse.c @@ -51,23 +51,16 @@ #define FUSE_MAX_READ_BYTES (MIN(BDRV_REQUEST_MAX_BYTES, 1 * 1024 * 1024)) #define FUSE_MAX_WRITE_BYTES (64 * 1024) =20 -/* - * fuse_init_in structure before 7.36. We don't need the flags2 field add= ed - * there, so we can work with the smaller older structure to stay compatib= le - * with older kernels. - */ -struct fuse_init_in_compat { - uint32_t major; - uint32_t minor; - uint32_t max_readahead; - uint32_t flags; -}; - typedef struct FuseRequestInHeader { struct fuse_in_header common; /* All supported requests */ union { - struct fuse_init_in_compat init; + /* + * When using_old_fuse_init_in() is true, then the smaller older s= truct + * is used by the kernel. The flags2 member and other new members = must + * be treated as absent then. + */ + struct fuse_init_in init; struct fuse_open_in open; struct fuse_setattr_in setattr; struct fuse_read_in read; @@ -629,6 +622,16 @@ static int clone_fuse_fd(int fd, Error **errp) return new_fd; } =20 +/** + * Check whether the smaller older fuse_init_in structure from before prot= ocol + * version 7.36 is used. The flags2 member and other new members must be t= reated + * as absent then. + */ +static bool using_old_fuse_init_in(const struct fuse_init_in *in) +{ + return in->major < 7 || (in->major =3D=3D 7 && in->minor < 36); +} + /** * Try to read a single request from the FUSE FD. * Takes a FuseQueue pointer in `opaque`. @@ -693,6 +696,31 @@ static void coroutine_fn co_read_from_fuse_fd(void *op= aque) goto no_request; } =20 + /* + * If the request is of type FUSE_INIT, need to check the version to + * actually determine the length of the fuse_init_in structure used by= the + * kernel. In protocol version 7.36, the structure was extended. + */ + if (in_hdr->common.opcode =3D=3D FUSE_INIT) { + /* Length of the fuse_init_in structure before 7.36. */ + size_t old_init_hdr_len =3D 16; + + /* + * Expect at least the size of the smaller older structure to ensu= re the + * version can be checked. + */ + if (unlikely(ret < sizeof(in_hdr->common) + old_init_hdr_len)) { + error_report("FUSE_INIT request truncated, read only %zi bytes= ", + ret); + fuse_write_err(fuse_fd, &in_hdr->common, -EINVAL); + goto no_request; + } + + if (using_old_fuse_init_in(&in_hdr->init)) { + op_hdr_len =3D old_init_hdr_len; + } + } + if (unlikely(ret < sizeof(in_hdr->common) + op_hdr_len)) { error_report("FUSE request truncated, expected %zu bytes, read %zi= " "bytes", @@ -826,7 +854,7 @@ static bool is_regular_file(const char *path, Error **e= rrp) */ static ssize_t coroutine_fn GRAPH_RDLOCK fuse_co_init(FuseExport *exp, struct fuse_init_out *out, - const struct fuse_init_in_compat *in) + const struct fuse_init_in *in) { const uint32_t supported_flags =3D FUSE_ASYNC_READ | FUSE_ASYNC_DIO; =20 --=20 2.47.3 From nobody Mon May 25 04:56:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177807936017262.83568492123209; Wed, 6 May 2026 07:56:00 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKdeh-0006fd-QE; Wed, 06 May 2026 10:55:19 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeK-0006Rw-5g; Wed, 06 May 2026 10:54:57 -0400 Received: from proxmox-new.maurer-it.com ([94.136.29.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeG-0005sA-Uw; Wed, 06 May 2026 10:54:55 -0400 Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 6996780AF6; Wed, 06 May 2026 16:54:39 +0200 (CEST) From: Fiona Ebner To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, hreitz@redhat.com, kwolf@redhat.com, qemu-stable@nongnu.org Subject: [PATCH 2/3] block/export/fuse: set FUSE_DIRECT_IO_ALLOW_MMAP flag to fix regression Date: Wed, 6 May 2026 16:49:57 +0200 Message-ID: <20260506145424.10249-3-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260506145424.10249-1-f.ebner@proxmox.com> References: <20260506145424.10249-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778079171208 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=94.136.29.106; envelope-from=f.ebner@proxmox.com; helo=proxmox-new.maurer-it.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1778079365481158500 Content-Type: text/plain; charset="utf-8" Commit 8599559580 ("fuse: Set direct_io and parallel_direct_writes") broke use cases that require mmap() with MAP_SHARED on the export. In particular, swtpm_setup using its 'file://' protocol requires this. From the kernel documentation [0]: > To allow shared mmap, the FUSE_DIRECT_IO_ALLOW_MMAP flag may be > enabled in the FUSE_INIT reply. Set the FUSE_DIRECT_IO_ALLOW_MMAP flag to restore compatibility with users requiring shared mmap. The FUSE_INIT_EXT flag needs to be set for the flags2 member to have an effect. [0]: https://www.kernel.org/doc/html/next/filesystems/fuse/fuse-io.html Cc: qemu-stable@nongnu.org Fixes: 8599559580 ("fuse: Set direct_io and parallel_direct_writes") Signed-off-by: Fiona Ebner --- block/export/fuse.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/block/export/fuse.c b/block/export/fuse.c index 35218e3197..c0e8dfb643 100644 --- a/block/export/fuse.c +++ b/block/export/fuse.c @@ -856,7 +856,8 @@ static ssize_t coroutine_fn GRAPH_RDLOCK fuse_co_init(FuseExport *exp, struct fuse_init_out *out, const struct fuse_init_in *in) { - const uint32_t supported_flags =3D FUSE_ASYNC_READ | FUSE_ASYNC_DIO; + uint32_t supported_flags =3D FUSE_ASYNC_READ | FUSE_ASYNC_DIO; + uint32_t flags2 =3D 0; =20 if (in->major !=3D 7) { error_report("FUSE major version mismatch: We have 7, but kernel h= as %" @@ -871,13 +872,21 @@ fuse_co_init(FuseExport *exp, struct fuse_init_out *o= ut, return -EINVAL; } =20 + if (!using_old_fuse_init_in(in)) { + /* The flags2 flags must be shifted down by 32 bits. */ + const uint32_t supported_flags2 =3D FUSE_DIRECT_IO_ALLOW_MMAP >> 3= 2; + /* flags2 is only considered if FUSE_INIT_EXT is set. */ + supported_flags =3D supported_flags | FUSE_INIT_EXT; + flags2 =3D in->flags2 & supported_flags2; + } + *out =3D (struct fuse_init_out) { .major =3D 7, .minor =3D MIN(FUSE_KERNEL_MINOR_VERSION, in->minor), .max_readahead =3D in->max_readahead, .max_write =3D FUSE_MAX_WRITE_BYTES, .flags =3D in->flags & supported_flags, - .flags2 =3D 0, + .flags2 =3D flags2, =20 /* libfuse maximum: 2^16 - 1 */ .max_background =3D UINT16_MAX, --=20 2.47.3 From nobody Mon May 25 04:56:02 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177807937169035.85269121273575; Wed, 6 May 2026 07:56:11 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wKdec-0006eH-53; Wed, 06 May 2026 10:55:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeJ-0006Ri-8x; Wed, 06 May 2026 10:54:56 -0400 Received: from proxmox-new.maurer-it.com ([94.136.29.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wKdeG-0005sC-UV; Wed, 06 May 2026 10:54:54 -0400 Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id ABB90491C2; Wed, 06 May 2026 16:54:39 +0200 (CEST) From: Fiona Ebner To: qemu-devel@nongnu.org Cc: qemu-block@nongnu.org, hreitz@redhat.com, kwolf@redhat.com, qemu-stable@nongnu.org Subject: [PATCH 3/3] iotests: test shared mmap for fuse export Date: Wed, 6 May 2026 16:49:58 +0200 Message-ID: <20260506145424.10249-4-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260506145424.10249-1-f.ebner@proxmox.com> References: <20260506145424.10249-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1778079171273 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=94.136.29.106; envelope-from=f.ebner@proxmox.com; helo=proxmox-new.maurer-it.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1778079373553158500 Content-Type: text/plain; charset="utf-8" This test would have worked before commit 8599559580 ("fuse: Set direct_io and parallel_direct_writes") and is working again since commit HEAD~1 ("block/export/fuse: set FUSE_DIRECT_IO_ALLOW_MMAP flag to fix regression"). Signed-off-by: Fiona Ebner --- tests/qemu-iotests/tests/fuse-mmap-shared | 103 ++++++++++++++++++ tests/qemu-iotests/tests/fuse-mmap-shared.out | 5 + 2 files changed, 108 insertions(+) create mode 100755 tests/qemu-iotests/tests/fuse-mmap-shared create mode 100644 tests/qemu-iotests/tests/fuse-mmap-shared.out diff --git a/tests/qemu-iotests/tests/fuse-mmap-shared b/tests/qemu-iotests= /tests/fuse-mmap-shared new file mode 100755 index 0000000000..a0a10cea6a --- /dev/null +++ b/tests/qemu-iotests/tests/fuse-mmap-shared @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# group: rw +# +# Test that a FUSE export can be mmap()-ed with MAP_SHARED +# +# Copyright (C) 2026 Proxmox Server Solutions GmbH +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import itertools +import mmap +from mmap import MAP_SHARED +from pathlib import Path + +import iotests +from iotests import qemu_img, qemu_io, QemuStorageDaemon + +def test_fuse_support(mount_point): + test_qsd =3D QemuStorageDaemon('--blockdev', 'null-co,node-name=3Dnode= 0', + qmp=3DTrue) + res =3D test_qsd.qmp('block-export-add', { + 'id': 'exp0', + 'type': 'fuse', + 'node-name': 'node0', + 'mountpoint': mount_point, + 'allow-other': 'off' + }) + test_qsd.stop() + if 'error' in res: + assert (res['error']['desc'] =3D=3D + "Parameter 'type' does not accept value 'fuse'") + iotests.notrun('No FUSE support') + +# Shared mmap when using direct IO is only supported for Linux kernels >= =3D 6.6 +# with commit e78662e818f94 ("fuse: add a new fuse init flag to relax +# estrictions in no cache mode"). +def test_linux_kernel_support(): + [major, minor] =3D map(int, os.uname().release.split('.')[:2]) + if major < 6 or (major =3D=3D 6 and minor < 6): + iotests.notrun('No kernel support for shared mmap with direct IO') + +image_size =3D 1 * 1024 * 1024 +image =3D os.path.join(iotests.test_dir, 'image.' + iotests.imgfmt) +fuse_mount_point =3D os.path.join(iotests.test_dir, 'export.fuse') +Path(fuse_mount_point).touch() + +test_fuse_support(fuse_mount_point) +test_linux_kernel_support() + +class TestMmapShared(iotests.QMPTestCase): + + def setUp(self): + qemu_img('create', '-f', iotests.imgfmt, image, str(image_size)) + qemu_io(image, '-c', f'write -P 23 0 {image_size}') + + self.qsd =3D QemuStorageDaemon(qmp=3DTrue) + + self.qsd.cmd('blockdev-add', { + 'node-name': 'node0', + 'driver': iotests.imgfmt, + 'file': { + 'driver': 'file', + 'filename': image + } + }) + + self.qsd.cmd('block-export-add', { + 'id': 'exp0', + 'type': 'fuse', + 'node-name': 'node0', + 'mountpoint': fuse_mount_point, + 'writable': True, + 'allow-other': 'off' + }) + + def tearDown(self): + self.stop_qsd() + os.remove(image) + os.remove(fuse_mount_point) + + def stop_qsd(self): + if self.qsd: + self.qsd.stop() + self.qsd =3D None + + def test_mmap_shared(self): + with open(fuse_mount_point, 'r+b') as file: + with mmap.mmap(file.fileno(), image_size, flags=3DMAP_SHARED) = as mm: + buf =3D bytearray(image_size) + buf[:] =3D itertools.repeat(23, image_size) + assert mm.read(image_size) =3D=3D buf + buf[:] =3D itertools.repeat(42, image_size) + mm.seek(0) + mm.write(buf) + mm.flush() + self.stop_qsd() + qemu_io(image, '-c', f'read -P 42 0 {image_size}') + +if __name__ =3D=3D '__main__': + iotests.main(supported_fmts=3D['generic'], + supported_protocols=3D['file'], + supported_platforms=3D['linux']) diff --git a/tests/qemu-iotests/tests/fuse-mmap-shared.out b/tests/qemu-iot= ests/tests/fuse-mmap-shared.out new file mode 100644 index 0000000000..ae1213e6f8 --- /dev/null +++ b/tests/qemu-iotests/tests/fuse-mmap-shared.out @@ -0,0 +1,5 @@ +. +---------------------------------------------------------------------- +Ran 1 tests + +OK --=20 2.47.3