From cbf9030a9394ea300f5dd233f75804c9c8d4117a Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 29 Nov 2013 12:44:53 +0100 Subject: [PATCH] re-add pve backup monitor commands --- .../backup-add-pve-monitor-commands.patch | 723 ++++++++++++++++++ debian/patches/backup-modify-job-api.patch | 149 ++++ debian/patches/series | 4 +- 3 files changed, 874 insertions(+), 2 deletions(-) create mode 100644 debian/patches/backup-add-pve-monitor-commands.patch create mode 100644 debian/patches/backup-modify-job-api.patch diff --git a/debian/patches/backup-add-pve-monitor-commands.patch b/debian/patches/backup-add-pve-monitor-commands.patch new file mode 100644 index 0000000..3955237 --- /dev/null +++ b/debian/patches/backup-add-pve-monitor-commands.patch @@ -0,0 +1,723 @@ +Index: new/blockdev.c +=================================================================== +--- new.orig/blockdev.c 2013-12-02 07:42:33.000000000 +0100 ++++ new/blockdev.c 2013-12-02 08:40:19.000000000 +0100 +@@ -45,6 +45,7 @@ + #include "qmp-commands.h" + #include "trace.h" + #include "sysemu/arch_init.h" ++#include "vma.h" + + static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); + +@@ -1438,6 +1439,412 @@ + } + } + ++/* PVE backup related function */ ++ ++static struct PVEBackupState { ++ Error *error; ++ bool cancel; ++ uuid_t uuid; ++ char uuid_str[37]; ++ int64_t speed; ++ time_t start_time; ++ time_t end_time; ++ char *backup_file; ++ VmaWriter *vmaw; ++ GList *di_list; ++ size_t total; ++ size_t transferred; ++ size_t zero_bytes; ++} backup_state; ++ ++typedef struct PVEBackupDevInfo { ++ BlockDriverState *bs; ++ uint8_t dev_id; ++ //bool started; ++ bool completed; ++} PVEBackupDevInfo; ++ ++static void pvebackup_run_next_job(void); ++ ++static int pvebackup_dump_cb(void *opaque, BlockDriverState *bs, ++ int64_t cluster_num, unsigned char *buf) ++{ ++ PVEBackupDevInfo *di = opaque; ++ ++ assert(backup_state.vmaw); ++ ++ size_t zero_bytes = 0; ++ return vma_writer_write(backup_state.vmaw, di->dev_id, cluster_num, ++ buf, &zero_bytes); ++} ++ ++static void pvebackup_update_status(void) ++{ ++ g_assert(backup_state.vmaw); ++ ++ VmaStatus vmastat; ++ ++ vma_writer_get_status(backup_state.vmaw, &vmastat); ++ ++ uint64_t total = 0; ++ uint64_t transferred = 0; ++ uint64_t zero_bytes = 0; ++ ++ int i; ++ for (i = 0; i < 256; i++) { ++ if (vmastat.stream_info[i].size) { ++ total += vmastat.stream_info[i].size; ++ transferred += vmastat.stream_info[i].transferred; ++ zero_bytes += vmastat.stream_info[i].zero_bytes; ++ } ++ } ++ ++ backup_state.total = total; ++ backup_state.transferred = transferred; ++ backup_state.zero_bytes = zero_bytes; ++} ++ ++static void pvebackup_cleanup(void) ++{ ++ if (backup_state.vmaw) { ++ backup_state.end_time = time(NULL); ++ Error *local_err = NULL; ++ pvebackup_update_status(); ++ vma_writer_close(backup_state.vmaw, &local_err); ++ error_propagate(&backup_state.error, local_err); ++ backup_state.vmaw = NULL; ++ } ++ ++ if (backup_state.di_list) { ++ GList *l = backup_state.di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ g_free(di); ++ } ++ g_list_free(backup_state.di_list); ++ backup_state.di_list = NULL; ++ } ++} ++ ++static void pvebackup_complete_cb(void *opaque, int ret) ++{ ++ PVEBackupDevInfo *di = opaque; ++ ++ assert(backup_state.vmaw); ++ ++ di->completed = true; ++ ++ vma_writer_close_stream(backup_state.vmaw, di->dev_id); ++ ++ if (!backup_state.cancel) { ++ pvebackup_run_next_job(); ++ } ++} ++ ++static void pvebackup_cancel(void) ++{ ++ backup_state.cancel = true; ++ ++ if (!backup_state.error) { ++ error_setg(&backup_state.error, "backup cancelled"); ++ } ++ ++ /* drain all i/o (awake jobs waiting for aio) */ ++ bdrv_drain_all(); ++ ++ int job_count = 0; ++ GList *l = backup_state.di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ BlockJob *job = di->bs->job; ++ if (job) { ++ job_count++; ++ if (!di->completed) { ++ block_job_cancel_sync(job); ++ } ++ } ++ } ++ ++ pvebackup_cleanup(); ++} ++ ++void qmp_backup_cancel(Error **errp) ++{ ++ pvebackup_cancel(); ++} ++ ++static void pvebackup_run_next_job(void) ++{ ++ GList *l = backup_state.di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ ++ if (di->bs && di->bs->job && !di->completed) { ++ if (block_job_is_paused(di->bs->job)) { ++ bool cancel = backup_state.error || backup_state.cancel; ++ if (cancel) { ++ block_job_cancel(di->bs->job); ++ } else { ++ block_job_resume(di->bs->job); ++ } ++ } ++ return; ++ } ++ } ++ ++ pvebackup_cleanup(); ++} ++ ++char *qmp_backup(const char *backup_file, bool has_format, ++ BackupFormat format, ++ bool has_config_file, const char *config_file, ++ bool has_devlist, const char *devlist, ++ bool has_speed, int64_t speed, Error **errp) ++{ ++ BlockDriverState *bs; ++ Error *local_err = NULL; ++ uuid_t uuid; ++ VmaWriter *vmaw = NULL; ++ gchar **devs = NULL; ++ GList *di_list = NULL; ++ GList *l; ++ ++ if (backup_state.di_list) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "previous backup not finished"); ++ return NULL; ++ } ++ ++ /* Todo: try to auto-detect format based on file name */ ++ format = has_format ? format : BACKUP_FORMAT_VMA; ++ ++ if (format != BACKUP_FORMAT_VMA) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format"); ++ return NULL; ++ } ++ ++ if (has_devlist) { ++ devs = g_strsplit_set(devlist, ",;:", -1); ++ ++ gchar **d = devs; ++ while (d && *d) { ++ bs = bdrv_find(*d); ++ if (bs) { ++ if (bdrv_is_read_only(bs)) { ++ error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d); ++ goto err; ++ } ++ if (!bdrv_is_inserted(bs)) { ++ error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d); ++ goto err; ++ } ++ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1); ++ di->bs = bs; ++ di_list = g_list_append(di_list, di); ++ } else { ++ error_set(errp, QERR_DEVICE_NOT_FOUND, *d); ++ goto err; ++ } ++ d++; ++ } ++ ++ } else { ++ ++ bs = NULL; ++ while ((bs = bdrv_next(bs))) { ++ ++ if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) { ++ continue; ++ } ++ ++ PVEBackupDevInfo *di = g_new0(PVEBackupDevInfo, 1); ++ di->bs = bs; ++ di_list = g_list_append(di_list, di); ++ } ++ } ++ ++ if (!di_list) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list"); ++ goto err; ++ } ++ ++ l = di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ if (di->bs->job) { ++ error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(di->bs)); ++ goto err; ++ } ++ } ++ ++ uuid_generate(uuid); ++ ++ vmaw = vma_writer_create(backup_file, uuid, &local_err); ++ if (!vmaw) { ++ if (error_is_set(&local_err)) { ++ error_propagate(errp, local_err); ++ } ++ goto err; ++ } ++ ++ /* register all devices for vma writer */ ++ l = di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ ++ int64_t size = bdrv_getlength(di->bs); ++ const char *devname = bdrv_get_device_name(di->bs); ++ di->dev_id = vma_writer_register_stream(vmaw, devname, size); ++ if (di->dev_id <= 0) { ++ error_set(errp, ERROR_CLASS_GENERIC_ERROR, ++ "register_stream failed"); ++ goto err; ++ } ++ } ++ ++ /* add configuration file to archive */ ++ if (has_config_file) { ++ char *cdata = NULL; ++ gsize clen = 0; ++ GError *err = NULL; ++ if (!g_file_get_contents(config_file, &cdata, &clen, &err)) { ++ error_setg(errp, "unable to read file '%s'", config_file); ++ goto err; ++ } ++ ++ const char *basename = g_path_get_basename(config_file); ++ if (vma_writer_add_config(vmaw, basename, cdata, clen) != 0) { ++ error_setg(errp, "unable to add config data to vma archive"); ++ g_free(cdata); ++ goto err; ++ } ++ g_free(cdata); ++ } ++ ++ /* initialize global backup_state now */ ++ ++ backup_state.cancel = false; ++ ++ if (backup_state.error) { ++ error_free(backup_state.error); ++ backup_state.error = NULL; ++ } ++ ++ backup_state.speed = (has_speed && speed > 0) ? speed : 0; ++ ++ backup_state.start_time = time(NULL); ++ backup_state.end_time = 0; ++ ++ if (backup_state.backup_file) { ++ g_free(backup_state.backup_file); ++ } ++ backup_state.backup_file = g_strdup(backup_file); ++ ++ backup_state.vmaw = vmaw; ++ ++ uuid_copy(backup_state.uuid, uuid); ++ uuid_unparse_lower(uuid, backup_state.uuid_str); ++ ++ backup_state.di_list = di_list; ++ ++ pvebackup_update_status(); ++ ++ /* start all jobs (paused state) */ ++ l = di_list; ++ while (l) { ++ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; ++ l = g_list_next(l); ++ ++ backup_start(di->bs, NULL, speed, MIRROR_SYNC_MODE_FULL, ++ BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, ++ pvebackup_dump_cb, pvebackup_complete_cb, di, &local_err); ++ if (local_err != NULL) { ++ error_setg(&backup_state.error, "backup_job_create failed"); ++ pvebackup_cancel(); ++ } ++ } ++ ++ if (!backup_state.error) { ++ pvebackup_run_next_job(); // run one job ++ } ++ ++ return g_strdup(backup_state.uuid_str); ++ ++err: ++ ++ l = di_list; ++ while (l) { ++ g_free(l->data); ++ l = g_list_next(l); ++ } ++ g_list_free(di_list); ++ ++ if (devs) { ++ g_strfreev(devs); ++ } ++ ++ if (vmaw) { ++ Error *err = NULL; ++ vma_writer_close(vmaw, &err); ++ unlink(backup_file); ++ } ++ ++ return NULL; ++} ++ ++BackupStatus *qmp_query_backup(Error **errp) ++{ ++ BackupStatus *info = g_malloc0(sizeof(*info)); ++ ++ if (!backup_state.start_time) { ++ /* not started, return {} */ ++ return info; ++ } ++ ++ info->has_status = true; ++ info->has_start_time = true; ++ info->start_time = backup_state.start_time; ++ ++ if (backup_state.backup_file) { ++ info->has_backup_file = true; ++ info->backup_file = g_strdup(backup_state.backup_file); ++ } ++ ++ info->has_uuid = true; ++ info->uuid = g_strdup(backup_state.uuid_str); ++ ++ if (backup_state.end_time) { ++ if (backup_state.error) { ++ info->status = g_strdup("error"); ++ info->has_errmsg = true; ++ info->errmsg = g_strdup(error_get_pretty(backup_state.error)); ++ } else { ++ info->status = g_strdup("done"); ++ } ++ info->has_end_time = true; ++ info->end_time = backup_state.end_time; ++ } else { ++ info->status = g_strdup("active"); ++ } ++ ++ if (backup_state.vmaw) { ++ pvebackup_update_status(); ++ } ++ ++ info->has_total = true; ++ info->total = backup_state.total; ++ info->has_zero_bytes = true; ++ info->zero_bytes = backup_state.zero_bytes; ++ info->has_transferred = true; ++ info->transferred = backup_state.transferred; ++ ++ return info; ++} + + static void eject_device(BlockDriverState *bs, int force, Error **errp) + { +Index: new/hmp-commands.hx +=================================================================== +--- new.orig/hmp-commands.hx 2013-12-02 07:39:56.000000000 +0100 ++++ new/hmp-commands.hx 2013-12-02 08:19:27.000000000 +0100 +@@ -83,6 +83,35 @@ + Copy data from a backing file into a block device. + ETEXI + ++ { ++ .name = "backup", ++ .args_type = "backupfile:s,speed:o?,devlist:s?", ++ .params = "backupfile [speed [devlist]]", ++ .help = "create a VM Backup.", ++ .mhandler.cmd = hmp_backup, ++ }, ++ ++STEXI ++@item backup ++@findex backup ++Create a VM backup. ++ETEXI ++ ++ { ++ .name = "backup_cancel", ++ .args_type = "", ++ .params = "", ++ .help = "cancel the current VM backup", ++ .mhandler.cmd = hmp_backup_cancel, ++ }, ++ ++STEXI ++@item backup_cancel ++@findex backup_cancel ++Cancel the current VM backup. ++ ++ETEXI ++ + { + .name = "block_job_set_speed", + .args_type = "device:B,speed:o", +@@ -1692,6 +1721,8 @@ + show CPU statistics + @item info usernet + show user network stack connection states ++@item info backup ++show backup status + @item info migrate + show migration status + @item info migrate_capabilities +Index: new/hmp.c +=================================================================== +--- new.orig/hmp.c 2013-12-02 07:39:56.000000000 +0100 ++++ new/hmp.c 2013-12-02 08:21:00.000000000 +0100 +@@ -133,6 +133,38 @@ + qapi_free_MouseInfoList(mice_list); + } + ++void hmp_info_backup(Monitor *mon, const QDict *qdict) ++{ ++ BackupStatus *info; ++ ++ info = qmp_query_backup(NULL); ++ if (info->has_status) { ++ if (info->has_errmsg) { ++ monitor_printf(mon, "Backup status: %s - %s\n", ++ info->status, info->errmsg); ++ } else { ++ monitor_printf(mon, "Backup status: %s\n", info->status); ++ } ++ } ++ if (info->has_backup_file) { ++ int per = (info->has_total && info->total && ++ info->has_transferred && info->transferred) ? ++ (info->transferred * 100)/info->total : 0; ++ int zero_per = (info->has_total && info->total && ++ info->has_zero_bytes && info->zero_bytes) ? ++ (info->zero_bytes * 100)/info->total : 0; ++ monitor_printf(mon, "Backup file: %s\n", info->backup_file); ++ monitor_printf(mon, "Backup uuid: %s\n", info->uuid); ++ monitor_printf(mon, "Total size: %zd\n", info->total); ++ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n", ++ info->transferred, per); ++ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n", ++ info->zero_bytes, zero_per); ++ } ++ ++ qapi_free_BackupStatus(info); ++} ++ + void hmp_info_migrate(Monitor *mon, const QDict *qdict) + { + MigrationInfo *info; +@@ -1194,6 +1226,37 @@ + hmp_handle_error(mon, &error); + } + ++void hmp_backup_cancel(Monitor *mon, const QDict *qdict) ++{ ++ Error *errp = NULL; ++ ++ qmp_backup_cancel(&errp); ++ ++ if (error_is_set(&errp)) { ++ monitor_printf(mon, "%s\n", error_get_pretty(errp)); ++ error_free(errp); ++ return; ++ } ++} ++ ++void hmp_backup(Monitor *mon, const QDict *qdict) ++{ ++ const char *backup_file = qdict_get_str(qdict, "backup-file"); ++ const char *devlist = qdict_get_try_str(qdict, "devlist"); ++ int64_t speed = qdict_get_try_int(qdict, "speed", 0); ++ ++ Error *errp = NULL; ++ ++ qmp_backup(backup_file, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist, ++ devlist, qdict_haskey(qdict, "speed"), speed, &errp); ++ ++ if (error_is_set(&errp)) { ++ monitor_printf(mon, "%s\n", error_get_pretty(errp)); ++ error_free(errp); ++ return; ++ } ++} ++ + void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict) + { + Error *error = NULL; +Index: new/hmp.h +=================================================================== +--- new.orig/hmp.h 2013-12-02 07:39:56.000000000 +0100 ++++ new/hmp.h 2013-12-02 08:22:00.000000000 +0100 +@@ -28,6 +28,7 @@ + void hmp_info_migrate(Monitor *mon, const QDict *qdict); + void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict); + void hmp_info_migrate_cache_size(Monitor *mon, const QDict *qdict); ++void hmp_info_backup(Monitor *mon, const QDict *qdict); + void hmp_info_cpus(Monitor *mon, const QDict *qdict); + void hmp_info_block(Monitor *mon, const QDict *qdict); + void hmp_info_blockstats(Monitor *mon, const QDict *qdict); +@@ -69,6 +70,8 @@ + void hmp_change(Monitor *mon, const QDict *qdict); + void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); + void hmp_block_stream(Monitor *mon, const QDict *qdict); ++void hmp_backup(Monitor *mon, const QDict *qdict); ++void hmp_backup_cancel(Monitor *mon, const QDict *qdict); + void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict); + void hmp_block_job_cancel(Monitor *mon, const QDict *qdict); + void hmp_block_job_pause(Monitor *mon, const QDict *qdict); +Index: new/monitor.c +=================================================================== +--- new.orig/monitor.c 2013-12-02 07:39:56.000000000 +0100 ++++ new/monitor.c 2013-12-02 08:22:29.000000000 +0100 +@@ -2880,6 +2880,13 @@ + }, + #endif + { ++ .name = "backup", ++ .args_type = "", ++ .params = "", ++ .help = "show backup status", ++ .mhandler.cmd = hmp_info_backup, ++ }, ++ { + .name = "migrate", + .args_type = "", + .params = "", +Index: new/qapi-schema.json +=================================================================== +--- new.orig/qapi-schema.json 2013-12-02 07:39:56.000000000 +0100 ++++ new/qapi-schema.json 2013-12-02 08:16:27.000000000 +0100 +@@ -547,6 +547,95 @@ + ## + { 'command': 'query-events', 'returns': ['EventInfo'] } + ++# @BackupStatus: ++# ++# Detailed backup status. ++# ++# @status: #optional string describing the current backup status. ++# This can be 'active', 'done', 'error'. If this field is not ++# returned, no backup process has been initiated ++# ++# @errmsg: #optional error message (only returned if status is 'error') ++# ++# @total: #optional total amount of bytes involved in the backup process ++# ++# @transferred: #optional amount of bytes already backed up. ++# ++# @zero-bytes: #optional amount of 'zero' bytes detected. ++# ++# @start-time: #optional time (epoch) when backup job started. ++# ++# @end-time: #optional time (epoch) when backup job finished. ++# ++# @backupfile: #optional backup file name ++# ++# @uuid: #optional uuid for this backup job ++# ++## ++{ 'type': 'BackupStatus', ++ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', ++ '*transferred': 'int', '*zero-bytes': 'int', ++ '*start-time': 'int', '*end-time': 'int', ++ '*backup-file': 'str', '*uuid': 'str' } } ++ ++## ++# @BackupFormat ++# ++# An enumeration of supported backup formats. ++# ++# @vma: Proxmox vma backup format ++## ++{ 'enum': 'BackupFormat', ++ 'data': [ 'vma' ] } ++ ++## ++# @backup: ++# ++# Starts a VM backup. ++# ++# @backup-file: the backup file name ++# ++# @format: format of the backup file ++# ++# @config-filename: #optional name of a configuration file to include into ++# the backup archive. ++# ++# @speed: #optional the maximum speed, in bytes per second ++# ++# @devlist: #optional list of block device names (separated by ',', ';' ++# or ':'). By default the backup includes all writable block devices. ++# ++# Returns: the uuid of the backup job ++# ++## ++{ 'command': 'backup', 'data': { 'backup-file': 'str', ++ '*format': 'BackupFormat', ++ '*config-file': 'str', ++ '*devlist': 'str', '*speed': 'int' }, ++ 'returns': 'str' } ++ ++## ++# @query-backup ++# ++# Returns information about current/last backup task. ++# ++# Returns: @BackupStatus ++# ++## ++{ 'command': 'query-backup', 'returns': 'BackupStatus' } ++ ++## ++# @backup-cancel ++# ++# Cancel the current executing backup process. ++# ++# Returns: nothing on success ++# ++# Notes: This command succeeds even if there is no backup process running. ++# ++## ++{ 'command': 'backup-cancel' } ++ + ## + # @MigrationStats + # +Index: new/qmp-commands.hx +=================================================================== +--- new.orig/qmp-commands.hx 2013-12-02 07:39:56.000000000 +0100 ++++ new/qmp-commands.hx 2013-12-02 08:19:45.000000000 +0100 +@@ -966,6 +966,24 @@ + EQMP + + { ++ .name = "backup", ++ .args_type = "backup-file:s,format:s?,config-file:F?,speed:o?,devlist:s?", ++ .mhandler.cmd_new = qmp_marshal_input_backup, ++ }, ++ ++ { ++ .name = "backup-cancel", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_backup_cancel, ++ }, ++ ++ { ++ .name = "query-backup", ++ .args_type = "", ++ .mhandler.cmd_new = qmp_marshal_input_query_backup, ++ }, ++ ++ { + .name = "block-job-set-speed", + .args_type = "device:B,speed:o", + .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed, diff --git a/debian/patches/backup-modify-job-api.patch b/debian/patches/backup-modify-job-api.patch new file mode 100644 index 0000000..c4dab45 --- /dev/null +++ b/debian/patches/backup-modify-job-api.patch @@ -0,0 +1,149 @@ +Index: new/block/backup.c +=================================================================== +--- new.orig/block/backup.c 2013-11-27 10:51:32.000000000 +0100 ++++ new/block/backup.c 2013-12-02 07:41:30.000000000 +0100 +@@ -39,6 +39,7 @@ + BlockDriverState *target; + MirrorSyncMode sync_mode; + RateLimit limit; ++ BackupDumpFunc *dump_cb; + BlockdevOnError on_source_error; + BlockdevOnError on_target_error; + CoRwlock flush_rwlock; +@@ -137,12 +138,20 @@ + } + + if (buffer_is_zero(iov.iov_base, iov.iov_len)) { +- ret = bdrv_co_write_zeroes(job->target, +- start * BACKUP_SECTORS_PER_CLUSTER, n); ++ if (job->dump_cb) { ++ ret = job->dump_cb(job->common.opaque, bs, start, NULL); ++ } else { ++ ret = bdrv_co_write_zeroes(job->target, ++ start * BACKUP_SECTORS_PER_CLUSTER, n); ++ } + } else { +- ret = bdrv_co_writev(job->target, +- start * BACKUP_SECTORS_PER_CLUSTER, n, +- &bounce_qiov); ++ if (job->dump_cb) { ++ ret = job->dump_cb(job->common.opaque, bs, start, bounce_buffer); ++ } else { ++ ret = bdrv_co_writev(job->target, ++ start * BACKUP_SECTORS_PER_CLUSTER, n, ++ &bounce_qiov); ++ } + } + if (ret < 0) { + trace_backup_do_cow_write_fail(job, start, ret); +@@ -199,7 +208,9 @@ + { + BackupBlockJob *s = container_of(job, BackupBlockJob, common); + +- bdrv_iostatus_reset(s->target); ++ if (s->target) { ++ bdrv_iostatus_reset(s->target); ++ } + } + + static const BlockJobDriver backup_job_driver = { +@@ -215,9 +226,11 @@ + if (read) { + return block_job_error_action(&job->common, job->common.bs, + job->on_source_error, true, error); +- } else { ++ } else if (job->target) { + return block_job_error_action(&job->common, job->target, + job->on_target_error, false, error); ++ } else { ++ return BDRV_ACTION_REPORT; + } + } + +@@ -242,9 +255,11 @@ + + job->bitmap = hbitmap_alloc(end, 0); + +- bdrv_set_enable_write_cache(target, true); +- bdrv_set_on_error(target, on_target_error, on_target_error); +- bdrv_iostatus_enable(target); ++ if (target) { ++ bdrv_set_enable_write_cache(target, true); ++ bdrv_set_on_error(target, on_target_error, on_target_error); ++ bdrv_iostatus_enable(target); ++ } + + bdrv_add_before_write_notifier(bs, &before_write); + +@@ -337,8 +352,10 @@ + + hbitmap_free(job->bitmap); + +- bdrv_iostatus_disable(target); +- bdrv_unref(target); ++ if (target) { ++ bdrv_iostatus_disable(target); ++ bdrv_unref(target); ++ } + + block_job_completed(&job->common, ret); + } +@@ -347,13 +364,14 @@ + int64_t speed, MirrorSyncMode sync_mode, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, ++ BackupDumpFunc *dump_cb, + BlockDriverCompletionFunc *cb, void *opaque, + Error **errp) + { + int64_t len; + + assert(bs); +- assert(target); ++ assert(target || dump_cb); + assert(cb); + + if ((on_source_error == BLOCKDEV_ON_ERROR_STOP || +@@ -376,6 +394,7 @@ + return; + } + ++ job->dump_cb = dump_cb; + job->on_source_error = on_source_error; + job->on_target_error = on_target_error; + job->target = target; +Index: new/blockdev.c +=================================================================== +--- new.orig/blockdev.c 2013-12-02 06:12:24.000000000 +0100 ++++ new/blockdev.c 2013-12-02 08:43:25.000000000 +0100 +@@ -1932,7 +1932,7 @@ + } + + backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error, +- block_job_cb, bs, &local_err); ++ NULL, block_job_cb, bs, &local_err); + if (local_err != NULL) { + bdrv_unref(target_bs); + error_propagate(errp, local_err); +Index: new/include/block/block_int.h +=================================================================== +--- new.orig/include/block/block_int.h 2013-11-27 10:51:33.000000000 +0100 ++++ new/include/block/block_int.h 2013-12-02 07:40:35.000000000 +0100 +@@ -54,6 +54,9 @@ + #define BLOCK_OPT_LAZY_REFCOUNTS "lazy_refcounts" + #define BLOCK_OPT_ADAPTER_TYPE "adapter_type" + ++typedef int BackupDumpFunc(void *opaque, BlockDriverState *bs, ++ int64_t cluster_num, unsigned char *buf); ++ + typedef struct BdrvTrackedRequest { + BlockDriverState *bs; + int64_t sector_num; +@@ -427,6 +430,7 @@ + int64_t speed, MirrorSyncMode sync_mode, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, ++ BackupDumpFunc *dump_cb, + BlockDriverCompletionFunc *cb, void *opaque, + Error **errp); + diff --git a/debian/patches/series b/debian/patches/series index c22e92e..10f323d 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -27,6 +27,6 @@ modify-query-spice.patch spice-use-pve-certs.patch backup-add-vma-binary.patch backup-add-vma-verify-command.patch - - backup-vma-restore-tolerate-a-size-difference-up-to-4M.patch +backup-modify-job-api.patch +backup-add-pve-monitor-commands.patch -- 2.39.2