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