diff --git a/block/stream.c b/block/stream.c index 310ccbaa4c..8ce6729a33 100644 --- a/block/stream.c +++ b/block/stream.c @@ -31,7 +31,8 @@ enum { typedef struct StreamBlockJob { BlockJob common; - BlockDriverState *bottom; + BlockDriverState *base_overlay; /* COW overlay (stream from this) */ + BlockDriverState *above_base; /* Node directly above the base */ BlockdevOnError on_error; char *backing_file_str; bool bs_read_only; @@ -53,7 +54,7 @@ static void stream_abort(Job *job) if (s->chain_frozen) { BlockJob *bjob = &s->common; - bdrv_unfreeze_backing_chain(blk_bs(bjob->blk), s->bottom); + bdrv_unfreeze_backing_chain(blk_bs(bjob->blk), s->above_base); } } @@ -62,14 +63,15 @@ static int stream_prepare(Job *job) StreamBlockJob *s = container_of(job, StreamBlockJob, common.job); BlockJob *bjob = &s->common; BlockDriverState *bs = blk_bs(bjob->blk); - BlockDriverState *base = backing_bs(s->bottom); + BlockDriverState *unfiltered_bs = bdrv_skip_filters(bs); + BlockDriverState *base = bdrv_filter_or_cow_bs(s->above_base); Error *local_err = NULL; int ret = 0; - bdrv_unfreeze_backing_chain(bs, s->bottom); + bdrv_unfreeze_backing_chain(bs, s->above_base); s->chain_frozen = false; - if (bs->backing) { + if (bdrv_cow_child(unfiltered_bs)) { const char *base_id = NULL, *base_fmt = NULL; if (base) { base_id = s->backing_file_str; @@ -77,8 +79,8 @@ static int stream_prepare(Job *job) base_fmt = base->drv->format_name; } } - bdrv_set_backing_hd(bs, base, &local_err); - ret = bdrv_change_backing_file(bs, base_id, base_fmt, false); + bdrv_set_backing_hd(unfiltered_bs, base, &local_err); + ret = bdrv_change_backing_file(unfiltered_bs, base_id, base_fmt, false); if (local_err) { error_report_err(local_err); return -EPERM; @@ -109,14 +111,15 @@ static int coroutine_fn stream_run(Job *job, Error **errp) StreamBlockJob *s = container_of(job, StreamBlockJob, common.job); BlockBackend *blk = s->common.blk; BlockDriverState *bs = blk_bs(blk); - bool enable_cor = !backing_bs(s->bottom); + BlockDriverState *unfiltered_bs = bdrv_skip_filters(bs); + bool enable_cor = !bdrv_cow_child(s->base_overlay); int64_t len; int64_t offset = 0; uint64_t delay_ns = 0; int error = 0; int64_t n = 0; /* bytes */ - if (bs == s->bottom) { + if (unfiltered_bs == s->base_overlay) { /* Nothing to stream */ return 0; } @@ -150,13 +153,14 @@ static int coroutine_fn stream_run(Job *job, Error **errp) copy = false; - ret = bdrv_is_allocated(bs, offset, STREAM_CHUNK, &n); + ret = bdrv_is_allocated(unfiltered_bs, offset, STREAM_CHUNK, &n); if (ret == 1) { /* Allocated in the top, no need to copy. */ } else if (ret >= 0) { /* Copy if allocated in the intermediate images. Limit to the * known-unallocated area [offset, offset+n*BDRV_SECTOR_SIZE). */ - ret = bdrv_is_allocated_above(backing_bs(bs), s->bottom, true, + ret = bdrv_is_allocated_above(bdrv_cow_bs(unfiltered_bs), + s->base_overlay, true, offset, n, &n); /* Finish early if end of backing file has been reached */ if (ret == 0 && n == 0) { @@ -223,9 +227,29 @@ void stream_start(const char *job_id, BlockDriverState *bs, BlockDriverState *iter; bool bs_read_only; int basic_flags = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED; - BlockDriverState *bottom = bdrv_find_overlay(bs, base); + BlockDriverState *base_overlay = bdrv_find_overlay(bs, base); + BlockDriverState *above_base; - if (bdrv_freeze_backing_chain(bs, bottom, errp) < 0) { + if (!base_overlay) { + error_setg(errp, "'%s' is not in the backing chain of '%s'", + base->node_name, bs->node_name); + return; + } + + /* + * Find the node directly above @base. @base_overlay is a COW overlay, so + * it must have a bdrv_cow_child(), but it is the immediate overlay of + * @base, so between the two there can only be filters. + */ + above_base = base_overlay; + if (bdrv_cow_bs(above_base) != base) { + above_base = bdrv_cow_bs(above_base); + while (bdrv_filter_bs(above_base) != base) { + above_base = bdrv_filter_bs(above_base); + } + } + + if (bdrv_freeze_backing_chain(bs, above_base, errp) < 0) { return; } @@ -255,14 +279,19 @@ void stream_start(const char *job_id, BlockDriverState *bs, * and resizes. Reassign the base node pointer because the backing BS of the * bottom node might change after the call to bdrv_reopen_set_read_only() * due to parallel block jobs running. + * above_base node might change after the call to + * bdrv_reopen_set_read_only() due to parallel block jobs running. */ - base = backing_bs(bottom); - for (iter = backing_bs(bs); iter && iter != base; iter = backing_bs(iter)) { + base = bdrv_filter_or_cow_bs(above_base); + for (iter = bdrv_filter_or_cow_bs(bs); iter != base; + iter = bdrv_filter_or_cow_bs(iter)) + { block_job_add_bdrv(&s->common, "intermediate node", iter, 0, basic_flags, &error_abort); } - s->bottom = bottom; + s->base_overlay = base_overlay; + s->above_base = above_base; s->backing_file_str = g_strdup(backing_file_str); s->bs_read_only = bs_read_only; s->chain_frozen = true; @@ -276,5 +305,5 @@ fail: if (bs_read_only) { bdrv_reopen_set_read_only(bs, true, NULL); } - bdrv_unfreeze_backing_chain(bs, bottom); + bdrv_unfreeze_backing_chain(bs, above_base); } diff --git a/blockdev.c b/blockdev.c index 3848a9c8ab..862aa1b9aa 100644 --- a/blockdev.c +++ b/blockdev.c @@ -2528,7 +2528,9 @@ void qmp_block_stream(bool has_job_id, const char *job_id, const char *device, } /* Check for op blockers in the whole chain between bs and base */ - for (iter = bs; iter && iter != base_bs; iter = backing_bs(iter)) { + for (iter = bs; iter && iter != base_bs; + iter = bdrv_filter_or_cow_bs(iter)) + { if (bdrv_op_is_blocked(iter, BLOCK_OP_TYPE_STREAM, errp)) { goto out; } diff --git a/qapi/block-core.json b/qapi/block-core.json index 55b58ba892..b04df13bea 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -2484,13 +2484,20 @@ # of 'device'. # # If a base file is specified then sectors are not copied from that base file and -# its backing chain. When streaming completes the image file will have the base -# file as its backing file. This can be used to stream a subset of the backing -# file chain instead of flattening the entire image. +# its backing chain. This can be used to stream a subset of the backing file +# chain instead of flattening the entire image. +# When streaming completes the image file will have the base file as its backing +# file, unless that node was changed while the job was running. In that case, +# base's parent's backing (or filtered, whichever exists) child (i.e., base at +# the beginning of the job) will be the new backing file. # # On successful completion the image file is updated to drop the backing file # and the BLOCK_JOB_COMPLETED event is emitted. # +# In case @device is a filter node, block-stream modifies the first non-filter +# overlay node below it to point to the new backing node instead of modifying +# @device itself. +# # @job-id: identifier for the newly-created block job. If # omitted, the device name will be used. (Since 2.7) #