Error management is important for mirroring; otherwise, an error on the target (even something as "innocent" as ENOSPC) requires to start again with a full copy. Similar to on_read_error/on_write_error, two separate knobs are provided for on_source_error (reads) and on_target_error (writes). The default is 'report' for both.
The 'ignore' policy will leave the sector dirty, so that it will be retried later. Thus, it will not cause corruption. Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> --- block/mirror.c | 50 +++++++++++++++++++++++++++++++++++++++++++++----- block_int.h | 4 ++++ blockdev.c | 14 ++++++++++++-- hmp.c | 3 ++- qapi-schema.json | 12 +++++++++++- qmp-commands.hx | 8 +++++++- 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/block/mirror.c b/block/mirror.c index 4454ef3..fb54d27 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -32,13 +32,15 @@ typedef struct MirrorBlockJob { RateLimit limit; BlockDriverState *target; MirrorSyncMode mode; + BlockdevOnError on_source_error, on_target_error; bool synced; bool complete; int64_t sector_num; uint8_t *buf; } MirrorBlockJob; -static int coroutine_fn mirror_iteration(MirrorBlockJob *s) +static int coroutine_fn mirror_iteration(MirrorBlockJob *s, + BlockErrorAction *p_action) { BlockDriverState *source = s->common.bs; BlockDriverState *target = s->target; @@ -60,9 +62,24 @@ static int coroutine_fn mirror_iteration(MirrorBlockJob *s) trace_mirror_one_iteration(s, s->sector_num, nb_sectors); ret = bdrv_co_readv(source, s->sector_num, nb_sectors, &qiov); if (ret < 0) { - return ret; + *p_action = block_job_error_action(&s->common, source, + s->on_source_error, true, -ret); + s->synced = false; + goto fail; } - return bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov); + ret = bdrv_co_writev(target, s->sector_num, nb_sectors, &qiov); + if (ret < 0) { + *p_action = block_job_error_action(&s->common, target, + s->on_target_error, false, -ret); + s->synced = false; + goto fail; + } + return 0; + +fail: + /* Try again later. */ + bdrv_set_dirty(source, s->sector_num, nb_sectors); + return ret; } static void coroutine_fn mirror_run(void *opaque) @@ -118,8 +135,9 @@ static void coroutine_fn mirror_run(void *opaque) cnt = bdrv_get_dirty_count(bs); if (cnt != 0) { - ret = mirror_iteration(s); - if (ret < 0) { + BlockErrorAction action = BDRV_ACTION_REPORT; + ret = mirror_iteration(s, &action); + if (ret < 0 && action == BDRV_ACTION_REPORT) { break; } cnt = bdrv_get_dirty_count(bs); @@ -193,6 +211,7 @@ static void coroutine_fn mirror_run(void *opaque) immediate_exit: g_free(s->buf); bdrv_set_dirty_tracking(bs, false); + bdrv_iostatus_disable(s->target); if (s->complete && ret == 0) { bdrv_swap(s->target, s->common.bs); } else { @@ -213,6 +232,13 @@ static void mirror_set_speed(BlockJob *job, int64_t speed, Error **errp) ratelimit_set_speed(&s->limit, speed / BDRV_SECTOR_SIZE, SLICE_TIME); } +static void mirror_iostatus_reset(BlockJob *job) +{ + MirrorBlockJob *s = container_of(job, MirrorBlockJob, common); + + bdrv_iostatus_reset(s->target); +} + static void mirror_query(BlockJob *job, BlockJobInfo *info) { MirrorBlockJob *s = container_of(job, MirrorBlockJob, common); @@ -249,25 +275,39 @@ static BlockJobType mirror_job_type = { .instance_size = sizeof(MirrorBlockJob), .job_type = "mirror", .set_speed = mirror_set_speed, + .iostatus_reset= mirror_iostatus_reset, .query = mirror_query, .complete = mirror_complete, }; void mirror_start(BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode mode, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, BlockDriverCompletionFunc *cb, void *opaque, Error **errp) { MirrorBlockJob *s; + if ((on_source_error == BLOCKDEV_ON_ERROR_STOP || + on_source_error == BLOCKDEV_ON_ERROR_ENOSPC) && + !bdrv_iostatus_is_enabled(bs)) { + error_set(errp, QERR_INVALID_PARAMETER, "on-source-error"); + return; + } + s = block_job_create(&mirror_job_type, bs, speed, cb, opaque, errp); if (!s) { return; } + s->on_source_error = on_source_error; + s->on_target_error = on_target_error; s->target = target; s->mode = mode; bdrv_set_dirty_tracking(bs, true); + bdrv_set_on_error(s->target, on_target_error, on_target_error); + bdrv_iostatus_enable(s->target); s->common.co = qemu_coroutine_create(mirror_run); trace_mirror_start(bs, s, s->common.co, opaque); qemu_coroutine_enter(s->common.co, s); diff --git a/block_int.h b/block_int.h index 714f939..0de2fdc 100644 --- a/block_int.h +++ b/block_int.h @@ -311,6 +311,8 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base, * @target: Block device to write to. * @speed: The maximum speed, in bytes per second, or 0 for unlimited. * @mode: Whether to collapse all images in the chain to the target. + * @on_source_error: The action to take upon error reading from the source. + * @on_target_error: The action to take upon error writing to the target. * @cb: Completion function for the job. * @opaque: Opaque pointer value passed to @cb. * @errp: Error object. @@ -322,6 +324,8 @@ void stream_start(BlockDriverState *bs, BlockDriverState *base, */ void mirror_start(BlockDriverState *bs, BlockDriverState *target, int64_t speed, MirrorSyncMode mode, + BlockdevOnError on_source_error, + BlockdevOnError on_target_error, BlockDriverCompletionFunc *cb, void *opaque, Error **errp); diff --git a/blockdev.c b/blockdev.c index 4b4574a..eb528cd 100644 --- a/blockdev.c +++ b/blockdev.c @@ -831,7 +831,10 @@ void qmp_drive_mirror(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync, bool has_mode, enum NewImageMode mode, - bool has_speed, int64_t speed, Error **errp) + bool has_speed, int64_t speed, + bool has_on_source_error, BlockdevOnError on_source_error, + bool has_on_target_error, BlockdevOnError on_target_error, + Error **errp) { BlockDriverInfo bdi; BlockDriverState *bs; @@ -846,6 +849,12 @@ void qmp_drive_mirror(const char *device, const char *target, if (!has_speed) { speed = 0; } + if (!has_on_source_error) { + on_source_error = BLOCKDEV_ON_ERROR_REPORT; + } + if (!has_on_target_error) { + on_target_error = BLOCKDEV_ON_ERROR_REPORT; + } if (!has_mode) { mode = NEW_IMAGE_MODE_ABSOLUTE_PATHS; } @@ -938,7 +947,8 @@ void qmp_drive_mirror(const char *device, const char *target, } } - mirror_start(bs, target_bs, speed, sync, block_job_cb, bs, &local_err); + mirror_start(bs, target_bs, speed, sync, on_source_error, on_target_error, + block_job_cb, bs, &local_err); if (local_err != NULL) { bdrv_delete(target_bs); error_propagate(errp, local_err); diff --git a/hmp.c b/hmp.c index 1d6a606..b6bc263 100644 --- a/hmp.c +++ b/hmp.c @@ -719,7 +719,8 @@ void hmp_drive_mirror(Monitor *mon, const QDict *qdict) qmp_drive_mirror(device, filename, !!format, format, full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP, - true, mode, false, 0, &errp); + true, mode, false, 0, + false, 0, false, 0, &errp); hmp_handle_error(mon, &errp); } diff --git a/qapi-schema.json b/qapi-schema.json index f5c20ca..7a97fad 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1389,17 +1389,27 @@ # (all the disk, only the sectors allocated in the topmost image, or # only new I/O). # +# @on-source-error: #optional the action to take on an error on the source, +# default 'report'. 'stop' and 'enospc' can only be used +# if the block device supports io-status (see BlockInfo). +# +# @on-target-error: #optional the action to take on an error on the target, +# default 'report' (no limitations, since this applies to +# a different block device than @device). +# # Returns: nothing on success # If @device is not a valid block device, DeviceNotFound # If @target can't be opened, OpenFileFailed # If @format is invalid, InvalidBlockFormat +# If @on_source_error is not supported, InvalidParameter # # Since 1.2 ## { 'command': 'drive-mirror', 'data': { 'device': 'str', 'target': 'str', '*format': 'str', 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode', - '*speed': 'int' } } + '*speed': 'int', '*on-source-error': 'BlockdevOnError', + '*on-target-error': 'BlockdevOnError' } } ## # @migrate_cancel diff --git a/qmp-commands.hx b/qmp-commands.hx index 7a32bb6..5081b01 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -838,7 +838,8 @@ EQMP { .name = "drive-mirror", - .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?", + .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?," + "on-source-error:s?,on-target-error:s?", .mhandler.cmd_new = qmp_marshal_input_drive_mirror, }, @@ -866,6 +867,11 @@ Arguments: possibilities include "full" for all the disk, "top" for only the sectors allocated in the topmost image, or "none" to only replicate new I/O (MirrorSyncMode). +- "on-source-error": the action to take on an error on the source + (BlockdevOnError, default 'report') +- "on-target-error": the action to take on an error on the target + (BlockdevOnError, default 'report') + Example: -- 1.7.10.4