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