1 | The following changes since commit 9db3065c62a983286d06c207f4981408cf42184d: | 1 | The following changes since commit d922088eb4ba6bc31a99f17b32cf75e59dd306cd: |
---|---|---|---|
2 | 2 | ||
3 | Merge remote-tracking branch 'remotes/vivier2/tags/linux-user-for-6.1-pull-request' into staging (2021-07-08 16:30:18 +0100) | 3 | Merge tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging (2025-02-03 13:42:02 -0500) |
4 | 4 | ||
5 | are available in the Git repository at: | 5 | are available in the Git repository at: |
6 | 6 | ||
7 | git://repo.or.cz/qemu/kevin.git tags/for-upstream | 7 | https://repo.or.cz/qemu/kevin.git tags/for-upstream |
8 | 8 | ||
9 | for you to fetch changes up to e60edf69e2f64e818466019313517a2e6d6b63f4: | 9 | for you to fetch changes up to fc4e394b2887e15d5f83994e4fc7b26c895c627a: |
10 | 10 | ||
11 | block: Make blockdev-reopen stable API (2021-07-09 13:19:11 +0200) | 11 | block: remove unused BLOCK_OP_TYPE_DATAPLANE (2025-02-06 14:51:10 +0100) |
12 | 12 | ||
13 | ---------------------------------------------------------------- | 13 | ---------------------------------------------------------------- |
14 | Block layer patches | 14 | Block layer patches |
15 | 15 | ||
16 | - Make blockdev-reopen stable | 16 | - Managing inactive nodes (enables QSD migration with shared storage) |
17 | - Remove deprecated qemu-img backing file without format | 17 | - Fix swapped values for BLOCK_IO_ERROR 'device' and 'qom-path' |
18 | - rbd: Convert to coroutines and add write zeroes support | 18 | - vpc: Read images exported from Azure correctly |
19 | - rbd: Updated MAINTAINERS | 19 | - scripts/qemu-gdb: Support coroutine dumps in coredumps |
20 | - export/fuse: Allow other users access to the export | 20 | - Minor cleanups |
21 | - vhost-user: Fix backends without multiqueue support | ||
22 | - Fix drive-backup transaction endless drained section | ||
23 | 21 | ||
24 | ---------------------------------------------------------------- | 22 | ---------------------------------------------------------------- |
25 | Alberto Garcia (4): | 23 | Fabiano Rosas (1): |
26 | block: Add bdrv_reopen_queue_free() | 24 | block: Fix leak in send_qmp_error_event |
27 | block: Support multiple reopening with x-blockdev-reopen | ||
28 | iotests: Test reopening multiple devices at the same time | ||
29 | block: Make blockdev-reopen stable API | ||
30 | 25 | ||
31 | Eric Blake (3): | 26 | Kevin Wolf (16): |
32 | qcow2: Prohibit backing file changes in 'qemu-img amend' | 27 | block: Add 'active' field to BlockDeviceInfo |
33 | qemu-img: Require -F with -b backing image | 28 | block: Allow inactivating already inactive nodes |
34 | qemu-img: Improve error for rebase without backing format | 29 | block: Inactivate external snapshot overlays when necessary |
30 | migration/block-active: Remove global active flag | ||
31 | block: Don't attach inactive child to active node | ||
32 | block: Fix crash on block_resize on inactive node | ||
33 | block: Add option to create inactive nodes | ||
34 | block: Add blockdev-set-active QMP command | ||
35 | block: Support inactive nodes in blk_insert_bs() | ||
36 | block/export: Don't ignore image activation error in blk_exp_add() | ||
37 | block: Drain nodes before inactivating them | ||
38 | block/export: Add option to allow export of inactive nodes | ||
39 | nbd/server: Support inactive nodes | ||
40 | iotests: Add filter_qtest() | ||
41 | iotests: Add qsd-migrate case | ||
42 | iotests: Add (NBD-based) tests for inactive nodes | ||
35 | 43 | ||
36 | Heinrich Schuchardt (1): | 44 | Peter Krempa (1): |
37 | util/uri: do not check argument of uri_free() | 45 | block-backend: Fix argument order when calling 'qapi_event_send_block_io_error()' |
38 | 46 | ||
39 | Ilya Dryomov (1): | 47 | Peter Xu (3): |
40 | MAINTAINERS: update block/rbd.c maintainer | 48 | scripts/qemu-gdb: Always do full stack dump for python errors |
49 | scripts/qemu-gdb: Simplify fs_base fetching for coroutines | ||
50 | scripts/qemu-gdb: Support coroutine dumps in coredumps | ||
41 | 51 | ||
42 | Kevin Wolf (3): | 52 | Philippe Mathieu-Daudé (1): |
43 | vhost-user: Fix backends without multiqueue support | 53 | block: Improve blk_get_attached_dev_id() docstring |
44 | qcow2: Fix dangling pointer after reopen for 'file' | ||
45 | block: Acquire AioContexts during bdrv_reopen_multiple() | ||
46 | 54 | ||
47 | Max Reitz (6): | 55 | Stefan Hajnoczi (1): |
48 | export/fuse: Pass default_permissions for mount | 56 | block: remove unused BLOCK_OP_TYPE_DATAPLANE |
49 | export/fuse: Add allow-other option | ||
50 | export/fuse: Give SET_ATTR_SIZE its own branch | ||
51 | export/fuse: Let permissions be adjustable | ||
52 | iotests/308: Test +w on read-only FUSE exports | ||
53 | iotests/fuse-allow-other: Test allow-other | ||
54 | 57 | ||
55 | Or Ozeri (1): | 58 | Vitaly Kuznetsov (2): |
56 | block/rbd: Add support for rbd image encryption | 59 | vpc: Split off vpc_ignore_current_size() helper |
60 | vpc: Read images exported from Azure correctly | ||
57 | 61 | ||
58 | Peter Lieven (8): | 62 | qapi/block-core.json | 44 +++- |
59 | block/rbd: bump librbd requirement to luminous release | 63 | qapi/block-export.json | 10 +- |
60 | block/rbd: store object_size in BDRVRBDState | 64 | include/block/block-common.h | 2 +- |
61 | block/rbd: update s->image_size in qemu_rbd_getlength | 65 | include/block/block-global-state.h | 6 + |
62 | block/rbd: migrate from aio to coroutines | 66 | include/block/export.h | 3 + |
63 | block/rbd: add write zeroes support | 67 | include/system/block-backend-io.h | 7 + |
64 | block/rbd: drop qemu_rbd_refresh_limits | 68 | migration/migration.h | 3 - |
65 | block/rbd: fix type of task->complete | 69 | block.c | 64 +++++- |
66 | MAINTAINERS: add block/rbd.c reviewer | 70 | block/block-backend.c | 32 ++- |
67 | 71 | block/export/export.c | 29 ++- | |
68 | Vladimir Sementsov-Ogievskiy (1): | 72 | block/monitor/block-hmp-cmds.c | 5 +- |
69 | blockdev: fix drive-backup transaction endless drained section | 73 | block/qapi.c | 1 + |
70 | 74 | block/replication.c | 1 - | |
71 | qapi/block-core.json | 134 +++- | 75 | block/vpc.c | 65 +++--- |
72 | qapi/block-export.json | 33 +- | 76 | blockdev.c | 48 ++++ |
73 | docs/system/deprecated.rst | 32 - | 77 | blockjob.c | 2 - |
74 | docs/system/removed-features.rst | 31 + | 78 | hw/block/virtio-blk.c | 9 - |
75 | include/block/block.h | 3 + | 79 | hw/scsi/virtio-scsi.c | 3 - |
76 | block.c | 108 +-- | 80 | migration/block-active.c | 46 ---- |
77 | block/export/fuse.c | 121 +++- | 81 | migration/migration.c | 8 - |
78 | block/nfs.c | 4 +- | 82 | nbd/server.c | 17 ++ |
79 | block/qcow2.c | 42 +- | 83 | scripts/qemu-gdb.py | 2 + |
80 | block/rbd.c | 749 +++++++++++++-------- | 84 | scripts/qemugdb/coroutine.py | 102 ++++++--- |
81 | block/replication.c | 7 + | 85 | tests/qemu-iotests/iotests.py | 8 + |
82 | block/ssh.c | 4 +- | 86 | tests/qemu-iotests/041 | 4 +- |
83 | blockdev.c | 77 ++- | 87 | tests/qemu-iotests/165 | 4 +- |
84 | hw/virtio/vhost-user.c | 3 + | 88 | tests/qemu-iotests/184.out | 2 + |
85 | qemu-img.c | 9 +- | 89 | tests/qemu-iotests/191.out | 16 ++ |
86 | qemu-io-cmds.c | 7 +- | 90 | tests/qemu-iotests/273.out | 5 + |
87 | util/uri.c | 22 +- | 91 | tests/qemu-iotests/tests/copy-before-write | 3 +- |
88 | MAINTAINERS | 3 +- | 92 | tests/qemu-iotests/tests/inactive-node-nbd | 303 +++++++++++++++++++++++++ |
89 | meson.build | 7 +- | 93 | tests/qemu-iotests/tests/inactive-node-nbd.out | 239 +++++++++++++++++++ |
90 | tests/qemu-iotests/040 | 4 +- | 94 | tests/qemu-iotests/tests/migrate-bitmaps-test | 7 +- |
91 | tests/qemu-iotests/041 | 6 +- | 95 | tests/qemu-iotests/tests/qsd-migrate | 140 ++++++++++++ |
92 | tests/qemu-iotests/061 | 3 + | 96 | tests/qemu-iotests/tests/qsd-migrate.out | 59 +++++ |
93 | tests/qemu-iotests/061.out | 3 +- | 97 | 35 files changed, 1133 insertions(+), 166 deletions(-) |
94 | tests/qemu-iotests/082.out | 6 +- | 98 | create mode 100755 tests/qemu-iotests/tests/inactive-node-nbd |
95 | tests/qemu-iotests/114 | 18 +- | 99 | create mode 100644 tests/qemu-iotests/tests/inactive-node-nbd.out |
96 | tests/qemu-iotests/114.out | 11 +- | 100 | create mode 100755 tests/qemu-iotests/tests/qsd-migrate |
97 | tests/qemu-iotests/155 | 9 +- | 101 | create mode 100644 tests/qemu-iotests/tests/qsd-migrate.out |
98 | tests/qemu-iotests/165 | 4 +- | ||
99 | tests/qemu-iotests/245 | 78 ++- | ||
100 | tests/qemu-iotests/245.out | 4 +- | ||
101 | tests/qemu-iotests/248 | 4 +- | ||
102 | tests/qemu-iotests/248.out | 2 +- | ||
103 | tests/qemu-iotests/296 | 11 +- | ||
104 | tests/qemu-iotests/298 | 4 +- | ||
105 | tests/qemu-iotests/301 | 4 +- | ||
106 | tests/qemu-iotests/301.out | 16 +- | ||
107 | tests/qemu-iotests/308 | 20 +- | ||
108 | tests/qemu-iotests/308.out | 6 +- | ||
109 | tests/qemu-iotests/common.rc | 6 +- | ||
110 | tests/qemu-iotests/tests/fuse-allow-other | 168 +++++ | ||
111 | tests/qemu-iotests/tests/fuse-allow-other.out | 88 +++ | ||
112 | .../qemu-iotests/tests/remove-bitmap-from-backing | 22 +- | ||
113 | 42 files changed, 1350 insertions(+), 543 deletions(-) | ||
114 | create mode 100755 tests/qemu-iotests/tests/fuse-allow-other | ||
115 | create mode 100644 tests/qemu-iotests/tests/fuse-allow-other.out | ||
116 | 102 | ||
117 | 103 | diff view generated by jsdifflib |
1 | From: Peter Lieven <pl@kamp.de> | 1 | From: Vitaly Kuznetsov <vkuznets@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | librbd supports 1 byte alignment for all aio operations. | 3 | In preparation to making changes to the logic deciding whether CHS or |
4 | 'current_size' need to be used in determining the image size, split off | ||
5 | vpc_ignore_current_size() helper. | ||
4 | 6 | ||
5 | Currently, there is no API call to query limits from the Ceph | 7 | No functional change intended. |
6 | ObjectStore backend. So drop the bdrv_refresh_limits completely | ||
7 | until there is such an API call. | ||
8 | 8 | ||
9 | Signed-off-by: Peter Lieven <pl@kamp.de> | 9 | Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> |
10 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | 10 | Message-ID: <20241212134504.1983757-2-vkuznets@redhat.com> |
11 | Message-Id: <20210702172356.11574-7-idryomov@gmail.com> | 11 | Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> |
12 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | --- | 14 | --- |
14 | block/rbd.c | 9 --------- | 15 | block/vpc.c | 67 +++++++++++++++++++++++++++++------------------------ |
15 | 1 file changed, 9 deletions(-) | 16 | 1 file changed, 37 insertions(+), 30 deletions(-) |
16 | 17 | ||
17 | diff --git a/block/rbd.c b/block/rbd.c | 18 | diff --git a/block/vpc.c b/block/vpc.c |
18 | index XXXXXXX..XXXXXXX 100644 | 19 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/block/rbd.c | 20 | --- a/block/vpc.c |
20 | +++ b/block/rbd.c | 21 | +++ b/block/vpc.c |
21 | @@ -XXX,XX +XXX,XX @@ done: | 22 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, |
22 | return; | 23 | } |
23 | } | 24 | } |
24 | 25 | ||
25 | - | 26 | +/* |
26 | -static void qemu_rbd_refresh_limits(BlockDriverState *bs, Error **errp) | 27 | + * Microsoft Virtual PC and Microsoft Hyper-V produce and read |
27 | -{ | 28 | + * VHD image sizes differently. VPC will rely on CHS geometry, |
28 | - /* XXX Does RBD support AIO on less than 512-byte alignment? */ | 29 | + * while Hyper-V and disk2vhd use the size specified in the footer. |
29 | - bs->bl.request_alignment = 512; | 30 | + * |
30 | -} | 31 | + * We use a couple of approaches to try and determine the correct method: |
31 | - | 32 | + * look at the Creator App field, and look for images that have CHS |
32 | - | 33 | + * geometry that is the maximum value. |
33 | static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts, | 34 | + * |
34 | Error **errp) | 35 | + * If the CHS geometry is the maximum CHS geometry, then we assume that |
36 | + * the size is the footer->current_size to avoid truncation. Otherwise, | ||
37 | + * we follow the table based on footer->creator_app: | ||
38 | + * | ||
39 | + * Known creator apps: | ||
40 | + * 'vpc ' : CHS Virtual PC (uses disk geometry) | ||
41 | + * 'qemu' : CHS QEMU (uses disk geometry) | ||
42 | + * 'qem2' : current_size QEMU (uses current_size) | ||
43 | + * 'win ' : current_size Hyper-V | ||
44 | + * 'd2v ' : current_size Disk2vhd | ||
45 | + * 'tap\0' : current_size XenServer | ||
46 | + * 'CTXS' : current_size XenConverter | ||
47 | + * | ||
48 | + * The user can override the table values via drive options, however | ||
49 | + * even with an override we will still use current_size for images | ||
50 | + * that have CHS geometry of the maximum size. | ||
51 | + */ | ||
52 | +static bool vpc_ignore_current_size(VHDFooter *footer) | ||
53 | +{ | ||
54 | + return !!strncmp(footer->creator_app, "win ", 4) && | ||
55 | + !!strncmp(footer->creator_app, "qem2", 4) && | ||
56 | + !!strncmp(footer->creator_app, "d2v ", 4) && | ||
57 | + !!strncmp(footer->creator_app, "CTXS", 4) && | ||
58 | + !!memcmp(footer->creator_app, "tap", 4)); | ||
59 | +} | ||
60 | + | ||
61 | static int vpc_open(BlockDriverState *bs, QDict *options, int flags, | ||
62 | Error **errp) | ||
35 | { | 63 | { |
36 | @@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_rbd = { | 64 | @@ -XXX,XX +XXX,XX @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags, |
37 | .format_name = "rbd", | 65 | bs->total_sectors = (int64_t) |
38 | .instance_size = sizeof(BDRVRBDState), | 66 | be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl; |
39 | .bdrv_parse_filename = qemu_rbd_parse_filename, | 67 | |
40 | - .bdrv_refresh_limits = qemu_rbd_refresh_limits, | 68 | - /* Microsoft Virtual PC and Microsoft Hyper-V produce and read |
41 | .bdrv_file_open = qemu_rbd_open, | 69 | - * VHD image sizes differently. VPC will rely on CHS geometry, |
42 | .bdrv_close = qemu_rbd_close, | 70 | - * while Hyper-V and disk2vhd use the size specified in the footer. |
43 | .bdrv_reopen_prepare = qemu_rbd_reopen_prepare, | 71 | - * |
72 | - * We use a couple of approaches to try and determine the correct method: | ||
73 | - * look at the Creator App field, and look for images that have CHS | ||
74 | - * geometry that is the maximum value. | ||
75 | - * | ||
76 | - * If the CHS geometry is the maximum CHS geometry, then we assume that | ||
77 | - * the size is the footer->current_size to avoid truncation. Otherwise, | ||
78 | - * we follow the table based on footer->creator_app: | ||
79 | - * | ||
80 | - * Known creator apps: | ||
81 | - * 'vpc ' : CHS Virtual PC (uses disk geometry) | ||
82 | - * 'qemu' : CHS QEMU (uses disk geometry) | ||
83 | - * 'qem2' : current_size QEMU (uses current_size) | ||
84 | - * 'win ' : current_size Hyper-V | ||
85 | - * 'd2v ' : current_size Disk2vhd | ||
86 | - * 'tap\0' : current_size XenServer | ||
87 | - * 'CTXS' : current_size XenConverter | ||
88 | - * | ||
89 | - * The user can override the table values via drive options, however | ||
90 | - * even with an override we will still use current_size for images | ||
91 | - * that have CHS geometry of the maximum size. | ||
92 | - */ | ||
93 | - use_chs = (!!strncmp(footer->creator_app, "win ", 4) && | ||
94 | - !!strncmp(footer->creator_app, "qem2", 4) && | ||
95 | - !!strncmp(footer->creator_app, "d2v ", 4) && | ||
96 | - !!strncmp(footer->creator_app, "CTXS", 4) && | ||
97 | - !!memcmp(footer->creator_app, "tap", 4)) || s->force_use_chs; | ||
98 | + /* Use CHS or current_size to determine the image size. */ | ||
99 | + use_chs = vpc_ignore_current_size(footer) || s->force_use_chs; | ||
100 | |||
101 | if (!use_chs || bs->total_sectors == VHD_MAX_GEOMETRY || s->force_use_sz) { | ||
102 | bs->total_sectors = be64_to_cpu(footer->current_size) / | ||
44 | -- | 103 | -- |
45 | 2.31.1 | 104 | 2.48.1 |
46 | 105 | ||
47 | 106 | diff view generated by jsdifflib |
1 | From: Peter Lieven <pl@kamp.de> | 1 | From: Vitaly Kuznetsov <vkuznets@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | task->complete is a bool not an integer. | 3 | It was found that 'qemu-nbd' is not able to work with some disk images |
4 | exported from Azure. Looking at the 512b footer (which contains VPC | ||
5 | metadata): | ||
4 | 6 | ||
5 | Signed-off-by: Peter Lieven <pl@kamp.de> | 7 | 00000000 63 6f 6e 65 63 74 69 78 00 00 00 02 00 01 00 00 |conectix........| |
6 | Message-Id: <20210707180449.32665-1-pl@kamp.de> | 8 | 00000010 ff ff ff ff ff ff ff ff 2e c7 9b 96 77 61 00 00 |............wa..| |
7 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | 9 | 00000020 00 07 00 00 57 69 32 6b 00 00 00 01 40 00 00 00 |....Wi2k....@...| |
10 | 00000030 00 00 00 01 40 00 00 00 28 a2 10 3f 00 00 00 02 |....@...(..?....| | ||
11 | 00000040 ff ff e7 47 8c 54 df 94 bd 35 71 4c 94 5f e5 44 |...G.T...5qL._.D| | ||
12 | 00000050 44 53 92 1a 00 00 00 00 00 00 00 00 00 00 00 00 |DS..............| | ||
13 | 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
14 | |||
15 | we can see that Azure uses a different 'Creator application' -- | ||
16 | 'wa\0\0' (offset 0x1c, likely reads as 'Windows Azure') and QEMU uses this | ||
17 | field to determine how it can get image size. Apparently, Azure uses 'new' | ||
18 | method, just like Hyper-V. | ||
19 | |||
20 | Overall, it seems that only VPC and old QEMUs need to be ignored as all new | ||
21 | creator apps seem to have reliable current_size. Invert the logic and make | ||
22 | 'current_size' method the default to avoid adding every new creator app to | ||
23 | the list. | ||
24 | |||
25 | Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> | ||
26 | Message-ID: <20241212134504.1983757-3-vkuznets@redhat.com> | ||
27 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 28 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
9 | --- | 29 | --- |
10 | block/rbd.c | 2 +- | 30 | block/vpc.c | 8 +++----- |
11 | 1 file changed, 1 insertion(+), 1 deletion(-) | 31 | 1 file changed, 3 insertions(+), 5 deletions(-) |
12 | 32 | ||
13 | diff --git a/block/rbd.c b/block/rbd.c | 33 | diff --git a/block/vpc.c b/block/vpc.c |
14 | index XXXXXXX..XXXXXXX 100644 | 34 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/block/rbd.c | 35 | --- a/block/vpc.c |
16 | +++ b/block/rbd.c | 36 | +++ b/block/vpc.c |
17 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_resize(BlockDriverState *bs, uint64_t size) | 37 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, |
18 | static void qemu_rbd_finish_bh(void *opaque) | 38 | * 'd2v ' : current_size Disk2vhd |
39 | * 'tap\0' : current_size XenServer | ||
40 | * 'CTXS' : current_size XenConverter | ||
41 | + * 'wa\0\0': current_size Azure | ||
42 | * | ||
43 | * The user can override the table values via drive options, however | ||
44 | * even with an override we will still use current_size for images | ||
45 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, | ||
46 | */ | ||
47 | static bool vpc_ignore_current_size(VHDFooter *footer) | ||
19 | { | 48 | { |
20 | RBDTask *task = opaque; | 49 | - return !!strncmp(footer->creator_app, "win ", 4) && |
21 | - task->complete = 1; | 50 | - !!strncmp(footer->creator_app, "qem2", 4) && |
22 | + task->complete = true; | 51 | - !!strncmp(footer->creator_app, "d2v ", 4) && |
23 | aio_co_wake(task->co); | 52 | - !!strncmp(footer->creator_app, "CTXS", 4) && |
53 | - !!memcmp(footer->creator_app, "tap", 4)); | ||
54 | + return !strncmp(footer->creator_app, "vpc ", 4) || | ||
55 | + !strncmp(footer->creator_app, "qemu", 4); | ||
24 | } | 56 | } |
25 | 57 | ||
58 | static int vpc_open(BlockDriverState *bs, QDict *options, int flags, | ||
26 | -- | 59 | -- |
27 | 2.31.1 | 60 | 2.48.1 |
28 | |||
29 | diff view generated by jsdifflib |
1 | From: Heinrich Schuchardt <xypron.glpk@gmx.de> | 1 | From: Philippe Mathieu-Daudé <philmd@linaro.org> |
---|---|---|---|
2 | 2 | ||
3 | uri_free() checks if its argument is NULL in uri_clean() and g_free(). | 3 | Expose the method docstring in the header, and mention |
4 | There is no need to check the argument before the call. | 4 | returned value must be free'd by caller. |
5 | 5 | ||
6 | Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de> | 6 | Reported-by: Fabiano Rosas <farosas@suse.de> |
7 | Message-Id: <20210629063602.4239-1-xypron.glpk@gmx.de> | 7 | Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> |
8 | Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> | 8 | Message-ID: <20241111170333.43833-2-philmd@linaro.org> |
9 | Reviewed-by: Richard W.M. Jones <rjones@redhat.com> | ||
10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | --- | 10 | --- |
12 | block/nfs.c | 4 +--- | 11 | include/system/block-backend-io.h | 7 +++++++ |
13 | block/ssh.c | 4 +--- | 12 | block/block-backend.c | 12 ++++++++---- |
14 | util/uri.c | 22 ++++++---------------- | 13 | 2 files changed, 15 insertions(+), 4 deletions(-) |
15 | 3 files changed, 8 insertions(+), 22 deletions(-) | ||
16 | 14 | ||
17 | diff --git a/block/nfs.c b/block/nfs.c | 15 | diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h |
18 | index XXXXXXX..XXXXXXX 100644 | 16 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/block/nfs.c | 17 | --- a/include/system/block-backend-io.h |
20 | +++ b/block/nfs.c | 18 | +++ b/include/system/block-backend-io.h |
21 | @@ -XXX,XX +XXX,XX @@ out: | 19 | @@ -XXX,XX +XXX,XX @@ void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow); |
22 | if (qp) { | 20 | void blk_set_disable_request_queuing(BlockBackend *blk, bool disable); |
23 | query_params_free(qp); | 21 | bool blk_iostatus_is_enabled(const BlockBackend *blk); |
24 | } | 22 | |
25 | - if (uri) { | 23 | +/* |
26 | - uri_free(uri); | 24 | + * Return the qdev ID, or if no ID is assigned the QOM path, |
27 | - } | 25 | + * of the block device attached to the BlockBackend. |
28 | + uri_free(uri); | 26 | + * |
29 | return ret; | 27 | + * The caller is responsible for releasing the value returned |
28 | + * with g_free() after use. | ||
29 | + */ | ||
30 | char *blk_get_attached_dev_id(BlockBackend *blk); | ||
31 | |||
32 | BlockAIOCB *blk_aio_pwrite_zeroes(BlockBackend *blk, int64_t offset, | ||
33 | diff --git a/block/block-backend.c b/block/block-backend.c | ||
34 | index XXXXXXX..XXXXXXX 100644 | ||
35 | --- a/block/block-backend.c | ||
36 | +++ b/block/block-backend.c | ||
37 | @@ -XXX,XX +XXX,XX @@ DeviceState *blk_get_attached_dev(BlockBackend *blk) | ||
38 | return blk->dev; | ||
30 | } | 39 | } |
31 | 40 | ||
32 | diff --git a/block/ssh.c b/block/ssh.c | 41 | +/* |
33 | index XXXXXXX..XXXXXXX 100644 | 42 | + * The caller is responsible for releasing the value returned |
34 | --- a/block/ssh.c | 43 | + * with g_free() after use. |
35 | +++ b/block/ssh.c | 44 | + */ |
36 | @@ -XXX,XX +XXX,XX @@ static int parse_uri(const char *filename, QDict *options, Error **errp) | 45 | static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) |
37 | return 0; | 46 | { |
38 | 47 | DeviceState *dev = blk->dev; | |
39 | err: | 48 | @@ -XXX,XX +XXX,XX @@ static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) |
40 | - if (uri) { | 49 | return object_get_canonical_path(OBJECT(dev)) ?: g_strdup(""); |
41 | - uri_free(uri); | ||
42 | - } | ||
43 | + uri_free(uri); | ||
44 | return -EINVAL; | ||
45 | } | 50 | } |
46 | 51 | ||
47 | diff --git a/util/uri.c b/util/uri.c | 52 | -/* |
48 | index XXXXXXX..XXXXXXX 100644 | 53 | - * Return the qdev ID, or if no ID is assigned the QOM path, of the block |
49 | --- a/util/uri.c | 54 | - * device attached to the BlockBackend. |
50 | +++ b/util/uri.c | 55 | - */ |
51 | @@ -XXX,XX +XXX,XX @@ static void uri_clean(URI *uri) | 56 | char *blk_get_attached_dev_id(BlockBackend *blk) |
52 | 57 | { | |
53 | /** | 58 | return blk_get_attached_dev_id_or_path(blk, true); |
54 | * uri_free: | ||
55 | - * @uri: pointer to an URI | ||
56 | + * @uri: pointer to an URI, NULL is ignored | ||
57 | * | ||
58 | * Free up the URI struct | ||
59 | */ | ||
60 | @@ -XXX,XX +XXX,XX @@ step_7: | ||
61 | val = uri_to_string(res); | ||
62 | |||
63 | done: | ||
64 | - if (ref != NULL) { | ||
65 | - uri_free(ref); | ||
66 | - } | ||
67 | - if (bas != NULL) { | ||
68 | - uri_free(bas); | ||
69 | - } | ||
70 | - if (res != NULL) { | ||
71 | - uri_free(res); | ||
72 | - } | ||
73 | + uri_free(ref); | ||
74 | + uri_free(bas); | ||
75 | + uri_free(res); | ||
76 | return val; | ||
77 | } | 59 | } |
78 | 60 | ||
79 | @@ -XXX,XX +XXX,XX @@ done: | 61 | +/* |
80 | if (remove_path != 0) { | 62 | + * The caller is responsible for releasing the value returned |
81 | ref->path = NULL; | 63 | + * with g_free() after use. |
82 | } | 64 | + */ |
83 | - if (ref != NULL) { | 65 | static char *blk_get_attached_dev_path(BlockBackend *blk) |
84 | - uri_free(ref); | 66 | { |
85 | - } | 67 | return blk_get_attached_dev_id_or_path(blk, false); |
86 | - if (bas != NULL) { | ||
87 | - uri_free(bas); | ||
88 | - } | ||
89 | + uri_free(ref); | ||
90 | + uri_free(bas); | ||
91 | |||
92 | return val; | ||
93 | } | ||
94 | -- | 68 | -- |
95 | 2.31.1 | 69 | 2.48.1 |
96 | 70 | ||
97 | 71 | diff view generated by jsdifflib |
1 | From: Alberto Garcia <berto@igalia.com> | 1 | From: Fabiano Rosas <farosas@suse.de> |
---|---|---|---|
2 | 2 | ||
3 | This test swaps the images used by two active block devices. | 3 | ASAN detected a leak when running the ahci-test |
4 | /ahci/io/dma/lba28/retry: | ||
4 | 5 | ||
5 | This is now possible thanks to the new ability to run | 6 | Direct leak of 35 byte(s) in 1 object(s) allocated from: |
6 | x-blockdev-reopen on multiple devices at the same time. | 7 | #0 in malloc |
8 | #1 in __vasprintf_internal | ||
9 | #2 in vasprintf | ||
10 | #3 in g_vasprintf | ||
11 | #4 in g_strdup_vprintf | ||
12 | #5 in g_strdup_printf | ||
13 | #6 in object_get_canonical_path ../qom/object.c:2096:19 | ||
14 | #7 in blk_get_attached_dev_id_or_path ../block/block-backend.c:1033:12 | ||
15 | #8 in blk_get_attached_dev_path ../block/block-backend.c:1047:12 | ||
16 | #9 in send_qmp_error_event ../block/block-backend.c:2140:36 | ||
17 | #10 in blk_error_action ../block/block-backend.c:2172:9 | ||
18 | #11 in ide_handle_rw_error ../hw/ide/core.c:875:5 | ||
19 | #12 in ide_dma_cb ../hw/ide/core.c:894:13 | ||
20 | #13 in dma_complete ../system/dma-helpers.c:107:9 | ||
21 | #14 in dma_blk_cb ../system/dma-helpers.c:129:9 | ||
22 | #15 in blk_aio_complete ../block/block-backend.c:1552:9 | ||
23 | #16 in blk_aio_write_entry ../block/block-backend.c:1619:5 | ||
24 | #17 in coroutine_trampoline ../util/coroutine-ucontext.c:175:9 | ||
7 | 25 | ||
8 | Signed-off-by: Alberto Garcia <berto@igalia.com> | 26 | Plug the leak by freeing the device path string. |
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 27 | |
10 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 28 | Signed-off-by: Fabiano Rosas <farosas@suse.de> |
11 | Message-Id: <20210708114709.206487-6-kwolf@redhat.com> | 29 | Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> |
30 | Message-ID: <20241111145214.8261-1-farosas@suse.de> | ||
31 | [PMD: Use g_autofree] | ||
32 | Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> | ||
33 | Message-ID: <20241111170333.43833-3-philmd@linaro.org> | ||
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 34 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | --- | 35 | --- |
14 | tests/qemu-iotests/245 | 47 ++++++++++++++++++++++++++++++++++++++ | 36 | block/block-backend.c | 4 ++-- |
15 | tests/qemu-iotests/245.out | 4 ++-- | 37 | 1 file changed, 2 insertions(+), 2 deletions(-) |
16 | 2 files changed, 49 insertions(+), 2 deletions(-) | ||
17 | 38 | ||
18 | diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 | 39 | diff --git a/block/block-backend.c b/block/block-backend.c |
19 | index XXXXXXX..XXXXXXX 100755 | ||
20 | --- a/tests/qemu-iotests/245 | ||
21 | +++ b/tests/qemu-iotests/245 | ||
22 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
23 | '-c', 'read -P 0x40 0x40008 1', | ||
24 | '-c', 'read -P 0x80 0x40010 1', hd_path[0]) | ||
25 | |||
26 | + # Swap the disk images of two active block devices | ||
27 | + def test_swap_files(self): | ||
28 | + # Add hd0 and hd2 (none of them with backing files) | ||
29 | + opts0 = hd_opts(0) | ||
30 | + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts0) | ||
31 | + self.assert_qmp(result, 'return', {}) | ||
32 | + | ||
33 | + opts2 = hd_opts(2) | ||
34 | + result = self.vm.qmp('blockdev-add', conv_keys = False, **opts2) | ||
35 | + self.assert_qmp(result, 'return', {}) | ||
36 | + | ||
37 | + # Write different data to both block devices | ||
38 | + self.run_qemu_io("hd0", "write -P 0xa0 0 1k") | ||
39 | + self.run_qemu_io("hd2", "write -P 0xa2 0 1k") | ||
40 | + | ||
41 | + # Check that the data reads correctly | ||
42 | + self.run_qemu_io("hd0", "read -P 0xa0 0 1k") | ||
43 | + self.run_qemu_io("hd2", "read -P 0xa2 0 1k") | ||
44 | + | ||
45 | + # It's not possible to make a block device use an image that | ||
46 | + # is already being used by the other device. | ||
47 | + self.reopen(opts0, {'file': 'hd2-file'}, | ||
48 | + "Permission conflict on node 'hd2-file': permissions " | ||
49 | + "'write, resize' are both required by node 'hd2' (uses " | ||
50 | + "node 'hd2-file' as 'file' child) and unshared by node " | ||
51 | + "'hd0' (uses node 'hd2-file' as 'file' child).") | ||
52 | + self.reopen(opts2, {'file': 'hd0-file'}, | ||
53 | + "Permission conflict on node 'hd0-file': permissions " | ||
54 | + "'write, resize' are both required by node 'hd0' (uses " | ||
55 | + "node 'hd0-file' as 'file' child) and unshared by node " | ||
56 | + "'hd2' (uses node 'hd0-file' as 'file' child).") | ||
57 | + | ||
58 | + # But we can swap the images if we reopen both devices at the | ||
59 | + # same time | ||
60 | + opts0['file'] = 'hd2-file' | ||
61 | + opts2['file'] = 'hd0-file' | ||
62 | + self.reopenMultiple([opts0, opts2]) | ||
63 | + self.run_qemu_io("hd0", "read -P 0xa2 0 1k") | ||
64 | + self.run_qemu_io("hd2", "read -P 0xa0 0 1k") | ||
65 | + | ||
66 | + # And we can of course come back to the original state | ||
67 | + opts0['file'] = 'hd0-file' | ||
68 | + opts2['file'] = 'hd2-file' | ||
69 | + self.reopenMultiple([opts0, opts2]) | ||
70 | + self.run_qemu_io("hd0", "read -P 0xa0 0 1k") | ||
71 | + self.run_qemu_io("hd2", "read -P 0xa2 0 1k") | ||
72 | + | ||
73 | # Misc reopen tests with different block drivers | ||
74 | @iotests.skip_if_unsupported(['quorum', 'throttle']) | ||
75 | def test_misc_drivers(self): | ||
76 | diff --git a/tests/qemu-iotests/245.out b/tests/qemu-iotests/245.out | ||
77 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
78 | --- a/tests/qemu-iotests/245.out | 41 | --- a/block/block-backend.c |
79 | +++ b/tests/qemu-iotests/245.out | 42 | +++ b/block/block-backend.c |
80 | @@ -XXX,XX +XXX,XX @@ read 1/1 bytes at offset 262152 | 43 | @@ -XXX,XX +XXX,XX @@ static void send_qmp_error_event(BlockBackend *blk, |
81 | read 1/1 bytes at offset 262160 | 44 | { |
82 | 1 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | 45 | IoOperationType optype; |
83 | 46 | BlockDriverState *bs = blk_bs(blk); | |
84 | -.............. | 47 | + g_autofree char *path = blk_get_attached_dev_path(blk); |
85 | +............... | 48 | |
86 | ---------------------------------------------------------------------- | 49 | optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE; |
87 | -Ran 24 tests | 50 | - qapi_event_send_block_io_error(blk_name(blk), |
88 | +Ran 25 tests | 51 | - blk_get_attached_dev_path(blk), |
89 | 52 | + qapi_event_send_block_io_error(blk_name(blk), path, | |
90 | OK | 53 | bs ? bdrv_get_node_name(bs) : NULL, optype, |
54 | action, blk_iostatus_is_enabled(blk), | ||
55 | error == ENOSPC, strerror(error)); | ||
91 | -- | 56 | -- |
92 | 2.31.1 | 57 | 2.48.1 |
93 | 58 | ||
94 | 59 | diff view generated by jsdifflib |
1 | From: Eric Blake <eblake@redhat.com> | 1 | From: Peter Xu <peterx@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | When removeing support for qemu-img being able to create backing | 3 | It's easier for either debugging plugin errors, or issue reports. |
4 | chains without embedded backing formats, we caused a poor error | ||
5 | message as caught by iotest 114. Improve the situation to inform the | ||
6 | user what went wrong. | ||
7 | 4 | ||
8 | Suggested-by: Kevin Wolf <kwolf@redhat.com> | 5 | Signed-off-by: Peter Xu <peterx@redhat.com> |
9 | Signed-off-by: Eric Blake <eblake@redhat.com> | 6 | Message-ID: <20241212204801.1420528-2-peterx@redhat.com> |
10 | Message-Id: <20210708155228.2666172-1-eblake@redhat.com> | 7 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> |
11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
12 | --- | 9 | --- |
13 | qemu-img.c | 3 +++ | 10 | scripts/qemu-gdb.py | 2 ++ |
14 | tests/qemu-iotests/114.out | 2 +- | 11 | 1 file changed, 2 insertions(+) |
15 | 2 files changed, 4 insertions(+), 1 deletion(-) | ||
16 | 12 | ||
17 | diff --git a/qemu-img.c b/qemu-img.c | 13 | diff --git a/scripts/qemu-gdb.py b/scripts/qemu-gdb.py |
18 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/qemu-img.c | 15 | --- a/scripts/qemu-gdb.py |
20 | +++ b/qemu-img.c | 16 | +++ b/scripts/qemu-gdb.py |
21 | @@ -XXX,XX +XXX,XX @@ static int img_rebase(int argc, char **argv) | 17 | @@ -XXX,XX +XXX,XX @@ def __init__(self): |
22 | if (ret == -ENOSPC) { | 18 | # Default to silently passing through SIGUSR1, because QEMU sends it |
23 | error_report("Could not change the backing file to '%s': No " | 19 | # to itself a lot. |
24 | "space left in the file header", out_baseimg); | 20 | gdb.execute('handle SIGUSR1 pass noprint nostop') |
25 | + } else if (ret == -EINVAL && out_baseimg && !out_basefmt) { | 21 | +# Always print full stack for python errors, easier to debug and report issues |
26 | + error_report("Could not change the backing file to '%s': backing " | 22 | +gdb.execute('set python print-stack full') |
27 | + "format must be specified", out_baseimg); | ||
28 | } else if (ret < 0) { | ||
29 | error_report("Could not change the backing file to '%s': %s", | ||
30 | out_baseimg, strerror(-ret)); | ||
31 | diff --git a/tests/qemu-iotests/114.out b/tests/qemu-iotests/114.out | ||
32 | index XXXXXXX..XXXXXXX 100644 | ||
33 | --- a/tests/qemu-iotests/114.out | ||
34 | +++ b/tests/qemu-iotests/114.out | ||
35 | @@ -XXX,XX +XXX,XX @@ qemu-io: can't open device TEST_DIR/t.qcow2: Could not open backing file: Unknow | ||
36 | no file open, try 'help open' | ||
37 | read 4096/4096 bytes at offset 0 | ||
38 | 4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
39 | -qemu-img: Could not change the backing file to 'TEST_DIR/t.qcow2.base': Invalid argument | ||
40 | +qemu-img: Could not change the backing file to 'TEST_DIR/t.qcow2.base': backing format must be specified | ||
41 | read 4096/4096 bytes at offset 0 | ||
42 | 4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
43 | *** done | ||
44 | -- | 23 | -- |
45 | 2.31.1 | 24 | 2.48.1 |
46 | |||
47 | diff view generated by jsdifflib |
1 | From: Eric Blake <eblake@redhat.com> | 1 | From: Peter Xu <peterx@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | This was deprecated back in bc5ee6da7 (qcow2: Deprecate use of | 3 | There're a bunch of code trying to fetch fs_base in different ways. IIUC |
4 | qemu-img amend to change backing file), and no one in the meantime has | 4 | the simplest way instead is "$fs_base". It also has the benefit that it'll |
5 | given any reasons why it should be supported. Time to make change | 5 | work for both live gdb session or coredumps. |
6 | attempts a hard error (but for convenience, specifying the _same_ | ||
7 | backing chain is not forbidden). Update a couple of iotests to match. | ||
8 | 6 | ||
9 | Signed-off-by: Eric Blake <eblake@redhat.com> | 7 | Signed-off-by: Peter Xu <peterx@redhat.com> |
10 | Message-Id: <20210503213600.569128-2-eblake@redhat.com> | 8 | Message-ID: <20241212204801.1420528-3-peterx@redhat.com> |
11 | Reviewed-by: Connor Kuehl <ckuehl@redhat.com> | 9 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> |
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | --- | 11 | --- |
14 | docs/system/deprecated.rst | 12 ------------ | 12 | scripts/qemugdb/coroutine.py | 23 ++--------------------- |
15 | docs/system/removed-features.rst | 12 ++++++++++++ | 13 | 1 file changed, 2 insertions(+), 21 deletions(-) |
16 | block/qcow2.c | 13 ++++--------- | ||
17 | tests/qemu-iotests/061 | 3 +++ | ||
18 | tests/qemu-iotests/061.out | 3 ++- | ||
19 | tests/qemu-iotests/082.out | 6 ++++-- | ||
20 | 6 files changed, 25 insertions(+), 24 deletions(-) | ||
21 | 14 | ||
22 | diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst | 15 | diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py |
23 | index XXXXXXX..XXXXXXX 100644 | 16 | index XXXXXXX..XXXXXXX 100644 |
24 | --- a/docs/system/deprecated.rst | 17 | --- a/scripts/qemugdb/coroutine.py |
25 | +++ b/docs/system/deprecated.rst | 18 | +++ b/scripts/qemugdb/coroutine.py |
26 | @@ -XXX,XX +XXX,XX @@ this CPU is also deprecated. | 19 | @@ -XXX,XX +XXX,XX @@ |
27 | Related binaries | 20 | |
28 | ---------------- | 21 | VOID_PTR = gdb.lookup_type('void').pointer() |
29 | 22 | ||
30 | -qemu-img amend to adjust backing file (since 5.1) | 23 | -def get_fs_base(): |
31 | -''''''''''''''''''''''''''''''''''''''''''''''''' | 24 | - '''Fetch %fs base value using arch_prctl(ARCH_GET_FS). This is |
25 | - pthread_self().''' | ||
26 | - # %rsp - 120 is scratch space according to the SystemV ABI | ||
27 | - old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') | ||
28 | - gdb.execute('call (int)arch_prctl(0x1003, $rsp - 120)', False, True) | ||
29 | - fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') | ||
30 | - gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True) | ||
31 | - return fs_base | ||
32 | - | 32 | - |
33 | -The use of ``qemu-img amend`` to modify the name or format of a qcow2 | 33 | def pthread_self(): |
34 | -backing image is deprecated; this functionality was never fully | 34 | - '''Fetch pthread_self() from the glibc start_thread function.''' |
35 | -documented or tested, and interferes with other amend operations that | 35 | - f = gdb.newest_frame() |
36 | -need access to the original backing image (such as deciding whether a | 36 | - while f.name() != 'start_thread': |
37 | -v3 zero cluster may be left unallocated when converting to a v2 | 37 | - f = f.older() |
38 | -image). Rather, any changes to the backing chain should be performed | 38 | - if f is None: |
39 | -with ``qemu-img rebase -u`` either before or after the remaining | 39 | - return get_fs_base() |
40 | -changes being performed by amend, as appropriate. | ||
41 | - | 40 | - |
42 | qemu-img backing file without format (since 5.1) | 41 | - try: |
43 | '''''''''''''''''''''''''''''''''''''''''''''''' | 42 | - return f.read_var("arg") |
44 | 43 | - except ValueError: | |
45 | diff --git a/docs/system/removed-features.rst b/docs/system/removed-features.rst | 44 | - return get_fs_base() |
46 | index XXXXXXX..XXXXXXX 100644 | 45 | + '''Fetch the base address of TLS.''' |
47 | --- a/docs/system/removed-features.rst | 46 | + return gdb.parse_and_eval("$fs_base") |
48 | +++ b/docs/system/removed-features.rst | 47 | |
49 | @@ -XXX,XX +XXX,XX @@ topologies described with -smp include all possible cpus, i.e. | 48 | def get_glibc_pointer_guard(): |
50 | The ``enforce-config-section`` property was replaced by the | 49 | '''Fetch glibc pointer guard value''' |
51 | ``-global migration.send-configuration={on|off}`` option. | ||
52 | |||
53 | +qemu-img amend to adjust backing file (removed in 6.1) | ||
54 | +'''''''''''''''''''''''''''''''''''''''''''''''''''''' | ||
55 | + | ||
56 | +The use of ``qemu-img amend`` to modify the name or format of a qcow2 | ||
57 | +backing image was never fully documented or tested, and interferes | ||
58 | +with other amend operations that need access to the original backing | ||
59 | +image (such as deciding whether a v3 zero cluster may be left | ||
60 | +unallocated when converting to a v2 image). Any changes to the | ||
61 | +backing chain should be performed with ``qemu-img rebase -u`` either | ||
62 | +before or after the remaining changes being performed by amend, as | ||
63 | +appropriate. | ||
64 | + | ||
65 | Block devices | ||
66 | ------------- | ||
67 | |||
68 | diff --git a/block/qcow2.c b/block/qcow2.c | ||
69 | index XXXXXXX..XXXXXXX 100644 | ||
70 | --- a/block/qcow2.c | ||
71 | +++ b/block/qcow2.c | ||
72 | @@ -XXX,XX +XXX,XX @@ static int qcow2_amend_options(BlockDriverState *bs, QemuOpts *opts, | ||
73 | if (backing_file || backing_format) { | ||
74 | if (g_strcmp0(backing_file, s->image_backing_file) || | ||
75 | g_strcmp0(backing_format, s->image_backing_format)) { | ||
76 | - warn_report("Deprecated use of amend to alter the backing file; " | ||
77 | - "use qemu-img rebase instead"); | ||
78 | - } | ||
79 | - ret = qcow2_change_backing_file(bs, | ||
80 | - backing_file ?: s->image_backing_file, | ||
81 | - backing_format ?: s->image_backing_format); | ||
82 | - if (ret < 0) { | ||
83 | - error_setg_errno(errp, -ret, "Failed to change the backing file"); | ||
84 | - return ret; | ||
85 | + error_setg(errp, "Cannot amend the backing file"); | ||
86 | + error_append_hint(errp, | ||
87 | + "You can use 'qemu-img rebase' instead.\n"); | ||
88 | + return -EINVAL; | ||
89 | } | ||
90 | } | ||
91 | |||
92 | diff --git a/tests/qemu-iotests/061 b/tests/qemu-iotests/061 | ||
93 | index XXXXXXX..XXXXXXX 100755 | ||
94 | --- a/tests/qemu-iotests/061 | ||
95 | +++ b/tests/qemu-iotests/061 | ||
96 | @@ -XXX,XX +XXX,XX @@ _make_test_img -o "compat=1.1" 64M | ||
97 | TEST_IMG="$TEST_IMG.base" _make_test_img -o "compat=1.1" 64M | ||
98 | $QEMU_IO -c "write -P 0x2a 0 128k" "$TEST_IMG.base" | _filter_qemu_io | ||
99 | $QEMU_IO -c "read -P 0 0 128k" "$TEST_IMG" | _filter_qemu_io | ||
100 | +$QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" \ | ||
101 | + "$TEST_IMG" && echo "unexpected pass" | ||
102 | +$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F qcow2 "$TEST_IMG" | ||
103 | $QEMU_IMG amend -o "backing_file=$TEST_IMG.base,backing_fmt=qcow2" "$TEST_IMG" | ||
104 | $QEMU_IO -c "read -P 0x2a 0 128k" "$TEST_IMG" | _filter_qemu_io | ||
105 | _check_test_img | ||
106 | diff --git a/tests/qemu-iotests/061.out b/tests/qemu-iotests/061.out | ||
107 | index XXXXXXX..XXXXXXX 100644 | ||
108 | --- a/tests/qemu-iotests/061.out | ||
109 | +++ b/tests/qemu-iotests/061.out | ||
110 | @@ -XXX,XX +XXX,XX @@ wrote 131072/131072 bytes at offset 0 | ||
111 | 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
112 | read 131072/131072 bytes at offset 0 | ||
113 | 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
114 | -qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead | ||
115 | +qemu-img: Cannot amend the backing file | ||
116 | +You can use 'qemu-img rebase' instead. | ||
117 | read 131072/131072 bytes at offset 0 | ||
118 | 128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
119 | No errors were found on the image. | ||
120 | diff --git a/tests/qemu-iotests/082.out b/tests/qemu-iotests/082.out | ||
121 | index XXXXXXX..XXXXXXX 100644 | ||
122 | --- a/tests/qemu-iotests/082.out | ||
123 | +++ b/tests/qemu-iotests/082.out | ||
124 | @@ -XXX,XX +XXX,XX @@ Amend options for 'qcow2': | ||
125 | size=<size> - Virtual disk size | ||
126 | |||
127 | Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,help TEST_DIR/t.qcow2 | ||
128 | -qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead | ||
129 | +qemu-img: Cannot amend the backing file | ||
130 | +You can use 'qemu-img rebase' instead. | ||
131 | |||
132 | Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2 | ||
133 | |||
134 | Testing: amend -f qcow2 -o backing_file=TEST_DIR/t.qcow2,,? TEST_DIR/t.qcow2 | ||
135 | -qemu-img: warning: Deprecated use of amend to alter the backing file; use qemu-img rebase instead | ||
136 | +qemu-img: Cannot amend the backing file | ||
137 | +You can use 'qemu-img rebase' instead. | ||
138 | |||
139 | Testing: rebase -u -b -f qcow2 TEST_DIR/t.qcow2 | ||
140 | |||
141 | -- | 50 | -- |
142 | 2.31.1 | 51 | 2.48.1 |
143 | |||
144 | diff view generated by jsdifflib |
1 | dev->max_queues was never initialised for backends that don't support | 1 | From: Peter Xu <peterx@redhat.com> |
---|---|---|---|
2 | VHOST_USER_PROTOCOL_F_MQ, so it would use 0 as the maximum number of | ||
3 | queues to check against and consequently fail for any such backend. | ||
4 | 2 | ||
5 | Set it to 1 if the backend doesn't have multiqueue support. | 3 | Dumping coroutines don't yet work with coredumps. Let's make it work. |
6 | 4 | ||
7 | Fixes: c90bd505a3e8210c23d69fecab9ee6f56ec4a161 | 5 | We still kept most of the old code because they can be either more |
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 6 | flexible, or prettier. Only add the fallbacks when they stop working. |
9 | Message-Id: <20210705171429.29286-1-kwolf@redhat.com> | 7 | |
10 | Reviewed-by: Cornelia Huck <cohuck@redhat.com> | 8 | Currently the raw unwind is pretty ugly, but it works, like this: |
11 | Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com> | 9 | |
10 | (gdb) qemu bt | ||
11 | #0 process_incoming_migration_co (opaque=0x0) at ../migration/migration.c:788 | ||
12 | #1 0x000055ae6c0dc4d9 in coroutine_trampoline (i0=-1711718576, i1=21934) at ../util/coroutine-ucontext.c:175 | ||
13 | #2 0x00007f9f59d72f40 in ??? () at /lib64/libc.so.6 | ||
14 | #3 0x00007ffd549214a0 in ??? () | ||
15 | #4 0x0000000000000000 in ??? () | ||
16 | Coroutine at 0x7f9f4c57c748: | ||
17 | #0 0x55ae6c0dc9a8 in qemu_coroutine_switch<+120> () at ../util/coroutine-ucontext.c:321 | ||
18 | #1 0x55ae6c0da2f8 in qemu_aio_coroutine_enter<+356> () at ../util/qemu-coroutine.c:293 | ||
19 | #2 0x55ae6c0da3f1 in qemu_coroutine_enter<+34> () at ../util/qemu-coroutine.c:316 | ||
20 | #3 0x55ae6baf775e in migration_incoming_process<+43> () at ../migration/migration.c:876 | ||
21 | #4 0x55ae6baf7ab4 in migration_ioc_process_incoming<+490> () at ../migration/migration.c:1008 | ||
22 | #5 0x55ae6bae9ae7 in migration_channel_process_incoming<+145> () at ../migration/channel.c:45 | ||
23 | #6 0x55ae6bb18e35 in socket_accept_incoming_migration<+118> () at ../migration/socket.c:132 | ||
24 | #7 0x55ae6be939ef in qio_net_listener_channel_func<+131> () at ../io/net-listener.c:54 | ||
25 | #8 0x55ae6be8ce1a in qio_channel_fd_source_dispatch<+78> () at ../io/channel-watch.c:84 | ||
26 | #9 0x7f9f5b26728c in g_main_context_dispatch_unlocked.lto_priv<+315> () | ||
27 | #10 0x7f9f5b267555 in g_main_context_dispatch<+36> () | ||
28 | #11 0x55ae6c0d91a7 in glib_pollfds_poll<+90> () at ../util/main-loop.c:287 | ||
29 | #12 0x55ae6c0d9235 in os_host_main_loop_wait<+128> () at ../util/main-loop.c:310 | ||
30 | #13 0x55ae6c0d9364 in main_loop_wait<+203> () at ../util/main-loop.c:589 | ||
31 | #14 0x55ae6bac212a in qemu_main_loop<+41> () at ../system/runstate.c:835 | ||
32 | #15 0x55ae6bfdf522 in qemu_default_main<+19> () at ../system/main.c:37 | ||
33 | #16 0x55ae6bfdf55f in main<+40> () at ../system/main.c:48 | ||
34 | #17 0x7f9f59d42248 in __libc_start_call_main<+119> () | ||
35 | #18 0x7f9f59d4230b in __libc_start_main_impl<+138> () | ||
36 | |||
37 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
38 | Message-ID: <20241212204801.1420528-4-peterx@redhat.com> | ||
39 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 40 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | --- | 41 | --- |
14 | hw/virtio/vhost-user.c | 3 +++ | 42 | scripts/qemugdb/coroutine.py | 79 +++++++++++++++++++++++++++++++++--- |
15 | 1 file changed, 3 insertions(+) | 43 | 1 file changed, 73 insertions(+), 6 deletions(-) |
16 | 44 | ||
17 | diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c | 45 | diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py |
18 | index XXXXXXX..XXXXXXX 100644 | 46 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/hw/virtio/vhost-user.c | 47 | --- a/scripts/qemugdb/coroutine.py |
20 | +++ b/hw/virtio/vhost-user.c | 48 | +++ b/scripts/qemugdb/coroutine.py |
21 | @@ -XXX,XX +XXX,XX @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque, | 49 | @@ -XXX,XX +XXX,XX @@ def get_jmpbuf_regs(jmpbuf): |
22 | if (err < 0) { | 50 | 'r15': jmpbuf[JB_R15], |
23 | return -EPROTO; | 51 | 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } |
24 | } | 52 | |
25 | + } else { | 53 | -def bt_jmpbuf(jmpbuf): |
26 | + dev->max_queues = 1; | 54 | - '''Backtrace a jmpbuf''' |
27 | } | 55 | - regs = get_jmpbuf_regs(jmpbuf) |
56 | +def symbol_lookup(addr): | ||
57 | + # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" | ||
58 | + result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() | ||
59 | + try: | ||
60 | + if "+" in result: | ||
61 | + (func, result) = result.split(" + ") | ||
62 | + (offset, result) = result.split(" in ") | ||
63 | + else: | ||
64 | + offset = "0" | ||
65 | + (func, result) = result.split(" in ") | ||
66 | + func_str = f"{func}<+{offset}> ()" | ||
67 | + except: | ||
68 | + return f"??? ({result})" | ||
28 | + | 69 | + |
29 | if (dev->num_queues && dev->max_queues < dev->num_queues) { | 70 | + # Example: Line 321 of "../util/coroutine-ucontext.c" starts at address |
30 | error_setg(errp, "The maximum number of queues supported by the " | 71 | + # 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab |
31 | "backend is %" PRIu64, dev->max_queues); | 72 | + # <qemu_coroutine_switch+123>. |
73 | + result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip() | ||
74 | + if not result.startswith("Line "): | ||
75 | + return func_str | ||
76 | + result = result[5:] | ||
77 | + | ||
78 | + try: | ||
79 | + result = result.split(" starts ")[0] | ||
80 | + (line, path) = result.split(" of ") | ||
81 | + path = path.replace("\"", "") | ||
82 | + except: | ||
83 | + return func_str | ||
84 | + | ||
85 | + return f"{func_str} at {path}:{line}" | ||
86 | + | ||
87 | +def dump_backtrace(regs): | ||
88 | + ''' | ||
89 | + Backtrace dump with raw registers, mimic GDB command 'bt'. | ||
90 | + ''' | ||
91 | + # Here only rbp and rip that matter.. | ||
92 | + rbp = regs['rbp'] | ||
93 | + rip = regs['rip'] | ||
94 | + i = 0 | ||
95 | + | ||
96 | + while rbp: | ||
97 | + # For all return addresses on stack, we want to look up symbol/line | ||
98 | + # on the CALL command, because the return address is the next | ||
99 | + # instruction instead of the CALL. Here -1 would work for any | ||
100 | + # sized CALL instruction. | ||
101 | + print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}") | ||
102 | + rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") | ||
103 | + rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") | ||
104 | + i += 1 | ||
105 | + | ||
106 | +def dump_backtrace_live(regs): | ||
107 | + ''' | ||
108 | + Backtrace dump with gdb's 'bt' command, only usable in a live session. | ||
109 | + ''' | ||
110 | old = dict() | ||
111 | |||
112 | # remember current stack frame and select the topmost | ||
113 | @@ -XXX,XX +XXX,XX @@ def bt_jmpbuf(jmpbuf): | ||
114 | |||
115 | selected_frame.select() | ||
116 | |||
117 | +def bt_jmpbuf(jmpbuf): | ||
118 | + '''Backtrace a jmpbuf''' | ||
119 | + regs = get_jmpbuf_regs(jmpbuf) | ||
120 | + try: | ||
121 | + # This reuses gdb's "bt" command, which can be slightly prettier | ||
122 | + # but only works with live sessions. | ||
123 | + dump_backtrace_live(regs) | ||
124 | + except: | ||
125 | + # If above doesn't work, fallback to poor man's unwind | ||
126 | + dump_backtrace(regs) | ||
127 | + | ||
128 | def co_cast(co): | ||
129 | return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) | ||
130 | |||
131 | @@ -XXX,XX +XXX,XX @@ def invoke(self, arg, from_tty): | ||
132 | |||
133 | gdb.execute("bt") | ||
134 | |||
135 | - if gdb.parse_and_eval("qemu_in_coroutine()") == False: | ||
136 | - return | ||
137 | + try: | ||
138 | + # This only works with a live session | ||
139 | + co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") | ||
140 | + except: | ||
141 | + # Fallback to use hard-coded ucontext vars if it's coredump | ||
142 | + co_ptr = gdb.parse_and_eval("co_tls_current") | ||
143 | |||
144 | - co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") | ||
145 | + if co_ptr == False: | ||
146 | + return | ||
147 | |||
148 | while True: | ||
149 | co = co_cast(co_ptr) | ||
32 | -- | 150 | -- |
33 | 2.31.1 | 151 | 2.48.1 |
34 | |||
35 | diff view generated by jsdifflib |
1 | From: Ilya Dryomov <idryomov@gmail.com> | 1 | From: Peter Krempa <pkrempa@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | Jason has moved on from working on RBD and Ceph. I'm taking over | 3 | Commit 7452162adec25c10 introduced 'qom-path' argument to BLOCK_IO_ERROR |
4 | his role upstream. | 4 | event but when the event is instantiated in 'send_qmp_error_event()' the |
5 | arguments for 'device' and 'qom_path' in | ||
6 | qapi_event_send_block_io_error() were reversed : | ||
5 | 7 | ||
6 | Signed-off-by: Ilya Dryomov <idryomov@gmail.com> | 8 | Generated code for sending event: |
7 | Message-Id: <20210519112513.19694-1-idryomov@gmail.com> | 9 | |
8 | Acked-by: Stefano Garzarella <sgarzare@redhat.com> | 10 | void qapi_event_send_block_io_error(const char *qom_path, |
11 | const char *device, | ||
12 | const char *node_name, | ||
13 | IoOperationType operation, | ||
14 | [...] | ||
15 | |||
16 | Call inside send_qmp_error_event(): | ||
17 | |||
18 | qapi_event_send_block_io_error(blk_name(blk), | ||
19 | blk_get_attached_dev_path(blk), | ||
20 | bs ? bdrv_get_node_name(bs) : NULL, optype, | ||
21 | [...] | ||
22 | |||
23 | This results into reporting the QOM path as the device alias and vice | ||
24 | versa which in turn breaks libvirt, which expects the device alias being | ||
25 | either a valid alias or empty (which would make libvirt do the lookup by | ||
26 | node-name instead). | ||
27 | |||
28 | Cc: qemu-stable@nongnu.org | ||
29 | Fixes: 7452162adec2 ("qapi: add qom-path to BLOCK_IO_ERROR event") | ||
30 | Signed-off-by: Peter Krempa <pkrempa@redhat.com> | ||
31 | Message-ID: <09728d784888b38d7a8f09ee5e9e9c542c875e1e.1737973614.git.pkrempa@redhat.com> | ||
32 | Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> | ||
33 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 34 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | --- | 35 | --- |
11 | MAINTAINERS | 2 +- | 36 | block/block-backend.c | 2 +- |
12 | 1 file changed, 1 insertion(+), 1 deletion(-) | 37 | 1 file changed, 1 insertion(+), 1 deletion(-) |
13 | 38 | ||
14 | diff --git a/MAINTAINERS b/MAINTAINERS | 39 | diff --git a/block/block-backend.c b/block/block-backend.c |
15 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
16 | --- a/MAINTAINERS | 41 | --- a/block/block-backend.c |
17 | +++ b/MAINTAINERS | 42 | +++ b/block/block-backend.c |
18 | @@ -XXX,XX +XXX,XX @@ S: Supported | 43 | @@ -XXX,XX +XXX,XX @@ static void send_qmp_error_event(BlockBackend *blk, |
19 | F: block/vmdk.c | 44 | g_autofree char *path = blk_get_attached_dev_path(blk); |
20 | 45 | ||
21 | RBD | 46 | optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE; |
22 | -M: Jason Dillaman <dillaman@redhat.com> | 47 | - qapi_event_send_block_io_error(blk_name(blk), path, |
23 | +M: Ilya Dryomov <idryomov@gmail.com> | 48 | + qapi_event_send_block_io_error(path, blk_name(blk), |
24 | L: qemu-block@nongnu.org | 49 | bs ? bdrv_get_node_name(bs) : NULL, optype, |
25 | S: Supported | 50 | action, blk_iostatus_is_enabled(blk), |
26 | F: block/rbd.c | 51 | error == ENOSPC, strerror(error)); |
27 | -- | 52 | -- |
28 | 2.31.1 | 53 | 2.48.1 |
29 | 54 | ||
30 | 55 | diff view generated by jsdifflib |
1 | From: Alberto Garcia <berto@igalia.com> | 1 | This allows querying from QMP (and also HMP) whether an image is |
---|---|---|---|
2 | currently active or inactive (in the sense of BDRV_O_INACTIVE). | ||
2 | 3 | ||
3 | Move the code to free a BlockReopenQueue to a separate function. | ||
4 | It will be used in a subsequent patch. | ||
5 | |||
6 | [ kwolf: Also free explicit_options and options, and explicitly | ||
7 | qobject_ref() the value when it continues to be used. This makes | ||
8 | future memory leaks less likely. ] | ||
9 | |||
10 | Signed-off-by: Alberto Garcia <berto@igalia.com> | ||
11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
12 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 5 | Acked-by: Fabiano Rosas <farosas@suse.de> |
13 | Message-Id: <20210708114709.206487-3-kwolf@redhat.com> | 6 | Reviewed-by: Eric Blake <eblake@redhat.com> |
7 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
8 | Message-ID: <20250204211407.381505-2-kwolf@redhat.com> | ||
14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
15 | --- | 10 | --- |
16 | include/block/block.h | 1 + | 11 | qapi/block-core.json | 6 +++++- |
17 | block.c | 22 ++++++++++++++++------ | 12 | include/block/block-global-state.h | 3 +++ |
18 | 2 files changed, 17 insertions(+), 6 deletions(-) | 13 | block.c | 4 ++++ |
14 | block/monitor/block-hmp-cmds.c | 5 +++-- | ||
15 | block/qapi.c | 1 + | ||
16 | tests/qemu-iotests/184.out | 2 ++ | ||
17 | tests/qemu-iotests/191.out | 16 ++++++++++++++++ | ||
18 | tests/qemu-iotests/273.out | 5 +++++ | ||
19 | 8 files changed, 39 insertions(+), 3 deletions(-) | ||
19 | 20 | ||
20 | diff --git a/include/block/block.h b/include/block/block.h | 21 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
21 | index XXXXXXX..XXXXXXX 100644 | 22 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/include/block/block.h | 23 | --- a/qapi/block-core.json |
23 | +++ b/include/block/block.h | 24 | +++ b/qapi/block-core.json |
24 | @@ -XXX,XX +XXX,XX @@ BlockDriverState *bdrv_new_open_driver(BlockDriver *drv, const char *node_name, | 25 | @@ -XXX,XX +XXX,XX @@ |
25 | BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, | 26 | # @backing_file_depth: number of files in the backing file chain |
26 | BlockDriverState *bs, QDict *options, | 27 | # (since: 1.2) |
27 | bool keep_old_opts); | 28 | # |
28 | +void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue); | 29 | +# @active: true if the backend is active; typical cases for inactive backends |
29 | int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp); | 30 | +# are on the migration source instance after migration completes and on the |
30 | int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, | 31 | +# destination before it completes. (since: 10.0) |
31 | Error **errp); | 32 | +# |
33 | # @encrypted: true if the backing device is encrypted | ||
34 | # | ||
35 | # @detect_zeroes: detect and optimize zero writes (Since 2.1) | ||
36 | @@ -XXX,XX +XXX,XX @@ | ||
37 | { 'struct': 'BlockDeviceInfo', | ||
38 | 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', | ||
39 | '*backing_file': 'str', 'backing_file_depth': 'int', | ||
40 | - 'encrypted': 'bool', | ||
41 | + 'active': 'bool', 'encrypted': 'bool', | ||
42 | 'detect_zeroes': 'BlockdevDetectZeroesOptions', | ||
43 | 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', | ||
44 | 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int', | ||
45 | diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h | ||
46 | index XXXXXXX..XXXXXXX 100644 | ||
47 | --- a/include/block/block-global-state.h | ||
48 | +++ b/include/block/block-global-state.h | ||
49 | @@ -XXX,XX +XXX,XX @@ BlockDriverState * GRAPH_RDLOCK | ||
50 | check_to_replace_node(BlockDriverState *parent_bs, const char *node_name, | ||
51 | Error **errp); | ||
52 | |||
53 | + | ||
54 | +bool GRAPH_RDLOCK bdrv_is_inactive(BlockDriverState *bs); | ||
55 | + | ||
56 | int no_coroutine_fn GRAPH_RDLOCK | ||
57 | bdrv_activate(BlockDriverState *bs, Error **errp); | ||
58 | |||
32 | diff --git a/block.c b/block.c | 59 | diff --git a/block.c b/block.c |
33 | index XXXXXXX..XXXXXXX 100644 | 60 | index XXXXXXX..XXXXXXX 100644 |
34 | --- a/block.c | 61 | --- a/block.c |
35 | +++ b/block.c | 62 | +++ b/block.c |
36 | @@ -XXX,XX +XXX,XX @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, | 63 | @@ -XXX,XX +XXX,XX @@ void bdrv_init_with_whitelist(void) |
37 | NULL, 0, keep_old_opts); | 64 | bdrv_init(); |
38 | } | 65 | } |
39 | 66 | ||
40 | +void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue) | 67 | +bool bdrv_is_inactive(BlockDriverState *bs) { |
41 | +{ | 68 | + return bs->open_flags & BDRV_O_INACTIVE; |
42 | + if (bs_queue) { | ||
43 | + BlockReopenQueueEntry *bs_entry, *next; | ||
44 | + QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) { | ||
45 | + qobject_unref(bs_entry->state.explicit_options); | ||
46 | + qobject_unref(bs_entry->state.options); | ||
47 | + g_free(bs_entry); | ||
48 | + } | ||
49 | + g_free(bs_queue); | ||
50 | + } | ||
51 | +} | 69 | +} |
52 | + | 70 | + |
53 | /* | 71 | int bdrv_activate(BlockDriverState *bs, Error **errp) |
54 | * Reopen multiple BlockDriverStates atomically & transactionally. | 72 | { |
55 | * | 73 | BdrvChild *child, *parent; |
56 | @@ -XXX,XX +XXX,XX @@ abort: | 74 | diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c |
57 | if (bs_entry->prepared) { | 75 | index XXXXXXX..XXXXXXX 100644 |
58 | bdrv_reopen_abort(&bs_entry->state); | 76 | --- a/block/monitor/block-hmp-cmds.c |
59 | } | 77 | +++ b/block/monitor/block-hmp-cmds.c |
60 | - qobject_unref(bs_entry->state.explicit_options); | 78 | @@ -XXX,XX +XXX,XX @@ static void print_block_info(Monitor *mon, BlockInfo *info, |
61 | - qobject_unref(bs_entry->state.options); | ||
62 | } | 79 | } |
63 | 80 | ||
64 | cleanup: | 81 | if (inserted) { |
65 | - QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) { | 82 | - monitor_printf(mon, ": %s (%s%s%s)\n", |
66 | - g_free(bs_entry); | 83 | + monitor_printf(mon, ": %s (%s%s%s%s)\n", |
67 | - } | 84 | inserted->file, |
68 | - g_free(bs_queue); | 85 | inserted->drv, |
69 | + bdrv_reopen_queue_free(bs_queue); | 86 | inserted->ro ? ", read-only" : "", |
70 | 87 | - inserted->encrypted ? ", encrypted" : ""); | |
71 | return ret; | 88 | + inserted->encrypted ? ", encrypted" : "", |
72 | } | 89 | + inserted->active ? "" : ", inactive"); |
73 | @@ -XXX,XX +XXX,XX @@ static void bdrv_reopen_commit(BDRVReopenState *reopen_state) | 90 | } else { |
74 | /* set BDS specific flags now */ | 91 | monitor_printf(mon, ": [not inserted]\n"); |
75 | qobject_unref(bs->explicit_options); | 92 | } |
76 | qobject_unref(bs->options); | 93 | diff --git a/block/qapi.c b/block/qapi.c |
77 | + qobject_ref(reopen_state->explicit_options); | 94 | index XXXXXXX..XXXXXXX 100644 |
78 | + qobject_ref(reopen_state->options); | 95 | --- a/block/qapi.c |
79 | 96 | +++ b/block/qapi.c | |
80 | bs->explicit_options = reopen_state->explicit_options; | 97 | @@ -XXX,XX +XXX,XX @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk, |
81 | bs->options = reopen_state->options; | 98 | info->file = g_strdup(bs->filename); |
99 | info->ro = bdrv_is_read_only(bs); | ||
100 | info->drv = g_strdup(bs->drv->format_name); | ||
101 | + info->active = !bdrv_is_inactive(bs); | ||
102 | info->encrypted = bs->encrypted; | ||
103 | |||
104 | info->cache = g_new(BlockdevCacheInfo, 1); | ||
105 | diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out | ||
106 | index XXXXXXX..XXXXXXX 100644 | ||
107 | --- a/tests/qemu-iotests/184.out | ||
108 | +++ b/tests/qemu-iotests/184.out | ||
109 | @@ -XXX,XX +XXX,XX @@ Testing: | ||
110 | { | ||
111 | "iops_rd": 0, | ||
112 | "detect_zeroes": "off", | ||
113 | + "active": true, | ||
114 | "image": { | ||
115 | "backing-image": { | ||
116 | "virtual-size": 1073741824, | ||
117 | @@ -XXX,XX +XXX,XX @@ Testing: | ||
118 | { | ||
119 | "iops_rd": 0, | ||
120 | "detect_zeroes": "off", | ||
121 | + "active": true, | ||
122 | "image": { | ||
123 | "virtual-size": 1073741824, | ||
124 | "filename": "null-co://", | ||
125 | diff --git a/tests/qemu-iotests/191.out b/tests/qemu-iotests/191.out | ||
126 | index XXXXXXX..XXXXXXX 100644 | ||
127 | --- a/tests/qemu-iotests/191.out | ||
128 | +++ b/tests/qemu-iotests/191.out | ||
129 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
130 | { | ||
131 | "iops_rd": 0, | ||
132 | "detect_zeroes": "off", | ||
133 | + "active": true, | ||
134 | "image": { | ||
135 | "backing-image": { | ||
136 | "virtual-size": 67108864, | ||
137 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
138 | { | ||
139 | "iops_rd": 0, | ||
140 | "detect_zeroes": "off", | ||
141 | + "active": true, | ||
142 | "image": { | ||
143 | "virtual-size": 197120, | ||
144 | "filename": "TEST_DIR/t.IMGFMT.ovl2", | ||
145 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
146 | { | ||
147 | "iops_rd": 0, | ||
148 | "detect_zeroes": "off", | ||
149 | + "active": true, | ||
150 | "image": { | ||
151 | "backing-image": { | ||
152 | "virtual-size": 67108864, | ||
153 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
154 | { | ||
155 | "iops_rd": 0, | ||
156 | "detect_zeroes": "off", | ||
157 | + "active": true, | ||
158 | "image": { | ||
159 | "virtual-size": 197120, | ||
160 | "filename": "TEST_DIR/t.IMGFMT", | ||
161 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
162 | { | ||
163 | "iops_rd": 0, | ||
164 | "detect_zeroes": "off", | ||
165 | + "active": true, | ||
166 | "image": { | ||
167 | "backing-image": { | ||
168 | "virtual-size": 67108864, | ||
169 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
170 | { | ||
171 | "iops_rd": 0, | ||
172 | "detect_zeroes": "off", | ||
173 | + "active": true, | ||
174 | "image": { | ||
175 | "virtual-size": 393216, | ||
176 | "filename": "TEST_DIR/t.IMGFMT.mid", | ||
177 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
178 | { | ||
179 | "iops_rd": 0, | ||
180 | "detect_zeroes": "off", | ||
181 | + "active": true, | ||
182 | "image": { | ||
183 | "virtual-size": 67108864, | ||
184 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
185 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
186 | { | ||
187 | "iops_rd": 0, | ||
188 | "detect_zeroes": "off", | ||
189 | + "active": true, | ||
190 | "image": { | ||
191 | "virtual-size": 393216, | ||
192 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
193 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
194 | { | ||
195 | "iops_rd": 0, | ||
196 | "detect_zeroes": "off", | ||
197 | + "active": true, | ||
198 | "image": { | ||
199 | "backing-image": { | ||
200 | "virtual-size": 67108864, | ||
201 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
202 | { | ||
203 | "iops_rd": 0, | ||
204 | "detect_zeroes": "off", | ||
205 | + "active": true, | ||
206 | "image": { | ||
207 | "virtual-size": 197120, | ||
208 | "filename": "TEST_DIR/t.IMGFMT.ovl2", | ||
209 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
210 | { | ||
211 | "iops_rd": 0, | ||
212 | "detect_zeroes": "off", | ||
213 | + "active": true, | ||
214 | "image": { | ||
215 | "backing-image": { | ||
216 | "backing-image": { | ||
217 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
218 | { | ||
219 | "iops_rd": 0, | ||
220 | "detect_zeroes": "off", | ||
221 | + "active": true, | ||
222 | "image": { | ||
223 | "virtual-size": 197120, | ||
224 | "filename": "TEST_DIR/t.IMGFMT.ovl3", | ||
225 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
226 | { | ||
227 | "iops_rd": 0, | ||
228 | "detect_zeroes": "off", | ||
229 | + "active": true, | ||
230 | "image": { | ||
231 | "virtual-size": 67108864, | ||
232 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
233 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
234 | { | ||
235 | "iops_rd": 0, | ||
236 | "detect_zeroes": "off", | ||
237 | + "active": true, | ||
238 | "image": { | ||
239 | "virtual-size": 393216, | ||
240 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
241 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
242 | { | ||
243 | "iops_rd": 0, | ||
244 | "detect_zeroes": "off", | ||
245 | + "active": true, | ||
246 | "image": { | ||
247 | "backing-image": { | ||
248 | "virtual-size": 67108864, | ||
249 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
250 | { | ||
251 | "iops_rd": 0, | ||
252 | "detect_zeroes": "off", | ||
253 | + "active": true, | ||
254 | "image": { | ||
255 | "virtual-size": 197120, | ||
256 | "filename": "TEST_DIR/t.IMGFMT", | ||
257 | diff --git a/tests/qemu-iotests/273.out b/tests/qemu-iotests/273.out | ||
258 | index XXXXXXX..XXXXXXX 100644 | ||
259 | --- a/tests/qemu-iotests/273.out | ||
260 | +++ b/tests/qemu-iotests/273.out | ||
261 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
262 | { | ||
263 | "iops_rd": 0, | ||
264 | "detect_zeroes": "off", | ||
265 | + "active": true, | ||
266 | "image": { | ||
267 | "backing-image": { | ||
268 | "backing-image": { | ||
269 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
270 | { | ||
271 | "iops_rd": 0, | ||
272 | "detect_zeroes": "off", | ||
273 | + "active": true, | ||
274 | "image": { | ||
275 | "virtual-size": 197120, | ||
276 | "filename": "TEST_DIR/t.IMGFMT", | ||
277 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
278 | { | ||
279 | "iops_rd": 0, | ||
280 | "detect_zeroes": "off", | ||
281 | + "active": true, | ||
282 | "image": { | ||
283 | "backing-image": { | ||
284 | "virtual-size": 197120, | ||
285 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
286 | { | ||
287 | "iops_rd": 0, | ||
288 | "detect_zeroes": "off", | ||
289 | + "active": true, | ||
290 | "image": { | ||
291 | "virtual-size": 197120, | ||
292 | "filename": "TEST_DIR/t.IMGFMT.mid", | ||
293 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
294 | { | ||
295 | "iops_rd": 0, | ||
296 | "detect_zeroes": "off", | ||
297 | + "active": true, | ||
298 | "image": { | ||
299 | "virtual-size": 197120, | ||
300 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
82 | -- | 301 | -- |
83 | 2.31.1 | 302 | 2.48.1 |
84 | |||
85 | diff view generated by jsdifflib |
1 | From: Peter Lieven <pl@kamp.de> | 1 | What we wanted to catch with the assertion is cases where the recursion |
---|---|---|---|
2 | finds that a child was inactive before its parent. This should never | ||
3 | happen. But if the user tries to inactivate an image that is already | ||
4 | inactive, that's harmless and we don't want to fail the assertion. | ||
2 | 5 | ||
3 | Signed-off-by: Peter Lieven <pl@kamp.de> | 6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | 7 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | Message-Id: <20210702172356.11574-5-idryomov@gmail.com> | 8 | Reviewed-by: Eric Blake <eblake@redhat.com> |
9 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
10 | Message-ID: <20250204211407.381505-3-kwolf@redhat.com> | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
7 | --- | 12 | --- |
8 | block/rbd.c | 252 +++++++++++++++++++--------------------------------- | 13 | block.c | 16 ++++++++++++---- |
9 | 1 file changed, 90 insertions(+), 162 deletions(-) | 14 | 1 file changed, 12 insertions(+), 4 deletions(-) |
10 | 15 | ||
11 | diff --git a/block/rbd.c b/block/rbd.c | 16 | diff --git a/block.c b/block.c |
12 | index XXXXXXX..XXXXXXX 100644 | 17 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/block/rbd.c | 18 | --- a/block.c |
14 | +++ b/block/rbd.c | 19 | +++ b/block.c |
15 | @@ -XXX,XX +XXX,XX @@ typedef enum { | 20 | @@ -XXX,XX +XXX,XX @@ bdrv_has_bds_parent(BlockDriverState *bs, bool only_active) |
16 | RBD_AIO_FLUSH | 21 | return false; |
17 | } RBDAIOCmd; | ||
18 | |||
19 | -typedef struct RBDAIOCB { | ||
20 | - BlockAIOCB common; | ||
21 | - int64_t ret; | ||
22 | - QEMUIOVector *qiov; | ||
23 | - RBDAIOCmd cmd; | ||
24 | - int error; | ||
25 | - struct BDRVRBDState *s; | ||
26 | -} RBDAIOCB; | ||
27 | - | ||
28 | -typedef struct RADOSCB { | ||
29 | - RBDAIOCB *acb; | ||
30 | - struct BDRVRBDState *s; | ||
31 | - int64_t size; | ||
32 | - int64_t ret; | ||
33 | -} RADOSCB; | ||
34 | - | ||
35 | typedef struct BDRVRBDState { | ||
36 | rados_t cluster; | ||
37 | rados_ioctx_t io_ctx; | ||
38 | @@ -XXX,XX +XXX,XX @@ typedef struct BDRVRBDState { | ||
39 | uint64_t object_size; | ||
40 | } BDRVRBDState; | ||
41 | |||
42 | +typedef struct RBDTask { | ||
43 | + BlockDriverState *bs; | ||
44 | + Coroutine *co; | ||
45 | + bool complete; | ||
46 | + int64_t ret; | ||
47 | +} RBDTask; | ||
48 | + | ||
49 | static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx, | ||
50 | BlockdevOptionsRbd *opts, bool cache, | ||
51 | const char *keypairs, const char *secretid, | ||
52 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json, | ||
53 | return ret; | ||
54 | } | 22 | } |
55 | 23 | ||
56 | -static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs) | 24 | -static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) |
57 | -{ | 25 | +static int GRAPH_RDLOCK |
58 | - RBDAIOCB *acb = rcb->acb; | 26 | +bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) |
59 | - iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0, | ||
60 | - acb->qiov->size - offs); | ||
61 | -} | ||
62 | - | ||
63 | #ifdef LIBRBD_SUPPORTS_ENCRYPTION | ||
64 | static int qemu_rbd_convert_luks_options( | ||
65 | RbdEncryptionOptionsLUKSBase *luks_opts, | ||
66 | @@ -XXX,XX +XXX,XX @@ exit: | ||
67 | return ret; | ||
68 | } | ||
69 | |||
70 | -/* | ||
71 | - * This aio completion is being called from rbd_finish_bh() and runs in qemu | ||
72 | - * BH context. | ||
73 | - */ | ||
74 | -static void qemu_rbd_complete_aio(RADOSCB *rcb) | ||
75 | -{ | ||
76 | - RBDAIOCB *acb = rcb->acb; | ||
77 | - int64_t r; | ||
78 | - | ||
79 | - r = rcb->ret; | ||
80 | - | ||
81 | - if (acb->cmd != RBD_AIO_READ) { | ||
82 | - if (r < 0) { | ||
83 | - acb->ret = r; | ||
84 | - acb->error = 1; | ||
85 | - } else if (!acb->error) { | ||
86 | - acb->ret = rcb->size; | ||
87 | - } | ||
88 | - } else { | ||
89 | - if (r < 0) { | ||
90 | - qemu_rbd_memset(rcb, 0); | ||
91 | - acb->ret = r; | ||
92 | - acb->error = 1; | ||
93 | - } else if (r < rcb->size) { | ||
94 | - qemu_rbd_memset(rcb, r); | ||
95 | - if (!acb->error) { | ||
96 | - acb->ret = rcb->size; | ||
97 | - } | ||
98 | - } else if (!acb->error) { | ||
99 | - acb->ret = r; | ||
100 | - } | ||
101 | - } | ||
102 | - | ||
103 | - g_free(rcb); | ||
104 | - | ||
105 | - acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret)); | ||
106 | - | ||
107 | - qemu_aio_unref(acb); | ||
108 | -} | ||
109 | - | ||
110 | static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp) | ||
111 | { | 27 | { |
112 | const char **vals; | 28 | BdrvChild *child, *parent; |
113 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_resize(BlockDriverState *bs, uint64_t size) | 29 | int ret; |
114 | return 0; | 30 | @@ -XXX,XX +XXX,XX @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) |
115 | } | 31 | return 0; |
116 | |||
117 | -static const AIOCBInfo rbd_aiocb_info = { | ||
118 | - .aiocb_size = sizeof(RBDAIOCB), | ||
119 | -}; | ||
120 | - | ||
121 | -static void rbd_finish_bh(void *opaque) | ||
122 | +static void qemu_rbd_finish_bh(void *opaque) | ||
123 | { | ||
124 | - RADOSCB *rcb = opaque; | ||
125 | - qemu_rbd_complete_aio(rcb); | ||
126 | + RBDTask *task = opaque; | ||
127 | + task->complete = 1; | ||
128 | + aio_co_wake(task->co); | ||
129 | } | ||
130 | |||
131 | /* | ||
132 | - * This is the callback function for rbd_aio_read and _write | ||
133 | + * This is the completion callback function for all rbd aio calls | ||
134 | + * started from qemu_rbd_start_co(). | ||
135 | * | ||
136 | * Note: this function is being called from a non qemu thread so | ||
137 | * we need to be careful about what we do here. Generally we only | ||
138 | * schedule a BH, and do the rest of the io completion handling | ||
139 | - * from rbd_finish_bh() which runs in a qemu context. | ||
140 | + * from qemu_rbd_finish_bh() which runs in a qemu context. | ||
141 | */ | ||
142 | -static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb) | ||
143 | +static void qemu_rbd_completion_cb(rbd_completion_t c, RBDTask *task) | ||
144 | { | ||
145 | - RBDAIOCB *acb = rcb->acb; | ||
146 | - | ||
147 | - rcb->ret = rbd_aio_get_return_value(c); | ||
148 | + task->ret = rbd_aio_get_return_value(c); | ||
149 | rbd_aio_release(c); | ||
150 | - | ||
151 | - replay_bh_schedule_oneshot_event(bdrv_get_aio_context(acb->common.bs), | ||
152 | - rbd_finish_bh, rcb); | ||
153 | + aio_bh_schedule_oneshot(bdrv_get_aio_context(task->bs), | ||
154 | + qemu_rbd_finish_bh, task); | ||
155 | } | ||
156 | |||
157 | -static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | ||
158 | - int64_t off, | ||
159 | - QEMUIOVector *qiov, | ||
160 | - int64_t size, | ||
161 | - BlockCompletionFunc *cb, | ||
162 | - void *opaque, | ||
163 | - RBDAIOCmd cmd) | ||
164 | +static int coroutine_fn qemu_rbd_start_co(BlockDriverState *bs, | ||
165 | + uint64_t offset, | ||
166 | + uint64_t bytes, | ||
167 | + QEMUIOVector *qiov, | ||
168 | + int flags, | ||
169 | + RBDAIOCmd cmd) | ||
170 | { | ||
171 | - RBDAIOCB *acb; | ||
172 | - RADOSCB *rcb = NULL; | ||
173 | + BDRVRBDState *s = bs->opaque; | ||
174 | + RBDTask task = { .bs = bs, .co = qemu_coroutine_self() }; | ||
175 | rbd_completion_t c; | ||
176 | int r; | ||
177 | |||
178 | - BDRVRBDState *s = bs->opaque; | ||
179 | - | ||
180 | - acb = qemu_aio_get(&rbd_aiocb_info, bs, cb, opaque); | ||
181 | - acb->cmd = cmd; | ||
182 | - acb->qiov = qiov; | ||
183 | - assert(!qiov || qiov->size == size); | ||
184 | - | ||
185 | - rcb = g_new(RADOSCB, 1); | ||
186 | + assert(!qiov || qiov->size == bytes); | ||
187 | |||
188 | - acb->ret = 0; | ||
189 | - acb->error = 0; | ||
190 | - acb->s = s; | ||
191 | - | ||
192 | - rcb->acb = acb; | ||
193 | - rcb->s = acb->s; | ||
194 | - rcb->size = size; | ||
195 | - r = rbd_aio_create_completion(rcb, (rbd_callback_t) rbd_finish_aiocb, &c); | ||
196 | + r = rbd_aio_create_completion(&task, | ||
197 | + (rbd_callback_t) qemu_rbd_completion_cb, &c); | ||
198 | if (r < 0) { | ||
199 | - goto failed; | ||
200 | + return r; | ||
201 | } | 32 | } |
202 | 33 | ||
203 | switch (cmd) { | 34 | - assert(!(bs->open_flags & BDRV_O_INACTIVE)); |
204 | - case RBD_AIO_WRITE: | 35 | + /* |
205 | - /* | 36 | + * Inactivating an already inactive node on user request is harmless, but if |
206 | - * RBD APIs don't allow us to write more than actual size, so in order | 37 | + * a child is already inactive before its parent, that's bad. |
207 | - * to support growing images, we resize the image before write | 38 | + */ |
208 | - * operations that exceed the current size. | 39 | + if (bs->open_flags & BDRV_O_INACTIVE) { |
209 | - */ | 40 | + assert(top_level); |
210 | - if (off + size > s->image_size) { | 41 | + return 0; |
211 | - r = qemu_rbd_resize(bs, off + size); | ||
212 | - if (r < 0) { | ||
213 | - goto failed_completion; | ||
214 | - } | ||
215 | - } | ||
216 | - r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c); | ||
217 | - break; | ||
218 | case RBD_AIO_READ: | ||
219 | - r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c); | ||
220 | + r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, offset, c); | ||
221 | + break; | ||
222 | + case RBD_AIO_WRITE: | ||
223 | + r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, offset, c); | ||
224 | break; | ||
225 | case RBD_AIO_DISCARD: | ||
226 | - r = rbd_aio_discard(s->image, off, size, c); | ||
227 | + r = rbd_aio_discard(s->image, offset, bytes, c); | ||
228 | break; | ||
229 | case RBD_AIO_FLUSH: | ||
230 | r = rbd_aio_flush(s->image, c); | ||
231 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | ||
232 | } | ||
233 | |||
234 | if (r < 0) { | ||
235 | - goto failed_completion; | ||
236 | + error_report("rbd request failed early: cmd %d offset %" PRIu64 | ||
237 | + " bytes %" PRIu64 " flags %d r %d (%s)", cmd, offset, | ||
238 | + bytes, flags, r, strerror(-r)); | ||
239 | + rbd_aio_release(c); | ||
240 | + return r; | ||
241 | } | ||
242 | - return &acb->common; | ||
243 | |||
244 | -failed_completion: | ||
245 | - rbd_aio_release(c); | ||
246 | -failed: | ||
247 | - g_free(rcb); | ||
248 | + while (!task.complete) { | ||
249 | + qemu_coroutine_yield(); | ||
250 | + } | 42 | + } |
251 | 43 | ||
252 | - qemu_aio_unref(acb); | 44 | /* Inactivate this node */ |
253 | - return NULL; | 45 | if (bs->drv->bdrv_inactivate) { |
254 | + if (task.ret < 0) { | 46 | @@ -XXX,XX +XXX,XX @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) |
255 | + error_report("rbd request failed: cmd %d offset %" PRIu64 " bytes %" | 47 | |
256 | + PRIu64 " flags %d task.ret %" PRIi64 " (%s)", cmd, offset, | 48 | /* Recursively inactivate children */ |
257 | + bytes, flags, task.ret, strerror(-task.ret)); | 49 | QLIST_FOREACH(child, &bs->children, next) { |
258 | + return task.ret; | 50 | - ret = bdrv_inactivate_recurse(child->bs); |
259 | + } | 51 | + ret = bdrv_inactivate_recurse(child->bs, false); |
260 | + | 52 | if (ret < 0) { |
261 | + /* zero pad short reads */ | 53 | return ret; |
262 | + if (cmd == RBD_AIO_READ && task.ret < qiov->size) { | 54 | } |
263 | + qemu_iovec_memset(qiov, task.ret, 0, qiov->size - task.ret); | 55 | @@ -XXX,XX +XXX,XX @@ int bdrv_inactivate_all(void) |
264 | + } | 56 | if (bdrv_has_bds_parent(bs, false)) { |
265 | + | 57 | continue; |
266 | + return 0; | 58 | } |
267 | +} | 59 | - ret = bdrv_inactivate_recurse(bs); |
268 | + | 60 | + ret = bdrv_inactivate_recurse(bs, true); |
269 | +static int | 61 | if (ret < 0) { |
270 | +coroutine_fn qemu_rbd_co_preadv(BlockDriverState *bs, uint64_t offset, | 62 | bdrv_next_cleanup(&it); |
271 | + uint64_t bytes, QEMUIOVector *qiov, | 63 | break; |
272 | + int flags) | ||
273 | +{ | ||
274 | + return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_READ); | ||
275 | } | ||
276 | |||
277 | -static BlockAIOCB *qemu_rbd_aio_preadv(BlockDriverState *bs, | ||
278 | - uint64_t offset, uint64_t bytes, | ||
279 | - QEMUIOVector *qiov, int flags, | ||
280 | - BlockCompletionFunc *cb, | ||
281 | - void *opaque) | ||
282 | +static int | ||
283 | +coroutine_fn qemu_rbd_co_pwritev(BlockDriverState *bs, uint64_t offset, | ||
284 | + uint64_t bytes, QEMUIOVector *qiov, | ||
285 | + int flags) | ||
286 | { | ||
287 | - return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque, | ||
288 | - RBD_AIO_READ); | ||
289 | + BDRVRBDState *s = bs->opaque; | ||
290 | + /* | ||
291 | + * RBD APIs don't allow us to write more than actual size, so in order | ||
292 | + * to support growing images, we resize the image before write | ||
293 | + * operations that exceed the current size. | ||
294 | + */ | ||
295 | + if (offset + bytes > s->image_size) { | ||
296 | + int r = qemu_rbd_resize(bs, offset + bytes); | ||
297 | + if (r < 0) { | ||
298 | + return r; | ||
299 | + } | ||
300 | + } | ||
301 | + return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_WRITE); | ||
302 | } | ||
303 | |||
304 | -static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs, | ||
305 | - uint64_t offset, uint64_t bytes, | ||
306 | - QEMUIOVector *qiov, int flags, | ||
307 | - BlockCompletionFunc *cb, | ||
308 | - void *opaque) | ||
309 | +static int coroutine_fn qemu_rbd_co_flush(BlockDriverState *bs) | ||
310 | { | ||
311 | - return rbd_start_aio(bs, offset, qiov, bytes, cb, opaque, | ||
312 | - RBD_AIO_WRITE); | ||
313 | + return qemu_rbd_start_co(bs, 0, 0, NULL, 0, RBD_AIO_FLUSH); | ||
314 | } | ||
315 | |||
316 | -static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs, | ||
317 | - BlockCompletionFunc *cb, | ||
318 | - void *opaque) | ||
319 | +static int coroutine_fn qemu_rbd_co_pdiscard(BlockDriverState *bs, | ||
320 | + int64_t offset, int count) | ||
321 | { | ||
322 | - return rbd_start_aio(bs, 0, NULL, 0, cb, opaque, RBD_AIO_FLUSH); | ||
323 | + return qemu_rbd_start_co(bs, offset, count, NULL, 0, RBD_AIO_DISCARD); | ||
324 | } | ||
325 | |||
326 | static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) | ||
327 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_snap_list(BlockDriverState *bs, | ||
328 | return snap_count; | ||
329 | } | ||
330 | |||
331 | -static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs, | ||
332 | - int64_t offset, | ||
333 | - int bytes, | ||
334 | - BlockCompletionFunc *cb, | ||
335 | - void *opaque) | ||
336 | -{ | ||
337 | - return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque, | ||
338 | - RBD_AIO_DISCARD); | ||
339 | -} | ||
340 | - | ||
341 | static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs, | ||
342 | Error **errp) | ||
343 | { | ||
344 | @@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_rbd = { | ||
345 | .bdrv_co_truncate = qemu_rbd_co_truncate, | ||
346 | .protocol_name = "rbd", | ||
347 | |||
348 | - .bdrv_aio_preadv = qemu_rbd_aio_preadv, | ||
349 | - .bdrv_aio_pwritev = qemu_rbd_aio_pwritev, | ||
350 | - | ||
351 | - .bdrv_aio_flush = qemu_rbd_aio_flush, | ||
352 | - .bdrv_aio_pdiscard = qemu_rbd_aio_pdiscard, | ||
353 | + .bdrv_co_preadv = qemu_rbd_co_preadv, | ||
354 | + .bdrv_co_pwritev = qemu_rbd_co_pwritev, | ||
355 | + .bdrv_co_flush_to_disk = qemu_rbd_co_flush, | ||
356 | + .bdrv_co_pdiscard = qemu_rbd_co_pdiscard, | ||
357 | |||
358 | .bdrv_snapshot_create = qemu_rbd_snap_create, | ||
359 | .bdrv_snapshot_delete = qemu_rbd_snap_remove, | ||
360 | -- | 64 | -- |
361 | 2.31.1 | 65 | 2.48.1 |
362 | |||
363 | diff view generated by jsdifflib |
1 | From: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 1 | Putting an active block node on top of an inactive one is strictly |
---|---|---|---|
2 | speaking an invalid configuration and the next patch will turn it into a | ||
3 | hard error. | ||
2 | 4 | ||
3 | drive_backup_prepare() does bdrv_drained_begin() in hope that | 5 | However, taking a snapshot while disk images are inactive after |
4 | bdrv_drained_end() will be called in drive_backup_clean(). Still we | 6 | completing migration has an important use case: After migrating to a |
5 | need to set state->bs for this to work. That's done too late: a lot of | 7 | file, taking an external snapshot is what is needed to take a full VM |
6 | failure paths in drive_backup_prepare() miss setting state->bs. Fix | 8 | snapshot. |
7 | that. | ||
8 | 9 | ||
9 | Fixes: 2288ccfac96281c316db942d10e3f921c1373064 | 10 | In order for this to keep working after the later patches, change |
10 | Fixes: https://gitlab.com/qemu-project/qemu/-/issues/399 | 11 | creating a snapshot such that it automatically inactivates an overlay |
11 | Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 12 | that is added on top of an already inactive node. |
12 | Message-Id: <20210608171852.250775-1-vsementsov@virtuozzo.com> | 13 | |
14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
15 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
13 | Reviewed-by: Eric Blake <eblake@redhat.com> | 16 | Reviewed-by: Eric Blake <eblake@redhat.com> |
17 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
18 | Message-ID: <20250204211407.381505-4-kwolf@redhat.com> | ||
14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 19 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
15 | --- | 20 | --- |
16 | blockdev.c | 3 +-- | 21 | blockdev.c | 16 ++++++++++++++++ |
17 | 1 file changed, 1 insertion(+), 2 deletions(-) | 22 | 1 file changed, 16 insertions(+) |
18 | 23 | ||
19 | diff --git a/blockdev.c b/blockdev.c | 24 | diff --git a/blockdev.c b/blockdev.c |
20 | index XXXXXXX..XXXXXXX 100644 | 25 | index XXXXXXX..XXXXXXX 100644 |
21 | --- a/blockdev.c | 26 | --- a/blockdev.c |
22 | +++ b/blockdev.c | 27 | +++ b/blockdev.c |
23 | @@ -XXX,XX +XXX,XX @@ static void drive_backup_prepare(BlkActionState *common, Error **errp) | 28 | @@ -XXX,XX +XXX,XX @@ static void external_snapshot_action(TransactionAction *action, |
24 | aio_context = bdrv_get_aio_context(bs); | 29 | return; |
25 | aio_context_acquire(aio_context); | ||
26 | |||
27 | + state->bs = bs; | ||
28 | /* Paired with .clean() */ | ||
29 | bdrv_drained_begin(bs); | ||
30 | |||
31 | @@ -XXX,XX +XXX,XX @@ static void drive_backup_prepare(BlkActionState *common, Error **errp) | ||
32 | } | ||
33 | } | 30 | } |
34 | 31 | ||
35 | - state->bs = bs; | 32 | + /* |
36 | - | 33 | + * Older QEMU versions have allowed adding an active parent node to an |
37 | state->job = do_backup_common(qapi_DriveBackup_base(backup), | 34 | + * inactive child node. This is unsafe in the general case, but there is an |
38 | bs, target_bs, aio_context, | 35 | + * important use case, which is taking a VM snapshot with migration to file |
39 | common->block_job_txn, errp); | 36 | + * and then adding an external snapshot while the VM is still stopped and |
37 | + * images are inactive. Requiring the user to explicitly create the overlay | ||
38 | + * as inactive would break compatibility, so just do it automatically here | ||
39 | + * to keep this working. | ||
40 | + */ | ||
41 | + if (bdrv_is_inactive(state->old_bs) && !bdrv_is_inactive(state->new_bs)) { | ||
42 | + ret = bdrv_inactivate(state->new_bs, errp); | ||
43 | + if (ret < 0) { | ||
44 | + return; | ||
45 | + } | ||
46 | + } | ||
47 | + | ||
48 | ret = bdrv_append(state->new_bs, state->old_bs, errp); | ||
49 | if (ret < 0) { | ||
50 | return; | ||
40 | -- | 51 | -- |
41 | 2.31.1 | 52 | 2.48.1 |
42 | |||
43 | diff view generated by jsdifflib |
1 | From: Peter Lieven <pl@kamp.de> | 1 | Block devices have an individual active state, a single global flag |
---|---|---|---|
2 | can't cover this correctly. This becomes more important as we allow | ||
3 | users to manually manage which nodes are active or inactive. | ||
2 | 4 | ||
3 | Ceph Luminous (version 12.2.z) is almost 4 years old at this point. | 5 | Now that it's allowed to call bdrv_inactivate_all() even when some |
4 | Bump the requirement to get rid of the ifdef'ry in the code. | 6 | nodes are already inactive, we can remove the flag and just |
5 | Qemu 6.1 dropped the support for RHEL-7 which was the last supported | 7 | unconditionally call bdrv_inactivate_all() and, more importantly, |
6 | OS that required an older librbd. | 8 | bdrv_activate_all() before we make use of the nodes. |
7 | 9 | ||
8 | Signed-off-by: Peter Lieven <pl@kamp.de> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
9 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | 11 | Acked-by: Fabiano Rosas <farosas@suse.de> |
10 | Message-Id: <20210702172356.11574-2-idryomov@gmail.com> | 12 | Reviewed-by: Eric Blake <eblake@redhat.com> |
13 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
14 | Message-ID: <20250204211407.381505-5-kwolf@redhat.com> | ||
11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
12 | --- | 16 | --- |
13 | block/rbd.c | 120 ++++------------------------------------------------ | 17 | migration/migration.h | 3 --- |
14 | meson.build | 7 ++- | 18 | migration/block-active.c | 46 ---------------------------------------- |
15 | 2 files changed, 13 insertions(+), 114 deletions(-) | 19 | migration/migration.c | 8 ------- |
20 | 3 files changed, 57 deletions(-) | ||
16 | 21 | ||
17 | diff --git a/block/rbd.c b/block/rbd.c | 22 | diff --git a/migration/migration.h b/migration/migration.h |
18 | index XXXXXXX..XXXXXXX 100644 | 23 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/block/rbd.c | 24 | --- a/migration/migration.h |
20 | +++ b/block/rbd.c | 25 | +++ b/migration/migration.h |
26 | @@ -XXX,XX +XXX,XX @@ void migration_bitmap_sync_precopy(bool last_stage); | ||
27 | void dirty_bitmap_mig_init(void); | ||
28 | bool should_send_vmdesc(void); | ||
29 | |||
30 | -/* migration/block-active.c */ | ||
31 | -void migration_block_active_setup(bool active); | ||
32 | - | ||
33 | #endif | ||
34 | diff --git a/migration/block-active.c b/migration/block-active.c | ||
35 | index XXXXXXX..XXXXXXX 100644 | ||
36 | --- a/migration/block-active.c | ||
37 | +++ b/migration/block-active.c | ||
21 | @@ -XXX,XX +XXX,XX @@ | 38 | @@ -XXX,XX +XXX,XX @@ |
22 | * leading "\". | 39 | #include "qemu/error-report.h" |
23 | */ | 40 | #include "trace.h" |
24 | 41 | ||
25 | -/* rbd_aio_discard added in 0.1.2 */ | 42 | -/* |
26 | -#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 2) | 43 | - * Migration-only cache to remember the block layer activation status. |
27 | -#define LIBRBD_SUPPORTS_DISCARD | 44 | - * Protected by BQL. |
28 | -#else | 45 | - * |
29 | -#undef LIBRBD_SUPPORTS_DISCARD | 46 | - * We need this because.. |
30 | -#endif | 47 | - * |
48 | - * - Migration can fail after block devices are invalidated (during | ||
49 | - * switchover phase). When that happens, we need to be able to recover | ||
50 | - * the block drive status by re-activating them. | ||
51 | - * | ||
52 | - * - Currently bdrv_inactivate_all() is not safe to be invoked on top of | ||
53 | - * invalidated drives (even if bdrv_activate_all() is actually safe to be | ||
54 | - * called any time!). It means remembering this could help migration to | ||
55 | - * make sure it won't invalidate twice in a row, crashing QEMU. It can | ||
56 | - * happen when we migrate a PAUSED VM from host1 to host2, then migrate | ||
57 | - * again to host3 without starting it. TODO: a cleaner solution is to | ||
58 | - * allow safe invoke of bdrv_inactivate_all() at anytime, like | ||
59 | - * bdrv_activate_all(). | ||
60 | - * | ||
61 | - * For freshly started QEMU, the flag is initialized to TRUE reflecting the | ||
62 | - * scenario where QEMU owns block device ownerships. | ||
63 | - * | ||
64 | - * For incoming QEMU taking a migration stream, the flag is initialized to | ||
65 | - * FALSE reflecting that the incoming side doesn't own the block devices, | ||
66 | - * not until switchover happens. | ||
67 | - */ | ||
68 | -static bool migration_block_active; | ||
31 | - | 69 | - |
32 | #define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER) | 70 | -/* Setup the disk activation status */ |
33 | 71 | -void migration_block_active_setup(bool active) | |
34 | #define RBD_MAX_SNAPS 100 | 72 | -{ |
35 | 73 | - migration_block_active = active; | |
36 | -/* The LIBRBD_SUPPORTS_IOVEC is defined in librbd.h */ | 74 | -} |
37 | -#ifdef LIBRBD_SUPPORTS_IOVEC | ||
38 | -#define LIBRBD_USE_IOVEC 1 | ||
39 | -#else | ||
40 | -#define LIBRBD_USE_IOVEC 0 | ||
41 | -#endif | ||
42 | - | 75 | - |
43 | #define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8 | 76 | bool migration_block_activate(Error **errp) |
44 | |||
45 | static const char rbd_luks_header_verification[ | ||
46 | @@ -XXX,XX +XXX,XX @@ typedef struct RBDAIOCB { | ||
47 | BlockAIOCB common; | ||
48 | int64_t ret; | ||
49 | QEMUIOVector *qiov; | ||
50 | - char *bounce; | ||
51 | RBDAIOCmd cmd; | ||
52 | int error; | ||
53 | struct BDRVRBDState *s; | ||
54 | @@ -XXX,XX +XXX,XX @@ typedef struct RADOSCB { | ||
55 | RBDAIOCB *acb; | ||
56 | struct BDRVRBDState *s; | ||
57 | int64_t size; | ||
58 | - char *buf; | ||
59 | int64_t ret; | ||
60 | } RADOSCB; | ||
61 | |||
62 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json, | ||
63 | |||
64 | static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs) | ||
65 | { | 77 | { |
66 | - if (LIBRBD_USE_IOVEC) { | 78 | ERRP_GUARD(); |
67 | - RBDAIOCB *acb = rcb->acb; | 79 | |
68 | - iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0, | 80 | assert(bql_locked()); |
69 | - acb->qiov->size - offs); | 81 | |
70 | - } else { | 82 | - if (migration_block_active) { |
71 | - memset(rcb->buf + offs, 0, rcb->size - offs); | 83 | - trace_migration_block_activation("active-skipped"); |
72 | - } | 84 | - return true; |
73 | + RBDAIOCB *acb = rcb->acb; | ||
74 | + iov_memset(acb->qiov->iov, acb->qiov->niov, offs, 0, | ||
75 | + acb->qiov->size - offs); | ||
76 | } | ||
77 | |||
78 | #ifdef LIBRBD_SUPPORTS_ENCRYPTION | ||
79 | @@ -XXX,XX +XXX,XX @@ static void qemu_rbd_complete_aio(RADOSCB *rcb) | ||
80 | |||
81 | g_free(rcb); | ||
82 | |||
83 | - if (!LIBRBD_USE_IOVEC) { | ||
84 | - if (acb->cmd == RBD_AIO_READ) { | ||
85 | - qemu_iovec_from_buf(acb->qiov, 0, acb->bounce, acb->qiov->size); | ||
86 | - } | ||
87 | - qemu_vfree(acb->bounce); | ||
88 | - } | 85 | - } |
89 | - | 86 | - |
90 | acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret)); | 87 | trace_migration_block_activation("active"); |
91 | 88 | ||
92 | qemu_aio_unref(acb); | 89 | bdrv_activate_all(errp); |
93 | @@ -XXX,XX +XXX,XX @@ static void rbd_finish_aiocb(rbd_completion_t c, RADOSCB *rcb) | 90 | @@ -XXX,XX +XXX,XX @@ bool migration_block_activate(Error **errp) |
94 | rbd_finish_bh, rcb); | 91 | return false; |
92 | } | ||
93 | |||
94 | - migration_block_active = true; | ||
95 | return true; | ||
95 | } | 96 | } |
96 | 97 | ||
97 | -static int rbd_aio_discard_wrapper(rbd_image_t image, | 98 | @@ -XXX,XX +XXX,XX @@ bool migration_block_inactivate(void) |
98 | - uint64_t off, | 99 | |
99 | - uint64_t len, | 100 | assert(bql_locked()); |
100 | - rbd_completion_t comp) | 101 | |
101 | -{ | 102 | - if (!migration_block_active) { |
102 | -#ifdef LIBRBD_SUPPORTS_DISCARD | 103 | - trace_migration_block_activation("inactive-skipped"); |
103 | - return rbd_aio_discard(image, off, len, comp); | 104 | - return true; |
104 | -#else | ||
105 | - return -ENOTSUP; | ||
106 | -#endif | ||
107 | -} | ||
108 | - | ||
109 | -static int rbd_aio_flush_wrapper(rbd_image_t image, | ||
110 | - rbd_completion_t comp) | ||
111 | -{ | ||
112 | -#ifdef LIBRBD_SUPPORTS_AIO_FLUSH | ||
113 | - return rbd_aio_flush(image, comp); | ||
114 | -#else | ||
115 | - return -ENOTSUP; | ||
116 | -#endif | ||
117 | -} | ||
118 | - | ||
119 | static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | ||
120 | int64_t off, | ||
121 | QEMUIOVector *qiov, | ||
122 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | ||
123 | |||
124 | rcb = g_new(RADOSCB, 1); | ||
125 | |||
126 | - if (!LIBRBD_USE_IOVEC) { | ||
127 | - if (cmd == RBD_AIO_DISCARD || cmd == RBD_AIO_FLUSH) { | ||
128 | - acb->bounce = NULL; | ||
129 | - } else { | ||
130 | - acb->bounce = qemu_try_blockalign(bs, qiov->size); | ||
131 | - if (acb->bounce == NULL) { | ||
132 | - goto failed; | ||
133 | - } | ||
134 | - } | ||
135 | - if (cmd == RBD_AIO_WRITE) { | ||
136 | - qemu_iovec_to_buf(acb->qiov, 0, acb->bounce, qiov->size); | ||
137 | - } | ||
138 | - rcb->buf = acb->bounce; | ||
139 | - } | 105 | - } |
140 | - | 106 | - |
141 | acb->ret = 0; | 107 | trace_migration_block_activation("inactive"); |
142 | acb->error = 0; | 108 | |
143 | acb->s = s; | 109 | ret = bdrv_inactivate_all(); |
144 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | 110 | @@ -XXX,XX +XXX,XX @@ bool migration_block_inactivate(void) |
111 | return false; | ||
145 | } | 112 | } |
146 | 113 | ||
147 | switch (cmd) { | 114 | - migration_block_active = false; |
148 | - case RBD_AIO_WRITE: { | 115 | return true; |
149 | + case RBD_AIO_WRITE: | ||
150 | /* | ||
151 | * RBD APIs don't allow us to write more than actual size, so in order | ||
152 | * to support growing images, we resize the image before write | ||
153 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *rbd_start_aio(BlockDriverState *bs, | ||
154 | goto failed_completion; | ||
155 | } | ||
156 | } | ||
157 | -#ifdef LIBRBD_SUPPORTS_IOVEC | ||
158 | - r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c); | ||
159 | -#else | ||
160 | - r = rbd_aio_write(s->image, off, size, rcb->buf, c); | ||
161 | -#endif | ||
162 | + r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, off, c); | ||
163 | break; | ||
164 | - } | ||
165 | case RBD_AIO_READ: | ||
166 | -#ifdef LIBRBD_SUPPORTS_IOVEC | ||
167 | - r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c); | ||
168 | -#else | ||
169 | - r = rbd_aio_read(s->image, off, size, rcb->buf, c); | ||
170 | -#endif | ||
171 | + r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, off, c); | ||
172 | break; | ||
173 | case RBD_AIO_DISCARD: | ||
174 | - r = rbd_aio_discard_wrapper(s->image, off, size, c); | ||
175 | + r = rbd_aio_discard(s->image, off, size, c); | ||
176 | break; | ||
177 | case RBD_AIO_FLUSH: | ||
178 | - r = rbd_aio_flush_wrapper(s->image, c); | ||
179 | + r = rbd_aio_flush(s->image, c); | ||
180 | break; | ||
181 | default: | ||
182 | r = -EINVAL; | ||
183 | @@ -XXX,XX +XXX,XX @@ failed_completion: | ||
184 | rbd_aio_release(c); | ||
185 | failed: | ||
186 | g_free(rcb); | ||
187 | - if (!LIBRBD_USE_IOVEC) { | ||
188 | - qemu_vfree(acb->bounce); | ||
189 | - } | ||
190 | |||
191 | qemu_aio_unref(acb); | ||
192 | return NULL; | ||
193 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *qemu_rbd_aio_pwritev(BlockDriverState *bs, | ||
194 | RBD_AIO_WRITE); | ||
195 | } | 116 | } |
196 | 117 | diff --git a/migration/migration.c b/migration/migration.c | |
197 | -#ifdef LIBRBD_SUPPORTS_AIO_FLUSH | 118 | index XXXXXXX..XXXXXXX 100644 |
198 | static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs, | 119 | --- a/migration/migration.c |
199 | BlockCompletionFunc *cb, | 120 | +++ b/migration/migration.c |
200 | void *opaque) | 121 | @@ -XXX,XX +XXX,XX @@ void qmp_migrate_incoming(const char *uri, bool has_channels, |
201 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs, | 122 | return; |
202 | return rbd_start_aio(bs, 0, NULL, 0, cb, opaque, RBD_AIO_FLUSH); | 123 | } |
124 | |||
125 | - /* | ||
126 | - * Newly setup incoming QEMU. Mark the block active state to reflect | ||
127 | - * that the src currently owns the disks. | ||
128 | - */ | ||
129 | - migration_block_active_setup(false); | ||
130 | - | ||
131 | once = false; | ||
203 | } | 132 | } |
204 | 133 | ||
205 | -#else | 134 | @@ -XXX,XX +XXX,XX @@ static void migration_instance_init(Object *obj) |
206 | - | 135 | ms->state = MIGRATION_STATUS_NONE; |
207 | -static int qemu_rbd_co_flush(BlockDriverState *bs) | 136 | ms->mbps = -1; |
208 | -{ | 137 | ms->pages_per_second = -1; |
209 | -#if LIBRBD_VERSION_CODE >= LIBRBD_VERSION(0, 1, 1) | 138 | - /* Freshly started QEMU owns all the block devices */ |
210 | - /* rbd_flush added in 0.1.1 */ | 139 | - migration_block_active_setup(true); |
211 | - BDRVRBDState *s = bs->opaque; | 140 | qemu_sem_init(&ms->pause_sem, 0); |
212 | - return rbd_flush(s->image); | 141 | qemu_mutex_init(&ms->error_mutex); |
213 | -#else | 142 | |
214 | - return 0; | ||
215 | -#endif | ||
216 | -} | ||
217 | -#endif | ||
218 | - | ||
219 | static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) | ||
220 | { | ||
221 | BDRVRBDState *s = bs->opaque; | ||
222 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_snap_list(BlockDriverState *bs, | ||
223 | return snap_count; | ||
224 | } | ||
225 | |||
226 | -#ifdef LIBRBD_SUPPORTS_DISCARD | ||
227 | static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs, | ||
228 | int64_t offset, | ||
229 | int bytes, | ||
230 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *qemu_rbd_aio_pdiscard(BlockDriverState *bs, | ||
231 | return rbd_start_aio(bs, offset, NULL, bytes, cb, opaque, | ||
232 | RBD_AIO_DISCARD); | ||
233 | } | ||
234 | -#endif | ||
235 | |||
236 | -#ifdef LIBRBD_SUPPORTS_INVALIDATE | ||
237 | static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs, | ||
238 | Error **errp) | ||
239 | { | ||
240 | @@ -XXX,XX +XXX,XX @@ static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs, | ||
241 | error_setg_errno(errp, -r, "Failed to invalidate the cache"); | ||
242 | } | ||
243 | } | ||
244 | -#endif | ||
245 | |||
246 | static QemuOptsList qemu_rbd_create_opts = { | ||
247 | .name = "rbd-create-opts", | ||
248 | @@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_rbd = { | ||
249 | .bdrv_aio_preadv = qemu_rbd_aio_preadv, | ||
250 | .bdrv_aio_pwritev = qemu_rbd_aio_pwritev, | ||
251 | |||
252 | -#ifdef LIBRBD_SUPPORTS_AIO_FLUSH | ||
253 | .bdrv_aio_flush = qemu_rbd_aio_flush, | ||
254 | -#else | ||
255 | - .bdrv_co_flush_to_disk = qemu_rbd_co_flush, | ||
256 | -#endif | ||
257 | - | ||
258 | -#ifdef LIBRBD_SUPPORTS_DISCARD | ||
259 | .bdrv_aio_pdiscard = qemu_rbd_aio_pdiscard, | ||
260 | -#endif | ||
261 | |||
262 | .bdrv_snapshot_create = qemu_rbd_snap_create, | ||
263 | .bdrv_snapshot_delete = qemu_rbd_snap_remove, | ||
264 | .bdrv_snapshot_list = qemu_rbd_snap_list, | ||
265 | .bdrv_snapshot_goto = qemu_rbd_snap_rollback, | ||
266 | -#ifdef LIBRBD_SUPPORTS_INVALIDATE | ||
267 | .bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache, | ||
268 | -#endif | ||
269 | |||
270 | .strong_runtime_opts = qemu_rbd_strong_runtime_opts, | ||
271 | }; | ||
272 | diff --git a/meson.build b/meson.build | ||
273 | index XXXXXXX..XXXXXXX 100644 | ||
274 | --- a/meson.build | ||
275 | +++ b/meson.build | ||
276 | @@ -XXX,XX +XXX,XX @@ if not get_option('rbd').auto() or have_block | ||
277 | int main(void) { | ||
278 | rados_t cluster; | ||
279 | rados_create(&cluster, NULL); | ||
280 | + #if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 12, 0) | ||
281 | + #error | ||
282 | + #endif | ||
283 | return 0; | ||
284 | }''', dependencies: [librbd, librados]) | ||
285 | rbd = declare_dependency(dependencies: [librbd, librados]) | ||
286 | elif get_option('rbd').enabled() | ||
287 | - error('could not link librados') | ||
288 | + error('librbd >= 1.12.0 required') | ||
289 | else | ||
290 | - warning('could not link librados, disabling') | ||
291 | + warning('librbd >= 1.12.0 not found, disabling') | ||
292 | endif | ||
293 | endif | ||
294 | endif | ||
295 | -- | 143 | -- |
296 | 2.31.1 | 144 | 2.48.1 |
297 | |||
298 | diff view generated by jsdifflib |
1 | From: Peter Lieven <pl@kamp.de> | 1 | An active node makes unrestricted use of its children and would possibly |
---|---|---|---|
2 | run into assertion failures when it operates on an inactive child node. | ||
2 | 3 | ||
3 | adding myself as a designated reviewer. | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | 5 | Acked-by: Fabiano Rosas <farosas@suse.de> | |
5 | Signed-off-by: Peter Lieven <pl@kamp.de> | 6 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | Message-Id: <20210707180449.32665-2-pl@kamp.de> | 7 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
7 | Acked-by: Ilya Dryomov <idryomov@gmail.com> | 8 | Message-ID: <20250204211407.381505-6-kwolf@redhat.com> |
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
9 | --- | 10 | --- |
10 | MAINTAINERS | 1 + | 11 | block.c | 5 +++++ |
11 | 1 file changed, 1 insertion(+) | 12 | 1 file changed, 5 insertions(+) |
12 | 13 | ||
13 | diff --git a/MAINTAINERS b/MAINTAINERS | 14 | diff --git a/block.c b/block.c |
14 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/MAINTAINERS | 16 | --- a/block.c |
16 | +++ b/MAINTAINERS | 17 | +++ b/block.c |
17 | @@ -XXX,XX +XXX,XX @@ F: block/vmdk.c | 18 | @@ -XXX,XX +XXX,XX @@ bdrv_attach_child_noperm(BlockDriverState *parent_bs, |
18 | 19 | child_bs->node_name, child_name, parent_bs->node_name); | |
19 | RBD | 20 | return NULL; |
20 | M: Ilya Dryomov <idryomov@gmail.com> | 21 | } |
21 | +R: Peter Lieven <pl@kamp.de> | 22 | + if (bdrv_is_inactive(child_bs) && !bdrv_is_inactive(parent_bs)) { |
22 | L: qemu-block@nongnu.org | 23 | + error_setg(errp, "Inactive '%s' can't be a %s child of active '%s'", |
23 | S: Supported | 24 | + child_bs->node_name, child_name, parent_bs->node_name); |
24 | F: block/rbd.c | 25 | + return NULL; |
26 | + } | ||
27 | |||
28 | bdrv_get_cumulative_perm(parent_bs, &perm, &shared_perm); | ||
29 | bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL, | ||
25 | -- | 30 | -- |
26 | 2.31.1 | 31 | 2.48.1 |
27 | |||
28 | diff view generated by jsdifflib |
1 | As the BlockReopenQueue can contain nodes in multiple AioContexts, only | 1 | In order for block_resize to fail gracefully on an inactive node instead |
---|---|---|---|
2 | one of which may be locked when AIO_WAIT_WHILE() can be called, we can't | 2 | of crashing with an assertion failure in bdrv_co_write_req_prepare() |
3 | let the caller lock the right contexts. Instead, individually lock the | 3 | (called from bdrv_co_truncate()), we need to check for inactive nodes |
4 | AioContext of a single node when iterating the queue. | 4 | also when they are attached as a root node and make sure that |
5 | 5 | BLK_PERM_RESIZE isn't among the permissions allowed for inactive nodes. | |
6 | Reintroduce bdrv_reopen() as a wrapper for reopening a single node that | 6 | To this effect, don't enumerate the permissions that are incompatible |
7 | drains the node and temporarily drops the AioContext lock for | 7 | with inactive nodes any more, but allow only BLK_PERM_CONSISTENT_READ |
8 | bdrv_reopen_multiple(). | 8 | for them. |
9 | 9 | ||
10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 11 | Acked-by: Fabiano Rosas <farosas@suse.de> |
12 | Message-Id: <20210708114709.206487-4-kwolf@redhat.com> | 12 | Reviewed-by: Eric Blake <eblake@redhat.com> |
13 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
14 | Message-ID: <20250204211407.381505-7-kwolf@redhat.com> | ||
13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
14 | --- | 16 | --- |
15 | include/block/block.h | 2 ++ | 17 | block.c | 7 +++++++ |
16 | block.c | 49 ++++++++++++++++++++++++++++++++++++------- | 18 | block/block-backend.c | 2 +- |
17 | block/replication.c | 7 +++++++ | 19 | 2 files changed, 8 insertions(+), 1 deletion(-) |
18 | blockdev.c | 5 +++++ | ||
19 | qemu-io-cmds.c | 7 +------ | ||
20 | 5 files changed, 57 insertions(+), 13 deletions(-) | ||
21 | 20 | ||
22 | diff --git a/include/block/block.h b/include/block/block.h | ||
23 | index XXXXXXX..XXXXXXX 100644 | ||
24 | --- a/include/block/block.h | ||
25 | +++ b/include/block/block.h | ||
26 | @@ -XXX,XX +XXX,XX @@ BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue, | ||
27 | bool keep_old_opts); | ||
28 | void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue); | ||
29 | int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp); | ||
30 | +int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts, | ||
31 | + Error **errp); | ||
32 | int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, | ||
33 | Error **errp); | ||
34 | int bdrv_pwrite_zeroes(BdrvChild *child, int64_t offset, | ||
35 | diff --git a/block.c b/block.c | 21 | diff --git a/block.c b/block.c |
36 | index XXXXXXX..XXXXXXX 100644 | 22 | index XXXXXXX..XXXXXXX 100644 |
37 | --- a/block.c | 23 | --- a/block.c |
38 | +++ b/block.c | 24 | +++ b/block.c |
39 | @@ -XXX,XX +XXX,XX @@ void bdrv_reopen_queue_free(BlockReopenQueue *bs_queue) | 25 | @@ -XXX,XX +XXX,XX @@ bdrv_attach_child_common(BlockDriverState *child_bs, |
40 | * | 26 | assert(child_class->get_parent_desc); |
41 | * All affected nodes must be drained between bdrv_reopen_queue() and | 27 | GLOBAL_STATE_CODE(); |
42 | * bdrv_reopen_multiple(). | 28 | |
43 | + * | 29 | + if (bdrv_is_inactive(child_bs) && (perm & ~BLK_PERM_CONSISTENT_READ)) { |
44 | + * To be called from the main thread, with all other AioContexts unlocked. | 30 | + g_autofree char *perm_names = bdrv_perm_names(perm); |
45 | */ | 31 | + error_setg(errp, "Permission '%s' unavailable on inactive node", |
46 | int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) | 32 | + perm_names); |
47 | { | 33 | + return NULL; |
48 | int ret = -1; | ||
49 | BlockReopenQueueEntry *bs_entry, *next; | ||
50 | + AioContext *ctx; | ||
51 | Transaction *tran = tran_new(); | ||
52 | g_autoptr(GHashTable) found = NULL; | ||
53 | g_autoptr(GSList) refresh_list = NULL; | ||
54 | |||
55 | + assert(qemu_get_current_aio_context() == qemu_get_aio_context()); | ||
56 | assert(bs_queue != NULL); | ||
57 | |||
58 | QTAILQ_FOREACH(bs_entry, bs_queue, entry) { | ||
59 | + ctx = bdrv_get_aio_context(bs_entry->state.bs); | ||
60 | + aio_context_acquire(ctx); | ||
61 | ret = bdrv_flush(bs_entry->state.bs); | ||
62 | + aio_context_release(ctx); | ||
63 | if (ret < 0) { | ||
64 | error_setg_errno(errp, -ret, "Error flushing drive"); | ||
65 | goto abort; | ||
66 | @@ -XXX,XX +XXX,XX @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) | ||
67 | |||
68 | QTAILQ_FOREACH(bs_entry, bs_queue, entry) { | ||
69 | assert(bs_entry->state.bs->quiesce_counter > 0); | ||
70 | + ctx = bdrv_get_aio_context(bs_entry->state.bs); | ||
71 | + aio_context_acquire(ctx); | ||
72 | ret = bdrv_reopen_prepare(&bs_entry->state, bs_queue, tran, errp); | ||
73 | + aio_context_release(ctx); | ||
74 | if (ret < 0) { | ||
75 | goto abort; | ||
76 | } | ||
77 | @@ -XXX,XX +XXX,XX @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) | ||
78 | * to first element. | ||
79 | */ | ||
80 | QTAILQ_FOREACH_REVERSE(bs_entry, bs_queue, entry) { | ||
81 | + ctx = bdrv_get_aio_context(bs_entry->state.bs); | ||
82 | + aio_context_acquire(ctx); | ||
83 | bdrv_reopen_commit(&bs_entry->state); | ||
84 | + aio_context_release(ctx); | ||
85 | } | ||
86 | |||
87 | tran_commit(tran); | ||
88 | @@ -XXX,XX +XXX,XX @@ int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) | ||
89 | BlockDriverState *bs = bs_entry->state.bs; | ||
90 | |||
91 | if (bs->drv->bdrv_reopen_commit_post) { | ||
92 | + ctx = bdrv_get_aio_context(bs); | ||
93 | + aio_context_acquire(ctx); | ||
94 | bs->drv->bdrv_reopen_commit_post(&bs_entry->state); | ||
95 | + aio_context_release(ctx); | ||
96 | } | ||
97 | } | ||
98 | |||
99 | @@ -XXX,XX +XXX,XX @@ abort: | ||
100 | tran_abort(tran); | ||
101 | QTAILQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) { | ||
102 | if (bs_entry->prepared) { | ||
103 | + ctx = bdrv_get_aio_context(bs_entry->state.bs); | ||
104 | + aio_context_acquire(ctx); | ||
105 | bdrv_reopen_abort(&bs_entry->state); | ||
106 | + aio_context_release(ctx); | ||
107 | } | ||
108 | } | ||
109 | |||
110 | @@ -XXX,XX +XXX,XX @@ cleanup: | ||
111 | return ret; | ||
112 | } | ||
113 | |||
114 | -int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, | ||
115 | - Error **errp) | ||
116 | +int bdrv_reopen(BlockDriverState *bs, QDict *opts, bool keep_old_opts, | ||
117 | + Error **errp) | ||
118 | { | ||
119 | - int ret; | ||
120 | + AioContext *ctx = bdrv_get_aio_context(bs); | ||
121 | BlockReopenQueue *queue; | ||
122 | - QDict *opts = qdict_new(); | ||
123 | - | ||
124 | - qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only); | ||
125 | + int ret; | ||
126 | |||
127 | bdrv_subtree_drained_begin(bs); | ||
128 | - queue = bdrv_reopen_queue(NULL, bs, opts, true); | ||
129 | + if (ctx != qemu_get_aio_context()) { | ||
130 | + aio_context_release(ctx); | ||
131 | + } | 34 | + } |
132 | + | 35 | + |
133 | + queue = bdrv_reopen_queue(NULL, bs, opts, keep_old_opts); | 36 | new_child = g_new(BdrvChild, 1); |
134 | ret = bdrv_reopen_multiple(queue, errp); | 37 | *new_child = (BdrvChild) { |
135 | + | 38 | .bs = NULL, |
136 | + if (ctx != qemu_get_aio_context()) { | 39 | diff --git a/block/block-backend.c b/block/block-backend.c |
137 | + aio_context_acquire(ctx); | ||
138 | + } | ||
139 | bdrv_subtree_drained_end(bs); | ||
140 | |||
141 | return ret; | ||
142 | } | ||
143 | |||
144 | +int bdrv_reopen_set_read_only(BlockDriverState *bs, bool read_only, | ||
145 | + Error **errp) | ||
146 | +{ | ||
147 | + QDict *opts = qdict_new(); | ||
148 | + | ||
149 | + qdict_put_bool(opts, BDRV_OPT_READ_ONLY, read_only); | ||
150 | + | ||
151 | + return bdrv_reopen(bs, opts, true, errp); | ||
152 | +} | ||
153 | + | ||
154 | /* | ||
155 | * Take a BDRVReopenState and check if the value of 'backing' in the | ||
156 | * reopen_state->options QDict is valid or not. | ||
157 | diff --git a/block/replication.c b/block/replication.c | ||
158 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
159 | --- a/block/replication.c | 41 | --- a/block/block-backend.c |
160 | +++ b/block/replication.c | 42 | +++ b/block/block-backend.c |
161 | @@ -XXX,XX +XXX,XX @@ static void reopen_backing_file(BlockDriverState *bs, bool writable, | 43 | @@ -XXX,XX +XXX,XX @@ static bool blk_can_inactivate(BlockBackend *blk) |
44 | * guest. For block job BBs that satisfy this, we can just allow | ||
45 | * it. This is the case for mirror job source, which is required | ||
46 | * by libvirt non-shared block migration. */ | ||
47 | - if (!(blk->perm & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED))) { | ||
48 | + if (!(blk->perm & ~BLK_PERM_CONSISTENT_READ)) { | ||
49 | return true; | ||
162 | } | 50 | } |
163 | 51 | ||
164 | if (reopen_queue) { | ||
165 | + AioContext *ctx = bdrv_get_aio_context(bs); | ||
166 | + if (ctx != qemu_get_aio_context()) { | ||
167 | + aio_context_release(ctx); | ||
168 | + } | ||
169 | bdrv_reopen_multiple(reopen_queue, errp); | ||
170 | + if (ctx != qemu_get_aio_context()) { | ||
171 | + aio_context_acquire(ctx); | ||
172 | + } | ||
173 | } | ||
174 | |||
175 | bdrv_subtree_drained_end(s->hidden_disk->bs); | ||
176 | diff --git a/blockdev.c b/blockdev.c | ||
177 | index XXXXXXX..XXXXXXX 100644 | ||
178 | --- a/blockdev.c | ||
179 | +++ b/blockdev.c | ||
180 | @@ -XXX,XX +XXX,XX @@ void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp) | ||
181 | ctx = bdrv_get_aio_context(bs); | ||
182 | aio_context_acquire(ctx); | ||
183 | bdrv_subtree_drained_begin(bs); | ||
184 | + aio_context_release(ctx); | ||
185 | + | ||
186 | queue = bdrv_reopen_queue(NULL, bs, qdict, false); | ||
187 | bdrv_reopen_multiple(queue, errp); | ||
188 | + | ||
189 | + ctx = bdrv_get_aio_context(bs); | ||
190 | + aio_context_acquire(ctx); | ||
191 | bdrv_subtree_drained_end(bs); | ||
192 | aio_context_release(ctx); | ||
193 | |||
194 | diff --git a/qemu-io-cmds.c b/qemu-io-cmds.c | ||
195 | index XXXXXXX..XXXXXXX 100644 | ||
196 | --- a/qemu-io-cmds.c | ||
197 | +++ b/qemu-io-cmds.c | ||
198 | @@ -XXX,XX +XXX,XX @@ static int reopen_f(BlockBackend *blk, int argc, char **argv) | ||
199 | bool writethrough = !blk_enable_write_cache(blk); | ||
200 | bool has_rw_option = false; | ||
201 | bool has_cache_option = false; | ||
202 | - | ||
203 | - BlockReopenQueue *brq; | ||
204 | Error *local_err = NULL; | ||
205 | |||
206 | while ((c = getopt(argc, argv, "c:o:rw")) != -1) { | ||
207 | @@ -XXX,XX +XXX,XX @@ static int reopen_f(BlockBackend *blk, int argc, char **argv) | ||
208 | qdict_put_bool(opts, BDRV_OPT_CACHE_NO_FLUSH, flags & BDRV_O_NO_FLUSH); | ||
209 | } | ||
210 | |||
211 | - bdrv_subtree_drained_begin(bs); | ||
212 | - brq = bdrv_reopen_queue(NULL, bs, opts, true); | ||
213 | - bdrv_reopen_multiple(brq, &local_err); | ||
214 | - bdrv_subtree_drained_end(bs); | ||
215 | + bdrv_reopen(bs, opts, true, &local_err); | ||
216 | |||
217 | if (local_err) { | ||
218 | error_report_err(local_err); | ||
219 | -- | 52 | -- |
220 | 2.31.1 | 53 | 2.48.1 |
221 | |||
222 | diff view generated by jsdifflib |
1 | From: Alberto Garcia <berto@igalia.com> | 1 | In QEMU, nodes are automatically created inactive while expecting an |
---|---|---|---|
2 | incoming migration (i.e. RUN_STATE_INMIGRATE). In qemu-storage-daemon, | ||
3 | the notion of runstates doesn't exist. It also wouldn't necessarily make | ||
4 | sense to introduce it because a single daemon can serve multiple VMs | ||
5 | that can be in different states. | ||
2 | 6 | ||
3 | This patch drops the 'x-' prefix from x-blockdev-reopen. | 7 | Therefore, allow the user to explicitly open images as inactive with a |
8 | new option. The default is as before: Nodes are usually active, except | ||
9 | when created during RUN_STATE_INMIGRATE. | ||
4 | 10 | ||
5 | Signed-off-by: Alberto Garcia <berto@igalia.com> | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
7 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 12 | Acked-by: Fabiano Rosas <farosas@suse.de> |
8 | Message-Id: <20210708114709.206487-7-kwolf@redhat.com> | 13 | Reviewed-by: Eric Blake <eblake@redhat.com> |
14 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
15 | Message-ID: <20250204211407.381505-8-kwolf@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 16 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | --- | 17 | --- |
11 | qapi/block-core.json | 6 +++--- | 18 | qapi/block-core.json | 6 ++++++ |
12 | blockdev.c | 2 +- | 19 | include/block/block-common.h | 1 + |
13 | tests/qemu-iotests/155 | 2 +- | 20 | block.c | 9 +++++++++ |
14 | tests/qemu-iotests/165 | 2 +- | 21 | 3 files changed, 16 insertions(+) |
15 | tests/qemu-iotests/245 | 10 +++++----- | ||
16 | tests/qemu-iotests/248 | 2 +- | ||
17 | tests/qemu-iotests/248.out | 2 +- | ||
18 | tests/qemu-iotests/296 | 2 +- | ||
19 | tests/qemu-iotests/298 | 2 +- | ||
20 | tests/qemu-iotests/tests/remove-bitmap-from-backing | 4 ++-- | ||
21 | 10 files changed, 17 insertions(+), 17 deletions(-) | ||
22 | 22 | ||
23 | diff --git a/qapi/block-core.json b/qapi/block-core.json | 23 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
24 | index XXXXXXX..XXXXXXX 100644 | 24 | index XXXXXXX..XXXXXXX 100644 |
25 | --- a/qapi/block-core.json | 25 | --- a/qapi/block-core.json |
26 | +++ b/qapi/block-core.json | 26 | +++ b/qapi/block-core.json |
27 | @@ -XXX,XX +XXX,XX @@ | 27 | @@ -XXX,XX +XXX,XX @@ |
28 | { 'command': 'blockdev-add', 'data': 'BlockdevOptions', 'boxed': true } | ||
29 | |||
30 | ## | ||
31 | -# @x-blockdev-reopen: | ||
32 | +# @blockdev-reopen: | ||
33 | # | 28 | # |
34 | # Reopens one or more block devices using the given set of options. | 29 | # @cache: cache-related options |
35 | # Any option not specified will be reset to its default value regardless | 30 | # |
31 | +# @active: whether the block node should be activated (default: true). | ||
32 | +# Having inactive block nodes is useful primarily for migration because it | ||
33 | +# allows opening an image on the destination while the source is still | ||
34 | +# holding locks for it. (Since 10.0) | ||
35 | +# | ||
36 | # @read-only: whether the block device should be read-only (default: | ||
37 | # false). Note that some block drivers support only read-only | ||
38 | # access, either generally or in certain configurations. In this | ||
36 | @@ -XXX,XX +XXX,XX @@ | 39 | @@ -XXX,XX +XXX,XX @@ |
37 | # image does not have a default backing file name as part of its | 40 | '*node-name': 'str', |
38 | # metadata. | 41 | '*discard': 'BlockdevDiscardOptions', |
39 | # | 42 | '*cache': 'BlockdevCacheOptions', |
40 | -# Since: 4.0 | 43 | + '*active': 'bool', |
41 | +# Since: 6.1 | 44 | '*read-only': 'bool', |
42 | ## | 45 | '*auto-read-only': 'bool', |
43 | -{ 'command': 'x-blockdev-reopen', | 46 | '*force-share': 'bool', |
44 | +{ 'command': 'blockdev-reopen', | 47 | diff --git a/include/block/block-common.h b/include/block/block-common.h |
45 | 'data': { 'options': ['BlockdevOptions'] } } | ||
46 | |||
47 | ## | ||
48 | diff --git a/blockdev.c b/blockdev.c | ||
49 | index XXXXXXX..XXXXXXX 100644 | 48 | index XXXXXXX..XXXXXXX 100644 |
50 | --- a/blockdev.c | 49 | --- a/include/block/block-common.h |
51 | +++ b/blockdev.c | 50 | +++ b/include/block/block-common.h |
52 | @@ -XXX,XX +XXX,XX @@ fail: | 51 | @@ -XXX,XX +XXX,XX @@ typedef enum { |
53 | visit_free(v); | 52 | #define BDRV_OPT_AUTO_READ_ONLY "auto-read-only" |
53 | #define BDRV_OPT_DISCARD "discard" | ||
54 | #define BDRV_OPT_FORCE_SHARE "force-share" | ||
55 | +#define BDRV_OPT_ACTIVE "active" | ||
56 | |||
57 | |||
58 | #define BDRV_SECTOR_BITS 9 | ||
59 | diff --git a/block.c b/block.c | ||
60 | index XXXXXXX..XXXXXXX 100644 | ||
61 | --- a/block.c | ||
62 | +++ b/block.c | ||
63 | @@ -XXX,XX +XXX,XX @@ static void update_flags_from_options(int *flags, QemuOpts *opts) | ||
64 | if (qemu_opt_get_bool_del(opts, BDRV_OPT_AUTO_READ_ONLY, false)) { | ||
65 | *flags |= BDRV_O_AUTO_RDONLY; | ||
66 | } | ||
67 | + | ||
68 | + if (!qemu_opt_get_bool_del(opts, BDRV_OPT_ACTIVE, true)) { | ||
69 | + *flags |= BDRV_O_INACTIVE; | ||
70 | + } | ||
54 | } | 71 | } |
55 | 72 | ||
56 | -void qmp_x_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp) | 73 | static void update_options_from_flags(QDict *options, int flags) |
57 | +void qmp_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp) | 74 | @@ -XXX,XX +XXX,XX @@ QemuOptsList bdrv_runtime_opts = { |
58 | { | 75 | .type = QEMU_OPT_BOOL, |
59 | BlockReopenQueue *queue = NULL; | 76 | .help = "Ignore flush requests", |
60 | GSList *drained = NULL; | 77 | }, |
61 | diff --git a/tests/qemu-iotests/155 b/tests/qemu-iotests/155 | 78 | + { |
62 | index XXXXXXX..XXXXXXX 100755 | 79 | + .name = BDRV_OPT_ACTIVE, |
63 | --- a/tests/qemu-iotests/155 | 80 | + .type = QEMU_OPT_BOOL, |
64 | +++ b/tests/qemu-iotests/155 | 81 | + .help = "Node is activated", |
65 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevMirrorReopen(MirrorBaseClass): | 82 | + }, |
66 | result = self.vm.qmp('blockdev-add', node_name="backing", | 83 | { |
67 | driver="null-co") | 84 | .name = BDRV_OPT_READ_ONLY, |
68 | self.assert_qmp(result, 'return', {}) | 85 | .type = QEMU_OPT_BOOL, |
69 | - result = self.vm.qmp('x-blockdev-reopen', options=[{ | ||
70 | + result = self.vm.qmp('blockdev-reopen', options=[{ | ||
71 | 'node-name': "target", | ||
72 | 'driver': iotests.imgfmt, | ||
73 | 'file': "target-file", | ||
74 | diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165 | ||
75 | index XXXXXXX..XXXXXXX 100755 | ||
76 | --- a/tests/qemu-iotests/165 | ||
77 | +++ b/tests/qemu-iotests/165 | ||
78 | @@ -XXX,XX +XXX,XX @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): | ||
79 | assert sha256_1 == self.getSha256() | ||
80 | |||
81 | # Reopen to RW | ||
82 | - result = self.vm.qmp('x-blockdev-reopen', options=[{ | ||
83 | + result = self.vm.qmp('blockdev-reopen', options=[{ | ||
84 | 'node-name': 'node0', | ||
85 | 'driver': iotests.imgfmt, | ||
86 | 'file': { | ||
87 | diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 | ||
88 | index XXXXXXX..XXXXXXX 100755 | ||
89 | --- a/tests/qemu-iotests/245 | ||
90 | +++ b/tests/qemu-iotests/245 | ||
91 | @@ -XXX,XX +XXX,XX @@ | ||
92 | #!/usr/bin/env python3 | ||
93 | # group: rw | ||
94 | # | ||
95 | -# Test cases for the QMP 'x-blockdev-reopen' command | ||
96 | +# Test cases for the QMP 'blockdev-reopen' command | ||
97 | # | ||
98 | # Copyright (C) 2018-2019 Igalia, S.L. | ||
99 | # Author: Alberto Garcia <berto@igalia.com> | ||
100 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
101 | "Expected output of %d qemu-io commands, found %d" % | ||
102 | (found, self.total_io_cmds)) | ||
103 | |||
104 | - # Run x-blockdev-reopen on a list of block devices | ||
105 | + # Run blockdev-reopen on a list of block devices | ||
106 | def reopenMultiple(self, opts, errmsg = None): | ||
107 | - result = self.vm.qmp('x-blockdev-reopen', conv_keys=False, options=opts) | ||
108 | + result = self.vm.qmp('blockdev-reopen', conv_keys=False, options=opts) | ||
109 | if errmsg: | ||
110 | self.assert_qmp(result, 'error/class', 'GenericError') | ||
111 | self.assert_qmp(result, 'error/desc', errmsg) | ||
112 | else: | ||
113 | self.assert_qmp(result, 'return', {}) | ||
114 | |||
115 | - # Run x-blockdev-reopen on a single block device (specified by | ||
116 | + # Run blockdev-reopen on a single block device (specified by | ||
117 | # 'opts') but applying 'newopts' on top of it. The original 'opts' | ||
118 | # dict is unmodified | ||
119 | def reopen(self, opts, newopts = {}, errmsg = None): | ||
120 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
121 | self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") | ||
122 | self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string") | ||
123 | |||
124 | - # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it | ||
125 | + # node-name is optional in BlockdevOptions, but blockdev-reopen needs it | ||
126 | del opts['node-name'] | ||
127 | self.reopen(opts, {}, "node-name not specified") | ||
128 | |||
129 | diff --git a/tests/qemu-iotests/248 b/tests/qemu-iotests/248 | ||
130 | index XXXXXXX..XXXXXXX 100755 | ||
131 | --- a/tests/qemu-iotests/248 | ||
132 | +++ b/tests/qemu-iotests/248 | ||
133 | @@ -XXX,XX +XXX,XX @@ vm.event_wait('JOB_STATUS_CHANGE', timeout=3.0, | ||
134 | vm.get_qmp_events() | ||
135 | |||
136 | del blockdev_opts['file']['size'] | ||
137 | -vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles], | ||
138 | +vm.qmp_log('blockdev-reopen', filters=[filter_qmp_testfiles], | ||
139 | options = [ blockdev_opts ]) | ||
140 | |||
141 | vm.qmp_log('block-job-resume', device='drive0') | ||
142 | diff --git a/tests/qemu-iotests/248.out b/tests/qemu-iotests/248.out | ||
143 | index XXXXXXX..XXXXXXX 100644 | ||
144 | --- a/tests/qemu-iotests/248.out | ||
145 | +++ b/tests/qemu-iotests/248.out | ||
146 | @@ -XXX,XX +XXX,XX @@ | ||
147 | {"return": {}} | ||
148 | {"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}} | ||
149 | {"return": {}} | ||
150 | -{"execute": "x-blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}} | ||
151 | +{"execute": "blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}} | ||
152 | {"return": {}} | ||
153 | {"execute": "block-job-resume", "arguments": {"device": "drive0"}} | ||
154 | {"return": {}} | ||
155 | diff --git a/tests/qemu-iotests/296 b/tests/qemu-iotests/296 | ||
156 | index XXXXXXX..XXXXXXX 100755 | ||
157 | --- a/tests/qemu-iotests/296 | ||
158 | +++ b/tests/qemu-iotests/296 | ||
159 | @@ -XXX,XX +XXX,XX @@ class EncryptionSetupTestCase(iotests.QMPTestCase): | ||
160 | def openImageQmp(self, vm, id, file, secret, | ||
161 | readOnly = False, reOpen = False): | ||
162 | |||
163 | - command = 'x-blockdev-reopen' if reOpen else 'blockdev-add' | ||
164 | + command = 'blockdev-reopen' if reOpen else 'blockdev-add' | ||
165 | |||
166 | opts = { | ||
167 | 'driver': iotests.imgfmt, | ||
168 | diff --git a/tests/qemu-iotests/298 b/tests/qemu-iotests/298 | ||
169 | index XXXXXXX..XXXXXXX 100755 | ||
170 | --- a/tests/qemu-iotests/298 | ||
171 | +++ b/tests/qemu-iotests/298 | ||
172 | @@ -XXX,XX +XXX,XX @@ class TestPreallocateFilter(TestPreallocateBase): | ||
173 | self.check_big() | ||
174 | |||
175 | def test_reopen_opts(self): | ||
176 | - result = self.vm.qmp('x-blockdev-reopen', options=[{ | ||
177 | + result = self.vm.qmp('blockdev-reopen', options=[{ | ||
178 | 'node-name': 'disk', | ||
179 | 'driver': iotests.imgfmt, | ||
180 | 'file': { | ||
181 | diff --git a/tests/qemu-iotests/tests/remove-bitmap-from-backing b/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
182 | index XXXXXXX..XXXXXXX 100755 | ||
183 | --- a/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
184 | +++ b/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
185 | @@ -XXX,XX +XXX,XX @@ new_base_opts = { | ||
186 | } | ||
187 | |||
188 | # Don't want to bother with filtering qmp_log for reopen command | ||
189 | -result = vm.qmp('x-blockdev-reopen', **new_base_opts) | ||
190 | +result = vm.qmp('blockdev-reopen', **new_base_opts) | ||
191 | if result != {'return': {}}: | ||
192 | log('Failed to reopen: ' + str(result)) | ||
193 | |||
194 | @@ -XXX,XX +XXX,XX @@ log('Remove persistent bitmap from base node reopened to RW:') | ||
195 | vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0') | ||
196 | |||
197 | new_base_opts['options'][0]['read-only'] = True | ||
198 | -result = vm.qmp('x-blockdev-reopen', **new_base_opts) | ||
199 | +result = vm.qmp('blockdev-reopen', **new_base_opts) | ||
200 | if result != {'return': {}}: | ||
201 | log('Failed to reopen: ' + str(result)) | ||
202 | |||
203 | -- | 86 | -- |
204 | 2.31.1 | 87 | 2.48.1 |
205 | |||
206 | diff view generated by jsdifflib |
1 | From: Or Ozeri <oro@il.ibm.com> | 1 | The system emulator tries to automatically activate and inactivate block |
---|---|---|---|
2 | nodes at the right point during migration. However, there are still | ||
3 | cases where it's necessary that the user can do this manually. | ||
2 | 4 | ||
3 | Starting from ceph Pacific, RBD has built-in support for image-level encryption. | 5 | Images are only activated on the destination VM of a migration when the |
4 | Currently supported formats are LUKS version 1 and 2. | 6 | VM is actually resumed. If the VM was paused, this doesn't happen |
7 | automatically. The user may want to perform some operation on a block | ||
8 | device (e.g. taking a snapshot or starting a block job) without also | ||
9 | resuming the VM yet. This is an example where a manual command is | ||
10 | necessary. | ||
5 | 11 | ||
6 | There are 2 new relevant librbd APIs for controlling encryption, both expect an | 12 | Another example is VM migration when the image files are opened by an |
7 | open image context: | 13 | external qemu-storage-daemon instance on each side. In this case, the |
14 | process that needs to hand over the images isn't even part of the | ||
15 | migration and can't know when the migration completes. Management tools | ||
16 | need a way to explicitly inactivate images on the source and activate | ||
17 | them on the destination. | ||
8 | 18 | ||
9 | rbd_encryption_format: formats an image (i.e. writes the LUKS header) | 19 | This adds a new blockdev-set-active QMP command that lets the user |
10 | rbd_encryption_load: loads encryptor/decryptor to the image IO stack | 20 | change the status of individual nodes (this is necessary in |
21 | qemu-storage-daemon because it could be serving multiple VMs and only | ||
22 | one of them migrates at a time). For convenience, operating on all | ||
23 | devices (like QEMU does automatically during migration) is offered as an | ||
24 | option, too, and can be used in the context of single VM. | ||
11 | 25 | ||
12 | This commit extends the qemu rbd driver API to support the above. | 26 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | 27 | Acked-by: Fabiano Rosas <farosas@suse.de> | |
14 | Signed-off-by: Or Ozeri <oro@il.ibm.com> | 28 | Reviewed-by: Eric Blake <eblake@redhat.com> |
15 | Message-Id: <20210627114635.39326-1-oro@il.ibm.com> | 29 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
16 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | 30 | Message-ID: <20250204211407.381505-9-kwolf@redhat.com> |
17 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 31 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
18 | --- | 32 | --- |
19 | qapi/block-core.json | 110 ++++++++++++- | 33 | qapi/block-core.json | 32 ++++++++++++++++++++++++++++++ |
20 | block/rbd.c | 361 ++++++++++++++++++++++++++++++++++++++++++- | 34 | include/block/block-global-state.h | 3 +++ |
21 | 2 files changed, 465 insertions(+), 6 deletions(-) | 35 | block.c | 21 ++++++++++++++++++++ |
36 | blockdev.c | 32 ++++++++++++++++++++++++++++++ | ||
37 | 4 files changed, 88 insertions(+) | ||
22 | 38 | ||
23 | diff --git a/qapi/block-core.json b/qapi/block-core.json | 39 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
24 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
25 | --- a/qapi/block-core.json | 41 | --- a/qapi/block-core.json |
26 | +++ b/qapi/block-core.json | 42 | +++ b/qapi/block-core.json |
27 | @@ -XXX,XX +XXX,XX @@ | 43 | @@ -XXX,XX +XXX,XX @@ |
28 | 'extents': ['ImageInfo'] | 44 | { 'command': 'blockdev-del', 'data': { 'node-name': 'str' }, |
29 | } } | 45 | 'allow-preconfig': true } |
30 | 46 | ||
31 | +## | 47 | +## |
32 | +# @ImageInfoSpecificRbd: | 48 | +# @blockdev-set-active: |
33 | +# | 49 | +# |
34 | +# @encryption-format: Image encryption format | 50 | +# Activate or inactivate a block device. Use this to manage the handover of |
51 | +# block devices on migration with qemu-storage-daemon. | ||
35 | +# | 52 | +# |
36 | +# Since: 6.1 | 53 | +# Activating a node automatically activates all of its child nodes first. |
54 | +# Inactivating a node automatically inactivates any of its child nodes that are | ||
55 | +# not in use by a still active node. | ||
56 | +# | ||
57 | +# @node-name: Name of the graph node to activate or inactivate. By default, all | ||
58 | +# nodes are affected by the operation. | ||
59 | +# | ||
60 | +# @active: true if the nodes should be active when the command returns success, | ||
61 | +# false if they should be inactive. | ||
62 | +# | ||
63 | +# Since: 10.0 | ||
64 | +# | ||
65 | +# .. qmp-example:: | ||
66 | +# | ||
67 | +# -> { "execute": "blockdev-set-active", | ||
68 | +# "arguments": { | ||
69 | +# "node-name": "node0", | ||
70 | +# "active": false | ||
71 | +# } | ||
72 | +# } | ||
73 | +# <- { "return": {} } | ||
37 | +## | 74 | +## |
38 | +{ 'struct': 'ImageInfoSpecificRbd', | 75 | +{ 'command': 'blockdev-set-active', |
39 | + 'data': { | 76 | + 'data': { '*node-name': 'str', 'active': 'bool' }, |
40 | + '*encryption-format': 'RbdImageEncryptionFormat' | 77 | + 'allow-preconfig': true } |
41 | + } } | ||
42 | + | 78 | + |
43 | ## | 79 | ## |
44 | # @ImageInfoSpecific: | 80 | # @BlockdevCreateOptionsFile: |
45 | # | 81 | # |
46 | @@ -XXX,XX +XXX,XX @@ | 82 | diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h |
47 | # If we need to add block driver specific parameters for | 83 | index XXXXXXX..XXXXXXX 100644 |
48 | # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS | 84 | --- a/include/block/block-global-state.h |
49 | # to define a ImageInfoSpecificLUKS | 85 | +++ b/include/block/block-global-state.h |
50 | - 'luks': 'QCryptoBlockInfoLUKS' | 86 | @@ -XXX,XX +XXX,XX @@ bdrv_activate(BlockDriverState *bs, Error **errp); |
51 | + 'luks': 'QCryptoBlockInfoLUKS', | 87 | int coroutine_fn no_co_wrapper_bdrv_rdlock |
52 | + 'rbd': 'ImageInfoSpecificRbd' | 88 | bdrv_co_activate(BlockDriverState *bs, Error **errp); |
53 | } } | 89 | |
54 | 90 | +int no_coroutine_fn | |
55 | ## | 91 | +bdrv_inactivate(BlockDriverState *bs, Error **errp); |
56 | @@ -XXX,XX +XXX,XX @@ | ||
57 | { 'enum': 'RbdAuthMode', | ||
58 | 'data': [ 'cephx', 'none' ] } | ||
59 | |||
60 | +## | ||
61 | +# @RbdImageEncryptionFormat: | ||
62 | +# | ||
63 | +# Since: 6.1 | ||
64 | +## | ||
65 | +{ 'enum': 'RbdImageEncryptionFormat', | ||
66 | + 'data': [ 'luks', 'luks2' ] } | ||
67 | + | 92 | + |
68 | +## | 93 | void bdrv_activate_all(Error **errp); |
69 | +# @RbdEncryptionOptionsLUKSBase: | 94 | int bdrv_inactivate_all(void); |
70 | +# | 95 | |
71 | +# @key-secret: ID of a QCryptoSecret object providing a passphrase | 96 | diff --git a/block.c b/block.c |
72 | +# for unlocking the encryption | 97 | index XXXXXXX..XXXXXXX 100644 |
73 | +# | 98 | --- a/block.c |
74 | +# Since: 6.1 | 99 | +++ b/block.c |
75 | +## | 100 | @@ -XXX,XX +XXX,XX @@ bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) |
76 | +{ 'struct': 'RbdEncryptionOptionsLUKSBase', | 101 | return 0; |
77 | + 'data': { 'key-secret': 'str' } } | 102 | } |
103 | |||
104 | +int bdrv_inactivate(BlockDriverState *bs, Error **errp) | ||
105 | +{ | ||
106 | + int ret; | ||
78 | + | 107 | + |
79 | +## | 108 | + GLOBAL_STATE_CODE(); |
80 | +# @RbdEncryptionCreateOptionsLUKSBase: | 109 | + GRAPH_RDLOCK_GUARD_MAINLOOP(); |
81 | +# | ||
82 | +# @cipher-alg: The encryption algorithm | ||
83 | +# | ||
84 | +# Since: 6.1 | ||
85 | +## | ||
86 | +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase', | ||
87 | + 'base': 'RbdEncryptionOptionsLUKSBase', | ||
88 | + 'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } } | ||
89 | + | 110 | + |
90 | +## | 111 | + if (bdrv_has_bds_parent(bs, true)) { |
91 | +# @RbdEncryptionOptionsLUKS: | 112 | + error_setg(errp, "Node has active parent node"); |
92 | +# | 113 | + return -EPERM; |
93 | +# Since: 6.1 | ||
94 | +## | ||
95 | +{ 'struct': 'RbdEncryptionOptionsLUKS', | ||
96 | + 'base': 'RbdEncryptionOptionsLUKSBase', | ||
97 | + 'data': { } } | ||
98 | + | ||
99 | +## | ||
100 | +# @RbdEncryptionOptionsLUKS2: | ||
101 | +# | ||
102 | +# Since: 6.1 | ||
103 | +## | ||
104 | +{ 'struct': 'RbdEncryptionOptionsLUKS2', | ||
105 | + 'base': 'RbdEncryptionOptionsLUKSBase', | ||
106 | + 'data': { } } | ||
107 | + | ||
108 | +## | ||
109 | +# @RbdEncryptionCreateOptionsLUKS: | ||
110 | +# | ||
111 | +# Since: 6.1 | ||
112 | +## | ||
113 | +{ 'struct': 'RbdEncryptionCreateOptionsLUKS', | ||
114 | + 'base': 'RbdEncryptionCreateOptionsLUKSBase', | ||
115 | + 'data': { } } | ||
116 | + | ||
117 | +## | ||
118 | +# @RbdEncryptionCreateOptionsLUKS2: | ||
119 | +# | ||
120 | +# Since: 6.1 | ||
121 | +## | ||
122 | +{ 'struct': 'RbdEncryptionCreateOptionsLUKS2', | ||
123 | + 'base': 'RbdEncryptionCreateOptionsLUKSBase', | ||
124 | + 'data': { } } | ||
125 | + | ||
126 | +## | ||
127 | +# @RbdEncryptionOptions: | ||
128 | +# | ||
129 | +# Since: 6.1 | ||
130 | +## | ||
131 | +{ 'union': 'RbdEncryptionOptions', | ||
132 | + 'base': { 'format': 'RbdImageEncryptionFormat' }, | ||
133 | + 'discriminator': 'format', | ||
134 | + 'data': { 'luks': 'RbdEncryptionOptionsLUKS', | ||
135 | + 'luks2': 'RbdEncryptionOptionsLUKS2' } } | ||
136 | + | ||
137 | +## | ||
138 | +# @RbdEncryptionCreateOptions: | ||
139 | +# | ||
140 | +# Since: 6.1 | ||
141 | +## | ||
142 | +{ 'union': 'RbdEncryptionCreateOptions', | ||
143 | + 'base': { 'format': 'RbdImageEncryptionFormat' }, | ||
144 | + 'discriminator': 'format', | ||
145 | + 'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS', | ||
146 | + 'luks2': 'RbdEncryptionCreateOptionsLUKS2' } } | ||
147 | + | ||
148 | ## | ||
149 | # @BlockdevOptionsRbd: | ||
150 | # | ||
151 | @@ -XXX,XX +XXX,XX @@ | ||
152 | # | ||
153 | # @snapshot: Ceph snapshot name. | ||
154 | # | ||
155 | +# @encrypt: Image encryption options. (Since 6.1) | ||
156 | +# | ||
157 | # @user: Ceph id name. | ||
158 | # | ||
159 | # @auth-client-required: Acceptable authentication modes. | ||
160 | @@ -XXX,XX +XXX,XX @@ | ||
161 | 'image': 'str', | ||
162 | '*conf': 'str', | ||
163 | '*snapshot': 'str', | ||
164 | + '*encrypt': 'RbdEncryptionOptions', | ||
165 | '*user': 'str', | ||
166 | '*auth-client-required': ['RbdAuthMode'], | ||
167 | '*key-secret': 'str', | ||
168 | @@ -XXX,XX +XXX,XX @@ | ||
169 | # point to a snapshot. | ||
170 | # @size: Size of the virtual disk in bytes | ||
171 | # @cluster-size: RBD object size | ||
172 | +# @encrypt: Image encryption options. (Since 6.1) | ||
173 | # | ||
174 | # Since: 2.12 | ||
175 | ## | ||
176 | { 'struct': 'BlockdevCreateOptionsRbd', | ||
177 | 'data': { 'location': 'BlockdevOptionsRbd', | ||
178 | 'size': 'size', | ||
179 | - '*cluster-size' : 'size' } } | ||
180 | + '*cluster-size' : 'size', | ||
181 | + '*encrypt' : 'RbdEncryptionCreateOptions' } } | ||
182 | |||
183 | ## | ||
184 | # @BlockdevVmdkSubformat: | ||
185 | diff --git a/block/rbd.c b/block/rbd.c | ||
186 | index XXXXXXX..XXXXXXX 100644 | ||
187 | --- a/block/rbd.c | ||
188 | +++ b/block/rbd.c | ||
189 | @@ -XXX,XX +XXX,XX @@ | ||
190 | #define LIBRBD_USE_IOVEC 0 | ||
191 | #endif | ||
192 | |||
193 | +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8 | ||
194 | + | ||
195 | +static const char rbd_luks_header_verification[ | ||
196 | + RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = { | ||
197 | + 'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1 | ||
198 | +}; | ||
199 | + | ||
200 | +static const char rbd_luks2_header_verification[ | ||
201 | + RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = { | ||
202 | + 'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2 | ||
203 | +}; | ||
204 | + | ||
205 | typedef enum { | ||
206 | RBD_AIO_READ, | ||
207 | RBD_AIO_WRITE, | ||
208 | @@ -XXX,XX +XXX,XX @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs) | ||
209 | } | ||
210 | } | ||
211 | |||
212 | +#ifdef LIBRBD_SUPPORTS_ENCRYPTION | ||
213 | +static int qemu_rbd_convert_luks_options( | ||
214 | + RbdEncryptionOptionsLUKSBase *luks_opts, | ||
215 | + char **passphrase, | ||
216 | + size_t *passphrase_len, | ||
217 | + Error **errp) | ||
218 | +{ | ||
219 | + return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase, | ||
220 | + passphrase_len, errp); | ||
221 | +} | ||
222 | + | ||
223 | +static int qemu_rbd_convert_luks_create_options( | ||
224 | + RbdEncryptionCreateOptionsLUKSBase *luks_opts, | ||
225 | + rbd_encryption_algorithm_t *alg, | ||
226 | + char **passphrase, | ||
227 | + size_t *passphrase_len, | ||
228 | + Error **errp) | ||
229 | +{ | ||
230 | + int r = 0; | ||
231 | + | ||
232 | + r = qemu_rbd_convert_luks_options( | ||
233 | + qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts), | ||
234 | + passphrase, passphrase_len, errp); | ||
235 | + if (r < 0) { | ||
236 | + return r; | ||
237 | + } | 114 | + } |
238 | + | 115 | + |
239 | + if (luks_opts->has_cipher_alg) { | 116 | + ret = bdrv_inactivate_recurse(bs, true); |
240 | + switch (luks_opts->cipher_alg) { | 117 | + if (ret < 0) { |
241 | + case QCRYPTO_CIPHER_ALG_AES_128: { | 118 | + error_setg_errno(errp, -ret, "Failed to inactivate node"); |
242 | + *alg = RBD_ENCRYPTION_ALGORITHM_AES128; | 119 | + return ret; |
243 | + break; | ||
244 | + } | ||
245 | + case QCRYPTO_CIPHER_ALG_AES_256: { | ||
246 | + *alg = RBD_ENCRYPTION_ALGORITHM_AES256; | ||
247 | + break; | ||
248 | + } | ||
249 | + default: { | ||
250 | + r = -ENOTSUP; | ||
251 | + error_setg_errno(errp, -r, "unknown encryption algorithm: %u", | ||
252 | + luks_opts->cipher_alg); | ||
253 | + return r; | ||
254 | + } | ||
255 | + } | ||
256 | + } else { | ||
257 | + /* default alg */ | ||
258 | + *alg = RBD_ENCRYPTION_ALGORITHM_AES256; | ||
259 | + } | 120 | + } |
260 | + | 121 | + |
261 | + return 0; | 122 | + return 0; |
262 | +} | 123 | +} |
263 | + | 124 | + |
264 | +static int qemu_rbd_encryption_format(rbd_image_t image, | 125 | int bdrv_inactivate_all(void) |
265 | + RbdEncryptionCreateOptions *encrypt, | 126 | { |
266 | + Error **errp) | 127 | BlockDriverState *bs = NULL; |
128 | diff --git a/blockdev.c b/blockdev.c | ||
129 | index XXXXXXX..XXXXXXX 100644 | ||
130 | --- a/blockdev.c | ||
131 | +++ b/blockdev.c | ||
132 | @@ -XXX,XX +XXX,XX @@ void qmp_blockdev_del(const char *node_name, Error **errp) | ||
133 | bdrv_unref(bs); | ||
134 | } | ||
135 | |||
136 | +void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp) | ||
267 | +{ | 137 | +{ |
268 | + int r = 0; | 138 | + int ret; |
269 | + g_autofree char *passphrase = NULL; | ||
270 | + size_t passphrase_len; | ||
271 | + rbd_encryption_format_t format; | ||
272 | + rbd_encryption_options_t opts; | ||
273 | + rbd_encryption_luks1_format_options_t luks_opts; | ||
274 | + rbd_encryption_luks2_format_options_t luks2_opts; | ||
275 | + size_t opts_size; | ||
276 | + uint64_t raw_size, effective_size; | ||
277 | + | 139 | + |
278 | + r = rbd_get_size(image, &raw_size); | 140 | + GLOBAL_STATE_CODE(); |
279 | + if (r < 0) { | 141 | + GRAPH_RDLOCK_GUARD_MAINLOOP(); |
280 | + error_setg_errno(errp, -r, "cannot get raw image size"); | ||
281 | + return r; | ||
282 | + } | ||
283 | + | 142 | + |
284 | + switch (encrypt->format) { | 143 | + if (!node_name) { |
285 | + case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: { | 144 | + if (active) { |
286 | + memset(&luks_opts, 0, sizeof(luks_opts)); | 145 | + bdrv_activate_all(errp); |
287 | + format = RBD_ENCRYPTION_FORMAT_LUKS1; | 146 | + } else { |
288 | + opts = &luks_opts; | 147 | + ret = bdrv_inactivate_all(); |
289 | + opts_size = sizeof(luks_opts); | 148 | + if (ret < 0) { |
290 | + r = qemu_rbd_convert_luks_create_options( | 149 | + error_setg_errno(errp, -ret, "Failed to inactivate all nodes"); |
291 | + qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks), | ||
292 | + &luks_opts.alg, &passphrase, &passphrase_len, errp); | ||
293 | + if (r < 0) { | ||
294 | + return r; | ||
295 | + } | 150 | + } |
296 | + luks_opts.passphrase = passphrase; | ||
297 | + luks_opts.passphrase_size = passphrase_len; | ||
298 | + break; | ||
299 | + } | 151 | + } |
300 | + case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: { | 152 | + } else { |
301 | + memset(&luks2_opts, 0, sizeof(luks2_opts)); | 153 | + BlockDriverState *bs = bdrv_find_node(node_name); |
302 | + format = RBD_ENCRYPTION_FORMAT_LUKS2; | 154 | + if (!bs) { |
303 | + opts = &luks2_opts; | 155 | + error_setg(errp, "Failed to find node with node-name='%s'", |
304 | + opts_size = sizeof(luks2_opts); | 156 | + node_name); |
305 | + r = qemu_rbd_convert_luks_create_options( | 157 | + return; |
306 | + qapi_RbdEncryptionCreateOptionsLUKS2_base( | ||
307 | + &encrypt->u.luks2), | ||
308 | + &luks2_opts.alg, &passphrase, &passphrase_len, errp); | ||
309 | + if (r < 0) { | ||
310 | + return r; | ||
311 | + } | ||
312 | + luks2_opts.passphrase = passphrase; | ||
313 | + luks2_opts.passphrase_size = passphrase_len; | ||
314 | + break; | ||
315 | + } | 158 | + } |
316 | + default: { | 159 | + |
317 | + r = -ENOTSUP; | 160 | + if (active) { |
318 | + error_setg_errno( | 161 | + bdrv_activate(bs, errp); |
319 | + errp, -r, "unknown image encryption format: %u", | 162 | + } else { |
320 | + encrypt->format); | 163 | + bdrv_inactivate(bs, errp); |
321 | + return r; | ||
322 | + } | 164 | + } |
323 | + } | 165 | + } |
324 | + | ||
325 | + r = rbd_encryption_format(image, format, opts, opts_size); | ||
326 | + if (r < 0) { | ||
327 | + error_setg_errno(errp, -r, "encryption format fail"); | ||
328 | + return r; | ||
329 | + } | ||
330 | + | ||
331 | + r = rbd_get_size(image, &effective_size); | ||
332 | + if (r < 0) { | ||
333 | + error_setg_errno(errp, -r, "cannot get effective image size"); | ||
334 | + return r; | ||
335 | + } | ||
336 | + | ||
337 | + r = rbd_resize(image, raw_size + (raw_size - effective_size)); | ||
338 | + if (r < 0) { | ||
339 | + error_setg_errno(errp, -r, "cannot resize image after format"); | ||
340 | + return r; | ||
341 | + } | ||
342 | + | ||
343 | + return 0; | ||
344 | +} | 166 | +} |
345 | + | 167 | + |
346 | +static int qemu_rbd_encryption_load(rbd_image_t image, | 168 | static BdrvChild * GRAPH_RDLOCK |
347 | + RbdEncryptionOptions *encrypt, | 169 | bdrv_find_child(BlockDriverState *parent_bs, const char *child_name) |
348 | + Error **errp) | ||
349 | +{ | ||
350 | + int r = 0; | ||
351 | + g_autofree char *passphrase = NULL; | ||
352 | + size_t passphrase_len; | ||
353 | + rbd_encryption_luks1_format_options_t luks_opts; | ||
354 | + rbd_encryption_luks2_format_options_t luks2_opts; | ||
355 | + rbd_encryption_format_t format; | ||
356 | + rbd_encryption_options_t opts; | ||
357 | + size_t opts_size; | ||
358 | + | ||
359 | + switch (encrypt->format) { | ||
360 | + case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: { | ||
361 | + memset(&luks_opts, 0, sizeof(luks_opts)); | ||
362 | + format = RBD_ENCRYPTION_FORMAT_LUKS1; | ||
363 | + opts = &luks_opts; | ||
364 | + opts_size = sizeof(luks_opts); | ||
365 | + r = qemu_rbd_convert_luks_options( | ||
366 | + qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks), | ||
367 | + &passphrase, &passphrase_len, errp); | ||
368 | + if (r < 0) { | ||
369 | + return r; | ||
370 | + } | ||
371 | + luks_opts.passphrase = passphrase; | ||
372 | + luks_opts.passphrase_size = passphrase_len; | ||
373 | + break; | ||
374 | + } | ||
375 | + case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: { | ||
376 | + memset(&luks2_opts, 0, sizeof(luks2_opts)); | ||
377 | + format = RBD_ENCRYPTION_FORMAT_LUKS2; | ||
378 | + opts = &luks2_opts; | ||
379 | + opts_size = sizeof(luks2_opts); | ||
380 | + r = qemu_rbd_convert_luks_options( | ||
381 | + qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2), | ||
382 | + &passphrase, &passphrase_len, errp); | ||
383 | + if (r < 0) { | ||
384 | + return r; | ||
385 | + } | ||
386 | + luks2_opts.passphrase = passphrase; | ||
387 | + luks2_opts.passphrase_size = passphrase_len; | ||
388 | + break; | ||
389 | + } | ||
390 | + default: { | ||
391 | + r = -ENOTSUP; | ||
392 | + error_setg_errno( | ||
393 | + errp, -r, "unknown image encryption format: %u", | ||
394 | + encrypt->format); | ||
395 | + return r; | ||
396 | + } | ||
397 | + } | ||
398 | + | ||
399 | + r = rbd_encryption_load(image, format, opts, opts_size); | ||
400 | + if (r < 0) { | ||
401 | + error_setg_errno(errp, -r, "encryption load fail"); | ||
402 | + return r; | ||
403 | + } | ||
404 | + | ||
405 | + return 0; | ||
406 | +} | ||
407 | +#endif | ||
408 | + | ||
409 | /* FIXME Deprecate and remove keypairs or make it available in QMP. */ | ||
410 | static int qemu_rbd_do_create(BlockdevCreateOptions *options, | ||
411 | const char *keypairs, const char *password_secret, | ||
412 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options, | ||
413 | return -EINVAL; | ||
414 | } | ||
415 | |||
416 | +#ifndef LIBRBD_SUPPORTS_ENCRYPTION | ||
417 | + if (opts->has_encrypt) { | ||
418 | + error_setg(errp, "RBD library does not support image encryption"); | ||
419 | + return -ENOTSUP; | ||
420 | + } | ||
421 | +#endif | ||
422 | + | ||
423 | if (opts->has_cluster_size) { | ||
424 | int64_t objsize = opts->cluster_size; | ||
425 | if ((objsize - 1) & objsize) { /* not a power of 2? */ | ||
426 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options, | ||
427 | goto out; | ||
428 | } | ||
429 | |||
430 | +#ifdef LIBRBD_SUPPORTS_ENCRYPTION | ||
431 | + if (opts->has_encrypt) { | ||
432 | + rbd_image_t image; | ||
433 | + | ||
434 | + ret = rbd_open(io_ctx, opts->location->image, &image, NULL); | ||
435 | + if (ret < 0) { | ||
436 | + error_setg_errno(errp, -ret, | ||
437 | + "error opening image '%s' for encryption format", | ||
438 | + opts->location->image); | ||
439 | + goto out; | ||
440 | + } | ||
441 | + | ||
442 | + ret = qemu_rbd_encryption_format(image, opts->encrypt, errp); | ||
443 | + rbd_close(image); | ||
444 | + if (ret < 0) { | ||
445 | + /* encryption format fail, try removing the image */ | ||
446 | + rbd_remove(io_ctx, opts->location->image); | ||
447 | + goto out; | ||
448 | + } | ||
449 | + } | ||
450 | +#endif | ||
451 | + | ||
452 | ret = 0; | ||
453 | out: | ||
454 | rados_ioctx_destroy(io_ctx); | ||
455 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp) | ||
456 | return qemu_rbd_do_create(options, NULL, NULL, errp); | ||
457 | } | ||
458 | |||
459 | +static int qemu_rbd_extract_encryption_create_options( | ||
460 | + QemuOpts *opts, | ||
461 | + RbdEncryptionCreateOptions **spec, | ||
462 | + Error **errp) | ||
463 | +{ | ||
464 | + QDict *opts_qdict; | ||
465 | + QDict *encrypt_qdict; | ||
466 | + Visitor *v; | ||
467 | + int ret = 0; | ||
468 | + | ||
469 | + opts_qdict = qemu_opts_to_qdict(opts, NULL); | ||
470 | + qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt."); | ||
471 | + qobject_unref(opts_qdict); | ||
472 | + if (!qdict_size(encrypt_qdict)) { | ||
473 | + *spec = NULL; | ||
474 | + goto exit; | ||
475 | + } | ||
476 | + | ||
477 | + /* Convert options into a QAPI object */ | ||
478 | + v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp); | ||
479 | + if (!v) { | ||
480 | + ret = -EINVAL; | ||
481 | + goto exit; | ||
482 | + } | ||
483 | + | ||
484 | + visit_type_RbdEncryptionCreateOptions(v, NULL, spec, errp); | ||
485 | + visit_free(v); | ||
486 | + if (!*spec) { | ||
487 | + ret = -EINVAL; | ||
488 | + goto exit; | ||
489 | + } | ||
490 | + | ||
491 | +exit: | ||
492 | + qobject_unref(encrypt_qdict); | ||
493 | + return ret; | ||
494 | +} | ||
495 | + | ||
496 | static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv, | ||
497 | const char *filename, | ||
498 | QemuOpts *opts, | ||
499 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv, | ||
500 | BlockdevCreateOptions *create_options; | ||
501 | BlockdevCreateOptionsRbd *rbd_opts; | ||
502 | BlockdevOptionsRbd *loc; | ||
503 | + RbdEncryptionCreateOptions *encrypt = NULL; | ||
504 | Error *local_err = NULL; | ||
505 | const char *keypairs, *password_secret; | ||
506 | QDict *options = NULL; | ||
507 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv, | ||
508 | goto exit; | ||
509 | } | ||
510 | |||
511 | + ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp); | ||
512 | + if (ret < 0) { | ||
513 | + goto exit; | ||
514 | + } | ||
515 | + rbd_opts->encrypt = encrypt; | ||
516 | + rbd_opts->has_encrypt = !!encrypt; | ||
517 | + | ||
518 | /* | ||
519 | * Caution: while qdict_get_try_str() is fine, getting non-string | ||
520 | * types would require more care. When @options come from -blockdev | ||
521 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
522 | goto failed_open; | ||
523 | } | ||
524 | |||
525 | + if (opts->has_encrypt) { | ||
526 | +#ifdef LIBRBD_SUPPORTS_ENCRYPTION | ||
527 | + r = qemu_rbd_encryption_load(s->image, opts->encrypt, errp); | ||
528 | + if (r < 0) { | ||
529 | + goto failed_post_open; | ||
530 | + } | ||
531 | +#else | ||
532 | + r = -ENOTSUP; | ||
533 | + error_setg(errp, "RBD library does not support image encryption"); | ||
534 | + goto failed_post_open; | ||
535 | +#endif | ||
536 | + } | ||
537 | + | ||
538 | r = rbd_get_size(s->image, &s->image_size); | ||
539 | if (r < 0) { | ||
540 | error_setg_errno(errp, -r, "error getting image size from %s", | ||
541 | s->image_name); | ||
542 | - rbd_close(s->image); | ||
543 | - goto failed_open; | ||
544 | + goto failed_post_open; | ||
545 | } | ||
546 | |||
547 | /* If we are using an rbd snapshot, we must be r/o, otherwise | ||
548 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
549 | if (s->snap != NULL) { | ||
550 | r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp); | ||
551 | if (r < 0) { | ||
552 | - rbd_close(s->image); | ||
553 | - goto failed_open; | ||
554 | + goto failed_post_open; | ||
555 | } | ||
556 | } | ||
557 | |||
558 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
559 | r = 0; | ||
560 | goto out; | ||
561 | |||
562 | +failed_post_open: | ||
563 | + rbd_close(s->image); | ||
564 | failed_open: | ||
565 | rados_ioctx_destroy(s->io_ctx); | ||
566 | g_free(s->snap); | ||
567 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) | ||
568 | return 0; | ||
569 | } | ||
570 | |||
571 | +static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs, | ||
572 | + Error **errp) | ||
573 | +{ | ||
574 | + BDRVRBDState *s = bs->opaque; | ||
575 | + ImageInfoSpecific *spec_info; | ||
576 | + char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0}; | ||
577 | + int r; | ||
578 | + | ||
579 | + if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) { | ||
580 | + r = rbd_read(s->image, 0, | ||
581 | + RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf); | ||
582 | + if (r < 0) { | ||
583 | + error_setg_errno(errp, -r, "cannot read image start for probe"); | ||
584 | + return NULL; | ||
585 | + } | ||
586 | + } | ||
587 | + | ||
588 | + spec_info = g_new(ImageInfoSpecific, 1); | ||
589 | + *spec_info = (ImageInfoSpecific){ | ||
590 | + .type = IMAGE_INFO_SPECIFIC_KIND_RBD, | ||
591 | + .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1), | ||
592 | + }; | ||
593 | + | ||
594 | + if (memcmp(buf, rbd_luks_header_verification, | ||
595 | + RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) { | ||
596 | + spec_info->u.rbd.data->encryption_format = | ||
597 | + RBD_IMAGE_ENCRYPTION_FORMAT_LUKS; | ||
598 | + spec_info->u.rbd.data->has_encryption_format = true; | ||
599 | + } else if (memcmp(buf, rbd_luks2_header_verification, | ||
600 | + RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) { | ||
601 | + spec_info->u.rbd.data->encryption_format = | ||
602 | + RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2; | ||
603 | + spec_info->u.rbd.data->has_encryption_format = true; | ||
604 | + } else { | ||
605 | + spec_info->u.rbd.data->has_encryption_format = false; | ||
606 | + } | ||
607 | + | ||
608 | + return spec_info; | ||
609 | +} | ||
610 | + | ||
611 | static int64_t qemu_rbd_getlength(BlockDriverState *bs) | ||
612 | { | 170 | { |
613 | BDRVRBDState *s = bs->opaque; | ||
614 | @@ -XXX,XX +XXX,XX @@ static QemuOptsList qemu_rbd_create_opts = { | ||
615 | .type = QEMU_OPT_STRING, | ||
616 | .help = "ID of secret providing the password", | ||
617 | }, | ||
618 | + { | ||
619 | + .name = "encrypt.format", | ||
620 | + .type = QEMU_OPT_STRING, | ||
621 | + .help = "Encrypt the image, format choices: 'luks', 'luks2'", | ||
622 | + }, | ||
623 | + { | ||
624 | + .name = "encrypt.cipher-alg", | ||
625 | + .type = QEMU_OPT_STRING, | ||
626 | + .help = "Name of encryption cipher algorithm" | ||
627 | + " (allowed values: aes-128, aes-256)", | ||
628 | + }, | ||
629 | + { | ||
630 | + .name = "encrypt.key-secret", | ||
631 | + .type = QEMU_OPT_STRING, | ||
632 | + .help = "ID of secret providing LUKS passphrase", | ||
633 | + }, | ||
634 | { /* end of list */ } | ||
635 | } | ||
636 | }; | ||
637 | @@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_rbd = { | ||
638 | .bdrv_co_create_opts = qemu_rbd_co_create_opts, | ||
639 | .bdrv_has_zero_init = bdrv_has_zero_init_1, | ||
640 | .bdrv_get_info = qemu_rbd_getinfo, | ||
641 | + .bdrv_get_specific_info = qemu_rbd_get_specific_info, | ||
642 | .create_opts = &qemu_rbd_create_opts, | ||
643 | .bdrv_getlength = qemu_rbd_getlength, | ||
644 | .bdrv_co_truncate = qemu_rbd_co_truncate, | ||
645 | -- | 171 | -- |
646 | 2.31.1 | 172 | 2.48.1 |
647 | |||
648 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Peter Lieven <pl@kamp.de> | ||
2 | 1 | ||
3 | Signed-off-by: Peter Lieven <pl@kamp.de> | ||
4 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | ||
5 | Message-Id: <20210702172356.11574-3-idryomov@gmail.com> | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | --- | ||
8 | block/rbd.c | 18 +++++++----------- | ||
9 | 1 file changed, 7 insertions(+), 11 deletions(-) | ||
10 | |||
11 | diff --git a/block/rbd.c b/block/rbd.c | ||
12 | index XXXXXXX..XXXXXXX 100644 | ||
13 | --- a/block/rbd.c | ||
14 | +++ b/block/rbd.c | ||
15 | @@ -XXX,XX +XXX,XX @@ typedef struct BDRVRBDState { | ||
16 | char *snap; | ||
17 | char *namespace; | ||
18 | uint64_t image_size; | ||
19 | + uint64_t object_size; | ||
20 | } BDRVRBDState; | ||
21 | |||
22 | static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx, | ||
23 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
24 | const QDictEntry *e; | ||
25 | Error *local_err = NULL; | ||
26 | char *keypairs, *secretid; | ||
27 | + rbd_image_info_t info; | ||
28 | int r; | ||
29 | |||
30 | keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs")); | ||
31 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
32 | #endif | ||
33 | } | ||
34 | |||
35 | - r = rbd_get_size(s->image, &s->image_size); | ||
36 | + r = rbd_stat(s->image, &info, sizeof(info)); | ||
37 | if (r < 0) { | ||
38 | - error_setg_errno(errp, -r, "error getting image size from %s", | ||
39 | + error_setg_errno(errp, -r, "error getting image info from %s", | ||
40 | s->image_name); | ||
41 | goto failed_post_open; | ||
42 | } | ||
43 | + s->image_size = info.size; | ||
44 | + s->object_size = info.obj_size; | ||
45 | |||
46 | /* If we are using an rbd snapshot, we must be r/o, otherwise | ||
47 | * leave as-is */ | ||
48 | @@ -XXX,XX +XXX,XX @@ static BlockAIOCB *qemu_rbd_aio_flush(BlockDriverState *bs, | ||
49 | static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) | ||
50 | { | ||
51 | BDRVRBDState *s = bs->opaque; | ||
52 | - rbd_image_info_t info; | ||
53 | - int r; | ||
54 | - | ||
55 | - r = rbd_stat(s->image, &info, sizeof(info)); | ||
56 | - if (r < 0) { | ||
57 | - return r; | ||
58 | - } | ||
59 | - | ||
60 | - bdi->cluster_size = info.obj_size; | ||
61 | + bdi->cluster_size = s->object_size; | ||
62 | return 0; | ||
63 | } | ||
64 | |||
65 | -- | ||
66 | 2.31.1 | ||
67 | |||
68 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Peter Lieven <pl@kamp.de> | ||
2 | 1 | ||
3 | While at it just call rbd_get_size and avoid rbd_image_info_t. | ||
4 | |||
5 | Signed-off-by: Peter Lieven <pl@kamp.de> | ||
6 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | ||
7 | Message-Id: <20210702172356.11574-4-idryomov@gmail.com> | ||
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
9 | --- | ||
10 | block/rbd.c | 5 ++--- | ||
11 | 1 file changed, 2 insertions(+), 3 deletions(-) | ||
12 | |||
13 | diff --git a/block/rbd.c b/block/rbd.c | ||
14 | index XXXXXXX..XXXXXXX 100644 | ||
15 | --- a/block/rbd.c | ||
16 | +++ b/block/rbd.c | ||
17 | @@ -XXX,XX +XXX,XX @@ static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs, | ||
18 | static int64_t qemu_rbd_getlength(BlockDriverState *bs) | ||
19 | { | ||
20 | BDRVRBDState *s = bs->opaque; | ||
21 | - rbd_image_info_t info; | ||
22 | int r; | ||
23 | |||
24 | - r = rbd_stat(s->image, &info, sizeof(info)); | ||
25 | + r = rbd_get_size(s->image, &s->image_size); | ||
26 | if (r < 0) { | ||
27 | return r; | ||
28 | } | ||
29 | |||
30 | - return info.size; | ||
31 | + return s->image_size; | ||
32 | } | ||
33 | |||
34 | static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs, | ||
35 | -- | ||
36 | 2.31.1 | ||
37 | |||
38 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Peter Lieven <pl@kamp.de> | ||
2 | 1 | ||
3 | This patch wittingly sets BDRV_REQ_NO_FALLBACK and silently ignores | ||
4 | BDRV_REQ_MAY_UNMAP for older librbd versions. | ||
5 | |||
6 | The rationale for this is as follows (citing Ilya Dryomov current RBD | ||
7 | maintainer): | ||
8 | |||
9 | ---8<--- | ||
10 | a) remove the BDRV_REQ_MAY_UNMAP check in qemu_rbd_co_pwrite_zeroes() | ||
11 | and as a consequence always unmap if librbd is too old | ||
12 | |||
13 | It's not clear what qemu's expectation is but in general Write | ||
14 | Zeroes is allowed to unmap. The only guarantee is that subsequent | ||
15 | reads return zeroes, everything else is a hint. This is how it is | ||
16 | specified in the kernel and in the NVMe spec. | ||
17 | |||
18 | In particular, block/nvme.c implements it as follows: | ||
19 | |||
20 | if (flags & BDRV_REQ_MAY_UNMAP) { | ||
21 | cdw12 |= (1 << 25); | ||
22 | } | ||
23 | |||
24 | This sets the Deallocate bit. But if it's not set, the device may | ||
25 | still deallocate: | ||
26 | |||
27 | """ | ||
28 | If the Deallocate bit (CDW12.DEAC) is set to '1' in a Write Zeroes | ||
29 | command, and the namespace supports clearing all bytes to 0h in the | ||
30 | values read (e.g., bits 2:0 in the DLFEAT field are set to 001b) | ||
31 | from a deallocated logical block and its metadata (excluding | ||
32 | protection information), then for each specified logical block, the | ||
33 | controller: | ||
34 | - should deallocate that logical block; | ||
35 | |||
36 | ... | ||
37 | |||
38 | If the Deallocate bit is cleared to '0' in a Write Zeroes command, | ||
39 | and the namespace supports clearing all bytes to 0h in the values | ||
40 | read (e.g., bits 2:0 in the DLFEAT field are set to 001b) from | ||
41 | a deallocated logical block and its metadata (excluding protection | ||
42 | information), then, for each specified logical block, the | ||
43 | controller: | ||
44 | - may deallocate that logical block; | ||
45 | """ | ||
46 | |||
47 | https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-2021.06.02-Ratified-1.pdf | ||
48 | |||
49 | b) set BDRV_REQ_NO_FALLBACK in supported_zero_flags | ||
50 | |||
51 | Again, it's not clear what qemu expects here, but without it we end | ||
52 | up in a ridiculous situation where specifying the "don't allow slow | ||
53 | fallback" switch immediately fails all efficient zeroing requests on | ||
54 | a device where Write Zeroes is always efficient: | ||
55 | |||
56 | $ qemu-io -c 'help write' | grep -- '-[zun]' | ||
57 | -n, -- with -z, don't allow slow fallback | ||
58 | -u, -- with -z, allow unmapping | ||
59 | -z, -- write zeroes using blk_co_pwrite_zeroes | ||
60 | |||
61 | $ qemu-io -f rbd -c 'write -z -u -n 0 1M' rbd:foo/bar | ||
62 | write failed: Operation not supported | ||
63 | --->8--- | ||
64 | |||
65 | Signed-off-by: Peter Lieven <pl@kamp.de> | ||
66 | Reviewed-by: Ilya Dryomov <idryomov@gmail.com> | ||
67 | Message-Id: <20210702172356.11574-6-idryomov@gmail.com> | ||
68 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
69 | --- | ||
70 | block/rbd.c | 32 +++++++++++++++++++++++++++++++- | ||
71 | 1 file changed, 31 insertions(+), 1 deletion(-) | ||
72 | |||
73 | diff --git a/block/rbd.c b/block/rbd.c | ||
74 | index XXXXXXX..XXXXXXX 100644 | ||
75 | --- a/block/rbd.c | ||
76 | +++ b/block/rbd.c | ||
77 | @@ -XXX,XX +XXX,XX @@ typedef enum { | ||
78 | RBD_AIO_READ, | ||
79 | RBD_AIO_WRITE, | ||
80 | RBD_AIO_DISCARD, | ||
81 | - RBD_AIO_FLUSH | ||
82 | + RBD_AIO_FLUSH, | ||
83 | + RBD_AIO_WRITE_ZEROES | ||
84 | } RBDAIOCmd; | ||
85 | |||
86 | typedef struct BDRVRBDState { | ||
87 | @@ -XXX,XX +XXX,XX @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags, | ||
88 | } | ||
89 | } | ||
90 | |||
91 | +#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES | ||
92 | + bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK; | ||
93 | +#endif | ||
94 | + | ||
95 | /* When extending regular files, we get zeros from the OS */ | ||
96 | bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE; | ||
97 | |||
98 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn qemu_rbd_start_co(BlockDriverState *bs, | ||
99 | case RBD_AIO_FLUSH: | ||
100 | r = rbd_aio_flush(s->image, c); | ||
101 | break; | ||
102 | +#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES | ||
103 | + case RBD_AIO_WRITE_ZEROES: { | ||
104 | + int zero_flags = 0; | ||
105 | +#ifdef RBD_WRITE_ZEROES_FLAG_THICK_PROVISION | ||
106 | + if (!(flags & BDRV_REQ_MAY_UNMAP)) { | ||
107 | + zero_flags = RBD_WRITE_ZEROES_FLAG_THICK_PROVISION; | ||
108 | + } | ||
109 | +#endif | ||
110 | + r = rbd_aio_write_zeroes(s->image, offset, bytes, c, zero_flags, 0); | ||
111 | + break; | ||
112 | + } | ||
113 | +#endif | ||
114 | default: | ||
115 | r = -EINVAL; | ||
116 | } | ||
117 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn qemu_rbd_co_pdiscard(BlockDriverState *bs, | ||
118 | return qemu_rbd_start_co(bs, offset, count, NULL, 0, RBD_AIO_DISCARD); | ||
119 | } | ||
120 | |||
121 | +#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES | ||
122 | +static int | ||
123 | +coroutine_fn qemu_rbd_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset, | ||
124 | + int count, BdrvRequestFlags flags) | ||
125 | +{ | ||
126 | + return qemu_rbd_start_co(bs, offset, count, NULL, flags, | ||
127 | + RBD_AIO_WRITE_ZEROES); | ||
128 | +} | ||
129 | +#endif | ||
130 | + | ||
131 | static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi) | ||
132 | { | ||
133 | BDRVRBDState *s = bs->opaque; | ||
134 | @@ -XXX,XX +XXX,XX @@ static BlockDriver bdrv_rbd = { | ||
135 | .bdrv_co_pwritev = qemu_rbd_co_pwritev, | ||
136 | .bdrv_co_flush_to_disk = qemu_rbd_co_flush, | ||
137 | .bdrv_co_pdiscard = qemu_rbd_co_pdiscard, | ||
138 | +#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES | ||
139 | + .bdrv_co_pwrite_zeroes = qemu_rbd_co_pwrite_zeroes, | ||
140 | +#endif | ||
141 | |||
142 | .bdrv_snapshot_create = qemu_rbd_snap_create, | ||
143 | .bdrv_snapshot_delete = qemu_rbd_snap_remove, | ||
144 | -- | ||
145 | 2.31.1 | ||
146 | |||
147 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | Device models have a relatively complex way to set up their block |
---|---|---|---|
2 | backends, in which blk_attach_dev() sets blk->disable_perm = true. | ||
3 | We want to support inactive images in exports, too, so that | ||
4 | qemu-storage-daemon can be used with migration. Because they don't use | ||
5 | blk_attach_dev(), they need another way to set this flag. The most | ||
6 | convenient is to do this automatically when an inactive node is attached | ||
7 | to a BlockBackend that can be inactivated. | ||
2 | 8 | ||
3 | Test that +w on read-only FUSE exports returns an EROFS error. u+x on | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | the other hand should work. (There is no special reason to choose u+x | 10 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | here, it simply is like +w another flag that is not set by default.) | 11 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | 12 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | |
7 | Signed-off-by: Max Reitz <mreitz@redhat.com> | 13 | Message-ID: <20250204211407.381505-10-kwolf@redhat.com> |
8 | Message-Id: <20210625142317.271673-6-mreitz@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | --- | 15 | --- |
11 | tests/qemu-iotests/308 | 11 +++++++++++ | 16 | block/block-backend.c | 14 ++++++++++++-- |
12 | tests/qemu-iotests/308.out | 4 ++++ | 17 | 1 file changed, 12 insertions(+), 2 deletions(-) |
13 | 2 files changed, 15 insertions(+) | ||
14 | 18 | ||
15 | diff --git a/tests/qemu-iotests/308 b/tests/qemu-iotests/308 | 19 | diff --git a/block/block-backend.c b/block/block-backend.c |
16 | index XXXXXXX..XXXXXXX 100755 | 20 | index XXXXXXX..XXXXXXX 100644 |
17 | --- a/tests/qemu-iotests/308 | 21 | --- a/block/block-backend.c |
18 | +++ b/tests/qemu-iotests/308 | 22 | +++ b/block/block-backend.c |
19 | @@ -XXX,XX +XXX,XX @@ fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP'" | 23 | @@ -XXX,XX +XXX,XX @@ void blk_remove_bs(BlockBackend *blk) |
20 | # Check that the export presents the same data as the original image | 24 | int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) |
21 | $QEMU_IMG compare -f raw -F $IMGFMT -U "$EXT_MP" "$TEST_IMG" | 25 | { |
22 | 26 | ThrottleGroupMember *tgm = &blk->public.throttle_group_member; | |
23 | +# Some quick chmod tests | 27 | + uint64_t perm, shared_perm; |
24 | +stat -c 'Permissions pre-chmod: %a' "$EXT_MP" | 28 | |
29 | GLOBAL_STATE_CODE(); | ||
30 | bdrv_ref(bs); | ||
31 | bdrv_graph_wrlock(); | ||
25 | + | 32 | + |
26 | +# Verify that we cannot set +w | 33 | + if ((bs->open_flags & BDRV_O_INACTIVE) && blk_can_inactivate(blk)) { |
27 | +chmod u+w "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt | 34 | + blk->disable_perm = true; |
28 | +stat -c 'Permissions post-+w: %a' "$EXT_MP" | 35 | + perm = 0; |
36 | + shared_perm = BLK_PERM_ALL; | ||
37 | + } else { | ||
38 | + perm = blk->perm; | ||
39 | + shared_perm = blk->shared_perm; | ||
40 | + } | ||
29 | + | 41 | + |
30 | +# But that we can set, say, +x (if we are so inclined) | 42 | blk->root = bdrv_root_attach_child(bs, "root", &child_root, |
31 | +chmod u+x "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt | 43 | BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, |
32 | +stat -c 'Permissions post-+x: %a' "$EXT_MP" | 44 | - blk->perm, blk->shared_perm, |
33 | + | 45 | - blk, errp); |
34 | echo | 46 | + perm, shared_perm, blk, errp); |
35 | echo '=== Mount over existing file ===' | 47 | bdrv_graph_wrunlock(); |
36 | 48 | if (blk->root == NULL) { | |
37 | diff --git a/tests/qemu-iotests/308.out b/tests/qemu-iotests/308.out | 49 | return -EPERM; |
38 | index XXXXXXX..XXXXXXX 100644 | ||
39 | --- a/tests/qemu-iotests/308.out | ||
40 | +++ b/tests/qemu-iotests/308.out | ||
41 | @@ -XXX,XX +XXX,XX @@ wrote 67108864/67108864 bytes at offset 0 | ||
42 | } } | ||
43 | {"return": {}} | ||
44 | Images are identical. | ||
45 | +Permissions pre-chmod: 400 | ||
46 | +chmod: changing permissions of 'TEST_DIR/t.IMGFMT.fuse': Read-only file system | ||
47 | +Permissions post-+w: 400 | ||
48 | +Permissions post-+x: 500 | ||
49 | |||
50 | === Mount over existing file === | ||
51 | {'execute': 'block-export-add', | ||
52 | -- | 50 | -- |
53 | 2.31.1 | 51 | 2.48.1 |
54 | |||
55 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | Currently, block exports can't handle inactive images correctly. |
---|---|---|---|
2 | Incoming write requests would run into assertion failures. Make sure | ||
3 | that we return an error when creating an export can't activate the | ||
4 | image. | ||
2 | 5 | ||
3 | In order to support changing other attributes than the file size in | 6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | fuse_setattr(), we have to give each its own independent branch. This | 7 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | also applies to the only attribute we do support right now. | 8 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | 9 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | |
7 | Signed-off-by: Max Reitz <mreitz@redhat.com> | 10 | Message-ID: <20250204211407.381505-11-kwolf@redhat.com> |
8 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
9 | Message-Id: <20210625142317.271673-4-mreitz@redhat.com> | ||
10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | --- | 12 | --- |
12 | block/export/fuse.c | 20 +++++++++++--------- | 13 | block/export/export.c | 6 +++++- |
13 | 1 file changed, 11 insertions(+), 9 deletions(-) | 14 | 1 file changed, 5 insertions(+), 1 deletion(-) |
14 | 15 | ||
15 | diff --git a/block/export/fuse.c b/block/export/fuse.c | 16 | diff --git a/block/export/export.c b/block/export/export.c |
16 | index XXXXXXX..XXXXXXX 100644 | 17 | index XXXXXXX..XXXXXXX 100644 |
17 | --- a/block/export/fuse.c | 18 | --- a/block/export/export.c |
18 | +++ b/block/export/fuse.c | 19 | +++ b/block/export/export.c |
19 | @@ -XXX,XX +XXX,XX @@ static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf, | 20 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) |
20 | FuseExport *exp = fuse_req_userdata(req); | 21 | * ctx was acquired in the caller. |
21 | int ret; | 22 | */ |
22 | 23 | bdrv_graph_rdlock_main_loop(); | |
23 | - if (!exp->writable) { | 24 | - bdrv_activate(bs, NULL); |
24 | - fuse_reply_err(req, EACCES); | 25 | + ret = bdrv_activate(bs, errp); |
25 | - return; | 26 | + if (ret < 0) { |
26 | - } | 27 | + bdrv_graph_rdunlock_main_loop(); |
27 | - | 28 | + goto fail; |
28 | if (to_set & ~FUSE_SET_ATTR_SIZE) { | 29 | + } |
29 | fuse_reply_err(req, ENOTSUP); | 30 | bdrv_graph_rdunlock_main_loop(); |
30 | return; | 31 | |
31 | } | 32 | perm = BLK_PERM_CONSISTENT_READ; |
32 | |||
33 | - ret = fuse_do_truncate(exp, statbuf->st_size, true, PREALLOC_MODE_OFF); | ||
34 | - if (ret < 0) { | ||
35 | - fuse_reply_err(req, -ret); | ||
36 | - return; | ||
37 | + if (to_set & FUSE_SET_ATTR_SIZE) { | ||
38 | + if (!exp->writable) { | ||
39 | + fuse_reply_err(req, EACCES); | ||
40 | + return; | ||
41 | + } | ||
42 | + | ||
43 | + ret = fuse_do_truncate(exp, statbuf->st_size, true, PREALLOC_MODE_OFF); | ||
44 | + if (ret < 0) { | ||
45 | + fuse_reply_err(req, -ret); | ||
46 | + return; | ||
47 | + } | ||
48 | } | ||
49 | |||
50 | fuse_getattr(req, inode, fi); | ||
51 | -- | 33 | -- |
52 | 2.31.1 | 34 | 2.48.1 |
53 | |||
54 | diff view generated by jsdifflib |
1 | From: Eric Blake <eblake@redhat.com> | 1 | So far the assumption has always been that if we try to inactivate a |
---|---|---|---|
2 | node, it is already idle. This doesn't hold true any more if we allow | ||
3 | inactivating exported nodes because we can't know when new external | ||
4 | requests come in. | ||
2 | 5 | ||
3 | Back in commit d9f059aa6c (qemu-img: Deprecate use of -b without -F), | 6 | Drain the node around setting BDRV_O_INACTIVE so that requests can't |
4 | we deprecated the ability to create a file with a backing image that | 7 | start operating on an active node and then in the middle it suddenly |
5 | requires qemu to perform format probing. Qemu can still probe older | 8 | becomes inactive. With this change, it's enough for exports to check |
6 | files for backwards compatibility, but it is time to finish off the | 9 | for new requests that they operate on an active node (or, like reads, |
7 | ability to create such images, due to the potential security risk they | 10 | are allowed even on an inactive node). |
8 | present. Update a couple of iotests affected by the change. | ||
9 | 11 | ||
10 | Signed-off-by: Eric Blake <eblake@redhat.com> | 12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | Message-Id: <20210503213600.569128-3-eblake@redhat.com> | 13 | Acked-by: Fabiano Rosas <farosas@suse.de> |
12 | Reviewed-by: Connor Kuehl <ckuehl@redhat.com> | 14 | Message-ID: <20250204211407.381505-12-kwolf@redhat.com> |
15 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
16 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 17 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
14 | --- | 18 | --- |
15 | docs/system/deprecated.rst | 20 ----------------- | 19 | block.c | 2 ++ |
16 | docs/system/removed-features.rst | 19 ++++++++++++++++ | 20 | 1 file changed, 2 insertions(+) |
17 | block.c | 37 ++++++++++---------------------- | ||
18 | qemu-img.c | 6 ++++-- | ||
19 | tests/qemu-iotests/040 | 4 ++-- | ||
20 | tests/qemu-iotests/041 | 6 ++++-- | ||
21 | tests/qemu-iotests/114 | 18 ++++++++-------- | ||
22 | tests/qemu-iotests/114.out | 11 ++++------ | ||
23 | tests/qemu-iotests/301 | 4 +--- | ||
24 | tests/qemu-iotests/301.out | 16 ++------------ | ||
25 | 10 files changed, 56 insertions(+), 85 deletions(-) | ||
26 | 21 | ||
27 | diff --git a/docs/system/deprecated.rst b/docs/system/deprecated.rst | ||
28 | index XXXXXXX..XXXXXXX 100644 | ||
29 | --- a/docs/system/deprecated.rst | ||
30 | +++ b/docs/system/deprecated.rst | ||
31 | @@ -XXX,XX +XXX,XX @@ this CPU is also deprecated. | ||
32 | Related binaries | ||
33 | ---------------- | ||
34 | |||
35 | -qemu-img backing file without format (since 5.1) | ||
36 | -'''''''''''''''''''''''''''''''''''''''''''''''' | ||
37 | - | ||
38 | -The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img | ||
39 | -convert`` to create or modify an image that depends on a backing file | ||
40 | -now recommends that an explicit backing format be provided. This is | ||
41 | -for safety: if QEMU probes a different format than what you thought, | ||
42 | -the data presented to the guest will be corrupt; similarly, presenting | ||
43 | -a raw image to a guest allows a potential security exploit if a future | ||
44 | -probe sees a non-raw image based on guest writes. | ||
45 | - | ||
46 | -To avoid the warning message, or even future refusal to create an | ||
47 | -unsafe image, you must pass ``-o backing_fmt=`` (or the shorthand | ||
48 | -``-F`` during create) to specify the intended backing format. You may | ||
49 | -use ``qemu-img rebase -u`` to retroactively add a backing format to an | ||
50 | -existing image. However, be aware that there are already potential | ||
51 | -security risks to blindly using ``qemu-img info`` to probe the format | ||
52 | -of an untrusted backing image, when deciding what format to add into | ||
53 | -an existing image. | ||
54 | - | ||
55 | Backwards compatibility | ||
56 | ----------------------- | ||
57 | |||
58 | diff --git a/docs/system/removed-features.rst b/docs/system/removed-features.rst | ||
59 | index XXXXXXX..XXXXXXX 100644 | ||
60 | --- a/docs/system/removed-features.rst | ||
61 | +++ b/docs/system/removed-features.rst | ||
62 | @@ -XXX,XX +XXX,XX @@ backing chain should be performed with ``qemu-img rebase -u`` either | ||
63 | before or after the remaining changes being performed by amend, as | ||
64 | appropriate. | ||
65 | |||
66 | +qemu-img backing file without format (removed in 6.1) | ||
67 | +''''''''''''''''''''''''''''''''''''''''''''''''''''' | ||
68 | + | ||
69 | +The use of ``qemu-img create``, ``qemu-img rebase``, or ``qemu-img | ||
70 | +convert`` to create or modify an image that depends on a backing file | ||
71 | +now requires that an explicit backing format be provided. This is | ||
72 | +for safety: if QEMU probes a different format than what you thought, | ||
73 | +the data presented to the guest will be corrupt; similarly, presenting | ||
74 | +a raw image to a guest allows a potential security exploit if a future | ||
75 | +probe sees a non-raw image based on guest writes. | ||
76 | + | ||
77 | +To avoid creating unsafe backing chains, you must pass ``-o | ||
78 | +backing_fmt=`` (or the shorthand ``-F`` during create) to specify the | ||
79 | +intended backing format. You may use ``qemu-img rebase -u`` to | ||
80 | +retroactively add a backing format to an existing image. However, be | ||
81 | +aware that there are already potential security risks to blindly using | ||
82 | +``qemu-img info`` to probe the format of an untrusted backing image, | ||
83 | +when deciding what format to add into an existing image. | ||
84 | + | ||
85 | Block devices | ||
86 | ------------- | ||
87 | |||
88 | diff --git a/block.c b/block.c | 22 | diff --git a/block.c b/block.c |
89 | index XXXXXXX..XXXXXXX 100644 | 23 | index XXXXXXX..XXXXXXX 100644 |
90 | --- a/block.c | 24 | --- a/block.c |
91 | +++ b/block.c | 25 | +++ b/block.c |
92 | @@ -XXX,XX +XXX,XX @@ int coroutine_fn bdrv_co_check(BlockDriverState *bs, | 26 | @@ -XXX,XX +XXX,XX @@ bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) |
93 | * -ENOTSUP - format driver doesn't support changing the backing file | 27 | return -EPERM; |
94 | */ | ||
95 | int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file, | ||
96 | - const char *backing_fmt, bool warn) | ||
97 | + const char *backing_fmt, bool require) | ||
98 | { | ||
99 | BlockDriver *drv = bs->drv; | ||
100 | int ret; | ||
101 | @@ -XXX,XX +XXX,XX @@ int bdrv_change_backing_file(BlockDriverState *bs, const char *backing_file, | ||
102 | return -EINVAL; | ||
103 | } | 28 | } |
104 | 29 | ||
105 | - if (warn && backing_file && !backing_fmt) { | 30 | + bdrv_drained_begin(bs); |
106 | - warn_report("Deprecated use of backing file without explicit " | 31 | bs->open_flags |= BDRV_O_INACTIVE; |
107 | - "backing format, use of this image requires " | 32 | + bdrv_drained_end(bs); |
108 | - "potentially unsafe format probing"); | 33 | |
109 | + if (require && backing_file && !backing_fmt) { | 34 | /* |
110 | + return -EINVAL; | 35 | * Update permissions, they may differ for inactive nodes. |
111 | } | ||
112 | |||
113 | if (drv->bdrv_change_backing_file != NULL) { | ||
114 | @@ -XXX,XX +XXX,XX @@ void bdrv_img_create(const char *filename, const char *fmt, | ||
115 | goto out; | ||
116 | } else { | ||
117 | if (!backing_fmt) { | ||
118 | - warn_report("Deprecated use of backing file without explicit " | ||
119 | - "backing format (detected format of %s)", | ||
120 | - bs->drv->format_name); | ||
121 | - if (bs->drv != &bdrv_raw) { | ||
122 | - /* | ||
123 | - * A probe of raw deserves the most attention: | ||
124 | - * leaving the backing format out of the image | ||
125 | - * will ensure bs->probed is set (ensuring we | ||
126 | - * don't accidentally commit into the backing | ||
127 | - * file), and allow more spots to warn the users | ||
128 | - * to fix their toolchain when opening this image | ||
129 | - * later. For other images, we can safely record | ||
130 | - * the format that we probed. | ||
131 | - */ | ||
132 | - backing_fmt = bs->drv->format_name; | ||
133 | - qemu_opt_set(opts, BLOCK_OPT_BACKING_FMT, backing_fmt, | ||
134 | - NULL); | ||
135 | - } | ||
136 | + error_setg(&local_err, | ||
137 | + "Backing file specified without backing format"); | ||
138 | + error_append_hint(&local_err, "Detected format of %s.", | ||
139 | + bs->drv->format_name); | ||
140 | + goto out; | ||
141 | } | ||
142 | if (size == -1) { | ||
143 | /* Opened BS, have no size */ | ||
144 | @@ -XXX,XX +XXX,XX @@ void bdrv_img_create(const char *filename, const char *fmt, | ||
145 | } | ||
146 | /* (backing_file && !(flags & BDRV_O_NO_BACKING)) */ | ||
147 | } else if (backing_file && !backing_fmt) { | ||
148 | - warn_report("Deprecated use of unopened backing file without " | ||
149 | - "explicit backing format, use of this image requires " | ||
150 | - "potentially unsafe format probing"); | ||
151 | + error_setg(&local_err, | ||
152 | + "Backing file specified without backing format"); | ||
153 | + goto out; | ||
154 | } | ||
155 | |||
156 | if (size == -1) { | ||
157 | diff --git a/qemu-img.c b/qemu-img.c | ||
158 | index XXXXXXX..XXXXXXX 100644 | ||
159 | --- a/qemu-img.c | ||
160 | +++ b/qemu-img.c | ||
161 | @@ -XXX,XX +XXX,XX @@ static int img_convert(int argc, char **argv) | ||
162 | |||
163 | if (out_baseimg_param) { | ||
164 | if (!qemu_opt_get(opts, BLOCK_OPT_BACKING_FMT)) { | ||
165 | - warn_report("Deprecated use of backing file without explicit " | ||
166 | - "backing format"); | ||
167 | + error_report("Use of backing file requires explicit " | ||
168 | + "backing format"); | ||
169 | + ret = -1; | ||
170 | + goto out; | ||
171 | } | ||
172 | } | ||
173 | |||
174 | diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040 | ||
175 | index XXXXXXX..XXXXXXX 100755 | ||
176 | --- a/tests/qemu-iotests/040 | ||
177 | +++ b/tests/qemu-iotests/040 | ||
178 | @@ -XXX,XX +XXX,XX @@ class TestCommitWithOverriddenBacking(iotests.QMPTestCase): | ||
179 | def setUp(self): | ||
180 | qemu_img('create', '-f', iotests.imgfmt, self.img_base_a, '1M') | ||
181 | qemu_img('create', '-f', iotests.imgfmt, self.img_base_b, '1M') | ||
182 | - qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a, \ | ||
183 | - self.img_top) | ||
184 | + qemu_img('create', '-f', iotests.imgfmt, '-b', self.img_base_a, | ||
185 | + '-F', iotests.imgfmt, self.img_top) | ||
186 | |||
187 | self.vm = iotests.VM() | ||
188 | self.vm.launch() | ||
189 | diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 | ||
190 | index XXXXXXX..XXXXXXX 100755 | ||
191 | --- a/tests/qemu-iotests/041 | ||
192 | +++ b/tests/qemu-iotests/041 | ||
193 | @@ -XXX,XX +XXX,XX @@ class TestReplaces(iotests.QMPTestCase): | ||
194 | class TestFilters(iotests.QMPTestCase): | ||
195 | def setUp(self): | ||
196 | qemu_img('create', '-f', iotests.imgfmt, backing_img, '1M') | ||
197 | - qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, test_img) | ||
198 | - qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, target_img) | ||
199 | + qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, | ||
200 | + '-F', iotests.imgfmt, test_img) | ||
201 | + qemu_img('create', '-f', iotests.imgfmt, '-b', backing_img, | ||
202 | + '-F', iotests.imgfmt, target_img) | ||
203 | |||
204 | qemu_io('-c', 'write -P 1 0 512k', backing_img) | ||
205 | qemu_io('-c', 'write -P 2 512k 512k', test_img) | ||
206 | diff --git a/tests/qemu-iotests/114 b/tests/qemu-iotests/114 | ||
207 | index XXXXXXX..XXXXXXX 100755 | ||
208 | --- a/tests/qemu-iotests/114 | ||
209 | +++ b/tests/qemu-iotests/114 | ||
210 | @@ -XXX,XX +XXX,XX @@ _supported_os Linux | ||
211 | # qcow2.py does not work too well with external data files | ||
212 | _unsupported_imgopts data_file | ||
213 | |||
214 | -# Intentionally specify backing file without backing format; demonstrate | ||
215 | -# the difference in warning messages when backing file could be probed. | ||
216 | -# Note that only a non-raw probe result will affect the resulting image. | ||
217 | +# Older qemu-img could set up backing file without backing format; modern | ||
218 | +# qemu can't but we can use qcow2.py to simulate older files. | ||
219 | truncate -s $((64 * 1024 * 1024)) "$TEST_IMG.orig" | ||
220 | -_make_test_img -b "$TEST_IMG.orig" 64M | ||
221 | +_make_test_img -b "$TEST_IMG.orig" -F raw 64M | ||
222 | +$PYTHON qcow2.py "$TEST_IMG" del-header-ext 0xE2792ACA | ||
223 | |||
224 | TEST_IMG="$TEST_IMG.base" _make_test_img 64M | ||
225 | $QEMU_IMG convert -O qcow2 -B "$TEST_IMG.orig" "$TEST_IMG.orig" "$TEST_IMG" | ||
226 | -_make_test_img -b "$TEST_IMG.base" 64M | ||
227 | -_make_test_img -u -b "$TEST_IMG.base" 64M | ||
228 | +_make_test_img -b "$TEST_IMG.base" -F $IMGFMT 64M | ||
229 | +_make_test_img -u -b "$TEST_IMG.base" -F $IMGFMT 64M | ||
230 | |||
231 | # Set an invalid backing file format | ||
232 | $PYTHON qcow2.py "$TEST_IMG" add-header-ext 0xE2792ACA "foo" | ||
233 | @@ -XXX,XX +XXX,XX @@ _img_info | ||
234 | $QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir | ||
235 | $QEMU_IO -c "open -o backing.driver=$IMGFMT $TEST_IMG" -c "read 0 4k" | _filter_qemu_io | ||
236 | |||
237 | -# Rebase the image, to show that omitting backing format triggers a warning, | ||
238 | -# but probing now lets us use the backing file. | ||
239 | -$QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG" | ||
240 | +# Rebase the image, to show that backing format is required. | ||
241 | +($QEMU_IMG rebase -u -b "$TEST_IMG.base" "$TEST_IMG" 2>&1 && echo "unexpected pass") | _filter_testdir | ||
242 | +$QEMU_IMG rebase -u -b "$TEST_IMG.base" -F $IMGFMT "$TEST_IMG" | ||
243 | $QEMU_IO -c "open $TEST_IMG" -c "read 0 4k" 2>&1 | _filter_qemu_io | _filter_testdir | ||
244 | |||
245 | # success, all done | ||
246 | diff --git a/tests/qemu-iotests/114.out b/tests/qemu-iotests/114.out | ||
247 | index XXXXXXX..XXXXXXX 100644 | ||
248 | --- a/tests/qemu-iotests/114.out | ||
249 | +++ b/tests/qemu-iotests/114.out | ||
250 | @@ -XXX,XX +XXX,XX @@ | ||
251 | QA output created by 114 | ||
252 | -qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw) | ||
253 | -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig | ||
254 | +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.orig backing_fmt=raw | ||
255 | Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=67108864 | ||
256 | -qemu-img: warning: Deprecated use of backing file without explicit backing format | ||
257 | -qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT) | ||
258 | +qemu-img: Use of backing file requires explicit backing format | ||
259 | +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT | ||
260 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT | ||
261 | -qemu-img: warning: Deprecated use of unopened backing file without explicit backing format, use of this image requires potentially unsafe format probing | ||
262 | -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864 backing_file=TEST_DIR/t.IMGFMT.base | ||
263 | image: TEST_DIR/t.IMGFMT | ||
264 | file format: IMGFMT | ||
265 | virtual size: 64 MiB (67108864 bytes) | ||
266 | @@ -XXX,XX +XXX,XX @@ qemu-io: can't open device TEST_DIR/t.qcow2: Could not open backing file: Unknow | ||
267 | no file open, try 'help open' | ||
268 | read 4096/4096 bytes at offset 0 | ||
269 | 4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
270 | -qemu-img: warning: Deprecated use of backing file without explicit backing format, use of this image requires potentially unsafe format probing | ||
271 | +qemu-img: Could not change the backing file to 'TEST_DIR/t.qcow2.base': Invalid argument | ||
272 | read 4096/4096 bytes at offset 0 | ||
273 | 4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
274 | *** done | ||
275 | diff --git a/tests/qemu-iotests/301 b/tests/qemu-iotests/301 | ||
276 | index XXXXXXX..XXXXXXX 100755 | ||
277 | --- a/tests/qemu-iotests/301 | ||
278 | +++ b/tests/qemu-iotests/301 | ||
279 | @@ -XXX,XX +XXX,XX @@ | ||
280 | # | ||
281 | # Test qcow backing file warnings | ||
282 | # | ||
283 | -# Copyright (C) 2020 Red Hat, Inc. | ||
284 | +# Copyright (C) 2020-2021 Red Hat, Inc. | ||
285 | # | ||
286 | # This program is free software; you can redistribute it and/or modify | ||
287 | # it under the terms of the GNU General Public License as published by | ||
288 | @@ -XXX,XX +XXX,XX @@ echo "== qcow backed by qcow ==" | ||
289 | |||
290 | TEST_IMG="$TEST_IMG.base" _make_test_img $size | ||
291 | _make_test_img -b "$TEST_IMG.base" $size | ||
292 | -_img_info | ||
293 | _make_test_img -b "$TEST_IMG.base" -F $IMGFMT $size | ||
294 | _img_info | ||
295 | |||
296 | @@ -XXX,XX +XXX,XX @@ echo "== qcow backed by raw ==" | ||
297 | rm "$TEST_IMG.base" | ||
298 | truncate --size=$size "$TEST_IMG.base" | ||
299 | _make_test_img -b "$TEST_IMG.base" $size | ||
300 | -_img_info | ||
301 | _make_test_img -b "$TEST_IMG.base" -F raw $size | ||
302 | _img_info | ||
303 | |||
304 | diff --git a/tests/qemu-iotests/301.out b/tests/qemu-iotests/301.out | ||
305 | index XXXXXXX..XXXXXXX 100644 | ||
306 | --- a/tests/qemu-iotests/301.out | ||
307 | +++ b/tests/qemu-iotests/301.out | ||
308 | @@ -XXX,XX +XXX,XX @@ QA output created by 301 | ||
309 | |||
310 | == qcow backed by qcow == | ||
311 | Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=33554432 | ||
312 | -qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of IMGFMT) | ||
313 | -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT | ||
314 | -image: TEST_DIR/t.IMGFMT | ||
315 | -file format: IMGFMT | ||
316 | -virtual size: 32 MiB (33554432 bytes) | ||
317 | -cluster_size: 512 | ||
318 | -backing file: TEST_DIR/t.IMGFMT.base | ||
319 | +qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format | ||
320 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=IMGFMT | ||
321 | image: TEST_DIR/t.IMGFMT | ||
322 | file format: IMGFMT | ||
323 | @@ -XXX,XX +XXX,XX @@ cluster_size: 512 | ||
324 | backing file: TEST_DIR/t.IMGFMT.base | ||
325 | |||
326 | == qcow backed by raw == | ||
327 | -qemu-img: warning: Deprecated use of backing file without explicit backing format (detected format of raw) | ||
328 | -Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base | ||
329 | -image: TEST_DIR/t.IMGFMT | ||
330 | -file format: IMGFMT | ||
331 | -virtual size: 32 MiB (33554432 bytes) | ||
332 | -cluster_size: 512 | ||
333 | -backing file: TEST_DIR/t.IMGFMT.base | ||
334 | +qemu-img: TEST_DIR/t.IMGFMT: Backing file specified without backing format | ||
335 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw | ||
336 | image: TEST_DIR/t.IMGFMT | ||
337 | file format: IMGFMT | ||
338 | -- | 36 | -- |
339 | 2.31.1 | 37 | 2.48.1 |
340 | |||
341 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | Add an option in BlockExportOptions to allow creating an export on an |
---|---|---|---|
2 | inactive node without activating the node. This mode needs to be | ||
3 | explicitly supported by the export type (so that it doesn't perform any | ||
4 | operations that are forbidden for inactive nodes), so this patch alone | ||
5 | doesn't allow this option to be successfully used yet. | ||
2 | 6 | ||
3 | Without the allow_other mount option, no user (not even root) but the | 7 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | one who started qemu/the storage daemon can access the export. Allow | 8 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | users to configure the export such that such accesses are possible. | 9 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | 10 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | |
7 | While allow_other is probably what users want, we cannot make it an | 11 | Message-ID: <20250204211407.381505-13-kwolf@redhat.com> |
8 | unconditional default, because passing it is only possible (for non-root | ||
9 | users) if the global fuse.conf configuration file allows it. Thus, the | ||
10 | default is an 'auto' mode, in which we first try with allow_other, and | ||
11 | then fall back to without. | ||
12 | |||
13 | FuseExport.allow_other reports whether allow_other was actually used as | ||
14 | a mount option or not. Currently, this information is not used, but a | ||
15 | future patch will let this field decide whether e.g. an export's UID and | ||
16 | GID can be changed through chmod. | ||
17 | |||
18 | One notable thing about 'auto' mode is that libfuse may print error | ||
19 | messages directly to stderr, and so may fusermount (which it executes). | ||
20 | Our export code cannot really filter or hide them. Therefore, if 'auto' | ||
21 | fails its first attempt and has to fall back, fusermount will print an | ||
22 | error message that mounting with allow_other failed. | ||
23 | |||
24 | This behavior necessitates a change to iotest 308, namely we need to | ||
25 | filter out this error message (because if the first attempt at mounting | ||
26 | with allow_other succeeds, there will be no such message). | ||
27 | |||
28 | Furthermore, common.rc's _make_test_img should use allow-other=off for | ||
29 | FUSE exports, because iotests generally do not need to access images | ||
30 | from other users, so allow-other=on or allow-other=auto have no | ||
31 | advantage. OTOH, allow-other=on will not work on systems where | ||
32 | user_allow_other is disabled, and with allow-other=auto, we get said | ||
33 | error message that we would need to filter out again. Just disabling | ||
34 | allow-other is simplest. | ||
35 | |||
36 | Signed-off-by: Max Reitz <mreitz@redhat.com> | ||
37 | Message-Id: <20210625142317.271673-3-mreitz@redhat.com> | ||
38 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
39 | --- | 13 | --- |
40 | qapi/block-export.json | 33 ++++++++++++++++++++++++++++++++- | 14 | qapi/block-export.json | 10 +++++++++- |
41 | block/export/fuse.c | 28 +++++++++++++++++++++++----- | 15 | include/block/export.h | 3 +++ |
42 | tests/qemu-iotests/308 | 6 +++++- | 16 | block/export/export.c | 31 +++++++++++++++++++++---------- |
43 | tests/qemu-iotests/common.rc | 6 +++++- | 17 | 3 files changed, 33 insertions(+), 11 deletions(-) |
44 | 4 files changed, 65 insertions(+), 8 deletions(-) | ||
45 | 18 | ||
46 | diff --git a/qapi/block-export.json b/qapi/block-export.json | 19 | diff --git a/qapi/block-export.json b/qapi/block-export.json |
47 | index XXXXXXX..XXXXXXX 100644 | 20 | index XXXXXXX..XXXXXXX 100644 |
48 | --- a/qapi/block-export.json | 21 | --- a/qapi/block-export.json |
49 | +++ b/qapi/block-export.json | 22 | +++ b/qapi/block-export.json |
50 | @@ -XXX,XX +XXX,XX @@ | 23 | @@ -XXX,XX +XXX,XX @@ |
51 | '*logical-block-size': 'size', | 24 | # cannot be moved to the iothread. The default is false. |
52 | '*num-queues': 'uint16'} } | 25 | # (since: 5.2) |
53 | 26 | # | |
54 | +## | 27 | +# @allow-inactive: If true, the export allows the exported node to be inactive. |
55 | +# @FuseExportAllowOther: | 28 | +# If it is created for an inactive block node, the node remains inactive. If |
29 | +# the export type doesn't support running on an inactive node, an error is | ||
30 | +# returned. If false, inactive block nodes are automatically activated before | ||
31 | +# creating the export and trying to inactivate them later fails. | ||
32 | +# (since: 10.0; default: false) | ||
56 | +# | 33 | +# |
57 | +# Possible allow_other modes for FUSE exports. | 34 | # Since: 4.2 |
58 | +# | 35 | ## |
59 | +# @off: Do not pass allow_other as a mount option. | 36 | { 'union': 'BlockExportOptions', |
60 | +# | 37 | @@ -XXX,XX +XXX,XX @@ |
61 | +# @on: Pass allow_other as a mount option. | 38 | '*iothread': 'str', |
62 | +# | 39 | 'node-name': 'str', |
63 | +# @auto: Try mounting with allow_other first, and if that fails, retry | 40 | '*writable': 'bool', |
64 | +# without allow_other. | 41 | - '*writethrough': 'bool' }, |
65 | +# | 42 | + '*writethrough': 'bool', |
66 | +# Since: 6.1 | 43 | + '*allow-inactive': 'bool' }, |
67 | +## | 44 | 'discriminator': 'type', |
68 | +{ 'enum': 'FuseExportAllowOther', | 45 | 'data': { |
69 | + 'data': ['off', 'on', 'auto'] } | 46 | 'nbd': 'BlockExportOptionsNbd', |
47 | diff --git a/include/block/export.h b/include/block/export.h | ||
48 | index XXXXXXX..XXXXXXX 100644 | ||
49 | --- a/include/block/export.h | ||
50 | +++ b/include/block/export.h | ||
51 | @@ -XXX,XX +XXX,XX @@ typedef struct BlockExportDriver { | ||
52 | */ | ||
53 | size_t instance_size; | ||
54 | |||
55 | + /* True if the export type supports running on an inactive node */ | ||
56 | + bool supports_inactive; | ||
70 | + | 57 | + |
71 | ## | 58 | /* Creates and starts a new block export */ |
72 | # @BlockExportOptionsFuse: | 59 | int (*create)(BlockExport *, BlockExportOptions *, Error **); |
73 | # | 60 | |
74 | @@ -XXX,XX +XXX,XX @@ | 61 | diff --git a/block/export/export.c b/block/export/export.c |
75 | # @growable: Whether writes beyond the EOF should grow the block node | ||
76 | # accordingly. (default: false) | ||
77 | # | ||
78 | +# @allow-other: If this is off, only qemu's user is allowed access to | ||
79 | +# this export. That cannot be changed even with chmod or | ||
80 | +# chown. | ||
81 | +# Enabling this option will allow other users access to | ||
82 | +# the export with the FUSE mount option "allow_other". | ||
83 | +# Note that using allow_other as a non-root user requires | ||
84 | +# user_allow_other to be enabled in the global fuse.conf | ||
85 | +# configuration file. | ||
86 | +# In auto mode (the default), the FUSE export driver will | ||
87 | +# first attempt to mount the export with allow_other, and | ||
88 | +# if that fails, try again without. | ||
89 | +# (since 6.1; default: auto) | ||
90 | +# | ||
91 | # Since: 6.0 | ||
92 | ## | ||
93 | { 'struct': 'BlockExportOptionsFuse', | ||
94 | 'data': { 'mountpoint': 'str', | ||
95 | - '*growable': 'bool' }, | ||
96 | + '*growable': 'bool', | ||
97 | + '*allow-other': 'FuseExportAllowOther' }, | ||
98 | 'if': 'defined(CONFIG_FUSE)' } | ||
99 | |||
100 | ## | ||
101 | diff --git a/block/export/fuse.c b/block/export/fuse.c | ||
102 | index XXXXXXX..XXXXXXX 100644 | 62 | index XXXXXXX..XXXXXXX 100644 |
103 | --- a/block/export/fuse.c | 63 | --- a/block/export/export.c |
104 | +++ b/block/export/fuse.c | 64 | +++ b/block/export/export.c |
105 | @@ -XXX,XX +XXX,XX @@ typedef struct FuseExport { | 65 | @@ -XXX,XX +XXX,XX @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type) |
106 | char *mountpoint; | 66 | BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) |
107 | bool writable; | 67 | { |
108 | bool growable; | 68 | bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread; |
109 | + /* Whether allow_other was used as a mount option or not */ | 69 | + bool allow_inactive = export->has_allow_inactive && export->allow_inactive; |
110 | + bool allow_other; | 70 | const BlockExportDriver *drv; |
111 | } FuseExport; | 71 | BlockExport *exp = NULL; |
112 | 72 | BlockDriverState *bs; | |
113 | static GHashTable *exports; | 73 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) |
114 | @@ -XXX,XX +XXX,XX @@ static void fuse_export_delete(BlockExport *exp); | 74 | } |
115 | static void init_exports_table(void); | 75 | } |
116 | 76 | ||
117 | static int setup_fuse_export(FuseExport *exp, const char *mountpoint, | 77 | - /* |
118 | - Error **errp); | 78 | - * Block exports are used for non-shared storage migration. Make sure |
119 | + bool allow_other, Error **errp); | 79 | - * that BDRV_O_INACTIVE is cleared and the image is ready for write |
120 | static void read_from_fuse_export(void *opaque); | 80 | - * access since the export could be available before migration handover. |
121 | 81 | - * ctx was acquired in the caller. | |
122 | static bool is_regular_file(const char *path, Error **errp); | 82 | - */ |
123 | @@ -XXX,XX +XXX,XX @@ static int fuse_export_create(BlockExport *blk_exp, | 83 | bdrv_graph_rdlock_main_loop(); |
124 | exp->writable = blk_exp_args->writable; | 84 | - ret = bdrv_activate(bs, errp); |
125 | exp->growable = args->growable; | 85 | - if (ret < 0) { |
126 | 86 | - bdrv_graph_rdunlock_main_loop(); | |
127 | - ret = setup_fuse_export(exp, args->mountpoint, errp); | 87 | - goto fail; |
128 | + /* set default */ | 88 | + if (allow_inactive) { |
129 | + if (!args->has_allow_other) { | 89 | + if (!drv->supports_inactive) { |
130 | + args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO; | 90 | + error_setg(errp, "Export type does not support inactive exports"); |
131 | + } | 91 | + bdrv_graph_rdunlock_main_loop(); |
132 | + | 92 | + goto fail; |
133 | + if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) { | ||
134 | + /* Ignore errors on our first attempt */ | ||
135 | + ret = setup_fuse_export(exp, args->mountpoint, true, NULL); | ||
136 | + exp->allow_other = ret == 0; | ||
137 | + if (ret < 0) { | ||
138 | + ret = setup_fuse_export(exp, args->mountpoint, false, errp); | ||
139 | + } | 93 | + } |
140 | + } else { | 94 | + } else { |
141 | + exp->allow_other = args->allow_other == FUSE_EXPORT_ALLOW_OTHER_ON; | 95 | + /* |
142 | + ret = setup_fuse_export(exp, args->mountpoint, exp->allow_other, errp); | 96 | + * Block exports are used for non-shared storage migration. Make sure |
97 | + * that BDRV_O_INACTIVE is cleared and the image is ready for write | ||
98 | + * access since the export could be available before migration handover. | ||
99 | + */ | ||
100 | + ret = bdrv_activate(bs, errp); | ||
101 | + if (ret < 0) { | ||
102 | + bdrv_graph_rdunlock_main_loop(); | ||
103 | + goto fail; | ||
104 | + } | ||
105 | } | ||
106 | bdrv_graph_rdunlock_main_loop(); | ||
107 | |||
108 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) | ||
109 | if (!fixed_iothread) { | ||
110 | blk_set_allow_aio_context_change(blk, true); | ||
111 | } | ||
112 | + if (allow_inactive) { | ||
113 | + blk_set_force_allow_inactivate(blk); | ||
143 | + } | 114 | + } |
115 | |||
116 | ret = blk_insert_bs(blk, bs, errp); | ||
144 | if (ret < 0) { | 117 | if (ret < 0) { |
145 | goto fail; | ||
146 | } | ||
147 | @@ -XXX,XX +XXX,XX @@ static void init_exports_table(void) | ||
148 | * Create exp->fuse_session and mount it. | ||
149 | */ | ||
150 | static int setup_fuse_export(FuseExport *exp, const char *mountpoint, | ||
151 | - Error **errp) | ||
152 | + bool allow_other, Error **errp) | ||
153 | { | ||
154 | const char *fuse_argv[4]; | ||
155 | char *mount_opts; | ||
156 | @@ -XXX,XX +XXX,XX @@ static int setup_fuse_export(FuseExport *exp, const char *mountpoint, | ||
157 | * max_read needs to match what fuse_init() sets. | ||
158 | * max_write need not be supplied. | ||
159 | */ | ||
160 | - mount_opts = g_strdup_printf("max_read=%zu,default_permissions", | ||
161 | - FUSE_MAX_BOUNCE_BYTES); | ||
162 | + mount_opts = g_strdup_printf("max_read=%zu,default_permissions%s", | ||
163 | + FUSE_MAX_BOUNCE_BYTES, | ||
164 | + allow_other ? ",allow_other" : ""); | ||
165 | |||
166 | fuse_argv[0] = ""; /* Dummy program name */ | ||
167 | fuse_argv[1] = "-o"; | ||
168 | diff --git a/tests/qemu-iotests/308 b/tests/qemu-iotests/308 | ||
169 | index XXXXXXX..XXXXXXX 100755 | ||
170 | --- a/tests/qemu-iotests/308 | ||
171 | +++ b/tests/qemu-iotests/308 | ||
172 | @@ -XXX,XX +XXX,XX @@ _supported_os Linux # We need /dev/urandom | ||
173 | # $4: Node to export (defaults to 'node-format') | ||
174 | fuse_export_add() | ||
175 | { | ||
176 | + # The grep -v is a filter for errors when /etc/fuse.conf does not contain | ||
177 | + # user_allow_other. (The error is benign, but it is printed by fusermount | ||
178 | + # on the first mount attempt, so our export code cannot hide it.) | ||
179 | _send_qemu_cmd $QEMU_HANDLE \ | ||
180 | "{'execute': 'block-export-add', | ||
181 | 'arguments': { | ||
182 | @@ -XXX,XX +XXX,XX @@ fuse_export_add() | ||
183 | $2 | ||
184 | } }" \ | ||
185 | "${3:-return}" \ | ||
186 | - | _filter_imgfmt | ||
187 | + | _filter_imgfmt \ | ||
188 | + | grep -v 'option allow_other only allowed if' | ||
189 | } | ||
190 | |||
191 | # $1: Export ID | ||
192 | diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc | ||
193 | index XXXXXXX..XXXXXXX 100644 | ||
194 | --- a/tests/qemu-iotests/common.rc | ||
195 | +++ b/tests/qemu-iotests/common.rc | ||
196 | @@ -XXX,XX +XXX,XX @@ _make_test_img() | ||
197 | # Usually, users would export formatted nodes. But we present fuse as a | ||
198 | # protocol-level driver here, so we have to leave the format to the | ||
199 | # client. | ||
200 | + # Switch off allow-other, because in general we do not need it for | ||
201 | + # iotests. The default allow-other=auto has the downside of printing a | ||
202 | + # fusermount error on its first attempt if allow_other is not | ||
203 | + # permissible, which we would need to filter. | ||
204 | QSD_NEED_PID=y $QSD \ | ||
205 | --blockdev file,node-name=export-node,filename=$img_name,discard=unmap \ | ||
206 | - --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on \ | ||
207 | + --export fuse,id=fuse-export,node-name=export-node,mountpoint="$export_mp",writable=on,growable=on,allow-other=off \ | ||
208 | & | ||
209 | |||
210 | pidfile="$QEMU_TEST_DIR/qemu-storage-daemon.pid" | ||
211 | -- | 118 | -- |
212 | 2.31.1 | 119 | 2.48.1 |
213 | |||
214 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | In order to support running an NBD export on inactive nodes, we must |
---|---|---|---|
2 | make sure to return errors for any operations that aren't allowed on | ||
3 | inactive nodes. Reads are the only operation we know we need for | ||
4 | inactive images, so to err on the side of caution, return errors for | ||
5 | everything else, even if some operations could possibly be okay. | ||
2 | 6 | ||
3 | We do not do any permission checks in fuse_open(), so let the kernel do | 7 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | them. We already let fuse_getattr() report the proper UNIX permissions, | 8 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | so this should work the way we want. | 9 | Message-ID: <20250204211407.381505-14-kwolf@redhat.com> |
6 | 10 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | |
7 | This causes a change in 308's reference output, because now opening a | 11 | Reviewed-by: Eric Blake <eblake@redhat.com> |
8 | non-writable export with O_RDWR fails already, instead of only actually | ||
9 | attempting to write to it. (That is an improvement.) | ||
10 | |||
11 | Signed-off-by: Max Reitz <mreitz@redhat.com> | ||
12 | Message-Id: <20210625142317.271673-2-mreitz@redhat.com> | ||
13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
14 | --- | 13 | --- |
15 | block/export/fuse.c | 8 ++++++-- | 14 | nbd/server.c | 17 +++++++++++++++++ |
16 | tests/qemu-iotests/308 | 3 ++- | 15 | 1 file changed, 17 insertions(+) |
17 | tests/qemu-iotests/308.out | 2 +- | ||
18 | 3 files changed, 9 insertions(+), 4 deletions(-) | ||
19 | 16 | ||
20 | diff --git a/block/export/fuse.c b/block/export/fuse.c | 17 | diff --git a/nbd/server.c b/nbd/server.c |
21 | index XXXXXXX..XXXXXXX 100644 | 18 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/block/export/fuse.c | 19 | --- a/nbd/server.c |
23 | +++ b/block/export/fuse.c | 20 | +++ b/nbd/server.c |
24 | @@ -XXX,XX +XXX,XX @@ static int setup_fuse_export(FuseExport *exp, const char *mountpoint, | 21 | @@ -XXX,XX +XXX,XX @@ static void nbd_export_delete(BlockExport *blk_exp) |
25 | struct fuse_args fuse_args; | 22 | const BlockExportDriver blk_exp_nbd = { |
26 | int ret; | 23 | .type = BLOCK_EXPORT_TYPE_NBD, |
27 | 24 | .instance_size = sizeof(NBDExport), | |
28 | - /* Needs to match what fuse_init() sets. Only max_read must be supplied. */ | 25 | + .supports_inactive = true, |
29 | - mount_opts = g_strdup_printf("max_read=%zu", FUSE_MAX_BOUNCE_BYTES); | 26 | .create = nbd_export_create, |
30 | + /* | 27 | .delete = nbd_export_delete, |
31 | + * max_read needs to match what fuse_init() sets. | 28 | .request_shutdown = nbd_export_request_shutdown, |
32 | + * max_write need not be supplied. | 29 | @@ -XXX,XX +XXX,XX @@ static coroutine_fn int nbd_handle_request(NBDClient *client, |
33 | + */ | 30 | NBDExport *exp = client->exp; |
34 | + mount_opts = g_strdup_printf("max_read=%zu,default_permissions", | 31 | char *msg; |
35 | + FUSE_MAX_BOUNCE_BYTES); | 32 | size_t i; |
36 | 33 | + bool inactive; | |
37 | fuse_argv[0] = ""; /* Dummy program name */ | 34 | + |
38 | fuse_argv[1] = "-o"; | 35 | + WITH_GRAPH_RDLOCK_GUARD() { |
39 | diff --git a/tests/qemu-iotests/308 b/tests/qemu-iotests/308 | 36 | + inactive = bdrv_is_inactive(blk_bs(exp->common.blk)); |
40 | index XXXXXXX..XXXXXXX 100755 | 37 | + if (inactive) { |
41 | --- a/tests/qemu-iotests/308 | 38 | + switch (request->type) { |
42 | +++ b/tests/qemu-iotests/308 | 39 | + case NBD_CMD_READ: |
43 | @@ -XXX,XX +XXX,XX @@ echo '=== Writable export ===' | 40 | + /* These commands are allowed on inactive nodes */ |
44 | fuse_export_add 'export-mp' "'mountpoint': '$EXT_MP', 'writable': true" | 41 | + break; |
45 | 42 | + default: | |
46 | # Check that writing to the read-only export fails | 43 | + /* Return an error for the rest */ |
47 | -$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" | _filter_qemu_io | 44 | + return nbd_send_generic_reply(client, request, -EPERM, |
48 | +$QEMU_IO -f raw -c 'write -P 42 1M 64k' "$TEST_IMG" 2>&1 \ | 45 | + "export is inactive", errp); |
49 | + | _filter_qemu_io | _filter_testdir | _filter_imgfmt | 46 | + } |
50 | 47 | + } | |
51 | # But here it should work | 48 | + } |
52 | $QEMU_IO -f raw -c 'write -P 42 1M 64k' "$EXT_MP" | _filter_qemu_io | 49 | |
53 | diff --git a/tests/qemu-iotests/308.out b/tests/qemu-iotests/308.out | 50 | switch (request->type) { |
54 | index XXXXXXX..XXXXXXX 100644 | 51 | case NBD_CMD_CACHE: |
55 | --- a/tests/qemu-iotests/308.out | ||
56 | +++ b/tests/qemu-iotests/308.out | ||
57 | @@ -XXX,XX +XXX,XX @@ virtual size: 0 B (0 bytes) | ||
58 | 'mountpoint': 'TEST_DIR/t.IMGFMT.fuse', 'writable': true | ||
59 | } } | ||
60 | {"return": {}} | ||
61 | -write failed: Permission denied | ||
62 | +qemu-io: can't open device TEST_DIR/t.IMGFMT: Could not open 'TEST_DIR/t.IMGFMT': Permission denied | ||
63 | wrote 65536/65536 bytes at offset 1048576 | ||
64 | 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
65 | wrote 65536/65536 bytes at offset 1048576 | ||
66 | -- | 52 | -- |
67 | 2.31.1 | 53 | 2.48.1 |
68 | |||
69 | diff view generated by jsdifflib |
1 | From: Alberto Garcia <berto@igalia.com> | 1 | The open-coded form of this filter has been copied into enough tests |
---|---|---|---|
2 | that it's better to move it into iotests.py. | ||
2 | 3 | ||
3 | [ kwolf: Fixed AioContext locking ] | ||
4 | |||
5 | Signed-off-by: Alberto Garcia <berto@igalia.com> | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
7 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 5 | Acked-by: Fabiano Rosas <farosas@suse.de> |
8 | Message-Id: <20210708114709.206487-5-kwolf@redhat.com> | 6 | Reviewed-by: Eric Blake <eblake@redhat.com> |
7 | Message-ID: <20250204211407.381505-15-kwolf@redhat.com> | ||
8 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | --- | 10 | --- |
11 | qapi/block-core.json | 18 +++-- | 11 | tests/qemu-iotests/iotests.py | 4 ++++ |
12 | blockdev.c | 81 ++++++++++--------- | 12 | tests/qemu-iotests/041 | 4 +--- |
13 | tests/qemu-iotests/155 | 9 ++- | 13 | tests/qemu-iotests/165 | 4 +--- |
14 | tests/qemu-iotests/165 | 4 +- | 14 | tests/qemu-iotests/tests/copy-before-write | 3 +-- |
15 | tests/qemu-iotests/245 | 27 ++++--- | 15 | tests/qemu-iotests/tests/migrate-bitmaps-test | 7 +++---- |
16 | tests/qemu-iotests/248 | 2 +- | 16 | 5 files changed, 10 insertions(+), 12 deletions(-) |
17 | tests/qemu-iotests/248.out | 2 +- | ||
18 | tests/qemu-iotests/296 | 9 ++- | ||
19 | tests/qemu-iotests/298 | 4 +- | ||
20 | .../tests/remove-bitmap-from-backing | 18 +++-- | ||
21 | 10 files changed, 99 insertions(+), 75 deletions(-) | ||
22 | 17 | ||
23 | diff --git a/qapi/block-core.json b/qapi/block-core.json | 18 | diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py |
24 | index XXXXXXX..XXXXXXX 100644 | 19 | index XXXXXXX..XXXXXXX 100644 |
25 | --- a/qapi/block-core.json | 20 | --- a/tests/qemu-iotests/iotests.py |
26 | +++ b/qapi/block-core.json | 21 | +++ b/tests/qemu-iotests/iotests.py |
27 | @@ -XXX,XX +XXX,XX @@ | 22 | @@ -XXX,XX +XXX,XX @@ def _filter(_key, value): |
28 | ## | 23 | def filter_nbd_exports(output: str) -> str: |
29 | # @x-blockdev-reopen: | 24 | return re.sub(r'((min|opt|max) block): [0-9]+', r'\1: XXX', output) |
30 | # | 25 | |
31 | -# Reopens a block device using the given set of options. Any option | 26 | +def filter_qtest(output: str) -> str: |
32 | -# not specified will be reset to its default value regardless of its | 27 | + output = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', output) |
33 | -# previous status. If an option cannot be changed or a particular | 28 | + output = re.sub(r'\n?\[I \+\d+\.\d+\] CLOSED\n?$', '', output) |
34 | +# Reopens one or more block devices using the given set of options. | 29 | + return output |
35 | +# Any option not specified will be reset to its default value regardless | 30 | |
36 | +# of its previous status. If an option cannot be changed or a particular | 31 | Msg = TypeVar('Msg', Dict[str, Any], List[Any], str) |
37 | # driver does not support reopening then the command will return an | 32 | |
38 | -# error. | 33 | diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 |
39 | +# error. All devices in the list are reopened in one transaction, so | ||
40 | +# if one of them fails then the whole transaction is cancelled. | ||
41 | # | ||
42 | -# The top-level @node-name option (from BlockdevOptions) must be | ||
43 | +# The command receives a list of block devices to reopen. For each one | ||
44 | +# of them, the top-level @node-name option (from BlockdevOptions) must be | ||
45 | # specified and is used to select the block device to be reopened. | ||
46 | # Other @node-name options must be either omitted or set to the | ||
47 | # current name of the appropriate node. This command won't change any | ||
48 | @@ -XXX,XX +XXX,XX @@ | ||
49 | # | ||
50 | # 4) NULL: the current child (if any) is detached. | ||
51 | # | ||
52 | -# Options (1) and (2) are supported in all cases, but at the moment | ||
53 | -# only @backing allows replacing or detaching an existing child. | ||
54 | +# Options (1) and (2) are supported in all cases. Option (3) is | ||
55 | +# supported for @file and @backing, and option (4) for @backing only. | ||
56 | # | ||
57 | # Unlike with blockdev-add, the @backing option must always be present | ||
58 | # unless the node being reopened does not have a backing file and its | ||
59 | @@ -XXX,XX +XXX,XX @@ | ||
60 | # Since: 4.0 | ||
61 | ## | ||
62 | { 'command': 'x-blockdev-reopen', | ||
63 | - 'data': 'BlockdevOptions', 'boxed': true } | ||
64 | + 'data': { 'options': ['BlockdevOptions'] } } | ||
65 | |||
66 | ## | ||
67 | # @blockdev-del: | ||
68 | diff --git a/blockdev.c b/blockdev.c | ||
69 | index XXXXXXX..XXXXXXX 100644 | ||
70 | --- a/blockdev.c | ||
71 | +++ b/blockdev.c | ||
72 | @@ -XXX,XX +XXX,XX @@ fail: | ||
73 | visit_free(v); | ||
74 | } | ||
75 | |||
76 | -void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp) | ||
77 | -{ | ||
78 | - BlockDriverState *bs; | ||
79 | - AioContext *ctx; | ||
80 | - QObject *obj; | ||
81 | - Visitor *v = qobject_output_visitor_new(&obj); | ||
82 | - BlockReopenQueue *queue; | ||
83 | - QDict *qdict; | ||
84 | +void qmp_x_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp) | ||
85 | +{ | ||
86 | + BlockReopenQueue *queue = NULL; | ||
87 | + GSList *drained = NULL; | ||
88 | + | ||
89 | + /* Add each one of the BDS that we want to reopen to the queue */ | ||
90 | + for (; reopen_list != NULL; reopen_list = reopen_list->next) { | ||
91 | + BlockdevOptions *options = reopen_list->value; | ||
92 | + BlockDriverState *bs; | ||
93 | + AioContext *ctx; | ||
94 | + QObject *obj; | ||
95 | + Visitor *v; | ||
96 | + QDict *qdict; | ||
97 | + | ||
98 | + /* Check for the selected node name */ | ||
99 | + if (!options->has_node_name) { | ||
100 | + error_setg(errp, "node-name not specified"); | ||
101 | + goto fail; | ||
102 | + } | ||
103 | |||
104 | - /* Check for the selected node name */ | ||
105 | - if (!options->has_node_name) { | ||
106 | - error_setg(errp, "node-name not specified"); | ||
107 | - goto fail; | ||
108 | - } | ||
109 | + bs = bdrv_find_node(options->node_name); | ||
110 | + if (!bs) { | ||
111 | + error_setg(errp, "Failed to find node with node-name='%s'", | ||
112 | + options->node_name); | ||
113 | + goto fail; | ||
114 | + } | ||
115 | |||
116 | - bs = bdrv_find_node(options->node_name); | ||
117 | - if (!bs) { | ||
118 | - error_setg(errp, "Failed to find node with node-name='%s'", | ||
119 | - options->node_name); | ||
120 | - goto fail; | ||
121 | - } | ||
122 | + /* Put all options in a QDict and flatten it */ | ||
123 | + v = qobject_output_visitor_new(&obj); | ||
124 | + visit_type_BlockdevOptions(v, NULL, &options, &error_abort); | ||
125 | + visit_complete(v, &obj); | ||
126 | + visit_free(v); | ||
127 | |||
128 | - /* Put all options in a QDict and flatten it */ | ||
129 | - visit_type_BlockdevOptions(v, NULL, &options, &error_abort); | ||
130 | - visit_complete(v, &obj); | ||
131 | - qdict = qobject_to(QDict, obj); | ||
132 | + qdict = qobject_to(QDict, obj); | ||
133 | |||
134 | - qdict_flatten(qdict); | ||
135 | + qdict_flatten(qdict); | ||
136 | |||
137 | - /* Perform the reopen operation */ | ||
138 | - ctx = bdrv_get_aio_context(bs); | ||
139 | - aio_context_acquire(ctx); | ||
140 | - bdrv_subtree_drained_begin(bs); | ||
141 | - aio_context_release(ctx); | ||
142 | + ctx = bdrv_get_aio_context(bs); | ||
143 | + aio_context_acquire(ctx); | ||
144 | |||
145 | - queue = bdrv_reopen_queue(NULL, bs, qdict, false); | ||
146 | - bdrv_reopen_multiple(queue, errp); | ||
147 | + bdrv_subtree_drained_begin(bs); | ||
148 | + queue = bdrv_reopen_queue(queue, bs, qdict, false); | ||
149 | + drained = g_slist_prepend(drained, bs); | ||
150 | |||
151 | - ctx = bdrv_get_aio_context(bs); | ||
152 | - aio_context_acquire(ctx); | ||
153 | - bdrv_subtree_drained_end(bs); | ||
154 | - aio_context_release(ctx); | ||
155 | + aio_context_release(ctx); | ||
156 | + } | ||
157 | + | ||
158 | + /* Perform the reopen operation */ | ||
159 | + bdrv_reopen_multiple(queue, errp); | ||
160 | + queue = NULL; | ||
161 | |||
162 | fail: | ||
163 | - visit_free(v); | ||
164 | + bdrv_reopen_queue_free(queue); | ||
165 | + g_slist_free_full(drained, (GDestroyNotify) bdrv_subtree_drained_end); | ||
166 | } | ||
167 | |||
168 | void qmp_blockdev_del(const char *node_name, Error **errp) | ||
169 | diff --git a/tests/qemu-iotests/155 b/tests/qemu-iotests/155 | ||
170 | index XXXXXXX..XXXXXXX 100755 | 34 | index XXXXXXX..XXXXXXX 100755 |
171 | --- a/tests/qemu-iotests/155 | 35 | --- a/tests/qemu-iotests/041 |
172 | +++ b/tests/qemu-iotests/155 | 36 | +++ b/tests/qemu-iotests/041 |
173 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevMirrorReopen(MirrorBaseClass): | 37 | @@ -XXX,XX +XXX,XX @@ class TestRepairQuorum(iotests.QMPTestCase): |
174 | result = self.vm.qmp('blockdev-add', node_name="backing", | 38 | |
175 | driver="null-co") | 39 | # Check the full error message now |
176 | self.assert_qmp(result, 'return', {}) | 40 | self.vm.shutdown() |
177 | - result = self.vm.qmp('x-blockdev-reopen', node_name="target", | 41 | - log = self.vm.get_log() |
178 | - driver=iotests.imgfmt, file="target-file", | 42 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
179 | - backing="backing") | 43 | + log = iotests.filter_qtest(self.vm.get_log()) |
180 | + result = self.vm.qmp('x-blockdev-reopen', options=[{ | 44 | log = re.sub(r'^Formatting.*\n', '', log) |
181 | + 'node-name': "target", | 45 | - log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) |
182 | + 'driver': iotests.imgfmt, | 46 | log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) |
183 | + 'file': "target-file", | 47 | |
184 | + 'backing': "backing" | 48 | self.assertEqual(log, |
185 | + }]) | ||
186 | self.assert_qmp(result, 'return', {}) | ||
187 | |||
188 | class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen): | ||
189 | diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165 | 49 | diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165 |
190 | index XXXXXXX..XXXXXXX 100755 | 50 | index XXXXXXX..XXXXXXX 100755 |
191 | --- a/tests/qemu-iotests/165 | 51 | --- a/tests/qemu-iotests/165 |
192 | +++ b/tests/qemu-iotests/165 | 52 | +++ b/tests/qemu-iotests/165 |
193 | @@ -XXX,XX +XXX,XX @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): | 53 | @@ -XXX,XX +XXX,XX @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): |
194 | assert sha256_1 == self.getSha256() | 54 | self.vm.shutdown() |
195 | 55 | ||
196 | # Reopen to RW | 56 | #catch 'Persistent bitmaps are lost' possible error |
197 | - result = self.vm.qmp('x-blockdev-reopen', **{ | 57 | - log = self.vm.get_log() |
198 | + result = self.vm.qmp('x-blockdev-reopen', options=[{ | 58 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
199 | 'node-name': 'node0', | 59 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) |
200 | 'driver': iotests.imgfmt, | 60 | + log = iotests.filter_qtest(self.vm.get_log()) |
201 | 'file': { | 61 | if log: |
202 | @@ -XXX,XX +XXX,XX @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): | 62 | print(log) |
203 | 'filename': disk | 63 | |
204 | }, | 64 | diff --git a/tests/qemu-iotests/tests/copy-before-write b/tests/qemu-iotests/tests/copy-before-write |
205 | 'read-only': False | ||
206 | - }) | ||
207 | + }]) | ||
208 | self.assert_qmp(result, 'return', {}) | ||
209 | |||
210 | # Check that bitmap is reopened to RW and we can write to it. | ||
211 | diff --git a/tests/qemu-iotests/245 b/tests/qemu-iotests/245 | ||
212 | index XXXXXXX..XXXXXXX 100755 | 65 | index XXXXXXX..XXXXXXX 100755 |
213 | --- a/tests/qemu-iotests/245 | 66 | --- a/tests/qemu-iotests/tests/copy-before-write |
214 | +++ b/tests/qemu-iotests/245 | 67 | +++ b/tests/qemu-iotests/tests/copy-before-write |
215 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | 68 | @@ -XXX,XX +XXX,XX @@ class TestCbwError(iotests.QMPTestCase): |
216 | "Expected output of %d qemu-io commands, found %d" % | 69 | |
217 | (found, self.total_io_cmds)) | 70 | self.vm.shutdown() |
218 | 71 | log = self.vm.get_log() | |
219 | - # Run x-blockdev-reopen with 'opts' but applying 'newopts' | 72 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
220 | - # on top of it. The original 'opts' dict is unmodified | 73 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) |
221 | + # Run x-blockdev-reopen on a list of block devices | 74 | + log = iotests.filter_qtest(log) |
222 | + def reopenMultiple(self, opts, errmsg = None): | 75 | log = iotests.filter_qemu_io(log) |
223 | + result = self.vm.qmp('x-blockdev-reopen', conv_keys=False, options=opts) | 76 | return log |
224 | + if errmsg: | 77 | |
225 | + self.assert_qmp(result, 'error/class', 'GenericError') | 78 | diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-test b/tests/qemu-iotests/tests/migrate-bitmaps-test |
226 | + self.assert_qmp(result, 'error/desc', errmsg) | ||
227 | + else: | ||
228 | + self.assert_qmp(result, 'return', {}) | ||
229 | + | ||
230 | + # Run x-blockdev-reopen on a single block device (specified by | ||
231 | + # 'opts') but applying 'newopts' on top of it. The original 'opts' | ||
232 | + # dict is unmodified | ||
233 | def reopen(self, opts, newopts = {}, errmsg = None): | ||
234 | opts = copy.deepcopy(opts) | ||
235 | |||
236 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
237 | subdict = opts[prefix] | ||
238 | subdict[key] = value | ||
239 | |||
240 | - result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts) | ||
241 | - if errmsg: | ||
242 | - self.assert_qmp(result, 'error/class', 'GenericError') | ||
243 | - self.assert_qmp(result, 'error/desc', errmsg) | ||
244 | - else: | ||
245 | - self.assert_qmp(result, 'return', {}) | ||
246 | + self.reopenMultiple([ opts ], errmsg) | ||
247 | |||
248 | |||
249 | # Run query-named-block-nodes and return the specified entry | ||
250 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
251 | # We cannot change any of these | ||
252 | self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'") | ||
253 | self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''") | ||
254 | - self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string") | ||
255 | + self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'options[0].node-name', expected: string") | ||
256 | self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'") | ||
257 | self.reopen(opts, {'driver': ''}, "Invalid parameter ''") | ||
258 | - self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string") | ||
259 | + self.reopen(opts, {'driver': None}, "Invalid parameter type for 'options[0].driver', expected: string") | ||
260 | self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'") | ||
261 | self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''") | ||
262 | self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef") | ||
263 | @@ -XXX,XX +XXX,XX @@ class TestBlockdevReopen(iotests.QMPTestCase): | ||
264 | self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'") | ||
265 | self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'") | ||
266 | self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'") | ||
267 | - self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string") | ||
268 | + self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string") | ||
269 | |||
270 | # node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it | ||
271 | del opts['node-name'] | ||
272 | diff --git a/tests/qemu-iotests/248 b/tests/qemu-iotests/248 | ||
273 | index XXXXXXX..XXXXXXX 100755 | 79 | index XXXXXXX..XXXXXXX 100755 |
274 | --- a/tests/qemu-iotests/248 | 80 | --- a/tests/qemu-iotests/tests/migrate-bitmaps-test |
275 | +++ b/tests/qemu-iotests/248 | 81 | +++ b/tests/qemu-iotests/tests/migrate-bitmaps-test |
276 | @@ -XXX,XX +XXX,XX @@ vm.get_qmp_events() | 82 | @@ -XXX,XX +XXX,XX @@ class TestDirtyBitmapMigration(iotests.QMPTestCase): |
277 | 83 | ||
278 | del blockdev_opts['file']['size'] | 84 | # catch 'Could not reopen qcow2 layer: Bitmap already exists' |
279 | vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles], | 85 | # possible error |
280 | - **blockdev_opts) | 86 | - log = self.vm_a.get_log() |
281 | + options = [ blockdev_opts ]) | 87 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
282 | 88 | - log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', | |
283 | vm.qmp_log('block-job-resume', device='drive0') | 89 | + log = iotests.filter_qtest(self.vm_a.get_log()) |
284 | vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0, | 90 | + log = re.sub(r'^(wrote .* bytes at offset .*\n' |
285 | diff --git a/tests/qemu-iotests/248.out b/tests/qemu-iotests/248.out | 91 | + r'.*KiB.*ops.*sec.*\n?){3}', |
286 | index XXXXXXX..XXXXXXX 100644 | 92 | '', log) |
287 | --- a/tests/qemu-iotests/248.out | 93 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) |
288 | +++ b/tests/qemu-iotests/248.out | 94 | self.assertEqual(log, '') |
289 | @@ -XXX,XX +XXX,XX @@ | 95 | |
290 | {"return": {}} | 96 | # test that bitmap is still persistent |
291 | {"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}} | ||
292 | {"return": {}} | ||
293 | -{"execute": "x-blockdev-reopen", "arguments": {"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}} | ||
294 | +{"execute": "x-blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}} | ||
295 | {"return": {}} | ||
296 | {"execute": "block-job-resume", "arguments": {"device": "drive0"}} | ||
297 | {"return": {}} | ||
298 | diff --git a/tests/qemu-iotests/296 b/tests/qemu-iotests/296 | ||
299 | index XXXXXXX..XXXXXXX 100755 | ||
300 | --- a/tests/qemu-iotests/296 | ||
301 | +++ b/tests/qemu-iotests/296 | ||
302 | @@ -XXX,XX +XXX,XX @@ class EncryptionSetupTestCase(iotests.QMPTestCase): | ||
303 | |||
304 | command = 'x-blockdev-reopen' if reOpen else 'blockdev-add' | ||
305 | |||
306 | - result = vm.qmp(command, ** | ||
307 | - { | ||
308 | + opts = { | ||
309 | 'driver': iotests.imgfmt, | ||
310 | 'node-name': id, | ||
311 | 'read-only': readOnly, | ||
312 | @@ -XXX,XX +XXX,XX @@ class EncryptionSetupTestCase(iotests.QMPTestCase): | ||
313 | 'filename': test_img, | ||
314 | } | ||
315 | } | ||
316 | - ) | ||
317 | + | ||
318 | + if reOpen: | ||
319 | + result = vm.qmp(command, options=[opts]) | ||
320 | + else: | ||
321 | + result = vm.qmp(command, **opts) | ||
322 | self.assert_qmp(result, 'return', {}) | ||
323 | |||
324 | |||
325 | diff --git a/tests/qemu-iotests/298 b/tests/qemu-iotests/298 | ||
326 | index XXXXXXX..XXXXXXX 100755 | ||
327 | --- a/tests/qemu-iotests/298 | ||
328 | +++ b/tests/qemu-iotests/298 | ||
329 | @@ -XXX,XX +XXX,XX @@ class TestPreallocateFilter(TestPreallocateBase): | ||
330 | self.check_big() | ||
331 | |||
332 | def test_reopen_opts(self): | ||
333 | - result = self.vm.qmp('x-blockdev-reopen', **{ | ||
334 | + result = self.vm.qmp('x-blockdev-reopen', options=[{ | ||
335 | 'node-name': 'disk', | ||
336 | 'driver': iotests.imgfmt, | ||
337 | 'file': { | ||
338 | @@ -XXX,XX +XXX,XX @@ class TestPreallocateFilter(TestPreallocateBase): | ||
339 | 'filename': disk | ||
340 | } | ||
341 | } | ||
342 | - }) | ||
343 | + }]) | ||
344 | self.assert_qmp(result, 'return', {}) | ||
345 | |||
346 | self.vm.hmp_qemu_io('drive0', 'write 0 1M') | ||
347 | diff --git a/tests/qemu-iotests/tests/remove-bitmap-from-backing b/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
348 | index XXXXXXX..XXXXXXX 100755 | ||
349 | --- a/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
350 | +++ b/tests/qemu-iotests/tests/remove-bitmap-from-backing | ||
351 | @@ -XXX,XX +XXX,XX @@ log('Trying to remove persistent bitmap from r-o base node, should fail:') | ||
352 | vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0') | ||
353 | |||
354 | new_base_opts = { | ||
355 | - 'node-name': 'base', | ||
356 | - 'driver': 'qcow2', | ||
357 | - 'file': { | ||
358 | - 'driver': 'file', | ||
359 | - 'filename': base | ||
360 | - }, | ||
361 | - 'read-only': False | ||
362 | + 'options': [{ | ||
363 | + 'node-name': 'base', | ||
364 | + 'driver': 'qcow2', | ||
365 | + 'file': { | ||
366 | + 'driver': 'file', | ||
367 | + 'filename': base | ||
368 | + }, | ||
369 | + 'read-only': False | ||
370 | + }] | ||
371 | } | ||
372 | |||
373 | # Don't want to bother with filtering qmp_log for reopen command | ||
374 | @@ -XXX,XX +XXX,XX @@ if result != {'return': {}}: | ||
375 | log('Remove persistent bitmap from base node reopened to RW:') | ||
376 | vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0') | ||
377 | |||
378 | -new_base_opts['read-only'] = True | ||
379 | +new_base_opts['options'][0]['read-only'] = True | ||
380 | result = vm.qmp('x-blockdev-reopen', **new_base_opts) | ||
381 | if result != {'return': {}}: | ||
382 | log('Failed to reopen: ' + str(result)) | ||
383 | -- | 97 | -- |
384 | 2.31.1 | 98 | 2.48.1 |
385 | |||
386 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | Test that it's possible to migrate a VM that uses an image on shared |
---|---|---|---|
2 | storage through qemu-storage-daemon. | ||
2 | 3 | ||
3 | Signed-off-by: Max Reitz <mreitz@redhat.com> | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | Message-Id: <20210625142317.271673-7-mreitz@redhat.com> | 5 | Acked-by: Fabiano Rosas <farosas@suse.de> |
6 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
7 | Message-ID: <20250204211407.381505-16-kwolf@redhat.com> | ||
8 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
5 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
6 | --- | 10 | --- |
7 | tests/qemu-iotests/tests/fuse-allow-other | 168 ++++++++++++++++++ | 11 | tests/qemu-iotests/tests/qsd-migrate | 140 +++++++++++++++++++++++ |
8 | tests/qemu-iotests/tests/fuse-allow-other.out | 88 +++++++++ | 12 | tests/qemu-iotests/tests/qsd-migrate.out | 59 ++++++++++ |
9 | 2 files changed, 256 insertions(+) | 13 | 2 files changed, 199 insertions(+) |
10 | create mode 100755 tests/qemu-iotests/tests/fuse-allow-other | 14 | create mode 100755 tests/qemu-iotests/tests/qsd-migrate |
11 | create mode 100644 tests/qemu-iotests/tests/fuse-allow-other.out | 15 | create mode 100644 tests/qemu-iotests/tests/qsd-migrate.out |
12 | 16 | ||
13 | diff --git a/tests/qemu-iotests/tests/fuse-allow-other b/tests/qemu-iotests/tests/fuse-allow-other | 17 | diff --git a/tests/qemu-iotests/tests/qsd-migrate b/tests/qemu-iotests/tests/qsd-migrate |
14 | new file mode 100755 | 18 | new file mode 100755 |
15 | index XXXXXXX..XXXXXXX | 19 | index XXXXXXX..XXXXXXX |
16 | --- /dev/null | 20 | --- /dev/null |
17 | +++ b/tests/qemu-iotests/tests/fuse-allow-other | 21 | +++ b/tests/qemu-iotests/tests/qsd-migrate |
18 | @@ -XXX,XX +XXX,XX @@ | 22 | @@ -XXX,XX +XXX,XX @@ |
19 | +#!/usr/bin/env bash | 23 | +#!/usr/bin/env python3 |
20 | +# group: rw | 24 | +# group: rw quick |
21 | +# | 25 | +# |
22 | +# Test FUSE exports' allow-other option | 26 | +# Copyright (C) Red Hat, Inc. |
23 | +# | ||
24 | +# Copyright (C) 2021 Red Hat, Inc. | ||
25 | +# | 27 | +# |
26 | +# This program is free software; you can redistribute it and/or modify | 28 | +# This program is free software; you can redistribute it and/or modify |
27 | +# it under the terms of the GNU General Public License as published by | 29 | +# it under the terms of the GNU General Public License as published by |
28 | +# the Free Software Foundation; either version 2 of the License, or | 30 | +# the Free Software Foundation; either version 2 of the License, or |
29 | +# (at your option) any later version. | 31 | +# (at your option) any later version. |
... | ... | ||
34 | +# GNU General Public License for more details. | 36 | +# GNU General Public License for more details. |
35 | +# | 37 | +# |
36 | +# You should have received a copy of the GNU General Public License | 38 | +# You should have received a copy of the GNU General Public License |
37 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | 39 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
38 | +# | 40 | +# |
39 | + | 41 | +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> |
40 | +seq=$(basename "$0") | 42 | + |
41 | +echo "QA output created by $seq" | 43 | +import iotests |
42 | + | 44 | + |
43 | +status=1 # failure is the default! | 45 | +from iotests import filter_qemu_io, filter_qtest |
44 | + | 46 | + |
45 | +_cleanup() | 47 | +iotests.script_initialize(supported_fmts=['generic'], |
46 | +{ | 48 | + supported_protocols=['file'], |
47 | + _cleanup_qemu | 49 | + supported_platforms=['linux']) |
48 | + _cleanup_test_img | 50 | + |
49 | + rm -f "$EXT_MP" | 51 | +with iotests.FilePath('disk.img') as path, \ |
50 | +} | 52 | + iotests.FilePath('nbd-src.sock', base_dir=iotests.sock_dir) as nbd_src, \ |
51 | +trap "_cleanup; exit \$status" 0 1 2 3 15 | 53 | + iotests.FilePath('nbd-dst.sock', base_dir=iotests.sock_dir) as nbd_dst, \ |
52 | + | 54 | + iotests.FilePath('migrate.sock', base_dir=iotests.sock_dir) as mig_sock, \ |
53 | +# get standard environment, filters and checks | 55 | + iotests.VM(path_suffix="-src") as vm_src, \ |
54 | +. ../common.rc | 56 | + iotests.VM(path_suffix="-dst") as vm_dst: |
55 | +. ../common.filter | 57 | + |
56 | +. ../common.qemu | 58 | + img_size = '10M' |
57 | + | 59 | + |
58 | +_supported_fmt generic | 60 | + iotests.log('Preparing disk...') |
59 | + | 61 | + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) |
60 | +_supported_proto file # We create the FUSE export manually | 62 | + |
61 | + | 63 | + iotests.log('Launching source QSD...') |
62 | +sudo -n -u nobody true || \ | 64 | + qsd_src = iotests.QemuStorageDaemon( |
63 | + _notrun 'Password-less sudo as nobody required to test allow_other' | 65 | + '--blockdev', f'file,node-name=disk-file,filename={path}', |
64 | + | 66 | + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt', |
65 | +# $1: Export ID | 67 | + '--nbd-server', f'addr.type=unix,addr.path={nbd_src}', |
66 | +# $2: Options (beyond the node-name and ID) | 68 | + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' |
67 | +# $3: Expected return value (defaults to 'return') | 69 | + 'allow-inactive=true', |
68 | +# $4: Node to export (defaults to 'node-format') | 70 | + qmp=True, |
69 | +fuse_export_add() | ||
70 | +{ | ||
71 | + allow_other_not_supported='option allow_other only allowed if' | ||
72 | + | ||
73 | + output=$( | ||
74 | + success_or_failure=yes _send_qemu_cmd $QEMU_HANDLE \ | ||
75 | + "{'execute': 'block-export-add', | ||
76 | + 'arguments': { | ||
77 | + 'type': 'fuse', | ||
78 | + 'id': '$1', | ||
79 | + 'node-name': '${4:-node-format}', | ||
80 | + $2 | ||
81 | + } }" \ | ||
82 | + "${3:-return}" \ | ||
83 | + "$allow_other_not_supported" \ | ||
84 | + | _filter_imgfmt | ||
85 | + ) | 71 | + ) |
86 | + | 72 | + |
87 | + if echo "$output" | grep -q "$allow_other_not_supported"; then | 73 | + iotests.log('Launching source VM...') |
88 | + # Shut down qemu gracefully so it can unmount the export | 74 | + vm_src.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' |
89 | + _send_qemu_cmd $QEMU_HANDLE \ | 75 | + f'server.path={nbd_src},export=disk-fmt') |
90 | + "{'execute': 'quit'}" \ | 76 | + vm_src.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') |
91 | + 'return' | 77 | + vm_src.launch() |
92 | + | 78 | + |
93 | + wait=yes _cleanup_qemu | 79 | + iotests.log('Launching destination QSD...') |
94 | + | 80 | + qsd_dst = iotests.QemuStorageDaemon( |
95 | + _notrun "allow_other not supported" | 81 | + '--blockdev', f'file,node-name=disk-file,filename={path},active=off', |
96 | + fi | 82 | + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' |
97 | + | 83 | + f'active=off', |
98 | + echo "$output" | 84 | + '--nbd-server', f'addr.type=unix,addr.path={nbd_dst}', |
99 | +} | 85 | + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' |
100 | + | 86 | + 'allow-inactive=true', |
101 | +EXT_MP="$TEST_DIR/fuse-export" | 87 | + qmp=True, |
102 | + | 88 | + instance_id='b', |
103 | +_make_test_img 64k | 89 | + ) |
104 | +touch "$EXT_MP" | 90 | + |
105 | + | 91 | + iotests.log('Launching destination VM...') |
106 | +echo | 92 | + vm_dst.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' |
107 | +echo '=== Test permissions ===' | 93 | + f'server.path={nbd_dst},export=disk-fmt') |
108 | + | 94 | + vm_dst.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') |
109 | +# $1: allow-other value ('on'/'off'/'auto') | 95 | + vm_dst.add_args('-incoming', f'unix:{mig_sock}') |
110 | +run_permission_test() | 96 | + vm_dst.launch() |
111 | +{ | 97 | + |
112 | + _launch_qemu \ | 98 | + iotests.log('\nTest I/O on the source') |
113 | + -blockdev \ | 99 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x11 0 4k', |
114 | + "$IMGFMT,node-name=node-format,file.driver=file,file.filename=$TEST_IMG" | 100 | + use_log=True, qdev=True) |
115 | + | 101 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
116 | + _send_qemu_cmd $QEMU_HANDLE \ | 102 | + use_log=True, qdev=True) |
117 | + "{'execute': 'qmp_capabilities'}" \ | 103 | + |
118 | + 'return' | 104 | + iotests.log('\nStarting migration...') |
119 | + | 105 | + |
120 | + fuse_export_add 'export' \ | 106 | + mig_caps = [ |
121 | + "'mountpoint': '$EXT_MP', | 107 | + {'capability': 'events', 'state': True}, |
122 | + 'allow-other': '$1'" | 108 | + {'capability': 'pause-before-switchover', 'state': True}, |
123 | + | 109 | + ] |
124 | + # Should always work | 110 | + vm_src.qmp_log('migrate-set-capabilities', capabilities=mig_caps) |
125 | + echo '(Removing all permissions)' | 111 | + vm_dst.qmp_log('migrate-set-capabilities', capabilities=mig_caps) |
126 | + chmod 000 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt | 112 | + vm_src.qmp_log('migrate', uri=f'unix:{mig_sock}', |
127 | + stat -c 'Permissions post-chmod: %a' "$EXT_MP" | 113 | + filters=[iotests.filter_qmp_testfiles]) |
128 | + | 114 | + |
129 | + # Should always work | 115 | + vm_src.event_wait('MIGRATION', |
130 | + echo '(Granting u+r)' | 116 | + match={'data': {'status': 'pre-switchover'}}) |
131 | + chmod u+r "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt | 117 | + |
132 | + stat -c 'Permissions post-chmod: %a' "$EXT_MP" | 118 | + iotests.log('\nPre-switchover: Reconfigure QSD instances') |
133 | + | 119 | + |
134 | + # Should only work with allow-other: Otherwise, no permissions can be | 120 | + iotests.log(qsd_src.qmp('blockdev-set-active', {'active': False})) |
135 | + # granted to the group or others | 121 | + |
136 | + echo '(Granting read permissions for everyone)' | 122 | + # Reading is okay from both sides while the image is inactive. Note that |
137 | + chmod 444 "$EXT_MP" 2>&1 | _filter_testdir | _filter_imgfmt | 123 | + # the destination may have stale data until it activates the image, though. |
138 | + stat -c 'Permissions post-chmod: %a' "$EXT_MP" | 124 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
139 | + | 125 | + use_log=True, qdev=True) |
140 | + echo 'Doing operations as nobody:' | 126 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read 0 4k', |
141 | + # Change to TEST_DIR, so nobody will not have to attempt a lookup | 127 | + use_log=True, qdev=True) |
142 | + pushd "$TEST_DIR" >/dev/null | 128 | + |
143 | + | 129 | + iotests.log(qsd_dst.qmp('blockdev-set-active', {'active': True})) |
144 | + # This is already prevented by the permissions (without allow-other, FUSE | 130 | + |
145 | + # exports always have o-r), but test it anyway | 131 | + iotests.log('\nCompleting migration...') |
146 | + sudo -n -u nobody cat fuse-export >/dev/null | 132 | + |
147 | + | 133 | + vm_src.qmp_log('migrate-continue', state='pre-switchover') |
148 | + # If the only problem were the lack of permissions, we should still be able | 134 | + vm_dst.event_wait('MIGRATION', match={'data': {'status': 'completed'}}) |
149 | + # to stat the export as nobody; it should not work without allow-other, | 135 | + |
150 | + # though | 136 | + iotests.log('\nTest I/O on the destination') |
151 | + sudo -n -u nobody \ | 137 | + |
152 | + stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \ | 138 | + # Now the destination must see what the source wrote |
153 | + | _filter_imgfmt | 139 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
154 | + | 140 | + use_log=True, qdev=True) |
155 | + # To prove the point, revoke read permissions for others and try again | 141 | + |
156 | + chmod o-r fuse-export 2>&1 | _filter_testdir | _filter_imgfmt | 142 | + # And be able to overwrite it |
157 | + | 143 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x22 0 4k', |
158 | + # Should fail | 144 | + use_log=True, qdev=True) |
159 | + sudo -n -u nobody cat fuse-export >/dev/null | 145 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x22 0 4k', |
160 | + # Should work with allow_other | 146 | + use_log=True, qdev=True) |
161 | + sudo -n -u nobody \ | 147 | + |
162 | + stat -c 'Permissions seen by nobody: %a' fuse-export 2>&1 \ | 148 | + iotests.log('\nDone') |
163 | + | _filter_imgfmt | 149 | + |
164 | + | 150 | + vm_src.shutdown() |
165 | + popd >/dev/null | 151 | + iotests.log('\n--- vm_src log ---') |
166 | + | 152 | + log = vm_src.get_log() |
167 | + _send_qemu_cmd $QEMU_HANDLE \ | 153 | + if log: |
168 | + "{'execute': 'quit'}" \ | 154 | + iotests.log(log, [filter_qtest, filter_qemu_io]) |
169 | + 'return' | 155 | + qsd_src.stop() |
170 | + | 156 | + |
171 | + wait=yes _cleanup_qemu | 157 | + vm_dst.shutdown() |
172 | +} | 158 | + iotests.log('\n--- vm_dst log ---') |
173 | + | 159 | + log = vm_dst.get_log() |
174 | +# 'auto' should behave exactly like 'on', because 'on' tests that | 160 | + if log: |
175 | +# allow_other works (otherwise, this test is skipped) | 161 | + iotests.log(log, [filter_qtest, filter_qemu_io]) |
176 | +for ao in off on auto; do | 162 | + qsd_dst.stop() |
177 | + echo | 163 | diff --git a/tests/qemu-iotests/tests/qsd-migrate.out b/tests/qemu-iotests/tests/qsd-migrate.out |
178 | + echo "--- allow-other=$ao ---" | ||
179 | + | ||
180 | + run_permission_test "$ao" | ||
181 | +done | ||
182 | + | ||
183 | +# success, all done | ||
184 | +echo "*** done" | ||
185 | +rm -f $seq.full | ||
186 | +status=0 | ||
187 | diff --git a/tests/qemu-iotests/tests/fuse-allow-other.out b/tests/qemu-iotests/tests/fuse-allow-other.out | ||
188 | new file mode 100644 | 164 | new file mode 100644 |
189 | index XXXXXXX..XXXXXXX | 165 | index XXXXXXX..XXXXXXX |
190 | --- /dev/null | 166 | --- /dev/null |
191 | +++ b/tests/qemu-iotests/tests/fuse-allow-other.out | 167 | +++ b/tests/qemu-iotests/tests/qsd-migrate.out |
192 | @@ -XXX,XX +XXX,XX @@ | 168 | @@ -XXX,XX +XXX,XX @@ |
193 | +QA output created by fuse-allow-other | 169 | +Preparing disk... |
194 | +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=65536 | 170 | +Launching source QSD... |
195 | + | 171 | +Launching source VM... |
196 | +=== Test permissions === | 172 | +Launching destination QSD... |
197 | + | 173 | +Launching destination VM... |
198 | +--- allow-other=off --- | 174 | + |
199 | +{'execute': 'qmp_capabilities'} | 175 | +Test I/O on the source |
200 | +{"return": {}} | 176 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x11 0 4k\""}} |
201 | +{'execute': 'block-export-add', | 177 | +{"return": ""} |
202 | + 'arguments': { | 178 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
203 | + 'type': 'fuse', | 179 | +{"return": ""} |
204 | + 'id': 'export', | 180 | + |
205 | + 'node-name': 'node-format', | 181 | +Starting migration... |
206 | + 'mountpoint': 'TEST_DIR/fuse-export', | 182 | +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} |
207 | + 'allow-other': 'off' | 183 | +{"return": {}} |
208 | + } } | 184 | +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} |
209 | +{"return": {}} | 185 | +{"return": {}} |
210 | +(Removing all permissions) | 186 | +{"execute": "migrate", "arguments": {"uri": "unix:SOCK_DIR/PID-migrate.sock"}} |
211 | +Permissions post-chmod: 0 | 187 | +{"return": {}} |
212 | +(Granting u+r) | 188 | + |
213 | +Permissions post-chmod: 400 | 189 | +Pre-switchover: Reconfigure QSD instances |
214 | +(Granting read permissions for everyone) | 190 | +{"return": {}} |
215 | +chmod: changing permissions of 'TEST_DIR/fuse-export': Operation not permitted | 191 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
216 | +Permissions post-chmod: 400 | 192 | +{"return": ""} |
217 | +Doing operations as nobody: | 193 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read 0 4k\""}} |
218 | +cat: fuse-export: Permission denied | 194 | +{"return": ""} |
219 | +stat: cannot statx 'fuse-export': Permission denied | 195 | +{"return": {}} |
220 | +cat: fuse-export: Permission denied | 196 | + |
221 | +stat: cannot statx 'fuse-export': Permission denied | 197 | +Completing migration... |
222 | +{'execute': 'quit'} | 198 | +{"execute": "migrate-continue", "arguments": {"state": "pre-switchover"}} |
223 | +{"return": {}} | 199 | +{"return": {}} |
224 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} | 200 | + |
225 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} | 201 | +Test I/O on the destination |
226 | + | 202 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
227 | +--- allow-other=on --- | 203 | +{"return": ""} |
228 | +{'execute': 'qmp_capabilities'} | 204 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x22 0 4k\""}} |
229 | +{"return": {}} | 205 | +{"return": ""} |
230 | +{'execute': 'block-export-add', | 206 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x22 0 4k\""}} |
231 | + 'arguments': { | 207 | +{"return": ""} |
232 | + 'type': 'fuse', | 208 | + |
233 | + 'id': 'export', | 209 | +Done |
234 | + 'node-name': 'node-format', | 210 | + |
235 | + 'mountpoint': 'TEST_DIR/fuse-export', | 211 | +--- vm_src log --- |
236 | + 'allow-other': 'on' | 212 | +wrote 4096/4096 bytes at offset 0 |
237 | + } } | 213 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
238 | +{"return": {}} | 214 | +read 4096/4096 bytes at offset 0 |
239 | +(Removing all permissions) | 215 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
240 | +Permissions post-chmod: 0 | 216 | +read 4096/4096 bytes at offset 0 |
241 | +(Granting u+r) | 217 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
242 | +Permissions post-chmod: 400 | 218 | + |
243 | +(Granting read permissions for everyone) | 219 | +--- vm_dst log --- |
244 | +Permissions post-chmod: 444 | 220 | +read 4096/4096 bytes at offset 0 |
245 | +Doing operations as nobody: | 221 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
246 | +Permissions seen by nobody: 444 | 222 | +read 4096/4096 bytes at offset 0 |
247 | +cat: fuse-export: Permission denied | 223 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
248 | +Permissions seen by nobody: 440 | 224 | +wrote 4096/4096 bytes at offset 0 |
249 | +{'execute': 'quit'} | 225 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
250 | +{"return": {}} | 226 | +read 4096/4096 bytes at offset 0 |
251 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} | 227 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
252 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} | ||
253 | + | ||
254 | +--- allow-other=auto --- | ||
255 | +{'execute': 'qmp_capabilities'} | ||
256 | +{"return": {}} | ||
257 | +{'execute': 'block-export-add', | ||
258 | + 'arguments': { | ||
259 | + 'type': 'fuse', | ||
260 | + 'id': 'export', | ||
261 | + 'node-name': 'node-format', | ||
262 | + 'mountpoint': 'TEST_DIR/fuse-export', | ||
263 | + 'allow-other': 'auto' | ||
264 | + } } | ||
265 | +{"return": {}} | ||
266 | +(Removing all permissions) | ||
267 | +Permissions post-chmod: 0 | ||
268 | +(Granting u+r) | ||
269 | +Permissions post-chmod: 400 | ||
270 | +(Granting read permissions for everyone) | ||
271 | +Permissions post-chmod: 444 | ||
272 | +Doing operations as nobody: | ||
273 | +Permissions seen by nobody: 444 | ||
274 | +cat: fuse-export: Permission denied | ||
275 | +Permissions seen by nobody: 440 | ||
276 | +{'execute': 'quit'} | ||
277 | +{"return": {}} | ||
278 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} | ||
279 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} | ||
280 | +*** done | ||
281 | -- | 228 | -- |
282 | 2.31.1 | 229 | 2.48.1 |
283 | |||
284 | diff view generated by jsdifflib |
1 | Without an external data file, s->data_file is a second pointer with the | 1 | This tests different types of operations on inactive block nodes |
---|---|---|---|
2 | same value as bs->file. When changing bs->file to a different BdrvChild | 2 | (including graph changes, block jobs and NBD exports) to make sure that |
3 | and freeing the old BdrvChild, s->data_file must also be updated, | 3 | users manually activating and inactivating nodes doesn't break things. |
4 | otherwise it points to freed memory and causes crashes. | ||
5 | 4 | ||
6 | This problem was caught by iotests case 245. | 5 | Support for inactive nodes in other export types will have to come with |
6 | separate test cases because they have different dependencies like blkio | ||
7 | or root permissions and we don't want to disable this basic test when | ||
8 | they are not fulfilled. | ||
7 | 9 | ||
8 | Fixes: df2b7086f169239ebad5d150efa29c9bb6d4f820 | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | Message-Id: <20210708114709.206487-2-kwolf@redhat.com> | 11 | Acked-by: Fabiano Rosas <farosas@suse.de> |
12 | Message-ID: <20250204211407.381505-17-kwolf@redhat.com> | ||
11 | Reviewed-by: Eric Blake <eblake@redhat.com> | 13 | Reviewed-by: Eric Blake <eblake@redhat.com> |
12 | Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> | 14 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
14 | --- | 16 | --- |
15 | block/qcow2.c | 29 +++++++++++++++++++++++++++++ | 17 | tests/qemu-iotests/iotests.py | 4 + |
16 | 1 file changed, 29 insertions(+) | 18 | tests/qemu-iotests/tests/inactive-node-nbd | 303 ++++++++++++++++++ |
19 | .../qemu-iotests/tests/inactive-node-nbd.out | 239 ++++++++++++++ | ||
20 | 3 files changed, 546 insertions(+) | ||
21 | create mode 100755 tests/qemu-iotests/tests/inactive-node-nbd | ||
22 | create mode 100644 tests/qemu-iotests/tests/inactive-node-nbd.out | ||
17 | 23 | ||
18 | diff --git a/block/qcow2.c b/block/qcow2.c | 24 | diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py |
19 | index XXXXXXX..XXXXXXX 100644 | 25 | index XXXXXXX..XXXXXXX 100644 |
20 | --- a/block/qcow2.c | 26 | --- a/tests/qemu-iotests/iotests.py |
21 | +++ b/block/qcow2.c | 27 | +++ b/tests/qemu-iotests/iotests.py |
22 | @@ -XXX,XX +XXX,XX @@ static void qcow2_refresh_limits(BlockDriverState *bs, Error **errp) | 28 | @@ -XXX,XX +XXX,XX @@ def add_incoming(self, addr): |
23 | static int qcow2_reopen_prepare(BDRVReopenState *state, | 29 | self._args.append(addr) |
24 | BlockReopenQueue *queue, Error **errp) | 30 | return self |
25 | { | 31 | |
26 | + BDRVQcow2State *s = state->bs->opaque; | 32 | + def add_paused(self): |
27 | Qcow2ReopenState *r; | 33 | + self._args.append('-S') |
28 | int ret; | 34 | + return self |
29 | 35 | + | |
30 | @@ -XXX,XX +XXX,XX @@ static int qcow2_reopen_prepare(BDRVReopenState *state, | 36 | def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage: |
31 | } | 37 | cmd = 'human-monitor-command' |
32 | } | 38 | kwargs: Dict[str, Any] = {'command-line': command_line} |
33 | 39 | diff --git a/tests/qemu-iotests/tests/inactive-node-nbd b/tests/qemu-iotests/tests/inactive-node-nbd | |
34 | + /* | 40 | new file mode 100755 |
35 | + * Without an external data file, s->data_file points to the same BdrvChild | 41 | index XXXXXXX..XXXXXXX |
36 | + * as bs->file. It needs to be resynced after reopen because bs->file may | 42 | --- /dev/null |
37 | + * be changed. We can't use it in the meantime. | 43 | +++ b/tests/qemu-iotests/tests/inactive-node-nbd |
38 | + */ | 44 | @@ -XXX,XX +XXX,XX @@ |
39 | + if (!has_data_file(state->bs)) { | 45 | +#!/usr/bin/env python3 |
40 | + assert(s->data_file == state->bs->file); | 46 | +# group: rw quick |
41 | + s->data_file = NULL; | 47 | +# |
48 | +# Copyright (C) Red Hat, Inc. | ||
49 | +# | ||
50 | +# This program is free software; you can redistribute it and/or modify | ||
51 | +# it under the terms of the GNU General Public License as published by | ||
52 | +# the Free Software Foundation; either version 2 of the License, or | ||
53 | +# (at your option) any later version. | ||
54 | +# | ||
55 | +# This program is distributed in the hope that it will be useful, | ||
56 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
57 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
58 | +# GNU General Public License for more details. | ||
59 | +# | ||
60 | +# You should have received a copy of the GNU General Public License | ||
61 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
62 | +# | ||
63 | +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> | ||
64 | + | ||
65 | +import iotests | ||
66 | + | ||
67 | +from iotests import QemuIoInteractive | ||
68 | +from iotests import filter_qemu_io, filter_qtest, filter_qmp_testfiles | ||
69 | + | ||
70 | +iotests.script_initialize(supported_fmts=['generic'], | ||
71 | + supported_protocols=['file'], | ||
72 | + supported_platforms=['linux']) | ||
73 | + | ||
74 | +def get_export(node_name='disk-fmt', allow_inactive=None): | ||
75 | + exp = { | ||
76 | + 'id': 'exp0', | ||
77 | + 'type': 'nbd', | ||
78 | + 'node-name': node_name, | ||
79 | + 'writable': True, | ||
42 | + } | 80 | + } |
43 | + | 81 | + |
44 | return 0; | 82 | + if allow_inactive is not None: |
45 | 83 | + exp['allow-inactive'] = allow_inactive | |
46 | fail: | 84 | + |
47 | @@ -XXX,XX +XXX,XX @@ fail: | 85 | + return exp |
48 | 86 | + | |
49 | static void qcow2_reopen_commit(BDRVReopenState *state) | 87 | +def node_is_active(_vm, node_name): |
50 | { | 88 | + nodes = _vm.cmd('query-named-block-nodes', flat=True) |
51 | + BDRVQcow2State *s = state->bs->opaque; | 89 | + node = next(n for n in nodes if n['node-name'] == node_name) |
52 | + | 90 | + return node['active'] |
53 | qcow2_update_options_commit(state->bs, state->opaque); | 91 | + |
54 | + if (!s->data_file) { | 92 | +with iotests.FilePath('disk.img') as path, \ |
55 | + /* | 93 | + iotests.FilePath('snap.qcow2') as snap_path, \ |
56 | + * If we don't have an external data file, s->data_file was cleared by | 94 | + iotests.FilePath('snap2.qcow2') as snap2_path, \ |
57 | + * qcow2_reopen_prepare() and needs to be updated. | 95 | + iotests.FilePath('target.img') as target_path, \ |
58 | + */ | 96 | + iotests.FilePath('nbd.sock', base_dir=iotests.sock_dir) as nbd_sock, \ |
59 | + s->data_file = state->bs->file; | 97 | + iotests.VM() as vm: |
60 | + } | 98 | + |
61 | g_free(state->opaque); | 99 | + img_size = '10M' |
62 | } | 100 | + |
63 | 101 | + iotests.log('Preparing disk...') | |
64 | @@ -XXX,XX +XXX,XX @@ static void qcow2_reopen_commit_post(BDRVReopenState *state) | 102 | + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) |
65 | 103 | + iotests.qemu_img_create('-f', iotests.imgfmt, target_path, img_size) | |
66 | static void qcow2_reopen_abort(BDRVReopenState *state) | 104 | + |
67 | { | 105 | + iotests.qemu_img_create('-f', 'qcow2', '-b', path, '-F', iotests.imgfmt, |
68 | + BDRVQcow2State *s = state->bs->opaque; | 106 | + snap_path) |
69 | + | 107 | + iotests.qemu_img_create('-f', 'qcow2', '-b', snap_path, '-F', 'qcow2', |
70 | + if (!s->data_file) { | 108 | + snap2_path) |
71 | + /* | 109 | + |
72 | + * If we don't have an external data file, s->data_file was cleared by | 110 | + iotests.log('Launching VM...') |
73 | + * qcow2_reopen_prepare() and needs to be restored. | 111 | + vm.add_blockdev(f'file,node-name=disk-file,filename={path}') |
74 | + */ | 112 | + vm.add_blockdev(f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' |
75 | + s->data_file = state->bs->file; | 113 | + 'active=off') |
76 | + } | 114 | + vm.add_blockdev(f'file,node-name=target-file,filename={target_path}') |
77 | qcow2_update_options_abort(state->bs, state->opaque); | 115 | + vm.add_blockdev(f'{iotests.imgfmt},file=target-file,node-name=target-fmt') |
78 | g_free(state->opaque); | 116 | + vm.add_blockdev(f'file,node-name=snap-file,filename={snap_path}') |
79 | } | 117 | + vm.add_blockdev(f'file,node-name=snap2-file,filename={snap2_path}') |
118 | + | ||
119 | + # Actually running the VM activates all images | ||
120 | + vm.add_paused() | ||
121 | + | ||
122 | + vm.launch() | ||
123 | + vm.qmp_log('nbd-server-start', | ||
124 | + addr={'type': 'unix', 'data':{'path': nbd_sock}}, | ||
125 | + filters=[filter_qmp_testfiles]) | ||
126 | + | ||
127 | + iotests.log('\n=== Creating export of inactive node ===') | ||
128 | + | ||
129 | + iotests.log('\nExports activate nodes without allow-inactive') | ||
130 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
131 | + vm.qmp_log('block-export-add', **get_export()) | ||
132 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
133 | + vm.qmp_log('query-block-exports') | ||
134 | + vm.qmp_log('block-export-del', id='exp0') | ||
135 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
136 | + vm.qmp_log('query-block-exports') | ||
137 | + | ||
138 | + iotests.log('\nExports activate nodes with allow-inactive=false') | ||
139 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
140 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
141 | + vm.qmp_log('block-export-add', **get_export(allow_inactive=False)) | ||
142 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
143 | + vm.qmp_log('query-block-exports') | ||
144 | + vm.qmp_log('block-export-del', id='exp0') | ||
145 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
146 | + vm.qmp_log('query-block-exports') | ||
147 | + | ||
148 | + iotests.log('\nExport leaves nodes inactive with allow-inactive=true') | ||
149 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
150 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
151 | + vm.qmp_log('block-export-add', **get_export(allow_inactive=True)) | ||
152 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
153 | + vm.qmp_log('query-block-exports') | ||
154 | + vm.qmp_log('block-export-del', id='exp0') | ||
155 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
156 | + vm.qmp_log('query-block-exports') | ||
157 | + | ||
158 | + iotests.log('\n=== Inactivating node with existing export ===') | ||
159 | + | ||
160 | + iotests.log('\nInactivating nodes with an export fails without ' | ||
161 | + 'allow-inactive') | ||
162 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) | ||
163 | + vm.qmp_log('block-export-add', **get_export(node_name='disk-fmt')) | ||
164 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
165 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
166 | + vm.qmp_log('query-block-exports') | ||
167 | + vm.qmp_log('block-export-del', id='exp0') | ||
168 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
169 | + vm.qmp_log('query-block-exports') | ||
170 | + | ||
171 | + iotests.log('\nInactivating nodes with an export fails with ' | ||
172 | + 'allow-inactive=false') | ||
173 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) | ||
174 | + vm.qmp_log('block-export-add', | ||
175 | + **get_export(node_name='disk-fmt', allow_inactive=False)) | ||
176 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
177 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
178 | + vm.qmp_log('query-block-exports') | ||
179 | + vm.qmp_log('block-export-del', id='exp0') | ||
180 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
181 | + vm.qmp_log('query-block-exports') | ||
182 | + | ||
183 | + iotests.log('\nInactivating nodes with an export works with ' | ||
184 | + 'allow-inactive=true') | ||
185 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) | ||
186 | + vm.qmp_log('block-export-add', | ||
187 | + **get_export(node_name='disk-fmt', allow_inactive=True)) | ||
188 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
189 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
190 | + vm.qmp_log('query-block-exports') | ||
191 | + vm.qmp_log('block-export-del', id='exp0') | ||
192 | + vm.event_wait('BLOCK_EXPORT_DELETED') | ||
193 | + vm.qmp_log('query-block-exports') | ||
194 | + | ||
195 | + iotests.log('\n=== Inactive nodes with parent ===') | ||
196 | + | ||
197 | + iotests.log('\nInactivating nodes with an active parent fails') | ||
198 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) | ||
199 | + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) | ||
200 | + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) | ||
201 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
202 | + | ||
203 | + iotests.log('\nInactivating nodes with an inactive parent works') | ||
204 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) | ||
205 | + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) | ||
206 | + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) | ||
207 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
208 | + | ||
209 | + iotests.log('\nCreating active parent node with an inactive child fails') | ||
210 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', | ||
211 | + node_name='disk-filter') | ||
212 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', | ||
213 | + node_name='disk-filter', active=True) | ||
214 | + | ||
215 | + iotests.log('\nCreating inactive parent node with an inactive child works') | ||
216 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', | ||
217 | + node_name='disk-filter', active=False) | ||
218 | + vm.qmp_log('blockdev-del', node_name='disk-filter') | ||
219 | + | ||
220 | + iotests.log('\n=== Resizing an inactive node ===') | ||
221 | + vm.qmp_log('block_resize', node_name='disk-fmt', size=16*1024*1024) | ||
222 | + | ||
223 | + iotests.log('\n=== Taking a snapshot of an inactive node ===') | ||
224 | + | ||
225 | + iotests.log('\nActive overlay over inactive backing file automatically ' | ||
226 | + 'makes both inactive for compatibility') | ||
227 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', | ||
228 | + file='snap-file', backing=None) | ||
229 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
230 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
231 | + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') | ||
232 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
233 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
234 | + vm.qmp_log('blockdev-del', node_name='snap-fmt') | ||
235 | + | ||
236 | + iotests.log('\nInactive overlay over inactive backing file just works') | ||
237 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', | ||
238 | + file='snap-file', backing=None, active=False) | ||
239 | + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') | ||
240 | + | ||
241 | + iotests.log('\n=== Block jobs with inactive nodes ===') | ||
242 | + | ||
243 | + iotests.log('\nStreaming into an inactive node') | ||
244 | + vm.qmp_log('block-stream', device='snap-fmt', | ||
245 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
246 | + | ||
247 | + iotests.log('\nCommitting an inactive root node (active commit)') | ||
248 | + vm.qmp_log('block-commit', job_id='job0', device='snap-fmt', | ||
249 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
250 | + | ||
251 | + iotests.log('\nCommitting an inactive intermediate node to inactive base') | ||
252 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap2-fmt', | ||
253 | + file='snap2-file', backing='snap-fmt', active=False) | ||
254 | + | ||
255 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
256 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
257 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
258 | + | ||
259 | + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', | ||
260 | + top_node='snap-fmt', | ||
261 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
262 | + | ||
263 | + iotests.log('\nCommitting an inactive intermediate node to active base') | ||
264 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) | ||
265 | + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', | ||
266 | + top_node='snap-fmt', | ||
267 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
268 | + | ||
269 | + iotests.log('\nMirror from inactive source to active target') | ||
270 | + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', | ||
271 | + target='target-fmt', sync='full', | ||
272 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
273 | + | ||
274 | + iotests.log('\nMirror from active source to inactive target') | ||
275 | + | ||
276 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
277 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
278 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
279 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
280 | + | ||
281 | + # Activating snap2-fmt recursively activates the whole backing chain | ||
282 | + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=True) | ||
283 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) | ||
284 | + | ||
285 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
286 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
287 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
288 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
289 | + | ||
290 | + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', | ||
291 | + target='target-fmt', sync='full', | ||
292 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
293 | + | ||
294 | + iotests.log('\nBackup from active source to inactive target') | ||
295 | + | ||
296 | + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', | ||
297 | + target='target-fmt', sync='full', | ||
298 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
299 | + | ||
300 | + iotests.log('\nBackup from inactive source to active target') | ||
301 | + | ||
302 | + # Inactivating snap2-fmt recursively inactivates the whole backing chain | ||
303 | + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=False) | ||
304 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=True) | ||
305 | + | ||
306 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
307 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
308 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
309 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
310 | + | ||
311 | + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', | ||
312 | + target='target-fmt', sync='full', | ||
313 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
314 | + | ||
315 | + iotests.log('\n=== Accessing export on inactive node ===') | ||
316 | + | ||
317 | + # Use the target node because it has the right image format and isn't the | ||
318 | + # (read-only) backing file of a qcow2 node | ||
319 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) | ||
320 | + vm.qmp_log('block-export-add', | ||
321 | + **get_export(node_name='target-fmt', allow_inactive=True)) | ||
322 | + | ||
323 | + # The read should succeed, everything else should fail gracefully | ||
324 | + qemu_io = QemuIoInteractive('-f', 'raw', | ||
325 | + f'nbd+unix:///target-fmt?socket={nbd_sock}') | ||
326 | + iotests.log(qemu_io.cmd('read 0 64k'), filters=[filter_qemu_io]) | ||
327 | + iotests.log(qemu_io.cmd('write 0 64k'), filters=[filter_qemu_io]) | ||
328 | + iotests.log(qemu_io.cmd('write -z 0 64k'), filters=[filter_qemu_io]) | ||
329 | + iotests.log(qemu_io.cmd('write -zu 0 64k'), filters=[filter_qemu_io]) | ||
330 | + iotests.log(qemu_io.cmd('discard 0 64k'), filters=[filter_qemu_io]) | ||
331 | + iotests.log(qemu_io.cmd('flush'), filters=[filter_qemu_io]) | ||
332 | + iotests.log(qemu_io.cmd('map'), filters=[filter_qemu_io]) | ||
333 | + qemu_io.close() | ||
334 | + | ||
335 | + iotests.log('\n=== Resuming VM activates all images ===') | ||
336 | + vm.qmp_log('cont') | ||
337 | + | ||
338 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
339 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
340 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
341 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
342 | + | ||
343 | + iotests.log('\nShutting down...') | ||
344 | + vm.shutdown() | ||
345 | + log = vm.get_log() | ||
346 | + if log: | ||
347 | + iotests.log(log, [filter_qtest, filter_qemu_io]) | ||
348 | diff --git a/tests/qemu-iotests/tests/inactive-node-nbd.out b/tests/qemu-iotests/tests/inactive-node-nbd.out | ||
349 | new file mode 100644 | ||
350 | index XXXXXXX..XXXXXXX | ||
351 | --- /dev/null | ||
352 | +++ b/tests/qemu-iotests/tests/inactive-node-nbd.out | ||
353 | @@ -XXX,XX +XXX,XX @@ | ||
354 | +Preparing disk... | ||
355 | +Launching VM... | ||
356 | +{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-nbd.sock"}, "type": "unix"}}} | ||
357 | +{"return": {}} | ||
358 | + | ||
359 | +=== Creating export of inactive node === | ||
360 | + | ||
361 | +Exports activate nodes without allow-inactive | ||
362 | +disk-fmt active: False | ||
363 | +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
364 | +{"return": {}} | ||
365 | +disk-fmt active: True | ||
366 | +{"execute": "query-block-exports", "arguments": {}} | ||
367 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
368 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
369 | +{"return": {}} | ||
370 | +{"execute": "query-block-exports", "arguments": {}} | ||
371 | +{"return": []} | ||
372 | + | ||
373 | +Exports activate nodes with allow-inactive=false | ||
374 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
375 | +{"return": {}} | ||
376 | +disk-fmt active: False | ||
377 | +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
378 | +{"return": {}} | ||
379 | +disk-fmt active: True | ||
380 | +{"execute": "query-block-exports", "arguments": {}} | ||
381 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
382 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
383 | +{"return": {}} | ||
384 | +{"execute": "query-block-exports", "arguments": {}} | ||
385 | +{"return": []} | ||
386 | + | ||
387 | +Export leaves nodes inactive with allow-inactive=true | ||
388 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
389 | +{"return": {}} | ||
390 | +disk-fmt active: False | ||
391 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
392 | +{"return": {}} | ||
393 | +disk-fmt active: False | ||
394 | +{"execute": "query-block-exports", "arguments": {}} | ||
395 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
396 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
397 | +{"return": {}} | ||
398 | +{"execute": "query-block-exports", "arguments": {}} | ||
399 | +{"return": []} | ||
400 | + | ||
401 | +=== Inactivating node with existing export === | ||
402 | + | ||
403 | +Inactivating nodes with an export fails without allow-inactive | ||
404 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
405 | +{"return": {}} | ||
406 | +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
407 | +{"return": {}} | ||
408 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
409 | +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} | ||
410 | +disk-fmt active: True | ||
411 | +{"execute": "query-block-exports", "arguments": {}} | ||
412 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
413 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
414 | +{"return": {}} | ||
415 | +{"execute": "query-block-exports", "arguments": {}} | ||
416 | +{"return": []} | ||
417 | + | ||
418 | +Inactivating nodes with an export fails with allow-inactive=false | ||
419 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
420 | +{"return": {}} | ||
421 | +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
422 | +{"return": {}} | ||
423 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
424 | +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} | ||
425 | +disk-fmt active: True | ||
426 | +{"execute": "query-block-exports", "arguments": {}} | ||
427 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
428 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
429 | +{"return": {}} | ||
430 | +{"execute": "query-block-exports", "arguments": {}} | ||
431 | +{"return": []} | ||
432 | + | ||
433 | +Inactivating nodes with an export works with allow-inactive=true | ||
434 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
435 | +{"return": {}} | ||
436 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} | ||
437 | +{"return": {}} | ||
438 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
439 | +{"return": {}} | ||
440 | +disk-fmt active: False | ||
441 | +{"execute": "query-block-exports", "arguments": {}} | ||
442 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} | ||
443 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} | ||
444 | +{"return": {}} | ||
445 | +{"execute": "query-block-exports", "arguments": {}} | ||
446 | +{"return": []} | ||
447 | + | ||
448 | +=== Inactive nodes with parent === | ||
449 | + | ||
450 | +Inactivating nodes with an active parent fails | ||
451 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
452 | +{"return": {}} | ||
453 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} | ||
454 | +{"error": {"class": "GenericError", "desc": "Node has active parent node"}} | ||
455 | +disk-file active: True | ||
456 | +disk-fmt active: True | ||
457 | + | ||
458 | +Inactivating nodes with an inactive parent works | ||
459 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} | ||
460 | +{"return": {}} | ||
461 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} | ||
462 | +{"return": {}} | ||
463 | +disk-file active: False | ||
464 | +disk-fmt active: False | ||
465 | + | ||
466 | +Creating active parent node with an inactive child fails | ||
467 | +{"execute": "blockdev-add", "arguments": {"driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
468 | +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} | ||
469 | +{"execute": "blockdev-add", "arguments": {"active": true, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
470 | +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} | ||
471 | + | ||
472 | +Creating inactive parent node with an inactive child works | ||
473 | +{"execute": "blockdev-add", "arguments": {"active": false, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
474 | +{"return": {}} | ||
475 | +{"execute": "blockdev-del", "arguments": {"node-name": "disk-filter"}} | ||
476 | +{"return": {}} | ||
477 | + | ||
478 | +=== Resizing an inactive node === | ||
479 | +{"execute": "block_resize", "arguments": {"node-name": "disk-fmt", "size": 16777216}} | ||
480 | +{"error": {"class": "GenericError", "desc": "Permission 'resize' unavailable on inactive node"}} | ||
481 | + | ||
482 | +=== Taking a snapshot of an inactive node === | ||
483 | + | ||
484 | +Active overlay over inactive backing file automatically makes both inactive for compatibility | ||
485 | +{"execute": "blockdev-add", "arguments": {"backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} | ||
486 | +{"return": {}} | ||
487 | +disk-fmt active: False | ||
488 | +snap-fmt active: True | ||
489 | +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} | ||
490 | +{"return": {}} | ||
491 | +disk-fmt active: False | ||
492 | +snap-fmt active: False | ||
493 | +{"execute": "blockdev-del", "arguments": {"node-name": "snap-fmt"}} | ||
494 | +{"return": {}} | ||
495 | + | ||
496 | +Inactive overlay over inactive backing file just works | ||
497 | +{"execute": "blockdev-add", "arguments": {"active": false, "backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} | ||
498 | +{"return": {}} | ||
499 | +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} | ||
500 | +{"return": {}} | ||
501 | + | ||
502 | +=== Block jobs with inactive nodes === | ||
503 | + | ||
504 | +Streaming into an inactive node | ||
505 | +{"execute": "block-stream", "arguments": {"device": "snap-fmt"}} | ||
506 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap-fmt' can't be a file child of active 'NODE_NAME'"}} | ||
507 | + | ||
508 | +Committing an inactive root node (active commit) | ||
509 | +{"execute": "block-commit", "arguments": {"device": "snap-fmt", "job-id": "job0"}} | ||
510 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
511 | + | ||
512 | +Committing an inactive intermediate node to inactive base | ||
513 | +{"execute": "blockdev-add", "arguments": {"active": false, "backing": "snap-fmt", "driver": "qcow2", "file": "snap2-file", "node-name": "snap2-fmt"}} | ||
514 | +{"return": {}} | ||
515 | +disk-fmt active: False | ||
516 | +snap-fmt active: False | ||
517 | +snap2-fmt active: False | ||
518 | +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} | ||
519 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
520 | + | ||
521 | +Committing an inactive intermediate node to active base | ||
522 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
523 | +{"return": {}} | ||
524 | +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} | ||
525 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
526 | + | ||
527 | +Mirror from inactive source to active target | ||
528 | +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
529 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap2-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
530 | + | ||
531 | +Mirror from active source to inactive target | ||
532 | +disk-fmt active: True | ||
533 | +snap-fmt active: False | ||
534 | +snap2-fmt active: False | ||
535 | +target-fmt active: True | ||
536 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "snap2-fmt"}} | ||
537 | +{"return": {}} | ||
538 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} | ||
539 | +{"return": {}} | ||
540 | +disk-fmt active: True | ||
541 | +snap-fmt active: True | ||
542 | +snap2-fmt active: True | ||
543 | +target-fmt active: False | ||
544 | +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
545 | +{"error": {"class": "GenericError", "desc": "Permission 'write' unavailable on inactive node"}} | ||
546 | + | ||
547 | +Backup from active source to inactive target | ||
548 | +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
549 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'target-fmt' can't be a target child of active 'NODE_NAME'"}} | ||
550 | + | ||
551 | +Backup from inactive source to active target | ||
552 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "snap2-fmt"}} | ||
553 | +{"return": {}} | ||
554 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "target-fmt"}} | ||
555 | +{"return": {}} | ||
556 | +disk-fmt active: False | ||
557 | +snap-fmt active: False | ||
558 | +snap2-fmt active: False | ||
559 | +target-fmt active: True | ||
560 | +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
561 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap2-fmt' can't be a file child of active 'NODE_NAME'"}} | ||
562 | + | ||
563 | +=== Accessing export on inactive node === | ||
564 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} | ||
565 | +{"return": {}} | ||
566 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "target-fmt", "type": "nbd", "writable": true}} | ||
567 | +{"return": {}} | ||
568 | +read 65536/65536 bytes at offset 0 | ||
569 | +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
570 | + | ||
571 | +write failed: Operation not permitted | ||
572 | + | ||
573 | +write failed: Operation not permitted | ||
574 | + | ||
575 | +write failed: Operation not permitted | ||
576 | + | ||
577 | +discard failed: Operation not permitted | ||
578 | + | ||
579 | + | ||
580 | +qemu-io: Failed to get allocation status: Operation not permitted | ||
581 | + | ||
582 | + | ||
583 | +=== Resuming VM activates all images === | ||
584 | +{"execute": "cont", "arguments": {}} | ||
585 | +{"return": {}} | ||
586 | +disk-fmt active: True | ||
587 | +snap-fmt active: True | ||
588 | +snap2-fmt active: True | ||
589 | +target-fmt active: True | ||
590 | + | ||
591 | +Shutting down... | ||
592 | + | ||
80 | -- | 593 | -- |
81 | 2.31.1 | 594 | 2.48.1 |
82 | |||
83 | diff view generated by jsdifflib |
1 | From: Max Reitz <mreitz@redhat.com> | 1 | From: Stefan Hajnoczi <stefanha@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | Allow changing the file mode, UID, and GID through SETATTR. | 3 | BLOCK_OP_TYPE_DATAPLANE prevents BlockDriverState from being used by |
4 | virtio-blk/virtio-scsi with IOThread. Commit b112a65c52aa ("block: | ||
5 | declare blockjobs and dataplane friends!") eliminated the main reason | ||
6 | for this blocker in 2014. | ||
4 | 7 | ||
5 | Without allow_other, UID and GID are not allowed to be changed, because | 8 | Nowadays the block layer supports I/O from multiple AioContexts, so |
6 | it would not make sense. Also, changing group or others' permissions | 9 | there is even less reason to block IOThread users. Any legitimate |
7 | is not allowed either. | 10 | reasons related to interference would probably also apply to |
11 | non-IOThread users. | ||
8 | 12 | ||
9 | For read-only exports, +w cannot be set. | 13 | The only remaining users are bdrv_op_unblock(BLOCK_OP_TYPE_DATAPLANE) |
14 | calls after bdrv_op_block_all(). If we remove BLOCK_OP_TYPE_DATAPLANE | ||
15 | their behavior doesn't change. | ||
10 | 16 | ||
11 | Signed-off-by: Max Reitz <mreitz@redhat.com> | 17 | Existing bdrv_op_block_all() callers that don't explicitly unblock |
12 | Message-Id: <20210625142317.271673-5-mreitz@redhat.com> | 18 | BLOCK_OP_TYPE_DATAPLANE seem to do so simply because no one bothered to |
19 | rather than because it is necessary to keep BLOCK_OP_TYPE_DATAPLANE | ||
20 | blocked. | ||
21 | |||
22 | Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
23 | Message-ID: <20250203182529.269066-1-stefanha@redhat.com> | ||
24 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
25 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 26 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
14 | --- | 27 | --- |
15 | block/export/fuse.c | 73 ++++++++++++++++++++++++++++++++++++++------- | 28 | include/block/block-common.h | 1 - |
16 | 1 file changed, 62 insertions(+), 11 deletions(-) | 29 | block/replication.c | 1 - |
30 | blockjob.c | 2 -- | ||
31 | hw/block/virtio-blk.c | 9 --------- | ||
32 | hw/scsi/virtio-scsi.c | 3 --- | ||
33 | 5 files changed, 16 deletions(-) | ||
17 | 34 | ||
18 | diff --git a/block/export/fuse.c b/block/export/fuse.c | 35 | diff --git a/include/block/block-common.h b/include/block/block-common.h |
19 | index XXXXXXX..XXXXXXX 100644 | 36 | index XXXXXXX..XXXXXXX 100644 |
20 | --- a/block/export/fuse.c | 37 | --- a/include/block/block-common.h |
21 | +++ b/block/export/fuse.c | 38 | +++ b/include/block/block-common.h |
22 | @@ -XXX,XX +XXX,XX @@ typedef struct FuseExport { | 39 | @@ -XXX,XX +XXX,XX @@ typedef enum BlockOpType { |
23 | bool growable; | 40 | BLOCK_OP_TYPE_CHANGE, |
24 | /* Whether allow_other was used as a mount option or not */ | 41 | BLOCK_OP_TYPE_COMMIT_SOURCE, |
25 | bool allow_other; | 42 | BLOCK_OP_TYPE_COMMIT_TARGET, |
26 | + | 43 | - BLOCK_OP_TYPE_DATAPLANE, |
27 | + mode_t st_mode; | 44 | BLOCK_OP_TYPE_DRIVE_DEL, |
28 | + uid_t st_uid; | 45 | BLOCK_OP_TYPE_EJECT, |
29 | + gid_t st_gid; | 46 | BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, |
30 | } FuseExport; | 47 | diff --git a/block/replication.c b/block/replication.c |
31 | 48 | index XXXXXXX..XXXXXXX 100644 | |
32 | static GHashTable *exports; | 49 | --- a/block/replication.c |
33 | @@ -XXX,XX +XXX,XX @@ static int fuse_export_create(BlockExport *blk_exp, | 50 | +++ b/block/replication.c |
34 | args->allow_other = FUSE_EXPORT_ALLOW_OTHER_AUTO; | 51 | @@ -XXX,XX +XXX,XX @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, |
52 | return; | ||
53 | } | ||
54 | bdrv_op_block_all(top_bs, s->blocker); | ||
55 | - bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker); | ||
56 | |||
57 | bdrv_graph_wrunlock(); | ||
58 | |||
59 | diff --git a/blockjob.c b/blockjob.c | ||
60 | index XXXXXXX..XXXXXXX 100644 | ||
61 | --- a/blockjob.c | ||
62 | +++ b/blockjob.c | ||
63 | @@ -XXX,XX +XXX,XX @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, | ||
64 | goto fail; | ||
35 | } | 65 | } |
36 | 66 | ||
37 | + exp->st_mode = S_IFREG | S_IRUSR; | 67 | - bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker); |
38 | + if (exp->writable) { | 68 | - |
39 | + exp->st_mode |= S_IWUSR; | 69 | if (!block_job_set_speed(job, speed, errp)) { |
40 | + } | 70 | goto fail; |
41 | + exp->st_uid = getuid(); | ||
42 | + exp->st_gid = getgid(); | ||
43 | + | ||
44 | if (args->allow_other == FUSE_EXPORT_ALLOW_OTHER_AUTO) { | ||
45 | /* Ignore errors on our first attempt */ | ||
46 | ret = setup_fuse_export(exp, args->mountpoint, true, NULL); | ||
47 | @@ -XXX,XX +XXX,XX @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode, | ||
48 | int64_t length, allocated_blocks; | ||
49 | time_t now = time(NULL); | ||
50 | FuseExport *exp = fuse_req_userdata(req); | ||
51 | - mode_t mode; | ||
52 | |||
53 | length = blk_getlength(exp->common.blk); | ||
54 | if (length < 0) { | ||
55 | @@ -XXX,XX +XXX,XX @@ static void fuse_getattr(fuse_req_t req, fuse_ino_t inode, | ||
56 | allocated_blocks = DIV_ROUND_UP(allocated_blocks, 512); | ||
57 | } | 71 | } |
58 | 72 | diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c | |
59 | - mode = S_IFREG | S_IRUSR; | 73 | index XXXXXXX..XXXXXXX 100644 |
60 | - if (exp->writable) { | 74 | --- a/hw/block/virtio-blk.c |
61 | - mode |= S_IWUSR; | 75 | +++ b/hw/block/virtio-blk.c |
62 | - } | 76 | @@ -XXX,XX +XXX,XX @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) |
77 | error_setg(errp, "ioeventfd is required for iothread"); | ||
78 | return false; | ||
79 | } | ||
63 | - | 80 | - |
64 | statbuf = (struct stat) { | 81 | - /* |
65 | .st_ino = inode, | 82 | - * If ioeventfd is (re-)enabled while the guest is running there could |
66 | - .st_mode = mode, | 83 | - * be block jobs that can conflict. |
67 | + .st_mode = exp->st_mode, | 84 | - */ |
68 | .st_nlink = 1, | 85 | - if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { |
69 | - .st_uid = getuid(), | 86 | - error_prepend(errp, "cannot start virtio-blk ioeventfd: "); |
70 | - .st_gid = getgid(), | 87 | - return false; |
71 | + .st_uid = exp->st_uid, | 88 | - } |
72 | + .st_gid = exp->st_gid, | 89 | } |
73 | .st_size = length, | 90 | |
74 | .st_blksize = blk_bs(exp->common.blk)->bl.request_alignment, | 91 | s->vq_aio_context = g_new(AioContext *, conf->num_queues); |
75 | .st_blocks = allocated_blocks, | 92 | diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c |
76 | @@ -XXX,XX +XXX,XX @@ static int fuse_do_truncate(const FuseExport *exp, int64_t size, | 93 | index XXXXXXX..XXXXXXX 100644 |
77 | } | 94 | --- a/hw/scsi/virtio-scsi.c |
78 | 95 | +++ b/hw/scsi/virtio-scsi.c | |
79 | /** | 96 | @@ -XXX,XX +XXX,XX @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, |
80 | - * Let clients set file attributes. Only resizing is supported. | ||
81 | + * Let clients set file attributes. Only resizing and changing | ||
82 | + * permissions (st_mode, st_uid, st_gid) is allowed. | ||
83 | + * Changing permissions is only allowed as far as it will actually | ||
84 | + * permit access: Read-only exports cannot be given +w, and exports | ||
85 | + * without allow_other cannot be given a different UID or GID, and | ||
86 | + * they cannot be given non-owner access. | ||
87 | */ | ||
88 | static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf, | ||
89 | int to_set, struct fuse_file_info *fi) | ||
90 | { | ||
91 | FuseExport *exp = fuse_req_userdata(req); | ||
92 | + int supported_attrs; | ||
93 | int ret; | 97 | int ret; |
94 | 98 | ||
95 | - if (to_set & ~FUSE_SET_ATTR_SIZE) { | 99 | if (s->ctx && !s->dataplane_fenced) { |
96 | + supported_attrs = FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_MODE; | 100 | - if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { |
97 | + if (exp->allow_other) { | 101 | - return; |
98 | + supported_attrs |= FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID; | 102 | - } |
99 | + } | 103 | ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp); |
100 | + | 104 | if (ret < 0) { |
101 | + if (to_set & ~supported_attrs) { | 105 | return; |
102 | fuse_reply_err(req, ENOTSUP); | ||
103 | return; | ||
104 | } | ||
105 | |||
106 | + /* Do some argument checks first before committing to anything */ | ||
107 | + if (to_set & FUSE_SET_ATTR_MODE) { | ||
108 | + /* | ||
109 | + * Without allow_other, non-owners can never access the export, so do | ||
110 | + * not allow setting permissions for them | ||
111 | + */ | ||
112 | + if (!exp->allow_other && | ||
113 | + (statbuf->st_mode & (S_IRWXG | S_IRWXO)) != 0) | ||
114 | + { | ||
115 | + fuse_reply_err(req, EPERM); | ||
116 | + return; | ||
117 | + } | ||
118 | + | ||
119 | + /* +w for read-only exports makes no sense, disallow it */ | ||
120 | + if (!exp->writable && | ||
121 | + (statbuf->st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) != 0) | ||
122 | + { | ||
123 | + fuse_reply_err(req, EROFS); | ||
124 | + return; | ||
125 | + } | ||
126 | + } | ||
127 | + | ||
128 | if (to_set & FUSE_SET_ATTR_SIZE) { | ||
129 | if (!exp->writable) { | ||
130 | fuse_reply_err(req, EACCES); | ||
131 | @@ -XXX,XX +XXX,XX @@ static void fuse_setattr(fuse_req_t req, fuse_ino_t inode, struct stat *statbuf, | ||
132 | } | ||
133 | } | ||
134 | |||
135 | + if (to_set & FUSE_SET_ATTR_MODE) { | ||
136 | + /* Ignore FUSE-supplied file type, only change the mode */ | ||
137 | + exp->st_mode = (statbuf->st_mode & 07777) | S_IFREG; | ||
138 | + } | ||
139 | + | ||
140 | + if (to_set & FUSE_SET_ATTR_UID) { | ||
141 | + exp->st_uid = statbuf->st_uid; | ||
142 | + } | ||
143 | + | ||
144 | + if (to_set & FUSE_SET_ATTR_GID) { | ||
145 | + exp->st_gid = statbuf->st_gid; | ||
146 | + } | ||
147 | + | ||
148 | fuse_getattr(req, inode, fi); | ||
149 | } | ||
150 | |||
151 | -- | 106 | -- |
152 | 2.31.1 | 107 | 2.48.1 |
153 | |||
154 | diff view generated by jsdifflib |