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