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