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