aboutsummaryrefslogtreecommitdiff
path: root/block/commit.c
diff options
context:
space:
mode:
Diffstat (limited to 'block/commit.c')
-rw-r--r--block/commit.c176
1 files changed, 153 insertions, 23 deletions
diff --git a/block/commit.c b/block/commit.c
index c284e8535d..22a0a4db98 100644
--- a/block/commit.c
+++ b/block/commit.c
@@ -36,6 +36,7 @@ typedef struct CommitBlockJob {
BlockJob common;
RateLimit limit;
BlockDriverState *active;
+ BlockDriverState *commit_top_bs;
BlockBackend *top;
BlockBackend *base;
BlockdevOnError on_error;
@@ -83,12 +84,23 @@ static void commit_complete(BlockJob *job, void *opaque)
BlockDriverState *active = s->active;
BlockDriverState *top = blk_bs(s->top);
BlockDriverState *base = blk_bs(s->base);
- BlockDriverState *overlay_bs = bdrv_find_overlay(active, top);
+ BlockDriverState *overlay_bs = bdrv_find_overlay(active, s->commit_top_bs);
int ret = data->ret;
+ bool remove_commit_top_bs = false;
+
+ /* Remove base node parent that still uses BLK_PERM_WRITE/RESIZE before
+ * the normal backing chain can be restored. */
+ blk_unref(s->base);
if (!block_job_is_cancelled(&s->common) && ret == 0) {
/* success */
- ret = bdrv_drop_intermediate(active, top, base, s->backing_file_str);
+ ret = bdrv_drop_intermediate(active, s->commit_top_bs, base,
+ s->backing_file_str);
+ } else if (overlay_bs) {
+ /* XXX Can (or should) we somehow keep 'consistent read' blocked even
+ * after the failed/cancelled commit job is gone? If we already wrote
+ * something to base, the intermediate images aren't valid any more. */
+ remove_commit_top_bs = true;
}
/* restore base open flags here if appropriate (e.g., change the base back
@@ -102,9 +114,15 @@ static void commit_complete(BlockJob *job, void *opaque)
}
g_free(s->backing_file_str);
blk_unref(s->top);
- blk_unref(s->base);
block_job_completed(&s->common, ret);
g_free(data);
+
+ /* If bdrv_drop_intermediate() didn't already do that, remove the commit
+ * filter driver from the backing chain. Do this as the final step so that
+ * the 'consistent read' permission can be granted. */
+ if (remove_commit_top_bs) {
+ bdrv_set_backing_hd(overlay_bs, top, &error_abort);
+ }
}
static void coroutine_fn commit_run(void *opaque)
@@ -208,10 +226,38 @@ static const BlockJobDriver commit_job_driver = {
.start = commit_run,
};
+static int coroutine_fn bdrv_commit_top_preadv(BlockDriverState *bs,
+ uint64_t offset, uint64_t bytes, QEMUIOVector *qiov, int flags)
+{
+ return bdrv_co_preadv(bs->backing, offset, bytes, qiov, flags);
+}
+
+static void bdrv_commit_top_close(BlockDriverState *bs)
+{
+}
+
+static void bdrv_commit_top_child_perm(BlockDriverState *bs, BdrvChild *c,
+ const BdrvChildRole *role,
+ uint64_t perm, uint64_t shared,
+ uint64_t *nperm, uint64_t *nshared)
+{
+ *nperm = 0;
+ *nshared = BLK_PERM_ALL;
+}
+
+/* Dummy node that provides consistent read to its users without requiring it
+ * from its backing file and that allows writes on the backing file chain. */
+static BlockDriver bdrv_commit_top = {
+ .format_name = "commit_top",
+ .bdrv_co_preadv = bdrv_commit_top_preadv,
+ .bdrv_close = bdrv_commit_top_close,
+ .bdrv_child_perm = bdrv_commit_top_child_perm,
+};
+
void commit_start(const char *job_id, BlockDriverState *bs,
BlockDriverState *base, BlockDriverState *top, int64_t speed,
BlockdevOnError on_error, const char *backing_file_str,
- Error **errp)
+ const char *filter_node_name, Error **errp)
{
CommitBlockJob *s;
BlockReopenQueue *reopen_queue = NULL;
@@ -219,7 +265,9 @@ void commit_start(const char *job_id, BlockDriverState *bs,
int orig_base_flags;
BlockDriverState *iter;
BlockDriverState *overlay_bs;
+ BlockDriverState *commit_top_bs = NULL;
Error *local_err = NULL;
+ int ret;
assert(top != bs);
if (top == base) {
@@ -234,8 +282,8 @@ void commit_start(const char *job_id, BlockDriverState *bs,
return;
}
- s = block_job_create(job_id, &commit_job_driver, bs, speed,
- BLOCK_JOB_DEFAULT, NULL, NULL, errp);
+ s = block_job_create(job_id, &commit_job_driver, bs, 0, BLK_PERM_ALL,
+ speed, BLOCK_JOB_DEFAULT, NULL, NULL, errp);
if (!s) {
return;
}
@@ -256,30 +304,70 @@ void commit_start(const char *job_id, BlockDriverState *bs,
bdrv_reopen_multiple(bdrv_get_aio_context(bs), reopen_queue, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
- block_job_unref(&s->common);
- return;
+ goto fail;
}
}
+ /* Insert commit_top block node above top, so we can block consistent read
+ * on the backing chain below it */
+ commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, filter_node_name, 0,
+ errp);
+ if (commit_top_bs == NULL) {
+ goto fail;
+ }
+
+ bdrv_set_backing_hd(commit_top_bs, top, &error_abort);
+ bdrv_set_backing_hd(overlay_bs, commit_top_bs, &error_abort);
+
+ s->commit_top_bs = commit_top_bs;
+ bdrv_unref(commit_top_bs);
/* Block all nodes between top and base, because they will
* disappear from the chain after this operation. */
assert(bdrv_chain_contains(top, base));
- for (iter = top; iter != backing_bs(base); iter = backing_bs(iter)) {
- block_job_add_bdrv(&s->common, iter);
+ for (iter = top; iter != base; iter = backing_bs(iter)) {
+ /* XXX BLK_PERM_WRITE needs to be allowed so we don't block ourselves
+ * at s->base (if writes are blocked for a node, they are also blocked
+ * for its backing file). The other options would be a second filter
+ * driver above s->base. */
+ ret = block_job_add_bdrv(&s->common, "intermediate node", iter, 0,
+ BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE,
+ errp);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
+ ret = block_job_add_bdrv(&s->common, "base", base, 0, BLK_PERM_ALL, errp);
+ if (ret < 0) {
+ goto fail;
}
+
/* overlay_bs must be blocked because it needs to be modified to
- * update the backing image string, but if it's the root node then
- * don't block it again */
- if (bs != overlay_bs) {
- block_job_add_bdrv(&s->common, overlay_bs);
+ * update the backing image string. */
+ ret = block_job_add_bdrv(&s->common, "overlay of top", overlay_bs,
+ BLK_PERM_GRAPH_MOD, BLK_PERM_ALL, errp);
+ if (ret < 0) {
+ goto fail;
}
- s->base = blk_new();
- blk_insert_bs(s->base, base);
+ s->base = blk_new(BLK_PERM_CONSISTENT_READ
+ | BLK_PERM_WRITE
+ | BLK_PERM_RESIZE,
+ BLK_PERM_CONSISTENT_READ
+ | BLK_PERM_GRAPH_MOD
+ | BLK_PERM_WRITE_UNCHANGED);
+ ret = blk_insert_bs(s->base, base, errp);
+ if (ret < 0) {
+ goto fail;
+ }
- s->top = blk_new();
- blk_insert_bs(s->top, top);
+ /* Required permissions are already taken with block_job_add_bdrv() */
+ s->top = blk_new(0, BLK_PERM_ALL);
+ blk_insert_bs(s->top, top, errp);
+ if (ret < 0) {
+ goto fail;
+ }
s->active = bs;
@@ -292,6 +380,19 @@ void commit_start(const char *job_id, BlockDriverState *bs,
trace_commit_start(bs, base, top, s);
block_job_start(&s->common);
+ return;
+
+fail:
+ if (s->base) {
+ blk_unref(s->base);
+ }
+ if (s->top) {
+ blk_unref(s->top);
+ }
+ if (commit_top_bs) {
+ bdrv_set_backing_hd(overlay_bs, top, &error_abort);
+ }
+ block_job_unref(&s->common);
}
@@ -301,11 +402,14 @@ void commit_start(const char *job_id, BlockDriverState *bs,
int bdrv_commit(BlockDriverState *bs)
{
BlockBackend *src, *backing;
+ BlockDriverState *backing_file_bs = NULL;
+ BlockDriverState *commit_top_bs = NULL;
BlockDriver *drv = bs->drv;
int64_t sector, total_sectors, length, backing_length;
int n, ro, open_flags;
int ret = 0;
uint8_t *buf = NULL;
+ Error *local_err = NULL;
if (!drv)
return -ENOMEDIUM;
@@ -328,11 +432,33 @@ int bdrv_commit(BlockDriverState *bs)
}
}
- src = blk_new();
- blk_insert_bs(src, bs);
+ src = blk_new(BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL);
+ backing = blk_new(BLK_PERM_WRITE | BLK_PERM_RESIZE, BLK_PERM_ALL);
- backing = blk_new();
- blk_insert_bs(backing, bs->backing->bs);
+ ret = blk_insert_bs(src, bs, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ goto ro_cleanup;
+ }
+
+ /* Insert commit_top block node above backing, so we can write to it */
+ backing_file_bs = backing_bs(bs);
+
+ commit_top_bs = bdrv_new_open_driver(&bdrv_commit_top, NULL, BDRV_O_RDWR,
+ &local_err);
+ if (commit_top_bs == NULL) {
+ error_report_err(local_err);
+ goto ro_cleanup;
+ }
+
+ bdrv_set_backing_hd(commit_top_bs, backing_file_bs, &error_abort);
+ bdrv_set_backing_hd(bs, commit_top_bs, &error_abort);
+
+ ret = blk_insert_bs(backing, backing_file_bs, &local_err);
+ if (ret < 0) {
+ error_report_err(local_err);
+ goto ro_cleanup;
+ }
length = blk_getlength(src);
if (length < 0) {
@@ -404,8 +530,12 @@ int bdrv_commit(BlockDriverState *bs)
ro_cleanup:
qemu_vfree(buf);
- blk_unref(src);
blk_unref(backing);
+ if (backing_file_bs) {
+ bdrv_set_backing_hd(bs, backing_file_bs, &error_abort);
+ }
+ bdrv_unref(commit_top_bs);
+ blk_unref(src);
if (ro) {
/* ignoring error return here */