-From 6e030cd398065d17ab01fe7b2a3e655c5e5f19eb Mon Sep 17 00:00:00 2001
+From cf9cc8878d3246069da3fdf7ec865b7b983487fe Mon Sep 17 00:00:00 2001
From: Dietmar Maurer <dietmar@proxmox.com>
Date: Tue, 13 Nov 2012 11:27:56 +0100
-Subject: [PATCH v3 3/6] add backup related monitor commands
+Subject: [PATCH v5 3/6] add backup related monitor commands
-We use a generic BackupDriver struct to encaplulated all archive format
+We use a generic BackupDriver struct to encapsulate all archive format
related function.
Another option would be to simply dump <devid,cluster_num,cluster_data> to
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
- backup.h | 13 ++
- blockdev.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
- hmp-commands.hx | 31 +++++
- hmp.c | 63 +++++++++
+ backup.h | 15 ++
+ blockdev.c | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ hmp-commands.hx | 31 ++++
+ hmp.c | 63 ++++++++
hmp.h | 3 +
monitor.c | 7 +
- qapi-schema.json | 81 ++++++++++++
+ qapi-schema.json | 95 ++++++++++++
qmp-commands.hx | 27 ++++
- 8 files changed, 601 insertions(+), 0 deletions(-)
+ 8 files changed, 664 insertions(+), 0 deletions(-)
diff --git a/backup.h b/backup.h
-index e1f0290..ae4aa8c 100644
+index 9b1ea1c..22598a6 100644
--- a/backup.h
+++ b/backup.h
-@@ -27,4 +27,17 @@ int backup_job_start(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb,
- BlockDriverCompletionFunc *backup_complete_cb,
- void *opaque);
+@@ -14,6 +14,9 @@
+ #ifndef QEMU_BACKUP_H
+ #define QEMU_BACKUP_H
+
++#include <uuid/uuid.h>
++#include "block/block.h"
++
+ #define BACKUP_CLUSTER_BITS 16
+ #define BACKUP_CLUSTER_SIZE (1<<BACKUP_CLUSTER_BITS)
+ #define BACKUP_BLOCKS_PER_CLUSTER (BACKUP_CLUSTER_SIZE/BDRV_SECTOR_SIZE)
+@@ -27,4 +30,16 @@ int backup_job_create(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb,
+ BlockDriverCompletionFunc *backup_complete_cb,
+ void *opaque, int64_t speed);
+typedef struct BackupDriver {
+ const char *format;
-+ void *(*open_cb)(const char *filename, uuid_t uuid, int64_t speed,
-+ Error **errp);
-+ int (*close_cb)(void *opaque, Error **errp);
-+ int (*register_config_cb)(void *opaque, const char *name, gpointer data,
++ void *(*open)(const char *filename, uuid_t uuid, Error **errp);
++ int (*close)(void *opaque, Error **errp);
++ int (*register_config)(void *opaque, const char *name, gpointer data,
+ size_t data_len);
-+ int (*register_stream_cb)(void *opaque, const char *devname, size_t size);
-+ int (*dump_cb)(void *opaque, uint8_t dev_id, int64_t cluster_num,
++ int (*register_stream)(void *opaque, const char *devname, size_t size);
++ int (*dump)(void *opaque, uint8_t dev_id, int64_t cluster_num,
+ unsigned char *buf, size_t *zero_bytes);
-+ int (*complete_cb)(void *opaque, uint8_t dev_id, int ret);
++ int (*complete)(void *opaque, uint8_t dev_id, int ret);
+} BackupDriver;
+
#endif /* QEMU_BACKUP_H */
diff --git a/blockdev.c b/blockdev.c
-index e73fd6e..c635d21 100644
+index 63e6f1e..84f598d 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -20,6 +20,7 @@
#include "qmp-commands.h"
#include "trace.h"
- #include "arch_init.h"
+ #include "sysemu/arch_init.h"
+#include "backup.h"
static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
-@@ -1321,6 +1322,381 @@ void qmp_drive_mirror(const char *device, const char *target,
+@@ -1334,6 +1335,428 @@ void qmp_drive_mirror(const char *device, const char *target,
drive_get_ref(drive_get_by_blockdev(bs));
}
+/* Backup related function */
+
++static void backup_run_next_job(void);
++
+static struct GenericBackupState {
+ Error *error;
++ bool cancel;
+ uuid_t uuid;
+ char uuid_str[37];
++ int64_t speed;
+ time_t start_time;
+ time_t end_time;
-+ char *backupfile;
++ char *backup_file;
+ const BackupDriver *driver;
+ void *writer;
+ GList *bcb_list;
+
+typedef struct BackupCB {
+ BlockDriverState *bs;
-+ bool job_started;
+ uint8_t dev_id;
++ bool started;
++ bool completed;
+ size_t size;
+ size_t transferred;
+ size_t zero_bytes;
+
+ assert(backup_state.driver);
+ assert(backup_state.writer);
-+ assert(backup_state.driver->dump_cb);
++ assert(backup_state.driver->dump);
+
+ size_t zero_bytes = 0;
-+ int bytes = backup_state.driver->dump_cb(backup_state.writer,
-+ bcb->dev_id, cluster_num,
-+ buf, &zero_bytes);
++ int bytes = backup_state.driver->dump(backup_state.writer,
++ bcb->dev_id, cluster_num,
++ buf, &zero_bytes);
+
+ if (bytes > 0) {
+ bcb->transferred += bytes;
+ if (backup_state.writer && backup_state.driver) {
+ backup_state.end_time = time(NULL);
+ Error *local_err = NULL;
-+ backup_state.driver->close_cb(backup_state.writer, &local_err);
++ backup_state.driver->close(backup_state.writer, &local_err);
+ error_propagate(&backup_state.error, local_err);
+ backup_state.writer = NULL;
++ }
+
++ if (backup_state.bcb_list) {
+ GList *l = backup_state.bcb_list;
+ while (l) {
-+ g_free(l->data);
++ BackupCB *bcb = l->data;
+ l = g_list_next(l);
++ drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs));
++ g_free(bcb);
+ }
+ g_list_free(backup_state.bcb_list);
+ backup_state.bcb_list = NULL;
+
+ assert(backup_state.driver);
+ assert(backup_state.writer);
-+ assert(backup_state.driver->complete_cb);
-+ assert(backup_state.driver->close_cb);
-+
-+ drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs));
++ assert(backup_state.driver->complete);
++ assert(backup_state.driver->close);
+
-+ backup_state.bcb_list = g_list_remove(backup_state.bcb_list, bcb);
++ bcb->completed = true;
+
-+ backup_state.driver->complete_cb(backup_state.writer, bcb->dev_id, ret);
++ backup_state.driver->complete(backup_state.writer, bcb->dev_id, ret);
+
-+ if (g_list_length(backup_state.bcb_list) == 0) {
-+ backup_cleanup();
++ if (!backup_state.cancel) {
++ backup_run_next_job();
+ }
-+
-+ g_free(bcb);
+}
+
+static void backup_cancel(void)
+{
-+ GList *l = backup_state.bcb_list;
-+ int job_count = 0;
++ backup_state.cancel = true;
+
++ if (!backup_state.error) {
++ error_setg(&backup_state.error, "backup cancelled");
++ }
++
++ /* drain all i/o (awake jobs waiting for aio) */
++ bdrv_drain_all();
++
++ int job_count = 0;
++ GList *l = backup_state.bcb_list;
+ while (l) {
+ BackupCB *bcb = l->data;
+ l = g_list_next(l);
-+ if (bcb->bs->job && bcb->job_started) {
-+ block_job_cancel(bcb->bs->job);
++ BlockJob *job = bcb->bs->job;
++ if (job) {
+ job_count++;
++ if (!bcb->started) {
++ bcb->started = true;
++ backup_job_start(bcb->bs, true);
++ }
++ if (!bcb->completed) {
++ block_job_cancel_sync(job);
++ }
+ }
+ }
+
-+ if (!job_count) {
-+ backup_cleanup();
-+ }
++ backup_cleanup();
+}
+
+void qmp_backup_cancel(Error **errp)
+{
-+ if (!backup_state.error) {
-+ error_setg(&backup_state.error, "backup cancelled");
++ backup_cancel();
++}
++
++static void backup_run_next_job(void)
++{
++ GList *l = backup_state.bcb_list;
++ while (l) {
++ BackupCB *bcb = l->data;
++ l = g_list_next(l);
++
++ if (bcb->bs && bcb->bs->job && !bcb->completed) {
++ if (!bcb->started) {
++ bcb->started = true;
++ bool cancel = backup_state.error || backup_state.cancel;
++ backup_job_start(bcb->bs, cancel);
++ }
++ return;
++ }
+ }
+
-+ backup_cancel();
++ backup_cleanup();
+}
+
+static void backup_start_jobs(void)
+{
-+ /* start all jobs (one for each device) */
++ /* create all jobs (one for each device), start first one */
+ GList *l = backup_state.bcb_list;
+ while (l) {
+ BackupCB *bcb = l->data;
+ l = g_list_next(l);
+
-+ if (backup_job_start(bcb->bs, backup_dump_cb, backup_complete_cb,
-+ bcb) == 0) {
-+ bcb->job_started = true;
-+ /* Grab a reference so hotplug does not delete the
-+ * BlockDriverState from underneath us.
-+ */
-+ drive_get_ref(drive_get_by_blockdev(bcb->bs));
-+ } else {
-+ if (!backup_state.error) {
-+ error_setg(&backup_state.error, "backup_job_start failed");
-+ }
++ if (backup_job_create(bcb->bs, backup_dump_cb, backup_complete_cb,
++ bcb, backup_state.speed) != 0) {
++ error_setg(&backup_state.error, "backup_job_create failed");
+ backup_cancel();
+ return;
-+ }
++ }
+ }
++
++ backup_run_next_job();
+}
+
-+char *qmp_backup(const char *backupfile, bool has_format, const char *format,
-+ bool has_config_filename, const char *config_filename,
++char *qmp_backup(const char *backup_file, bool has_format, BackupFormat format,
++ bool has_config_file, const char *config_file,
+ bool has_devlist, const char *devlist,
+ bool has_speed, int64_t speed, Error **errp)
+{
+ gchar **devs = NULL;
+ GList *bcblist = NULL;
+
++ if (backup_state.bcb_list) {
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
++ "previous backup not finished");
++ return NULL;
++ }
++
+ /* Todo: try to auto-detect format based on file name */
-+ format = has_format ? format : "vma";
++ format = has_format ? format : BACKUP_FORMAT_VMA;
+
+ /* fixme: find driver for specifued format */
+ const BackupDriver *driver = NULL;
+
+ if (!driver) {
-+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
-+ "no backup driver for format '%s'", format);
++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
+ return NULL;
+ }
+
+ if (has_devlist) {
-+ devs = g_strsplit(devlist, ",;:", -1);
++ devs = g_strsplit_set(devlist, ",;:", -1);
+
+ gchar **d = devs;
+ while (d && *d) {
+
+ uuid_generate(uuid);
+
-+ writer = driver->open_cb(backupfile, uuid, speed, &local_err);
++ writer = driver->open(backup_file, uuid, &local_err);
+ if (!writer) {
+ if (error_is_set(&local_err)) {
+ error_propagate(errp, local_err);
+
+ int64_t size = bdrv_getlength(bcb->bs);
+ const char *devname = bdrv_get_device_name(bcb->bs);
-+ bcb->dev_id = driver->register_stream_cb(writer, devname, size);
++ bcb->dev_id = driver->register_stream(writer, devname, size);
+ if (bcb->dev_id <= 0) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "register_stream failed");
+ }
+
+ /* add configuration file to archive */
-+ if (has_config_filename) {
++ if (has_config_file) {
+ char *cdata = NULL;
+ gsize clen = 0;
+ GError *err = NULL;
-+ if (!g_file_get_contents(config_filename, &cdata, &clen, &err)) {
-+ error_setg(errp, "unable to read file '%s'", config_filename);
++ if (!g_file_get_contents(config_file, &cdata, &clen, &err)) {
++ error_setg(errp, "unable to read file '%s'", config_file);
+ goto err;
+ }
+
-+ const char *basename = g_path_get_basename(config_filename);
-+ if (driver->register_config_cb(writer, basename, cdata, clen) <= 0) {
++ const char *basename = g_path_get_basename(config_file);
++ if (driver->register_config(writer, basename, cdata, clen) < 0) {
+ error_setg(errp, "register_config failed");
+ g_free(cdata);
+ goto err;
+
+ /* initialize global backup_state now */
+
++ backup_state.cancel = false;
++
+ if (backup_state.error) {
+ error_free(backup_state.error);
+ backup_state.error = NULL;
+
+ backup_state.driver = driver;
+
++ backup_state.speed = (has_speed && speed > 0) ? speed : 0;
++
+ backup_state.start_time = time(NULL);
+ backup_state.end_time = 0;
+
-+ if (backup_state.backupfile) {
-+ g_free(backup_state.backupfile);
++ if (backup_state.backup_file) {
++ g_free(backup_state.backup_file);
+ }
-+ backup_state.backupfile = g_strdup(backupfile);
++ backup_state.backup_file = g_strdup(backup_file);
+
+ backup_state.writer = writer;
+
+ backup_state.transferred = 0;
+ backup_state.zero_bytes = 0;
+
++ /* Grab a reference so hotplug does not delete the
++ * BlockDriverState from underneath us.
++ */
++ l = bcblist;
++ while (l) {
++ BackupCB *bcb = l->data;
++ l = g_list_next(l);
++ drive_get_ref(drive_get_by_blockdev(bcb->bs));
++ }
++
+ backup_start_jobs();
+
+ return g_strdup(backup_state.uuid_str);
+ }
+
+ if (writer) {
-+ unlink(backupfile);
++ unlink(backup_file);
+ if (driver) {
+ Error *err = NULL;
-+ driver->close_cb(writer, &err);
++ driver->close(writer, &err);
+ }
+ }
+
+ info->has_start_time = true;
+ info->start_time = backup_state.start_time;
+
-+ if (backup_state.backupfile) {
-+ info->has_backupfile = true;
-+ info->backupfile = g_strdup(backup_state.backupfile);
++ if (backup_state.backup_file) {
++ info->has_backup_file = true;
++ info->backup_file = g_strdup(backup_state.backup_file);
+ }
+
+ info->has_uuid = true;
{
BlockDriverState *bs;
diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 010b8c9..57be357 100644
+index 64008a9..0f178d8 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -83,6 +83,35 @@ STEXI
{
.name = "block_job_set_speed",
.args_type = "device:B,speed:o",
-@@ -1558,6 +1587,8 @@ show CPU statistics
+@@ -1630,6 +1659,8 @@ show CPU statistics
show user network stack connection states
@item info migrate
show migration status
show current migration capabilities
@item info migrate_cache_size
diff --git a/hmp.c b/hmp.c
-index 180ba2b..600792f 100644
+index 2f47a8a..b2c1f23 100644
--- a/hmp.c
+++ b/hmp.c
-@@ -130,6 +130,38 @@ void hmp_info_mice(Monitor *mon)
+@@ -131,6 +131,38 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
qapi_free_MouseInfoList(mice_list);
}
-+void hmp_info_backup(Monitor *mon)
++void hmp_info_backup(Monitor *mon, const QDict *qdict)
+{
+ BackupStatus *info;
+
+ monitor_printf(mon, "Backup status: %s\n", info->status);
+ }
+ }
-+ if (info->has_backupfile) {
++ if (info->has_backup_file) {
+ int per = (info->has_total && info->total &&
+ info->has_transferred && info->transferred) ?
+ (info->transferred * 100)/info->total : 0;
+ int zero_per = (info->has_total && info->total &&
+ info->has_zero_bytes && info->zero_bytes) ?
+ (info->zero_bytes * 100)/info->total : 0;
-+ monitor_printf(mon, "Backup file: %s\n", info->backupfile);
++ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
+ monitor_printf(mon, "Total size: %zd\n", info->total);
+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
+ qapi_free_BackupStatus(info);
+}
+
- void hmp_info_migrate(Monitor *mon)
+ void hmp_info_migrate(Monitor *mon, const QDict *qdict)
{
MigrationInfo *info;
-@@ -977,6 +1009,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
+@@ -998,6 +1030,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
hmp_handle_error(mon, &error);
}
+
+void hmp_backup(Monitor *mon, const QDict *qdict)
+{
-+ const char *backupfile = qdict_get_str(qdict, "backupfile");
++ const char *backup_file = qdict_get_str(qdict, "backup-file");
+ const char *devlist = qdict_get_try_str(qdict, "devlist");
+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
+
+ Error *errp = NULL;
+
-+ qmp_backup(backupfile, false, NULL, false, NULL, !!devlist, devlist,
-+ qdict_haskey(qdict, "speed"), speed, &errp);
++ qmp_backup(backup_file, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist,
++ devlist, qdict_haskey(qdict, "speed"), speed, &errp);
+
+ if (error_is_set(&errp)) {
+ monitor_printf(mon, "%s\n", error_get_pretty(errp));
{
Error *error = NULL;
diff --git a/hmp.h b/hmp.h
-index 0ab03be..20c9a62 100644
+index 30b3c20..ad4cf80 100644
--- a/hmp.h
+++ b/hmp.h
-@@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon);
- void hmp_info_migrate(Monitor *mon);
- void hmp_info_migrate_capabilities(Monitor *mon);
- void hmp_info_migrate_cache_size(Monitor *mon);
-+void hmp_info_backup(Monitor *mon);
- void hmp_info_cpus(Monitor *mon);
- void hmp_info_block(Monitor *mon);
- void hmp_info_blockstats(Monitor *mon);
-@@ -63,6 +64,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
+@@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
+ void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
++void hmp_info_backup(Monitor *mon, const QDict *qdict);
+ void hmp_info_cpus(Monitor *mon, const QDict *qdict);
+ void hmp_info_block(Monitor *mon, const QDict *qdict);
+ void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
+@@ -65,6 +66,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
void hmp_change(Monitor *mon, const QDict *qdict);
void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
void hmp_block_stream(Monitor *mon, const QDict *qdict);
void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
diff --git a/monitor.c b/monitor.c
-index c0e32d6..85cf47e 100644
+index 6a0f257..e4a810c 100644
--- a/monitor.c
+++ b/monitor.c
-@@ -2680,6 +2680,13 @@ static mon_cmd_t info_cmds[] = {
+@@ -2666,6 +2666,13 @@ static mon_cmd_t info_cmds[] = {
},
#endif
{
+ .args_type = "",
+ .params = "",
+ .help = "show backup status",
-+ .mhandler.info = hmp_info_backup,
++ .mhandler.cmd = hmp_info_backup,
+ },
+ {
.name = "migrate",
.args_type = "",
.params = "",
diff --git a/qapi-schema.json b/qapi-schema.json
-index 5dfa052..71e0ed9 100644
+index 7275b5d..09ca8ef 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
-@@ -358,6 +358,39 @@
+@@ -425,6 +425,39 @@
{ 'type': 'EventInfo', 'data': {'name': 'str'} }
##
+# Detailed backup status.
+#
+# @status: #optional string describing the current backup status.
-+# Tthis can be 'active', 'done', 'error'. If this field is not
++# This can be 'active', 'done', 'error'. If this field is not
+# returned, no backup process has been initiated
+#
+# @errmsg: #optional error message (only returned if status is 'error')
+#
+# @uuid: #optional uuid for this backup job
+#
-+# Since: 1.4.0
++# Since: 1.5.0
+##
+{ 'type': 'BackupStatus',
+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
+ '*transferred': 'int', '*zero-bytes': 'int',
+ '*start-time': 'int', '*end-time': 'int',
-+ '*backupfile': 'str', '*uuid': 'str' } }
++ '*backup-file': 'str', '*uuid': 'str' } }
+
+##
# @query-events:
#
# Return a list of supported QMP events by this server
-@@ -1764,6 +1797,54 @@
+@@ -1824,6 +1857,68 @@
'data': { 'path': 'str' },
'returns': [ 'ObjectPropertyInfo' ] }
++
++##
++# @BackupFormat
++#
++# An enumeration of supported backup formats.
++#
++# @vma: Proxmox vma backup format
++##
++{ 'enum': 'BackupFormat',
++ 'data': [ 'vma' ] }
+
+##
+# @backup:
+#
+# Starts a VM backup.
+#
-+# @backupfile: the backup file name
++# @backup-file: the backup file name
+#
+# @format: format of the backup file
+#
+#
+# @speed: #optional the maximum speed, in bytes per second
+#
++# @devlist: #optional list of block device names (separated by ',', ';'
++# or ':'). By default the backup includes all writable block devices.
++#
+# Returns: the uuid of the backup job
+#
-+# Since: 1.4.0
++# Since: 1.5.0
+##
-+{ 'command': 'backup', 'data': { 'backupfile': 'str', '*format': 'str',
-+ '*config-filename': 'str',
++{ 'command': 'backup', 'data': { 'backup-file': 'str',
++ '*format': 'BackupFormat',
++ '*config-file': 'str',
+ '*devlist': 'str', '*speed': 'int' },
+ 'returns': 'str' }
+
+#
+# Returns: @BackupStatus
+#
-+# Since: 1.4.0
++# Since: 1.5.0
+##
+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
+
+#
+# Notes: This command succeeds even if there is no backup process running.
+#
-+# Since: 1.4.0
++# Since: 1.5.0
+##
+{ 'command': 'backup-cancel' }
+
# @qom-get:
#
diff --git a/qmp-commands.hx b/qmp-commands.hx
-index 5c692d0..c46fdc4 100644
+index 799adea..17e381b 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
-@@ -822,6 +822,18 @@ EQMP
+@@ -889,6 +889,18 @@ EQMP
},
{
+ .name = "backup",
-+ .args_type = "backupfile:s,format:s?,config-filename:F?,speed:o?,devlist:s?",
++ .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?",
+ .mhandler.cmd_new = qmp_marshal_input_backup,
+ },
+
+ {
-+ .name = "backup_cancel",
++ .name = "backup-cancel",
+ .args_type = "",
+ .mhandler.cmd_new = qmp_marshal_input_backup_cancel,
+ },
.name = "block-job-set-speed",
.args_type = "device:B,speed:o",
.mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
-@@ -2491,6 +2503,21 @@ EQMP
+@@ -2566,6 +2578,21 @@ EQMP
},
SQMP