]> git.proxmox.com Git - pve-qemu.git/blame - debian/patches/pve/0020-PVE-backup-introduce-vma-archive-format.patch
Update and rebase to QEMU 4.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 8---
be901f66
SR
9 MAINTAINERS | 6 +
10 block/Makefile.objs | 3 +
11 block/vma.c | 503 +++++++++++++++++++++++++++++++++++++++
12 blockdev.c | 536 ++++++++++++++++++++++++++++++++++++++++++
13 configure | 30 +++
14 hmp-commands-info.hx | 13 +
15 hmp-commands.hx | 31 +++
16 include/monitor/hmp.h | 3 +
17 monitor/hmp-cmds.c | 63 +++++
18 qapi/block-core.json | 109 ++++++++-
19 qapi/common.json | 13 +
20 qapi/misc.json | 13 -
21 12 files changed, 1309 insertions(+), 14 deletions(-)
67af0fa4
WB
22 create mode 100644 block/vma.c
23
24diff --git a/MAINTAINERS b/MAINTAINERS
be901f66 25index d6de200453..de09d099f6 100644
67af0fa4
WB
26--- a/MAINTAINERS
27+++ b/MAINTAINERS
be901f66 28@@ -2563,6 +2563,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
be901f66 42index 6022242c3f..86b10d8ea7 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
be901f66 48 block-obj-$(CONFIG_LIBSSH) += ssh.o
67af0fa4
WB
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
be901f66 569index a7c97b1585..7047475a3c 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"
be901f66 586@@ -45,6 +47,7 @@
53e83913
WB
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"
be901f66 594@@ -3168,6 +3171,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
be901f66 1135index 714e7fb6a1..5e16d8e562 100755
67af0fa4
WB
1136--- a/configure
1137+++ b/configure
be901f66 1138@@ -499,6 +499,8 @@ docker="no"
53e83913 1139 debug_mutex="no"
b855dce7
TL
1140 libpmem=""
1141 default_devices="yes"
be901f66 1142+libudev="no"
67af0fa4
WB
1143+vma=""
1144
53e83913
WB
1145 # cross compilers defaults, can be overridden with --cross-cc-ARCH
1146 cross_cc_aarch64="aarch64-linux-gnu-gcc"
be901f66 1147@@ -1543,6 +1545,10 @@ for opt do
67af0fa4 1148 ;;
b855dce7 1149 --disable-libpmem) libpmem=no
67af0fa4 1150 ;;
53e83913 1151+ --enable-vma) vma=yes
67af0fa4 1152+ ;;
53e83913 1153+ --disable-vma) vma=no
67af0fa4
WB
1154+ ;;
1155 *)
1156 echo "ERROR: unknown option $opt"
1157 echo "Try '$0 --help' for more information"
be901f66 1158@@ -1842,6 +1848,7 @@ disabled with --disable-FEATURE, default is enabled if available:
6838f038 1159 capstone capstone disassembler support
53e83913 1160 debug-mutex mutex debugging support
b855dce7 1161 libpmem libpmem support
67af0fa4
WB
1162+ vma VMA archive backend
1163
1164 NOTE: The object files are built at the place where configure is launched
1165 EOF
be901f66 1166@@ -4402,6 +4409,22 @@ EOF
b855dce7 1167 fi
67af0fa4
WB
1168 fi
1169
b855dce7 1170+##########################################
67af0fa4
WB
1171+# vma probe
1172+if test "$vma" != "no" ; then
1173+ if $pkg_config --exact-version=0.1.0 vma; then
1174+ vma="yes"
1175+ vma_cflags=$($pkg_config --cflags vma)
1176+ vma_libs=$($pkg_config --libs vma)
1177+ else
1178+ if test "$vma" = "yes" ; then
1179+ feature_not_found "VMA Archive backend support" \
1180+ "Install libvma devel"
1181+ fi
1182+ vma="no"
1183+ fi
1184+fi
1185+
b855dce7 1186 ##########################################
67af0fa4
WB
1187 # signalfd probe
1188 signalfd="no"
be901f66 1189@@ -6481,6 +6504,7 @@ echo "docker $docker"
b855dce7
TL
1190 echo "libpmem support $libpmem"
1191 echo "libudev $libudev"
1192 echo "default devices $default_devices"
67af0fa4
WB
1193+echo "VMA support $vma"
1194
b855dce7
TL
1195 if test "$supported_cpu" = "no"; then
1196 echo
be901f66 1197@@ -6983,6 +7007,12 @@ if test "$usb_redir" = "yes" ; then
53e83913 1198 echo "USB_REDIR_LIBS=$usb_redir_libs" >> $config_host_mak
67af0fa4
WB
1199 fi
1200
1201+if test "$vma" = "yes" ; then
1202+ echo "CONFIG_VMA=y" >> $config_host_mak
1203+ echo "VMA_CFLAGS=$vma_cflags" >> $config_host_mak
1204+ echo "VMA_LIBS=$vma_libs" >> $config_host_mak
1205+fi
1206+
53e83913
WB
1207 if test "$opengl" = "yes" ; then
1208 echo "CONFIG_OPENGL=y" >> $config_host_mak
1209 echo "OPENGL_LIBS=$opengl_libs" >> $config_host_mak
67af0fa4 1210diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
b855dce7 1211index 444bd8e43d..21106bcbe6 100644
67af0fa4
WB
1212--- a/hmp-commands-info.hx
1213+++ b/hmp-commands-info.hx
b855dce7
TL
1214@@ -536,6 +536,19 @@ STEXI
1215 @item info cpustats
1216 @findex info cpustats
67af0fa4 1217 Show CPU statistics.
b855dce7
TL
1218+ETEXI
1219+
67af0fa4
WB
1220+ {
1221+ .name = "backup",
1222+ .args_type = "",
1223+ .params = "",
1224+ .help = "show backup status",
1225+ .cmd = hmp_info_backup,
1226+ },
1227+
1228+STEXI
1229+@item info backup
1230+show backup status
b855dce7
TL
1231 ETEXI
1232
67af0fa4 1233 #if defined(CONFIG_SLIRP)
67af0fa4 1234diff --git a/hmp-commands.hx b/hmp-commands.hx
be901f66 1235index e075d413c0..5bdfdeaf57 100644
67af0fa4
WB
1236--- a/hmp-commands.hx
1237+++ b/hmp-commands.hx
b855dce7
TL
1238@@ -105,6 +105,37 @@ STEXI
1239 @item block_stream
1240 @findex block_stream
67af0fa4 1241 Copy data from a backing file into a block device.
b855dce7
TL
1242+ETEXI
1243+
67af0fa4
WB
1244+ {
1245+ .name = "backup",
1246+ .args_type = "directory:-d,backupfile:s,speed:o?,devlist:s?",
1247+ .params = "[-d] backupfile [speed [devlist]]",
1248+ .help = "create a VM Backup."
1249+ "\n\t\t\t Use -d to dump data into a directory instead"
1250+ "\n\t\t\t of using VMA format.",
1251+ .cmd = hmp_backup,
1252+ },
1253+
1254+STEXI
1255+@item backup
1256+@findex backup
1257+Create a VM backup.
1258+ETEXI
1259+
1260+ {
1261+ .name = "backup_cancel",
1262+ .args_type = "",
1263+ .params = "",
1264+ .help = "cancel the current VM backup",
1265+ .cmd = hmp_backup_cancel,
1266+ },
1267+
1268+STEXI
1269+@item backup_cancel
1270+@findex backup_cancel
1271+Cancel the current VM backup.
1272+
b855dce7
TL
1273 ETEXI
1274
67af0fa4 1275 {
be901f66
SR
1276diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
1277index c6ee8295f0..0f2f96c4af 100644
1278--- a/include/monitor/hmp.h
1279+++ b/include/monitor/hmp.h
1280@@ -30,6 +30,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
1281 void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
1282 void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
1283 void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
1284+void hmp_info_backup(Monitor *mon, const QDict *qdict);
1285 void hmp_info_cpus(Monitor *mon, const QDict *qdict);
1286 void hmp_info_block(Monitor *mon, const QDict *qdict);
1287 void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
1288@@ -90,6 +91,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
1289 void hmp_change(Monitor *mon, const QDict *qdict);
1290 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
1291 void hmp_block_stream(Monitor *mon, const QDict *qdict);
1292+void hmp_backup(Monitor *mon, const QDict *qdict);
1293+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
1294 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
1295 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
1296 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
1297diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
1298index 39a8020367..bc9ca346f7 100644
1299--- a/monitor/hmp-cmds.c
1300+++ b/monitor/hmp-cmds.c
1301@@ -196,6 +196,44 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
67af0fa4
WB
1302 qapi_free_MouseInfoList(mice_list);
1303 }
1304
1305+void hmp_info_backup(Monitor *mon, const QDict *qdict)
1306+{
1307+ BackupStatus *info;
1308+
1309+ info = qmp_query_backup(NULL);
1310+ if (info->has_status) {
1311+ if (info->has_errmsg) {
1312+ monitor_printf(mon, "Backup status: %s - %s\n",
1313+ info->status, info->errmsg);
1314+ } else {
1315+ monitor_printf(mon, "Backup status: %s\n", info->status);
1316+ }
1317+ }
1318+
1319+ if (info->has_backup_file) {
1320+ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
1321+ if (info->end_time) {
1322+ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
1323+ }
1324+
1325+ int per = (info->has_total && info->total &&
1326+ info->has_transferred && info->transferred) ?
1327+ (info->transferred * 100)/info->total : 0;
1328+ int zero_per = (info->has_total && info->total &&
1329+ info->has_zero_bytes && info->zero_bytes) ?
1330+ (info->zero_bytes * 100)/info->total : 0;
1331+ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
1332+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
1333+ monitor_printf(mon, "Total size: %zd\n", info->total);
1334+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
1335+ info->transferred, per);
1336+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
1337+ info->zero_bytes, zero_per);
1338+ }
1339+
1340+ qapi_free_BackupStatus(info);
1341+}
1342+
b855dce7 1343 static char *SocketAddress_to_str(SocketAddress *addr)
67af0fa4 1344 {
b855dce7 1345 switch (addr->type) {
be901f66 1346@@ -2078,6 +2116,31 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
67af0fa4
WB
1347 hmp_handle_error(mon, &error);
1348 }
1349
1350+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
1351+{
1352+ Error *error = NULL;
1353+
1354+ qmp_backup_cancel(&error);
1355+
1356+ hmp_handle_error(mon, &error);
1357+}
1358+
1359+void hmp_backup(Monitor *mon, const QDict *qdict)
1360+{
1361+ Error *error = NULL;
1362+
1363+ int dir = qdict_get_try_bool(qdict, "directory", 0);
1364+ const char *backup_file = qdict_get_str(qdict, "backupfile");
1365+ const char *devlist = qdict_get_try_str(qdict, "devlist");
1366+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
1367+
1368+ qmp_backup(backup_file, true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
1369+ false, NULL, false, NULL, !!devlist,
1370+ devlist, qdict_haskey(qdict, "speed"), speed, &error);
1371+
1372+ hmp_handle_error(mon, &error);
1373+}
1374+
1375 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
1376 {
1377 Error *error = NULL;
6838f038 1378diff --git a/qapi/block-core.json b/qapi/block-core.json
be901f66 1379index 0d43d4f37c..97cb7ec41c 100644
6838f038
WB
1380--- a/qapi/block-core.json
1381+++ b/qapi/block-core.json
b855dce7
TL
1382@@ -796,6 +796,97 @@
1383 { 'command': 'query-block', 'returns': ['BlockInfo'] }
6838f038 1384
67af0fa4 1385
b855dce7 1386+##
67af0fa4
WB
1387+# @BackupStatus:
1388+#
1389+# Detailed backup status.
1390+#
1391+# @status: string describing the current backup status.
1392+# This can be 'active', 'done', 'error'. If this field is not
1393+# returned, no backup process has been initiated
1394+#
1395+# @errmsg: error message (only returned if status is 'error')
1396+#
1397+# @total: total amount of bytes involved in the backup process
1398+#
1399+# @transferred: amount of bytes already backed up.
1400+#
1401+# @zero-bytes: amount of 'zero' bytes detected.
1402+#
1403+# @start-time: time (epoch) when backup job started.
1404+#
1405+# @end-time: time (epoch) when backup job finished.
1406+#
1407+# @backup-file: backup file name
1408+#
1409+# @uuid: uuid for this backup job
1410+#
1411+##
1412+{ 'struct': 'BackupStatus',
1413+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
1414+ '*transferred': 'int', '*zero-bytes': 'int',
1415+ '*start-time': 'int', '*end-time': 'int',
1416+ '*backup-file': 'str', '*uuid': 'str' } }
1417+
1418+##
1419+# @BackupFormat:
1420+#
1421+# An enumeration of supported backup formats.
1422+#
1423+# @vma: Proxmox vma backup format
1424+##
1425+{ 'enum': 'BackupFormat',
1426+ 'data': [ 'vma', 'dir' ] }
1427+
1428+##
1429+# @backup:
1430+#
1431+# Starts a VM backup.
1432+#
1433+# @backup-file: the backup file name
1434+#
1435+# @format: format of the backup file
1436+#
1437+# @config-file: a configuration file to include into
1438+# the backup archive.
1439+#
1440+# @speed: the maximum speed, in bytes per second
1441+#
1442+# @devlist: list of block device names (separated by ',', ';'
1443+# or ':'). By default the backup includes all writable block devices.
1444+#
1445+# Returns: the uuid of the backup job
1446+#
1447+##
1448+{ 'command': 'backup', 'data': { 'backup-file': 'str',
1449+ '*format': 'BackupFormat',
1450+ '*config-file': 'str',
1451+ '*firewall-file': 'str',
1452+ '*devlist': 'str', '*speed': 'int' },
1453+ 'returns': 'UuidInfo' }
1454+
1455+##
1456+# @query-backup:
1457+#
1458+# Returns information about current/last backup task.
1459+#
1460+# Returns: @BackupStatus
1461+#
1462+##
1463+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1464+
1465+##
1466+# @backup-cancel:
1467+#
1468+# Cancel the current executing backup process.
1469+#
1470+# Returns: nothing on success
1471+#
1472+# Notes: This command succeeds even if there is no backup process running.
1473+#
1474+##
1475+{ 'command': 'backup-cancel' }
1476+
b855dce7 1477 ##
6838f038 1478 # @BlockDeviceTimedStats:
67af0fa4 1479 #
be901f66 1480@@ -2835,7 +2926,7 @@
b855dce7
TL
1481 'qcow2', 'qed', 'quorum', 'raw', 'rbd',
1482 { 'name': 'replication', 'if': 'defined(CONFIG_REPLICATION)' },
1483 'sheepdog',
53e83913
WB
1484- 'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
1485+ 'ssh', 'throttle', 'vdi', 'vhdx', 'vma-drive', 'vmdk', 'vpc', 'vvfat', 'vxhs' ] }
67af0fa4
WB
1486
1487 ##
1488 # @BlockdevOptionsFile:
be901f66 1489@@ -3898,6 +3989,21 @@
b855dce7 1490 'server': 'InetSocketAddressBase',
6838f038 1491 '*tls-creds': 'str' } }
67af0fa4 1492
b855dce7 1493+##
67af0fa4
WB
1494+# @BlockdevOptionsVMADrive:
1495+#
1496+# Driver specific block device options for VMA Drives
1497+#
1498+# @filename: vma-drive path
1499+#
1500+# @size: drive size in bytes
1501+#
1502+# Since: 2.9
1503+##
1504+{ 'struct': 'BlockdevOptionsVMADrive',
1505+ 'data': { 'filename': 'str',
1506+ 'size': 'int' } }
1507+
b855dce7 1508 ##
6838f038 1509 # @BlockdevOptionsThrottle:
67af0fa4 1510 #
be901f66 1511@@ -3993,6 +4099,7 @@
6838f038
WB
1512 'throttle': 'BlockdevOptionsThrottle',
1513 'vdi': 'BlockdevOptionsGenericFormat',
67af0fa4 1514 'vhdx': 'BlockdevOptionsGenericFormat',
6838f038 1515+ 'vma-drive': 'BlockdevOptionsVMADrive',
67af0fa4
WB
1516 'vmdk': 'BlockdevOptionsGenericCOWFormat',
1517 'vpc': 'BlockdevOptionsGenericFormat',
6838f038 1518 'vvfat': 'BlockdevOptionsVVFAT',
53e83913 1519diff --git a/qapi/common.json b/qapi/common.json
b855dce7 1520index 99d313ef3b..bae0650c51 100644
53e83913
WB
1521--- a/qapi/common.json
1522+++ b/qapi/common.json
b855dce7
TL
1523@@ -193,3 +193,16 @@
1524 'ppc64', 'riscv32', 'riscv64', 's390x', 'sh4',
53e83913
WB
1525 'sh4eb', 'sparc', 'sparc64', 'tricore', 'unicore32',
1526 'x86_64', 'xtensa', 'xtensaeb' ] }
1527+
1528+##
1529+# @UuidInfo:
1530+#
1531+# Guest UUID information (Universally Unique Identifier).
1532+#
1533+# @UUID: the UUID of the guest
1534+#
1535+# Since: 0.14.0
1536+#
1537+# Notes: If no UUID was specified for the guest, a null UUID is returned.
1538+##
1539+{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1540diff --git a/qapi/misc.json b/qapi/misc.json
be901f66 1541index 31029e3132..335035c04d 100644
53e83913
WB
1542--- a/qapi/misc.json
1543+++ b/qapi/misc.json
be901f66 1544@@ -270,19 +270,6 @@
b855dce7 1545 ##
53e83913
WB
1546 { 'command': 'query-kvm', 'returns': 'KvmInfo' }
1547
b855dce7 1548-##
53e83913
WB
1549-# @UuidInfo:
1550-#
1551-# Guest UUID information (Universally Unique Identifier).
1552-#
1553-# @UUID: the UUID of the guest
1554-#
1555-# Since: 0.14.0
1556-#
1557-# Notes: If no UUID was specified for the guest, a null UUID is returned.
1558-##
1559-{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1560-
b855dce7 1561 ##
53e83913
WB
1562 # @query-uuid:
1563 #