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