Add an x-qemu-io QMP command that runs qemu-io commands on block
devices. The command accepts a device name (block backend name,
node-name, or qdev ID) and a qemu-io command string.
Refactor hmp_qemu_io() to be a thin wrapper around the new QMP
command, following the standard HMP-over-QMP pattern used by other
block commands.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
qapi/block.json | 34 +++++++++++++
block/monitor/block-hmp-cmds.c | 60 ++--------------------
block/monitor/qmp-cmds.c | 79 +++++++++++++++++++++++++++++
block/monitor/meson.build | 1 +
tests/qemu-iotests/tests/image-fleecing | 4 +-
tests/qemu-iotests/tests/image-fleecing.out | 52 +++++++++----------
6 files changed, 145 insertions(+), 85 deletions(-)
diff --git a/qapi/block.json b/qapi/block.json
index 46955bbb3e3..0a588714ea9 100644
--- a/qapi/block.json
+++ b/qapi/block.json
@@ -603,3 +603,37 @@
'*boundaries-zap': ['uint64'],
'*boundaries-flush': ['uint64'] },
'allow-preconfig': true }
+
+##
+# @x-qemu-io:
+#
+# Run a qemu-io command on a block device. Take either a block
+# backend name or a qdev ID to identify the device.
+#
+# @device: the block backend name, node-name to run the
+# command on.
+#
+# @qdev: the qdev ID of the block device to run the
+# command on.
+#
+# @command: the qemu-io command string to execute.
+#
+# Features:
+#
+# @unstable: This command is for testing only.
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "x-qemu-io",
+# "arguments": { "device": "virtio0",
+# "command": "read 0 512" } }
+# <- { "return": {} }
+##
+{ 'command': 'x-qemu-io',
+ 'data': { '*device': 'str',
+ '*qdev': 'str',
+ 'command': 'str' },
+ 'features': [ 'unstable' ],
+ 'allow-preconfig': true }
diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
index c7113b8ea5e..254a79ce855 100644
--- a/block/monitor/block-hmp-cmds.c
+++ b/block/monitor/block-hmp-cmds.c
@@ -56,7 +56,6 @@
#include "block/qapi.h"
#include "block/block_int.h"
#include "block/block-hmp-cmds.h"
-#include "qemu-io.h"
static void hmp_drive_add_node(Monitor *mon, const char *optstr)
{
@@ -551,67 +550,14 @@ void hmp_eject(Monitor *mon, const QDict *qdict)
void hmp_qemu_io(Monitor *mon, const QDict *qdict)
{
- BlockBackend *blk = NULL;
- BlockDriverState *bs = NULL;
- BlockBackend *local_blk = NULL;
bool qdev = qdict_get_try_bool(qdict, "qdev", false);
const char *device = qdict_get_str(qdict, "device");
const char *command = qdict_get_str(qdict, "command");
Error *err = NULL;
- int ret;
-
- if (qdev) {
- blk = blk_by_qdev_id(device, &err);
- if (!blk) {
- goto fail;
- }
- } else {
- blk = blk_by_name(device);
- if (!blk) {
- bs = bdrv_lookup_bs(NULL, device, &err);
- if (!bs) {
- goto fail;
- }
- }
- }
-
- if (bs) {
- blk = local_blk = blk_new(bdrv_get_aio_context(bs), 0, BLK_PERM_ALL);
- ret = blk_insert_bs(blk, bs, &err);
- if (ret < 0) {
- goto fail;
- }
- }
-
- /*
- * Notably absent: Proper permission management. This is sad, but it seems
- * almost impossible to achieve without changing the semantics and thereby
- * limiting the use cases of the qemu-io HMP command.
- *
- * In an ideal world we would unconditionally create a new BlockBackend for
- * qemuio_command(), but we have commands like 'reopen' and want them to
- * take effect on the exact BlockBackend whose name the user passed instead
- * of just on a temporary copy of it.
- *
- * Another problem is that deleting the temporary BlockBackend involves
- * draining all requests on it first, but some qemu-iotests cases want to
- * issue multiple aio_read/write requests and expect them to complete in
- * the background while the monitor has already returned.
- *
- * This is also what prevents us from saving the original permissions and
- * restoring them later: We can't revoke permissions until all requests
- * have completed, and we don't know when that is nor can we really let
- * anything else run before we have revoken them to avoid race conditions.
- *
- * What happens now is that command() in qemu-io-cmds.c can extend the
- * permissions if necessary for the qemu-io command. And they simply stay
- * extended, possibly resulting in a read-only guest device keeping write
- * permissions. Ugly, but it appears to be the lesser evil.
- */
- qemuio_command(blk, command, &err);
-fail:
- blk_unref(local_blk);
+ qmp_x_qemu_io(qdev ? NULL : device,
+ qdev ? device : NULL,
+ command, &err);
hmp_handle_error(mon, err);
}
diff --git a/block/monitor/qmp-cmds.c b/block/monitor/qmp-cmds.c
new file mode 100644
index 00000000000..e5759d824f1
--- /dev/null
+++ b/block/monitor/qmp-cmds.c
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include "qemu/osdep.h"
+
+#include "system/block-backend.h"
+#include "block/block_int.h"
+#include "qapi/qapi-commands-block.h"
+#include "qapi/error.h"
+#include "qemu-io.h"
+
+void qmp_x_qemu_io(const char *device, const char *qdev,
+ const char *command, Error **errp)
+{
+ BlockBackend *blk = NULL;
+ BlockBackend *local_blk = NULL;
+ BlockDriverState *bs = NULL;
+ int ret;
+
+ if (!device && !qdev) {
+ error_setg(errp, "Must specify either device or qdev");
+ return;
+ }
+ if (qdev && device) {
+ error_setg(errp, "Cannot specify both qdev and device");
+ return;
+ }
+
+ if (qdev) {
+ blk = blk_by_qdev_id(qdev, errp);
+ if (!blk) {
+ return;
+ }
+ } else {
+ blk = blk_by_name(device);
+ if (!blk) {
+ bs = bdrv_lookup_bs(NULL, device, errp);
+ if (!bs) {
+ return;
+ }
+ }
+ }
+
+ if (bs) {
+ blk = local_blk = blk_new(bdrv_get_aio_context(bs), 0, BLK_PERM_ALL);
+ ret = blk_insert_bs(blk, bs, errp);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
+ /*
+ * Notably absent: Proper permission management. This is sad, but it seems
+ * almost impossible to achieve without changing the semantics and thereby
+ * limiting the use cases of the qemu-io command.
+ *
+ * In an ideal world we would unconditionally create a new BlockBackend for
+ * qemuio_command(), but we have commands like 'reopen' and want them to
+ * take effect on the exact BlockBackend whose name the user passed instead
+ * of just on a temporary copy of it.
+ *
+ * Another problem is that deleting the temporary BlockBackend involves
+ * draining all requests on it first, but some qemu-iotests cases want to
+ * issue multiple aio_read/write requests and expect them to complete in
+ * the background while the monitor has already returned.
+ *
+ * This is also what prevents us from saving the original permissions and
+ * restoring them later: We can't revoke permissions until all requests
+ * have completed, and we don't know when that is nor can we really let
+ * anything else run before we have revoken them to avoid race conditions.
+ *
+ * What happens now is that command() in qemu-io-cmds.c can extend the
+ * permissions if necessary for the qemu-io command. And they simply stay
+ * extended, possibly resulting in a read-only guest device keeping write
+ * permissions. Ugly, but it appears to be the lesser evil.
+ */
+ qemuio_command(blk, command, errp);
+
+fail:
+ blk_unref(local_blk);
+}
diff --git a/block/monitor/meson.build b/block/monitor/meson.build
index 1022516e93c..74faced9e17 100644
--- a/block/monitor/meson.build
+++ b/block/monitor/meson.build
@@ -1,2 +1,3 @@
system_ss.add(files('block-hmp-cmds.c'))
block_ss.add(files('bitmap-qmp-cmds.c'))
+system_ss.add(files('qmp-cmds.c'))
diff --git a/tests/qemu-iotests/tests/image-fleecing b/tests/qemu-iotests/tests/image-fleecing
index 5e3b2c7e46a..62ef15ff046 100755
--- a/tests/qemu-iotests/tests/image-fleecing
+++ b/tests/qemu-iotests/tests/image-fleecing
@@ -202,12 +202,12 @@ def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path,
for p in overwrite:
cmd = 'write -P%s %s %s' % p
log(cmd)
- log(vm.hmp_qemu_io(qom_path, cmd, qdev=True))
+ log(vm.qmp('x-qemu-io', qdev='sda', command=cmd))
if push_backup:
# Check that previous operations were done during backup, not after
# If backup is already finished, it's possible that it was finished
- # even before hmp qemu_io write, and we didn't actually test
+ # even before qemu_io write, and we didn't actually test
# copy-before-write operation. This should not happen, as we use
# speed=1. But worth checking.
result = vm.qmp('query-block-jobs')
diff --git a/tests/qemu-iotests/tests/image-fleecing.out b/tests/qemu-iotests/tests/image-fleecing.out
index acfc89ff0e9..0a819539130 100644
--- a/tests/qemu-iotests/tests/image-fleecing.out
+++ b/tests/qemu-iotests/tests/image-fleecing.out
@@ -31,13 +31,13 @@ read -P0 0x3fe0000 64k
--- Testing COW ---
write -P0xab 0 64k
-{"return": ""}
+{"return": {}}
write -P0xad 0x00f8000 64k
-{"return": ""}
+{"return": {}}
write -P0x1d 0x2008000 64k
-{"return": ""}
+{"return": {}}
write -P0xea 0x3fe0000 64k
-{"return": ""}
+{"return": {}}
--- Verifying Data ---
@@ -101,13 +101,13 @@ read -P0 0x3fe0000 64k
--- Testing COW ---
write -P0xab 0 64k
-{"return": ""}
+{"return": {}}
write -P0xad 0x00f8000 64k
-{"return": ""}
+{"return": {}}
write -P0x1d 0x2008000 64k
-{"return": ""}
+{"return": {}}
write -P0xea 0x3fe0000 64k
-{"return": ""}
+{"return": {}}
--- Verifying Data ---
@@ -172,13 +172,13 @@ read -P0 0x3fe0000 64k
--- Testing COW ---
write -P0xab 0 64k
-{"return": ""}
+{"return": {}}
write -P0xad 0x00f8000 64k
-{"return": ""}
+{"return": {}}
write -P0x1d 0x2008000 64k
-{"return": ""}
+{"return": {}}
write -P0xea 0x3fe0000 64k
-{"return": ""}
+{"return": {}}
--- Verifying Data ---
@@ -238,25 +238,25 @@ read -P0xd5 1M 64k
read -P0xdc 32M 64k
read -P0xcd 0x3ff0000 64k
read -P0 0x00f8000 32k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
read -P0 0x2010000 32k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
read -P0 0x3fe0000 64k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
--- Testing COW ---
write -P0xab 0 64k
-{"return": ""}
+{"return": {}}
write -P0xad 0x00f8000 64k
-{"return": ""}
+{"return": {}}
write -P0x1d 0x2008000 64k
-{"return": ""}
+{"return": {}}
write -P0xea 0x3fe0000 64k
-{"return": ""}
+{"return": {}}
--- Verifying Data ---
@@ -265,13 +265,13 @@ read -P0xd5 1M 64k
read -P0xdc 32M 64k
read -P0xcd 0x3ff0000 64k
read -P0 0x00f8000 32k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
read -P0 0x2010000 32k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
read -P0 0x3fe0000 64k
-read failed: Invalid argument
+qemu-io: read failed: Invalid argument
--- Cleanup ---
@@ -318,13 +318,13 @@ Done
--- Testing COW ---
write -P0xab 0 64k
-{"return": ""}
+{"return": {}}
write -P0xad 0x00f8000 64k
-{"return": ""}
+{"return": {}}
write -P0x1d 0x2008000 64k
-{"return": ""}
+{"return": {}}
write -P0xea 0x3fe0000 64k
-{"return": ""}
+{"return": {}}
{"data": {"device": "push-backup", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"return": {}}
--
2.54.0