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