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