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