]> git.proxmox.com Git - pve-qemu.git/blame - debian/patches/pve/0020-PVE-backup-introduce-vma-archive-format.patch
update sources for v4.0.1
[pve-qemu.git] / debian / patches / pve / 0020-PVE-backup-introduce-vma-archive-format.patch
CommitLineData
23102ed6 1From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
67af0fa4
WB
2From: Wolfgang Bumiller <w.bumiller@proxmox.com>
3Date: Wed, 2 Aug 2017 13:51:02 +0200
53e83913 4Subject: [PATCH] PVE: backup: introduce vma archive format
67af0fa4 5
53e83913 6TODO: Move to a libvma block backend.
b855dce7 7Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
67af0fa4
WB
8---
9 MAINTAINERS | 6 +
10 block/Makefile.objs | 3 +
b855dce7
TL
11 block/vma.c | 503 ++++++++++++++++++++++++++++++++++++++++
12 blockdev.c | 536 +++++++++++++++++++++++++++++++++++++++++++
53e83913 13 configure | 29 +++
67af0fa4 14 hmp-commands-info.hx | 13 ++
6838f038 15 hmp-commands.hx | 31 +++
b855dce7 16 hmp.c | 63 +++++
67af0fa4 17 hmp.h | 3 +
b855dce7 18 qapi/block-core.json | 109 ++++++++-
53e83913
WB
19 qapi/common.json | 13 ++
20 qapi/misc.json | 13 --
0775f12b 21 12 files changed, 1308 insertions(+), 14 deletions(-)
67af0fa4
WB
22 create mode 100644 block/vma.c
23
24diff --git a/MAINTAINERS b/MAINTAINERS
b855dce7 25index 56139ac8ab..5588f5c91e 100644
67af0fa4
WB
26--- a/MAINTAINERS
27+++ b/MAINTAINERS
b855dce7 28@@ -2499,6 +2499,12 @@ L: qemu-block@nongnu.org
67af0fa4
WB
29 S: Supported
30 F: block/vvfat.c
31
32+VMA
33+M: Wolfgang Bumiller <w.bumiller@proxmox.com>.
34+L: pve-devel@proxmox.com
35+S: Supported
36+F: block/vma.c
37+
38 Image format fuzzer
39 M: Stefan Hajnoczi <stefanha@redhat.com>
40 L: qemu-block@nongnu.org
41diff --git a/block/Makefile.objs b/block/Makefile.objs
b855dce7 42index 03b5763bfa..00fa730d7b 100644
67af0fa4
WB
43--- a/block/Makefile.objs
44+++ b/block/Makefile.objs
b855dce7 45@@ -33,6 +33,7 @@ block-obj-$(CONFIG_RBD) += rbd.o
67af0fa4 46 block-obj-$(CONFIG_GLUSTERFS) += gluster.o
6838f038 47 block-obj-$(CONFIG_VXHS) += vxhs.o
67af0fa4
WB
48 block-obj-$(CONFIG_LIBSSH2) += ssh.o
49+block-obj-$(CONFIG_VMA) += vma.o
50 block-obj-y += accounting.o dirty-bitmap.o
51 block-obj-y += write-threshold.o
52 block-obj-y += backup.o
b855dce7 53@@ -64,3 +65,5 @@ qcow.o-libs := -lz
67af0fa4 54 linux-aio.o-libs := -laio
53e83913
WB
55 parallels.o-cflags := $(LIBXML2_CFLAGS)
56 parallels.o-libs := $(LIBXML2_LIBS)
67af0fa4
WB
57+vma.o-cflags := $(VMA_CFLAGS)
58+vma.o-libs := $(VMA_LIBS)
59diff --git a/block/vma.c b/block/vma.c
60new file mode 100644
0775f12b 61index 0000000000..b911b198dc
67af0fa4
WB
62--- /dev/null
63+++ b/block/vma.c
0775f12b 64@@ -0,0 +1,503 @@
67af0fa4
WB
65+/*
66+ * VMA archive backend for QEMU, container object
67+ *
68+ * Copyright (C) 2017 Proxmox Server Solutions GmbH
69+ *
70+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
71+ * See the COPYING file in the top-level directory.
72+ *
73+ */
74+#include <vma/vma.h>
75+
76+#include "qemu/osdep.h"
77+#include "qemu/uuid.h"
0775f12b 78+#include "qemu/option.h"
67af0fa4
WB
79+#include "qemu-common.h"
80+#include "qapi/error.h"
81+#include "qapi/qmp/qerror.h"
82+#include "qapi/qmp/qstring.h"
0775f12b 83+#include "qapi/qmp/qdict.h"
67af0fa4
WB
84+#include "qom/object.h"
85+#include "qom/object_interfaces.h"
86+#include "block/block_int.h"
87+
88+/* exported interface */
89+void vma_object_add_config_file(Object *obj, const char *name,
90+ const char *contents, size_t len,
91+ Error **errp);
92+
93+#define TYPE_VMA_OBJECT "vma"
94+#define VMA_OBJECT(obj) \
95+ OBJECT_CHECK(VMAObjectState, (obj), TYPE_VMA_OBJECT)
96+#define VMA_OBJECT_GET_CLASS(obj) \
97+ OBJECT_GET_CLASS(VMAObjectClass, (obj), TYPE_VMA_OBJECT)
98+
99+typedef struct VMAObjectClass {
100+ ObjectClass parent_class;
101+} VMAObjectClass;
102+
103+typedef struct VMAObjectState {
104+ Object parent;
105+
106+ char *filename;
107+
108+ QemuUUID uuid;
109+ bool blocked;
110+ VMAWriter *vma;
111+ QemuMutex mutex;
112+} VMAObjectState;
113+
114+static VMAObjectState *vma_by_id(const char *name)
115+{
116+ Object *container;
117+ Object *obj;
118+
119+ container = object_get_objects_root();
120+ obj = object_resolve_path_component(container, name);
121+
122+ return VMA_OBJECT(obj);
123+}
124+
125+static void vma_object_class_complete(UserCreatable *uc, Error **errp)
126+{
127+ int rc;
128+ VMAObjectState *vo = VMA_OBJECT(uc);
129+ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(uc);
130+ (void)!vo;
131+ (void)!voc;
132+
133+ if (!vo->filename) {
134+ error_setg(errp, "Parameter 'filename' is required");
135+ return;
136+ }
137+
0775f12b
WB
138+ vo->vma = VMAWriter_fopen(vo->filename);
139+ if (!vo->vma) {
140+ error_setg_errno(errp, errno, "failed to create VMA archive");
67af0fa4
WB
141+ return;
142+ }
143+
144+ rc = VMAWriter_set_uuid(vo->vma, vo->uuid.data, sizeof(vo->uuid.data));
145+ if (rc < 0) {
146+ error_setg_errno(errp, -rc, "failed to set UUID of VMA archive");
147+ return;
148+ }
149+
150+ qemu_mutex_init(&vo->mutex);
151+}
152+
0775f12b 153+static bool vma_object_can_be_deleted(UserCreatable *uc)
67af0fa4
WB
154+{
155+ //VMAObjectState *vo = VMA_OBJECT(uc);
156+ //if (!vo->vma) {
157+ // return true;
158+ //}
159+ //return false;
160+ return true;
161+}
162+
163+static void vma_object_class_init(ObjectClass *oc, void *data)
164+{
165+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
166+
167+ ucc->can_be_deleted = vma_object_can_be_deleted;
168+ ucc->complete = vma_object_class_complete;
169+}
170+
171+static char *vma_object_get_filename(Object *obj, Error **errp)
172+{
173+ VMAObjectState *vo = VMA_OBJECT(obj);
174+
175+ return g_strdup(vo->filename);
176+}
177+
178+static void vma_object_set_filename(Object *obj, const char *str, Error **errp)
179+{
180+ VMAObjectState *vo = VMA_OBJECT(obj);
181+
182+ if (vo->vma) {
183+ error_setg(errp, "filename cannot be changed after creation");
184+ return;
185+ }
186+
187+ g_free(vo->filename);
188+ vo->filename = g_strdup(str);
189+}
190+
191+static char *vma_object_get_uuid(Object *obj, Error **errp)
192+{
193+ VMAObjectState *vo = VMA_OBJECT(obj);
194+
195+ return qemu_uuid_unparse_strdup(&vo->uuid);
196+}
197+
198+static void vma_object_set_uuid(Object *obj, const char *str, Error **errp)
199+{
200+ VMAObjectState *vo = VMA_OBJECT(obj);
201+
202+ if (vo->vma) {
203+ error_setg(errp, "uuid cannot be changed after creation");
204+ return;
205+ }
206+
207+ qemu_uuid_parse(str, &vo->uuid);
208+}
209+
210+static bool vma_object_get_blocked(Object *obj, Error **errp)
211+{
212+ VMAObjectState *vo = VMA_OBJECT(obj);
213+
214+ return vo->blocked;
215+}
216+
217+static void vma_object_set_blocked(Object *obj, bool blocked, Error **errp)
218+{
219+ VMAObjectState *vo = VMA_OBJECT(obj);
220+
221+ (void)errp;
222+
223+ vo->blocked = blocked;
224+}
225+
226+void vma_object_add_config_file(Object *obj, const char *name,
227+ const char *contents, size_t len,
228+ Error **errp)
229+{
230+ int rc;
231+ VMAObjectState *vo = VMA_OBJECT(obj);
232+
233+ if (!vo || !vo->vma) {
234+ error_setg(errp, "not a valid vma object to add config files to");
235+ return;
236+ }
237+
238+ rc = VMAWriter_addConfigFile(vo->vma, name, contents, len);
239+ if (rc < 0) {
240+ error_setg_errno(errp, -rc, "failed to add config file to VMA");
241+ return;
242+ }
243+}
244+
245+static void vma_object_init(Object *obj)
246+{
247+ VMAObjectState *vo = VMA_OBJECT(obj);
248+ (void)!vo;
249+
250+ object_property_add_str(obj, "filename",
251+ vma_object_get_filename, vma_object_set_filename,
252+ NULL);
253+ object_property_add_str(obj, "uuid",
254+ vma_object_get_uuid, vma_object_set_uuid,
255+ NULL);
256+ object_property_add_bool(obj, "blocked",
257+ vma_object_get_blocked, vma_object_set_blocked,
258+ NULL);
259+}
260+
261+static void vma_object_finalize(Object *obj)
262+{
263+ VMAObjectState *vo = VMA_OBJECT(obj);
264+ VMAObjectClass *voc = VMA_OBJECT_GET_CLASS(obj);
265+ (void)!voc;
266+
267+ qemu_mutex_destroy(&vo->mutex);
268+
269+ VMAWriter_destroy(vo->vma, true);
270+ g_free(vo->filename);
271+}
272+
273+static const TypeInfo vma_object_info = {
274+ .name = TYPE_VMA_OBJECT,
275+ .parent = TYPE_OBJECT,
276+ .class_size = sizeof(VMAObjectClass),
277+ .class_init = vma_object_class_init,
278+ .instance_size = sizeof(VMAObjectState),
279+ .instance_init = vma_object_init,
280+ .instance_finalize = vma_object_finalize,
281+ .interfaces = (InterfaceInfo[]) {
282+ { TYPE_USER_CREATABLE },
283+ { }
284+ }
285+};
286+
287+static void register_types(void)
288+{
289+ type_register_static(&vma_object_info);
290+}
291+
292+type_init(register_types);
293+
294+typedef struct {
295+ VMAObjectState *vma_obj;
296+ char *name;
297+ size_t device_id;
298+ uint64_t byte_size;
299+} BDRVVMAState;
300+
301+static void qemu_vma_parse_filename(const char *filename, QDict *options,
302+ Error **errp)
303+{
304+ char *sep;
305+
0775f12b
WB
306+ if (strncmp(filename, "vma:", sizeof("vma:")-1) == 0) {
307+ filename += sizeof("vma:")-1;
308+ }
309+
67af0fa4
WB
310+ sep = strchr(filename, '/');
311+ if (!sep || sep == filename) {
0775f12b 312+ error_setg(errp, "VMA file should be <vma-obj>/<name>/<size>");
67af0fa4
WB
313+ return;
314+ }
315+
0775f12b 316+ qdict_put(options, "vma", qstring_from_substr(filename, 0, sep-filename));
67af0fa4
WB
317+
318+ while (*sep && *sep == '/')
319+ ++sep;
320+ if (!*sep) {
321+ error_setg(errp, "missing device name\n");
322+ return;
323+ }
324+
0775f12b
WB
325+ filename = sep;
326+ sep = strchr(filename, '/');
327+ if (!sep || sep == filename) {
328+ error_setg(errp, "VMA file should be <vma-obj>/<name>/<size>");
329+ return;
330+ }
331+
332+ qdict_put(options, "name", qstring_from_substr(filename, 0, sep-filename));
333+
334+ while (*sep && *sep == '/')
335+ ++sep;
336+ if (!*sep) {
337+ error_setg(errp, "missing device size\n");
338+ return;
339+ }
340+
341+ filename = sep;
342+ qdict_put_str(options, "size", filename);
67af0fa4
WB
343+}
344+
345+static QemuOptsList runtime_opts = {
346+ .name = "vma-drive",
347+ .head = QTAILQ_HEAD_INITIALIZER(runtime_opts.head),
348+ .desc = {
349+ {
350+ .name = "vma",
351+ .type = QEMU_OPT_STRING,
352+ .help = "VMA Object name",
353+ },
354+ {
355+ .name = "name",
356+ .type = QEMU_OPT_STRING,
357+ .help = "VMA device name",
358+ },
359+ {
360+ .name = BLOCK_OPT_SIZE,
361+ .type = QEMU_OPT_SIZE,
362+ .help = "Virtual disk size"
363+ },
364+ { /* end of list */ }
365+ },
366+};
367+static int qemu_vma_open(BlockDriverState *bs, QDict *options, int flags,
368+ Error **errp)
369+{
370+ Error *local_err = NULL;
371+ BDRVVMAState *s = bs->opaque;
372+ QemuOpts *opts;
373+ const char *vma_id, *device_name;
374+ ssize_t dev_id;
375+ int64_t bytes = 0;
376+ int ret;
377+
378+ opts = qemu_opts_create(&runtime_opts, NULL, 0, &error_abort);
379+ qemu_opts_absorb_qdict(opts, options, &local_err);
380+ if (local_err) {
381+ error_propagate(errp, local_err);
382+ ret = -EINVAL;
383+ goto failed_opts;
384+ }
385+
386+ bytes = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
387+ BDRV_SECTOR_SIZE);
388+
389+ vma_id = qemu_opt_get(opts, "vma");
0775f12b
WB
390+ if (!vma_id) {
391+ ret = -EINVAL;
392+ error_setg(errp, "missing 'vma' property");
393+ goto failed_opts;
394+ }
395+
67af0fa4 396+ device_name = qemu_opt_get(opts, "name");
0775f12b
WB
397+ if (!device_name) {
398+ ret = -EINVAL;
399+ error_setg(errp, "missing 'name' property");
400+ goto failed_opts;
401+ }
67af0fa4
WB
402+
403+ VMAObjectState *vma = vma_by_id(vma_id);
404+ if (!vma) {
405+ ret = -EINVAL;
406+ error_setg(errp, "no such VMA object: %s", vma_id);
407+ goto failed_opts;
408+ }
409+
410+ dev_id = VMAWriter_findDevice(vma->vma, device_name);
411+ if (dev_id >= 0) {
412+ error_setg(errp, "drive already exists in VMA object");
413+ ret = -EIO;
414+ goto failed_opts;
415+ }
416+
417+ dev_id = VMAWriter_addDevice(vma->vma, device_name, (uint64_t)bytes);
418+ if (dev_id < 0) {
419+ error_setg_errno(errp, -dev_id, "failed to add VMA device");
420+ ret = -EIO;
421+ goto failed_opts;
422+ }
423+
424+ object_ref(OBJECT(vma));
425+ s->vma_obj = vma;
426+ s->name = g_strdup(device_name);
427+ s->device_id = (size_t)dev_id;
428+ s->byte_size = bytes;
429+
430+ ret = 0;
431+
432+failed_opts:
433+ qemu_opts_del(opts);
434+ return ret;
435+}
436+
437+static void qemu_vma_close(BlockDriverState *bs)
438+{
439+ BDRVVMAState *s = bs->opaque;
440+
441+ (void)VMAWriter_finishDevice(s->vma_obj->vma, s->device_id);
442+ object_unref(OBJECT(s->vma_obj));
443+
444+ g_free(s->name);
445+}
446+
447+static int64_t qemu_vma_getlength(BlockDriverState *bs)
448+{
449+ BDRVVMAState *s = bs->opaque;
450+
451+ return s->byte_size;
452+}
453+
454+static coroutine_fn int qemu_vma_co_writev(BlockDriverState *bs,
455+ int64_t sector_num,
456+ int nb_sectors,
0775f12b
WB
457+ QEMUIOVector *qiov,
458+ int flags)
67af0fa4
WB
459+{
460+ size_t i;
461+ ssize_t rc;
462+ BDRVVMAState *s = bs->opaque;
463+ VMAObjectState *vo = s->vma_obj;
464+ off_t offset = sector_num * BDRV_SECTOR_SIZE;
0775f12b
WB
465+ /* flags can be only values we set in supported_write_flags */
466+ assert(flags == 0);
67af0fa4
WB
467+
468+ qemu_mutex_lock(&vo->mutex);
469+ if (vo->blocked) {
470+ return -EPERM;
471+ }
472+ for (i = 0; i != qiov->niov; ++i) {
473+ const struct iovec *v = &qiov->iov[i];
474+ size_t blocks = v->iov_len / VMA_BLOCK_SIZE;
475+ if (blocks * VMA_BLOCK_SIZE != v->iov_len) {
476+ return -EIO;
477+ }
478+ rc = VMAWriter_writeBlocks(vo->vma, s->device_id,
479+ v->iov_base, blocks, offset);
480+ if (errno) {
481+ return -errno;
482+ }
483+ if (rc != blocks) {
484+ return -EIO;
485+ }
486+ offset += v->iov_len;
487+ }
488+ qemu_mutex_unlock(&vo->mutex);
489+ return 0;
490+}
491+
492+static int qemu_vma_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
493+{
494+ bdi->cluster_size = VMA_CLUSTER_SIZE;
495+ bdi->unallocated_blocks_are_zero = true;
67af0fa4
WB
496+ return 0;
497+}
498+
0775f12b
WB
499+static int qemu_vma_check_perm(BlockDriverState *bs,
500+ uint64_t perm,
501+ uint64_t shared,
502+ Error **errp)
503+{
504+ /* Nothing to do. */
505+ return 0;
506+}
507+
508+static void qemu_vma_set_perm(BlockDriverState *bs,
509+ uint64_t perm,
510+ uint64_t shared)
511+{
512+ /* Nothing to do. */
513+}
514+
515+static void qemu_vma_abort_perm_update(BlockDriverState *bs)
516+{
517+ /* Nothing to do. */
518+}
519+
520+static void qemu_vma_refresh_limits(BlockDriverState *bs, Error **errp)
521+{
522+ bs->bl.request_alignment = BDRV_SECTOR_SIZE; /* No sub-sector I/O */
523+}
524+static void qemu_vma_child_perm(BlockDriverState *bs, BdrvChild *c,
525+ const BdrvChildRole *role,
526+ BlockReopenQueue *reopen_queue,
527+ uint64_t perm, uint64_t shared,
528+ uint64_t *nperm, uint64_t *nshared)
529+{
530+ *nperm = BLK_PERM_ALL;
531+ *nshared = BLK_PERM_ALL;
532+}
533+
67af0fa4
WB
534+static BlockDriver bdrv_vma_drive = {
535+ .format_name = "vma-drive",
0775f12b 536+ .protocol_name = "vma",
67af0fa4
WB
537+ .instance_size = sizeof(BDRVVMAState),
538+
539+#if 0
540+ .bdrv_create = qemu_vma_create,
541+ .create_opts = &qemu_vma_create_opts,
542+#endif
543+
544+ .bdrv_parse_filename = qemu_vma_parse_filename,
545+ .bdrv_file_open = qemu_vma_open,
546+
547+ .bdrv_close = qemu_vma_close,
548+ .bdrv_has_zero_init = bdrv_has_zero_init_1,
549+ .bdrv_getlength = qemu_vma_getlength,
550+ .bdrv_get_info = qemu_vma_get_info,
551+
0775f12b 552+ //.bdrv_co_preadv = qemu_vma_co_preadv,
67af0fa4 553+ .bdrv_co_writev = qemu_vma_co_writev,
0775f12b
WB
554+
555+ .bdrv_refresh_limits = qemu_vma_refresh_limits,
556+ .bdrv_check_perm = qemu_vma_check_perm,
557+ .bdrv_set_perm = qemu_vma_set_perm,
558+ .bdrv_abort_perm_update = qemu_vma_abort_perm_update,
559+ .bdrv_child_perm = qemu_vma_child_perm,
67af0fa4
WB
560+};
561+
562+static void bdrv_vma_init(void)
563+{
564+ bdrv_register(&bdrv_vma_drive);
565+}
566+
567+block_init(bdrv_vma_init);
568diff --git a/blockdev.c b/blockdev.c
b8d43c59 569index a3542db57a..5d524b9a74 100644
67af0fa4
WB
570--- a/blockdev.c
571+++ b/blockdev.c
53e83913 572@@ -31,11 +31,13 @@
67af0fa4
WB
573 */
574
575 #include "qemu/osdep.h"
576+#include "qemu/uuid.h"
577 #include "sysemu/block-backend.h"
578 #include "sysemu/blockdev.h"
579 #include "hw/block/block.h"
580 #include "block/blockjob.h"
53e83913 581 #include "block/qdict.h"
67af0fa4
WB
582+#include "block/blockjob_int.h"
583 #include "block/throttle-groups.h"
584 #include "monitor/monitor.h"
585 #include "qemu/error-report.h"
53e83913
WB
586@@ -44,6 +46,7 @@
587 #include "qapi/qapi-commands-block.h"
588 #include "qapi/qapi-commands-transaction.h"
589 #include "qapi/qapi-visit-block-core.h"
590+#include "qapi/qapi-types-misc.h"
591 #include "qapi/qmp/qdict.h"
592 #include "qapi/qmp/qnum.h"
593 #include "qapi/qmp/qstring.h"
b8d43c59 594@@ -3148,6 +3151,539 @@ out:
67af0fa4
WB
595 aio_context_release(aio_context);
596 }
597
598+/* PVE backup related function */
599+
600+static struct PVEBackupState {
601+ Error *error;
602+ bool cancel;
603+ QemuUUID uuid;
604+ char uuid_str[37];
605+ int64_t speed;
606+ time_t start_time;
607+ time_t end_time;
608+ char *backup_file;
609+ Object *vmaobj;
610+ GList *di_list;
611+ size_t next_job;
612+ size_t total;
613+ size_t transferred;
614+ size_t zero_bytes;
6838f038
WB
615+ QemuMutex backup_mutex;
616+ bool backup_mutex_initialized;
67af0fa4
WB
617+} backup_state;
618+
619+typedef struct PVEBackupDevInfo {
620+ BlockDriverState *bs;
621+ size_t size;
622+ uint8_t dev_id;
623+ bool completed;
624+ char targetfile[PATH_MAX];
625+ BlockDriverState *target;
626+} PVEBackupDevInfo;
627+
628+static void pvebackup_run_next_job(void);
629+
630+static void pvebackup_cleanup(void)
631+{
6838f038
WB
632+ qemu_mutex_lock(&backup_state.backup_mutex);
633+ // Avoid race between block jobs and backup-cancel command:
634+ if (!backup_state.vmaw) {
635+ qemu_mutex_unlock(&backup_state.backup_mutex);
636+ return;
637+ }
638+
67af0fa4
WB
639+ backup_state.end_time = time(NULL);
640+
641+ if (backup_state.vmaobj) {
642+ object_unparent(backup_state.vmaobj);
643+ backup_state.vmaobj = NULL;
644+ }
645+
6838f038
WB
646+ g_list_free(backup_state.di_list);
647+ backup_state.di_list = NULL;
648+ qemu_mutex_unlock(&backup_state.backup_mutex);
67af0fa4
WB
649+}
650+
651+static void pvebackup_complete_cb(void *opaque, int ret)
652+{
6838f038
WB
653+ // This always runs in the main loop
654+
67af0fa4
WB
655+ PVEBackupDevInfo *di = opaque;
656+
657+ di->completed = true;
658+
659+ if (ret < 0 && !backup_state.error) {
660+ error_setg(&backup_state.error, "job failed with err %d - %s",
661+ ret, strerror(-ret));
662+ }
663+
664+ di->bs = NULL;
665+ di->target = NULL;
666+
667+ if (backup_state.vmaobj) {
668+ object_unparent(backup_state.vmaobj);
669+ backup_state.vmaobj = NULL;
670+ }
671+
6838f038
WB
672+ // remove self from job queue
673+ qemu_mutex_lock(&backup_state.backup_mutex);
674+ backup_state.di_list = g_list_remove(backup_state.di_list, di);
675+ g_free(di);
676+ qemu_mutex_unlock(&backup_state.backup_mutex);
677+
67af0fa4
WB
678+ if (!backup_state.cancel) {
679+ pvebackup_run_next_job();
680+ }
681+}
682+
683+static void pvebackup_cancel(void *opaque)
684+{
685+ backup_state.cancel = true;
6838f038
WB
686+ qemu_mutex_lock(&backup_state.backup_mutex);
687+ // Avoid race between block jobs and backup-cancel command:
688+ if (!backup_state.vmaw) {
689+ qemu_mutex_unlock(&backup_state.backup_mutex);
690+ return;
691+ }
67af0fa4
WB
692+
693+ if (!backup_state.error) {
694+ error_setg(&backup_state.error, "backup cancelled");
695+ }
696+
697+ if (backup_state.vmaobj) {
698+ Error *err;
699+ /* make sure vma writer does not block anymore */
700+ if (!object_set_props(backup_state.vmaobj, &err, "blocked", "yes", NULL)) {
701+ if (err) {
702+ error_report_err(err);
703+ }
704+ }
705+ }
706+
707+ GList *l = backup_state.di_list;
708+ while (l) {
709+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
710+ l = g_list_next(l);
711+ if (!di->completed && di->bs) {
712+ BlockJob *job = di->bs->job;
713+ if (job) {
6838f038
WB
714+ AioContext *aio_context = blk_get_aio_context(job->blk);
715+ aio_context_acquire(aio_context);
67af0fa4 716+ if (!di->completed) {
53e83913 717+ job_cancel(&job->job, false);
67af0fa4 718+ }
6838f038 719+ aio_context_release(aio_context);
67af0fa4
WB
720+ }
721+ }
722+ }
723+
6838f038 724+ qemu_mutex_unlock(&backup_state.backup_mutex);
67af0fa4
WB
725+ pvebackup_cleanup();
726+}
727+
728+void qmp_backup_cancel(Error **errp)
729+{
6838f038
WB
730+ if (!backup_state.backup_mutex_initialized)
731+ return;
67af0fa4
WB
732+ Coroutine *co = qemu_coroutine_create(pvebackup_cancel, NULL);
733+ qemu_coroutine_enter(co);
734+
735+ while (backup_state.vmaobj) {
736+ /* FIXME: Find something better for this */
737+ aio_poll(qemu_get_aio_context(), true);
738+ }
739+}
740+
741+void vma_object_add_config_file(Object *obj, const char *name,
742+ const char *contents, size_t len,
743+ Error **errp);
744+static int config_to_vma(const char *file, BackupFormat format,
745+ Object *vmaobj,
746+ const char *backup_dir,
747+ Error **errp)
748+{
6838f038
WB
749+ char *cdata = NULL;
750+ gsize clen = 0;
751+ GError *err = NULL;
752+ if (!g_file_get_contents(file, &cdata, &clen, &err)) {
753+ error_setg(errp, "unable to read file '%s'", file);
754+ return 1;
755+ }
67af0fa4 756+
6838f038 757+ char *basename = g_path_get_basename(file);
67af0fa4 758+
6838f038
WB
759+ if (format == BACKUP_FORMAT_VMA) {
760+ vma_object_add_config_file(vmaobj, basename, cdata, clen, errp);
761+ } else if (format == BACKUP_FORMAT_DIR) {
762+ char config_path[PATH_MAX];
763+ snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, basename);
764+ if (!g_file_set_contents(config_path, cdata, clen, &err)) {
765+ error_setg(errp, "unable to write config file '%s'", config_path);
766+ g_free(cdata);
767+ g_free(basename);
768+ return 1;
769+ }
770+ }
67af0fa4 771+
6838f038
WB
772+ g_free(basename);
773+ g_free(cdata);
774+ return 0;
67af0fa4
WB
775+}
776+
777+static void pvebackup_run_next_job(void)
778+{
6838f038
WB
779+ qemu_mutex_lock(&backup_state.backup_mutex);
780+
67af0fa4
WB
781+ GList *next = g_list_nth(backup_state.di_list, backup_state.next_job);
782+ while (next) {
783+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)next->data;
784+ backup_state.next_job++;
785+ if (!di->completed && di->bs && di->bs->job) {
786+ BlockJob *job = di->bs->job;
6838f038
WB
787+ AioContext *aio_context = blk_get_aio_context(job->blk);
788+ aio_context_acquire(aio_context);
789+ qemu_mutex_unlock(&backup_state.backup_mutex);
790+ if (backup_state.error || backup_state.cancel) {
53e83913 791+ job_cancel_sync(job);
67af0fa4 792+ } else {
53e83913 793+ job_resume(job);
67af0fa4 794+ }
6838f038 795+ aio_context_release(aio_context);
67af0fa4
WB
796+ return;
797+ }
798+ next = g_list_next(next);
799+ }
6838f038
WB
800+ qemu_mutex_unlock(&backup_state.backup_mutex);
801+
802+ // no more jobs, run the cleanup
67af0fa4
WB
803+ pvebackup_cleanup();
804+}
805+
806+UuidInfo *qmp_backup(const char *backup_file, bool has_format,
807+ BackupFormat format,
808+ bool has_config_file, const char *config_file,
809+ bool has_firewall_file, const char *firewall_file,
810+ bool has_devlist, const char *devlist,
811+ bool has_speed, int64_t speed, Error **errp)
812+{
813+ BlockBackend *blk;
814+ BlockDriverState *bs = NULL;
815+ const char *backup_dir = NULL;
816+ Error *local_err = NULL;
817+ QemuUUID uuid;
818+ gchar **devs = NULL;
819+ GList *di_list = NULL;
820+ GList *l;
821+ UuidInfo *uuid_info;
822+ BlockJob *job;
823+
6838f038
WB
824+ if (!backup_state.backup_mutex_initialized) {
825+ qemu_mutex_init(&backup_state.backup_mutex);
826+ backup_state.backup_mutex_initialized = true;
827+ }
828+
67af0fa4
WB
829+ if (backup_state.di_list || backup_state.vmaobj) {
830+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
831+ "previous backup not finished");
832+ return NULL;
833+ }
834+
835+ /* Todo: try to auto-detect format based on file name */
836+ format = has_format ? format : BACKUP_FORMAT_VMA;
837+
838+ if (has_devlist) {
839+ devs = g_strsplit_set(devlist, ",;:", -1);
840+
841+ gchar **d = devs;
842+ while (d && *d) {
843+ blk = blk_by_name(*d);
844+ if (blk) {
845+ bs = blk_bs(blk);
846+ if (bdrv_is_read_only(bs)) {
847+ error_setg(errp, "Node '%s' is read only", *d);
848+ goto err;
849+ }
850+ if (!bdrv_is_inserted(bs)) {
851+ error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
852+ goto err;
853+ }
854+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
855+ di->bs = bs;
856+ di_list = g_list_append(di_list, di);
857+ } else {
858+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
859+ "Device '%s' not found", *d);
860+ goto err;
861+ }
862+ d++;
863+ }
864+
865+ } else {
866+ BdrvNextIterator it;
867+
868+ bs = NULL;
869+ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
870+ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
871+ continue;
872+ }
873+
874+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
875+ di->bs = bs;
876+ di_list = g_list_append(di_list, di);
877+ }
878+ }
879+
880+ if (!di_list) {
881+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
882+ goto err;
883+ }
884+
885+ size_t total = 0;
886+
887+ l = di_list;
888+ while (l) {
889+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
890+ l = g_list_next(l);
891+ if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
892+ goto err;
893+ }
894+
895+ ssize_t size = bdrv_getlength(di->bs);
896+ if (size < 0) {
897+ error_setg_errno(errp, -di->size, "bdrv_getlength failed");
898+ goto err;
899+ }
900+ di->size = size;
901+ total += size;
902+ }
903+
904+ qemu_uuid_generate(&uuid);
905+
906+ if (format == BACKUP_FORMAT_VMA) {
907+ char uuidstr[UUID_FMT_LEN+1];
908+ qemu_uuid_unparse(&uuid, uuidstr);
909+ uuidstr[UUID_FMT_LEN] = 0;
910+ backup_state.vmaobj =
911+ object_new_with_props("vma", object_get_objects_root(),
912+ "vma-backup-obj", &local_err,
913+ "filename", backup_file,
914+ "uuid", uuidstr,
915+ NULL);
916+ if (!backup_state.vmaobj) {
917+ if (local_err) {
918+ error_propagate(errp, local_err);
919+ }
920+ goto err;
921+ }
922+
923+ l = di_list;
924+ while (l) {
925+ QDict *options = qdict_new();
926+
927+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
928+ l = g_list_next(l);
929+
930+ const char *devname = bdrv_get_device_name(di->bs);
931+ snprintf(di->targetfile, PATH_MAX, "vma-backup-obj/%s.raw", devname);
932+
933+ qdict_put(options, "driver", qstring_from_str("vma-drive"));
934+ qdict_put(options, "size", qint_from_int(di->size));
935+ di->target = bdrv_open(di->targetfile, NULL, options, BDRV_O_RDWR, &local_err);
936+ if (!di->target) {
937+ error_propagate(errp, local_err);
938+ goto err;
939+ }
940+ }
941+ } else if (format == BACKUP_FORMAT_DIR) {
942+ if (mkdir(backup_file, 0640) != 0) {
943+ error_setg_errno(errp, errno, "can't create directory '%s'\n",
944+ backup_file);
945+ goto err;
946+ }
947+ backup_dir = backup_file;
948+
949+ l = di_list;
950+ while (l) {
951+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
952+ l = g_list_next(l);
953+
954+ const char *devname = bdrv_get_device_name(di->bs);
955+ snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
956+
957+ int flags = BDRV_O_RDWR;
958+ bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
6838f038 959+ di->size, flags, false, &local_err);
67af0fa4
WB
960+ if (local_err) {
961+ error_propagate(errp, local_err);
962+ goto err;
963+ }
964+
965+ di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
966+ if (!di->target) {
967+ error_propagate(errp, local_err);
968+ goto err;
969+ }
970+ }
971+ } else {
6838f038
WB
972+ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
973+ goto err;
67af0fa4
WB
974+ }
975+
976+ /* add configuration file to archive */
977+ if (has_config_file) {
6838f038
WB
978+ if(config_to_vma(config_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
979+ goto err;
980+ }
67af0fa4
WB
981+ }
982+
983+ /* add firewall file to archive */
984+ if (has_firewall_file) {
6838f038
WB
985+ if(config_to_vma(firewall_file, format, backup_state.vmaobj, backup_dir, errp) != 0) {
986+ goto err;
987+ }
67af0fa4
WB
988+ }
989+ /* initialize global backup_state now */
990+
991+ backup_state.cancel = false;
992+
993+ if (backup_state.error) {
994+ error_free(backup_state.error);
995+ backup_state.error = NULL;
996+ }
997+
998+ backup_state.speed = (has_speed && speed > 0) ? speed : 0;
999+
1000+ backup_state.start_time = time(NULL);
1001+ backup_state.end_time = 0;
1002+
1003+ if (backup_state.backup_file) {
1004+ g_free(backup_state.backup_file);
1005+ }
1006+ backup_state.backup_file = g_strdup(backup_file);
1007+
1008+ memcpy(&backup_state.uuid, &uuid, sizeof(uuid));
1009+ qemu_uuid_unparse(&uuid, backup_state.uuid_str);
1010+
6838f038 1011+ qemu_mutex_lock(&backup_state.backup_mutex);
67af0fa4
WB
1012+ backup_state.di_list = di_list;
1013+ backup_state.next_job = 0;
1014+
1015+ backup_state.total = total;
1016+ backup_state.transferred = 0;
1017+ backup_state.zero_bytes = 0;
1018+
1019+ /* start all jobs (paused state) */
1020+ l = di_list;
1021+ while (l) {
1022+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1023+ l = g_list_next(l);
1024+
1025+ job = backup_job_create(NULL, di->bs, di->target, speed, MIRROR_SYNC_MODE_FULL, NULL,
1026+ false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
53e83913 1027+ JOB_DEFAULT,
67af0fa4
WB
1028+ pvebackup_complete_cb, di, 2, NULL, &local_err);
1029+ if (di->target) {
1030+ bdrv_unref(di->target);
1031+ di->target = NULL;
1032+ }
1033+ if (!job || local_err != NULL) {
1034+ error_setg(&backup_state.error, "backup_job_create failed");
1035+ pvebackup_cancel(NULL);
1036+ } else {
53e83913 1037+ job_start(&job->job);
67af0fa4
WB
1038+ }
1039+ }
1040+
6838f038
WB
1041+ qemu_mutex_unlock(&backup_state.backup_mutex);
1042+
67af0fa4
WB
1043+ if (!backup_state.error) {
1044+ pvebackup_run_next_job(); // run one job
1045+ }
1046+
1047+ uuid_info = g_malloc0(sizeof(*uuid_info));
1048+ uuid_info->UUID = g_strdup(backup_state.uuid_str);
1049+
1050+ return uuid_info;
1051+
1052+err:
1053+
1054+ l = di_list;
1055+ while (l) {
1056+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1057+ l = g_list_next(l);
1058+
1059+ if (di->target) {
1060+ bdrv_unref(di->target);
1061+ }
1062+
1063+ if (di->targetfile[0]) {
1064+ unlink(di->targetfile);
1065+ }
1066+ g_free(di);
1067+ }
1068+ g_list_free(di_list);
1069+
1070+ if (devs) {
1071+ g_strfreev(devs);
1072+ }
1073+
1074+ if (backup_state.vmaobj) {
1075+ object_unparent(backup_state.vmaobj);
1076+ backup_state.vmaobj = NULL;
1077+ }
1078+
1079+ if (backup_dir) {
1080+ rmdir(backup_dir);
1081+ }
1082+
1083+ return NULL;
1084+}
1085+
1086+BackupStatus *qmp_query_backup(Error **errp)
1087+{
1088+ BackupStatus *info = g_malloc0(sizeof(*info));
1089+
1090+ if (!backup_state.start_time) {
1091+ /* not started, return {} */
1092+ return info;
1093+ }
1094+
1095+ info->has_status = true;
1096+ info->has_start_time = true;
1097+ info->start_time = backup_state.start_time;
1098+
1099+ if (backup_state.backup_file) {
1100+ info->has_backup_file = true;
1101+ info->backup_file = g_strdup(backup_state.backup_file);
1102+ }
1103+
1104+ info->has_uuid = true;
1105+ info->uuid = g_strdup(backup_state.uuid_str);
1106+
1107+ if (backup_state.end_time) {
1108+ if (backup_state.error) {
1109+ info->status = g_strdup("error");
1110+ info->has_errmsg = true;
1111+ info->errmsg = g_strdup(error_get_pretty(backup_state.error));
1112+ } else {
1113+ info->status = g_strdup("done");
1114+ }
1115+ info->has_end_time = true;
1116+ info->end_time = backup_state.end_time;
1117+ } else {
1118+ info->status = g_strdup("active");
1119+ }
1120+
1121+ info->has_total = true;
1122+ info->total = backup_state.total;
1123+ info->has_zero_bytes = true;
1124+ info->zero_bytes = backup_state.zero_bytes;
1125+ info->has_transferred = true;
1126+ info->transferred = backup_state.transferred;
1127+
1128+ return info;
1129+}
1130+
1131 void qmp_block_stream(bool has_job_id, const char *job_id, const char *device,
1132 bool has_base, const char *base,
1133 bool has_base_node, const char *base_node,
1134diff --git a/configure b/configure
b855dce7 1135index 1c563a7027..d164677950 100755
67af0fa4
WB
1136--- a/configure
1137+++ b/configure
b855dce7 1138@@ -491,6 +491,7 @@ docker="no"
53e83913 1139 debug_mutex="no"
b855dce7
TL
1140 libpmem=""
1141 default_devices="yes"
67af0fa4
WB
1142+vma=""
1143
53e83913
WB
1144 # cross compilers defaults, can be overridden with --cross-cc-ARCH
1145 cross_cc_aarch64="aarch64-linux-gnu-gcc"
b855dce7 1146@@ -1518,6 +1519,10 @@ for opt do
67af0fa4 1147 ;;
b855dce7 1148 --disable-libpmem) libpmem=no
67af0fa4 1149 ;;
53e83913 1150+ --enable-vma) vma=yes
67af0fa4 1151+ ;;
53e83913 1152+ --disable-vma) vma=no
67af0fa4
WB
1153+ ;;
1154 *)
1155 echo "ERROR: unknown option $opt"
1156 echo "Try '$0 --help' for more information"
b855dce7 1157@@ -1818,6 +1823,7 @@ disabled with --disable-FEATURE, default is enabled if available:
6838f038 1158 capstone capstone disassembler support
53e83913 1159 debug-mutex mutex debugging support
b855dce7 1160 libpmem libpmem support
67af0fa4
WB
1161+ vma VMA archive backend
1162
1163 NOTE: The object files are built at the place where configure is launched
1164 EOF
b855dce7
TL
1165@@ -4378,6 +4384,22 @@ EOF
1166 fi
67af0fa4
WB
1167 fi
1168
b855dce7 1169+##########################################
67af0fa4
WB
1170+# vma probe
1171+if test "$vma" != "no" ; then
1172+ if $pkg_config --exact-version=0.1.0 vma; then
1173+ vma="yes"
1174+ vma_cflags=$($pkg_config --cflags vma)
1175+ vma_libs=$($pkg_config --libs vma)
1176+ else
1177+ if test "$vma" = "yes" ; then
1178+ feature_not_found "VMA Archive backend support" \
1179+ "Install libvma devel"
1180+ fi
1181+ vma="no"
1182+ fi
1183+fi
1184+
b855dce7 1185 ##########################################
67af0fa4
WB
1186 # signalfd probe
1187 signalfd="no"
b855dce7
TL
1188@@ -6438,6 +6460,7 @@ echo "docker $docker"
1189 echo "libpmem support $libpmem"
1190 echo "libudev $libudev"
1191 echo "default devices $default_devices"
67af0fa4
WB
1192+echo "VMA support $vma"
1193
b855dce7
TL
1194 if test "$supported_cpu" = "no"; then
1195 echo
1196@@ -6931,6 +6954,12 @@ if test "$usb_redir" = "yes" ; then
53e83913 1197 echo "USB_REDIR_LIBS=$usb_redir_libs" >> $config_host_mak
67af0fa4
WB
1198 fi
1199
1200+if test "$vma" = "yes" ; then
1201+ echo "CONFIG_VMA=y" >> $config_host_mak
1202+ echo "VMA_CFLAGS=$vma_cflags" >> $config_host_mak
1203+ echo "VMA_LIBS=$vma_libs" >> $config_host_mak
1204+fi
1205+
53e83913
WB
1206 if test "$opengl" = "yes" ; then
1207 echo "CONFIG_OPENGL=y" >> $config_host_mak
1208 echo "OPENGL_LIBS=$opengl_libs" >> $config_host_mak
67af0fa4 1209diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
b855dce7 1210index 444bd8e43d..21106bcbe6 100644
67af0fa4
WB
1211--- a/hmp-commands-info.hx
1212+++ b/hmp-commands-info.hx
b855dce7
TL
1213@@ -536,6 +536,19 @@ STEXI
1214 @item info cpustats
1215 @findex info cpustats
67af0fa4 1216 Show CPU statistics.
b855dce7
TL
1217+ETEXI
1218+
67af0fa4
WB
1219+ {
1220+ .name = "backup",
1221+ .args_type = "",
1222+ .params = "",
1223+ .help = "show backup status",
1224+ .cmd = hmp_info_backup,
1225+ },
1226+
1227+STEXI
1228+@item info backup
1229+show backup status
b855dce7
TL
1230 ETEXI
1231
67af0fa4 1232 #if defined(CONFIG_SLIRP)
67af0fa4 1233diff --git a/hmp-commands.hx b/hmp-commands.hx
b855dce7 1234index 284e97973a..d723552bee 100644
67af0fa4
WB
1235--- a/hmp-commands.hx
1236+++ b/hmp-commands.hx
b855dce7
TL
1237@@ -105,6 +105,37 @@ STEXI
1238 @item block_stream
1239 @findex block_stream
67af0fa4 1240 Copy data from a backing file into a block device.
b855dce7
TL
1241+ETEXI
1242+
67af0fa4
WB
1243+ {
1244+ .name = "backup",
1245+ .args_type = "directory:-d,backupfile:s,speed:o?,devlist:s?",
1246+ .params = "[-d] backupfile [speed [devlist]]",
1247+ .help = "create a VM Backup."
1248+ "\n\t\t\t Use -d to dump data into a directory instead"
1249+ "\n\t\t\t of using VMA format.",
1250+ .cmd = hmp_backup,
1251+ },
1252+
1253+STEXI
1254+@item backup
1255+@findex backup
1256+Create a VM backup.
1257+ETEXI
1258+
1259+ {
1260+ .name = "backup_cancel",
1261+ .args_type = "",
1262+ .params = "",
1263+ .help = "cancel the current VM backup",
1264+ .cmd = hmp_backup_cancel,
1265+ },
1266+
1267+STEXI
1268+@item backup_cancel
1269+@findex backup_cancel
1270+Cancel the current VM backup.
1271+
b855dce7
TL
1272 ETEXI
1273
67af0fa4 1274 {
67af0fa4 1275diff --git a/hmp.c b/hmp.c
b855dce7 1276index 16243bba50..113671ad2a 100644
67af0fa4
WB
1277--- a/hmp.c
1278+++ b/hmp.c
b855dce7 1279@@ -167,6 +167,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
67af0fa4
WB
1280 qapi_free_MouseInfoList(mice_list);
1281 }
1282
1283+void hmp_info_backup(Monitor *mon, const QDict *qdict)
1284+{
1285+ BackupStatus *info;
1286+
1287+ info = qmp_query_backup(NULL);
1288+ if (info->has_status) {
1289+ if (info->has_errmsg) {
1290+ monitor_printf(mon, "Backup status: %s - %s\n",
1291+ info->status, info->errmsg);
1292+ } else {
1293+ monitor_printf(mon, "Backup status: %s\n", info->status);
1294+ }
1295+ }
1296+
1297+ if (info->has_backup_file) {
1298+ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
1299+ if (info->end_time) {
1300+ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
1301+ }
1302+
1303+ int per = (info->has_total && info->total &&
1304+ info->has_transferred && info->transferred) ?
1305+ (info->transferred * 100)/info->total : 0;
1306+ int zero_per = (info->has_total && info->total &&
1307+ info->has_zero_bytes && info->zero_bytes) ?
1308+ (info->zero_bytes * 100)/info->total : 0;
1309+ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
1310+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
1311+ monitor_printf(mon, "Total size: %zd\n", info->total);
1312+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
1313+ info->transferred, per);
1314+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
1315+ info->zero_bytes, zero_per);
1316+ }
1317+
1318+ qapi_free_BackupStatus(info);
1319+}
1320+
b855dce7 1321 static char *SocketAddress_to_str(SocketAddress *addr)
67af0fa4 1322 {
b855dce7
TL
1323 switch (addr->type) {
1324@@ -2059,6 +2097,31 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
67af0fa4
WB
1325 hmp_handle_error(mon, &error);
1326 }
1327
1328+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
1329+{
1330+ Error *error = NULL;
1331+
1332+ qmp_backup_cancel(&error);
1333+
1334+ hmp_handle_error(mon, &error);
1335+}
1336+
1337+void hmp_backup(Monitor *mon, const QDict *qdict)
1338+{
1339+ Error *error = NULL;
1340+
1341+ int dir = qdict_get_try_bool(qdict, "directory", 0);
1342+ const char *backup_file = qdict_get_str(qdict, "backupfile");
1343+ const char *devlist = qdict_get_try_str(qdict, "devlist");
1344+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
1345+
1346+ qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
1347+ false, NULL, false, NULL, !!devlist,
1348+ devlist, qdict_haskey(qdict, "speed"), speed, &error);
1349+
1350+ hmp_handle_error(mon, &error);
1351+}
1352+
1353 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
1354 {
1355 Error *error = NULL;
1356diff --git a/hmp.h b/hmp.h
b855dce7 1357index bcb90c478f..043b74edee 100644
67af0fa4
WB
1358--- a/hmp.h
1359+++ b/hmp.h
53e83913 1360@@ -29,6 +29,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
67af0fa4
WB
1361 void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
1362 void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
1363 void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
1364+void hmp_info_backup(Monitor *mon, const QDict *qdict);
1365 void hmp_info_cpus(Monitor *mon, const QDict *qdict);
1366 void hmp_info_block(Monitor *mon, const QDict *qdict);
1367 void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
b855dce7 1368@@ -89,6 +90,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
67af0fa4
WB
1369 void hmp_change(Monitor *mon, const QDict *qdict);
1370 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
1371 void hmp_block_stream(Monitor *mon, const QDict *qdict);
1372+void hmp_backup(Monitor *mon, const QDict *qdict);
1373+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
1374 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
1375 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
1376 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
6838f038 1377diff --git a/qapi/block-core.json b/qapi/block-core.json
b855dce7 1378index 7ccbfff9d0..6f97460806 100644
6838f038
WB
1379--- a/qapi/block-core.json
1380+++ b/qapi/block-core.json
b855dce7
TL
1381@@ -796,6 +796,97 @@
1382 { 'command': 'query-block', 'returns': ['BlockInfo'] }
6838f038 1383
67af0fa4 1384
b855dce7 1385+##
67af0fa4
WB
1386+# @BackupStatus:
1387+#
1388+# Detailed backup status.
1389+#
1390+# @status: string describing the current backup status.
1391+# This can be 'active', 'done', 'error'. If this field is not
1392+# returned, no backup process has been initiated
1393+#
1394+# @errmsg: error message (only returned if status is 'error')
1395+#
1396+# @total: total amount of bytes involved in the backup process
1397+#
1398+# @transferred: amount of bytes already backed up.
1399+#
1400+# @zero-bytes: amount of 'zero' bytes detected.
1401+#
1402+# @start-time: time (epoch) when backup job started.
1403+#
1404+# @end-time: time (epoch) when backup job finished.
1405+#
1406+# @backup-file: backup file name
1407+#
1408+# @uuid: uuid for this backup job
1409+#
1410+##
1411+{ 'struct': 'BackupStatus',
1412+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
1413+ '*transferred': 'int', '*zero-bytes': 'int',
1414+ '*start-time': 'int', '*end-time': 'int',
1415+ '*backup-file': 'str', '*uuid': 'str' } }
1416+
1417+##
1418+# @BackupFormat:
1419+#
1420+# An enumeration of supported backup formats.
1421+#
1422+# @vma: Proxmox vma backup format
1423+##
1424+{ 'enum': 'BackupFormat',
1425+ 'data': [ 'vma', 'dir' ] }
1426+
1427+##
1428+# @backup:
1429+#
1430+# Starts a VM backup.
1431+#
1432+# @backup-file: the backup file name
1433+#
1434+# @format: format of the backup file
1435+#
1436+# @config-file: a configuration file to include into
1437+# the backup archive.
1438+#
1439+# @speed: the maximum speed, in bytes per second
1440+#
1441+# @devlist: list of block device names (separated by ',', ';'
1442+# or ':'). By default the backup includes all writable block devices.
1443+#
1444+# Returns: the uuid of the backup job
1445+#
1446+##
1447+{ 'command': 'backup', 'data': { 'backup-file': 'str',
1448+ '*format': 'BackupFormat',
1449+ '*config-file': 'str',
1450+ '*firewall-file': 'str',
1451+ '*devlist': 'str', '*speed': 'int' },
1452+ 'returns': 'UuidInfo' }
1453+
1454+##
1455+# @query-backup:
1456+#
1457+# Returns information about current/last backup task.
1458+#
1459+# Returns: @BackupStatus
1460+#
1461+##
1462+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1463+
1464+##
1465+# @backup-cancel:
1466+#
1467+# Cancel the current executing backup process.
1468+#
1469+# Returns: nothing on success
1470+#
1471+# Notes: This command succeeds even if there is no backup process running.
1472+#
1473+##
1474+{ 'command': 'backup-cancel' }
1475+
b855dce7 1476 ##
6838f038 1477 # @BlockDeviceTimedStats:
67af0fa4 1478 #
b855dce7
TL
1479@@ -2819,7 +2910,7 @@
1480 'qcow2', 'qed', 'quorum', 'raw', 'rbd',
1481 { 'name': 'replication', 'if': 'defined(CONFIG_REPLICATION)' },
1482 'sheepdog',
53e83913
WB
1483- 'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
1484+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vma-drive', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
67af0fa4
WB
1485
1486 ##
1487 # @BlockdevOptionsFile:
b855dce7
TL
1488@@ -3836,6 +3927,21 @@
1489 'server': 'InetSocketAddressBase',
6838f038 1490 '*tls-creds': 'str' } }
67af0fa4 1491
b855dce7 1492+##
67af0fa4
WB
1493+# @BlockdevOptionsVMADrive:
1494+#
1495+# Driver specific block device options for VMA Drives
1496+#
1497+# @filename: vma-drive path
1498+#
1499+# @size: drive size in bytes
1500+#
1501+# Since: 2.9
1502+##
1503+{ 'struct': 'BlockdevOptionsVMADrive',
1504+ 'data': { 'filename': 'str',
1505+ 'size': 'int' } }
1506+
b855dce7 1507 ##
6838f038 1508 # @BlockdevOptionsThrottle:
67af0fa4 1509 #
b855dce7 1510@@ -3931,6 +4037,7 @@
6838f038
WB
1511 'throttle': 'BlockdevOptionsThrottle',
1512 'vdi': 'BlockdevOptionsGenericFormat',
67af0fa4 1513 'vhdx': 'BlockdevOptionsGenericFormat',
6838f038 1514+ 'vma-drive': 'BlockdevOptionsVMADrive',
67af0fa4
WB
1515 'vmdk': 'BlockdevOptionsGenericCOWFormat',
1516 'vpc': 'BlockdevOptionsGenericFormat',
6838f038 1517 'vvfat': 'BlockdevOptionsVVFAT',
53e83913 1518diff --git a/qapi/common.json b/qapi/common.json
b855dce7 1519index 99d313ef3b..bae0650c51 100644
53e83913
WB
1520--- a/qapi/common.json
1521+++ b/qapi/common.json
b855dce7
TL
1522@@ -193,3 +193,16 @@
1523 'ppc64', 'riscv32', 'riscv64', 's390x', 'sh4',
53e83913
WB
1524 'sh4eb', 'sparc', 'sparc64', 'tricore', 'unicore32',
1525 'x86_64', 'xtensa', 'xtensaeb' ] }
1526+
1527+##
1528+# @UuidInfo:
1529+#
1530+# Guest UUID information (Universally Unique Identifier).
1531+#
1532+# @UUID: the UUID of the guest
1533+#
1534+# Since: 0.14.0
1535+#
1536+# Notes: If no UUID was specified for the guest, a null UUID is returned.
1537+##
1538+{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1539diff --git a/qapi/misc.json b/qapi/misc.json
b855dce7 1540index 49dfda0b28..376f26002a 100644
53e83913
WB
1541--- a/qapi/misc.json
1542+++ b/qapi/misc.json
b855dce7
TL
1543@@ -274,19 +274,6 @@
1544 ##
53e83913
WB
1545 { 'command': 'query-kvm', 'returns': 'KvmInfo' }
1546
b855dce7 1547-##
53e83913
WB
1548-# @UuidInfo:
1549-#
1550-# Guest UUID information (Universally Unique Identifier).
1551-#
1552-# @UUID: the UUID of the guest
1553-#
1554-# Since: 0.14.0
1555-#
1556-# Notes: If no UUID was specified for the guest, a null UUID is returned.
1557-##
1558-{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1559-
b855dce7 1560 ##
53e83913
WB
1561 # @query-uuid:
1562 #