]> git.proxmox.com Git - pve-qemu.git/blob - 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
1 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2 From: Dietmar Maurer <dietmar@proxmox.com>
3 Date: Mon, 6 Apr 2020 12:16:59 +0200
4 Subject: [PATCH] PVE-Backup: proxmox backup patches for qemu
5
6 ---
7 block/meson.build | 5 +
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 +
14 meson.build | 1 +
15 monitor/hmp-cmds.c | 44 ++
16 proxmox-backup-client.c | 176 ++++++
17 proxmox-backup-client.h | 59 ++
18 pve-backup.c | 955 +++++++++++++++++++++++++++++++++
19 qapi/block-core.json | 109 ++++
20 qapi/common.json | 13 +
21 qapi/machine.json | 15 +-
22 15 files changed, 1444 insertions(+), 14 deletions(-)
23 create mode 100644 proxmox-backup-client.c
24 create mode 100644 proxmox-backup-client.h
25 create mode 100644 pve-backup.c
26
27 diff --git a/block/meson.build b/block/meson.build
28 index 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)
33
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 +
40
41 softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
42
43 diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
44 index d15a2be827..9ba7c774a2 100644
45 --- a/block/monitor/block-hmp-cmds.c
46 +++ b/block/monitor/block-hmp-cmds.c
47 @@ -1012,3 +1012,36 @@ void hmp_info_snapshots(Monitor *mon, const QDict *qdict)
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,
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
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 +}
84 diff --git a/blockdev.c b/blockdev.c
85 index fe6fb5dc1d..bae80b9177 100644
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"
96 diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
97 index b3b797ca28..295e14e64f 100644
98 --- a/hmp-commands-info.hx
99 +++ b/hmp-commands-info.hx
100 @@ -513,6 +513,19 @@ SRST
101 Show CPU statistics.
102 ERST
103
104 + {
105 + .name = "backup",
106 + .args_type = "",
107 + .params = "",
108 + .help = "show backup status",
109 + .cmd = hmp_info_backup,
110 + },
111 +
112 +SRST
113 + ``info backup``
114 + Show backup status.
115 +ERST
116 +
117 #if defined(CONFIG_SLIRP)
118 {
119 .name = "usernet",
120 diff --git a/hmp-commands.hx b/hmp-commands.hx
121 index d294c234a5..0c6b944850 100644
122 --- a/hmp-commands.hx
123 +++ b/hmp-commands.hx
124 @@ -98,6 +98,35 @@ ERST
125 SRST
126 ``block_stream``
127 Copy data from a backing file into a block device.
128 +ERST
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 +
140 +SRST
141 +``backup``
142 + Create a VM backup.
143 +ERST
144 +
145 + {
146 + .name = "backup_cancel",
147 + .args_type = "",
148 + .params = "",
149 + .help = "cancel the current VM backup",
150 + .cmd = hmp_backup_cancel,
151 + },
152 +
153 +SRST
154 +``backup_cancel``
155 + Cancel the current VM backup.
156 +
157 ERST
158
159 {
160 diff --git a/include/block/block_int.h b/include/block/block_int.h
161 index 2645e53282..9fa282ff54 100644
162 --- a/include/block/block_int.h
163 +++ b/include/block/block_int.h
164 @@ -65,7 +65,7 @@
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,
173 diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
174 index 4e06f89e8e..10f52bd92a 100644
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);
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);
188 void hmp_change(Monitor *mon, const QDict *qdict);
189 +void hmp_backup(Monitor *mon, const QDict *qdict);
190 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
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);
194 diff --git a/meson.build b/meson.build
195 index 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
206 diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
207 index 77ab152aab..182e79c943 100644
208 --- a/monitor/hmp-cmds.c
209 +++ b/monitor/hmp-cmds.c
210 @@ -195,6 +195,50 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
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) {
261 diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
262 new file mode 100644
263 index 0000000000..a8f6653a81
264 --- /dev/null
265 +++ b/proxmox-backup-client.c
266 @@ -0,0 +1,176 @@
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 +
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) {
307 + CoCtxData *waker = (CoCtxData *)data;
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();
316 + CoCtxData waker = { .co = co, .ctx = ctx };
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();
339 + CoCtxData waker = { .co = co, .ctx = ctx };
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();
362 + CoCtxData waker = { .co = co, .ctx = ctx };
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();
383 + CoCtxData waker = { .co = co, .ctx = ctx };
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();
405 + CoCtxData waker = { .co = co, .ctx = ctx };
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();
430 + CoCtxData waker = { .co = co, .ctx = ctx };
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 +}
443 diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
444 new file mode 100644
445 index 0000000000..1dda8b7d8f
446 --- /dev/null
447 +++ b/proxmox-backup-client.h
448 @@ -0,0 +1,59 @@
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 +
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
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 */
508 diff --git a/pve-backup.c b/pve-backup.c
509 new file mode 100644
510 index 0000000000..55441eb9d1
511 --- /dev/null
512 +++ b/pve-backup.c
513 @@ -0,0 +1,955 @@
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);
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 +}
1469 diff --git a/qapi/block-core.json b/qapi/block-core.json
1470 index 7957b9867d..be67dc3376 100644
1471 --- a/qapi/block-core.json
1472 +++ b/qapi/block-core.json
1473 @@ -745,6 +745,115 @@
1474 { 'command': 'query-block', 'returns': ['BlockInfo'] }
1475
1476
1477 +##
1478 +# @BackupStatus:
1479 +#
1480 +# Detailed backup status.
1481 +#
1482 +# @status: string describing the current backup status.
1483 +# This can be 'active', 'done', 'error'. If this field is not
1484 +# returned, no backup process has been initiated
1485 +#
1486 +# @errmsg: error message (only returned if status is 'error')
1487 +#
1488 +# @total: total amount of bytes involved in the backup process
1489 +#
1490 +# @transferred: amount of bytes already backed up.
1491 +#
1492 +# @zero-bytes: amount of 'zero' bytes detected.
1493 +#
1494 +# @start-time: time (epoch) when backup job started.
1495 +#
1496 +# @end-time: time (epoch) when backup job finished.
1497 +#
1498 +# @backup-file: backup file name
1499 +#
1500 +# @uuid: uuid for this backup job
1501 +#
1502 +##
1503 +{ 'struct': 'BackupStatus',
1504 + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
1505 + '*transferred': 'int', '*zero-bytes': 'int',
1506 + '*start-time': 'int', '*end-time': 'int',
1507 + '*backup-file': 'str', '*uuid': 'str' } }
1508 +
1509 +##
1510 +# @BackupFormat:
1511 +#
1512 +# An enumeration of supported backup formats.
1513 +#
1514 +# @vma: Proxmox vma backup format
1515 +##
1516 +{ 'enum': 'BackupFormat',
1517 + 'data': [ 'vma', 'dir', 'pbs' ] }
1518 +
1519 +##
1520 +# @backup:
1521 +#
1522 +# Starts a VM backup.
1523 +#
1524 +# @backup-file: the backup file name
1525 +#
1526 +# @format: format of the backup file
1527 +#
1528 +# @config-file: a configuration file to include into
1529 +# the backup archive.
1530 +#
1531 +# @speed: the maximum speed, in bytes per second
1532 +#
1533 +# @devlist: list of block device names (separated by ',', ';'
1534 +# or ':'). By default the backup includes all writable block devices.
1535 +#
1536 +# @password: backup server passsword (required for format 'pbs')
1537 +#
1538 +# @keyfile: keyfile used for encryption (optional for format 'pbs')
1539 +#
1540 +# @key-password: password for keyfile (optional for format 'pbs')
1541 +#
1542 +# @fingerprint: server cert fingerprint (optional for format 'pbs')
1543 +#
1544 +# @backup-id: backup ID (required for format 'pbs')
1545 +#
1546 +# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
1547 +#
1548 +# Returns: the uuid of the backup job
1549 +#
1550 +##
1551 +{ 'command': 'backup', 'data': { 'backup-file': 'str',
1552 + '*password': 'str',
1553 + '*keyfile': 'str',
1554 + '*key-password': 'str',
1555 + '*fingerprint': 'str',
1556 + '*backup-id': 'str',
1557 + '*backup-time': 'int',
1558 + '*format': 'BackupFormat',
1559 + '*config-file': 'str',
1560 + '*firewall-file': 'str',
1561 + '*devlist': 'str', '*speed': 'int' },
1562 + 'returns': 'UuidInfo' }
1563 +
1564 +##
1565 +# @query-backup:
1566 +#
1567 +# Returns information about current/last backup task.
1568 +#
1569 +# Returns: @BackupStatus
1570 +#
1571 +##
1572 +{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1573 +
1574 +##
1575 +# @backup-cancel:
1576 +#
1577 +# Cancel the current executing backup process.
1578 +#
1579 +# Returns: nothing on success
1580 +#
1581 +# Notes: This command succeeds even if there is no backup process running.
1582 +#
1583 +##
1584 +{ 'command': 'backup-cancel' }
1585 +
1586 ##
1587 # @BlockDeviceTimedStats:
1588 #
1589 diff --git a/qapi/common.json b/qapi/common.json
1590 index 716712d4b3..556dab79e1 100644
1591 --- a/qapi/common.json
1592 +++ b/qapi/common.json
1593 @@ -145,3 +145,16 @@
1594 ##
1595 { 'enum': 'PCIELinkWidth',
1596 'data': [ '1', '2', '4', '8', '12', '16', '32' ] }
1597 +
1598 +##
1599 +# @UuidInfo:
1600 +#
1601 +# Guest UUID information (Universally Unique Identifier).
1602 +#
1603 +# @UUID: the UUID of the guest
1604 +#
1605 +# Since: 0.14.0
1606 +#
1607 +# Notes: If no UUID was specified for the guest, a null UUID is returned.
1608 +##
1609 +{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1610 diff --git a/qapi/machine.json b/qapi/machine.json
1611 index 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 @@
1624 ##
1625 { 'command': 'query-target', 'returns': 'TargetInfo' }
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 #