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>
This commit is contained in:
Marcelo Tosatti 2012-01-18 14:40:53 +00:00 committed by Kevin Wolf
parent 019b8cbf76
commit c8c3080f4a
3 changed files with 96 additions and 9 deletions

View File

@ -57,6 +57,7 @@ typedef struct StreamBlockJob {
BlockJob common; BlockJob common;
RateLimit limit; RateLimit limit;
BlockDriverState *base; BlockDriverState *base;
char backing_file_id[1024];
} StreamBlockJob; } StreamBlockJob;
static int coroutine_fn stream_populate(BlockDriverState *bs, 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); 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) static void coroutine_fn stream_run(void *opaque)
{ {
StreamBlockJob *s = opaque; StreamBlockJob *s = opaque;
BlockDriverState *bs = s->common.bs; BlockDriverState *bs = s->common.bs;
BlockDriverState *base = s->base;
int64_t sector_num, end; int64_t sector_num, end;
int ret = 0; int ret = 0;
int n; int n;
@ -108,8 +175,15 @@ retry:
break; 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); trace_stream_one_iteration(s, sector_num, n, ret);
if (ret == 0) { if (ret == 0) {
if (s->common.speed) { if (s->common.speed) {
@ -126,6 +200,7 @@ retry:
if (ret < 0) { if (ret < 0) {
break; break;
} }
ret = 0;
/* Publish progress */ /* Publish progress */
s->common.offset += n * BDRV_SECTOR_SIZE; s->common.offset += n * BDRV_SECTOR_SIZE;
@ -141,7 +216,11 @@ retry:
} }
if (sector_num == end && ret == 0) { 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); qemu_vfree(buf);
@ -167,7 +246,8 @@ static BlockJobType stream_job_type = {
}; };
int stream_start(BlockDriverState *bs, BlockDriverState *base, int stream_start(BlockDriverState *bs, BlockDriverState *base,
BlockDriverCompletionFunc *cb, void *opaque) const char *base_id, BlockDriverCompletionFunc *cb,
void *opaque)
{ {
StreamBlockJob *s; StreamBlockJob *s;
Coroutine *co; Coroutine *co;
@ -178,6 +258,9 @@ int stream_start(BlockDriverState *bs, BlockDriverState *base,
} }
s->base = 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); co = qemu_coroutine_create(stream_run);
trace_stream_start(bs, base, s, co, opaque); trace_stream_start(bs, base, s, co, opaque);

View File

@ -328,6 +328,7 @@ void block_job_cancel(BlockJob *job);
bool block_job_is_cancelled(BlockJob *job); bool block_job_is_cancelled(BlockJob *job);
int stream_start(BlockDriverState *bs, BlockDriverState *base, int stream_start(BlockDriverState *bs, BlockDriverState *base,
BlockDriverCompletionFunc *cb, void *opaque); const char *base_id, BlockDriverCompletionFunc *cb,
void *opaque);
#endif /* BLOCK_INT_H */ #endif /* BLOCK_INT_H */

View File

@ -973,6 +973,7 @@ void qmp_block_stream(const char *device, bool has_base,
const char *base, Error **errp) const char *base, Error **errp)
{ {
BlockDriverState *bs; BlockDriverState *bs;
BlockDriverState *base_bs = NULL;
int ret; int ret;
bs = bdrv_find(device); bs = bdrv_find(device);
@ -981,13 +982,15 @@ void qmp_block_stream(const char *device, bool has_base,
return; return;
} }
/* Base device not supported */
if (base) { if (base) {
error_set(errp, QERR_NOT_SUPPORTED); base_bs = bdrv_find_backing_image(bs, base);
return; 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) { if (ret < 0) {
switch (ret) { switch (ret) {
case -EBUSY: case -EBUSY: