@samitouri / QOSamiQemu / commits / ba20257d9b

iotests: test shared mmap for fuse export

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 <f.ebner@proxmox.com> Message-ID: <20260506145424.10249-4-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>

Fiona Ebner committed May 6, 2026 at 16:49 UTC ba20257d9b45c28f23a66b3f79bebffd6322ff76
2 files changed +110
tests/qemu-iotests/tests/fuse-mmap-shared new
+105
@@ -0,0 +1,105 @@
1 +#!/usr/bin/env python3
2 +# group: rw
3 +#
4 +# Test that a FUSE export can be mmap()-ed with MAP_SHARED
5 +#
6 +# Copyright (C) 2026 Proxmox Server Solutions GmbH
7 +#
8 +# SPDX-License-Identifier: GPL-2.0-or-later
9 +
10 +import os
11 +import itertools
12 +import mmap
13 +from mmap import MAP_SHARED
14 +from pathlib import Path
15 +
16 +import iotests
17 +from iotests import qemu_img, qemu_io, QemuStorageDaemon
18 +
19 +def test_fuse_support(mount_point):
20 + test_qsd = QemuStorageDaemon('--blockdev', 'null-co,node-name=node0',
21 + qmp=True)
22 + res = test_qsd.qmp('block-export-add', {
23 + 'id': 'exp0',
24 + 'type': 'fuse',
25 + 'node-name': 'node0',
26 + 'mountpoint': mount_point,
27 + 'allow-other': 'off'
28 + })
29 + test_qsd.stop()
30 + if 'error' in res:
31 + assert (res['error']['desc'] ==
32 + "Parameter 'type' does not accept value 'fuse'")
33 + iotests.notrun('No FUSE support')
34 +
35 +# Shared mmap when using direct IO is only supported for Linux kernels >= 6.6
36 +# with commit e78662e818f94 ("fuse: add a new fuse init flag to relax
37 +# estrictions in no cache mode").
38 +def test_linux_kernel_support():
39 + [major, minor] = map(int, os.uname().release.split('.')[:2])
40 + if major < 6 or (major == 6 and minor < 6):
41 + iotests.notrun('No kernel support for shared mmap with direct IO')
42 +
43 +image_size = 1 * 1024 * 1024
44 +image = os.path.join(iotests.test_dir, 'image.' + iotests.imgfmt)
45 +fuse_mount_point = os.path.join(iotests.test_dir, 'export.fuse')
46 +Path(fuse_mount_point).touch()
47 +
48 +test_fuse_support(fuse_mount_point)
49 +test_linux_kernel_support()
50 +
51 +class TestMmapShared(iotests.QMPTestCase):
52 +
53 + def setUp(self):
54 + qemu_img('create', '-f', iotests.imgfmt, image, str(image_size))
55 + qemu_io(image, '-c', f'write -P 23 0 {image_size}')
56 +
57 + self.qsd = QemuStorageDaemon(qmp=True)
58 +
59 + self.qsd.cmd('blockdev-add', {
60 + 'node-name': 'node0',
61 + 'driver': iotests.imgfmt,
62 + 'file': {
63 + 'driver': 'file',
64 + 'filename': image
65 + }
66 + })
67 +
68 + self.qsd.cmd('block-export-add', {
69 + 'id': 'exp0',
70 + 'type': 'fuse',
71 + 'node-name': 'node0',
72 + 'mountpoint': fuse_mount_point,
73 + 'writable': True,
74 + 'allow-other': 'off'
75 + })
76 +
77 + def tearDown(self):
78 + self.stop_qsd()
79 + os.remove(image)
80 + os.remove(fuse_mount_point)
81 +
82 + def stop_qsd(self):
83 + if self.qsd:
84 + self.qsd.stop()
85 + self.qsd = None
86 +
87 + def test_mmap_shared(self):
88 + with open(fuse_mount_point, 'r+b') as file:
89 + with mmap.mmap(file.fileno(), image_size, flags=MAP_SHARED) as mm:
90 + buf = bytearray(image_size)
91 + buf[:] = itertools.repeat(23, image_size)
92 + assert mm.read(image_size) == buf
93 + buf[:] = itertools.repeat(42, image_size)
94 + mm.seek(0)
95 + mm.write(buf)
96 + mm.flush()
97 + self.stop_qsd()
98 + qemu_io(image, '-c', f'read -P 42 0 {image_size}')
99 +
100 +if __name__ == '__main__':
101 + # LUKS would require key-secret in blockdev-add
102 + iotests.main(supported_fmts=['generic'],
103 + unsupported_fmts=['luks'],
104 + supported_protocols=['file'],
105 + supported_platforms=['linux'])
tests/qemu-iotests/tests/fuse-mmap-shared.out new
+5
@@ -0,0 +1,5 @@
1 +.
2 +----------------------------------------------------------------------
3 +Ran 1 tests
4 +
5 +OK