aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcelo Tosatti <mtosatti@redhat.com>2012-01-18 14:40:53 +0000
committerKevin Wolf <kwolf@redhat.com>2012-01-26 14:49:18 +0100
commitc8c3080f4a6fbbf3c9c5d6efd1b49e7ca6479d1e (patch)
treeb034c08322e2c0f1d7bcbf752ff8c123b7a68213
parent019b8cbf76fc3126d300c401acca3102c69e7876 (diff)
block: add support for partial streaming
Add support for streaming data from an intermediate section of the image chain (see patch and documentation for details). Signed-off-by: Marcelo Tosatti <mtosatti@redhat.com> Signed-off-by: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
-rw-r--r--block/stream.c91
-rw-r--r--block_int.h3
-rw-r--r--blockdev.c11
3 files changed, 96 insertions, 9 deletions
diff --git a/block/stream.c b/block/stream.c
index b54b0b73c6..d1b3986a8a 100644
--- a/block/stream.c
+++ b/block/stream.c
@@ -57,6 +57,7 @@ typedef struct StreamBlockJob {
BlockJob common;
RateLimit limit;
BlockDriverState *base;
+ char backing_file_id[1024];
} StreamBlockJob;
static int coroutine_fn stream_populate(BlockDriverState *bs,
@@ -75,10 +76,76 @@ static int coroutine_fn stream_populate(BlockDriverState *bs,
return bdrv_co_copy_on_readv(bs, sector_num, nb_sectors, &qiov);
}
+/*
+ * Given an image chain: [BASE] -> [INTER1] -> [INTER2] -> [TOP]
+ *
+ * Return true if the given sector is allocated in top.
+ * Return false if the given sector is allocated in intermediate images.
+ * Return true otherwise.
+ *
+ * 'pnum' is set to the number of sectors (including and immediately following
+ * the specified sector) that are known to be in the same
+ * allocated/unallocated state.
+ *
+ */
+static int coroutine_fn is_allocated_base(BlockDriverState *top,
+ BlockDriverState *base,
+ int64_t sector_num,
+ int nb_sectors, int *pnum)
+{
+ BlockDriverState *intermediate;
+ int ret, n;
+
+ ret = bdrv_co_is_allocated(top, sector_num, nb_sectors, &n);
+ if (ret) {
+ *pnum = n;
+ return ret;
+ }
+
+ /*
+ * Is the unallocated chunk [sector_num, n] also
+ * unallocated between base and top?
+ */
+ intermediate = top->backing_hd;
+
+ while (intermediate) {
+ int pnum_inter;
+
+ /* reached base */
+ if (intermediate == base) {
+ *pnum = n;
+ return 1;
+ }
+ ret = bdrv_co_is_allocated(intermediate, sector_num, nb_sectors,
+ &pnum_inter);
+ if (ret < 0) {
+ return ret;
+ } else if (ret) {
+ *pnum = pnum_inter;
+ return 0;
+ }
+
+ /*
+ * [sector_num, nb_sectors] is unallocated on top but intermediate
+ * might have
+ *
+ * [sector_num+x, nr_sectors] allocated.
+ */
+ if (n > pnum_inter) {
+ n = pnum_inter;
+ }
+
+ intermediate = intermediate->backing_hd;
+ }
+
+ return 1;
+}
+
static void coroutine_fn stream_run(void *opaque)
{
StreamBlockJob *s = opaque;
BlockDriverState *bs = s->common.bs;
+ BlockDriverState *base = s->base;
int64_t sector_num, end;
int ret = 0;
int n;
@@ -108,8 +175,15 @@ retry:
break;
}
- ret = bdrv_co_is_allocated(bs, sector_num,
- STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE, &n);
+
+ if (base) {
+ ret = is_allocated_base(bs, base, sector_num,
+ STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE, &n);
+ } else {
+ ret = bdrv_co_is_allocated(bs, sector_num,
+ STREAM_BUFFER_SIZE / BDRV_SECTOR_SIZE,
+ &n);
+ }
trace_stream_one_iteration(s, sector_num, n, ret);
if (ret == 0) {
if (s->common.speed) {
@@ -126,6 +200,7 @@ retry:
if (ret < 0) {
break;
}
+ ret = 0;
/* Publish progress */
s->common.offset += n * BDRV_SECTOR_SIZE;
@@ -141,7 +216,11 @@ retry:
}
if (sector_num == end && ret == 0) {
- ret = bdrv_change_backing_file(bs, NULL, NULL);
+ const char *base_id = NULL;
+ if (base) {
+ base_id = s->backing_file_id;
+ }
+ ret = bdrv_change_backing_file(bs, base_id, NULL);
}
qemu_vfree(buf);
@@ -167,7 +246,8 @@ static BlockJobType stream_job_type = {
};
int stream_start(BlockDriverState *bs, BlockDriverState *base,
- BlockDriverCompletionFunc *cb, void *opaque)
+ const char *base_id, BlockDriverCompletionFunc *cb,
+ void *opaque)
{
StreamBlockJob *s;
Coroutine *co;
@@ -178,6 +258,9 @@ int stream_start(BlockDriverState *bs, BlockDriverState *base,
}
s->base = base;
+ if (base_id) {
+ pstrcpy(s->backing_file_id, sizeof(s->backing_file_id), base_id);
+ }
co = qemu_coroutine_create(stream_run);
trace_stream_start(bs, base, s, co, opaque);
diff --git a/block_int.h b/block_int.h
index 4f638705e0..7be2988ca7 100644
--- a/block_int.h
+++ b/block_int.h
@@ -328,6 +328,7 @@ void block_job_cancel(BlockJob *job);
bool block_job_is_cancelled(BlockJob *job);
int stream_start(BlockDriverState *bs, BlockDriverState *base,
- BlockDriverCompletionFunc *cb, void *opaque);
+ const char *base_id, BlockDriverCompletionFunc *cb,
+ void *opaque);
#endif /* BLOCK_INT_H */
diff --git a/blockdev.c b/blockdev.c
index 2fa01516f9..7e4c548426 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -973,6 +973,7 @@ void qmp_block_stream(const char *device, bool has_base,
const char *base, Error **errp)
{
BlockDriverState *bs;
+ BlockDriverState *base_bs = NULL;
int ret;
bs = bdrv_find(device);
@@ -981,13 +982,15 @@ void qmp_block_stream(const char *device, bool has_base,
return;
}
- /* Base device not supported */
if (base) {
- error_set(errp, QERR_NOT_SUPPORTED);
- return;
+ base_bs = bdrv_find_backing_image(bs, base);
+ if (base_bs == NULL) {
+ error_set(errp, QERR_BASE_NOT_FOUND, base);
+ return;
+ }
}
- ret = stream_start(bs, NULL, block_stream_cb, bs);
+ ret = stream_start(bs, base_bs, base, block_stream_cb, bs);
if (ret < 0) {
switch (ret) {
case -EBUSY: