]> git.proxmox.com Git - pve-qemu-kvm.git/blob - debian/patches/0003-add-backup-related-monitor-commands.patch
update backup patches
[pve-qemu-kvm.git] / debian / patches / 0003-add-backup-related-monitor-commands.patch
1 From cd593564217e6ae808cc8555845b14eabac95bd0 Mon Sep 17 00:00:00 2001
2 From: Dietmar Maurer <dietmar@proxmox.com>
3 Date: Tue, 13 Nov 2012 11:27:56 +0100
4 Subject: [PATCH v3 3/6] add backup related monitor commands
5
6 We use a generic BackupDriver struct to encapsulate all archive format
7 related function.
8
9 Another option would be to simply dump <devid,cluster_num,cluster_data> to
10 the output fh (pipe), and an external binary saves the data. That way we
11 could move the whole archive format related code out of qemu.
12
13 Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
14 ---
15 backup.h | 12 ++
16 blockdev.c | 419 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 hmp-commands.hx | 31 ++++
18 hmp.c | 63 ++++++++
19 hmp.h | 3 +
20 monitor.c | 7 +
21 qapi-schema.json | 91 ++++++++++++
22 qmp-commands.hx | 27 ++++
23 8 files changed, 653 insertions(+), 0 deletions(-)
24
25 diff --git a/backup.h b/backup.h
26 index a5f85e6..c9c20c9 100644
27 --- a/backup.h
28 +++ b/backup.h
29 @@ -29,4 +29,16 @@ int backup_job_create(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb,
30 BlockDriverCompletionFunc *backup_complete_cb,
31 void *opaque, int64_t speed);
32
33 +typedef struct BackupDriver {
34 + const char *format;
35 + void *(*open_cb)(const char *filename, uuid_t uuid, Error **errp);
36 + int (*close_cb)(void *opaque, Error **errp);
37 + int (*register_config_cb)(void *opaque, const char *name, gpointer data,
38 + size_t data_len);
39 + int (*register_stream_cb)(void *opaque, const char *devname, size_t size);
40 + int (*dump_cb)(void *opaque, uint8_t dev_id, int64_t cluster_num,
41 + unsigned char *buf, size_t *zero_bytes);
42 + int (*complete_cb)(void *opaque, uint8_t dev_id, int ret);
43 +} BackupDriver;
44 +
45 #endif /* QEMU_BACKUP_H */
46 diff --git a/blockdev.c b/blockdev.c
47 index e73fd6e..ba28654 100644
48 --- a/blockdev.c
49 +++ b/blockdev.c
50 @@ -20,6 +20,7 @@
51 #include "qmp-commands.h"
52 #include "trace.h"
53 #include "arch_init.h"
54 +#include "backup.h"
55
56 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
57
58 @@ -1321,6 +1322,424 @@ void qmp_drive_mirror(const char *device, const char *target,
59 drive_get_ref(drive_get_by_blockdev(bs));
60 }
61
62 +/* Backup related function */
63 +
64 +static void backup_run_next_job(void);
65 +
66 +static struct GenericBackupState {
67 + Error *error;
68 + uuid_t uuid;
69 + char uuid_str[37];
70 + int64_t speed;
71 + time_t start_time;
72 + time_t end_time;
73 + char *backupfile;
74 + const BackupDriver *driver;
75 + void *writer;
76 + GList *bcb_list;
77 + size_t total;
78 + size_t transferred;
79 + size_t zero_bytes;
80 +} backup_state;
81 +
82 +typedef struct BackupCB {
83 + BlockDriverState *bs;
84 + uint8_t dev_id;
85 + bool started;
86 + bool completed;
87 + size_t size;
88 + size_t transferred;
89 + size_t zero_bytes;
90 +} BackupCB;
91 +
92 +static int backup_dump_cb(void *opaque, BlockDriverState *bs,
93 + int64_t cluster_num, unsigned char *buf)
94 +{
95 + BackupCB *bcb = opaque;
96 +
97 + assert(backup_state.driver);
98 + assert(backup_state.writer);
99 + assert(backup_state.driver->dump_cb);
100 +
101 + size_t zero_bytes = 0;
102 + int bytes = backup_state.driver->dump_cb(backup_state.writer,
103 + bcb->dev_id, cluster_num,
104 + buf, &zero_bytes);
105 +
106 + if (bytes > 0) {
107 + bcb->transferred += bytes;
108 + backup_state.transferred += bytes;
109 + if (zero_bytes) {
110 + bcb->zero_bytes += bytes;
111 + backup_state.zero_bytes += zero_bytes;
112 + }
113 + }
114 +
115 + return bytes;
116 +}
117 +
118 +static void backup_cleanup(void)
119 +{
120 + if (backup_state.writer && backup_state.driver) {
121 + backup_state.end_time = time(NULL);
122 + Error *local_err = NULL;
123 + backup_state.driver->close_cb(backup_state.writer, &local_err);
124 + error_propagate(&backup_state.error, local_err);
125 + backup_state.writer = NULL;
126 + }
127 +
128 + if (backup_state.bcb_list) {
129 + GList *l = backup_state.bcb_list;
130 + while (l) {
131 + BackupCB *bcb = l->data;
132 + l = g_list_next(l);
133 + drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs));
134 + g_free(bcb);
135 + }
136 + g_list_free(backup_state.bcb_list);
137 + backup_state.bcb_list = NULL;
138 + }
139 +}
140 +
141 +static void backup_complete_cb(void *opaque, int ret)
142 +{
143 + BackupCB *bcb = opaque;
144 +
145 + assert(backup_state.driver);
146 + assert(backup_state.writer);
147 + assert(backup_state.driver->complete_cb);
148 + assert(backup_state.driver->close_cb);
149 +
150 + bcb->completed = true;
151 +
152 + backup_state.driver->complete_cb(backup_state.writer, bcb->dev_id, ret);
153 +
154 + backup_run_next_job();
155 +}
156 +
157 +static void backup_cancel(void)
158 +{
159 + if (!backup_state.error) {
160 + error_setg(&backup_state.error, "backup cancelled");
161 + }
162 +
163 + /* drain all i/o (awake jobs waiting for aio) */
164 + bdrv_drain_all();
165 +
166 + /* we only need to cancel the running job - backup_run_next_job() cancels
167 + * the other jobs */
168 +
169 + int job_count = 0;
170 + GList *l = backup_state.bcb_list;
171 + while (l) {
172 + BackupCB *bcb = l->data;
173 + l = g_list_next(l);
174 + BlockJob *job = bcb->bs->job;
175 + if (job) {
176 + job_count++;
177 + if (!bcb->completed) {
178 + block_job_cancel(job);
179 + /* make sure we call the complete callback */
180 + if (!job->co) {
181 + block_job_completed(job, -1);
182 + }
183 + break;
184 + }
185 + }
186 + }
187 +
188 + if (job_count == 0) { /* can happen when backup_start_jobs() fails */
189 + backup_cleanup();
190 + }
191 +}
192 +
193 +void qmp_backup_cancel(Error **errp)
194 +{
195 + backup_cancel();
196 +}
197 +
198 +static void backup_run_next_job(void)
199 +{
200 + GList *l = backup_state.bcb_list;
201 + while(l) {
202 + BackupCB *bcb = l->data;
203 + l = g_list_next(l);
204 +
205 + if (bcb->bs && bcb->bs->job && !bcb->completed) {
206 + if (!bcb->started) {
207 + bcb->started = true;
208 + if (!backup_state.error) {
209 + backup_job_start(bcb->bs);
210 + } else {
211 + block_job_completed(bcb->bs->job, -1);
212 + }
213 + }
214 + return;
215 + }
216 + }
217 +
218 + backup_cleanup();
219 +}
220 +
221 +static void backup_start_jobs(void)
222 +{
223 + /* create all jobs (one for each device), start first one */
224 + GList *l = backup_state.bcb_list;
225 + while (l) {
226 + BackupCB *bcb = l->data;
227 + l = g_list_next(l);
228 +
229 + if (backup_job_create(bcb->bs, backup_dump_cb, backup_complete_cb,
230 + bcb, backup_state.speed) != 0) {
231 + error_setg(&backup_state.error, "backup_job_create failed");
232 + backup_cancel();
233 + return;
234 + }
235 + }
236 +
237 + backup_run_next_job();
238 +}
239 +
240 +char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format,
241 + bool has_config_filename, const char *config_filename,
242 + bool has_devlist, const char *devlist,
243 + bool has_speed, int64_t speed, Error **errp)
244 +{
245 + BlockDriverState *bs;
246 + Error *local_err = NULL;
247 + uuid_t uuid;
248 + void *writer = NULL;
249 + gchar **devs = NULL;
250 + GList *bcblist = NULL;
251 +
252 + /* Todo: try to auto-detect format based on file name */
253 + format = has_format ? format : BACKUP_FORMAT_VMA;
254 +
255 + /* fixme: find driver for specifued format */
256 + const BackupDriver *driver = NULL;
257 +
258 + if (!driver) {
259 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
260 + return NULL;
261 + }
262 +
263 + if (has_devlist) {
264 + devs = g_strsplit_set(devlist, ",;:", -1);
265 +
266 + gchar **d = devs;
267 + while (d && *d) {
268 + bs = bdrv_find(*d);
269 + if (bs) {
270 + if (bdrv_is_read_only(bs)) {
271 + error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d);
272 + goto err;
273 + }
274 + if (!bdrv_is_inserted(bs)) {
275 + error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
276 + goto err;
277 + }
278 + BackupCB *bcb = g_new0(BackupCB, 1);
279 + bcb->bs = bs;
280 + bcblist = g_list_append(bcblist, bcb);
281 + } else {
282 + error_set(errp, QERR_DEVICE_NOT_FOUND, *d);
283 + goto err;
284 + }
285 + d++;
286 + }
287 +
288 + } else {
289 +
290 + bs = NULL;
291 + while ((bs = bdrv_next(bs))) {
292 +
293 + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
294 + continue;
295 + }
296 +
297 + BackupCB *bcb = g_new0(BackupCB, 1);
298 + bcb->bs = bs;
299 + bcblist = g_list_append(bcblist, bcb);
300 + }
301 + }
302 +
303 + if (!bcblist) {
304 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
305 + goto err;
306 + }
307 +
308 + GList *l = bcblist;
309 + while (l) {
310 + BackupCB *bcb = l->data;
311 + l = g_list_next(l);
312 + if (bcb->bs->job) {
313 + error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bcb->bs));
314 + goto err;
315 + }
316 + }
317 +
318 + uuid_generate(uuid);
319 +
320 + writer = driver->open_cb(backupfile, uuid, &local_err);
321 + if (!writer) {
322 + if (error_is_set(&local_err)) {
323 + error_propagate(errp, local_err);
324 + }
325 + goto err;
326 + }
327 +
328 + size_t total = 0;
329 +
330 + /* register all devices for vma writer */
331 + l = bcblist;
332 + while (l) {
333 + BackupCB *bcb = l->data;
334 + l = g_list_next(l);
335 +
336 + int64_t size = bdrv_getlength(bcb->bs);
337 + const char *devname = bdrv_get_device_name(bcb->bs);
338 + bcb->dev_id = driver->register_stream_cb(writer, devname, size);
339 + if (bcb->dev_id <= 0) {
340 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
341 + "register_stream failed");
342 + goto err;
343 + }
344 + bcb->size = size;
345 + total += size;
346 + }
347 +
348 + /* add configuration file to archive */
349 + if (has_config_filename) {
350 + char *cdata = NULL;
351 + gsize clen = 0;
352 + GError *err = NULL;
353 + if (!g_file_get_contents(config_filename, &cdata, &clen, &err)) {
354 + error_setg(errp, "unable to read file '%s'", config_filename);
355 + goto err;
356 + }
357 +
358 + const char *basename = g_path_get_basename(config_filename);
359 + if (driver->register_config_cb(writer, basename, cdata, clen) < 0) {
360 + error_setg(errp, "register_config failed");
361 + g_free(cdata);
362 + goto err;
363 + }
364 + g_free(cdata);
365 + }
366 +
367 + /* initialize global backup_state now */
368 +
369 + if (backup_state.error) {
370 + error_free(backup_state.error);
371 + backup_state.error = NULL;
372 + }
373 +
374 + backup_state.driver = driver;
375 +
376 + backup_state.speed = (has_speed && speed > 0) ? speed : 0;
377 +
378 + backup_state.start_time = time(NULL);
379 + backup_state.end_time = 0;
380 +
381 + if (backup_state.backupfile) {
382 + g_free(backup_state.backupfile);
383 + }
384 + backup_state.backupfile = g_strdup(backupfile);
385 +
386 + backup_state.writer = writer;
387 +
388 + uuid_copy(backup_state.uuid, uuid);
389 + uuid_unparse_lower(uuid, backup_state.uuid_str);
390 +
391 + backup_state.bcb_list = bcblist;
392 +
393 + backup_state.total = total;
394 + backup_state.transferred = 0;
395 + backup_state.zero_bytes = 0;
396 +
397 + /* Grab a reference so hotplug does not delete the
398 + * BlockDriverState from underneath us.
399 + */
400 + l = bcblist;
401 + while (l) {
402 + BackupCB *bcb = l->data;
403 + l = g_list_next(l);
404 + drive_get_ref(drive_get_by_blockdev(bcb->bs));
405 + }
406 +
407 + backup_start_jobs();
408 +
409 + return g_strdup(backup_state.uuid_str);
410 +
411 +err:
412 +
413 + l = bcblist;
414 + while (l) {
415 + g_free(l->data);
416 + l = g_list_next(l);
417 + }
418 + g_list_free(bcblist);
419 +
420 + if (devs) {
421 + g_strfreev(devs);
422 + }
423 +
424 + if (writer) {
425 + unlink(backupfile);
426 + if (driver) {
427 + Error *err = NULL;
428 + driver->close_cb(writer, &err);
429 + }
430 + }
431 +
432 + return NULL;
433 +}
434 +
435 +BackupStatus *qmp_query_backup(Error **errp)
436 +{
437 + BackupStatus *info = g_malloc0(sizeof(*info));
438 +
439 + if (!backup_state.start_time) {
440 + /* not started, return {} */
441 + return info;
442 + }
443 +
444 + info->has_status = true;
445 + info->has_start_time = true;
446 + info->start_time = backup_state.start_time;
447 +
448 + if (backup_state.backupfile) {
449 + info->has_backupfile = true;
450 + info->backupfile = g_strdup(backup_state.backupfile);
451 + }
452 +
453 + info->has_uuid = true;
454 + info->uuid = g_strdup(backup_state.uuid_str);
455 +
456 + if (backup_state.end_time) {
457 + if (backup_state.error) {
458 + info->status = g_strdup("error");
459 + info->has_errmsg = true;
460 + info->errmsg = g_strdup(error_get_pretty(backup_state.error));
461 + } else {
462 + info->status = g_strdup("done");
463 + }
464 + info->has_end_time = true;
465 + info->end_time = backup_state.end_time;
466 + } else {
467 + info->status = g_strdup("active");
468 + }
469 +
470 + info->has_total = true;
471 + info->total = backup_state.total;
472 + info->has_zero_bytes = true;
473 + info->zero_bytes = backup_state.zero_bytes;
474 + info->has_transferred = true;
475 + info->transferred = backup_state.transferred;
476 +
477 + return info;
478 +}
479 +
480 static BlockJob *find_block_job(const char *device)
481 {
482 BlockDriverState *bs;
483 diff --git a/hmp-commands.hx b/hmp-commands.hx
484 index 010b8c9..57be357 100644
485 --- a/hmp-commands.hx
486 +++ b/hmp-commands.hx
487 @@ -83,6 +83,35 @@ STEXI
488 Copy data from a backing file into a block device.
489 ETEXI
490
491 + {
492 + .name = "backup",
493 + .args_type = "backupfile:s,speed:o?,devlist:s?",
494 + .params = "backupfile [speed [devlist]]",
495 + .help = "create a VM Backup.",
496 + .mhandler.cmd = hmp_backup,
497 + },
498 +
499 +STEXI
500 +@item backup
501 +@findex backup
502 +Create a VM backup.
503 +ETEXI
504 +
505 + {
506 + .name = "backup_cancel",
507 + .args_type = "",
508 + .params = "",
509 + .help = "cancel the current VM backup",
510 + .mhandler.cmd = hmp_backup_cancel,
511 + },
512 +
513 +STEXI
514 +@item backup_cancel
515 +@findex backup_cancel
516 +Cancel the current VM backup.
517 +
518 +ETEXI
519 +
520 {
521 .name = "block_job_set_speed",
522 .args_type = "device:B,speed:o",
523 @@ -1558,6 +1587,8 @@ show CPU statistics
524 show user network stack connection states
525 @item info migrate
526 show migration status
527 +@item info backup
528 +show backup status
529 @item info migrate_capabilities
530 show current migration capabilities
531 @item info migrate_cache_size
532 diff --git a/hmp.c b/hmp.c
533 index 180ba2b..27fd421 100644
534 --- a/hmp.c
535 +++ b/hmp.c
536 @@ -130,6 +130,38 @@ void hmp_info_mice(Monitor *mon)
537 qapi_free_MouseInfoList(mice_list);
538 }
539
540 +void hmp_info_backup(Monitor *mon)
541 +{
542 + BackupStatus *info;
543 +
544 + info = qmp_query_backup(NULL);
545 + if (info->has_status) {
546 + if (info->has_errmsg) {
547 + monitor_printf(mon, "Backup status: %s - %s\n",
548 + info->status, info->errmsg);
549 + } else {
550 + monitor_printf(mon, "Backup status: %s\n", info->status);
551 + }
552 + }
553 + if (info->has_backupfile) {
554 + int per = (info->has_total && info->total &&
555 + info->has_transferred && info->transferred) ?
556 + (info->transferred * 100)/info->total : 0;
557 + int zero_per = (info->has_total && info->total &&
558 + info->has_zero_bytes && info->zero_bytes) ?
559 + (info->zero_bytes * 100)/info->total : 0;
560 + monitor_printf(mon, "Backup file: %s\n", info->backupfile);
561 + monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
562 + monitor_printf(mon, "Total size: %zd\n", info->total);
563 + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
564 + info->transferred, per);
565 + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
566 + info->zero_bytes, zero_per);
567 + }
568 +
569 + qapi_free_BackupStatus(info);
570 +}
571 +
572 void hmp_info_migrate(Monitor *mon)
573 {
574 MigrationInfo *info;
575 @@ -977,6 +1009,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
576 hmp_handle_error(mon, &error);
577 }
578
579 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
580 +{
581 + Error *errp = NULL;
582 +
583 + qmp_backup_cancel(&errp);
584 +
585 + if (error_is_set(&errp)) {
586 + monitor_printf(mon, "%s\n", error_get_pretty(errp));
587 + error_free(errp);
588 + return;
589 + }
590 +}
591 +
592 +void hmp_backup(Monitor *mon, const QDict *qdict)
593 +{
594 + const char *backupfile = qdict_get_str(qdict, "backupfile");
595 + const char *devlist = qdict_get_try_str(qdict, "devlist");
596 + int64_t speed = qdict_get_try_int(qdict, "speed", 0);
597 +
598 + Error *errp = NULL;
599 +
600 + qmp_backup(backupfile, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist,
601 + devlist, qdict_haskey(qdict, "speed"), speed, &errp);
602 +
603 + if (error_is_set(&errp)) {
604 + monitor_printf(mon, "%s\n", error_get_pretty(errp));
605 + error_free(errp);
606 + return;
607 + }
608 +}
609 +
610 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
611 {
612 Error *error = NULL;
613 diff --git a/hmp.h b/hmp.h
614 index 0ab03be..20c9a62 100644
615 --- a/hmp.h
616 +++ b/hmp.h
617 @@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon);
618 void hmp_info_migrate(Monitor *mon);
619 void hmp_info_migrate_capabilities(Monitor *mon);
620 void hmp_info_migrate_cache_size(Monitor *mon);
621 +void hmp_info_backup(Monitor *mon);
622 void hmp_info_cpus(Monitor *mon);
623 void hmp_info_block(Monitor *mon);
624 void hmp_info_blockstats(Monitor *mon);
625 @@ -63,6 +64,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
626 void hmp_change(Monitor *mon, const QDict *qdict);
627 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
628 void hmp_block_stream(Monitor *mon, const QDict *qdict);
629 +void hmp_backup(Monitor *mon, const QDict *qdict);
630 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
631 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
632 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
633 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
634 diff --git a/monitor.c b/monitor.c
635 index c0e32d6..85cf47e 100644
636 --- a/monitor.c
637 +++ b/monitor.c
638 @@ -2680,6 +2680,13 @@ static mon_cmd_t info_cmds[] = {
639 },
640 #endif
641 {
642 + .name = "backup",
643 + .args_type = "",
644 + .params = "",
645 + .help = "show backup status",
646 + .mhandler.info = hmp_info_backup,
647 + },
648 + {
649 .name = "migrate",
650 .args_type = "",
651 .params = "",
652 diff --git a/qapi-schema.json b/qapi-schema.json
653 index 5dfa052..2d3699b 100644
654 --- a/qapi-schema.json
655 +++ b/qapi-schema.json
656 @@ -358,6 +358,39 @@
657 { 'type': 'EventInfo', 'data': {'name': 'str'} }
658
659 ##
660 +# @BackupStatus:
661 +#
662 +# Detailed backup status.
663 +#
664 +# @status: #optional string describing the current backup status.
665 +# This can be 'active', 'done', 'error'. If this field is not
666 +# returned, no backup process has been initiated
667 +#
668 +# @errmsg: #optional error message (only returned if status is 'error')
669 +#
670 +# @total: #optional total amount of bytes involved in the backup process
671 +#
672 +# @transferred: #optional amount of bytes already backed up.
673 +#
674 +# @zero-bytes: #optional amount of 'zero' bytes detected.
675 +#
676 +# @start-time: #optional time (epoch) when backup job started.
677 +#
678 +# @end-time: #optional time (epoch) when backup job finished.
679 +#
680 +# @backupfile: #optional backup file name
681 +#
682 +# @uuid: #optional uuid for this backup job
683 +#
684 +# Since: 1.4.0
685 +##
686 +{ 'type': 'BackupStatus',
687 + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
688 + '*transferred': 'int', '*zero-bytes': 'int',
689 + '*start-time': 'int', '*end-time': 'int',
690 + '*backupfile': 'str', '*uuid': 'str' } }
691 +
692 +##
693 # @query-events:
694 #
695 # Return a list of supported QMP events by this server
696 @@ -1764,6 +1797,64 @@
697 'data': { 'path': 'str' },
698 'returns': [ 'ObjectPropertyInfo' ] }
699
700 +
701 +##
702 +# @BackupFormat
703 +#
704 +# An enumeration of supported backup formats.
705 +#
706 +# @vma: Proxmox vma backup format
707 +##
708 +{ 'enum': 'BackupFormat',
709 + 'data': [ 'vma' ] }
710 +
711 +##
712 +# @backup:
713 +#
714 +# Starts a VM backup.
715 +#
716 +# @backupfile: the backup file name
717 +#
718 +# @format: format of the backup file
719 +#
720 +# @config-filename: #optional name of a configuration file to include into
721 +# the backup archive.
722 +#
723 +# @speed: #optional the maximum speed, in bytes per second
724 +#
725 +# Returns: the uuid of the backup job
726 +#
727 +# Since: 1.4.0
728 +##
729 +{ 'command': 'backup', 'data': { 'backupfile': 'str', '*format': 'BackupFormat',
730 + '*config-filename': 'str',
731 + '*devlist': 'str', '*speed': 'int' },
732 + 'returns': 'str' }
733 +
734 +##
735 +# @query-backup
736 +#
737 +# Returns information about current/last backup task.
738 +#
739 +# Returns: @BackupStatus
740 +#
741 +# Since: 1.4.0
742 +##
743 +{ 'command': 'query-backup', 'returns': 'BackupStatus' }
744 +
745 +##
746 +# @backup-cancel
747 +#
748 +# Cancel the current executing backup process.
749 +#
750 +# Returns: nothing on success
751 +#
752 +# Notes: This command succeeds even if there is no backup process running.
753 +#
754 +# Since: 1.4.0
755 +##
756 +{ 'command': 'backup-cancel' }
757 +
758 ##
759 # @qom-get:
760 #
761 diff --git a/qmp-commands.hx b/qmp-commands.hx
762 index 5c692d0..c46fdc4 100644
763 --- a/qmp-commands.hx
764 +++ b/qmp-commands.hx
765 @@ -822,6 +822,18 @@ EQMP
766 },
767
768 {
769 + .name = "backup",
770 + .args_type = "backupfile:s,format:s?,config-filename:F?,speed:o?,devlist:s?",
771 + .mhandler.cmd_new = qmp_marshal_input_backup,
772 + },
773 +
774 + {
775 + .name = "backup_cancel",
776 + .args_type = "",
777 + .mhandler.cmd_new = qmp_marshal_input_backup_cancel,
778 + },
779 +
780 + {
781 .name = "block-job-set-speed",
782 .args_type = "device:B,speed:o",
783 .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
784 @@ -2491,6 +2503,21 @@ EQMP
785 },
786
787 SQMP
788 +
789 +query-backup
790 +-------------
791 +
792 +Backup status.
793 +
794 +EQMP
795 +
796 + {
797 + .name = "query-backup",
798 + .args_type = "",
799 + .mhandler.cmd_new = qmp_marshal_input_query_backup,
800 + },
801 +
802 +SQMP
803 migrate-set-capabilities
804 -------
805
806 --
807 1.7.2.5
808