7d4ca9d25b
Instead of taking the writer lock internally, require callers to already hold it when calling bdrv_attach_child_common(). These callers will typically already hold the graph lock once the locking work is completed, which means that they can't call functions that take it internally. Note that the transaction callbacks still take the lock internally, so tran_finalize() must be called without the lock held. This is because bdrv_append() also calls bdrv_replace_node_noperm(), which currently requires the transaction callbacks to be called unlocked. In the next step, both of them can be switched to locked tran_finalize() calls together. Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Message-ID: <20230911094620.45040-11-kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
402 lines
13 KiB
C
402 lines
13 KiB
C
/*
|
|
* Image streaming
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "trace.h"
|
|
#include "block/block_int.h"
|
|
#include "block/blockjob_int.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "qemu/ratelimit.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "block/copy-on-read.h"
|
|
|
|
enum {
|
|
/*
|
|
* Maximum chunk size to feed to copy-on-read. This should be
|
|
* large enough to process multiple clusters in a single call, so
|
|
* that populating contiguous regions of the image is efficient.
|
|
*/
|
|
STREAM_CHUNK = 512 * 1024, /* in bytes */
|
|
};
|
|
|
|
typedef struct StreamBlockJob {
|
|
BlockJob common;
|
|
BlockBackend *blk;
|
|
BlockDriverState *base_overlay; /* COW overlay (stream from this) */
|
|
BlockDriverState *above_base; /* Node directly above the base */
|
|
BlockDriverState *cor_filter_bs;
|
|
BlockDriverState *target_bs;
|
|
BlockdevOnError on_error;
|
|
char *backing_file_str;
|
|
bool bs_read_only;
|
|
} StreamBlockJob;
|
|
|
|
static int coroutine_fn stream_populate(BlockBackend *blk,
|
|
int64_t offset, uint64_t bytes)
|
|
{
|
|
assert(bytes < SIZE_MAX);
|
|
|
|
return blk_co_preadv(blk, offset, bytes, NULL, BDRV_REQ_PREFETCH);
|
|
}
|
|
|
|
static int stream_prepare(Job *job)
|
|
{
|
|
StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
|
|
BlockDriverState *unfiltered_bs = bdrv_skip_filters(s->target_bs);
|
|
BlockDriverState *unfiltered_bs_cow = bdrv_cow_bs(unfiltered_bs);
|
|
BlockDriverState *base;
|
|
BlockDriverState *unfiltered_base;
|
|
Error *local_err = NULL;
|
|
int ret = 0;
|
|
|
|
/* We should drop filter at this point, as filter hold the backing chain */
|
|
bdrv_cor_filter_drop(s->cor_filter_bs);
|
|
s->cor_filter_bs = NULL;
|
|
|
|
/*
|
|
* bdrv_set_backing_hd() requires that the unfiltered_bs and the COW child
|
|
* of unfiltered_bs is drained. Drain already here and use
|
|
* bdrv_set_backing_hd_drained() instead because the polling during
|
|
* drained_begin() might change the graph, and if we do this only later, we
|
|
* may end up working with the wrong base node (or it might even have gone
|
|
* away by the time we want to use it).
|
|
*/
|
|
bdrv_drained_begin(unfiltered_bs);
|
|
if (unfiltered_bs_cow) {
|
|
bdrv_ref(unfiltered_bs_cow);
|
|
bdrv_drained_begin(unfiltered_bs_cow);
|
|
}
|
|
|
|
base = bdrv_filter_or_cow_bs(s->above_base);
|
|
unfiltered_base = bdrv_skip_filters(base);
|
|
|
|
if (bdrv_cow_child(unfiltered_bs)) {
|
|
const char *base_id = NULL, *base_fmt = NULL;
|
|
if (unfiltered_base) {
|
|
base_id = s->backing_file_str ?: unfiltered_base->filename;
|
|
if (unfiltered_base->drv) {
|
|
base_fmt = unfiltered_base->drv->format_name;
|
|
}
|
|
}
|
|
|
|
bdrv_set_backing_hd_drained(unfiltered_bs, base, &local_err);
|
|
|
|
/*
|
|
* This call will do I/O, so the graph can change again from here on.
|
|
* We have already completed the graph change, so we are not in danger
|
|
* of operating on the wrong node any more if this happens.
|
|
*/
|
|
ret = bdrv_change_backing_file(unfiltered_bs, base_id, base_fmt, false);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
ret = -EPERM;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (unfiltered_bs_cow) {
|
|
bdrv_drained_end(unfiltered_bs_cow);
|
|
bdrv_unref(unfiltered_bs_cow);
|
|
}
|
|
bdrv_drained_end(unfiltered_bs);
|
|
return ret;
|
|
}
|
|
|
|
static void stream_clean(Job *job)
|
|
{
|
|
StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
|
|
|
|
if (s->cor_filter_bs) {
|
|
bdrv_cor_filter_drop(s->cor_filter_bs);
|
|
s->cor_filter_bs = NULL;
|
|
}
|
|
|
|
blk_unref(s->blk);
|
|
s->blk = NULL;
|
|
|
|
/* Reopen the image back in read-only mode if necessary */
|
|
if (s->bs_read_only) {
|
|
/* Give up write permissions before making it read-only */
|
|
bdrv_reopen_set_read_only(s->target_bs, true, NULL);
|
|
}
|
|
|
|
g_free(s->backing_file_str);
|
|
}
|
|
|
|
static int coroutine_fn stream_run(Job *job, Error **errp)
|
|
{
|
|
StreamBlockJob *s = container_of(job, StreamBlockJob, common.job);
|
|
BlockDriverState *unfiltered_bs = bdrv_skip_filters(s->target_bs);
|
|
int64_t len;
|
|
int64_t offset = 0;
|
|
int error = 0;
|
|
int64_t n = 0; /* bytes */
|
|
|
|
if (unfiltered_bs == s->base_overlay) {
|
|
/* Nothing to stream */
|
|
return 0;
|
|
}
|
|
|
|
WITH_GRAPH_RDLOCK_GUARD() {
|
|
len = bdrv_co_getlength(s->target_bs);
|
|
if (len < 0) {
|
|
return len;
|
|
}
|
|
}
|
|
job_progress_set_remaining(&s->common.job, len);
|
|
|
|
for ( ; offset < len; offset += n) {
|
|
bool copy;
|
|
int ret;
|
|
|
|
/* Note that even when no rate limit is applied we need to yield
|
|
* with no pending I/O here so that bdrv_drain_all() returns.
|
|
*/
|
|
block_job_ratelimit_sleep(&s->common);
|
|
if (job_is_cancelled(&s->common.job)) {
|
|
break;
|
|
}
|
|
|
|
copy = false;
|
|
|
|
WITH_GRAPH_RDLOCK_GUARD() {
|
|
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(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) {
|
|
n = len - offset;
|
|
}
|
|
|
|
copy = (ret > 0);
|
|
}
|
|
}
|
|
trace_stream_one_iteration(s, offset, n, ret);
|
|
if (copy) {
|
|
ret = stream_populate(s->blk, offset, n);
|
|
}
|
|
if (ret < 0) {
|
|
BlockErrorAction action =
|
|
block_job_error_action(&s->common, s->on_error, true, -ret);
|
|
if (action == BLOCK_ERROR_ACTION_STOP) {
|
|
n = 0;
|
|
continue;
|
|
}
|
|
if (error == 0) {
|
|
error = ret;
|
|
}
|
|
if (action == BLOCK_ERROR_ACTION_REPORT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Publish progress */
|
|
job_progress_update(&s->common.job, n);
|
|
if (copy) {
|
|
block_job_ratelimit_processed_bytes(&s->common, n);
|
|
}
|
|
}
|
|
|
|
/* Do not remove the backing file if an error was there but ignored. */
|
|
return error;
|
|
}
|
|
|
|
static const BlockJobDriver stream_job_driver = {
|
|
.job_driver = {
|
|
.instance_size = sizeof(StreamBlockJob),
|
|
.job_type = JOB_TYPE_STREAM,
|
|
.free = block_job_free,
|
|
.run = stream_run,
|
|
.prepare = stream_prepare,
|
|
.clean = stream_clean,
|
|
.user_resume = block_job_user_resume,
|
|
},
|
|
};
|
|
|
|
void stream_start(const char *job_id, BlockDriverState *bs,
|
|
BlockDriverState *base, const char *backing_file_str,
|
|
BlockDriverState *bottom,
|
|
int creation_flags, int64_t speed,
|
|
BlockdevOnError on_error,
|
|
const char *filter_node_name,
|
|
Error **errp)
|
|
{
|
|
StreamBlockJob *s = NULL;
|
|
BlockDriverState *iter;
|
|
bool bs_read_only;
|
|
int basic_flags = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE_UNCHANGED;
|
|
BlockDriverState *base_overlay;
|
|
BlockDriverState *cor_filter_bs = NULL;
|
|
BlockDriverState *above_base;
|
|
QDict *opts;
|
|
int ret;
|
|
|
|
GLOBAL_STATE_CODE();
|
|
|
|
assert(!(base && bottom));
|
|
assert(!(backing_file_str && bottom));
|
|
|
|
if (bottom) {
|
|
/*
|
|
* New simple interface. The code is written in terms of old interface
|
|
* with @base parameter (still, it doesn't freeze link to base, so in
|
|
* this mean old code is correct for new interface). So, for now, just
|
|
* emulate base_overlay and above_base. Still, when old interface
|
|
* finally removed, we should refactor code to use only "bottom", but
|
|
* not "*base*" things.
|
|
*/
|
|
assert(!bottom->drv->is_filter);
|
|
base_overlay = above_base = bottom;
|
|
} else {
|
|
base_overlay = bdrv_find_overlay(bs, base);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Make sure that the image is opened in read-write mode */
|
|
bs_read_only = bdrv_is_read_only(bs);
|
|
if (bs_read_only) {
|
|
int ret;
|
|
/* Hold the chain during reopen */
|
|
if (bdrv_freeze_backing_chain(bs, above_base, errp) < 0) {
|
|
return;
|
|
}
|
|
|
|
ret = bdrv_reopen_set_read_only(bs, false, errp);
|
|
|
|
/* failure, or cor-filter will hold the chain */
|
|
bdrv_unfreeze_backing_chain(bs, above_base);
|
|
|
|
if (ret < 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
opts = qdict_new();
|
|
|
|
qdict_put_str(opts, "driver", "copy-on-read");
|
|
qdict_put_str(opts, "file", bdrv_get_node_name(bs));
|
|
/* Pass the base_overlay node name as 'bottom' to COR driver */
|
|
qdict_put_str(opts, "bottom", base_overlay->node_name);
|
|
if (filter_node_name) {
|
|
qdict_put_str(opts, "node-name", filter_node_name);
|
|
}
|
|
|
|
cor_filter_bs = bdrv_insert_node(bs, opts, BDRV_O_RDWR, errp);
|
|
if (!cor_filter_bs) {
|
|
goto fail;
|
|
}
|
|
|
|
if (!filter_node_name) {
|
|
cor_filter_bs->implicit = true;
|
|
}
|
|
|
|
s = block_job_create(job_id, &stream_job_driver, NULL, cor_filter_bs,
|
|
0, BLK_PERM_ALL,
|
|
speed, creation_flags, NULL, NULL, errp);
|
|
if (!s) {
|
|
goto fail;
|
|
}
|
|
|
|
s->blk = blk_new_with_bs(cor_filter_bs, BLK_PERM_CONSISTENT_READ,
|
|
basic_flags | BLK_PERM_WRITE, errp);
|
|
if (!s->blk) {
|
|
goto fail;
|
|
}
|
|
/*
|
|
* Disable request queuing in the BlockBackend to avoid deadlocks on drain:
|
|
* The job reports that it's busy until it reaches a pause point.
|
|
*/
|
|
blk_set_disable_request_queuing(s->blk, true);
|
|
blk_set_allow_aio_context_change(s->blk, true);
|
|
|
|
/*
|
|
* Prevent concurrent jobs trying to modify the graph structure here, we
|
|
* already have our own plans. Also don't allow resize as the image size is
|
|
* queried only at the job start and then cached.
|
|
*/
|
|
if (block_job_add_bdrv(&s->common, "active node", bs, 0,
|
|
basic_flags | BLK_PERM_WRITE, errp)) {
|
|
goto fail;
|
|
}
|
|
|
|
/* Block all intermediate nodes between bs and base, because they will
|
|
* disappear from the chain after this operation. The streaming job reads
|
|
* every block only once, assuming that it doesn't change, so forbid writes
|
|
* 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 = bdrv_filter_or_cow_bs(above_base);
|
|
for (iter = bdrv_filter_or_cow_bs(bs); iter != base;
|
|
iter = bdrv_filter_or_cow_bs(iter))
|
|
{
|
|
ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
|
|
basic_flags, errp);
|
|
if (ret < 0) {
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
s->base_overlay = base_overlay;
|
|
s->above_base = above_base;
|
|
s->backing_file_str = g_strdup(backing_file_str);
|
|
s->cor_filter_bs = cor_filter_bs;
|
|
s->target_bs = bs;
|
|
s->bs_read_only = bs_read_only;
|
|
|
|
s->on_error = on_error;
|
|
trace_stream_start(bs, base, s);
|
|
job_start(&s->common.job);
|
|
return;
|
|
|
|
fail:
|
|
if (s) {
|
|
job_early_fail(&s->common.job);
|
|
}
|
|
if (cor_filter_bs) {
|
|
bdrv_cor_filter_drop(cor_filter_bs);
|
|
}
|
|
if (bs_read_only) {
|
|
bdrv_reopen_set_read_only(bs, true, NULL);
|
|
}
|
|
}
|