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