]> git.proxmox.com Git - pve-qemu.git/blame - debian/patches/pve/0031-PVE-Backup-proxmox-backup-patches-for-qemu.patch
bump version to 4.2.0-2
[pve-qemu.git] / debian / patches / pve / 0031-PVE-Backup-proxmox-backup-patches-for-qemu.patch
CommitLineData
6402d961
TL
1From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2From: Dietmar Maurer <dietmar@proxmox.com>
3Date: Tue, 10 Mar 2020 12:55:28 +0100
4Subject: [PATCH 31/32] PVE-Backup: proxmox backup patches for qemu
5
6---
7 Makefile.objs | 1 +
8 Makefile.target | 2 +-
9 blockdev.c | 1 +
10 hmp-commands-info.hx | 13 +
11 hmp-commands.hx | 31 ++
12 include/block/block_int.h | 2 +-
13 include/monitor/hmp.h | 3 +
14 monitor/hmp-cmds.c | 77 +++
15 proxmox-backup-client.c | 182 ++++++++
16 proxmox-backup-client.h | 52 +++
17 pve-backup.c | 959 ++++++++++++++++++++++++++++++++++++++
18 qapi/block-core.json | 109 +++++
19 qapi/common.json | 13 +
20 qapi/misc.json | 13 -
21 14 files changed, 1443 insertions(+), 15 deletions(-)
22 create mode 100644 proxmox-backup-client.c
23 create mode 100644 proxmox-backup-client.h
24 create mode 100644 pve-backup.c
25
26diff --git a/Makefile.objs b/Makefile.objs
27index db7fbbe73b..4406dcfeba 100644
28--- a/Makefile.objs
29+++ b/Makefile.objs
30@@ -46,6 +46,7 @@ io-obj-y = io/
31 ifeq ($(CONFIG_SOFTMMU),y)
32 common-obj-y = blockdev.o blockdev-nbd.o block/
33 common-obj-y += bootdevice.o iothread.o
34+common-obj-y += proxmox-backup-client.o pve-backup.o
35 common-obj-y += dump/
36 common-obj-y += job-qmp.o
37 common-obj-y += monitor/
38diff --git a/Makefile.target b/Makefile.target
39index 24d79d26eb..d7d91085a4 100644
40--- a/Makefile.target
41+++ b/Makefile.target
42@@ -160,7 +160,7 @@ obj-y += qapi/
43 obj-y += memory.o
44 obj-y += memory_mapping.o
45 obj-y += migration/ram.o
46-LIBS := $(libs_softmmu) $(LIBS)
47+LIBS := $(libs_softmmu) $(LIBS) -lproxmox_backup_qemu
48
49 # Hardware support
50 ifeq ($(TARGET_NAME), sparc64)
51diff --git a/blockdev.c b/blockdev.c
52index c7fa663ebf..e5310cb939 100644
53--- a/blockdev.c
54+++ b/blockdev.c
55@@ -36,6 +36,7 @@
56 #include "hw/block/block.h"
57 #include "block/blockjob.h"
58 #include "block/qdict.h"
59+#include "block/blockjob_int.h"
60 #include "block/throttle-groups.h"
61 #include "monitor/monitor.h"
62 #include "qemu/error-report.h"
63diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
64index 139e673bea..8db5ce03a7 100644
65--- a/hmp-commands-info.hx
66+++ b/hmp-commands-info.hx
67@@ -536,6 +536,19 @@ STEXI
68 @item info cpustats
69 @findex info cpustats
70 Show CPU statistics.
71+ETEXI
72+
73+ {
74+ .name = "backup",
75+ .args_type = "",
76+ .params = "",
77+ .help = "show backup status",
78+ .cmd = hmp_info_backup,
79+ },
80+
81+STEXI
82+@item info backup
83+show backup status
84 ETEXI
85
86 #if defined(CONFIG_SLIRP)
87diff --git a/hmp-commands.hx b/hmp-commands.hx
88index 104288322d..29d11dd321 100644
89--- a/hmp-commands.hx
90+++ b/hmp-commands.hx
91@@ -105,6 +105,37 @@ STEXI
92 @item block_stream
93 @findex block_stream
94 Copy data from a backing file into a block device.
95+ETEXI
96+
97+ {
98+ .name = "backup",
99+ .args_type = "directory:-d,backupfile:s,speed:o?,devlist:s?",
100+ .params = "[-d] backupfile [speed [devlist]]",
101+ .help = "create a VM Backup."
102+ "\n\t\t\t Use -d to dump data into a directory instead"
103+ "\n\t\t\t of using VMA format.",
104+ .cmd = hmp_backup,
105+ },
106+
107+STEXI
108+@item backup
109+@findex backup
110+Create a VM backup.
111+ETEXI
112+
113+ {
114+ .name = "backup_cancel",
115+ .args_type = "",
116+ .params = "",
117+ .help = "cancel the current VM backup",
118+ .cmd = hmp_backup_cancel,
119+ },
120+
121+STEXI
122+@item backup_cancel
123+@findex backup_cancel
124+Cancel the current VM backup.
125+
126 ETEXI
127
128 {
129diff --git a/include/block/block_int.h b/include/block/block_int.h
130index 105bffe0f7..43b00c15ac 100644
131--- a/include/block/block_int.h
132+++ b/include/block/block_int.h
133@@ -62,7 +62,7 @@
134
135 typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
136
137-BlockDriverState *bdrv_backuo_dump_create(
138+BlockDriverState *bdrv_backup_dump_create(
139 int dump_cb_block_size,
140 uint64_t byte_size,
141 BackupDumpFunc *dump_cb,
142diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
143index c6ee8295f0..0f2f96c4af 100644
144--- a/include/monitor/hmp.h
145+++ b/include/monitor/hmp.h
146@@ -30,6 +30,7 @@ void hmp_info_migrate(Monitor *mon, const QDict *qdict);
147 void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
148 void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
149 void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict);
150+void hmp_info_backup(Monitor *mon, const QDict *qdict);
151 void hmp_info_cpus(Monitor *mon, const QDict *qdict);
152 void hmp_info_block(Monitor *mon, const QDict *qdict);
153 void hmp_info_blockstats(Monitor *mon, const QDict *qdict);
154@@ -90,6 +91,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
155 void hmp_change(Monitor *mon, const QDict *qdict);
156 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
157 void hmp_block_stream(Monitor *mon, const QDict *qdict);
158+void hmp_backup(Monitor *mon, const QDict *qdict);
159+void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
160 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
161 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
162 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
163diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
164index 90aa34be25..1e33c61a2c 100644
165--- a/monitor/hmp-cmds.c
166+++ b/monitor/hmp-cmds.c
167@@ -194,6 +194,50 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
168 qapi_free_MouseInfoList(mice_list);
169 }
170
171+void hmp_info_backup(Monitor *mon, const QDict *qdict)
172+{
173+ BackupStatus *info;
174+
175+ info = qmp_query_backup(NULL);
176+
177+ if (!info) {
178+ monitor_printf(mon, "Backup status: not initialized\n");
179+ return;
180+ }
181+
182+ if (info->has_status) {
183+ if (info->has_errmsg) {
184+ monitor_printf(mon, "Backup status: %s - %s\n",
185+ info->status, info->errmsg);
186+ } else {
187+ monitor_printf(mon, "Backup status: %s\n", info->status);
188+ }
189+ }
190+
191+ if (info->has_backup_file) {
192+ monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
193+ if (info->end_time) {
194+ monitor_printf(mon, "End time: %s", ctime(&info->end_time));
195+ }
196+
197+ int per = (info->has_total && info->total &&
198+ info->has_transferred && info->transferred) ?
199+ (info->transferred * 100)/info->total : 0;
200+ int zero_per = (info->has_total && info->total &&
201+ info->has_zero_bytes && info->zero_bytes) ?
202+ (info->zero_bytes * 100)/info->total : 0;
203+ monitor_printf(mon, "Backup file: %s\n", info->backup_file);
204+ monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
205+ monitor_printf(mon, "Total size: %zd\n", info->total);
206+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
207+ info->transferred, per);
208+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
209+ info->zero_bytes, zero_per);
210+ }
211+
212+ qapi_free_BackupStatus(info);
213+}
214+
215 static char *SocketAddress_to_str(SocketAddress *addr)
216 {
217 switch (addr->type) {
218@@ -2062,6 +2106,39 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
219 hmp_handle_error(mon, &error);
220 }
221
222+void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
223+{
224+ Error *error = NULL;
225+
226+ qmp_backup_cancel(&error);
227+
228+ hmp_handle_error(mon, &error);
229+}
230+
231+void hmp_backup(Monitor *mon, const QDict *qdict)
232+{
233+ Error *error = NULL;
234+
235+ int dir = qdict_get_try_bool(qdict, "directory", 0);
236+ const char *backup_file = qdict_get_str(qdict, "backupfile");
237+ const char *devlist = qdict_get_try_str(qdict, "devlist");
238+ int64_t speed = qdict_get_try_int(qdict, "speed", 0);
239+
240+ qmp_backup(
241+ backup_file,
242+ false, NULL, // BPS password
243+ false, NULL, // BPS keyfile
244+ false, NULL, // BPS key_password
245+ false, NULL, // BPS fingerprint
246+ false, NULL, // BPS backup-id
247+ false, 0, // BPS backup-time
248+ true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
249+ false, NULL, false, NULL, !!devlist,
250+ devlist, qdict_haskey(qdict, "speed"), speed, &error);
251+
252+ hmp_handle_error(mon, &error);
253+}
254+
255 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
256 {
257 Error *error = NULL;
258diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
259new file mode 100644
260index 0000000000..b7bc7f2574
261--- /dev/null
262+++ b/proxmox-backup-client.c
263@@ -0,0 +1,182 @@
264+#include "proxmox-backup-client.h"
265+#include "qemu/main-loop.h"
266+#include "block/aio-wait.h"
267+#include "qapi/error.h"
268+
269+/* Proxmox Backup Server client bindings using coroutines */
270+
271+typedef struct BlockOnCoroutineWrapper {
272+ AioContext *ctx;
273+ CoroutineEntry *entry;
274+ void *entry_arg;
275+ bool finished;
276+} BlockOnCoroutineWrapper;
277+
278+// Waker implementaion to syncronice with proxmox backup rust code
279+typedef struct ProxmoxBackupWaker {
280+ Coroutine *co;
281+ AioContext *ctx;
282+} ProxmoxBackupWaker;
283+
284+static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
285+{
286+ BlockOnCoroutineWrapper *wrapper = opaque;
287+ wrapper->entry(wrapper->entry_arg);
288+ wrapper->finished = true;
289+ aio_wait_kick();
290+}
291+
292+void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
293+{
294+ assert(!qemu_in_coroutine());
295+
296+ AioContext *ctx = qemu_get_current_aio_context();
297+ BlockOnCoroutineWrapper wrapper = {
298+ .finished = false,
299+ .entry = entry,
300+ .entry_arg = entry_arg,
301+ .ctx = ctx,
302+ };
303+ Coroutine *wrapper_co = qemu_coroutine_create(block_on_coroutine_wrapper, &wrapper);
304+ aio_co_enter(ctx, wrapper_co);
305+ AIO_WAIT_WHILE(ctx, !wrapper.finished);
306+}
307+
308+// This is called from another thread, so we use aio_co_schedule()
309+static void proxmox_backup_schedule_wake(void *data) {
310+ ProxmoxBackupWaker *waker = (ProxmoxBackupWaker *)data;
311+ aio_co_schedule(waker->ctx, waker->co);
312+}
313+
314+int coroutine_fn
315+proxmox_backup_co_connect(ProxmoxBackupHandle *pbs, Error **errp)
316+{
317+ Coroutine *co = qemu_coroutine_self();
318+ AioContext *ctx = qemu_get_current_aio_context();
319+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
320+ char *pbs_err = NULL;
321+ int pbs_res = -1;
322+
323+ proxmox_backup_connect_async(pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
324+ qemu_coroutine_yield();
325+ if (pbs_res < 0) {
326+ if (errp) error_setg(errp, "backup connect failed: %s", pbs_err ? pbs_err : "unknown error");
327+ if (pbs_err) proxmox_backup_free_error(pbs_err);
328+ }
329+ return pbs_res;
330+}
331+
332+int coroutine_fn
333+proxmox_backup_co_add_config(
334+ ProxmoxBackupHandle *pbs,
335+ const char *name,
336+ const uint8_t *data,
337+ uint64_t size,
338+ Error **errp)
339+{
340+ Coroutine *co = qemu_coroutine_self();
341+ AioContext *ctx = qemu_get_current_aio_context();
342+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
343+ char *pbs_err = NULL;
344+ int pbs_res = -1;
345+
346+ proxmox_backup_add_config_async(
347+ pbs, name, data, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
348+ qemu_coroutine_yield();
349+ if (pbs_res < 0) {
350+ if (errp) error_setg(errp, "backup add_config %s failed: %s", name, pbs_err ? pbs_err : "unknown error");
351+ if (pbs_err) proxmox_backup_free_error(pbs_err);
352+ }
353+ return pbs_res;
354+}
355+
356+int coroutine_fn
357+proxmox_backup_co_register_image(
358+ ProxmoxBackupHandle *pbs,
359+ const char *device_name,
360+ uint64_t size,
361+ Error **errp)
362+{
363+ Coroutine *co = qemu_coroutine_self();
364+ AioContext *ctx = qemu_get_current_aio_context();
365+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
366+ char *pbs_err = NULL;
367+ int pbs_res = -1;
368+
369+ proxmox_backup_register_image_async(
370+ pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
371+ qemu_coroutine_yield();
372+ if (pbs_res < 0) {
373+ if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
374+ if (pbs_err) proxmox_backup_free_error(pbs_err);
375+ }
376+ return pbs_res;
377+}
378+
379+int coroutine_fn
380+proxmox_backup_co_finish(
381+ ProxmoxBackupHandle *pbs,
382+ Error **errp)
383+{
384+ Coroutine *co = qemu_coroutine_self();
385+ AioContext *ctx = qemu_get_current_aio_context();
386+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
387+ char *pbs_err = NULL;
388+ int pbs_res = -1;
389+
390+ proxmox_backup_finish_async(
391+ pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
392+ qemu_coroutine_yield();
393+ if (pbs_res < 0) {
394+ if (errp) error_setg(errp, "backup finish failed: %s", pbs_err ? pbs_err : "unknown error");
395+ if (pbs_err) proxmox_backup_free_error(pbs_err);
396+ }
397+ return pbs_res;
398+}
399+
400+int coroutine_fn
401+proxmox_backup_co_close_image(
402+ ProxmoxBackupHandle *pbs,
403+ uint8_t dev_id,
404+ Error **errp)
405+{
406+ Coroutine *co = qemu_coroutine_self();
407+ AioContext *ctx = qemu_get_current_aio_context();
408+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
409+ char *pbs_err = NULL;
410+ int pbs_res = -1;
411+
412+ proxmox_backup_close_image_async(
413+ pbs, dev_id, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
414+ qemu_coroutine_yield();
415+ if (pbs_res < 0) {
416+ if (errp) error_setg(errp, "backup close image failed: %s", pbs_err ? pbs_err : "unknown error");
417+ if (pbs_err) proxmox_backup_free_error(pbs_err);
418+ }
419+ return pbs_res;
420+}
421+
422+int coroutine_fn
423+proxmox_backup_co_write_data(
424+ ProxmoxBackupHandle *pbs,
425+ uint8_t dev_id,
426+ const uint8_t *data,
427+ uint64_t offset,
428+ uint64_t size,
429+ Error **errp)
430+{
431+ Coroutine *co = qemu_coroutine_self();
432+ AioContext *ctx = qemu_get_current_aio_context();
433+ ProxmoxBackupWaker waker = { .co = co, .ctx = ctx };
434+ char *pbs_err = NULL;
435+ int pbs_res = -1;
436+
437+ proxmox_backup_write_data_async(
438+ pbs, dev_id, data, offset, size, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
439+ qemu_coroutine_yield();
440+ if (pbs_res < 0) {
441+ if (errp) error_setg(errp, "backup write data failed: %s", pbs_err ? pbs_err : "unknown error");
442+ if (pbs_err) proxmox_backup_free_error(pbs_err);
443+ }
444+ return pbs_res;
445+}
446diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
447new file mode 100644
448index 0000000000..b311bf8de8
449--- /dev/null
450+++ b/proxmox-backup-client.h
451@@ -0,0 +1,52 @@
452+#ifndef PROXMOX_BACKUP_CLIENT_H
453+#define PROXMOX_BACKUP_CLIENT_H
454+
455+#include "qemu/osdep.h"
456+#include "qemu/coroutine.h"
457+#include "proxmox-backup-qemu.h"
458+
459+void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg);
460+
461+int coroutine_fn
462+proxmox_backup_co_connect(
463+ ProxmoxBackupHandle *pbs,
464+ Error **errp);
465+
466+int coroutine_fn
467+proxmox_backup_co_add_config(
468+ ProxmoxBackupHandle *pbs,
469+ const char *name,
470+ const uint8_t *data,
471+ uint64_t size,
472+ Error **errp);
473+
474+int coroutine_fn
475+proxmox_backup_co_register_image(
476+ ProxmoxBackupHandle *pbs,
477+ const char *device_name,
478+ uint64_t size,
479+ Error **errp);
480+
481+
482+int coroutine_fn
483+proxmox_backup_co_finish(
484+ ProxmoxBackupHandle *pbs,
485+ Error **errp);
486+
487+int coroutine_fn
488+proxmox_backup_co_close_image(
489+ ProxmoxBackupHandle *pbs,
490+ uint8_t dev_id,
491+ Error **errp);
492+
493+int coroutine_fn
494+proxmox_backup_co_write_data(
495+ ProxmoxBackupHandle *pbs,
496+ uint8_t dev_id,
497+ const uint8_t *data,
498+ uint64_t offset,
499+ uint64_t size,
500+ Error **errp);
501+
502+
503+#endif /* PROXMOX_BACKUP_CLIENT_H */
504diff --git a/pve-backup.c b/pve-backup.c
505new file mode 100644
506index 0000000000..9ae89fb679
507--- /dev/null
508+++ b/pve-backup.c
509@@ -0,0 +1,959 @@
510+#include "proxmox-backup-client.h"
511+#include "vma.h"
512+
513+#include "qemu/osdep.h"
514+#include "qemu/module.h"
515+#include "sysemu/block-backend.h"
516+#include "sysemu/blockdev.h"
517+#include "block/blockjob.h"
518+#include "qapi/qapi-commands-block.h"
519+#include "qapi/qmp/qerror.h"
520+
521+/* PVE backup state and related function */
522+
523+
524+static struct PVEBackupState {
525+ struct {
526+ // Everithing accessed from qmp command, protected using rwlock
527+ CoRwlock rwlock;
528+ Error *error;
529+ time_t start_time;
530+ time_t end_time;
531+ char *backup_file;
532+ uuid_t uuid;
533+ char uuid_str[37];
534+ size_t total;
535+ size_t transferred;
536+ size_t zero_bytes;
537+ bool cancel;
538+ } stat;
539+ int64_t speed;
540+ VmaWriter *vmaw;
541+ ProxmoxBackupHandle *pbs;
542+ GList *di_list;
543+ CoMutex backup_mutex;
544+} backup_state;
545+
546+static void pvebackup_init(void)
547+{
548+ qemu_co_rwlock_init(&backup_state.stat.rwlock);
549+ qemu_co_mutex_init(&backup_state.backup_mutex);
550+}
551+
552+// initialize PVEBackupState at startup
553+opts_init(pvebackup_init);
554+
555+typedef struct PVEBackupDevInfo {
556+ BlockDriverState *bs;
557+ size_t size;
558+ uint8_t dev_id;
559+ bool completed;
560+ char targetfile[PATH_MAX];
561+ BlockDriverState *target;
562+} PVEBackupDevInfo;
563+
564+static void pvebackup_co_run_next_job(void);
565+
566+static int coroutine_fn
567+pvebackup_co_dump_cb(
568+ void *opaque,
569+ uint64_t start,
570+ uint64_t bytes,
571+ const void *pbuf)
572+{
573+ assert(qemu_in_coroutine());
574+
575+ const uint64_t size = bytes;
576+ const unsigned char *buf = pbuf;
577+ PVEBackupDevInfo *di = opaque;
578+
579+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
580+ bool cancel = backup_state.stat.cancel;
581+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
582+
583+ if (cancel) {
584+ return size; // return success
585+ }
586+
587+ qemu_co_mutex_lock(&backup_state.backup_mutex);
588+
589+ int ret = -1;
590+
591+ if (backup_state.vmaw) {
592+ size_t zero_bytes = 0;
593+ uint64_t remaining = size;
594+
595+ uint64_t cluster_num = start / VMA_CLUSTER_SIZE;
596+ if ((cluster_num * VMA_CLUSTER_SIZE) != start) {
597+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
598+ if (!backup_state.stat.error) {
599+ qemu_co_rwlock_upgrade(&backup_state.stat.rwlock);
600+ error_setg(&backup_state.stat.error,
601+ "got unaligned write inside backup dump "
602+ "callback (sector %ld)", start);
603+ }
604+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
605+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
606+ return -1; // not aligned to cluster size
607+ }
608+
609+ while (remaining > 0) {
610+ ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num,
611+ buf, &zero_bytes);
612+ ++cluster_num;
613+ if (buf) {
614+ buf += VMA_CLUSTER_SIZE;
615+ }
616+ if (ret < 0) {
617+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
618+ if (!backup_state.stat.error) {
619+ qemu_co_rwlock_upgrade(&backup_state.stat.rwlock);
620+ vma_writer_error_propagate(backup_state.vmaw, &backup_state.stat.error);
621+ }
622+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
623+
624+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
625+ return ret;
626+ } else {
627+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
628+ backup_state.stat.zero_bytes += zero_bytes;
629+ if (remaining >= VMA_CLUSTER_SIZE) {
630+ backup_state.stat.transferred += VMA_CLUSTER_SIZE;
631+ remaining -= VMA_CLUSTER_SIZE;
632+ } else {
633+ backup_state.stat.transferred += remaining;
634+ remaining = 0;
635+ }
636+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
637+ }
638+ }
639+ } else if (backup_state.pbs) {
640+ Error *local_err = NULL;
641+ int pbs_res = -1;
642+
643+ pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
644+
645+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
646+
647+ if (pbs_res < 0) {
648+ error_propagate(&backup_state.stat.error, local_err);
649+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
650+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
651+ return pbs_res;
652+ } else {
653+ if (!buf) {
654+ backup_state.stat.zero_bytes += size;
655+ }
656+ backup_state.stat.transferred += size;
657+ }
658+
659+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
660+
661+ } else {
662+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
663+ if (!buf) {
664+ backup_state.stat.zero_bytes += size;
665+ }
666+ backup_state.stat.transferred += size;
667+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
668+ }
669+
670+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
671+
672+ return size;
673+}
674+
675+static void coroutine_fn pvebackup_co_cleanup(void)
676+{
677+ assert(qemu_in_coroutine());
678+
679+ qemu_co_mutex_lock(&backup_state.backup_mutex);
680+
681+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
682+ backup_state.stat.end_time = time(NULL);
683+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
684+
685+ if (backup_state.vmaw) {
686+ Error *local_err = NULL;
687+ vma_writer_close(backup_state.vmaw, &local_err);
688+
689+ if (local_err != NULL) {
690+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
691+ error_propagate(&backup_state.stat.error, local_err);
692+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
693+ }
694+
695+ backup_state.vmaw = NULL;
696+ }
697+
698+ if (backup_state.pbs) {
699+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
700+ bool error_or_canceled = backup_state.stat.error || backup_state.stat.cancel;
701+ if (!error_or_canceled) {
702+ Error *local_err = NULL;
703+ proxmox_backup_co_finish(backup_state.pbs, &local_err);
704+ if (local_err != NULL) {
705+ qemu_co_rwlock_upgrade(&backup_state.stat.rwlock);
706+ error_propagate(&backup_state.stat.error, local_err);
707+ }
708+ }
709+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
710+
711+ proxmox_backup_disconnect(backup_state.pbs);
712+ backup_state.pbs = NULL;
713+ }
714+
715+ g_list_free(backup_state.di_list);
716+ backup_state.di_list = NULL;
717+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
718+}
719+
720+typedef struct PVEBackupCompeteCallbackData {
721+ PVEBackupDevInfo *di;
722+ int result;
723+} PVEBackupCompeteCallbackData;
724+
725+static void coroutine_fn pvebackup_co_complete_cb(void *opaque)
726+{
727+ assert(qemu_in_coroutine());
728+
729+ PVEBackupCompeteCallbackData *cb_data = opaque;
730+
731+ qemu_co_mutex_lock(&backup_state.backup_mutex);
732+
733+ PVEBackupDevInfo *di = cb_data->di;
734+ int ret = cb_data->result;
735+
736+ di->completed = true;
737+
738+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
739+ bool error_or_canceled = backup_state.stat.error || backup_state.stat.cancel;
740+
741+ if (ret < 0 && !backup_state.stat.error) {
742+ qemu_co_rwlock_upgrade(&backup_state.stat.rwlock);
743+ error_setg(&backup_state.stat.error, "job failed with err %d - %s",
744+ ret, strerror(-ret));
745+ }
746+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
747+
748+ di->bs = NULL;
749+
750+ if (di->target) {
751+ bdrv_unref(di->target);
752+ di->target = NULL;
753+ }
754+
755+ if (backup_state.vmaw) {
756+ vma_writer_close_stream(backup_state.vmaw, di->dev_id);
757+ }
758+
759+ if (backup_state.pbs && !error_or_canceled) {
760+ Error *local_err = NULL;
761+ proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
762+ if (local_err != NULL) {
763+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
764+ error_propagate(&backup_state.stat.error, local_err);
765+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
766+ }
767+ }
768+
769+ // remove self from job queue
770+ backup_state.di_list = g_list_remove(backup_state.di_list, di);
771+ g_free(di);
772+
773+ int pending_jobs = g_list_length(backup_state.di_list);
774+
775+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
776+
777+ if (pending_jobs > 0) {
778+ pvebackup_co_run_next_job();
779+ } else {
780+ pvebackup_co_cleanup();
781+ }
782+}
783+
784+static void pvebackup_complete_cb(void *opaque, int ret)
785+{
786+ // This can be called from the main loop, or from a coroutine
787+ PVEBackupCompeteCallbackData cb_data = {
788+ .di = opaque,
789+ .result = ret,
790+ };
791+
792+ if (qemu_in_coroutine()) {
793+ pvebackup_co_complete_cb(&cb_data);
794+ } else {
795+ block_on_coroutine_fn(pvebackup_co_complete_cb, &cb_data);
796+ }
797+}
798+
799+static void coroutine_fn pvebackup_co_cancel(void *opaque)
800+{
801+ assert(qemu_in_coroutine());
802+
803+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
804+ backup_state.stat.cancel = true;
805+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
806+
807+ qemu_co_mutex_lock(&backup_state.backup_mutex);
808+
809+ // Avoid race between block jobs and backup-cancel command:
810+ if (!(backup_state.vmaw || backup_state.pbs)) {
811+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
812+ return;
813+ }
814+
815+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
816+ if (!backup_state.stat.error) {
817+ qemu_co_rwlock_upgrade(&backup_state.stat.rwlock);
818+ error_setg(&backup_state.stat.error, "backup cancelled");
819+ }
820+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
821+
822+ if (backup_state.vmaw) {
823+ /* make sure vma writer does not block anymore */
824+ vma_writer_set_error(backup_state.vmaw, "backup cancelled");
825+ }
826+
827+ if (backup_state.pbs) {
828+ proxmox_backup_abort(backup_state.pbs, "backup cancelled");
829+ }
830+
831+ bool running_jobs = 0;
832+ GList *l = backup_state.di_list;
833+ while (l) {
834+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
835+ l = g_list_next(l);
836+ if (!di->completed && di->bs) {
837+ for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
838+ if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
839+ continue;
840+ }
841+
842+ BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
843+ if (bjob && bjob->source_bs == di->bs) {
844+ AioContext *aio_context = job->job.aio_context;
845+ aio_context_acquire(aio_context);
846+
847+ if (!di->completed) {
848+ running_jobs += 1;
849+ job_cancel(&job->job, false);
850+ }
851+ aio_context_release(aio_context);
852+ }
853+ }
854+ }
855+ }
856+
857+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
858+
859+ if (running_jobs == 0) pvebackup_co_cleanup(); // else job will call completion handler
860+}
861+
862+void qmp_backup_cancel(Error **errp)
863+{
864+ block_on_coroutine_fn(pvebackup_co_cancel, NULL);
865+}
866+
867+static int coroutine_fn pvebackup_co_add_config(
868+ const char *file,
869+ const char *name,
870+ BackupFormat format,
871+ const char *backup_dir,
872+ VmaWriter *vmaw,
873+ ProxmoxBackupHandle *pbs,
874+ Error **errp)
875+{
876+ int res = 0;
877+
878+ char *cdata = NULL;
879+ gsize clen = 0;
880+ GError *err = NULL;
881+ if (!g_file_get_contents(file, &cdata, &clen, &err)) {
882+ error_setg(errp, "unable to read file '%s'", file);
883+ return 1;
884+ }
885+
886+ char *basename = g_path_get_basename(file);
887+ if (name == NULL) name = basename;
888+
889+ if (format == BACKUP_FORMAT_VMA) {
890+ if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
891+ error_setg(errp, "unable to add %s config data to vma archive", file);
892+ goto err;
893+ }
894+ } else if (format == BACKUP_FORMAT_PBS) {
895+ if (proxmox_backup_co_add_config(pbs, name, (unsigned char *)cdata, clen, errp) < 0)
896+ goto err;
897+ } else if (format == BACKUP_FORMAT_DIR) {
898+ char config_path[PATH_MAX];
899+ snprintf(config_path, PATH_MAX, "%s/%s", backup_dir, name);
900+ if (!g_file_set_contents(config_path, cdata, clen, &err)) {
901+ error_setg(errp, "unable to write config file '%s'", config_path);
902+ goto err;
903+ }
904+ }
905+
906+ out:
907+ g_free(basename);
908+ g_free(cdata);
909+ return res;
910+
911+ err:
912+ res = -1;
913+ goto out;
914+}
915+
916+bool job_should_pause(Job *job);
917+
918+static void coroutine_fn pvebackup_co_run_next_job(void)
919+{
920+ assert(qemu_in_coroutine());
921+
922+ qemu_co_mutex_lock(&backup_state.backup_mutex);
923+
924+ GList *l = backup_state.di_list;
925+ while (l) {
926+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
927+ l = g_list_next(l);
928+ if (!di->completed && di->bs) {
929+ for (BlockJob *job = block_job_next(NULL); job; job = block_job_next(job)) {
930+ if (job->job.driver->job_type != JOB_TYPE_BACKUP) {
931+ continue;
932+ }
933+
934+ BackupBlockJob *bjob = container_of(job, BackupBlockJob, common);
935+ if (bjob && bjob->source_bs == di->bs) {
936+ AioContext *aio_context = job->job.aio_context;
937+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
938+ aio_context_acquire(aio_context);
939+
940+ if (job_should_pause(&job->job)) {
941+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
942+ bool error_or_canceled = backup_state.stat.error || backup_state.stat.cancel;
943+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
944+
945+ if (error_or_canceled) {
946+ job_cancel(&job->job, false);
947+ } else {
948+ job_resume(&job->job);
949+ }
950+ }
951+ aio_context_release(aio_context);
952+ return;
953+ }
954+ }
955+ }
956+ }
957+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
958+}
959+
960+typedef struct QmpBackupTask {
961+ const char *backup_file;
962+ bool has_password;
963+ const char *password;
964+ bool has_keyfile;
965+ const char *keyfile;
966+ bool has_key_password;
967+ const char *key_password;
968+ bool has_backup_id;
969+ const char *backup_id;
970+ bool has_backup_time;
971+ const char *fingerprint;
972+ bool has_fingerprint;
973+ int64_t backup_time;
974+ bool has_format;
975+ BackupFormat format;
976+ bool has_config_file;
977+ const char *config_file;
978+ bool has_firewall_file;
979+ const char *firewall_file;
980+ bool has_devlist;
981+ const char *devlist;
982+ bool has_speed;
983+ int64_t speed;
984+ Error **errp;
985+ UuidInfo *result;
986+} QmpBackupTask;
987+
988+static void coroutine_fn pvebackup_co_start(void *opaque)
989+{
990+ assert(qemu_in_coroutine());
991+
992+ QmpBackupTask *task = opaque;
993+
994+ task->result = NULL; // just to be sure
995+
996+ BlockBackend *blk;
997+ BlockDriverState *bs = NULL;
998+ const char *backup_dir = NULL;
999+ Error *local_err = NULL;
1000+ uuid_t uuid;
1001+ VmaWriter *vmaw = NULL;
1002+ ProxmoxBackupHandle *pbs = NULL;
1003+ gchar **devs = NULL;
1004+ GList *di_list = NULL;
1005+ GList *l;
1006+ UuidInfo *uuid_info;
1007+ BlockJob *job;
1008+
1009+ const char *config_name = "qemu-server.conf";
1010+ const char *firewall_name = "qemu-server.fw";
1011+
1012+ qemu_co_mutex_lock(&backup_state.backup_mutex);
1013+
1014+ if (backup_state.di_list) {
1015+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
1016+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
1017+ "previous backup not finished");
1018+ return;
1019+ }
1020+
1021+ /* Todo: try to auto-detect format based on file name */
1022+ BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
1023+
1024+ if (task->has_devlist) {
1025+ devs = g_strsplit_set(task->devlist, ",;:", -1);
1026+
1027+ gchar **d = devs;
1028+ while (d && *d) {
1029+ blk = blk_by_name(*d);
1030+ if (blk) {
1031+ bs = blk_bs(blk);
1032+ if (bdrv_is_read_only(bs)) {
1033+ error_setg(task->errp, "Node '%s' is read only", *d);
1034+ goto err;
1035+ }
1036+ if (!bdrv_is_inserted(bs)) {
1037+ error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
1038+ goto err;
1039+ }
1040+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1041+ di->bs = bs;
1042+ di_list = g_list_append(di_list, di);
1043+ } else {
1044+ error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
1045+ "Device '%s' not found", *d);
1046+ goto err;
1047+ }
1048+ d++;
1049+ }
1050+
1051+ } else {
1052+ BdrvNextIterator it;
1053+
1054+ bs = NULL;
1055+ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
1056+ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
1057+ continue;
1058+ }
1059+
1060+ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1061+ di->bs = bs;
1062+ di_list = g_list_append(di_list, di);
1063+ }
1064+ }
1065+
1066+ if (!di_list) {
1067+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
1068+ goto err;
1069+ }
1070+
1071+ size_t total = 0;
1072+
1073+ l = di_list;
1074+ while (l) {
1075+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1076+ l = g_list_next(l);
1077+ if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, task->errp)) {
1078+ goto err;
1079+ }
1080+
1081+ ssize_t size = bdrv_getlength(di->bs);
1082+ if (size < 0) {
1083+ error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
1084+ goto err;
1085+ }
1086+ di->size = size;
1087+ total += size;
1088+ }
1089+
1090+ uuid_generate(uuid);
1091+
1092+ if (format == BACKUP_FORMAT_PBS) {
1093+ if (!task->has_password) {
1094+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
1095+ goto err;
1096+ }
1097+ if (!task->has_backup_id) {
1098+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
1099+ goto err;
1100+ }
1101+ if (!task->has_backup_time) {
1102+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
1103+ goto err;
1104+ }
1105+
1106+ int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
1107+ firewall_name = "fw.conf";
1108+
1109+ char *pbs_err = NULL;
1110+ pbs = proxmox_backup_new(
1111+ task->backup_file,
1112+ task->backup_id,
1113+ task->backup_time,
1114+ dump_cb_block_size,
1115+ task->has_password ? task->password : NULL,
1116+ task->has_keyfile ? task->keyfile : NULL,
1117+ task->has_key_password ? task->key_password : NULL,
1118+ task->has_fingerprint ? task->fingerprint : NULL,
1119+ &pbs_err);
1120+
1121+ if (!pbs) {
1122+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
1123+ "proxmox_backup_new failed: %s", pbs_err);
1124+ proxmox_backup_free_error(pbs_err);
1125+ goto err;
1126+ }
1127+
1128+ if (proxmox_backup_co_connect(pbs, task->errp) < 0)
1129+ goto err;
1130+
1131+ /* register all devices */
1132+ l = di_list;
1133+ while (l) {
1134+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1135+ l = g_list_next(l);
1136+
1137+ const char *devname = bdrv_get_device_name(di->bs);
1138+
1139+ int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
1140+ if (dev_id < 0)
1141+ goto err;
1142+
1143+ if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_cb, di, task->errp))) {
1144+ goto err;
1145+ }
1146+
1147+ di->dev_id = dev_id;
1148+ }
1149+ } else if (format == BACKUP_FORMAT_VMA) {
1150+ vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
1151+ if (!vmaw) {
1152+ if (local_err) {
1153+ error_propagate(task->errp, local_err);
1154+ }
1155+ goto err;
1156+ }
1157+
1158+ /* register all devices for vma writer */
1159+ l = di_list;
1160+ while (l) {
1161+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1162+ l = g_list_next(l);
1163+
1164+ if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_cb, di, task->errp))) {
1165+ goto err;
1166+ }
1167+
1168+ const char *devname = bdrv_get_device_name(di->bs);
1169+ di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
1170+ if (di->dev_id <= 0) {
1171+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
1172+ "register_stream failed");
1173+ goto err;
1174+ }
1175+ }
1176+ } else if (format == BACKUP_FORMAT_DIR) {
1177+ if (mkdir(task->backup_file, 0640) != 0) {
1178+ error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
1179+ task->backup_file);
1180+ goto err;
1181+ }
1182+ backup_dir = task->backup_file;
1183+
1184+ l = di_list;
1185+ while (l) {
1186+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1187+ l = g_list_next(l);
1188+
1189+ const char *devname = bdrv_get_device_name(di->bs);
1190+ snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
1191+
1192+ int flags = BDRV_O_RDWR;
1193+ bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
1194+ di->size, flags, false, &local_err);
1195+ if (local_err) {
1196+ error_propagate(task->errp, local_err);
1197+ goto err;
1198+ }
1199+
1200+ di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
1201+ if (!di->target) {
1202+ error_propagate(task->errp, local_err);
1203+ goto err;
1204+ }
1205+ }
1206+ } else {
1207+ error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
1208+ goto err;
1209+ }
1210+
1211+
1212+ /* add configuration file to archive */
1213+ if (task->has_config_file) {
1214+ if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
1215+ vmaw, pbs, task->errp) != 0) {
1216+ goto err;
1217+ }
1218+ }
1219+
1220+ /* add firewall file to archive */
1221+ if (task->has_firewall_file) {
1222+ if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
1223+ vmaw, pbs, task->errp) != 0) {
1224+ goto err;
1225+ }
1226+ }
1227+ /* initialize global backup_state now */
1228+
1229+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
1230+
1231+ backup_state.stat.cancel = false;
1232+
1233+ if (backup_state.stat.error) {
1234+ error_free(backup_state.stat.error);
1235+ backup_state.stat.error = NULL;
1236+ }
1237+
1238+ backup_state.stat.start_time = time(NULL);
1239+ backup_state.stat.end_time = 0;
1240+
1241+ if (backup_state.stat.backup_file) {
1242+ g_free(backup_state.stat.backup_file);
1243+ }
1244+ backup_state.stat.backup_file = g_strdup(task->backup_file);
1245+
1246+ uuid_copy(backup_state.stat.uuid, uuid);
1247+ uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
1248+ char *uuid_str = g_strdup(backup_state.stat.uuid_str);
1249+
1250+ backup_state.stat.total = total;
1251+ backup_state.stat.transferred = 0;
1252+ backup_state.stat.zero_bytes = 0;
1253+
1254+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1255+
1256+ backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
1257+
1258+ backup_state.vmaw = vmaw;
1259+ backup_state.pbs = pbs;
1260+
1261+ backup_state.di_list = di_list;
1262+
1263+ /* start all jobs (paused state) */
1264+ l = di_list;
1265+ while (l) {
1266+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1267+ l = g_list_next(l);
1268+
1269+ // make sure target runs in same aoi_context as source
1270+ AioContext *aio_context = bdrv_get_aio_context(di->bs);
1271+ aio_context_acquire(aio_context);
1272+ GSList *ignore = NULL;
1273+ bdrv_set_aio_context_ignore(di->target, aio_context, &ignore);
1274+ g_slist_free(ignore);
1275+ aio_context_release(aio_context);
1276+
1277+ job = backup_job_create(NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
1278+ BITMAP_SYNC_MODE_NEVER, false, NULL, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
1279+ JOB_DEFAULT, pvebackup_complete_cb, di, 1, NULL, &local_err);
1280+ if (!job || local_err != NULL) {
1281+ qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
1282+ error_setg(&backup_state.stat.error, "backup_job_create failed");
1283+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1284+ break;
1285+ }
1286+ job_start(&job->job);
1287+ if (di->target) {
1288+ bdrv_unref(di->target);
1289+ di->target = NULL;
1290+ }
1291+ }
1292+
1293+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
1294+
1295+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
1296+ bool no_errors = !backup_state.stat.error;
1297+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1298+
1299+ if (no_errors) {
1300+ pvebackup_co_run_next_job(); // run one job
1301+ } else {
1302+ pvebackup_co_cancel(NULL);
1303+ }
1304+
1305+ uuid_info = g_malloc0(sizeof(*uuid_info));
1306+ uuid_info->UUID = uuid_str;
1307+
1308+ task->result = uuid_info;
1309+ return;
1310+
1311+err:
1312+
1313+ l = di_list;
1314+ while (l) {
1315+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1316+ l = g_list_next(l);
1317+
1318+ if (di->target) {
1319+ bdrv_unref(di->target);
1320+ }
1321+
1322+ if (di->targetfile[0]) {
1323+ unlink(di->targetfile);
1324+ }
1325+ g_free(di);
1326+ }
1327+ g_list_free(di_list);
1328+
1329+ if (devs) {
1330+ g_strfreev(devs);
1331+ }
1332+
1333+ if (vmaw) {
1334+ Error *err = NULL;
1335+ vma_writer_close(vmaw, &err);
1336+ unlink(task->backup_file);
1337+ }
1338+
1339+ if (pbs) {
1340+ proxmox_backup_disconnect(pbs);
1341+ }
1342+
1343+ if (backup_dir) {
1344+ rmdir(backup_dir);
1345+ }
1346+
1347+ qemu_co_mutex_unlock(&backup_state.backup_mutex);
1348+
1349+ task->result = NULL;
1350+ return;
1351+}
1352+
1353+UuidInfo *qmp_backup(
1354+ const char *backup_file,
1355+ bool has_password, const char *password,
1356+ bool has_keyfile, const char *keyfile,
1357+ bool has_key_password, const char *key_password,
1358+ bool has_fingerprint, const char *fingerprint,
1359+ bool has_backup_id, const char *backup_id,
1360+ bool has_backup_time, int64_t backup_time,
1361+ bool has_format, BackupFormat format,
1362+ bool has_config_file, const char *config_file,
1363+ bool has_firewall_file, const char *firewall_file,
1364+ bool has_devlist, const char *devlist,
1365+ bool has_speed, int64_t speed, Error **errp)
1366+{
1367+ QmpBackupTask task = {
1368+ .backup_file = backup_file,
1369+ .has_password = has_password,
1370+ .password = password,
1371+ .has_key_password = has_key_password,
1372+ .key_password = key_password,
1373+ .has_fingerprint = has_fingerprint,
1374+ .fingerprint = fingerprint,
1375+ .has_backup_id = has_backup_id,
1376+ .backup_id = backup_id,
1377+ .has_backup_time = has_backup_time,
1378+ .backup_time = backup_time,
1379+ .has_format = has_format,
1380+ .format = format,
1381+ .has_config_file = has_config_file,
1382+ .config_file = config_file,
1383+ .has_firewall_file = has_firewall_file,
1384+ .firewall_file = firewall_file,
1385+ .has_devlist = has_devlist,
1386+ .devlist = devlist,
1387+ .has_speed = has_speed,
1388+ .speed = speed,
1389+ .errp = errp,
1390+ };
1391+
1392+ block_on_coroutine_fn(pvebackup_co_start, &task);
1393+
1394+ return task.result;
1395+}
1396+
1397+
1398+typedef struct QmpQueryBackupTask {
1399+ Error **errp;
1400+ BackupStatus *result;
1401+} QmpQueryBackupTask;
1402+
1403+static void coroutine_fn pvebackup_co_query(void *opaque)
1404+{
1405+ assert(qemu_in_coroutine());
1406+
1407+ QmpQueryBackupTask *task = opaque;
1408+
1409+ BackupStatus *info = g_malloc0(sizeof(*info));
1410+
1411+ qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
1412+
1413+ if (!backup_state.stat.start_time) {
1414+ /* not started, return {} */
1415+ task->result = info;
1416+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1417+ return;
1418+ }
1419+
1420+ info->has_status = true;
1421+ info->has_start_time = true;
1422+ info->start_time = backup_state.stat.start_time;
1423+
1424+ if (backup_state.stat.backup_file) {
1425+ info->has_backup_file = true;
1426+ info->backup_file = g_strdup(backup_state.stat.backup_file);
1427+ }
1428+
1429+ info->has_uuid = true;
1430+ info->uuid = g_strdup(backup_state.stat.uuid_str);
1431+
1432+ if (backup_state.stat.end_time) {
1433+ if (backup_state.stat.error) {
1434+ info->status = g_strdup("error");
1435+ info->has_errmsg = true;
1436+ info->errmsg = g_strdup(error_get_pretty(backup_state.stat.error));
1437+ } else {
1438+ info->status = g_strdup("done");
1439+ }
1440+ info->has_end_time = true;
1441+ info->end_time = backup_state.stat.end_time;
1442+ } else {
1443+ info->status = g_strdup("active");
1444+ }
1445+
1446+ info->has_total = true;
1447+ info->total = backup_state.stat.total;
1448+ info->has_zero_bytes = true;
1449+ info->zero_bytes = backup_state.stat.zero_bytes;
1450+ info->has_transferred = true;
1451+ info->transferred = backup_state.stat.transferred;
1452+
1453+ task->result = info;
1454+
1455+ qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1456+}
1457+
1458+BackupStatus *qmp_query_backup(Error **errp)
1459+{
1460+ QmpQueryBackupTask task = {
1461+ .errp = errp,
1462+ .result = NULL,
1463+ };
1464+
1465+ block_on_coroutine_fn(pvebackup_co_query, &task);
1466+
1467+ return task.result;
1468+}
1469diff --git a/qapi/block-core.json b/qapi/block-core.json
1470index 783a868eb2..0b987ad6e3 100644
1471--- a/qapi/block-core.json
1472+++ b/qapi/block-core.json
1473@@ -800,6 +800,115 @@
1474 { 'command': 'query-block', 'returns': ['BlockInfo'] }
1475
1476
1477+##
1478+# @BackupStatus:
1479+#
1480+# Detailed backup status.
1481+#
1482+# @status: string describing the current backup status.
1483+# This can be 'active', 'done', 'error'. If this field is not
1484+# returned, no backup process has been initiated
1485+#
1486+# @errmsg: error message (only returned if status is 'error')
1487+#
1488+# @total: total amount of bytes involved in the backup process
1489+#
1490+# @transferred: amount of bytes already backed up.
1491+#
1492+# @zero-bytes: amount of 'zero' bytes detected.
1493+#
1494+# @start-time: time (epoch) when backup job started.
1495+#
1496+# @end-time: time (epoch) when backup job finished.
1497+#
1498+# @backup-file: backup file name
1499+#
1500+# @uuid: uuid for this backup job
1501+#
1502+##
1503+{ 'struct': 'BackupStatus',
1504+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
1505+ '*transferred': 'int', '*zero-bytes': 'int',
1506+ '*start-time': 'int', '*end-time': 'int',
1507+ '*backup-file': 'str', '*uuid': 'str' } }
1508+
1509+##
1510+# @BackupFormat:
1511+#
1512+# An enumeration of supported backup formats.
1513+#
1514+# @vma: Proxmox vma backup format
1515+##
1516+{ 'enum': 'BackupFormat',
1517+ 'data': [ 'vma', 'dir', 'pbs' ] }
1518+
1519+##
1520+# @backup:
1521+#
1522+# Starts a VM backup.
1523+#
1524+# @backup-file: the backup file name
1525+#
1526+# @format: format of the backup file
1527+#
1528+# @config-file: a configuration file to include into
1529+# the backup archive.
1530+#
1531+# @speed: the maximum speed, in bytes per second
1532+#
1533+# @devlist: list of block device names (separated by ',', ';'
1534+# or ':'). By default the backup includes all writable block devices.
1535+#
1536+# @password: backup server passsword (required for format 'pbs')
1537+#
1538+# @keyfile: keyfile used for encryption (optional for format 'pbs')
1539+#
1540+# @key-password: password for keyfile (optional for format 'pbs')
1541+#
1542+# @fingerprint: server cert fingerprint (optional for format 'pbs')
1543+#
1544+# @backup-id: backup ID (required for format 'pbs')
1545+#
1546+# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
1547+#
1548+# Returns: the uuid of the backup job
1549+#
1550+##
1551+{ 'command': 'backup', 'data': { 'backup-file': 'str',
1552+ '*password': 'str',
1553+ '*keyfile': 'str',
1554+ '*key-password': 'str',
1555+ '*fingerprint': 'str',
1556+ '*backup-id': 'str',
1557+ '*backup-time': 'int',
1558+ '*format': 'BackupFormat',
1559+ '*config-file': 'str',
1560+ '*firewall-file': 'str',
1561+ '*devlist': 'str', '*speed': 'int' },
1562+ 'returns': 'UuidInfo' }
1563+
1564+##
1565+# @query-backup:
1566+#
1567+# Returns information about current/last backup task.
1568+#
1569+# Returns: @BackupStatus
1570+#
1571+##
1572+{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1573+
1574+##
1575+# @backup-cancel:
1576+#
1577+# Cancel the current executing backup process.
1578+#
1579+# Returns: nothing on success
1580+#
1581+# Notes: This command succeeds even if there is no backup process running.
1582+#
1583+##
1584+{ 'command': 'backup-cancel' }
1585+
1586 ##
1587 # @BlockDeviceTimedStats:
1588 #
1589diff --git a/qapi/common.json b/qapi/common.json
1590index 7b9cbcd97b..c3b8bb7b48 100644
1591--- a/qapi/common.json
1592+++ b/qapi/common.json
1593@@ -144,3 +144,16 @@
1594 ##
1595 { 'enum': 'PCIELinkWidth',
1596 'data': [ '1', '2', '4', '8', '12', '16', '32' ] }
1597+
1598+##
1599+# @UuidInfo:
1600+#
1601+# Guest UUID information (Universally Unique Identifier).
1602+#
1603+# @UUID: the UUID of the guest
1604+#
1605+# Since: 0.14.0
1606+#
1607+# Notes: If no UUID was specified for the guest, a null UUID is returned.
1608+##
1609+{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1610diff --git a/qapi/misc.json b/qapi/misc.json
1611index 4c4618a574..7d506b5300 100644
1612--- a/qapi/misc.json
1613+++ b/qapi/misc.json
1614@@ -270,19 +270,6 @@
1615 ##
1616 { 'command': 'query-kvm', 'returns': 'KvmInfo' }
1617
1618-##
1619-# @UuidInfo:
1620-#
1621-# Guest UUID information (Universally Unique Identifier).
1622-#
1623-# @UUID: the UUID of the guest
1624-#
1625-# Since: 0.14.0
1626-#
1627-# Notes: If no UUID was specified for the guest, a null UUID is returned.
1628-##
1629-{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1630-
1631 ##
1632 # @query-uuid:
1633 #