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
7 block/meson.build | 5 +
8 block/monitor/block-hmp-cmds.c | 33 ++
10 hmp-commands-info.hx | 13 +
11 hmp-commands.hx | 29 +
12 include/block/block_int.h | 2 +-
13 include/monitor/hmp.h | 3 +
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
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(
34 block_ss.add(files('../vma-writer.c'), libuuid)
36 + '../proxmox-backup-client.c',
38 +), libproxmox_backup_qemu)
41 softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c'))
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)
49 g_free(global_snapshots);
52 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
54 + Error *error = NULL;
56 + qmp_backup_cancel(&error);
58 + hmp_handle_error(mon, error);
61 +void hmp_backup(Monitor *mon, const QDict *qdict)
63 + Error *error = NULL;
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);
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);
82 + hmp_handle_error(mon, error);
84 diff --git a/blockdev.c b/blockdev.c
85 index fe6fb5dc1d..bae80b9177 100644
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
108 + .help = "show backup status",
109 + .cmd = hmp_info_backup,
114 + Show backup status.
117 #if defined(CONFIG_SLIRP)
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
127 Copy data from a backing file into a block device.
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.",
142 + Create a VM backup.
146 + .name = "backup_cancel",
149 + .help = "cancel the current VM backup",
150 + .cmd = hmp_backup_cancel,
155 + Cancel the current VM backup.
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
166 typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf);
168 -BlockDriverState *bdrv_backuo_dump_create(
169 +BlockDriverState *bdrv_backup_dump_create(
170 int dump_cb_block_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
198 @@ -726,6 +726,7 @@ keyutils = dependency('libkeyutils', required: false,
199 has_gettid = cc.has_function('gettid')
201 libuuid = cc.find_library('uuid', required: true)
202 +libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true)
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);
214 +void hmp_info_backup(Monitor *mon, const QDict *qdict)
216 + BackupStatus *info;
218 + info = qmp_query_backup(NULL);
221 + monitor_printf(mon, "Backup status: not initialized\n");
225 + if (info->has_status) {
226 + if (info->has_errmsg) {
227 + monitor_printf(mon, "Backup status: %s - %s\n",
228 + info->status, info->errmsg);
230 + monitor_printf(mon, "Backup status: %s\n", info->status);
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));
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);
255 + qapi_free_BackupStatus(info);
258 static char *SocketAddress_to_str(SocketAddress *addr)
260 switch (addr->type) {
261 diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
263 index 0000000000..a8f6653a81
265 +++ b/proxmox-backup-client.c
267 +#include "proxmox-backup-client.h"
268 +#include "qemu/main-loop.h"
269 +#include "block/aio-wait.h"
270 +#include "qapi/error.h"
272 +/* Proxmox Backup Server client bindings using coroutines */
274 +typedef struct BlockOnCoroutineWrapper {
276 + CoroutineEntry *entry;
279 +} BlockOnCoroutineWrapper;
281 +static void coroutine_fn block_on_coroutine_wrapper(void *opaque)
283 + BlockOnCoroutineWrapper *wrapper = opaque;
284 + wrapper->entry(wrapper->entry_arg);
285 + wrapper->finished = true;
289 +void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg)
291 + assert(!qemu_in_coroutine());
293 + AioContext *ctx = qemu_get_current_aio_context();
294 + BlockOnCoroutineWrapper wrapper = {
297 + .entry_arg = entry_arg,
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);
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);
312 +proxmox_backup_co_connect(ProxmoxBackupHandle *pbs, Error **errp)
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;
320 + proxmox_backup_connect_async(pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
321 + qemu_coroutine_yield();
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);
330 +proxmox_backup_co_add_config(
331 + ProxmoxBackupHandle *pbs,
333 + const uint8_t *data,
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;
343 + proxmox_backup_add_config_async(
344 + pbs, name, data, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
345 + qemu_coroutine_yield();
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);
354 +proxmox_backup_co_register_image(
355 + ProxmoxBackupHandle *pbs,
356 + const char *device_name,
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;
366 + proxmox_backup_register_image_async(
367 + pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
368 + qemu_coroutine_yield();
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);
377 +proxmox_backup_co_finish(
378 + ProxmoxBackupHandle *pbs,
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;
387 + proxmox_backup_finish_async(
388 + pbs, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
389 + qemu_coroutine_yield();
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);
398 +proxmox_backup_co_close_image(
399 + ProxmoxBackupHandle *pbs,
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;
409 + proxmox_backup_close_image_async(
410 + pbs, dev_id, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
411 + qemu_coroutine_yield();
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);
420 +proxmox_backup_co_write_data(
421 + ProxmoxBackupHandle *pbs,
423 + const uint8_t *data,
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;
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();
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);
443 diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
445 index 0000000000..1dda8b7d8f
447 +++ b/proxmox-backup-client.h
449 +#ifndef PROXMOX_BACKUP_CLIENT_H
450 +#define PROXMOX_BACKUP_CLIENT_H
452 +#include "qemu/osdep.h"
453 +#include "qemu/coroutine.h"
454 +#include "proxmox-backup-qemu.h"
456 +typedef struct CoCtxData {
462 +// FIXME: Remove once coroutines are supported for QMP
463 +void block_on_coroutine_fn(CoroutineEntry *entry, void *entry_arg);
466 +proxmox_backup_co_connect(
467 + ProxmoxBackupHandle *pbs,
471 +proxmox_backup_co_add_config(
472 + ProxmoxBackupHandle *pbs,
474 + const uint8_t *data,
479 +proxmox_backup_co_register_image(
480 + ProxmoxBackupHandle *pbs,
481 + const char *device_name,
487 +proxmox_backup_co_finish(
488 + ProxmoxBackupHandle *pbs,
492 +proxmox_backup_co_close_image(
493 + ProxmoxBackupHandle *pbs,
498 +proxmox_backup_co_write_data(
499 + ProxmoxBackupHandle *pbs,
501 + const uint8_t *data,
507 +#endif /* PROXMOX_BACKUP_CLIENT_H */
508 diff --git a/pve-backup.c b/pve-backup.c
510 index 0000000000..55441eb9d1
514 +#include "proxmox-backup-client.h"
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"
525 +/* PVE backup state and related function */
528 +static struct PVEBackupState {
530 + // Everithing accessed from qmp command, protected using rwlock
539 + size_t transferred;
545 + ProxmoxBackupHandle *pbs;
547 + CoMutex backup_mutex;
550 +static void pvebackup_init(void)
552 + qemu_co_rwlock_init(&backup_state.stat.rwlock);
553 + qemu_co_mutex_init(&backup_state.backup_mutex);
556 +// initialize PVEBackupState at startup
557 +opts_init(pvebackup_init);
559 +typedef struct PVEBackupDevInfo {
560 + BlockDriverState *bs;
564 + char targetfile[PATH_MAX];
565 + BlockDriverState *target;
568 +static void pvebackup_co_run_next_job(void);
570 +static int coroutine_fn
571 +pvebackup_co_dump_cb(
577 + assert(qemu_in_coroutine());
579 + const uint64_t size = bytes;
580 + const unsigned char *buf = pbuf;
581 + PVEBackupDevInfo *di = opaque;
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);
588 + return size; // return success
591 + qemu_co_mutex_lock(&backup_state.backup_mutex);
595 + if (backup_state.vmaw) {
596 + size_t zero_bytes = 0;
597 + uint64_t remaining = size;
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);
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
613 + while (remaining > 0) {
614 + ret = vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num,
618 + buf += VMA_CLUSTER_SIZE;
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);
626 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
628 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
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;
637 + backup_state.stat.transferred += remaining;
640 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
643 + } else if (backup_state.pbs) {
644 + Error *local_err = NULL;
647 + pbs_res = proxmox_backup_co_write_data(backup_state.pbs, di->dev_id, buf, start, size, &local_err);
649 + qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
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);
658 + backup_state.stat.zero_bytes += size;
660 + backup_state.stat.transferred += size;
663 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
666 + qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
668 + backup_state.stat.zero_bytes += size;
670 + backup_state.stat.transferred += size;
671 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
674 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
679 +static void coroutine_fn pvebackup_co_cleanup(void)
681 + assert(qemu_in_coroutine());
683 + qemu_co_mutex_lock(&backup_state.backup_mutex);
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);
689 + if (backup_state.vmaw) {
690 + Error *local_err = NULL;
691 + vma_writer_close(backup_state.vmaw, &local_err);
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);
699 + backup_state.vmaw = NULL;
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);
713 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
715 + proxmox_backup_disconnect(backup_state.pbs);
716 + backup_state.pbs = NULL;
719 + g_list_free(backup_state.di_list);
720 + backup_state.di_list = NULL;
721 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
724 +typedef struct PVEBackupCompeteCallbackData {
725 + PVEBackupDevInfo *di;
727 +} PVEBackupCompeteCallbackData;
729 +static void coroutine_fn pvebackup_co_complete_cb(void *opaque)
731 + assert(qemu_in_coroutine());
733 + PVEBackupCompeteCallbackData *cb_data = opaque;
735 + qemu_co_mutex_lock(&backup_state.backup_mutex);
737 + PVEBackupDevInfo *di = cb_data->di;
738 + int ret = cb_data->result;
740 + di->completed = true;
742 + qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
743 + bool error_or_canceled = backup_state.stat.error || backup_state.stat.cancel;
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));
750 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
755 + bdrv_unref(di->target);
759 + if (backup_state.vmaw) {
760 + vma_writer_close_stream(backup_state.vmaw, di->dev_id);
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);
773 + // remove self from job queue
774 + backup_state.di_list = g_list_remove(backup_state.di_list, di);
777 + int pending_jobs = g_list_length(backup_state.di_list);
779 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
781 + if (pending_jobs > 0) {
782 + pvebackup_co_run_next_job();
784 + pvebackup_co_cleanup();
788 +static void pvebackup_complete_cb(void *opaque, int ret)
790 + // This can be called from the main loop, or from a coroutine
791 + PVEBackupCompeteCallbackData cb_data = {
796 + if (qemu_in_coroutine()) {
797 + pvebackup_co_complete_cb(&cb_data);
799 + block_on_coroutine_fn(pvebackup_co_complete_cb, &cb_data);
803 +static void coroutine_fn pvebackup_co_cancel(void *opaque)
805 + assert(qemu_in_coroutine());
807 + qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
808 + backup_state.stat.cancel = true;
809 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
811 + qemu_co_mutex_lock(&backup_state.backup_mutex);
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);
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");
824 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
826 + if (backup_state.vmaw) {
827 + /* make sure vma writer does not block anymore */
828 + vma_writer_set_error(backup_state.vmaw, "backup cancelled");
831 + if (backup_state.pbs) {
832 + proxmox_backup_abort(backup_state.pbs, "backup cancelled");
835 + bool running_jobs = 0;
836 + GList *l = backup_state.di_list;
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) {
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);
851 + if (!di->completed) {
853 + job_cancel(&job->job, false);
855 + aio_context_release(aio_context);
861 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
863 + if (running_jobs == 0) pvebackup_co_cleanup(); // else job will call completion handler
866 +void qmp_backup_cancel(Error **errp)
868 + block_on_coroutine_fn(pvebackup_co_cancel, NULL);
871 +static int coroutine_fn pvebackup_co_add_config(
874 + BackupFormat format,
875 + const char *backup_dir,
877 + ProxmoxBackupHandle *pbs,
882 + char *cdata = NULL;
884 + GError *err = NULL;
885 + if (!g_file_get_contents(file, &cdata, &clen, &err)) {
886 + error_setg(errp, "unable to read file '%s'", file);
890 + char *basename = g_path_get_basename(file);
891 + if (name == NULL) name = basename;
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);
898 + } else if (format == BACKUP_FORMAT_PBS) {
899 + if (proxmox_backup_co_add_config(pbs, name, (unsigned char *)cdata, clen, errp) < 0)
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);
920 +bool job_should_pause(Job *job);
922 +static void coroutine_fn pvebackup_co_run_next_job(void)
924 + assert(qemu_in_coroutine());
926 + qemu_co_mutex_lock(&backup_state.backup_mutex);
928 + GList *l = backup_state.di_list;
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) {
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);
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);
949 + if (error_or_canceled) {
950 + job_cancel(&job->job, false);
952 + job_resume(&job->job);
955 + aio_context_release(aio_context);
961 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
964 +typedef struct QmpBackupTask {
965 + const char *backup_file;
967 + const char *password;
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;
979 + BackupFormat format;
980 + bool has_config_file;
981 + const char *config_file;
982 + bool has_firewall_file;
983 + const char *firewall_file;
985 + const char *devlist;
992 +static void coroutine_fn pvebackup_co_start(void *opaque)
994 + assert(qemu_in_coroutine());
996 + QmpBackupTask *task = opaque;
998 + task->result = NULL; // just to be sure
1000 + BlockBackend *blk;
1001 + BlockDriverState *bs = NULL;
1002 + const char *backup_dir = NULL;
1003 + Error *local_err = NULL;
1005 + VmaWriter *vmaw = NULL;
1006 + ProxmoxBackupHandle *pbs = NULL;
1007 + gchar **devs = NULL;
1008 + GList *di_list = NULL;
1010 + UuidInfo *uuid_info;
1013 + const char *config_name = "qemu-server.conf";
1014 + const char *firewall_name = "qemu-server.fw";
1016 + qemu_co_mutex_lock(&backup_state.backup_mutex);
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");
1025 + /* Todo: try to auto-detect format based on file name */
1026 + BackupFormat format = task->has_format ? task->format : BACKUP_FORMAT_VMA;
1028 + if (task->has_devlist) {
1029 + devs = g_strsplit_set(task->devlist, ",;:", -1);
1033 + blk = blk_by_name(*d);
1036 + if (!bdrv_is_inserted(bs)) {
1037 + error_setg(task->errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
1040 + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1042 + di_list = g_list_append(di_list, di);
1044 + error_set(task->errp, ERROR_CLASS_DEVICE_NOT_FOUND,
1045 + "Device '%s' not found", *d);
1052 + BdrvNextIterator it;
1055 + for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
1056 + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
1060 + PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1);
1062 + di_list = g_list_append(di_list, di);
1067 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
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)) {
1081 + ssize_t size = bdrv_getlength(di->bs);
1083 + error_setg_errno(task->errp, -di->size, "bdrv_getlength failed");
1090 + uuid_generate(uuid);
1092 + if (format == BACKUP_FORMAT_PBS) {
1093 + if (!task->has_password) {
1094 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
1097 + if (!task->has_backup_id) {
1098 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
1101 + if (!task->has_backup_time) {
1102 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
1106 + int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
1107 + firewall_name = "fw.conf";
1109 + char *pbs_err = NULL;
1110 + pbs = proxmox_backup_new(
1111 + task->backup_file,
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,
1122 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
1123 + "proxmox_backup_new failed: %s", pbs_err);
1124 + proxmox_backup_free_error(pbs_err);
1128 + if (proxmox_backup_co_connect(pbs, task->errp) < 0)
1131 + /* register all devices */
1134 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1135 + l = g_list_next(l);
1137 + const char *devname = bdrv_get_device_name(di->bs);
1139 + int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
1143 + if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_cb, di, task->errp))) {
1147 + di->dev_id = dev_id;
1149 + } else if (format == BACKUP_FORMAT_VMA) {
1150 + vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
1153 + error_propagate(task->errp, local_err);
1158 + /* register all devices for vma writer */
1161 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1162 + l = g_list_next(l);
1164 + if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_cb, di, task->errp))) {
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");
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);
1182 + backup_dir = task->backup_file;
1186 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1187 + l = g_list_next(l);
1189 + const char *devname = bdrv_get_device_name(di->bs);
1190 + snprintf(di->targetfile, PATH_MAX, "%s/%s.raw", backup_dir, devname);
1192 + int flags = BDRV_O_RDWR;
1193 + bdrv_img_create(di->targetfile, "raw", NULL, NULL, NULL,
1194 + di->size, flags, false, &local_err);
1196 + error_propagate(task->errp, local_err);
1200 + di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
1201 + if (!di->target) {
1202 + error_propagate(task->errp, local_err);
1207 + error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
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) {
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) {
1227 + /* initialize global backup_state now */
1229 + qemu_co_rwlock_wrlock(&backup_state.stat.rwlock);
1231 + backup_state.stat.cancel = false;
1233 + if (backup_state.stat.error) {
1234 + error_free(backup_state.stat.error);
1235 + backup_state.stat.error = NULL;
1238 + backup_state.stat.start_time = time(NULL);
1239 + backup_state.stat.end_time = 0;
1241 + if (backup_state.stat.backup_file) {
1242 + g_free(backup_state.stat.backup_file);
1244 + backup_state.stat.backup_file = g_strdup(task->backup_file);
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);
1250 + backup_state.stat.total = total;
1251 + backup_state.stat.transferred = 0;
1252 + backup_state.stat.zero_bytes = 0;
1254 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1256 + backup_state.speed = (task->has_speed && task->speed > 0) ? task->speed : 0;
1258 + backup_state.vmaw = vmaw;
1259 + backup_state.pbs = pbs;
1261 + backup_state.di_list = di_list;
1263 + /* start all jobs (paused state) */
1266 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1267 + l = g_list_next(l);
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);
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);
1286 + job_start(&job->job);
1288 + bdrv_unref(di->target);
1289 + di->target = NULL;
1293 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
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);
1300 + pvebackup_co_run_next_job(); // run one job
1302 + pvebackup_co_cancel(NULL);
1305 + uuid_info = g_malloc0(sizeof(*uuid_info));
1306 + uuid_info->UUID = uuid_str;
1308 + task->result = uuid_info;
1315 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
1316 + l = g_list_next(l);
1319 + bdrv_unref(di->target);
1322 + if (di->targetfile[0]) {
1323 + unlink(di->targetfile);
1327 + g_list_free(di_list);
1334 + Error *err = NULL;
1335 + vma_writer_close(vmaw, &err);
1336 + unlink(task->backup_file);
1340 + proxmox_backup_disconnect(pbs);
1344 + rmdir(backup_dir);
1347 + qemu_co_mutex_unlock(&backup_state.backup_mutex);
1349 + task->result = NULL;
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)
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,
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,
1392 + block_on_coroutine_fn(pvebackup_co_start, &task);
1394 + return task.result;
1398 +typedef struct QmpQueryBackupTask {
1400 + BackupStatus *result;
1401 +} QmpQueryBackupTask;
1403 +static void coroutine_fn pvebackup_co_query(void *opaque)
1405 + assert(qemu_in_coroutine());
1407 + QmpQueryBackupTask *task = opaque;
1409 + BackupStatus *info = g_malloc0(sizeof(*info));
1411 + qemu_co_rwlock_rdlock(&backup_state.stat.rwlock);
1413 + if (!backup_state.stat.start_time) {
1414 + /* not started, return {} */
1415 + task->result = info;
1416 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1420 + info->has_status = true;
1421 + info->has_start_time = true;
1422 + info->start_time = backup_state.stat.start_time;
1424 + if (backup_state.stat.backup_file) {
1425 + info->has_backup_file = true;
1426 + info->backup_file = g_strdup(backup_state.stat.backup_file);
1429 + info->has_uuid = true;
1430 + info->uuid = g_strdup(backup_state.stat.uuid_str);
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));
1438 + info->status = g_strdup("done");
1440 + info->has_end_time = true;
1441 + info->end_time = backup_state.stat.end_time;
1443 + info->status = g_strdup("active");
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;
1453 + task->result = info;
1455 + qemu_co_rwlock_unlock(&backup_state.stat.rwlock);
1458 +BackupStatus *qmp_query_backup(Error **errp)
1460 + QmpQueryBackupTask task = {
1465 + block_on_coroutine_fn(pvebackup_co_query, &task);
1467 + return task.result;
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'] }
1480 +# Detailed backup status.
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
1486 +# @errmsg: error message (only returned if status is 'error')
1488 +# @total: total amount of bytes involved in the backup process
1490 +# @transferred: amount of bytes already backed up.
1492 +# @zero-bytes: amount of 'zero' bytes detected.
1494 +# @start-time: time (epoch) when backup job started.
1496 +# @end-time: time (epoch) when backup job finished.
1498 +# @backup-file: backup file name
1500 +# @uuid: uuid for this backup job
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' } }
1512 +# An enumeration of supported backup formats.
1514 +# @vma: Proxmox vma backup format
1516 +{ 'enum': 'BackupFormat',
1517 + 'data': [ 'vma', 'dir', 'pbs' ] }
1522 +# Starts a VM backup.
1524 +# @backup-file: the backup file name
1526 +# @format: format of the backup file
1528 +# @config-file: a configuration file to include into
1529 +# the backup archive.
1531 +# @speed: the maximum speed, in bytes per second
1533 +# @devlist: list of block device names (separated by ',', ';'
1534 +# or ':'). By default the backup includes all writable block devices.
1536 +# @password: backup server passsword (required for format 'pbs')
1538 +# @keyfile: keyfile used for encryption (optional for format 'pbs')
1540 +# @key-password: password for keyfile (optional for format 'pbs')
1542 +# @fingerprint: server cert fingerprint (optional for format 'pbs')
1544 +# @backup-id: backup ID (required for format 'pbs')
1546 +# @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
1548 +# Returns: the uuid of the backup job
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' }
1567 +# Returns information about current/last backup task.
1569 +# Returns: @BackupStatus
1572 +{ 'command': 'query-backup', 'returns': 'BackupStatus' }
1577 +# Cancel the current executing backup process.
1579 +# Returns: nothing on success
1581 +# Notes: This command succeeds even if there is no backup process running.
1584 +{ 'command': 'backup-cancel' }
1587 # @BlockDeviceTimedStats:
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 @@
1595 { 'enum': 'PCIELinkWidth',
1596 'data': [ '1', '2', '4', '8', '12', '16', '32' ] }
1601 +# Guest UUID information (Universally Unique Identifier).
1603 +# @UUID: the UUID of the guest
1607 +# Notes: If no UUID was specified for the guest, a null UUID is returned.
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
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.
1618 +{ 'include': 'common.json' }
1623 @@ -406,19 +408,6 @@
1625 { 'command': 'query-target', 'returns': 'TargetInfo' }
1630 -# Guest UUID information (Universally Unique Identifier).
1632 -# @UUID: the UUID of the guest
1636 -# Notes: If no UUID was specified for the guest, a null UUID is returned.
1638 -{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }