]> git.proxmox.com Git - pve-qemu.git/blob - debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch
update submodule and patches to QEMU 8.1.2
[pve-qemu.git] / debian / patches / pve / 0030-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 MIME-Version: 1.0
6 Content-Type: text/plain; charset=UTF-8
7 Content-Transfer-Encoding: 8bit
8
9 For PBS, using dirty bitmaps is supported via QEMU's
10 MIRROR_SYNC_MODE_BITMAP. When the feature is used, the data-write
11 callback is only executed for any changed chunks, the PBS rust code
12 will reuse chunks from the previous index for everything it doesn't
13 receive if reuse_index is true. On error or cancellation, all dirty
14 bitmaps are removed to ensure consistency.
15
16 By using a JobTxn, we can sync dirty bitmaps only when *all* jobs were
17 successful - meaning we don't need to remove them when the backup
18 fails, since QEMU's BITMAP_SYNC_MODE_ON_SUCCESS will now handle that
19 for us. A sequential transaction is used, so drives will be backed up
20 one after the other.
21
22 The backup and backup-cancel QMP calls are coroutines. This has the
23 benefit that calls are asynchronous to the main loop, i.e. long
24 running operations like connecting to a PBS server will no longer hang
25 the VM.
26
27 backup_job_create() and job_cancel_sync() cannot be run from a
28 coroutine and requires an acuqired AioContext, so the job creation and
29 canceling are extracted as bottom halves and called from the
30 respective QMP coroutines.
31
32 To communicate the finishing state, a dedicated property is used for
33 query-backup: 'finishing'. A dedicated state is explicitly not used,
34 since that would break compatibility with older qemu-server versions.
35
36 The first call to job_cancel_sync() will cancel and free all jobs in
37 the transaction, but it is necessary to pick a job that is:
38 1. still referenced. For this, there is a job_ref directly after job
39 creation paired with a job_unref in cleanup paths.
40 2. not yet finalized. In job_cancel_bh(), the first job that's not
41 completed yet is used. This is not necessarily the first job in the
42 list, because pvebackup_co_complete_stream() might not yet have
43 removed a completed job when job_cancel_bh() runs. Why even bother
44 with the bottom half at all and not use job_cancel() in
45 qmp_backup_cancel() directly? The reason is that qmp_backup_cancel()
46 is a coroutine, so it will hang when reaching AIO_WAIT_WHILE() and
47 job_cancel() might end up calling that.
48
49 Regarding BackupPerf performance settings. For now, only the
50 max-workers setting is exposed, because:
51 1. use-copy-range would need to be implemented in backup-dump and the
52 feature was actually turned off by default in QEMU itself, because it
53 didn't provide the expected benefit, see commit 6a30f663d4 ("qapi:
54 backup: disable copy_range by default").
55 2. max-chunk: enforced to be at least the backup cluster size (4 MiB
56 for PBS) and otherwise maximum of source and target cluster size.
57 And block-copy has a maximum buffer size of 1 MiB, so setting a larger
58 max-chunk doesn't even have an effect. To make the setting sensibly
59 usable the check would need to be removed and optionally the
60 block-copy max buffer size would need to be bumped. I tried doing just
61 that, and tested different source/target combinations with different
62 max-chunk settings, but there were no noticable improvements over the
63 default "unlimited" (resulting in 1 MiB for block-copy).
64
65 Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
66 [SR: Add dirty-bitmap tracking for incremental backups
67 Add query_proxmox_support and query-pbs-bitmap-info QMP calls
68 Use a transaction to synchronize job states
69 Co-routine and async-related improvements
70 Improve finishing backups/cleanups
71 Various other improvements]
72 Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
73 [FG: add master key support]
74 Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
75 Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
76 [WB: add PBS namespace support]
77 Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
78 [FE: add new force parameter to job_cancel_sync calls
79 adapt for new job lock mechanism replacing AioContext locks
80 adapt to QAPI changes
81 improve canceling
82 allow passing max-workers setting
83 use malloc_trim after backup
84 create jobs in a drained section]
85 Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
86 ---
87 block/backup-dump.c | 10 +-
88 block/meson.build | 5 +
89 block/monitor/block-hmp-cmds.c | 39 ++
90 blockdev.c | 1 +
91 hmp-commands-info.hx | 14 +
92 hmp-commands.hx | 29 +
93 include/block/block_int-common.h | 2 +-
94 include/monitor/hmp.h | 3 +
95 meson.build | 1 +
96 monitor/hmp-cmds.c | 72 ++
97 proxmox-backup-client.c | 146 ++++
98 proxmox-backup-client.h | 60 ++
99 pve-backup.c | 1067 ++++++++++++++++++++++++++++++
100 qapi/block-core.json | 229 +++++++
101 qapi/common.json | 14 +
102 qapi/machine.json | 16 +-
103 16 files changed, 1690 insertions(+), 18 deletions(-)
104 create mode 100644 proxmox-backup-client.c
105 create mode 100644 proxmox-backup-client.h
106 create mode 100644 pve-backup.c
107
108 diff --git a/block/backup-dump.c b/block/backup-dump.c
109 index 232a094426..e46abf1070 100644
110 --- a/block/backup-dump.c
111 +++ b/block/backup-dump.c
112 @@ -9,6 +9,8 @@
113 */
114
115 #include "qemu/osdep.h"
116 +
117 +#include "qapi/qmp/qdict.h"
118 #include "qom/object_interfaces.h"
119 #include "block/block_int.h"
120
121 @@ -141,7 +143,7 @@ static void bdrv_backup_dump_init(void)
122 block_init(bdrv_backup_dump_init);
123
124
125 -BlockDriverState *bdrv_backup_dump_create(
126 +BlockDriverState *coroutine_fn bdrv_co_backup_dump_create(
127 int dump_cb_block_size,
128 uint64_t byte_size,
129 BackupDumpFunc *dump_cb,
130 @@ -149,9 +151,11 @@ BlockDriverState *bdrv_backup_dump_create(
131 Error **errp)
132 {
133 BDRVBackupDumpState *state;
134 - BlockDriverState *bs = bdrv_new_open_driver(
135 - &bdrv_backup_dump_drive, NULL, BDRV_O_RDWR, errp);
136
137 + QDict *options = qdict_new();
138 + qdict_put_str(options, "driver", "backup-dump-drive");
139 +
140 + BlockDriverState *bs = bdrv_co_open(NULL, NULL, options, BDRV_O_RDWR, errp);
141 if (!bs) {
142 return NULL;
143 }
144 diff --git a/block/meson.build b/block/meson.build
145 index 6fde9f7dcd..6d468f89e5 100644
146 --- a/block/meson.build
147 +++ b/block/meson.build
148 @@ -45,6 +45,11 @@ block_ss.add(files(
149 ), zstd, zlib, gnutls)
150
151 block_ss.add(files('../vma-writer.c'), libuuid)
152 +block_ss.add(files(
153 + '../proxmox-backup-client.c',
154 + '../pve-backup.c',
155 +), libproxmox_backup_qemu)
156 +
157
158 system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
159 system_ss.add(files('block-ram-registrar.c'))
160 diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
161 index ca2599de44..6efe28cef5 100644
162 --- a/block/monitor/block-hmp-cmds.c
163 +++ b/block/monitor/block-hmp-cmds.c
164 @@ -1029,3 +1029,42 @@ void hmp_change_medium(Monitor *mon, const char *device, const char *target,
165 qmp_blockdev_change_medium(device, NULL, target, arg, true, force,
166 !!read_only, read_only_mode, errp);
167 }
168 +
169 +void coroutine_fn hmp_backup_cancel(Monitor *mon, const QDict *qdict)
170 +{
171 + Error *error = NULL;
172 +
173 + qmp_backup_cancel(&error);
174 +
175 + hmp_handle_error(mon, error);
176 +}
177 +
178 +void coroutine_fn hmp_backup(Monitor *mon, const QDict *qdict)
179 +{
180 + Error *error = NULL;
181 +
182 + const char *backup_file = qdict_get_str(qdict, "backupfile");
183 + const char *devlist = qdict_get_try_str(qdict, "devlist");
184 + int64_t speed = qdict_get_try_int(qdict, "speed", 0);
185 +
186 + qmp_backup(
187 + backup_file,
188 + NULL, // PBS password
189 + NULL, // PBS keyfile
190 + NULL, // PBS key_password
191 + NULL, // PBS master_keyfile
192 + NULL, // PBS fingerprint
193 + NULL, // PBS backup-ns
194 + NULL, // PBS backup-id
195 + false, 0, // PBS backup-time
196 + false, false, // PBS use-dirty-bitmap
197 + false, false, // PBS compress
198 + false, false, // PBS encrypt
199 + true, BACKUP_FORMAT_VMA,
200 + NULL, NULL,
201 + devlist, qdict_haskey(qdict, "speed"), speed,
202 + false, 0, // BackupPerf max-workers
203 + &error);
204 +
205 + hmp_handle_error(mon, error);
206 +}
207 diff --git a/blockdev.c b/blockdev.c
208 index 060d86a65f..79c3575612 100644
209 --- a/blockdev.c
210 +++ b/blockdev.c
211 @@ -37,6 +37,7 @@
212 #include "block/blockjob.h"
213 #include "block/dirty-bitmap.h"
214 #include "block/qdict.h"
215 +#include "block/blockjob_int.h"
216 #include "block/throttle-groups.h"
217 #include "monitor/monitor.h"
218 #include "qemu/error-report.h"
219 diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
220 index 10fdd822e0..15937793c1 100644
221 --- a/hmp-commands-info.hx
222 +++ b/hmp-commands-info.hx
223 @@ -471,6 +471,20 @@ SRST
224 Show the current VM UUID.
225 ERST
226
227 +
228 + {
229 + .name = "backup",
230 + .args_type = "",
231 + .params = "",
232 + .help = "show backup status",
233 + .cmd = hmp_info_backup,
234 + },
235 +
236 +SRST
237 + ``info backup``
238 + Show backup status.
239 +ERST
240 +
241 #if defined(CONFIG_SLIRP)
242 {
243 .name = "usernet",
244 diff --git a/hmp-commands.hx b/hmp-commands.hx
245 index e352f86872..0c8b6725fb 100644
246 --- a/hmp-commands.hx
247 +++ b/hmp-commands.hx
248 @@ -101,6 +101,35 @@ ERST
249 SRST
250 ``block_stream``
251 Copy data from a backing file into a block device.
252 +ERST
253 +
254 + {
255 + .name = "backup",
256 + .args_type = "backupfile:s,speed:o?,devlist:s?",
257 + .params = "backupfile [speed [devlist]]",
258 + .help = "create a VM backup (VMA format).",
259 + .cmd = hmp_backup,
260 + .coroutine = true,
261 + },
262 +
263 +SRST
264 +``backup``
265 + Create a VM backup.
266 +ERST
267 +
268 + {
269 + .name = "backup_cancel",
270 + .args_type = "",
271 + .params = "",
272 + .help = "cancel the current VM backup",
273 + .cmd = hmp_backup_cancel,
274 + .coroutine = true,
275 + },
276 +
277 +SRST
278 +``backup_cancel``
279 + Cancel the current VM backup.
280 +
281 ERST
282
283 {
284 diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
285 index 0f2e1817ad..0a0339eee4 100644
286 --- a/include/block/block_int-common.h
287 +++ b/include/block/block_int-common.h
288 @@ -63,7 +63,7 @@
289
290 typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
291
292 -BlockDriverState *bdrv_backup_dump_create(
293 +BlockDriverState *coroutine_fn bdrv_co_backup_dump_create(
294 int dump_cb_block_size,
295 uint64_t byte_size,
296 BackupDumpFunc *dump_cb,
297 diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
298 index 7a7def7530..cba7afe70c 100644
299 --- a/include/monitor/hmp.h
300 +++ b/include/monitor/hmp.h
301 @@ -32,6 +32,7 @@ void hmp_info_savevm(Monitor *mon, const QDict *qdict);
302 void hmp_info_migrate(Monitor *mon, const QDict *qdict);
303 void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
304 void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
305 +void hmp_info_backup(Monitor *mon, const QDict *qdict);
306 void hmp_info_cpus(Monitor *mon, const QDict *qdict);
307 void hmp_info_vnc(Monitor *mon, const QDict *qdict);
308 void hmp_info_spice(Monitor *mon, const QDict *qdict);
309 @@ -84,6 +85,8 @@ void hmp_change_vnc(Monitor *mon, const char *device, const char *target,
310 void hmp_change_medium(Monitor *mon, const char *device, const char *target,
311 const char *arg, const char *read_only, bool force,
312 Error **errp);
313 +void hmp_backup(Monitor *mon, const QDict *qdict);
314 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
315 void hmp_migrate(Monitor *mon, const QDict *qdict);
316 void hmp_device_add(Monitor *mon, const QDict *qdict);
317 void hmp_device_del(Monitor *mon, const QDict *qdict);
318 diff --git a/meson.build b/meson.build
319 index cd95530d3b..d53976d621 100644
320 --- a/meson.build
321 +++ b/meson.build
322 @@ -1779,6 +1779,7 @@ endif
323 has_gettid = cc.has_function('gettid')
324
325 libuuid = cc.find_library('uuid', required: true)
326 +libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
327
328 # libselinux
329 selinux = dependency('libselinux',
330 diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
331 index 91be698308..5b9c231a4c 100644
332 --- a/monitor/hmp-cmds.c
333 +++ b/monitor/hmp-cmds.c
334 @@ -21,6 +21,7 @@
335 #include "qemu/help_option.h"
336 #include "monitor/monitor-internal.h"
337 #include "qapi/error.h"
338 +#include "qapi/qapi-commands-block-core.h"
339 #include "qapi/qapi-commands-control.h"
340 #include "qapi/qapi-commands-migration.h"
341 #include "qapi/qapi-commands-misc.h"
342 @@ -144,6 +145,77 @@ void hmp_sync_profile(Monitor *mon, const QDict *qdict)
343 }
344 }
345
346 +void hmp_info_backup(Monitor *mon, const QDict *qdict)
347 +{
348 + BackupStatus *info;
349 + PBSBitmapInfoList *bitmap_info;
350 +
351 + info = qmp_query_backup(NULL);
352 +
353 + if (!info) {
354 + monitor_printf(mon, "Backup status: not initialized\n");
355 + return;
356 + }
357 +
358 + if (info->status) {
359 + if (info->errmsg) {
360 + monitor_printf(mon, "Backup status: %s - %s\n",
361 + info->status, info->errmsg);
362 + } else {
363 + monitor_printf(mon, "Backup status: %s\n", info->status);
364 + }
365 + }
366 +
367 + if (info->backup_file) {
368 + monitor_printf(mon, "Start time: %s", ctime(&info->start_time));
369 + if (info->end_time) {
370 + monitor_printf(mon, "End time: %s", ctime(&info->end_time));
371 + }
372 +
373 + monitor_printf(mon, "Backup file: %s\n", info->backup_file);
374 + monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
375 +
376 + if (!(info->has_total && info->total)) {
377 + // this should not happen normally
378 + monitor_printf(mon, "Total size: %d\n", 0);
379 + } else {
380 + size_t total_or_dirty = info->total;
381 + bitmap_info = qmp_query_pbs_bitmap_info(NULL);
382 +
383 + while (bitmap_info) {
384 + monitor_printf(mon, "Drive %s:\n",
385 + bitmap_info->value->drive);
386 + monitor_printf(mon, " bitmap action: %s\n",
387 + PBSBitmapAction_str(bitmap_info->value->action));
388 + monitor_printf(mon, " size: %zd\n",
389 + bitmap_info->value->size);
390 + monitor_printf(mon, " dirty: %zd\n",
391 + bitmap_info->value->dirty);
392 + bitmap_info = bitmap_info->next;
393 + }
394 +
395 + qapi_free_PBSBitmapInfoList(bitmap_info);
396 +
397 + int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
398 + (info->zero_bytes * 100)/info->total : 0;
399 + monitor_printf(mon, "Total size: %zd\n", info->total);
400 + int trans_per = (info->transferred * 100)/total_or_dirty;
401 + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
402 + info->transferred, trans_per);
403 + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
404 + info->zero_bytes, zero_per);
405 +
406 + if (info->has_reused) {
407 + int reused_per = (info->reused * 100)/total_or_dirty;
408 + monitor_printf(mon, "Reused bytes: %zd (%d%%)\n",
409 + info->reused, reused_per);
410 + }
411 + }
412 + }
413 +
414 + qapi_free_BackupStatus(info);
415 +}
416 +
417 void hmp_exit_preconfig(Monitor *mon, const QDict *qdict)
418 {
419 Error *err = NULL;
420 diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
421 new file mode 100644
422 index 0000000000..0923037dec
423 --- /dev/null
424 +++ b/proxmox-backup-client.c
425 @@ -0,0 +1,146 @@
426 +#include "proxmox-backup-client.h"
427 +#include "qemu/main-loop.h"
428 +#include "block/aio-wait.h"
429 +#include "qapi/error.h"
430 +
431 +/* Proxmox Backup Server client bindings using coroutines */
432 +
433 +// This is called from another thread, so we use aio_co_schedule()
434 +static void proxmox_backup_schedule_wake(void *data) {
435 + CoCtxData *waker = (CoCtxData *)data;
436 + aio_co_schedule(waker->ctx, waker->co);
437 +}
438 +
439 +int coroutine_fn
440 +proxmox_backup_co_connect(ProxmoxBackupHandle *pbs, Error **errp)
441 +{
442 + Coroutine *co = qemu_coroutine_self();
443 + AioContext *ctx = qemu_get_current_aio_context();
444 + CoCtxData waker = { .co = co, .ctx = ctx };
445 + char *pbs_err = NULL;
446 + int pbs_res = -1;
447 +
448 + proxmox_backup_connect_async(pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
449 + qemu_coroutine_yield();
450 + if (pbs_res < 0) {
451 + if (errp) error_setg(errp, "backup connect failed: %s", pbs_err ? pbs_err : "unknown error");
452 + if (pbs_err) proxmox_backup_free_error(pbs_err);
453 + }
454 + return pbs_res;
455 +}
456 +
457 +int coroutine_fn
458 +proxmox_backup_co_add_config(
459 + ProxmoxBackupHandle *pbs,
460 + const char *name,
461 + const uint8_t *data,
462 + uint64_t size,
463 + Error **errp)
464 +{
465 + Coroutine *co = qemu_coroutine_self();
466 + AioContext *ctx = qemu_get_current_aio_context();
467 + CoCtxData waker = { .co = co, .ctx = ctx };
468 + char *pbs_err = NULL;
469 + int pbs_res = -1;
470 +
471 + proxmox_backup_add_config_async(
472 + pbs, name, data, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
473 + qemu_coroutine_yield();
474 + if (pbs_res < 0) {
475 + if (errp) error_setg(errp, "backup add_config %s failed: %s", name, pbs_err ? pbs_err : "unknown error");
476 + if (pbs_err) proxmox_backup_free_error(pbs_err);
477 + }
478 + return pbs_res;
479 +}
480 +
481 +int coroutine_fn
482 +proxmox_backup_co_register_image(
483 + ProxmoxBackupHandle *pbs,
484 + const char *device_name,
485 + uint64_t size,
486 + bool incremental,
487 + Error **errp)
488 +{
489 + Coroutine *co = qemu_coroutine_self();
490 + AioContext *ctx = qemu_get_current_aio_context();
491 + CoCtxData waker = { .co = co, .ctx = ctx };
492 + char *pbs_err = NULL;
493 + int pbs_res = -1;
494 +
495 + proxmox_backup_register_image_async(
496 + pbs, device_name, size, incremental, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
497 + qemu_coroutine_yield();
498 + if (pbs_res < 0) {
499 + if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
500 + if (pbs_err) proxmox_backup_free_error(pbs_err);
501 + }
502 + return pbs_res;
503 +}
504 +
505 +int coroutine_fn
506 +proxmox_backup_co_finish(
507 + ProxmoxBackupHandle *pbs,
508 + Error **errp)
509 +{
510 + Coroutine *co = qemu_coroutine_self();
511 + AioContext *ctx = qemu_get_current_aio_context();
512 + CoCtxData waker = { .co = co, .ctx = ctx };
513 + char *pbs_err = NULL;
514 + int pbs_res = -1;
515 +
516 + proxmox_backup_finish_async(
517 + pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
518 + qemu_coroutine_yield();
519 + if (pbs_res < 0) {
520 + if (errp) error_setg(errp, "backup finish failed: %s", pbs_err ? pbs_err : "unknown error");
521 + if (pbs_err) proxmox_backup_free_error(pbs_err);
522 + }
523 + return pbs_res;
524 +}
525 +
526 +int coroutine_fn
527 +proxmox_backup_co_close_image(
528 + ProxmoxBackupHandle *pbs,
529 + uint8_t dev_id,
530 + Error **errp)
531 +{
532 + Coroutine *co = qemu_coroutine_self();
533 + AioContext *ctx = qemu_get_current_aio_context();
534 + CoCtxData waker = { .co = co, .ctx = ctx };
535 + char *pbs_err = NULL;
536 + int pbs_res = -1;
537 +
538 + proxmox_backup_close_image_async(
539 + pbs, dev_id, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
540 + qemu_coroutine_yield();
541 + if (pbs_res < 0) {
542 + if (errp) error_setg(errp, "backup close image failed: %s", pbs_err ? pbs_err : "unknown error");
543 + if (pbs_err) proxmox_backup_free_error(pbs_err);
544 + }
545 + return pbs_res;
546 +}
547 +
548 +int coroutine_fn
549 +proxmox_backup_co_write_data(
550 + ProxmoxBackupHandle *pbs,
551 + uint8_t dev_id,
552 + const uint8_t *data,
553 + uint64_t offset,
554 + uint64_t size,
555 + Error **errp)
556 +{
557 + Coroutine *co = qemu_coroutine_self();
558 + AioContext *ctx = qemu_get_current_aio_context();
559 + CoCtxData waker = { .co = co, .ctx = ctx };
560 + char *pbs_err = NULL;
561 + int pbs_res = -1;
562 +
563 + proxmox_backup_write_data_async(
564 + pbs, dev_id, data, offset, size, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
565 + qemu_coroutine_yield();
566 + if (pbs_res < 0) {
567 + if (errp) error_setg(errp, "backup write data failed: %s", pbs_err ? pbs_err : "unknown error");
568 + if (pbs_err) proxmox_backup_free_error(pbs_err);
569 + }
570 + return pbs_res;
571 +}
572 diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
573 new file mode 100644
574 index 0000000000..8cbf645b2c
575 --- /dev/null
576 +++ b/proxmox-backup-client.h
577 @@ -0,0 +1,60 @@
578 +#ifndef PROXMOX_BACKUP_CLIENT_H
579 +#define PROXMOX_BACKUP_CLIENT_H
580 +
581 +#include "qemu/osdep.h"
582 +#include "qemu/coroutine.h"
583 +#include "proxmox-backup-qemu.h"
584 +
585 +typedef struct CoCtxData {
586 + Coroutine *co;
587 + AioContext *ctx;
588 + void *data;
589 +} CoCtxData;
590 +
591 +// FIXME: Remove once coroutines are supported for QMP
592 +void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg);
593 +
594 +int coroutine_fn
595 +proxmox_backup_co_connect(
596 + ProxmoxBackupHandle *pbs,
597 + Error **errp);
598 +
599 +int coroutine_fn
600 +proxmox_backup_co_add_config(
601 + ProxmoxBackupHandle *pbs,
602 + const char *name,
603 + const uint8_t *data,
604 + uint64_t size,
605 + Error **errp);
606 +
607 +int coroutine_fn
608 +proxmox_backup_co_register_image(
609 + ProxmoxBackupHandle *pbs,
610 + const char *device_name,
611 + uint64_t size,
612 + bool incremental,
613 + Error **errp);
614 +
615 +
616 +int coroutine_fn
617 +proxmox_backup_co_finish(
618 + ProxmoxBackupHandle *pbs,
619 + Error **errp);
620 +
621 +int coroutine_fn
622 +proxmox_backup_co_close_image(
623 + ProxmoxBackupHandle *pbs,
624 + uint8_t dev_id,
625 + Error **errp);
626 +
627 +int coroutine_fn
628 +proxmox_backup_co_write_data(
629 + ProxmoxBackupHandle *pbs,
630 + uint8_t dev_id,
631 + const uint8_t *data,
632 + uint64_t offset,
633 + uint64_t size,
634 + Error **errp);
635 +
636 +
637 +#endif /* PROXMOX_BACKUP_CLIENT_H */
638 diff --git a/pve-backup.c b/pve-backup.c
639 new file mode 100644
640 index 0000000000..d84d807654
641 --- /dev/null
642 +++ b/pve-backup.c
643 @@ -0,0 +1,1067 @@
644 +#include "proxmox-backup-client.h"
645 +#include "vma.h"
646 +
647 +#include "qemu/osdep.h"
648 +#include "qemu/module.h"
649 +#include "sysemu/block-backend.h"
650 +#include "sysemu/blockdev.h"
651 +#include "block/block_int-global-state.h"
652 +#include "block/blockjob.h"
653 +#include "block/dirty-bitmap.h"
654 +#include "qapi/qapi-commands-block.h"
655 +#include "qapi/qmp/qerror.h"
656 +#include "qemu/cutils.h"
657 +
658 +#if defined(CONFIG_MALLOC_TRIM)
659 +#include <malloc.h>
660 +#endif
661 +
662 +#include <proxmox-backup-qemu.h>
663 +
664 +/* PVE backup state and related function */
665 +
666 +/*
667 + * Note: A resume from a qemu_coroutine_yield can happen in a different thread,
668 + * so you may not use normal mutexes within coroutines:
669 + *
670 + * ---bad-example---
671 + * qemu_rec_mutex_lock(lock)
672 + * ...
673 + * qemu_coroutine_yield() // wait for something
674 + * // we are now inside a different thread
675 + * qemu_rec_mutex_unlock(lock) // Crash - wrong thread!!
676 + * ---end-bad-example--
677 + *
678 + * ==> Always use CoMutext inside coroutines.
679 + * ==> Never acquire/release AioContext withing coroutines (because that use QemuRecMutex)
680 + *
681 + */
682 +
683 +const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
684 +
685 +static struct PVEBackupState {
686 + struct {
687 + // Everything accessed from qmp_backup_query command is protected using
688 + // this lock. Do NOT hold this lock for long times, as it is sometimes
689 + // acquired from coroutines, and thus any wait time may block the guest.
690 + QemuMutex lock;
691 + Error *error;
692 + time_t start_time;
693 + time_t end_time;
694 + char *backup_file;
695 + uuid_t uuid;
696 + char uuid_str[37];
697 + size_t total;
698 + size_t dirty;
699 + size_t transferred;
700 + size_t reused;
701 + size_t zero_bytes;
702 + GList *bitmap_list;
703 + bool finishing;
704 + bool starting;
705 + } stat;
706 + int64_t speed;
707 + BackupPerf perf;
708 + VmaWriter *vmaw;
709 + ProxmoxBackupHandle *pbs;
710 + GList *di_list;
711 + JobTxn *txn;
712 + CoMutex backup_mutex;
713 + CoMutex dump_callback_mutex;
714 +} backup_state;
715 +
716 +static void pvebackup_init(void)
717 +{
718 + qemu_mutex_init(&backup_state.stat.lock);
719 + qemu_co_mutex_init(&backup_state.backup_mutex);
720 + qemu_co_mutex_init(&backup_state.dump_callback_mutex);
721 +}
722 +
723 +// initialize PVEBackupState at startup
724 +opts_init(pvebackup_init);
725 +
726 +typedef struct PVEBackupDevInfo {
727 + BlockDriverState *bs;
728 + size_t size;
729 + uint64_t block_size;
730 + uint8_t dev_id;
731 + int completed_ret; // INT_MAX if not completed
732 + char targetfile[PATH_MAX];
733 + BdrvDirtyBitmap *bitmap;
734 + BlockDriverState *target;
735 + BlockJob *job;
736 +} PVEBackupDevInfo;
737 +
738 +static void pvebackup_propagate_error(Error *err)
739 +{
740 + qemu_mutex_lock(&backup_state.stat.lock);
741 + error_propagate(&backup_state.stat.error, err);
742 + qemu_mutex_unlock(&backup_state.stat.lock);
743 +}
744 +
745 +static bool pvebackup_error_or_canceled(void)
746 +{
747 + qemu_mutex_lock(&backup_state.stat.lock);
748 + bool error_or_canceled = !!backup_state.stat.error;
749 + qemu_mutex_unlock(&backup_state.stat.lock);
750 +
751 + return error_or_canceled;
752 +}
753 +
754 +static void pvebackup_add_transferred_bytes(size_t transferred, size_t zero_bytes, size_t reused)
755 +{
756 + qemu_mutex_lock(&backup_state.stat.lock);
757 + backup_state.stat.zero_bytes += zero_bytes;
758 + backup_state.stat.transferred += transferred;
759 + backup_state.stat.reused += reused;
760 + qemu_mutex_unlock(&backup_state.stat.lock);
761 +}
762 +
763 +// This may get called from multiple coroutines in multiple io-threads
764 +// Note1: this may get called after job_cancel()
765 +static int coroutine_fn
766 +pvebackup_co_dump_pbs_cb(
767 + void *opaque,
768 + uint64_t start,
769 + uint64_t bytes,
770 + const void *pbuf)
771 +{
772 + assert(qemu_in_coroutine());
773 +
774 + const uint64_t size = bytes;
775 + const unsigned char *buf = pbuf;
776 + PVEBackupDevInfo *di = opaque;
777 +
778 + assert(backup_state.pbs);
779 + assert(buf);
780 +
781 + Error *local_err = NULL;
782 + int pbs_res = -1;
783 +
784 + bool is_zero_block = size == di->block_size && buffer_is_zero(buf, size);
785 +
786 + qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
787 +
788 + // avoid deadlock if job is cancelled
789 + if (pvebackup_error_or_canceled()) {
790 + qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
791 + return -1;
792 + }
793 +
794 + uint64_t transferred = 0;
795 + uint64_t reused = 0;
796 + while (transferred < size) {
797 + uint64_t left = size - transferred;
798 + uint64_t to_transfer = left < di->block_size ? left : di->block_size;
799 +
800 + pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id,
801 + is_zero_block ? NULL : buf + transferred, start + transferred,
802 + to_transfer, &local_err);
803 + transferred += to_transfer;
804 +
805 + if (pbs_res < 0) {
806 + pvebackup_propagate_error(local_err);
807 + qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
808 + return pbs_res;
809 + }
810 +
811 + reused += pbs_res == 0 ? to_transfer : 0;
812 + }
813 +
814 + qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
815 + pvebackup_add_transferred_bytes(size, is_zero_block ? size : 0, reused);
816 +
817 + return size;
818 +}
819 +
820 +// This may get called from multiple coroutines in multiple io-threads
821 +static int coroutine_fn
822 +pvebackup_co_dump_vma_cb(
823 + void *opaque,
824 + uint64_t start,
825 + uint64_t bytes,
826 + const void *pbuf)
827 +{
828 + assert(qemu_in_coroutine());
829 +
830 + const uint64_t size = bytes;
831 + const unsigned char *buf = pbuf;
832 + PVEBackupDevInfo *di = opaque;
833 +
834 + int ret = -1;
835 +
836 + assert(backup_state.vmaw);
837 + assert(buf);
838 +
839 + uint64_t remaining = size;
840 +
841 + uint64_t cluster_num = start / VMA_CLUSTER_SIZE;
842 + if ((cluster_num * VMA_CLUSTER_SIZE) != start) {
843 + Error *local_err = NULL;
844 + error_setg(&local_err,
845 + "got unaligned write inside backup dump "
846 + "callback (sector %ld)", start);
847 + pvebackup_propagate_error(local_err);
848 + return -1; // not aligned to cluster size
849 + }
850 +
851 + while (remaining > 0) {
852 + qemu_co_mutex_lock(&backup_state.dump_callback_mutex);
853 + // avoid deadlock if job is cancelled
854 + if (pvebackup_error_or_canceled()) {
855 + qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
856 + return -1;
857 + }
858 +
859 + size_t zero_bytes = 0;
860 + ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, buf, &zero_bytes);
861 + qemu_co_mutex_unlock(&backup_state.dump_callback_mutex);
862 +
863 + ++cluster_num;
864 + buf += VMA_CLUSTER_SIZE;
865 + if (ret < 0) {
866 + Error *local_err = NULL;
867 + vma_writer_error_propagate(backup_state.vmaw, &local_err);
868 + pvebackup_propagate_error(local_err);
869 + return ret;
870 + } else {
871 + if (remaining >= VMA_CLUSTER_SIZE) {
872 + assert(ret == VMA_CLUSTER_SIZE);
873 + pvebackup_add_transferred_bytes(VMA_CLUSTER_SIZE, zero_bytes, 0);
874 + remaining -= VMA_CLUSTER_SIZE;
875 + } else {
876 + assert(ret == remaining);
877 + pvebackup_add_transferred_bytes(remaining, zero_bytes, 0);
878 + remaining = 0;
879 + }
880 + }
881 + }
882 +
883 + return size;
884 +}
885 +
886 +// assumes the caller holds backup_mutex
887 +static void coroutine_fn pvebackup_co_cleanup(void)
888 +{
889 + assert(qemu_in_coroutine());
890 +
891 + qemu_mutex_lock(&backup_state.stat.lock);
892 + backup_state.stat.finishing = true;
893 + qemu_mutex_unlock(&backup_state.stat.lock);
894 +
895 + if (backup_state.vmaw) {
896 + Error *local_err = NULL;
897 + vma_writer_close(backup_state.vmaw, &local_err);
898 +
899 + if (local_err != NULL) {
900 + pvebackup_propagate_error(local_err);
901 + }
902 +
903 + backup_state.vmaw = NULL;
904 + }
905 +
906 + if (backup_state.pbs) {
907 + if (!pvebackup_error_or_canceled()) {
908 + Error *local_err = NULL;
909 + proxmox_backup_co_finish(backup_state.pbs, &local_err);
910 + if (local_err != NULL) {
911 + pvebackup_propagate_error(local_err);
912 + }
913 + }
914 +
915 + proxmox_backup_disconnect(backup_state.pbs);
916 + backup_state.pbs = NULL;
917 + }
918 +
919 + g_list_free(backup_state.di_list);
920 + backup_state.di_list = NULL;
921 +
922 + qemu_mutex_lock(&backup_state.stat.lock);
923 + backup_state.stat.end_time = time(NULL);
924 + backup_state.stat.finishing = false;
925 + qemu_mutex_unlock(&backup_state.stat.lock);
926 +
927 +#if defined(CONFIG_MALLOC_TRIM)
928 + /*
929 + * Try to reclaim memory for buffers (and, in case of PBS, Rust futures), etc.
930 + * Won't happen by default if there is fragmentation.
931 + */
932 + malloc_trim(4 * 1024 * 1024);
933 +#endif
934 +}
935 +
936 +static void coroutine_fn pvebackup_co_complete_stream(void *opaque)
937 +{
938 + PVEBackupDevInfo *di = opaque;
939 + int ret = di->completed_ret;
940 +
941 + qemu_mutex_lock(&backup_state.stat.lock);
942 + bool starting = backup_state.stat.starting;
943 + qemu_mutex_unlock(&backup_state.stat.lock);
944 + if (starting) {
945 + /* in 'starting' state, no tasks have been run yet, meaning we can (and
946 + * must) skip all cleanup, as we don't know what has and hasn't been
947 + * initialized yet. */
948 + return;
949 + }
950 +
951 + qemu_co_mutex_lock(&backup_state.backup_mutex);
952 +
953 + if (ret < 0) {
954 + Error *local_err = NULL;
955 + error_setg(&local_err, "job failed with err %d - %s", ret, strerror(-ret));
956 + pvebackup_propagate_error(local_err);
957 + }
958 +
959 + di->bs = NULL;
960 +
961 + assert(di->target == NULL);
962 +
963 + bool error_or_canceled = pvebackup_error_or_canceled();
964 +
965 + if (backup_state.vmaw) {
966 + vma_writer_close_stream(backup_state.vmaw, di->dev_id);
967 + }
968 +
969 + if (backup_state.pbs && !error_or_canceled) {
970 + Error *local_err = NULL;
971 + proxmox_backup_co_close_image(backup_state.pbs, di->dev_id, &local_err);
972 + if (local_err != NULL) {
973 + pvebackup_propagate_error(local_err);
974 + }
975 + }
976 +
977 + if (di->job) {
978 + WITH_JOB_LOCK_GUARD() {
979 + job_unref_locked(&di->job->job);
980 + di->job = NULL;
981 + }
982 + }
983 +
984 + // remove self from job list
985 + backup_state.di_list = g_list_remove(backup_state.di_list, di);
986 +
987 + g_free(di);
988 +
989 + /* call cleanup if we're the last job */
990 + if (!g_list_first(backup_state.di_list)) {
991 + pvebackup_co_cleanup();
992 + }
993 +
994 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
995 +}
996 +
997 +static void pvebackup_complete_cb(void *opaque, int ret)
998 +{
999 + PVEBackupDevInfo *di = opaque;
1000 + di->completed_ret = ret;
1001 +
1002 + /*
1003 + * Schedule stream cleanup in async coroutine. close_image and finish might
1004 + * take a while, so we can't block on them here. This way it also doesn't
1005 + * matter if we're already running in a coroutine or not.
1006 + * Note: di is a pointer to an entry in the global backup_state struct, so
1007 + * it stays valid.
1008 + */
1009 + Coroutine *co = qemu_coroutine_create(pvebackup_co_complete_stream, di);
1010 + aio_co_enter(qemu_get_aio_context(), co);
1011 +}
1012 +
1013 +/*
1014 + * job_cancel(_sync) does not like to be called from coroutines, so defer to
1015 + * main loop processing via a bottom half. Assumes that caller holds
1016 + * backup_mutex.
1017 + */
1018 +static void job_cancel_bh(void *opaque) {
1019 + CoCtxData *data = (CoCtxData*)opaque;
1020 +
1021 + /*
1022 + * Be careful to pick a valid job to cancel:
1023 + * 1. job_cancel_sync() does not expect the job to be finalized already.
1024 + * 2. job_exit() might run between scheduling and running job_cancel_bh()
1025 + * and pvebackup_co_complete_stream() might not have removed the job from
1026 + * the list yet (in fact, cannot, because it waits for the backup_mutex).
1027 + * Requiring !job_is_completed() ensures that no finalized job is picked.
1028 + */
1029 + GList *bdi = g_list_first(backup_state.di_list);
1030 + while (bdi) {
1031 + if (bdi->data) {
1032 + BlockJob *bj = ((PVEBackupDevInfo *)bdi->data)->job;
1033 + if (bj) {
1034 + Job *job = &bj->job;
1035 + WITH_JOB_LOCK_GUARD() {
1036 + if (!job_is_completed_locked(job)) {
1037 + job_cancel_sync_locked(job, true);
1038 + /*
1039 + * It's enough to cancel one job in the transaction, the
1040 + * rest will follow automatically.
1041 + */
1042 + break;
1043 + }
1044 + }
1045 + }
1046 + }
1047 + bdi = g_list_next(bdi);
1048 + }
1049 +
1050 + aio_co_enter(data->ctx, data->co);
1051 +}
1052 +
1053 +void coroutine_fn qmp_backup_cancel(Error **errp)
1054 +{
1055 + Error *cancel_err = NULL;
1056 + error_setg(&cancel_err, "backup canceled");
1057 + pvebackup_propagate_error(cancel_err);
1058 +
1059 + qemu_co_mutex_lock(&backup_state.backup_mutex);
1060 +
1061 + if (backup_state.vmaw) {
1062 + /* make sure vma writer does not block anymore */
1063 + vma_writer_set_error(backup_state.vmaw, "backup canceled");
1064 + }
1065 +
1066 + if (backup_state.pbs) {
1067 + proxmox_backup_abort(backup_state.pbs, "backup canceled");
1068 + }
1069 +
1070 + CoCtxData data = {
1071 + .ctx = qemu_get_current_aio_context(),
1072 + .co = qemu_coroutine_self(),
1073 + };
1074 + aio_bh_schedule_oneshot(data.ctx, job_cancel_bh, &data);
1075 + qemu_coroutine_yield();
1076 +
1077 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
1078 +}
1079 +
1080 +// assumes the caller holds backup_mutex
1081 +static int coroutine_fn pvebackup_co_add_config(
1082 + const char *file,
1083 + const char *name,
1084 + BackupFormat format,
1085 + VmaWriter *vmaw,
1086 + ProxmoxBackupHandle *pbs,
1087 + Error **errp)
1088 +{
1089 + int res = 0;
1090 +
1091 + char *cdata = NULL;
1092 + gsize clen = 0;
1093 + GError *err = NULL;
1094 + if (!g_file_get_contents(file, &cdata, &clen, &err)) {
1095 + error_setg(errp, "unable to read file '%s'", file);
1096 + return 1;
1097 + }
1098 +
1099 + char *basename = g_path_get_basename(file);
1100 + if (name == NULL) name = basename;
1101 +
1102 + if (format == BACKUP_FORMAT_VMA) {
1103 + if (vma_writer_add_config(vmaw, name, cdata, clen) != 0) {
1104 + error_setg(errp, "unable to add %s config data to vma archive", file);
1105 + goto err;
1106 + }
1107 + } else if (format == BACKUP_FORMAT_PBS) {
1108 + if (proxmox_backup_co_add_config(pbs, name, (unsigned char *)cdata, clen, errp) < 0)
1109 + goto err;
1110 + }
1111 +
1112 + out:
1113 + g_free(basename);
1114 + g_free(cdata);
1115 + return res;
1116 +
1117 + err:
1118 + res = -1;
1119 + goto out;
1120 +}
1121 +
1122 +/*
1123 + * backup_job_create can *not* be run from a coroutine (and requires an
1124 + * acquired AioContext), so this can't either.
1125 + * The caller is responsible that backup_mutex is held nonetheless.
1126 + */
1127 +static void create_backup_jobs_bh(void *opaque) {
1128 +
1129 + assert(!qemu_in_coroutine());
1130 +
1131 + CoCtxData *data = (CoCtxData*)opaque;
1132 + Error **errp = (Error**)data->data;
1133 +
1134 + Error *local_err = NULL;
1135 +
1136 + /* create job transaction to synchronize bitmap commit and cancel all
1137 + * jobs in case one errors */
1138 + if (backup_state.txn) {
1139 + job_txn_unref(backup_state.txn);
1140 + }
1141 + backup_state.txn = job_txn_new_seq();
1142 +
1143 + /* create and start all jobs (paused state) */
1144 + GList *l = backup_state.di_list;
1145 + while (l) {
1146 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1147 + l = g_list_next(l);
1148 +
1149 + assert(di->target != NULL);
1150 +
1151 + MirrorSyncMode sync_mode = MIRROR_SYNC_MODE_FULL;
1152 + BitmapSyncMode bitmap_mode = BITMAP_SYNC_MODE_NEVER;
1153 + if (di->bitmap) {
1154 + sync_mode = MIRROR_SYNC_MODE_BITMAP;
1155 + bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
1156 + }
1157 + AioContext *aio_context = bdrv_get_aio_context(di->bs);
1158 + aio_context_acquire(aio_context);
1159 +
1160 + bdrv_drained_begin(di->bs);
1161 +
1162 + BlockJob *job = backup_job_create(
1163 + NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
1164 + bitmap_mode, false, NULL, &backup_state.perf, BLOCKDEV_ON_ERROR_REPORT,
1165 + BLOCKDEV_ON_ERROR_REPORT, JOB_DEFAULT, pvebackup_complete_cb, di, backup_state.txn,
1166 + &local_err);
1167 +
1168 + bdrv_drained_end(di->bs);
1169 +
1170 + aio_context_release(aio_context);
1171 +
1172 + di->job = job;
1173 + if (job) {
1174 + WITH_JOB_LOCK_GUARD() {
1175 + job_ref_locked(&job->job);
1176 + }
1177 + }
1178 +
1179 + if (!job || local_err) {
1180 + error_setg(errp, "backup_job_create failed: %s",
1181 + local_err ? error_get_pretty(local_err) : "null");
1182 + break;
1183 + }
1184 +
1185 + bdrv_unref(di->target);
1186 + di->target = NULL;
1187 + }
1188 +
1189 + if (*errp) {
1190 + /*
1191 + * It's enough to cancel one job in the transaction, the rest will
1192 + * follow automatically.
1193 + */
1194 + bool canceled = false;
1195 + l = backup_state.di_list;
1196 + while (l) {
1197 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1198 + l = g_list_next(l);
1199 +
1200 + if (di->target) {
1201 + bdrv_unref(di->target);
1202 + di->target = NULL;
1203 + }
1204 +
1205 + if (di->job) {
1206 + WITH_JOB_LOCK_GUARD() {
1207 + if (!canceled) {
1208 + job_cancel_sync_locked(&di->job->job, true);
1209 + canceled = true;
1210 + }
1211 + job_unref_locked(&di->job->job);
1212 + di->job = NULL;
1213 + }
1214 + }
1215 + }
1216 + }
1217 +
1218 + /* return */
1219 + aio_co_enter(data->ctx, data->co);
1220 +}
1221 +
1222 +UuidInfo coroutine_fn *qmp_backup(
1223 + const char *backup_file,
1224 + const char *password,
1225 + const char *keyfile,
1226 + const char *key_password,
1227 + const char *master_keyfile,
1228 + const char *fingerprint,
1229 + const char *backup_ns,
1230 + const char *backup_id,
1231 + bool has_backup_time, int64_t backup_time,
1232 + bool has_use_dirty_bitmap, bool use_dirty_bitmap,
1233 + bool has_compress, bool compress,
1234 + bool has_encrypt, bool encrypt,
1235 + bool has_format, BackupFormat format,
1236 + const char *config_file,
1237 + const char *firewall_file,
1238 + const char *devlist,
1239 + bool has_speed, int64_t speed,
1240 + bool has_max_workers, int64_t max_workers,
1241 + Error **errp)
1242 +{
1243 + assert(qemu_in_coroutine());
1244 +
1245 + qemu_co_mutex_lock(&backup_state.backup_mutex);
1246 +
1247 + BlockBackend *blk;
1248 + BlockDriverState *bs = NULL;
1249 + Error *local_err = NULL;
1250 + uuid_t uuid;
1251 + VmaWriter *vmaw = NULL;
1252 + ProxmoxBackupHandle *pbs = NULL;
1253 + gchar **devs = NULL;
1254 + GList *di_list = NULL;
1255 + GList *l;
1256 + UuidInfo *uuid_info;
1257 +
1258 + const char *config_name = "qemu-server.conf";
1259 + const char *firewall_name = "qemu-server.fw";
1260 +
1261 + if (backup_state.di_list) {
1262 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
1263 + "previous backup not finished");
1264 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
1265 + return NULL;
1266 + }
1267 +
1268 + /* Todo: try to auto-detect format based on file name */
1269 + format = has_format ? format : BACKUP_FORMAT_VMA;
1270 +
1271 + if (devlist) {
1272 + devs = g_strsplit_set(devlist, ",;:", -1);
1273 +
1274 + gchar **d = devs;
1275 + while (d && *d) {
1276 + blk = blk_by_name(*d);
1277 + if (blk) {
1278 + bs = blk_bs(blk);
1279 + if (!bdrv_co_is_inserted(bs)) {
1280 + error_setg(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
1281 + goto err;
1282 + }
1283 + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1284 + di->bs = bs;
1285 + di_list = g_list_append(di_list, di);
1286 + } else {
1287 + error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
1288 + "Device '%s' not found", *d);
1289 + goto err;
1290 + }
1291 + d++;
1292 + }
1293 +
1294 + } else {
1295 + BdrvNextIterator it;
1296 +
1297 + bs = NULL;
1298 + for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
1299 + if (!bdrv_co_is_inserted(bs) || bdrv_is_read_only(bs)) {
1300 + continue;
1301 + }
1302 +
1303 + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1304 + di->bs = bs;
1305 + di_list = g_list_append(di_list, di);
1306 + }
1307 + }
1308 +
1309 + if (!di_list) {
1310 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
1311 + goto err;
1312 + }
1313 +
1314 + size_t total = 0;
1315 +
1316 + l = di_list;
1317 + while (l) {
1318 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1319 + l = g_list_next(l);
1320 + if (bdrv_op_is_blocked(di->bs, BLOCK_OP_TYPE_BACKUP_SOURCE, errp)) {
1321 + goto err;
1322 + }
1323 +
1324 + ssize_t size = bdrv_getlength(di->bs);
1325 + if (size < 0) {
1326 + error_setg_errno(errp, -size, "bdrv_getlength failed");
1327 + goto err;
1328 + }
1329 + di->size = size;
1330 + total += size;
1331 +
1332 + di->completed_ret = INT_MAX;
1333 + }
1334 +
1335 + uuid_generate(uuid);
1336 +
1337 + qemu_mutex_lock(&backup_state.stat.lock);
1338 + backup_state.stat.reused = 0;
1339 +
1340 + /* clear previous backup's bitmap_list */
1341 + if (backup_state.stat.bitmap_list) {
1342 + GList *bl = backup_state.stat.bitmap_list;
1343 + while (bl) {
1344 + g_free(((PBSBitmapInfo *)bl->data)->drive);
1345 + g_free(bl->data);
1346 + bl = g_list_next(bl);
1347 + }
1348 + g_list_free(backup_state.stat.bitmap_list);
1349 + backup_state.stat.bitmap_list = NULL;
1350 + }
1351 +
1352 + if (format == BACKUP_FORMAT_PBS) {
1353 + if (!password) {
1354 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
1355 + goto err_mutex;
1356 + }
1357 + if (!backup_id) {
1358 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
1359 + goto err_mutex;
1360 + }
1361 + if (!has_backup_time) {
1362 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
1363 + goto err_mutex;
1364 + }
1365 +
1366 + int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
1367 + firewall_name = "fw.conf";
1368 +
1369 + char *pbs_err = NULL;
1370 + pbs = proxmox_backup_new_ns(
1371 + backup_file,
1372 + backup_ns,
1373 + backup_id,
1374 + backup_time,
1375 + dump_cb_block_size,
1376 + password,
1377 + keyfile,
1378 + key_password,
1379 + master_keyfile,
1380 + has_compress ? compress : true,
1381 + has_encrypt ? encrypt : !!keyfile,
1382 + fingerprint,
1383 + &pbs_err);
1384 +
1385 + if (!pbs) {
1386 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
1387 + "proxmox_backup_new failed: %s", pbs_err);
1388 + proxmox_backup_free_error(pbs_err);
1389 + goto err_mutex;
1390 + }
1391 +
1392 + int connect_result = proxmox_backup_co_connect(pbs, errp);
1393 + if (connect_result < 0)
1394 + goto err_mutex;
1395 +
1396 + /* register all devices */
1397 + l = di_list;
1398 + while (l) {
1399 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1400 + l = g_list_next(l);
1401 +
1402 + di->block_size = dump_cb_block_size;
1403 +
1404 + const char *devname = bdrv_get_device_name(di->bs);
1405 + PBSBitmapAction action = PBS_BITMAP_ACTION_NOT_USED;
1406 + size_t dirty = di->size;
1407 +
1408 + BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
1409 + bool expect_only_dirty = false;
1410 +
1411 + if (has_use_dirty_bitmap && use_dirty_bitmap) {
1412 + if (bitmap == NULL) {
1413 + bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, errp);
1414 + if (!bitmap) {
1415 + goto err_mutex;
1416 + }
1417 + action = PBS_BITMAP_ACTION_NEW;
1418 + } else {
1419 + expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
1420 + }
1421 +
1422 + if (expect_only_dirty) {
1423 + /* track clean chunks as reused */
1424 + dirty = MIN(bdrv_get_dirty_count(bitmap), di->size);
1425 + backup_state.stat.reused += di->size - dirty;
1426 + action = PBS_BITMAP_ACTION_USED;
1427 + } else {
1428 + /* mark entire bitmap as dirty to make full backup */
1429 + bdrv_set_dirty_bitmap(bitmap, 0, di->size);
1430 + if (action != PBS_BITMAP_ACTION_NEW) {
1431 + action = PBS_BITMAP_ACTION_INVALID;
1432 + }
1433 + }
1434 + di->bitmap = bitmap;
1435 + } else {
1436 + /* after a full backup the old dirty bitmap is invalid anyway */
1437 + if (bitmap != NULL) {
1438 + bdrv_release_dirty_bitmap(bitmap);
1439 + action = PBS_BITMAP_ACTION_NOT_USED_REMOVED;
1440 + }
1441 + }
1442 +
1443 + int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, errp);
1444 + if (dev_id < 0) {
1445 + goto err_mutex;
1446 + }
1447 +
1448 + if (!(di->target = bdrv_co_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) {
1449 + goto err_mutex;
1450 + }
1451 +
1452 + di->dev_id = dev_id;
1453 +
1454 + PBSBitmapInfo *info = g_malloc(sizeof(*info));
1455 + info->drive = g_strdup(devname);
1456 + info->action = action;
1457 + info->size = di->size;
1458 + info->dirty = dirty;
1459 + backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
1460 + }
1461 + } else if (format == BACKUP_FORMAT_VMA) {
1462 + vmaw = vma_writer_create(backup_file, uuid, &local_err);
1463 + if (!vmaw) {
1464 + if (local_err) {
1465 + error_propagate(errp, local_err);
1466 + }
1467 + goto err_mutex;
1468 + }
1469 +
1470 + /* register all devices for vma writer */
1471 + l = di_list;
1472 + while (l) {
1473 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1474 + l = g_list_next(l);
1475 +
1476 + if (!(di->target = bdrv_co_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) {
1477 + goto err_mutex;
1478 + }
1479 +
1480 + const char *devname = bdrv_get_device_name(di->bs);
1481 + di->dev_id = vma_writer_register_stream(vmaw, devname, di->size);
1482 + if (di->dev_id <= 0) {
1483 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
1484 + "register_stream failed");
1485 + goto err_mutex;
1486 + }
1487 + }
1488 + } else {
1489 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
1490 + goto err_mutex;
1491 + }
1492 +
1493 + /* add configuration file to archive */
1494 + if (config_file) {
1495 + if (pvebackup_co_add_config(config_file, config_name, format, vmaw, pbs, errp) != 0) {
1496 + goto err_mutex;
1497 + }
1498 + }
1499 +
1500 + /* add firewall file to archive */
1501 + if (firewall_file) {
1502 + if (pvebackup_co_add_config(firewall_file, firewall_name, format, vmaw, pbs, errp) != 0) {
1503 + goto err_mutex;
1504 + }
1505 + }
1506 + /* initialize global backup_state now */
1507 + /* note: 'reused' and 'bitmap_list' are initialized earlier */
1508 +
1509 + if (backup_state.stat.error) {
1510 + error_free(backup_state.stat.error);
1511 + backup_state.stat.error = NULL;
1512 + }
1513 +
1514 + backup_state.stat.start_time = time(NULL);
1515 + backup_state.stat.end_time = 0;
1516 +
1517 + if (backup_state.stat.backup_file) {
1518 + g_free(backup_state.stat.backup_file);
1519 + }
1520 + backup_state.stat.backup_file = g_strdup(backup_file);
1521 +
1522 + uuid_copy(backup_state.stat.uuid, uuid);
1523 + uuid_unparse_lower(uuid, backup_state.stat.uuid_str);
1524 + char *uuid_str = g_strdup(backup_state.stat.uuid_str);
1525 +
1526 + backup_state.stat.total = total;
1527 + backup_state.stat.dirty = total - backup_state.stat.reused;
1528 + backup_state.stat.transferred = 0;
1529 + backup_state.stat.zero_bytes = 0;
1530 + backup_state.stat.finishing = false;
1531 + backup_state.stat.starting = true;
1532 +
1533 + qemu_mutex_unlock(&backup_state.stat.lock);
1534 +
1535 + backup_state.speed = (has_speed && speed > 0) ? speed : 0;
1536 +
1537 + backup_state.perf = (BackupPerf){ .max_workers = 16 };
1538 + if (has_max_workers) {
1539 + backup_state.perf.max_workers = max_workers;
1540 + }
1541 +
1542 + backup_state.vmaw = vmaw;
1543 + backup_state.pbs = pbs;
1544 +
1545 + backup_state.di_list = di_list;
1546 +
1547 + uuid_info = g_malloc0(sizeof(*uuid_info));
1548 + uuid_info->UUID = uuid_str;
1549 +
1550 + /* Run create_backup_jobs_bh outside of coroutine (in BH) but keep
1551 + * backup_mutex locked. This is fine, a CoMutex can be held across yield
1552 + * points, and we'll release it as soon as the BH reschedules us.
1553 + */
1554 + CoCtxData waker = {
1555 + .co = qemu_coroutine_self(),
1556 + .ctx = qemu_get_current_aio_context(),
1557 + .data = &local_err,
1558 + };
1559 + aio_bh_schedule_oneshot(waker.ctx, create_backup_jobs_bh, &waker);
1560 + qemu_coroutine_yield();
1561 +
1562 + if (local_err) {
1563 + error_propagate(errp, local_err);
1564 + goto err;
1565 + }
1566 +
1567 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
1568 +
1569 + qemu_mutex_lock(&backup_state.stat.lock);
1570 + backup_state.stat.starting = false;
1571 + qemu_mutex_unlock(&backup_state.stat.lock);
1572 +
1573 + /* start the first job in the transaction */
1574 + job_txn_start_seq(backup_state.txn);
1575 +
1576 + return uuid_info;
1577 +
1578 +err_mutex:
1579 + qemu_mutex_unlock(&backup_state.stat.lock);
1580 +
1581 +err:
1582 +
1583 + l = di_list;
1584 + while (l) {
1585 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1586 + l = g_list_next(l);
1587 +
1588 + if (di->target) {
1589 + bdrv_co_unref(di->target);
1590 + }
1591 +
1592 + if (di->targetfile[0]) {
1593 + unlink(di->targetfile);
1594 + }
1595 + g_free(di);
1596 + }
1597 + g_list_free(di_list);
1598 + backup_state.di_list = NULL;
1599 +
1600 + if (devs) {
1601 + g_strfreev(devs);
1602 + }
1603 +
1604 + if (vmaw) {
1605 + Error *err = NULL;
1606 + vma_writer_close(vmaw, &err);
1607 + unlink(backup_file);
1608 + }
1609 +
1610 + if (pbs) {
1611 + proxmox_backup_disconnect(pbs);
1612 + backup_state.pbs = NULL;
1613 + }
1614 +
1615 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
1616 + return NULL;
1617 +}
1618 +
1619 +BackupStatus *qmp_query_backup(Error **errp)
1620 +{
1621 + BackupStatus *info = g_malloc0(sizeof(*info));
1622 +
1623 + qemu_mutex_lock(&backup_state.stat.lock);
1624 +
1625 + if (!backup_state.stat.start_time) {
1626 + /* not started, return {} */
1627 + qemu_mutex_unlock(&backup_state.stat.lock);
1628 + return info;
1629 + }
1630 +
1631 + info->has_start_time = true;
1632 + info->start_time = backup_state.stat.start_time;
1633 +
1634 + if (backup_state.stat.backup_file) {
1635 + info->backup_file = g_strdup(backup_state.stat.backup_file);
1636 + }
1637 +
1638 + info->uuid = g_strdup(backup_state.stat.uuid_str);
1639 +
1640 + if (backup_state.stat.end_time) {
1641 + if (backup_state.stat.error) {
1642 + info->status = g_strdup("error");
1643 + info->errmsg = g_strdup(error_get_pretty(backup_state.stat.error));
1644 + } else {
1645 + info->status = g_strdup("done");
1646 + }
1647 + info->has_end_time = true;
1648 + info->end_time = backup_state.stat.end_time;
1649 + } else {
1650 + info->status = g_strdup("active");
1651 + }
1652 +
1653 + info->has_total = true;
1654 + info->total = backup_state.stat.total;
1655 + info->has_dirty = true;
1656 + info->dirty = backup_state.stat.dirty;
1657 + info->has_zero_bytes = true;
1658 + info->zero_bytes = backup_state.stat.zero_bytes;
1659 + info->has_transferred = true;
1660 + info->transferred = backup_state.stat.transferred;
1661 + info->has_reused = true;
1662 + info->reused = backup_state.stat.reused;
1663 + info->finishing = backup_state.stat.finishing;
1664 +
1665 + qemu_mutex_unlock(&backup_state.stat.lock);
1666 +
1667 + return info;
1668 +}
1669 +
1670 +PBSBitmapInfoList *qmp_query_pbs_bitmap_info(Error **errp)
1671 +{
1672 + PBSBitmapInfoList *head = NULL, **p_next = &head;
1673 +
1674 + qemu_mutex_lock(&backup_state.stat.lock);
1675 +
1676 + GList *l = backup_state.stat.bitmap_list;
1677 + while (l) {
1678 + PBSBitmapInfo *info = (PBSBitmapInfo *)l->data;
1679 + l = g_list_next(l);
1680 +
1681 + /* clone bitmap info to avoid auto free after QMP marshalling */
1682 + PBSBitmapInfo *info_ret = g_malloc0(sizeof(*info_ret));
1683 + info_ret->drive = g_strdup(info->drive);
1684 + info_ret->action = info->action;
1685 + info_ret->size = info->size;
1686 + info_ret->dirty = info->dirty;
1687 +
1688 + PBSBitmapInfoList *info_list = g_malloc0(sizeof(*info_list));
1689 + info_list->value = info_ret;
1690 +
1691 + *p_next = info_list;
1692 + p_next = &info_list->next;
1693 + }
1694 +
1695 + qemu_mutex_unlock(&backup_state.stat.lock);
1696 +
1697 + return head;
1698 +}
1699 +
1700 +ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
1701 +{
1702 + ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
1703 + ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
1704 + ret->pbs_dirty_bitmap = true;
1705 + ret->pbs_dirty_bitmap_savevm = true;
1706 + ret->query_bitmap_info = true;
1707 + ret->pbs_masterkey = true;
1708 + ret->backup_max_workers = true;
1709 + return ret;
1710 +}
1711 diff --git a/qapi/block-core.json b/qapi/block-core.json
1712 index 125aa89858..331c8336d1 100644
1713 --- a/qapi/block-core.json
1714 +++ b/qapi/block-core.json
1715 @@ -839,6 +839,235 @@
1716 { 'command': 'query-block', 'returns': ['BlockInfo'],
1717 'allow-preconfig': true }
1718
1719 +##
1720 +# @BackupStatus:
1721 +#
1722 +# Detailed backup status.
1723 +#
1724 +# @status: string describing the current backup status.
1725 +# This can be 'active', 'done', 'error'. If this field is not
1726 +# returned, no backup process has been initiated
1727 +#
1728 +# @errmsg: error message (only returned if status is 'error')
1729 +#
1730 +# @total: total amount of bytes involved in the backup process
1731 +#
1732 +# @dirty: with incremental mode (PBS) this is the amount of bytes involved
1733 +# in the backup process which are marked dirty.
1734 +#
1735 +# @transferred: amount of bytes already backed up.
1736 +#
1737 +# @reused: amount of bytes reused due to deduplication.
1738 +#
1739 +# @zero-bytes: amount of 'zero' bytes detected.
1740 +#
1741 +# @start-time: time (epoch) when backup job started.
1742 +#
1743 +# @end-time: time (epoch) when backup job finished.
1744 +#
1745 +# @backup-file: backup file name
1746 +#
1747 +# @uuid: uuid for this backup job
1748 +#
1749 +# @finishing: if status='active' and finishing=true, then the backup process is
1750 +# waiting for the target to finish.
1751 +#
1752 +##
1753 +{ 'struct': 'BackupStatus',
1754 + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
1755 + '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
1756 + '*start-time': 'int', '*end-time': 'int',
1757 + '*backup-file': 'str', '*uuid': 'str', 'finishing': 'bool' } }
1758 +
1759 +##
1760 +# @BackupFormat:
1761 +#
1762 +# An enumeration of supported backup formats.
1763 +#
1764 +# @vma: Proxmox vma backup format
1765 +#
1766 +# @pbs: Proxmox backup server format
1767 +#
1768 +##
1769 +{ 'enum': 'BackupFormat',
1770 + 'data': [ 'vma', 'pbs' ] }
1771 +
1772 +##
1773 +# @backup:
1774 +#
1775 +# Starts a VM backup.
1776 +#
1777 +# @backup-file: the backup file name
1778 +#
1779 +# @format: format of the backup file
1780 +#
1781 +# @config-file: a configuration file to include into
1782 +# the backup archive.
1783 +#
1784 +# @speed: the maximum speed, in bytes per second
1785 +#
1786 +# @devlist: list of block device names (separated by ',', ';'
1787 +# or ':'). By default the backup includes all writable block devices.
1788 +#
1789 +# @password: backup server passsword (required for format 'pbs')
1790 +#
1791 +# @keyfile: keyfile used for encryption (optional for format 'pbs')
1792 +#
1793 +# @key-password: password for keyfile (optional for format 'pbs')
1794 +#
1795 +# @master-keyfile: PEM-formatted master public keyfile (optional for format 'pbs')
1796 +#
1797 +# @fingerprint: server cert fingerprint (optional for format 'pbs')
1798 +#
1799 +# @backup-ns: backup namespace (required for format 'pbs')
1800 +#
1801 +# @backup-id: backup ID (required for format 'pbs')
1802 +#
1803 +# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
1804 +#
1805 +# @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
1806 +#
1807 +# @compress: use compression (optional for format 'pbs', defaults to true)
1808 +#
1809 +# @encrypt: use encryption ((optional for format 'pbs', defaults to true if there is a keyfile)
1810 +#
1811 +# @max-workers: see @BackupPerf for details. Default 16.
1812 +#
1813 +# Returns: the uuid of the backup job
1814 +#
1815 +##
1816 +{ 'command': 'backup', 'data': { 'backup-file': 'str',
1817 + '*password': 'str',
1818 + '*keyfile': 'str',
1819 + '*key-password': 'str',
1820 + '*master-keyfile': 'str',
1821 + '*fingerprint': 'str',
1822 + '*backup-ns': 'str',
1823 + '*backup-id': 'str',
1824 + '*backup-time': 'int',
1825 + '*use-dirty-bitmap': 'bool',
1826 + '*compress': 'bool',
1827 + '*encrypt': 'bool',
1828 + '*format': 'BackupFormat',
1829 + '*config-file': 'str',
1830 + '*firewall-file': 'str',
1831 + '*devlist': 'str',
1832 + '*speed': 'int',
1833 + '*max-workers': 'int' },
1834 + 'returns': 'UuidInfo', 'coroutine': true }
1835 +
1836 +##
1837 +# @query-backup:
1838 +#
1839 +# Returns information about current/last backup task.
1840 +#
1841 +# Returns: @BackupStatus
1842 +#
1843 +##
1844 +{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1845 +
1846 +##
1847 +# @backup-cancel:
1848 +#
1849 +# Cancel the current executing backup process.
1850 +#
1851 +# Returns: nothing on success
1852 +#
1853 +# Notes: This command succeeds even if there is no backup process running.
1854 +#
1855 +##
1856 +{ 'command': 'backup-cancel', 'coroutine': true }
1857 +
1858 +##
1859 +# @ProxmoxSupportStatus:
1860 +#
1861 +# Contains info about supported features added by Proxmox.
1862 +#
1863 +# @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
1864 +# supported.
1865 +#
1866 +# @query-bitmap-info: True if the 'query-pbs-bitmap-info' QMP call is supported.
1867 +#
1868 +# @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
1869 +# safely be set for savevm-async.
1870 +#
1871 +# @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile'
1872 +# parameter.
1873 +#
1874 +# @pbs-library-version: Running version of libproxmox-backup-qemu0 library.
1875 +#
1876 +##
1877 +{ 'struct': 'ProxmoxSupportStatus',
1878 + 'data': { 'pbs-dirty-bitmap': 'bool',
1879 + 'query-bitmap-info': 'bool',
1880 + 'pbs-dirty-bitmap-savevm': 'bool',
1881 + 'pbs-masterkey': 'bool',
1882 + 'pbs-library-version': 'str',
1883 + 'backup-max-workers': 'bool' } }
1884 +
1885 +##
1886 +# @query-proxmox-support:
1887 +#
1888 +# Returns information about supported features added by Proxmox.
1889 +#
1890 +# Returns: @ProxmoxSupportStatus
1891 +#
1892 +##
1893 +{ 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
1894 +
1895 +##
1896 +# @PBSBitmapAction:
1897 +#
1898 +# An action taken on a dirty-bitmap when a backup job was started.
1899 +#
1900 +# @not-used: Bitmap mode was not enabled.
1901 +#
1902 +# @not-used-removed: Bitmap mode was not enabled, but a bitmap from a
1903 +# previous backup still existed and was removed.
1904 +#
1905 +# @new: A new bitmap was attached to the drive for this backup.
1906 +#
1907 +# @used: An existing bitmap will be used to only backup changed data.
1908 +#
1909 +# @invalid: A bitmap existed, but had to be cleared since it's associated
1910 +# base snapshot did not match the base given for the current job or
1911 +# the crypt mode has changed.
1912 +#
1913 +##
1914 +{ 'enum': 'PBSBitmapAction',
1915 + 'data': ['not-used', 'not-used-removed', 'new', 'used', 'invalid'] }
1916 +
1917 +##
1918 +# @PBSBitmapInfo:
1919 +#
1920 +# Contains information about dirty bitmaps used for each drive in a PBS backup.
1921 +#
1922 +# @drive: The underlying drive.
1923 +#
1924 +# @action: The action that was taken when the backup started.
1925 +#
1926 +# @size: The total size of the drive.
1927 +#
1928 +# @dirty: How much of the drive is considered dirty and will be backed up,
1929 +# or 'size' if everything will be.
1930 +#
1931 +##
1932 +{ 'struct': 'PBSBitmapInfo',
1933 + 'data': { 'drive': 'str', 'action': 'PBSBitmapAction', 'size': 'int',
1934 + 'dirty': 'int' } }
1935 +
1936 +##
1937 +# @query-pbs-bitmap-info:
1938 +#
1939 +# Returns information about dirty bitmaps used on the most recently started
1940 +# backup. Returns nothing when the last backup was not using PBS or if no
1941 +# backup occured in this session.
1942 +#
1943 +# Returns: @PBSBitmapInfo
1944 +#
1945 +##
1946 +{ 'command': 'query-pbs-bitmap-info', 'returns': ['PBSBitmapInfo'] }
1947 +
1948 ##
1949 # @BlockDeviceTimedStats:
1950 #
1951 diff --git a/qapi/common.json b/qapi/common.json
1952 index 6fed9cde1a..630a2a8f9a 100644
1953 --- a/qapi/common.json
1954 +++ b/qapi/common.json
1955 @@ -207,3 +207,17 @@
1956 ##
1957 { 'struct': 'HumanReadableText',
1958 'data': { 'human-readable-text': 'str' } }
1959 +
1960 +##
1961 +# @UuidInfo:
1962 +#
1963 +# Guest UUID information (Universally Unique Identifier).
1964 +#
1965 +# @UUID: the UUID of the guest
1966 +#
1967 +# Since: 0.14.0
1968 +#
1969 +# Notes: If no UUID was specified for the guest, a null UUID is
1970 +# returned.
1971 +##
1972 +{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
1973 diff --git a/qapi/machine.json b/qapi/machine.json
1974 index 7da3c519ba..888457f810 100644
1975 --- a/qapi/machine.json
1976 +++ b/qapi/machine.json
1977 @@ -4,6 +4,8 @@
1978 # This work is licensed under the terms of the GNU GPL, version 2 or later.
1979 # See the COPYING file in the top-level directory.
1980
1981 +{ 'include': 'common.json' }
1982 +
1983 ##
1984 # = Machines
1985 ##
1986 @@ -230,20 +232,6 @@
1987 ##
1988 { 'command': 'query-target', 'returns': 'TargetInfo' }
1989
1990 -##
1991 -# @UuidInfo:
1992 -#
1993 -# Guest UUID information (Universally Unique Identifier).
1994 -#
1995 -# @UUID: the UUID of the guest
1996 -#
1997 -# Since: 0.14
1998 -#
1999 -# Notes: If no UUID was specified for the guest, a null UUID is
2000 -# returned.
2001 -##
2002 -{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }
2003 -
2004 ##
2005 # @query-uuid:
2006 #