1 From 5583ec7185e67b11681cead13e59d8ae3319748f 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
6 We use a generic BackupDriver struct to encaplulated all archive format
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.
13 Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
16 blockdev.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 hmp-commands.hx | 31 +++++
21 qapi-schema.json | 81 ++++++++++++
22 qmp-commands.hx | 27 ++++
23 8 files changed, 601 insertions(+), 0 deletions(-)
25 diff --git a/backup.h b/backup.h
26 index e1f0290..ae4aa8c 100644
29 @@ -27,4 +27,17 @@ int backup_job_start(BlockDriverState *bs, BackupDumpFunc *backup_dump_cb,
30 BlockDriverCompletionFunc *backup_complete_cb,
33 +typedef struct BackupDriver {
35 + void *(*open_cb)(const char *filename, uuid_t uuid, int64_t speed,
37 + int (*close_cb)(void *opaque, Error **errp);
38 + int (*register_config_cb)(void *opaque, const char *name, gpointer data,
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);
46 #endif /* QEMU_BACKUP_H */
47 diff --git a/blockdev.c b/blockdev.c
48 index e73fd6e..c635d21 100644
52 #include "qmp-commands.h"
54 #include "arch_init.h"
57 static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives);
59 @@ -1321,6 +1322,381 @@ void qmp_drive_mirror(const char *device, const char *target,
60 drive_get_ref(drive_get_by_blockdev(bs));
63 +/* Backup related function */
65 +static struct GenericBackupState {
72 + const BackupDriver *driver;
80 +typedef struct BackupCB {
81 + BlockDriverState *bs;
89 +static int backup_dump_cb(void *opaque, BlockDriverState *bs,
90 + int64_t cluster_num, unsigned char *buf)
92 + BackupCB *bcb = opaque;
94 + assert(backup_state.driver);
95 + assert(backup_state.writer);
96 + assert(backup_state.driver->dump_cb);
98 + size_t zero_bytes = 0;
99 + int bytes = backup_state.driver->dump_cb(backup_state.writer,
100 + bcb->dev_id, cluster_num,
104 + bcb->transferred += bytes;
105 + backup_state.transferred += bytes;
107 + bcb->zero_bytes += bytes;
108 + backup_state.zero_bytes += zero_bytes;
115 +static void backup_cleanup(void)
117 + if (backup_state.writer && backup_state.driver) {
118 + backup_state.end_time = time(NULL);
119 + Error *local_err = NULL;
120 + backup_state.driver->close_cb(backup_state.writer, &local_err);
121 + error_propagate(&backup_state.error, local_err);
122 + backup_state.writer = NULL;
124 + GList *l = backup_state.bcb_list;
127 + l = g_list_next(l);
129 + g_list_free(backup_state.bcb_list);
130 + backup_state.bcb_list = NULL;
134 +static void backup_complete_cb(void *opaque, int ret)
136 + BackupCB *bcb = opaque;
138 + assert(backup_state.driver);
139 + assert(backup_state.writer);
140 + assert(backup_state.driver->complete_cb);
141 + assert(backup_state.driver->close_cb);
143 + drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs));
145 + backup_state.bcb_list = g_list_remove(backup_state.bcb_list, bcb);
147 + backup_state.driver->complete_cb(backup_state.writer, bcb->dev_id, ret);
149 + if (g_list_length(backup_state.bcb_list) == 0) {
156 +static void backup_cancel(void)
158 + GList *l = backup_state.bcb_list;
162 + BackupCB *bcb = l->data;
163 + l = g_list_next(l);
164 + if (bcb->bs->job && bcb->job_started) {
165 + block_job_cancel(bcb->bs->job);
175 +void qmp_backup_cancel(Error **errp)
177 + if (!backup_state.error) {
178 + error_setg(&backup_state.error, "backup cancelled");
184 +static void backup_start_jobs(void)
186 + /* start all jobs (one for each device) */
187 + GList *l = backup_state.bcb_list;
189 + BackupCB *bcb = l->data;
190 + l = g_list_next(l);
192 + if (backup_job_start(bcb->bs, backup_dump_cb, backup_complete_cb,
194 + bcb->job_started = true;
195 + /* Grab a reference so hotplug does not delete the
196 + * BlockDriverState from underneath us.
198 + drive_get_ref(drive_get_by_blockdev(bcb->bs));
200 + if (!backup_state.error) {
201 + error_setg(&backup_state.error, "backup_job_start failed");
209 +char *qmp_backup(const char *backupfile, bool has_format, const char *format,
210 + bool has_config_filename, const char *config_filename,
211 + bool has_devlist, const char *devlist,
212 + bool has_speed, int64_t speed, Error **errp)
214 + BlockDriverState *bs;
215 + Error *local_err = NULL;
217 + void *writer = NULL;
218 + gchar **devs = NULL;
219 + GList *bcblist = NULL;
221 + /* Todo: try to auto-detect format based on file name */
222 + format = has_format ? format : "vma";
224 + /* fixme: find driver for specifued format */
225 + const BackupDriver *driver = NULL;
228 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
229 + "no backup driver for format '%s'", format);
234 + devs = g_strsplit(devlist, ",;:", -1);
238 + bs = bdrv_find(*d);
240 + if (bdrv_is_read_only(bs)) {
241 + error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d);
244 + if (!bdrv_is_inserted(bs)) {
245 + error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d);
248 + BackupCB *bcb = g_new0(BackupCB, 1);
250 + bcblist = g_list_append(bcblist, bcb);
252 + error_set(errp, QERR_DEVICE_NOT_FOUND, *d);
261 + while ((bs = bdrv_next(bs))) {
263 + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) {
267 + BackupCB *bcb = g_new0(BackupCB, 1);
269 + bcblist = g_list_append(bcblist, bcb);
274 + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list");
278 + GList *l = bcblist;
280 + BackupCB *bcb = l->data;
281 + l = g_list_next(l);
282 + if (bcb->bs->job) {
283 + error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bcb->bs));
288 + uuid_generate(uuid);
290 + writer = driver->open_cb(backupfile, uuid, speed, &local_err);
292 + if (error_is_set(&local_err)) {
293 + error_propagate(errp, local_err);
300 + /* register all devices for vma writer */
303 + BackupCB *bcb = l->data;
304 + l = g_list_next(l);
306 + int64_t size = bdrv_getlength(bcb->bs);
307 + const char *devname = bdrv_get_device_name(bcb->bs);
308 + bcb->dev_id = driver->register_stream_cb(writer, devname, size);
309 + if (bcb->dev_id <= 0) {
310 + error_set(errp, ERROR_CLASS_GENERIC_ERROR,
311 + "register_stream failed");
318 + /* add configuration file to archive */
319 + if (has_config_filename) {
320 + char *cdata = NULL;
322 + GError *err = NULL;
323 + if (!g_file_get_contents(config_filename, &cdata, &clen, &err)) {
324 + error_setg(errp, "unable to read file '%s'", config_filename);
328 + const char *basename = g_path_get_basename(config_filename);
329 + if (driver->register_config_cb(writer, basename, cdata, clen) <= 0) {
330 + error_setg(errp, "register_config failed");
337 + /* initialize global backup_state now */
339 + if (backup_state.error) {
340 + error_free(backup_state.error);
341 + backup_state.error = NULL;
344 + backup_state.driver = driver;
346 + backup_state.start_time = time(NULL);
347 + backup_state.end_time = 0;
349 + if (backup_state.backupfile) {
350 + g_free(backup_state.backupfile);
352 + backup_state.backupfile = g_strdup(backupfile);
354 + backup_state.writer = writer;
356 + uuid_copy(backup_state.uuid, uuid);
357 + uuid_unparse_lower(uuid, backup_state.uuid_str);
359 + backup_state.bcb_list = bcblist;
361 + backup_state.total = total;
362 + backup_state.transferred = 0;
363 + backup_state.zero_bytes = 0;
365 + backup_start_jobs();
367 + return g_strdup(backup_state.uuid_str);
374 + l = g_list_next(l);
376 + g_list_free(bcblist);
383 + unlink(backupfile);
386 + driver->close_cb(writer, &err);
393 +BackupStatus *qmp_query_backup(Error **errp)
395 + BackupStatus *info = g_malloc0(sizeof(*info));
397 + if (!backup_state.start_time) {
398 + /* not started, return {} */
402 + info->has_status = true;
403 + info->has_start_time = true;
404 + info->start_time = backup_state.start_time;
406 + if (backup_state.backupfile) {
407 + info->has_backupfile = true;
408 + info->backupfile = g_strdup(backup_state.backupfile);
411 + info->has_uuid = true;
412 + info->uuid = g_strdup(backup_state.uuid_str);
414 + if (backup_state.end_time) {
415 + if (backup_state.error) {
416 + info->status = g_strdup("error");
417 + info->has_errmsg = true;
418 + info->errmsg = g_strdup(error_get_pretty(backup_state.error));
420 + info->status = g_strdup("done");
422 + info->has_end_time = true;
423 + info->end_time = backup_state.end_time;
425 + info->status = g_strdup("active");
428 + info->has_total = true;
429 + info->total = backup_state.total;
430 + info->has_zero_bytes = true;
431 + info->zero_bytes = backup_state.zero_bytes;
432 + info->has_transferred = true;
433 + info->transferred = backup_state.transferred;
438 static BlockJob *find_block_job(const char *device)
440 BlockDriverState *bs;
441 diff --git a/hmp-commands.hx b/hmp-commands.hx
442 index 010b8c9..57be357 100644
443 --- a/hmp-commands.hx
444 +++ b/hmp-commands.hx
445 @@ -83,6 +83,35 @@ STEXI
446 Copy data from a backing file into a block device.
451 + .args_type = "backupfile:s,speed:o?,devlist:s?",
452 + .params = "backupfile [speed [devlist]]",
453 + .help = "create a VM Backup.",
454 + .mhandler.cmd = hmp_backup,
464 + .name = "backup_cancel",
467 + .help = "cancel the current VM backup",
468 + .mhandler.cmd = hmp_backup_cancel,
473 +@findex backup_cancel
474 +Cancel the current VM backup.
479 .name = "block_job_set_speed",
480 .args_type = "device:B,speed:o",
481 @@ -1558,6 +1587,8 @@ show CPU statistics
482 show user network stack connection states
484 show migration status
487 @item info migrate_capabilities
488 show current migration capabilities
489 @item info migrate_cache_size
490 diff --git a/hmp.c b/hmp.c
491 index 180ba2b..600792f 100644
494 @@ -130,6 +130,38 @@ void hmp_info_mice(Monitor *mon)
495 qapi_free_MouseInfoList(mice_list);
498 +void hmp_info_backup(Monitor *mon)
500 + BackupStatus *info;
502 + info = qmp_query_backup(NULL);
503 + if (info->has_status) {
504 + if (info->has_errmsg) {
505 + monitor_printf(mon, "Backup status: %s - %s\n",
506 + info->status, info->errmsg);
508 + monitor_printf(mon, "Backup status: %s\n", info->status);
511 + if (info->has_backupfile) {
512 + int per = (info->has_total && info->total &&
513 + info->has_transferred && info->transferred) ?
514 + (info->transferred * 100)/info->total : 0;
515 + int zero_per = (info->has_total && info->total &&
516 + info->has_zero_bytes && info->zero_bytes) ?
517 + (info->zero_bytes * 100)/info->total : 0;
518 + monitor_printf(mon, "Backup file: %s\n", info->backupfile);
519 + monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
520 + monitor_printf(mon, "Total size: %zd\n", info->total);
521 + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
522 + info->transferred, per);
523 + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
524 + info->zero_bytes, zero_per);
527 + qapi_free_BackupStatus(info);
530 void hmp_info_migrate(Monitor *mon)
533 @@ -977,6 +1009,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict)
534 hmp_handle_error(mon, &error);
537 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict)
539 + Error *errp = NULL;
541 + qmp_backup_cancel(&errp);
543 + if (error_is_set(&errp)) {
544 + monitor_printf(mon, "%s\n", error_get_pretty(errp));
550 +void hmp_backup(Monitor *mon, const QDict *qdict)
552 + const char *backupfile = qdict_get_str(qdict, "backupfile");
553 + const char *devlist = qdict_get_try_str(qdict, "devlist");
554 + int64_t speed = qdict_get_try_int(qdict, "speed", 0);
556 + Error *errp = NULL;
558 + qmp_backup(backupfile, false, NULL, false, NULL, !!devlist, devlist,
559 + qdict_haskey(qdict, "speed"), speed, &errp);
561 + if (error_is_set(&errp)) {
562 + monitor_printf(mon, "%s\n", error_get_pretty(errp));
568 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict)
571 diff --git a/hmp.h b/hmp.h
572 index 0ab03be..20c9a62 100644
575 @@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon);
576 void hmp_info_migrate(Monitor *mon);
577 void hmp_info_migrate_capabilities(Monitor *mon);
578 void hmp_info_migrate_cache_size(Monitor *mon);
579 +void hmp_info_backup(Monitor *mon);
580 void hmp_info_cpus(Monitor *mon);
581 void hmp_info_block(Monitor *mon);
582 void hmp_info_blockstats(Monitor *mon);
583 @@ -63,6 +64,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict);
584 void hmp_change(Monitor *mon, const QDict *qdict);
585 void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict);
586 void hmp_block_stream(Monitor *mon, const QDict *qdict);
587 +void hmp_backup(Monitor *mon, const QDict *qdict);
588 +void hmp_backup_cancel(Monitor *mon, const QDict *qdict);
589 void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict);
590 void hmp_block_job_cancel(Monitor *mon, const QDict *qdict);
591 void hmp_block_job_pause(Monitor *mon, const QDict *qdict);
592 diff --git a/monitor.c b/monitor.c
593 index c0e32d6..85cf47e 100644
596 @@ -2680,6 +2680,13 @@ static mon_cmd_t info_cmds[] = {
603 + .help = "show backup status",
604 + .mhandler.info = hmp_info_backup,
610 diff --git a/qapi-schema.json b/qapi-schema.json
611 index 5dfa052..71e0ed9 100644
612 --- a/qapi-schema.json
613 +++ b/qapi-schema.json
615 { 'type': 'EventInfo', 'data': {'name': 'str'} }
620 +# Detailed backup status.
622 +# @status: #optional string describing the current backup status.
623 +# Tthis can be 'active', 'done', 'error'. If this field is not
624 +# returned, no backup process has been initiated
626 +# @errmsg: #optional error message (only returned if status is 'error')
628 +# @total: #optional total amount of bytes involved in the backup process
630 +# @transferred: #optional amount of bytes already backed up.
632 +# @zero-bytes: #optional amount of 'zero' bytes detected.
634 +# @start-time: #optional time (epoch) when backup job started.
636 +# @end-time: #optional time (epoch) when backup job finished.
638 +# @backupfile: #optional backup file name
640 +# @uuid: #optional uuid for this backup job
644 +{ 'type': 'BackupStatus',
645 + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
646 + '*transferred': 'int', '*zero-bytes': 'int',
647 + '*start-time': 'int', '*end-time': 'int',
648 + '*backupfile': 'str', '*uuid': 'str' } }
653 # Return a list of supported QMP events by this server
654 @@ -1764,6 +1797,54 @@
655 'data': { 'path': 'str' },
656 'returns': [ 'ObjectPropertyInfo' ] }
662 +# Starts a VM backup.
664 +# @backupfile: the backup file name
666 +# @format: format of the backup file
668 +# @config-filename: #optional name of a configuration file to include into
669 +# the backup archive.
671 +# @speed: #optional the maximum speed, in bytes per second
673 +# Returns: the uuid of the backup job
677 +{ 'command': 'backup', 'data': { 'backupfile': 'str', '*format': 'str',
678 + '*config-filename': 'str',
679 + '*devlist': 'str', '*speed': 'int' },
685 +# Returns information about current/last backup task.
687 +# Returns: @BackupStatus
691 +{ 'command': 'query-backup', 'returns': 'BackupStatus' }
696 +# Cancel the current executing backup process.
698 +# Returns: nothing on success
700 +# Notes: This command succeeds even if there is no backup process running.
704 +{ 'command': 'backup-cancel' }
709 diff --git a/qmp-commands.hx b/qmp-commands.hx
710 index 5c692d0..c46fdc4 100644
711 --- a/qmp-commands.hx
712 +++ b/qmp-commands.hx
713 @@ -822,6 +822,18 @@ EQMP
718 + .args_type = "backupfile:s,format:s?,config-filename:F?,speed:o?,devlist:s?",
719 + .mhandler.cmd_new = qmp_marshal_input_backup,
723 + .name = "backup_cancel",
725 + .mhandler.cmd_new = qmp_marshal_input_backup_cancel,
729 .name = "block-job-set-speed",
730 .args_type = "device:B,speed:o",
731 .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed,
732 @@ -2491,6 +2503,21 @@ EQMP
745 + .name = "query-backup",
747 + .mhandler.cmd_new = qmp_marshal_input_query_backup,
751 migrate-set-capabilities