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