From 3ab823b980ae33cec3e64053230af7b2807dce60 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Thu, 11 Sep 2025 16:29:22 +0200 Subject: [PATCH 01/18] tests/qemu-iotests: Mark the 'inactive-node-nbd' as unsupported with -luks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running "./check -luks inactive-node-nbd", the test currently fails because QEMU terminates immediately. The reason can be seen with the "-p" parameter of the "check" script: qemu-system-x86_64: -blockdev luks,file=disk-file,node-name=disk-fmt,active=off: Parameter 'key-secret' is required for cipher Quoting Kevin: "The test case just isn't made for luks. iotests.py has special code for luks in VM.add_drive(), but not in VM.add_blockdev()." Thus let's mark it as unsupported on luks to avoid the failure. Suggested-by: Kevin Wolf Signed-off-by: Thomas Huth Message-ID: <20250911142922.222365-1-thuth@redhat.com> Reviewed-by: Daniel P. Berrangé Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/tests/inactive-node-nbd | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/qemu-iotests/tests/inactive-node-nbd b/tests/qemu-iotests/tests/inactive-node-nbd index a95b37e796..664157bfd0 100755 --- a/tests/qemu-iotests/tests/inactive-node-nbd +++ b/tests/qemu-iotests/tests/inactive-node-nbd @@ -24,6 +24,7 @@ from iotests import QemuIoInteractive from iotests import filter_qemu_io, filter_qtest, filter_qmp_testfiles iotests.script_initialize(supported_fmts=['generic'], + unsupported_fmts=['luks'], supported_protocols=['file'], supported_platforms=['linux']) From 6eda39a87f4fda78befa4085e3644e4440afc1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 19 Sep 2025 11:38:10 +0100 Subject: [PATCH 02/18] block: remove 'detached-header' option from opts after use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code for creating LUKS devices references a 'detached-header' option in the QemuOpts data, but does not consume (remove) the option. Thus when the code later tries to convert the remaining unused QemuOpts into a QCryptoBlockCreateOptions struct, an error is reported by the QAPI code that 'detached-header' is not a valid field. This fixes a regression caused by commit e818c01ae6e7c54c7019baaf307be59d99ce80b9 Author: Daniel P. Berrangé Date: Mon Feb 19 15:12:59 2024 +0000 qapi: drop unused QCryptoBlockCreateOptionsLUKS.detached-header which identified that the QAPI field was unused, but failed to realize the QemuOpts -> QCryptoBlockCreateOptions conversion was seeing the left-over 'detached-header' option which had not been removed from QemuOpts. This problem was identified by the 'luks-detached-header' I/O test, but unfortunately I/O tests are not run regularly for the LUKS format. Fixes: e818c01ae6e7c54c7019baaf307be59d99ce80b9 Reported-by: Thomas Huth Signed-off-by: Daniel P. Berrangé Message-ID: <20250919103810.1513109-1-berrange@redhat.com> Reviewed-by: Eric Blake Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block/crypto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/crypto.c b/block/crypto.c index d4226cc68a..17b4749a1e 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -792,7 +792,7 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename, char *buf = NULL; int64_t size; bool detached_hdr = - qemu_opt_get_bool(opts, "detached-header", false); + qemu_opt_get_bool_del(opts, "detached-header", false); unsigned int cflags = 0; int ret; Error *local_err = NULL; From c86488abaf017ed3f5a636c3247cd640a93d3b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 19 Sep 2025 12:22:13 +0100 Subject: [PATCH 03/18] block: fix luks 'amend' when run in coroutine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Launch QEMU with $ qemu-img create \ --object secret,id=sec0,data=123456 \ -f luks -o key-secret=sec0 demo.luks 1g $ qemu-system-x86_64 \ --object secret,id=sec0,data=123456 \ -blockdev driver=luks,key-secret=sec0,file.filename=demo.luks,file.driver=file,node-name=luks Then in QMP shell attempt x-blockdev-amend job-id=fish node-name=luks options={'state':'active','new-secret':'sec0','driver':'luks'} It will result in an assertion #0 __pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 #1 0x00007fad18b73f63 in __pthread_kill_internal (threadid=, signo=6) at pthread_kill.c:89 #2 0x00007fad18b19f3e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 #3 0x00007fad18b016d0 in __GI_abort () at abort.c:77 #4 0x00007fad18b01639 in __assert_fail_base (fmt=, assertion=, file=, line=, function=) at assert.c:118 #5 0x00007fad18b120af in __assert_fail (assertion=, file=, line=, function=) at assert.c:127 #6 0x000055ff74fdbd46 in bdrv_graph_rdlock_main_loop () at ../block/graph-lock.c:260 #7 0x000055ff7548521b in graph_lockable_auto_lock_mainloop (x=) at /usr/src/debug/qemu-9.2.4-1.fc42.x86_64/include/block/graph-lock.h:266 #8 block_crypto_read_func (block=, offset=4096, buf=0x55ffb6d66ef0 "", buflen=256000, opaque=0x55ffb5edcc30, errp=0x55ffb6f00700) at ../block/crypto.c:71 #9 0x000055ff75439f8b in qcrypto_block_luks_load_key (block=block@entry=0x55ffb5edbe90, slot_idx=slot_idx@entry=0, password=password@entry=0x55ffb67dc260 "123456", masterkey=masterkey@entry=0x55ffb5fb0c40 "", readfunc=readfunc@entry=0x55ff754851e0 , opaque=opaque@entry=0x55ffb5edcc30, errp=0x55ffb6f00700) at ../crypto/block-luks.c:927 #10 0x000055ff7543b90f in qcrypto_block_luks_find_key (block=, password=, masterkey=, readfunc=, opaque=, errp=) at ../crypto/block-luks.c:1045 #11 qcrypto_block_luks_amend_add_keyslot (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 , writefunc=0x55ff75485100 , opaque=0x55ffb5edcc3, opts_luks=0x7fad1715aef8, force=, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1673 #12 qcrypto_block_luks_amend_options (block=0x55ffb5edbe90, readfunc=0x55ff754851e0 , writefunc=0x55ff75485100 , opaque=0x55ffb5edcc30, options=0x7fad1715aef0, force=, errp=0x55ffb6f00700) at ../crypto/block-luks.c:1865 #13 0x000055ff75485b95 in block_crypto_amend_options_generic_luks (bs=, amend_options=, force=, errp=) at ../block/crypto.c:949 #14 0x000055ff75485c28 in block_crypto_co_amend_luks (bs=, opts=, force=, errp=) at ../block/crypto.c:1008 #15 0x000055ff754778e5 in blockdev_amend_run (job=0x55ffb6f00640, errp=0x55ffb6f00700) at ../block/amend.c:52 #16 0x000055ff75468b90 in job_co_entry (opaque=0x55ffb6f00640) at ../job.c:1106 #17 0x000055ff755a0fc2 in coroutine_trampoline (i0=, i1=) at ../util/coroutine-ucontext.c:175 This changes the read/write callbacks to not assert that they are run in mainloop context if already in a coroutine. This is also reproduced by qemu-iotests cases 295 and 296. Fixes: 1f051dcbdf2e4b6f518db731c84e304b2b9d15ce Signed-off-by: Daniel P. Berrangé Message-ID: <20250919112213.1530079-1-berrange@redhat.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block/crypto.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 17b4749a1e..7c37b23e36 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -67,11 +67,18 @@ static int block_crypto_read_func(QCryptoBlock *block, BlockCrypto *crypto = bs->opaque; ssize_t ret; - GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + if (qemu_in_coroutine()) { + GRAPH_RDLOCK_GUARD(); - ret = bdrv_pread(crypto->header ? crypto->header : bs->file, - offset, buflen, buf, 0); + ret = bdrv_co_pread(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); + } else { + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + ret = bdrv_pread(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); + } if (ret < 0) { error_setg_errno(errp, -ret, "Could not read encryption header"); return ret; @@ -90,11 +97,18 @@ static int block_crypto_write_func(QCryptoBlock *block, BlockCrypto *crypto = bs->opaque; ssize_t ret; - GLOBAL_STATE_CODE(); - GRAPH_RDLOCK_GUARD_MAINLOOP(); + if (qemu_in_coroutine()) { + GRAPH_RDLOCK_GUARD(); - ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file, - offset, buflen, buf, 0); + ret = bdrv_co_pwrite(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); + } else { + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); + } if (ret < 0) { error_setg_errno(errp, -ret, "Could not write encryption header"); return ret; From dec83ac02bee2f57f5b646dc1bedc5c200f3d679 Mon Sep 17 00:00:00 2001 From: Bin Guo Date: Tue, 16 Sep 2025 13:48:50 +0800 Subject: [PATCH 04/18] block/monitor: Use hmp_handle_error to report error According to writing-monitor-commands.rst, best practice is to use the 'hmp_handle_error' function, which ensures that the message gets an 'Error: ' prefix. Signed-off-by: Bin Guo Message-ID: <20250916054850.40963-1-guobin@linux.alibaba.com> Reviewed-by: Kevin Wolf [kwolf: Fixed up iotests reference output] Signed-off-by: Kevin Wolf --- block/monitor/block-hmp-cmds.c | 45 +++++++++++++++++----------------- tests/qemu-iotests/267.out | 8 +++--- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index 282d1c386e..3640d1f3dc 100644 --- a/block/monitor/block-hmp-cmds.c +++ b/block/monitor/block-hmp-cmds.c @@ -62,7 +62,7 @@ static void hmp_drive_add_node(Monitor *mon, const char *optstr) { QemuOpts *opts; QDict *qdict; - Error *local_err = NULL; + Error *err = NULL; opts = qemu_opts_parse_noisily(&qemu_drive_opts, optstr, false); if (!opts) { @@ -73,19 +73,19 @@ static void hmp_drive_add_node(Monitor *mon, const char *optstr) if (!qdict_get_try_str(qdict, "node-name")) { qobject_unref(qdict); - error_report("'node-name' needs to be specified"); + error_setg(&err, "'node-name' needs to be specified"); goto out; } - BlockDriverState *bs = bds_tree_init(qdict, &local_err); + BlockDriverState *bs = bds_tree_init(qdict, &err); if (!bs) { - error_report_err(local_err); goto out; } bdrv_set_monitor_owned(bs); out: qemu_opts_del(opts); + hmp_handle_error(mon, err); } void hmp_drive_add(Monitor *mon, const QDict *qdict) @@ -109,7 +109,6 @@ void hmp_drive_add(Monitor *mon, const QDict *qdict) mc = MACHINE_GET_CLASS(current_machine); dinfo = drive_new(opts, mc->block_default_type, &err); if (err) { - error_report_err(err); qemu_opts_del(opts); goto err; } @@ -123,7 +122,7 @@ void hmp_drive_add(Monitor *mon, const QDict *qdict) monitor_printf(mon, "OK\n"); break; default: - monitor_printf(mon, "Can't hot-add drive to type %d\n", dinfo->type); + error_setg(&err, "Can't hot-add drive to type %d", dinfo->type); goto err; } return; @@ -134,6 +133,7 @@ err: monitor_remove_blk(blk); blk_unref(blk); } + hmp_handle_error(mon, err); } void hmp_drive_del(Monitor *mon, const QDict *qdict) @@ -141,36 +141,32 @@ void hmp_drive_del(Monitor *mon, const QDict *qdict) const char *id = qdict_get_str(qdict, "id"); BlockBackend *blk; BlockDriverState *bs; - Error *local_err = NULL; + Error *err = NULL; GLOBAL_STATE_CODE(); bdrv_graph_rdlock_main_loop(); bs = bdrv_find_node(id); if (bs) { - qmp_blockdev_del(id, &local_err); - if (local_err) { - error_report_err(local_err); - } + qmp_blockdev_del(id, &err); goto unlock; } blk = blk_by_name(id); if (!blk) { - error_report("Device '%s' not found", id); + error_setg(&err, "Device '%s' not found", id); goto unlock; } if (!blk_legacy_dinfo(blk)) { - error_report("Deleting device added with blockdev-add" - " is not supported"); + error_setg(&err, "Deleting device added with blockdev-add" + " is not supported"); goto unlock; } bs = blk_bs(blk); if (bs) { - if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_DRIVE_DEL, &local_err)) { - error_report_err(local_err); + if (bdrv_op_is_blocked(bs, BLOCK_OP_TYPE_DRIVE_DEL, &err)) { goto unlock; } @@ -196,6 +192,7 @@ void hmp_drive_del(Monitor *mon, const QDict *qdict) unlock: bdrv_graph_rdunlock_main_loop(); + hmp_handle_error(mon, err); } void hmp_commit(Monitor *mon, const QDict *qdict) @@ -203,6 +200,7 @@ void hmp_commit(Monitor *mon, const QDict *qdict) const char *device = qdict_get_str(qdict, "device"); BlockBackend *blk; int ret; + Error *err = NULL; GLOBAL_STATE_CODE(); GRAPH_RDLOCK_GUARD_MAINLOOP(); @@ -214,22 +212,25 @@ void hmp_commit(Monitor *mon, const QDict *qdict) blk = blk_by_name(device); if (!blk) { - error_report("Device '%s' not found", device); - return; + error_setg(&err, "Device '%s' not found", device); + goto end; } bs = bdrv_skip_implicit_filters(blk_bs(blk)); if (!blk_is_available(blk)) { - error_report("Device '%s' has no medium", device); - return; + error_setg(&err, "Device '%s' has no medium", device); + goto end; } ret = bdrv_commit(bs); } if (ret < 0) { - error_report("'commit' error for '%s': %s", device, strerror(-ret)); + error_setg(&err, "'commit' error for '%s': %s", device, strerror(-ret)); } + +end: + hmp_handle_error(mon, err); } void hmp_drive_mirror(Monitor *mon, const QDict *qdict) @@ -890,7 +891,7 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict) bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, &err); if (!bs) { - error_report_err(err); + hmp_handle_error(mon, err); return; } diff --git a/tests/qemu-iotests/267.out b/tests/qemu-iotests/267.out index f6f5d8715a..37b7ebd280 100644 --- a/tests/qemu-iotests/267.out +++ b/tests/qemu-iotests/267.out @@ -8,7 +8,7 @@ QEMU X.Y.Z monitor - type 'help' for more information (qemu) savevm snap0 Error: no block device can store vmstate for snapshot (qemu) info snapshots -no block device can store vmstate for snapshot +Error: no block device can store vmstate for snapshot (qemu) loadvm snap0 Error: no block device can store vmstate for snapshot (qemu) quit @@ -22,7 +22,7 @@ QEMU X.Y.Z monitor - type 'help' for more information (qemu) savevm snap0 Error: Device 'none0' is writable but does not support snapshots (qemu) info snapshots -no block device can store vmstate for snapshot +Error: no block device can store vmstate for snapshot (qemu) loadvm snap0 Error: Device 'none0' is writable but does not support snapshots (qemu) quit @@ -58,7 +58,7 @@ QEMU X.Y.Z monitor - type 'help' for more information (qemu) savevm snap0 Error: Device 'virtio0' is writable but does not support snapshots (qemu) info snapshots -no block device can store vmstate for snapshot +Error: no block device can store vmstate for snapshot (qemu) loadvm snap0 Error: Device 'virtio0' is writable but does not support snapshots (qemu) quit @@ -83,7 +83,7 @@ QEMU X.Y.Z monitor - type 'help' for more information (qemu) savevm snap0 Error: Device 'file' is writable but does not support snapshots (qemu) info snapshots -no block device can store vmstate for snapshot +Error: no block device can store vmstate for snapshot (qemu) loadvm snap0 Error: Device 'file' is writable but does not support snapshots (qemu) quit From ad97769e9dcf4dbdaae6d859176e5f37fd6a7c66 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Mon, 13 Oct 2025 13:41:19 +0100 Subject: [PATCH 05/18] block/curl.c: Fix CURLOPT_VERBOSE parameter type In commit ed26056d90 ("block/curl.c: Use explicit long constants in curl_easy_setopt calls") we missed a further call that takes a long parameter. Reported-by: Kevin Wolf Signed-off-by: Richard W.M. Jones Message-ID: <20251013124127.604401-1-rjones@redhat.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block/curl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/curl.c b/block/curl.c index 68cf83ce55..d7d93d967f 100644 --- a/block/curl.c +++ b/block/curl.c @@ -524,7 +524,7 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state) #endif #ifdef DEBUG_VERBOSE - if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1)) { + if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1L)) { goto err; } #endif From 5e5aebd848a0daa69fe2b478e376ab2ddf9d9a32 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Mon, 13 Oct 2025 16:36:10 -0500 Subject: [PATCH 06/18] iotests: Adjust nbd expected outputs to match current behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'git bisect' confirms that the NBD iotests 94 and 119 have been broken since commit effd60c8 in v9.0.0; but as Dan Berrange's efforts to improve CI have proven, we haven't been reliably running them to notice. The change was good (moving coroutine commands to run in the right context), but it meant that "execute":"quit" now waits to complete until the coroutines tearing down NBD have first reported the SHUTDOWN event, in the opposite order of what happened pre-patch. Signed-off-by: Eric Blake Fixes: effd60c8 ("monitor: only run coroutine commands in qemu_aio_context", v9.0.0) Reported-by: Daniel P. Berrangé Message-ID: <20251013213638.494193-2-eblake@redhat.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/094.out | 2 +- tests/qemu-iotests/119.out | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/qemu-iotests/094.out b/tests/qemu-iotests/094.out index 97f894cf8f..9178474e79 100644 --- a/tests/qemu-iotests/094.out +++ b/tests/qemu-iotests/094.out @@ -23,6 +23,6 @@ Formatting 'TEST_DIR/source.IMGFMT', fmt=IMGFMT size=67108864 {'execute': 'quit'} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "src"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "src"}} -{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} +{"return": {}} *** done diff --git a/tests/qemu-iotests/119.out b/tests/qemu-iotests/119.out index 7b7f0f4bcc..45f82a4faa 100644 --- a/tests/qemu-iotests/119.out +++ b/tests/qemu-iotests/119.out @@ -5,7 +5,7 @@ QMP_VERSION read 65536/65536 bytes at offset 0 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": ""} -{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} +{"return": {}} *** done From 3ab02c857047c16e622a7ad2adb51ef21e24989e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Tue, 21 Oct 2025 15:58:40 -0500 Subject: [PATCH 07/18] iotests: Adjust fuse-allow-other expected output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iotest fuse-allow-other has been broken since commit effd60c8 in v9.0.0; but as Dan Berrange's efforts to improve CI have proven, we haven't been reliably running it to notice. The change in that commit was good (moving coroutine commands to run in the right context), but it meant that "execute":"quit" now waits to complete until the coroutines tearing down fuse have first reported the SHUTDOWN event, in the opposite order of what happened pre-patch. Signed-off-by: Eric Blake Fixes: effd60c8 ("monitor: only run coroutine commands in qemu_aio_context", Reported-by: Daniel P. Berrangé Message-ID: <20251021205843.2585624-2-eblake@redhat.com> Reviewed-by: Daniel P. Berrangé Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/tests/fuse-allow-other.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/qemu-iotests/tests/fuse-allow-other.out b/tests/qemu-iotests/tests/fuse-allow-other.out index 543fa52a06..3219fc35e0 100644 --- a/tests/qemu-iotests/tests/fuse-allow-other.out +++ b/tests/qemu-iotests/tests/fuse-allow-other.out @@ -28,9 +28,9 @@ stat: cannot statx 'fuse-export': Permission denied cat: fuse-export: Permission denied stat: cannot statx 'fuse-export': Permission denied {'execute': 'quit'} -{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} +{"return": {}} --- allow-other=on --- {'execute': 'qmp_capabilities'} @@ -55,9 +55,9 @@ Permissions seen by nobody: 444 cat: fuse-export: Permission denied Permissions seen by nobody: 440 {'execute': 'quit'} -{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} +{"return": {}} --- allow-other=auto --- {'execute': 'qmp_capabilities'} @@ -82,7 +82,7 @@ Permissions seen by nobody: 444 cat: fuse-export: Permission denied Permissions seen by nobody: 440 {'execute': 'quit'} -{"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false, "reason": "host-qmp-quit"}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_EXPORT_DELETED", "data": {"id": "export"}} +{"return": {}} *** done From 9f0c763e16c65d3aab5f3f17768df58321874685 Mon Sep 17 00:00:00 2001 From: Chandan Somani Date: Fri, 3 Oct 2025 14:59:26 -0700 Subject: [PATCH 08/18] block: enable stats-intervals for storage devices This patch allows stats-intervals to be used for storage devices with the -device option. It accepts a list of interval lengths in JSON format. It configures and collects the stats in the BlockBackend layer through the storage device that consumes the BlockBackend. Signed-off-by: Chandan Somani Message-ID: <20251003220039.1336663-1-csomani@redhat.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block/accounting.c | 17 +++++++++++++++-- blockdev.c | 3 ++- hw/block/block.c | 7 +++++-- include/block/accounting.h | 5 +++-- include/hw/block/block.h | 7 ++++++- tests/qemu-iotests/172.out | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 8 deletions(-) diff --git a/block/accounting.c b/block/accounting.c index 3e46159569..0933c61f3a 100644 --- a/block/accounting.c +++ b/block/accounting.c @@ -28,6 +28,7 @@ #include "block/block_int.h" #include "qemu/timer.h" #include "system/qtest.h" +#include "qapi/error.h" static QEMUClockType clock_type = QEMU_CLOCK_REALTIME; static const int qtest_latency_ns = NANOSECONDS_PER_SECOND / 1000; @@ -56,13 +57,25 @@ static bool bool_from_onoffauto(OnOffAuto val, bool def) } } -void block_acct_setup(BlockAcctStats *stats, enum OnOffAuto account_invalid, - enum OnOffAuto account_failed) +bool block_acct_setup(BlockAcctStats *stats, enum OnOffAuto account_invalid, + enum OnOffAuto account_failed, uint32_t *stats_intervals, + uint32_t num_stats_intervals, Error **errp) { stats->account_invalid = bool_from_onoffauto(account_invalid, stats->account_invalid); stats->account_failed = bool_from_onoffauto(account_failed, stats->account_failed); + if (stats_intervals) { + for (int i = 0; i < num_stats_intervals; i++) { + if (stats_intervals[i] <= 0) { + error_setg(errp, "Invalid interval length: %u", stats_intervals[i]); + return false; + } + block_acct_add_interval(stats, stats_intervals[i]); + } + g_free(stats_intervals); + } + return true; } void block_acct_cleanup(BlockAcctStats *stats) diff --git a/blockdev.c b/blockdev.c index b451fee6e1..dbd1d4d3e8 100644 --- a/blockdev.c +++ b/blockdev.c @@ -617,7 +617,8 @@ static BlockBackend *blockdev_init(const char *file, QDict *bs_opts, bs->detect_zeroes = detect_zeroes; - block_acct_setup(blk_get_stats(blk), account_invalid, account_failed); + block_acct_setup(blk_get_stats(blk), account_invalid, account_failed, + NULL, 0, NULL); if (!parse_stats_intervals(blk_get_stats(blk), interval_list, errp)) { blk_unref(blk); diff --git a/hw/block/block.c b/hw/block/block.c index 2e10611d95..f187fa025d 100644 --- a/hw/block/block.c +++ b/hw/block/block.c @@ -249,8 +249,11 @@ bool blkconf_apply_backend_options(BlockConf *conf, bool readonly, blk_set_enable_write_cache(blk, wce); blk_set_on_error(blk, rerror, werror); - block_acct_setup(blk_get_stats(blk), conf->account_invalid, - conf->account_failed); + if (!block_acct_setup(blk_get_stats(blk), conf->account_invalid, + conf->account_failed, conf->stats_intervals, + conf->num_stats_intervals, errp)) { + return false; + } return true; } diff --git a/include/block/accounting.h b/include/block/accounting.h index a59e39f49d..b1cf417b57 100644 --- a/include/block/accounting.h +++ b/include/block/accounting.h @@ -101,8 +101,9 @@ typedef struct BlockAcctCookie { } BlockAcctCookie; void block_acct_init(BlockAcctStats *stats); -void block_acct_setup(BlockAcctStats *stats, enum OnOffAuto account_invalid, - enum OnOffAuto account_failed); +bool block_acct_setup(BlockAcctStats *stats, enum OnOffAuto account_invalid, + enum OnOffAuto account_failed, uint32_t *stats_intervals, + uint32_t num_stats_intervals, Error **errp); void block_acct_cleanup(BlockAcctStats *stats); void block_acct_add_interval(BlockAcctStats *stats, unsigned interval_length); BlockAcctTimedStats *block_acct_interval_next(BlockAcctStats *stats, diff --git a/include/hw/block/block.h b/include/hw/block/block.h index de3946a5f1..b4d914624e 100644 --- a/include/hw/block/block.h +++ b/include/hw/block/block.h @@ -34,6 +34,8 @@ typedef struct BlockConf { OnOffAuto account_invalid, account_failed; BlockdevOnError rerror; BlockdevOnError werror; + uint32_t num_stats_intervals; + uint32_t *stats_intervals; } BlockConf; static inline unsigned int get_physical_block_exp(BlockConf *conf) @@ -66,7 +68,10 @@ static inline unsigned int get_physical_block_exp(BlockConf *conf) DEFINE_PROP_ON_OFF_AUTO("account-invalid", _state, \ _conf.account_invalid, ON_OFF_AUTO_AUTO), \ DEFINE_PROP_ON_OFF_AUTO("account-failed", _state, \ - _conf.account_failed, ON_OFF_AUTO_AUTO) + _conf.account_failed, ON_OFF_AUTO_AUTO), \ + DEFINE_PROP_ARRAY("stats-intervals", _state, \ + _conf.num_stats_intervals, _conf.stats_intervals, \ + qdev_prop_uint32, uint32_t) #define DEFINE_BLOCK_PROPERTIES(_state, _conf) \ DEFINE_PROP_DRIVE("drive", _state, _conf.blk), \ diff --git a/tests/qemu-iotests/172.out b/tests/qemu-iotests/172.out index 146fc72388..a023cd407d 100644 --- a/tests/qemu-iotests/172.out +++ b/tests/qemu-iotests/172.out @@ -30,6 +30,7 @@ Testing: share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" @@ -59,6 +60,7 @@ Testing: -fda TEST_DIR/t.qcow2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -95,6 +97,7 @@ Testing: -fdb TEST_DIR/t.qcow2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -109,6 +112,7 @@ Testing: -fdb TEST_DIR/t.qcow2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" floppy1 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -149,6 +153,7 @@ Testing: -fda TEST_DIR/t.qcow2 -fdb TEST_DIR/t.qcow2.2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -163,6 +168,7 @@ Testing: -fda TEST_DIR/t.qcow2 -fdb TEST_DIR/t.qcow2.2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -204,6 +210,7 @@ Testing: -fdb share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" dev: floppy, id "" unit = 0 (0x0) @@ -218,6 +225,7 @@ Testing: -fdb share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" @@ -247,6 +255,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -283,6 +292,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2,index=1 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -297,6 +307,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2,index=1 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" floppy1 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -337,6 +348,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=floppy,file=TEST_DIR/t share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -351,6 +363,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=floppy,file=TEST_DIR/t share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -395,6 +408,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -431,6 +445,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0,unit=1 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -467,6 +482,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qco share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -481,6 +497,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qco share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -531,6 +548,7 @@ Testing: -fda TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -545,6 +563,7 @@ Testing: -fda TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -586,6 +605,7 @@ Testing: -fda TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -600,6 +620,7 @@ Testing: -fda TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -641,6 +662,7 @@ Testing: -fdb TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 1 (0x1) @@ -655,6 +677,7 @@ Testing: -fdb TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy1 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -696,6 +719,7 @@ Testing: -fdb TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 1 (0x1) @@ -710,6 +734,7 @@ Testing: -fdb TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.qcow2.2 -device fl share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy1 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -760,6 +785,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.q share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -774,6 +800,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.q share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -815,6 +842,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.q share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" dev: floppy, id "" unit = 0 (0x0) @@ -829,6 +857,7 @@ Testing: -drive if=floppy,file=TEST_DIR/t.qcow2 -drive if=none,file=TEST_DIR/t.q share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" floppy0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/unattached/device[N] @@ -876,6 +905,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -global floppy.drive=none0 -device share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -942,6 +972,7 @@ Testing: -device floppy share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" Testing: -device floppy,drive-type=120 @@ -968,6 +999,7 @@ Testing: -device floppy,drive-type=120 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "120" Testing: -device floppy,drive-type=144 @@ -994,6 +1026,7 @@ Testing: -device floppy,drive-type=144 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" Testing: -device floppy,drive-type=288 @@ -1020,6 +1053,7 @@ Testing: -device floppy,drive-type=288 share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" @@ -1049,6 +1083,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0,drive-t share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "120" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -1085,6 +1120,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0,drive-t share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "288" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -1124,6 +1160,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0,logical share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] @@ -1160,6 +1197,7 @@ Testing: -drive if=none,file=TEST_DIR/t.qcow2 -device floppy,drive=none0,physica share-rw = false account-invalid = "auto" account-failed = "auto" + stats-intervals = drive-type = "144" none0 (NODE_NAME): TEST_DIR/t.qcow2 (qcow2) Attached to: /machine/peripheral-anon/device[N] From aa981ab3a371c1100f8efaddbc319126ecdc3dd0 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 2 Oct 2025 13:54:46 +0100 Subject: [PATCH 09/18] MAINTAINERS: Mark VHDX block driver as "Odd Fixes" In 2018 (in commit 5f5246b6b) Jeff Cody stepped down as block maintainer, but left himself as maintainer for VHDX and with a status of "Supported", with the rationale: For VHDX, added my personal email address as a maintainer, as I can answer questions or send the occassional bug fix. Leaving it as 'Supported', instead of 'Odd Fixes', because I think the rest of the block layer maintainers and developers will upkeep it as well, if needed. However, today the way we treat subsystems which are only maintained under the general umbrella of a wider system is usually to mark them as "Odd Fixes". The vhdx.c code has had no commits which aren't a part of more general refactoring changes since 2020, and Jeff himself hasn't been active on qemu-devel since 2018, so this seems also to be how we've handled the code in practice. Signed-off-by: Peter Maydell Message-ID: <20251002125446.2500179-1-peter.maydell@linaro.org> Signed-off-by: Kevin Wolf --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 64491c800c..fd78a563a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4081,7 +4081,7 @@ F: block/rbd.c VHDX M: Jeff Cody L: qemu-block@nongnu.org -S: Supported +S: Odd Fixes F: block/vhdx* VDI From 41203754207ce78b2024043c457f66a833b723b9 Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Wed, 17 Sep 2025 13:54:48 +0200 Subject: [PATCH 10/18] include/block/block_int-common: document when resize callback is used The 'resize' callback is only called by bdrv_parent_cb_resize() which is only called by bdrv_co_write_req_finish() to notify the parent(s) that the child was resized. Signed-off-by: Fiona Ebner Reviewed-by: Hanna Czenczek Message-ID: <20250917115509.401015-2-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- include/block/block_int-common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index 034c0634c8..8a3d427356 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -1020,6 +1020,9 @@ struct BdrvChildClass { * the I/O API. */ + /* + * Notifies the parent that the child was resized. + */ void (*resize)(BdrvChild *child); /* From 08736e7584c41b75e734f412e1fea5ae949afac1 Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Wed, 17 Sep 2025 13:54:49 +0200 Subject: [PATCH 11/18] block: make bdrv_co_parent_cb_resize() a proper IO API function In preparation for calling it via the bdrv_child_cb_resize() callback that will be added by the next commit. Rename it to include the "_co_" part while at it. Signed-off-by: Fiona Ebner Reviewed-by: Hanna Czenczek Message-ID: <20250917115509.401015-3-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block/io.c | 9 +++------ include/block/block_int-io.h | 6 ++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/block/io.c b/block/io.c index 9bd8ba8431..928c02d1ad 100644 --- a/block/io.c +++ b/block/io.c @@ -46,9 +46,6 @@ /* Maximum read size for checking if data reads as zero, in bytes */ #define MAX_ZERO_CHECK_BUFFER (128 * KiB) -static void coroutine_fn GRAPH_RDLOCK -bdrv_parent_cb_resize(BlockDriverState *bs); - static int coroutine_fn bdrv_co_do_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int64_t bytes, BdrvRequestFlags flags); @@ -2038,7 +2035,7 @@ bdrv_co_write_req_finish(BdrvChild *child, int64_t offset, int64_t bytes, end_sector > bs->total_sectors) && req->type != BDRV_TRACKED_DISCARD) { bs->total_sectors = end_sector; - bdrv_parent_cb_resize(bs); + bdrv_co_parent_cb_resize(bs); bdrv_dirty_bitmap_truncate(bs, end_sector << BDRV_SECTOR_BITS); } if (req->bytes) { @@ -3570,11 +3567,11 @@ int coroutine_fn bdrv_co_copy_range(BdrvChild *src, int64_t src_offset, bytes, read_flags, write_flags); } -static void coroutine_fn GRAPH_RDLOCK -bdrv_parent_cb_resize(BlockDriverState *bs) +void coroutine_fn bdrv_co_parent_cb_resize(BlockDriverState *bs) { BdrvChild *c; + IO_CODE(); assert_bdrv_graph_readable(); QLIST_FOREACH(c, &bs->parents, next_parent) { diff --git a/include/block/block_int-io.h b/include/block/block_int-io.h index 4f94eb3c5a..ed8b5657d6 100644 --- a/include/block/block_int-io.h +++ b/include/block/block_int-io.h @@ -191,4 +191,10 @@ void bdrv_bsc_invalidate_range(BlockDriverState *bs, */ void bdrv_bsc_fill(BlockDriverState *bs, int64_t offset, int64_t bytes); +/* + * Notify all parents that the size of the child changed. + */ +void coroutine_fn GRAPH_RDLOCK +bdrv_co_parent_cb_resize(BlockDriverState *bs); + #endif /* BLOCK_INT_IO_H */ From cbadaf57a775e84ff10455108afd6e191c5c0630 Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Wed, 17 Sep 2025 13:54:50 +0200 Subject: [PATCH 12/18] block: implement 'resize' callback for child_of_bds class If a filtered child is resized, the size of the parent node is now also refreshed (recursively for chains of filtered children). For filter block drivers that do not implement .bdrv_co_getlength(), this commit does not change the current behavior, because bdrv_co_refresh_total_sectors() will used the current size via the passed-in hint. This is the case for block drivers for (some) block jobs, as well as copy-before-write. Block jobs already set up a blocker preventing a QMP block_resize operation while the job is running. That does not directly cover an associated 'file' node of a 'raw' node, but resizing such a 'file' node is already prevented too (backup, commit, mirror and stream were checked). The other case is copy-before-write. This commit does not change the fact that the copy-before-write node still has the same size after its filtered child is resized. Block drivers that do implement .bdrv_co_getlength() and where .is_filter is true, already returned the length of the file child, so there is no change before and after this commit, with two exceptions: 1. preallocate can return an early data_end and otherwise queries the file child, but that special casing is not changed. 2. blkverify returns the length of the test file. This commit does not affect that behavior. Signed-off-by: Fiona Ebner Message-ID: <20250917115509.401015-4-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- block.c | 12 ++++++++++++ include/block/block_int-common.h | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/block.c b/block.c index 8848e9a7ed..cf08e64add 100644 --- a/block.c +++ b/block.c @@ -1497,6 +1497,17 @@ static void GRAPH_WRLOCK bdrv_child_cb_detach(BdrvChild *child) } } +static void coroutine_fn GRAPH_RDLOCK bdrv_child_cb_resize(BdrvChild *child) +{ + BlockDriverState *bs = child->opaque; + + if (child->role & BDRV_CHILD_FILTERED) { + /* Best effort, ignore errors. */ + bdrv_co_refresh_total_sectors(bs, bs->total_sectors); + bdrv_co_parent_cb_resize(bs); + } +} + static int bdrv_child_cb_update_filename(BdrvChild *c, BlockDriverState *base, const char *filename, bool backing_mask_protocol, @@ -1529,6 +1540,7 @@ const BdrvChildClass child_of_bds = { .detach = bdrv_child_cb_detach, .inactivate = bdrv_child_cb_inactivate, .change_aio_ctx = bdrv_child_cb_change_aio_ctx, + .resize = bdrv_child_cb_resize, .update_filename = bdrv_child_cb_update_filename, .get_parent_aio_context = child_of_bds_get_parent_aio_context, }; diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index 8a3d427356..c55b35da8e 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -1023,7 +1023,7 @@ struct BdrvChildClass { /* * Notifies the parent that the child was resized. */ - void (*resize)(BdrvChild *child); + void GRAPH_RDLOCK_PTR (*resize)(BdrvChild *child); /* * Returns a name that is supposedly more useful for human users than the From 4eb4365b9fbd564aa8ef40b1cbd4f0e2076b4d6d Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Wed, 17 Sep 2025 13:54:51 +0200 Subject: [PATCH 13/18] iotests: add test for resizing a node below filters Signed-off-by: Fiona Ebner Reviewed-by: Hanna Czenczek Message-ID: <20250917115509.401015-5-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/tests/resize-below-filter | 73 +++++++++++++++++++ .../tests/resize-below-filter.out | 5 ++ 2 files changed, 78 insertions(+) create mode 100755 tests/qemu-iotests/tests/resize-below-filter create mode 100644 tests/qemu-iotests/tests/resize-below-filter.out diff --git a/tests/qemu-iotests/tests/resize-below-filter b/tests/qemu-iotests/tests/resize-below-filter new file mode 100755 index 0000000000..f55619cf34 --- /dev/null +++ b/tests/qemu-iotests/tests/resize-below-filter @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# group: rw quick +# +# Test what happens when a node below filter nodes is resized. +# +# Copyright (C) Proxmox Server Solutions GmbH +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import iotests +from iotests import imgfmt, qemu_img_create, QMPTestCase + +image_size = 1 * 1024 * 1024 +image = os.path.join(iotests.test_dir, 'test.img') + +class TestResizeBelowFilter(QMPTestCase): + def setUp(self) -> None: + qemu_img_create('-f', imgfmt, image, str(image_size)) + + self.vm = iotests.VM() + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': imgfmt, + 'node-name': 'node0', + 'file': { + 'driver': 'file', + 'filename': image, + } + })) + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': 'compress', + 'node-name': 'comp0', + 'file': 'node0', + })) + self.vm.add_object('throttle-group,id=thrgr0') + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': 'throttle', + 'node-name': 'thr0', + 'throttle-group': 'thrgr0', + 'file': 'comp0', + })) + self.vm.add_object('throttle-group,id=thrgr1') + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': 'throttle', + 'node-name': 'thr1', + 'throttle-group': 'thrgr1', + 'file': 'node0', + })) + self.vm.launch() + + def tearDown(self) -> None: + self.vm.shutdown() + os.remove(image) + + def assert_size(self, size: int) -> None: + nodes = self.vm.qmp('query-named-block-nodes', flat=True)['return'] + self.assertEqual(len(nodes), 5) + for node in nodes: + if node['drv'] == 'file': + continue + self.assertEqual(node['image']['virtual-size'], size) + + def test_resize_below_filter(self) -> None: + self.assert_size(image_size) + self.vm.qmp('block_resize', node_name='thr0', size=2*image_size) + self.assert_size(2*image_size) + self.vm.qmp('block_resize', node_name='comp0', size=3*image_size) + self.assert_size(3*image_size) + self.vm.qmp('block_resize', node_name='node0', size=4*image_size) + self.assert_size(4*image_size) + +if __name__ == '__main__': + iotests.main(supported_fmts=['qcow2'], supported_protocols=['file']) diff --git a/tests/qemu-iotests/tests/resize-below-filter.out b/tests/qemu-iotests/tests/resize-below-filter.out new file mode 100644 index 0000000000..ae1213e6f8 --- /dev/null +++ b/tests/qemu-iotests/tests/resize-below-filter.out @@ -0,0 +1,5 @@ +. +---------------------------------------------------------------------- +Ran 1 tests + +OK From 59e231f3f03f6db5506312e3e7659480a752eba3 Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Wed, 17 Sep 2025 13:54:52 +0200 Subject: [PATCH 14/18] iotests: add test for resizing a 'file' node below a 'raw' node Signed-off-by: Fiona Ebner Message-ID: <20250917115509.401015-6-f.ebner@proxmox.com> Reviewed-by: Kevin Wolf Signed-off-by: Kevin Wolf --- tests/qemu-iotests/tests/resize-below-raw | 53 +++++++++++++++++++ tests/qemu-iotests/tests/resize-below-raw.out | 5 ++ 2 files changed, 58 insertions(+) create mode 100755 tests/qemu-iotests/tests/resize-below-raw create mode 100644 tests/qemu-iotests/tests/resize-below-raw.out diff --git a/tests/qemu-iotests/tests/resize-below-raw b/tests/qemu-iotests/tests/resize-below-raw new file mode 100755 index 0000000000..3c9241c918 --- /dev/null +++ b/tests/qemu-iotests/tests/resize-below-raw @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# group: rw quick +# +# Test what happens when a 'file' node below a 'raw' node is resized. +# +# Copyright (C) Proxmox Server Solutions GmbH +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import iotests +from iotests import imgfmt, qemu_img_create, QMPTestCase + +image_size = 1 * 1024 * 1024 +image = os.path.join(iotests.test_dir, 'test.img') + +class TestResizeBelowRaw(QMPTestCase): + def setUp(self) -> None: + qemu_img_create('-f', imgfmt, image, str(image_size)) + + self.vm = iotests.VM() + self.vm.add_blockdev(self.vm.qmp_to_opts({ + 'driver': imgfmt, + 'node-name': 'node0', + 'file': { + 'driver': 'file', + 'filename': image, + 'node-name': 'file0', + } + })) + self.vm.launch() + + def tearDown(self) -> None: + self.vm.shutdown() + os.remove(image) + + def assert_size(self, size: int) -> None: + nodes = self.vm.qmp('query-named-block-nodes', flat=True)['return'] + self.assertEqual(len(nodes), 2) + for node in nodes: + if node['drv'] == 'file': + continue + self.assertEqual(node['image']['virtual-size'], size) + + def test_resize_below_raw(self) -> None: + self.assert_size(image_size) + self.vm.qmp('block_resize', node_name='file0', size=2*image_size) + self.assert_size(2*image_size) + self.vm.qmp('block_resize', node_name='node0', size=3*image_size) + self.assert_size(3*image_size) + +if __name__ == '__main__': + iotests.main(supported_fmts=['raw'], supported_protocols=['file']) diff --git a/tests/qemu-iotests/tests/resize-below-raw.out b/tests/qemu-iotests/tests/resize-below-raw.out new file mode 100644 index 0000000000..ae1213e6f8 --- /dev/null +++ b/tests/qemu-iotests/tests/resize-below-raw.out @@ -0,0 +1,5 @@ +. +---------------------------------------------------------------------- +Ran 1 tests + +OK From 46dd683d56b1328cb2bc923914bfd7ac590064f7 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 24 Oct 2025 14:30:37 +0200 Subject: [PATCH 15/18] block: Improve comments in BlockLimits Patches to expose the limits in QAPI have made clear that the existing documentation of BlockLimits could be improved: The meaning of min_mem_alignment and opt_mem_alignment could be clearer, and talking about better alignment values isn't helpful when we only detect these values and never choose them. Make the changes in the BlockLimits documentation now, so that the patches exposing the fields in QAPI can use descriptions consistent with it. Signed-off-by: Kevin Wolf Message-ID: <20251024123041.51254-2-kwolf@redhat.com> Reviewed-by: Eric Blake Signed-off-by: Kevin Wolf --- include/block/block_int-common.h | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h index c55b35da8e..f2a4e863fc 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -817,10 +817,10 @@ typedef struct BlockLimits { int64_t max_pdiscard; /* - * Optimal alignment for discard requests in bytes. A power of 2 - * is best but not mandatory. Must be a multiple of - * bl.request_alignment, and must be less than max_pdiscard if - * that is set. May be 0 if bl.request_alignment is good enough + * Optimal alignment for discard requests in bytes. Note that this doesn't + * have to be a power of two. Must be a multiple of bl.request_alignment, + * and must be less than max_pdiscard if that is set. May be 0 if + * bl.request_alignment is good enough. */ uint32_t pdiscard_alignment; @@ -831,11 +831,10 @@ typedef struct BlockLimits { int64_t max_pwrite_zeroes; /* - * Optimal alignment for write zeroes requests in bytes. A power - * of 2 is best but not mandatory. Must be a multiple of - * bl.request_alignment, and must be less than max_pwrite_zeroes - * if that is set. May be 0 if bl.request_alignment is good - * enough + * Optimal alignment for write zeroes requests in bytes. Note that this + * doesn't have to be a power of two. Must be a multiple of + * bl.request_alignment, and must be less than max_pwrite_zeroes if that is + * set. May be 0 if bl.request_alignment is good enough. */ uint32_t pwrite_zeroes_alignment; @@ -863,18 +862,23 @@ typedef struct BlockLimits { uint64_t max_hw_transfer; /* - * Maximal number of scatter/gather elements allowed by the hardware. + * Maximum number of scatter/gather elements allowed by the hardware. * Applies whenever transfers to the device bypass the kernel I/O * scheduler, for example with SG_IO. If larger than max_iov * or if zero, blk_get_max_hw_iov will fall back to max_iov. */ int max_hw_iov; - - /* memory alignment, in bytes so that no bounce buffer is needed */ + /* + * Minimal required memory alignment in bytes for zero-copy I/O to succeed. + * For unaligned requests, a bounce buffer will be used. + */ size_t min_mem_alignment; - /* memory alignment, in bytes, for bounce buffer */ + /* + * Optimal memory alignment in bytes. This is the alignment used for any + * buffer allocations QEMU performs internally. + */ size_t opt_mem_alignment; /* maximum number of iovec elements */ From d2634e18286a5772f04cc724e64f8b16a2124587 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 24 Oct 2025 14:30:38 +0200 Subject: [PATCH 16/18] block: Expose block limits for images in QMP This information can be useful both for debugging and for management tools trying to configure guest devices with the optimal limits (possibly across multiple hosts). There is no reason not to make it available, so just add it to BlockNodeInfo. Signed-off-by: Kevin Wolf Reviewed-by: Eric Blake Reviewed-by: Hanna Czenczek Message-ID: <20251024123041.51254-3-kwolf@redhat.com> Signed-off-by: Kevin Wolf --- block/qapi.c | 34 ++++++++++++++-- qapi/block-core.json | 66 ++++++++++++++++++++++++++++++++ tests/qemu-iotests/184 | 5 ++- tests/qemu-iotests/184.out | 8 ---- tests/qemu-iotests/common.filter | 3 +- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/block/qapi.c b/block/qapi.c index 12fbf8d1b7..54521d0a68 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -235,7 +235,8 @@ int bdrv_query_snapshot_info_list(BlockDriverState *bs, * in @info, setting @errp on error. */ static void GRAPH_RDLOCK -bdrv_do_query_node_info(BlockDriverState *bs, BlockNodeInfo *info, Error **errp) +bdrv_do_query_node_info(BlockDriverState *bs, BlockNodeInfo *info, bool limits, + Error **errp) { int64_t size; const char *backing_filename; @@ -269,6 +270,33 @@ bdrv_do_query_node_info(BlockDriverState *bs, BlockNodeInfo *info, Error **errp) info->dirty_flag = bdi.is_dirty; info->has_dirty_flag = true; } + + if (limits) { + info->limits = g_new(BlockLimitsInfo, 1); + *info->limits = (BlockLimitsInfo) { + .request_alignment = bs->bl.request_alignment, + .has_max_discard = bs->bl.max_pdiscard != 0, + .max_discard = bs->bl.max_pdiscard, + .has_discard_alignment = bs->bl.pdiscard_alignment != 0, + .discard_alignment = bs->bl.pdiscard_alignment, + .has_max_write_zeroes = bs->bl.max_pwrite_zeroes != 0, + .max_write_zeroes = bs->bl.max_pwrite_zeroes, + .has_write_zeroes_alignment = bs->bl.pwrite_zeroes_alignment != 0, + .write_zeroes_alignment = bs->bl.pwrite_zeroes_alignment, + .has_opt_transfer = bs->bl.opt_transfer != 0, + .opt_transfer = bs->bl.opt_transfer, + .has_max_transfer = bs->bl.max_transfer != 0, + .max_transfer = bs->bl.max_transfer, + .has_max_hw_transfer = bs->bl.max_hw_transfer != 0, + .max_hw_transfer = bs->bl.max_hw_transfer, + .max_iov = bs->bl.max_iov, + .has_max_hw_iov = bs->bl.max_hw_iov != 0, + .max_hw_iov = bs->bl.max_hw_iov, + .min_mem_alignment = bs->bl.min_mem_alignment, + .opt_mem_alignment = bs->bl.opt_mem_alignment, + }; + } + info->format_specific = bdrv_get_specific_info(bs, &err); if (err) { error_propagate(errp, err); @@ -343,7 +371,7 @@ void bdrv_query_image_info(BlockDriverState *bs, ImageInfo *info; info = g_new0(ImageInfo, 1); - bdrv_do_query_node_info(bs, qapi_ImageInfo_base(info), errp); + bdrv_do_query_node_info(bs, qapi_ImageInfo_base(info), true, errp); if (*errp) { goto fail; } @@ -397,7 +425,7 @@ void bdrv_query_block_graph_info(BlockDriverState *bs, BdrvChild *c; info = g_new0(BlockGraphInfo, 1); - bdrv_do_query_node_info(bs, qapi_BlockGraphInfo_base(info), errp); + bdrv_do_query_node_info(bs, qapi_BlockGraphInfo_base(info), false, errp); if (*errp) { goto fail; } diff --git a/qapi/block-core.json b/qapi/block-core.json index dc6eb4ae23..2c037183f0 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -275,6 +275,69 @@ 'file': 'ImageInfoSpecificFileWrapper' } } +## +# @BlockLimitsInfo: +# +# @request-alignment: Alignment requirement, in bytes, for +# offset/length of I/O requests. +# +# @max-discard: Maximum number of bytes that can be discarded at once. +# If not present, there is no specific maximum. +# +# @discard-alignment: Optimal alignment for discard requests in bytes. +# Note that this doesn't have to be a power of two. If not +# present, discards don't have a alignment requirement different +# from @request-alignment. +# +# @max-write-zeroes: Maximum number of bytes that can be zeroed out at +# once. If not present, there is no specific maximum. +# +# @write-zeroes-alignment: Optimal alignment for write zeroes requests +# in bytes. Note that this doesn't have to be a power of two. If +# not present, write_zeroes doesn't have a alignment requirement +# different from @request-alignment. +# +# @opt-transfer: Optimal transfer length in bytes. If not present, +# there is no preferred size. +# +# @max-transfer: Maximal transfer length in bytes. If not present, +# there is no specific maximum. +# +# @max-hw-transfer: Maximal hardware transfer length in bytes. +# Applies whenever transfers to the device bypass the kernel I/O +# scheduler, for example with SG_IO. If not present, there is no +# specific maximum. +# +# @max-iov: Maximum number of scatter/gather elements +# +# @max-hw-iov: Maximum number of scatter/gather elements allowed by +# the hardware. Applies whenever transfers to the device bypass +# the kernel I/O scheduler, for example with SG_IO. If not +# present, the hardware limits is unknown and @max-iov is always +# used. +# +# @min-mem-alignment: Minimal required memory alignment in bytes for +# zero-copy I/O to succeed. For unaligned requests, a bounce +# buffer will be used. +# +# @opt-mem-alignment: Optimal memory alignment in bytes. This is the +# alignment used for any buffer allocations QEMU performs +# internally. +## +{ 'struct': 'BlockLimitsInfo', + 'data': { 'request-alignment': 'uint32', + '*max-discard': 'uint64', + '*discard-alignment': 'uint32', + '*max-write-zeroes': 'uint64', + '*write-zeroes-alignment': 'uint32', + '*opt-transfer': 'uint32', + '*max-transfer': 'uint32', + '*max-hw-transfer': 'uint32', + 'max-iov': 'int', + '*max-hw-iov': 'int', + 'min-mem-alignment': 'size', + 'opt-mem-alignment': 'size' } } + ## # @BlockNodeInfo: # @@ -304,6 +367,8 @@ # # @snapshots: list of VM snapshots # +# @limits: block limits that are used for I/O on the node (Since 10.2) +# # @format-specific: structure supplying additional format-specific # information (since 1.7) # @@ -315,6 +380,7 @@ '*cluster-size': 'int', '*encrypted': 'bool', '*compressed': 'bool', '*backing-filename': 'str', '*full-backing-filename': 'str', '*backing-filename-format': 'str', '*snapshots': ['SnapshotInfo'], + '*limits': 'BlockLimitsInfo', '*format-specific': 'ImageInfoSpecific' } } ## diff --git a/tests/qemu-iotests/184 b/tests/qemu-iotests/184 index e4cbcd8634..6d0afe9d38 100755 --- a/tests/qemu-iotests/184 +++ b/tests/qemu-iotests/184 @@ -45,8 +45,9 @@ do_run_qemu() run_qemu() { - do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp\ - | _filter_qemu_io | _filter_generated_node_ids + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qemu | _filter_qmp \ + | _filter_qemu_io | _filter_generated_node_ids \ + | _filter_img_info } test_throttle=$($QEMU_IMG --help|grep throttle) diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out index ef99bb2e9a..52692b6b3b 100644 --- a/tests/qemu-iotests/184.out +++ b/tests/qemu-iotests/184.out @@ -41,12 +41,6 @@ Testing: }, "iops_wr": 0, "ro": false, - "children": [ - { - "node-name": "disk0", - "child": "file" - } - ], "node-name": "throttle0", "backing_file_depth": 1, "drv": "throttle", @@ -75,8 +69,6 @@ Testing: }, "iops_wr": 0, "ro": false, - "children": [ - ], "node-name": "disk0", "backing_file_depth": 0, "drv": "null-co", diff --git a/tests/qemu-iotests/common.filter b/tests/qemu-iotests/common.filter index 511a55b1e8..26e6b45b04 100644 --- a/tests/qemu-iotests/common.filter +++ b/tests/qemu-iotests/common.filter @@ -229,6 +229,7 @@ _filter_img_info() discard=0 regex_json_spec_start='^ *"format-specific": \{' regex_json_child_start='^ *"children": \[' + regex_json_limit_start='^ *"limits": \{' gsed -e "s#$REMOTE_TEST_DIR#TEST_DIR#g" \ -e "s#$IMGPROTO:$TEST_DIR#TEST_DIR#g" \ -e "s#$TEST_DIR#TEST_DIR#g" \ @@ -261,7 +262,7 @@ _filter_img_info() discard=1 elif [[ $line =~ "Child node '/" ]]; then discard=1 - elif [[ $line =~ $regex_json_spec_start ]]; then + elif [[ $line =~ $regex_json_spec_start || $line =~ $regex_json_limit_start ]]; then discard=2 regex_json_end="^${line%%[^ ]*}\\},? *$" elif [[ $line =~ $regex_json_child_start ]]; then From 5b4b3bfdfc28d2398f34194d260d6eef9a9048b4 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 24 Oct 2025 14:30:39 +0200 Subject: [PATCH 17/18] qemu-img info: Optionally show block limits Add a new --limits option to 'qemu-img info' that displays the block limits for the image and all of its children, making the information more accessible for human users than in QMP. This option is not enabled by default because it can be a lot of output that isn't usually relevant if you're not specifically trying to diagnose some I/O problem. This makes the same information automatically also available in HMP 'info block -v'. Signed-off-by: Kevin Wolf Reviewed-by: Eric Blake Reviewed-by: Hanna Czenczek Message-ID: <20251024123041.51254-4-kwolf@redhat.com> Signed-off-by: Kevin Wolf --- block/qapi.c | 34 ++++++++++++++++++++++++++++++++-- docs/tools/qemu-img.rst | 6 +++++- include/block/qapi.h | 2 +- qemu-img-cmds.hx | 4 ++-- qemu-img.c | 15 ++++++++++++--- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/block/qapi.c b/block/qapi.c index 54521d0a68..9f5771e019 100644 --- a/block/qapi.c +++ b/block/qapi.c @@ -417,6 +417,7 @@ fail: */ void bdrv_query_block_graph_info(BlockDriverState *bs, BlockGraphInfo **p_info, + bool limits, Error **errp) { ERRP_GUARD(); @@ -425,7 +426,7 @@ void bdrv_query_block_graph_info(BlockDriverState *bs, BdrvChild *c; info = g_new0(BlockGraphInfo, 1); - bdrv_do_query_node_info(bs, qapi_BlockGraphInfo_base(info), false, errp); + bdrv_do_query_node_info(bs, qapi_BlockGraphInfo_base(info), limits, errp); if (*errp) { goto fail; } @@ -439,7 +440,7 @@ void bdrv_query_block_graph_info(BlockDriverState *bs, QAPI_LIST_APPEND(children_list_tail, c_info); c_info->name = g_strdup(c->name); - bdrv_query_block_graph_info(c->bs, &c_info->info, errp); + bdrv_query_block_graph_info(c->bs, &c_info->info, limits, errp); if (*errp) { goto fail; } @@ -936,6 +937,29 @@ void bdrv_image_info_specific_dump(ImageInfoSpecific *info_spec, visit_free(v); } +/** + * Dumps the given BlockLimitsInfo object in a human-readable form, + * prepending an optional prefix if the dump is not empty. + */ +static void bdrv_image_info_limits_dump(BlockLimitsInfo *limits, + const char *prefix, + int indentation) +{ + QObject *obj; + Visitor *v = qobject_output_visitor_new(&obj); + + visit_type_BlockLimitsInfo(v, NULL, &limits, &error_abort); + visit_complete(v, &obj); + if (!qobject_is_empty_dump(obj)) { + if (prefix) { + qemu_printf("%*s%s", indentation * 4, "", prefix); + } + dump_qobject(indentation + 1, obj); + } + qobject_unref(obj); + visit_free(v); +} + /** * Print the given @info object in human-readable form. Every field is indented * using the given @indentation (four spaces per indentation level). @@ -1011,6 +1035,12 @@ void bdrv_node_info_dump(BlockNodeInfo *info, int indentation, bool protocol) } } + if (info->limits) { + bdrv_image_info_limits_dump(info->limits, + "Block limits:\n", + indentation); + } + if (info->has_snapshots) { SnapshotInfoList *elem; diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst index 5e7b85079d..fdc9ea9cf2 100644 --- a/docs/tools/qemu-img.rst +++ b/docs/tools/qemu-img.rst @@ -503,7 +503,7 @@ Command description: The size syntax is similar to :manpage:`dd(1)`'s size syntax. -.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [-U] FILENAME +.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-U] FILENAME Give information about the disk image *FILENAME*. Use it in particular to know the size reserved on disk which can be different @@ -571,6 +571,10 @@ Command description: ``ImageInfoSpecific*`` QAPI object (e.g. ``ImageInfoSpecificQCow2`` for qcow2 images). + *Block limits* + The block limits for I/O that QEMU detected for the image. + This information is only shown if the ``--limits`` option was specified. + .. option:: map [--object OBJECTDEF] [--image-opts] [-f FMT] [--start-offset=OFFSET] [--max-length=LEN] [--output=OFMT] [-U] FILENAME Dump the metadata of image *FILENAME* and its backing file chain. diff --git a/include/block/qapi.h b/include/block/qapi.h index 54c48de26a..be554e53dc 100644 --- a/include/block/qapi.h +++ b/include/block/qapi.h @@ -42,7 +42,7 @@ bdrv_query_image_info(BlockDriverState *bs, ImageInfo **p_info, bool flat, bool skip_implicit_filters, Error **errp); void GRAPH_RDLOCK bdrv_query_block_graph_info(BlockDriverState *bs, BlockGraphInfo **p_info, - Error **errp); + bool limits, Error **errp); void bdrv_snapshot_dump(QEMUSnapshotInfo *sn); void bdrv_image_info_specific_dump(ImageInfoSpecific *info_spec, diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 2c5a8a28f9..74b66f9d42 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -66,9 +66,9 @@ SRST ERST DEF("info", img_info, - "info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] [-U] filename") + "info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] [--limits] [-U] filename") SRST -.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [-U] FILENAME +.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-U] FILENAME ERST DEF("map", img_map, diff --git a/qemu-img.c b/qemu-img.c index 7a162fdc08..5cdbeda969 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -86,6 +86,7 @@ enum { OPTION_BITMAPS = 275, OPTION_FORCE = 276, OPTION_SKIP_BROKEN = 277, + OPTION_LIMITS = 278, }; typedef enum OutputFormat { @@ -3002,7 +3003,8 @@ static gboolean str_equal_func(gconstpointer a, gconstpointer b) static BlockGraphInfoList *collect_image_info_list(bool image_opts, const char *filename, const char *fmt, - bool chain, bool force_share) + bool chain, bool limits, + bool force_share) { BlockGraphInfoList *head = NULL; BlockGraphInfoList **tail = &head; @@ -3039,7 +3041,7 @@ static BlockGraphInfoList *collect_image_info_list(bool image_opts, * the chain manually here. */ bdrv_graph_rdlock_main_loop(); - bdrv_query_block_graph_info(bs, &info, &err); + bdrv_query_block_graph_info(bs, &info, limits, &err); bdrv_graph_rdunlock_main_loop(); if (err) { @@ -3088,6 +3090,7 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) BlockGraphInfoList *list; bool image_opts = false; bool force_share = false; + bool limits = false; fmt = NULL; for(;;) { @@ -3097,6 +3100,7 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, {"force-share", no_argument, 0, 'U'}, + {"limits", no_argument, 0, OPTION_LIMITS}, {"output", required_argument, 0, OPTION_OUTPUT}, {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} @@ -3119,6 +3123,8 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) " display information about the backing chain for copy-on-write overlays\n" " -U, --force-share\n" " open image in shared mode for concurrent access\n" +" --limits\n" +" show detected block limits (may depend on options, e.g. cache mode)\n" " --output human|json\n" " specify output format (default: human)\n" " --object OBJDEF\n" @@ -3140,6 +3146,9 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) case 'U': force_share = true; break; + case OPTION_LIMITS: + limits = true; + break; case OPTION_OUTPUT: output_format = parse_output_format(argv[0], optarg); break; @@ -3156,7 +3165,7 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) filename = argv[optind++]; list = collect_image_info_list(image_opts, filename, fmt, chain, - force_share); + limits, force_share); if (!list) { return 1; } From 911992fd6ec7a84c7cc82831b4bcd8a2ca5ccc76 Mon Sep 17 00:00:00 2001 From: Kevin Wolf Date: Fri, 24 Oct 2025 14:30:40 +0200 Subject: [PATCH 18/18] qemu-img info: Add cache mode option When querying block limits, different cache modes (in particular O_DIRECT or not) can result in different limits. Add an option to 'qemu-img info' that allows the user to specify a cache mode, so that they can get the block limits for the cache mode they intend to use with their VM. Signed-off-by: Kevin Wolf Message-ID: <20251024123041.51254-5-kwolf@redhat.com> Reviewed-by: Eric Blake Signed-off-by: Kevin Wolf --- docs/tools/qemu-img.rst | 2 +- qemu-img-cmds.hx | 4 ++-- qemu-img.c | 25 +++++++++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/tools/qemu-img.rst b/docs/tools/qemu-img.rst index fdc9ea9cf2..558b0eb84d 100644 --- a/docs/tools/qemu-img.rst +++ b/docs/tools/qemu-img.rst @@ -503,7 +503,7 @@ Command description: The size syntax is similar to :manpage:`dd(1)`'s size syntax. -.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-U] FILENAME +.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-t CACHE] [-U] FILENAME Give information about the disk image *FILENAME*. Use it in particular to know the size reserved on disk which can be different diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 74b66f9d42..6bc8265cfb 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -66,9 +66,9 @@ SRST ERST DEF("info", img_info, - "info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] [--limits] [-U] filename") + "info [--object objectdef] [--image-opts] [-f fmt] [--output=ofmt] [--backing-chain] [--limits] [-t CACHE] [-U] filename") SRST -.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-U] FILENAME +.. option:: info [--object OBJECTDEF] [--image-opts] [-f FMT] [--output=OFMT] [--backing-chain] [--limits] [-t CACHE] [-U] FILENAME ERST DEF("map", img_map, diff --git a/qemu-img.c b/qemu-img.c index 5cdbeda969..a7791896c1 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -3003,6 +3003,7 @@ static gboolean str_equal_func(gconstpointer a, gconstpointer b) static BlockGraphInfoList *collect_image_info_list(bool image_opts, const char *filename, const char *fmt, + const char *cache, bool chain, bool limits, bool force_share) { @@ -3010,6 +3011,15 @@ static BlockGraphInfoList *collect_image_info_list(bool image_opts, BlockGraphInfoList **tail = &head; GHashTable *filenames; Error *err = NULL; + int cache_flags = 0; + bool writethrough = false; + int ret; + + ret = bdrv_parse_cache_mode(cache, &cache_flags, &writethrough); + if (ret < 0) { + error_report("Invalid cache option: %s", cache); + return NULL; + } filenames = g_hash_table_new_full(g_str_hash, str_equal_func, NULL, NULL); @@ -3026,8 +3036,8 @@ static BlockGraphInfoList *collect_image_info_list(bool image_opts, g_hash_table_insert(filenames, (gpointer)filename, NULL); blk = img_open(image_opts, filename, fmt, - BDRV_O_NO_BACKING | BDRV_O_NO_IO, false, false, - force_share); + BDRV_O_NO_BACKING | BDRV_O_NO_IO | cache_flags, + writethrough, false, force_share); if (!blk) { goto err; } @@ -3087,6 +3097,7 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) OutputFormat output_format = OFORMAT_HUMAN; bool chain = false; const char *filename, *fmt; + const char *cache = BDRV_DEFAULT_CACHE; BlockGraphInfoList *list; bool image_opts = false; bool force_share = false; @@ -3099,13 +3110,14 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) {"format", required_argument, 0, 'f'}, {"image-opts", no_argument, 0, OPTION_IMAGE_OPTS}, {"backing-chain", no_argument, 0, OPTION_BACKING_CHAIN}, + {"cache", required_argument, 0, 't'}, {"force-share", no_argument, 0, 'U'}, {"limits", no_argument, 0, OPTION_LIMITS}, {"output", required_argument, 0, OPTION_OUTPUT}, {"object", required_argument, 0, OPTION_OBJECT}, {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "hf:U", long_options, NULL); + c = getopt_long(argc, argv, "hf:t:U", long_options, NULL); if (c == -1) { break; } @@ -3121,6 +3133,8 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) " (incompatible with -f|--format)\n" " --backing-chain\n" " display information about the backing chain for copy-on-write overlays\n" +" -t, --cache CACHE\n" +" cache mode for FILE (default: " BDRV_DEFAULT_CACHE ")\n" " -U, --force-share\n" " open image in shared mode for concurrent access\n" " --limits\n" @@ -3143,6 +3157,9 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) case OPTION_BACKING_CHAIN: chain = true; break; + case 't': + cache = optarg; + break; case 'U': force_share = true; break; @@ -3164,7 +3181,7 @@ static int img_info(const img_cmd_t *ccmd, int argc, char **argv) } filename = argv[optind++]; - list = collect_image_info_list(image_opts, filename, fmt, chain, + list = collect_image_info_list(image_opts, filename, fmt, cache, chain, limits, force_share); if (!list) { return 1;