]> git.proxmox.com Git - pve-qemu-kvm.git/blame - debian/patches/0003-add-backup-related-monitor-commands.patch
updates for qemu 1.3.0 rc2
[pve-qemu-kvm.git] / debian / patches / 0003-add-backup-related-monitor-commands.patch
CommitLineData
16aecab6 1From 6e030cd398065d17ab01fe7b2a3e655c5e5f19eb Mon Sep 17 00:00:00 2001
5ad5891c
DM
2From: Dietmar Maurer <dietmar@proxmox.com>
3Date: Tue, 13 Nov 2012 11:27:56 +0100
4Subject: [PATCH v3 3/6] add backup related monitor commands
5
6We use a generic BackupDriver struct to encaplulated all archive format
7related function.
8
9Another option would be to simply dump <devid,cluster_num,cluster_data> to
10the output fh (pipe), and an external binary saves the data. That way we
11could move the whole archive format related code out of qemu.
12
13Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
14---
15 backup.h | 13 ++
16 blockdev.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 hmp-commands.hx | 31 +++++
18 hmp.c | 63 +++++++++
19 hmp.h | 3 +
20 monitor.c | 7 +
21 qapi-schema.json | 81 ++++++++++++
22 qmp-commands.hx | 27 ++++
23 8 files changed, 601 insertions(+), 0 deletions(-)
24
25diff --git a/backup.h b/backup.h
26index e1f0290..ae4aa8c 100644
27--- a/backup.h
28+++ b/backup.h
29@@ -27,4 +27,17 @@ int backup_job_start(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb,
30 BlockDriverCompletionFunc *backup_complete_cb,
31 void *opaque);
32
33+typedef struct BackupDriver {
34+ const char *format;
35+ void *(*open_cb)(const char *filename, uuid_t uuid, int64_t speed,
36+ Error **errp);
37+ int (*close_cb)(void *opaque, Error **errp);
38+ int (*register_config_cb)(void *opaque, const char *name, gpointer data,
39+ size_t data_len);
40+ int (*register_stream_cb)(void *opaque, const char *devname, size_t size);
41+ int (*dump_cb)(void *opaque, uint8_t dev_id, int64_t cluster_num,
42+ unsigned char *buf, size_t *zero_bytes);
43+ int (*complete_cb)(void *opaque, uint8_t dev_id, int ret);
44+} BackupDriver;
45+
46 #endif /* QEMU_BACKUP_H */
47diff --git a/blockdev.c b/blockdev.c
48index e73fd6e..c635d21 100644
49--- a/blockdev.c
50+++ b/blockdev.c
51@@ -20,6 +20,7 @@
52 #include "qmp-commands.h"
53 #include "trace.h"
54 #include "arch_init.h"
55+#include "backup.h"
56
57 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
58
59@@ -1321,6 +1322,381 @@ void qmp_drive_mirror(const char *device, const char *target,
60 drive_get_ref(drive_get_by_blockdev(bs));
61 }
62
63+/* Backup related function */
64+
65+static struct GenericBackupState {
66+ Error *error;
67+ uuid_t uuid;
68+ char uuid_str[37];
69+ time_t start_time;
70+ time_t end_time;
71+ char *backupfile;
72+ const BackupDriver *driver;
73+ void *writer;
74+ GList *bcb_list;
75+ size_t total;
76+ size_t transferred;
77+ size_t zero_bytes;
78+} backup_state;
79+
80+typedef struct BackupCB {
81+ BlockDriverState *bs;
82+ bool job_started;
83+ uint8_t dev_id;
84+ size_t size;
85+ size_t transferred;
86+ size_t zero_bytes;
87+} BackupCB;
88+
89+static int backup_dump_cb(void *opaque, BlockDriverState *bs,
90+ int64_t cluster_num, unsigned char *buf)
91+{
92+ BackupCB *bcb = opaque;
93+
94+ assert(backup_state.driver);
95+ assert(backup_state.writer);
96+ assert(backup_state.driver->dump_cb);
97+
98+ size_t zero_bytes = 0;
99+ int bytes = backup_state.driver->dump_cb(backup_state.writer,
100+ bcb->dev_id, cluster_num,
101+ buf, &zero_bytes);
102+
103+ if (bytes > 0) {
104+ bcb->transferred += bytes;
105+ backup_state.transferred += bytes;
106+ if (zero_bytes) {
107+ bcb->zero_bytes += bytes;
108+ backup_state.zero_bytes += zero_bytes;
109+ }
110+ }
111+
112+ return bytes;
113+}
114+
115+static void backup_cleanup(void)
116+{
117+ if (backup_state.writer && backup_state.driver) {
118+ backup_state.end_time = time(NULL);
119+ Error *local_err = NULL;
120+ backup_state.driver->close_cb(backup_state.writer, &local_err);
121+ error_propagate(&backup_state.error, local_err);
122+ backup_state.writer = NULL;
123+
124+ GList *l = backup_state.bcb_list;
125+ while (l) {
126+ g_free(l->data);
127+ l = g_list_next(l);
128+ }
129+ g_list_free(backup_state.bcb_list);
130+ backup_state.bcb_list = NULL;
131+ }
132+}
133+
134+static void backup_complete_cb(void *opaque, int ret)
135+{
136+ BackupCB *bcb = opaque;
137+
138+ assert(backup_state.driver);
139+ assert(backup_state.writer);
140+ assert(backup_state.driver->complete_cb);
141+ assert(backup_state.driver->close_cb);
142+
143+ drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs));
144+
145+ backup_state.bcb_list = g_list_remove(backup_state.bcb_list, bcb);
146+
147+ backup_state.driver->complete_cb(backup_state.writer, bcb->dev_id, ret);
148+
149+ if (g_list_length(backup_state.bcb_list) == 0) {
150+ backup_cleanup();
151+ }
152+
153+ g_free(bcb);
154+}
155+
156+static void backup_cancel(void)
157+{
158+ GList *l = backup_state.bcb_list;
159+ int job_count = 0;
160+
161+ while (l) {
162+ BackupCB *bcb = l->data;
163+ l = g_list_next(l);
164+ if (bcb->bs->job && bcb->job_started) {
165+ block_job_cancel(bcb->bs->job);
166+ job_count++;
167+ }
168+ }
169+
170+ if (!job_count) {
171+ backup_cleanup();
172+ }
173+}
174+
175+void qmp_backup_cancel(Error **errp)
176+{
177+ if (!backup_state.error) {
178+ error_setg(&backup_state.error, "backup cancelled");
179+ }
180+
181+ backup_cancel();
182+}
183+
184+static void backup_start_jobs(void)
185+{
186+ /* start all jobs (one for each device) */
187+ GList *l = backup_state.bcb_list;
188+ while (l) {
189+ BackupCB *bcb = l->data;
190+ l = g_list_next(l);
191+
192+ if (backup_job_start(bcb->bs, backup_dump_cb, backup_complete_cb,
193+ bcb) == 0) {
194+ bcb->job_started = true;
195+ /* Grab a reference so hotplug does not delete the
196+ * BlockDriverState from underneath us.
197+ */
198+ drive_get_ref(drive_get_by_blockdev(bcb->bs));
199+ } else {
200+ if (!backup_state.error) {
201+ error_setg(&backup_state.error, "backup_job_start failed");
202+ }
203+ backup_cancel();
204+ return;
205+ }
206+ }
207+}
208+
209+char *qmp_backup(const char *backupfile, bool has_format, const char *format,
210+ bool has_config_filename, const char *config_filename,
211+ bool has_devlist, const char *devlist,
212+ bool has_speed, int64_t speed, Error **errp)
213+{
214+ BlockDriverState *bs;
215+ Error *local_err = NULL;
216+ uuid_t uuid;
217+ void *writer = NULL;
218+ gchar **devs = NULL;
219+ GList *bcblist = NULL;
220+
221+ /* Todo: try to auto-detect format based on file name */
222+ format = has_format ? format : "vma";
223+
224+ /* fixme: find driver for specifued format */
225+ const BackupDriver *driver = NULL;
226+
227+ if (!driver) {
228+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
229+ "no backup driver for format '%s'", format);
230+ return NULL;
231+ }
232+
233+ if (has_devlist) {
234+ devs = g_strsplit(devlist, ",;:", -1);
235+
236+ gchar **d = devs;
237+ while (d && *d) {
238+ bs = bdrv_find(*d);
239+ if (bs) {
240+ if (bdrv_is_read_only(bs)) {
241+ error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d);
242+ goto err;
243+ }
244+ if (!bdrv_is_inserted(bs)) {
245+ error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
246+ goto err;
247+ }
248+ BackupCB *bcb = g_new0(BackupCB, 1);
249+ bcb->bs = bs;
250+ bcblist = g_list_append(bcblist, bcb);
251+ } else {
252+ error_set(errp, QERR_DEVICE_NOT_FOUND, *d);
253+ goto err;
254+ }
255+ d++;
256+ }
257+
258+ } else {
259+
260+ bs = NULL;
261+ while ((bs = bdrv_next(bs))) {
262+
263+ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
264+ continue;
265+ }
266+
267+ BackupCB *bcb = g_new0(BackupCB, 1);
268+ bcb->bs = bs;
269+ bcblist = g_list_append(bcblist, bcb);
270+ }
271+ }
272+
273+ if (!bcblist) {
274+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
275+ goto err;
276+ }
277+
278+ GList *l = bcblist;
279+ while (l) {
280+ BackupCB *bcb = l->data;
281+ l = g_list_next(l);
282+ if (bcb->bs->job) {
283+ error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bcb->bs));
284+ goto err;
285+ }
286+ }
287+
288+ uuid_generate(uuid);
289+
290+ writer = driver->open_cb(backupfile, uuid, speed, &local_err);
291+ if (!writer) {
292+ if (error_is_set(&local_err)) {
293+ error_propagate(errp, local_err);
294+ }
295+ goto err;
296+ }
297+
298+ size_t total = 0;
299+
300+ /* register all devices for vma writer */
301+ l = bcblist;
302+ while (l) {
303+ BackupCB *bcb = l->data;
304+ l = g_list_next(l);
305+
306+ int64_t size = bdrv_getlength(bcb->bs);
307+ const char *devname = bdrv_get_device_name(bcb->bs);
308+ bcb->dev_id = driver->register_stream_cb(writer, devname, size);
309+ if (bcb->dev_id <= 0) {
310+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
311+ "register_stream failed");
312+ goto err;
313+ }
314+ bcb->size = size;
315+ total += size;
316+ }
317+
318+ /* add configuration file to archive */
319+ if (has_config_filename) {
320+ char *cdata = NULL;
321+ gsize clen = 0;
322+ GError *err = NULL;
323+ if (!g_file_get_contents(config_filename, &cdata, &clen, &err)) {
324+ error_setg(errp, "unable to read file '%s'", config_filename);
325+ goto err;
326+ }
327+
328+ const char *basename = g_path_get_basename(config_filename);
329+ if (driver->register_config_cb(writer, basename, cdata, clen) <= 0) {
330+ error_setg(errp, "register_config failed");
331+ g_free(cdata);
332+ goto err;
333+ }
334+ g_free(cdata);
335+ }
336+
337+ /* initialize global backup_state now */
338+
339+ if (backup_state.error) {
340+ error_free(backup_state.error);
341+ backup_state.error = NULL;
342+ }
343+
344+ backup_state.driver = driver;
345+
346+ backup_state.start_time = time(NULL);
347+ backup_state.end_time = 0;
348+
349+ if (backup_state.backupfile) {
350+ g_free(backup_state.backupfile);
351+ }
352+ backup_state.backupfile = g_strdup(backupfile);
353+
354+ backup_state.writer = writer;
355+
356+ uuid_copy(backup_state.uuid, uuid);
357+ uuid_unparse_lower(uuid, backup_state.uuid_str);
358+
359+ backup_state.bcb_list = bcblist;
360+
361+ backup_state.total = total;
362+ backup_state.transferred = 0;
363+ backup_state.zero_bytes = 0;
364+
365+ backup_start_jobs();
366+
367+ return g_strdup(backup_state.uuid_str);
368+
369+err:
370+
371+ l = bcblist;
372+ while (l) {
373+ g_free(l->data);
374+ l = g_list_next(l);
375+ }
376+ g_list_free(bcblist);
377+
378+ if (devs) {
379+ g_strfreev(devs);
380+ }
381+
382+ if (writer) {
383+ unlink(backupfile);
384+ if (driver) {
385+ Error *err = NULL;
386+ driver->close_cb(writer, &err);
387+ }
388+ }
389+
390+ return NULL;
391+}
392+
393+BackupStatus *qmp_query_backup(Error **errp)
394+{
395+ BackupStatus *info = g_malloc0(sizeof(*info));
396+
397+ if (!backup_state.start_time) {
398+ /* not started, return {} */
399+ return info;
400+ }
401+
402+ info->has_status = true;
403+ info->has_start_time = true;
404+ info->start_time = backup_state.start_time;
405+
406+ if (backup_state.backupfile) {
407+ info->has_backupfile = true;
408+ info->backupfile = g_strdup(backup_state.backupfile);
409+ }
410+
411+ info->has_uuid = true;
412+ info->uuid = g_strdup(backup_state.uuid_str);
413+
414+ if (backup_state.end_time) {
415+ if (backup_state.error) {
416+ info->status = g_strdup("error");
417+ info->has_errmsg = true;
418+ info->errmsg = g_strdup(error_get_pretty(backup_state.error));
419+ } else {
420+ info->status = g_strdup("done");
421+ }
422+ info->has_end_time = true;
423+ info->end_time = backup_state.end_time;
424+ } else {
425+ info->status = g_strdup("active");
426+ }
427+
428+ info->has_total = true;
429+ info->total = backup_state.total;
430+ info->has_zero_bytes = true;
431+ info->zero_bytes = backup_state.zero_bytes;
432+ info->has_transferred = true;
433+ info->transferred = backup_state.transferred;
434+
435+ return info;
436+}
437+
438 static BlockJob *find_block_job(const char *device)
439 {
440 BlockDriverState *bs;
441diff --git a/hmp-commands.hx b/hmp-commands.hx
442index 010b8c9..57be357 100644
443--- a/hmp-commands.hx
444+++ b/hmp-commands.hx
445@@ -83,6 +83,35 @@ STEXI
446 Copy data from a backing file into a block device.
447 ETEXI
448
449+ {
450+ .name = "backup",
451+ .args_type = "backupfile:s,speed:o?,devlist:s?",
452+ .params = "backupfile [speed [devlist]]",
453+ .help = "create a VM Backup.",
454+ .mhandler.cmd = hmp_backup,
455+ },
456+
457+STEXI
458+@item backup
459+@findex backup
460+Create a VM backup.
461+ETEXI
462+
463+ {
464+ .name = "backup_cancel",
465+ .args_type = "",
466+ .params = "",
467+ .help = "cancel the current VM backup",
468+ .mhandler.cmd = hmp_backup_cancel,
469+ },
470+
471+STEXI
472+@item backup_cancel
473+@findex backup_cancel
474+Cancel the current VM backup.
475+
476+ETEXI
477+
478 {
479 .name = "block_job_set_speed",
480 .args_type = "device:B,speed:o",
481@@ -1558,6 +1587,8 @@ show CPU statistics
482 show user network stack connection states
483 @item info migrate
484 show migration status
485+@item info backup
486+show backup status
487 @item info migrate_capabilities
488 show current migration capabilities
489 @item info migrate_cache_size
490diff --git a/hmp.c b/hmp.c
491index 180ba2b..600792f 100644
492--- a/hmp.c
493+++ b/hmp.c
494@@ -130,6 +130,38 @@ void hmp_info_mice(Monitor *mon)
495 qapi_free_MouseInfoList(mice_list);
496 }
497
498+void hmp_info_backup(Monitor *mon)
499+{
500+ BackupStatus *info;
501+
502+ info = qmp_query_backup(NULL);
503+ if (info->has_status) {
504+ if (info->has_errmsg) {
505+ monitor_printf(mon, "Backup status: %s - %s\n",
506+ info->status, info->errmsg);
507+ } else {
508+ monitor_printf(mon, "Backup status: %s\n", info->status);
509+ }
510+ }
511+ if (info->has_backupfile) {
512+ int per = (info->has_total && info->total &&
513+ info->has_transferred && info->transferred) ?
514+ (info->transferred * 100)/info->total : 0;
515+ int zero_per = (info->has_total && info->total &&
516+ info->has_zero_bytes && info->zero_bytes) ?
517+ (info->zero_bytes * 100)/info->total : 0;
518+ monitor_printf(mon, "Backup file: %s\n", info->backupfile);
519+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
520+ monitor_printf(mon, "Total size: %zd\n", info->total);
521+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
522+ info->transferred, per);
523+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
524+ info->zero_bytes, zero_per);
525+ }
526+
527+ qapi_free_BackupStatus(info);
528+}
529+
530 void hmp_info_migrate(Monitor *mon)
531 {
532 MigrationInfo *info;
533@@ -977,6 +1009,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
534 hmp_handle_error(mon, &error);
535 }
536
537+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
538+{
539+ Error *errp = NULL;
540+
541+ qmp_backup_cancel(&errp);
542+
543+ if (error_is_set(&errp)) {
544+ monitor_printf(mon, "%s\n", error_get_pretty(errp));
545+ error_free(errp);
546+ return;
547+ }
548+}
549+
550+void hmp_backup(Monitor *mon, const QDict *qdict)
551+{
552+ const char *backupfile = qdict_get_str(qdict, "backupfile");
553+ const char *devlist = qdict_get_try_str(qdict, "devlist");
554+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
555+
556+ Error *errp = NULL;
557+
558+ qmp_backup(backupfile, false, NULL, false, NULL, !!devlist, devlist,
559+ qdict_haskey(qdict, "speed"), speed, &errp);
560+
561+ if (error_is_set(&errp)) {
562+ monitor_printf(mon, "%s\n", error_get_pretty(errp));
563+ error_free(errp);
564+ return;
565+ }
566+}
567+
568 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
569 {
570 Error *error = NULL;
571diff --git a/hmp.h b/hmp.h
572index 0ab03be..20c9a62 100644
573--- a/hmp.h
574+++ b/hmp.h
575@@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon);
576 void hmp_info_migrate(Monitor *mon);
577 void hmp_info_migrate_capabilities(Monitor *mon);
578 void hmp_info_migrate_cache_size(Monitor *mon);
579+void hmp_info_backup(Monitor *mon);
580 void hmp_info_cpus(Monitor *mon);
581 void hmp_info_block(Monitor *mon);
582 void hmp_info_blockstats(Monitor *mon);
583@@ -63,6 +64,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
584 void hmp_change(Monitor *mon, const QDict *qdict);
585 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
586 void hmp_block_stream(Monitor *mon, const QDict *qdict);
587+void hmp_backup(Monitor *mon, const QDict *qdict);
588+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
589 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
590 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
591 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
592diff --git a/monitor.c b/monitor.c
593index c0e32d6..85cf47e 100644
594--- a/monitor.c
595+++ b/monitor.c
596@@ -2680,6 +2680,13 @@ static mon_cmd_t info_cmds[] = {
597 },
598 #endif
599 {
600+ .name = "backup",
601+ .args_type = "",
602+ .params = "",
603+ .help = "show backup status",
604+ .mhandler.info = hmp_info_backup,
605+ },
606+ {
607 .name = "migrate",
608 .args_type = "",
609 .params = "",
610diff --git a/qapi-schema.json b/qapi-schema.json
611index 5dfa052..71e0ed9 100644
612--- a/qapi-schema.json
613+++ b/qapi-schema.json
614@@ -358,6 +358,39 @@
615 { 'type': 'EventInfo', 'data': {'name': 'str'} }
616
617 ##
618+# @BackupStatus:
619+#
620+# Detailed backup status.
621+#
622+# @status: #optional string describing the current backup status.
623+# Tthis can be 'active', 'done', 'error'. If this field is not
624+# returned, no backup process has been initiated
625+#
626+# @errmsg: #optional error message (only returned if status is 'error')
627+#
628+# @total: #optional total amount of bytes involved in the backup process
629+#
630+# @transferred: #optional amount of bytes already backed up.
631+#
632+# @zero-bytes: #optional amount of 'zero' bytes detected.
633+#
634+# @start-time: #optional time (epoch) when backup job started.
635+#
636+# @end-time: #optional time (epoch) when backup job finished.
637+#
638+# @backupfile: #optional backup file name
639+#
640+# @uuid: #optional uuid for this backup job
641+#
642+# Since: 1.4.0
643+##
644+{ 'type': 'BackupStatus',
645+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
646+ '*transferred': 'int', '*zero-bytes': 'int',
647+ '*start-time': 'int', '*end-time': 'int',
648+ '*backupfile': 'str', '*uuid': 'str' } }
649+
650+##
651 # @query-events:
652 #
653 # Return a list of supported QMP events by this server
654@@ -1764,6 +1797,54 @@
655 'data': { 'path': 'str' },
656 'returns': [ 'ObjectPropertyInfo' ] }
657
658+
659+##
660+# @backup:
661+#
662+# Starts a VM backup.
663+#
664+# @backupfile: the backup file name
665+#
666+# @format: format of the backup file
667+#
668+# @config-filename: #optional name of a configuration file to include into
669+# the backup archive.
670+#
671+# @speed: #optional the maximum speed, in bytes per second
672+#
673+# Returns: the uuid of the backup job
674+#
675+# Since: 1.4.0
676+##
677+{ 'command': 'backup', 'data': { 'backupfile': 'str', '*format': 'str',
678+ '*config-filename': 'str',
679+ '*devlist': 'str', '*speed': 'int' },
680+ 'returns': 'str' }
681+
682+##
683+# @query-backup
684+#
685+# Returns information about current/last backup task.
686+#
687+# Returns: @BackupStatus
688+#
689+# Since: 1.4.0
690+##
691+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
692+
693+##
694+# @backup-cancel
695+#
696+# Cancel the current executing backup process.
697+#
698+# Returns: nothing on success
699+#
700+# Notes: This command succeeds even if there is no backup process running.
701+#
702+# Since: 1.4.0
703+##
704+{ 'command': 'backup-cancel' }
705+
706 ##
707 # @qom-get:
708 #
709diff --git a/qmp-commands.hx b/qmp-commands.hx
710index 5c692d0..c46fdc4 100644
711--- a/qmp-commands.hx
712+++ b/qmp-commands.hx
713@@ -822,6 +822,18 @@ EQMP
714 },
715
716 {
717+ .name = "backup",
718+ .args_type = "backupfile:s,format:s?,config-filename:F?,speed:o?,devlist:s?",
719+ .mhandler.cmd_new = qmp_marshal_input_backup,
720+ },
721+
722+ {
723+ .name = "backup_cancel",
724+ .args_type = "",
725+ .mhandler.cmd_new = qmp_marshal_input_backup_cancel,
726+ },
727+
728+ {
729 .name = "block-job-set-speed",
730 .args_type = "device:B,speed:o",
731 .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
732@@ -2491,6 +2503,21 @@ EQMP
733 },
734
735 SQMP
736+
737+query-backup
738+-------------
739+
740+Backup status.
741+
742+EQMP
743+
744+ {
745+ .name = "query-backup",
746+ .args_type = "",
747+ .mhandler.cmd_new = qmp_marshal_input_query_backup,
748+ },
749+
750+SQMP
751 migrate-set-capabilities
752 -------
753
754--
7551.7.2.5
756