Block patches for 7.0-rc0:
- New fleecing backup scheme - iotest fixes - Fixes for the curl block driver - Fix for the preallocate block driver - IDE fix for zero-length TRIM requests -----BEGIN PGP SIGNATURE----- iQJGBAABCAAwFiEEy2LXoO44KeRfAE00ofpA0JgBnN8FAmIl33sSHGhyZWl0ekBy ZWRoYXQuY29tAAoJEKH6QNCYAZzfD60P/0xXwKmXiyq7GkoVdkZaI4ErMn29rnzl APoM6Q3GbMII6ipkqVIFM2BIhGDCl8X+KbzvDTm23jvXR7SAnjel+GBcFYTZ4/3r LbS/mW3SGPjEG6CldXjHDxD/k+3sw1wXOGW30+PhEkpokNSJKh3evsNxUA+liPQT MqrNWilh28S4CMM2YnHEJNX/e0WujYagE0PN4UKrr/+bw8mN+UrirCC/BwH0RsAz H+OVek/SaGUer4MAXNH+LVWwm3/F+sZQBqyLZc0ORavKnXEPlf/fKbGx5+IkV2ty h/GX+oxLSsfnXDVhqvP71RbSkLIOCsrSPzTqz3vicBmwtqaIzjor7wVS/vJlK1i5 cVEYa2te3Ccbpijb9VOBwTu+t8YB8x/Ot8S22p++uC0CY/Gf1paCe8uNqXHQYhfD Sv2Xw9vQsCIvB+7vmJ+iMSf5LFPO0I4Hh7zil6ecKgtxtkVLtqYa7t1UE/5cOUtO mF3mzS7bQhcU569z3+hZEBiCrH+rSNvHvnnAV/IFN4Y1PqSBk6uwoqHzUFZQq6Dr 6IdduqUmGs9ooDZuxKgShr81Sn18T6BkawcsQMMHOI4gKRik3l7kxUnPeTIYWi2T j0ihaZyH5rRfa80CBnN18Q+90buYb0OafKZ9y4/RDOScoHeR8Yx/RjUwKpn+6yGI TOUr+3PvGOPl =OH/S -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/hreitz-gitlab/tags/pull-block-2022-03-07' into staging Block patches for 7.0-rc0: - New fleecing backup scheme - iotest fixes - Fixes for the curl block driver - Fix for the preallocate block driver - IDE fix for zero-length TRIM requests # gpg: Signature made Mon 07 Mar 2022 10:33:31 GMT # gpg: using RSA key CB62D7A0EE3829E45F004D34A1FA40D098019CDF # gpg: issuer "hreitz@redhat.com" # gpg: Good signature from "Hanna Reitz <hreitz@redhat.com>" [marginal] # gpg: WARNING: This key is not certified with sufficiently trusted signatures! # gpg: It is not certain that the signature belongs to the owner. # Primary key fingerprint: CB62 D7A0 EE38 29E4 5F00 4D34 A1FA 40D0 9801 9CDF * remotes/hreitz-gitlab/tags/pull-block-2022-03-07: (23 commits) iotests/image-fleecing: test push backup with fleecing iotests/image-fleecing: add test case with bitmap iotests.py: add qemu_io_pipe_and_status() iotests/image-fleecing: add test-case for fleecing format node block: copy-before-write: realize snapshot-access API block: introduce snapshot-access block driver block/io: introduce block driver snapshot-access API block/reqlist: add reqlist_wait_all() block/dirty-bitmap: introduce bdrv_dirty_bitmap_status() block/reqlist: reqlist_find_conflict(): use ranges_overlap() block: intoduce reqlist block/block-copy: add block_copy_reset() block/copy-before-write: add bitmap open parameter block/block-copy: block_copy_state_new(): add bitmap parameter block/dirty-bitmap: bdrv_merge_dirty_bitmap(): add return value block/block-copy: move copy_bitmap initialization to block_copy_state_new() iotests: Write test output to TEST_DIR tests/qemu-iotests/testrunner: Quote "case not run" lines in TAP mode tests/qemu-iotests/040: Skip TestCommitWithFilters without 'throttle' block: fix preallocate filter: don't do unaligned preallocate requests ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
b49872aa8f
@ -2515,9 +2515,12 @@ F: block/stream.c
|
||||
F: block/mirror.c
|
||||
F: qapi/job.json
|
||||
F: block/block-copy.c
|
||||
F: include/block/block-copy.c
|
||||
F: include/block/block-copy.h
|
||||
F: block/reqlist.c
|
||||
F: include/block/reqlist.h
|
||||
F: block/copy-before-write.h
|
||||
F: block/copy-before-write.c
|
||||
F: block/snapshot-access.c
|
||||
F: include/block/aio_task.h
|
||||
F: block/aio_task.c
|
||||
F: util/qemu-co-shared-resource.c
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
#include "block/block-copy.h"
|
||||
#include "block/reqlist.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/units.h"
|
||||
#include "qemu/coroutine.h"
|
||||
@ -83,7 +84,6 @@ typedef struct BlockCopyTask {
|
||||
*/
|
||||
BlockCopyState *s;
|
||||
BlockCopyCallState *call_state;
|
||||
int64_t offset;
|
||||
/*
|
||||
* @method can also be set again in the while loop of
|
||||
* block_copy_dirty_clusters(), but it is never accessed concurrently
|
||||
@ -94,21 +94,17 @@ typedef struct BlockCopyTask {
|
||||
BlockCopyMethod method;
|
||||
|
||||
/*
|
||||
* Fields whose state changes throughout the execution
|
||||
* Protected by lock in BlockCopyState.
|
||||
* Generally, req is protected by lock in BlockCopyState, Still req.offset
|
||||
* is only set on task creation, so may be read concurrently after creation.
|
||||
* req.bytes is changed at most once, and need only protecting the case of
|
||||
* parallel read while updating @bytes value in block_copy_task_shrink().
|
||||
*/
|
||||
CoQueue wait_queue; /* coroutines blocked on this task */
|
||||
/*
|
||||
* Only protect the case of parallel read while updating @bytes
|
||||
* value in block_copy_task_shrink().
|
||||
*/
|
||||
int64_t bytes;
|
||||
QLIST_ENTRY(BlockCopyTask) list;
|
||||
BlockReq req;
|
||||
} BlockCopyTask;
|
||||
|
||||
static int64_t task_end(BlockCopyTask *task)
|
||||
{
|
||||
return task->offset + task->bytes;
|
||||
return task->req.offset + task->req.bytes;
|
||||
}
|
||||
|
||||
typedef struct BlockCopyState {
|
||||
@ -136,7 +132,7 @@ typedef struct BlockCopyState {
|
||||
CoMutex lock;
|
||||
int64_t in_flight_bytes;
|
||||
BlockCopyMethod method;
|
||||
QLIST_HEAD(, BlockCopyTask) tasks; /* All tasks from all block-copy calls */
|
||||
BlockReqList reqs;
|
||||
QLIST_HEAD(, BlockCopyCallState) calls;
|
||||
/*
|
||||
* skip_unallocated:
|
||||
@ -160,42 +156,6 @@ typedef struct BlockCopyState {
|
||||
RateLimit rate_limit;
|
||||
} BlockCopyState;
|
||||
|
||||
/* Called with lock held */
|
||||
static BlockCopyTask *find_conflicting_task(BlockCopyState *s,
|
||||
int64_t offset, int64_t bytes)
|
||||
{
|
||||
BlockCopyTask *t;
|
||||
|
||||
QLIST_FOREACH(t, &s->tasks, list) {
|
||||
if (offset + bytes > t->offset && offset < t->offset + t->bytes) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no intersecting tasks return false. Otherwise, wait for the
|
||||
* first found intersecting tasks to finish and return true.
|
||||
*
|
||||
* Called with lock held. May temporary release the lock.
|
||||
* Return value of 0 proves that lock was NOT released.
|
||||
*/
|
||||
static bool coroutine_fn block_copy_wait_one(BlockCopyState *s, int64_t offset,
|
||||
int64_t bytes)
|
||||
{
|
||||
BlockCopyTask *task = find_conflicting_task(s, offset, bytes);
|
||||
|
||||
if (!task) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qemu_co_queue_wait(&task->wait_queue, &s->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Called with lock held */
|
||||
static int64_t block_copy_chunk_size(BlockCopyState *s)
|
||||
{
|
||||
@ -239,7 +199,7 @@ block_copy_task_create(BlockCopyState *s, BlockCopyCallState *call_state,
|
||||
bytes = QEMU_ALIGN_UP(bytes, s->cluster_size);
|
||||
|
||||
/* region is dirty, so no existent tasks possible in it */
|
||||
assert(!find_conflicting_task(s, offset, bytes));
|
||||
assert(!reqlist_find_conflict(&s->reqs, offset, bytes));
|
||||
|
||||
bdrv_reset_dirty_bitmap(s->copy_bitmap, offset, bytes);
|
||||
s->in_flight_bytes += bytes;
|
||||
@ -249,12 +209,9 @@ block_copy_task_create(BlockCopyState *s, BlockCopyCallState *call_state,
|
||||
.task.func = block_copy_task_entry,
|
||||
.s = s,
|
||||
.call_state = call_state,
|
||||
.offset = offset,
|
||||
.bytes = bytes,
|
||||
.method = s->method,
|
||||
};
|
||||
qemu_co_queue_init(&task->wait_queue);
|
||||
QLIST_INSERT_HEAD(&s->tasks, task, list);
|
||||
reqlist_init_req(&s->reqs, &task->req, offset, bytes);
|
||||
|
||||
return task;
|
||||
}
|
||||
@ -270,34 +227,34 @@ static void coroutine_fn block_copy_task_shrink(BlockCopyTask *task,
|
||||
int64_t new_bytes)
|
||||
{
|
||||
QEMU_LOCK_GUARD(&task->s->lock);
|
||||
if (new_bytes == task->bytes) {
|
||||
if (new_bytes == task->req.bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(new_bytes > 0 && new_bytes < task->bytes);
|
||||
assert(new_bytes > 0 && new_bytes < task->req.bytes);
|
||||
|
||||
task->s->in_flight_bytes -= task->bytes - new_bytes;
|
||||
task->s->in_flight_bytes -= task->req.bytes - new_bytes;
|
||||
bdrv_set_dirty_bitmap(task->s->copy_bitmap,
|
||||
task->offset + new_bytes, task->bytes - new_bytes);
|
||||
task->req.offset + new_bytes,
|
||||
task->req.bytes - new_bytes);
|
||||
|
||||
task->bytes = new_bytes;
|
||||
qemu_co_queue_restart_all(&task->wait_queue);
|
||||
reqlist_shrink_req(&task->req, new_bytes);
|
||||
}
|
||||
|
||||
static void coroutine_fn block_copy_task_end(BlockCopyTask *task, int ret)
|
||||
{
|
||||
QEMU_LOCK_GUARD(&task->s->lock);
|
||||
task->s->in_flight_bytes -= task->bytes;
|
||||
task->s->in_flight_bytes -= task->req.bytes;
|
||||
if (ret < 0) {
|
||||
bdrv_set_dirty_bitmap(task->s->copy_bitmap, task->offset, task->bytes);
|
||||
bdrv_set_dirty_bitmap(task->s->copy_bitmap, task->req.offset,
|
||||
task->req.bytes);
|
||||
}
|
||||
QLIST_REMOVE(task, list);
|
||||
if (task->s->progress) {
|
||||
progress_set_remaining(task->s->progress,
|
||||
bdrv_get_dirty_count(task->s->copy_bitmap) +
|
||||
task->s->in_flight_bytes);
|
||||
}
|
||||
qemu_co_queue_restart_all(&task->wait_queue);
|
||||
reqlist_remove_req(&task->req);
|
||||
}
|
||||
|
||||
void block_copy_state_free(BlockCopyState *s)
|
||||
@ -384,8 +341,10 @@ static int64_t block_copy_calculate_cluster_size(BlockDriverState *target,
|
||||
}
|
||||
|
||||
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
const BdrvDirtyBitmap *bitmap,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
BlockCopyState *s;
|
||||
int64_t cluster_size;
|
||||
BdrvDirtyBitmap *copy_bitmap;
|
||||
@ -402,6 +361,17 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
return NULL;
|
||||
}
|
||||
bdrv_disable_dirty_bitmap(copy_bitmap);
|
||||
if (bitmap) {
|
||||
if (!bdrv_merge_dirty_bitmap(copy_bitmap, bitmap, NULL, errp)) {
|
||||
error_prepend(errp, "Failed to merge bitmap '%s' to internal "
|
||||
"copy-bitmap: ", bdrv_dirty_bitmap_name(bitmap));
|
||||
bdrv_release_dirty_bitmap(copy_bitmap);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
bdrv_set_dirty_bitmap(copy_bitmap, 0,
|
||||
bdrv_dirty_bitmap_size(copy_bitmap));
|
||||
}
|
||||
|
||||
/*
|
||||
* If source is in backing chain of target assume that target is going to be
|
||||
@ -437,7 +407,7 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
|
||||
ratelimit_init(&s->rate_limit);
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
QLIST_INIT(&s->tasks);
|
||||
QLIST_INIT(&s->reqs);
|
||||
QLIST_INIT(&s->calls);
|
||||
|
||||
return s;
|
||||
@ -470,7 +440,7 @@ static coroutine_fn int block_copy_task_run(AioTaskPool *pool,
|
||||
|
||||
aio_task_pool_wait_slot(pool);
|
||||
if (aio_task_pool_status(pool) < 0) {
|
||||
co_put_to_shres(task->s->mem, task->bytes);
|
||||
co_put_to_shres(task->s->mem, task->req.bytes);
|
||||
block_copy_task_end(task, -ECANCELED);
|
||||
g_free(task);
|
||||
return -ECANCELED;
|
||||
@ -583,7 +553,8 @@ static coroutine_fn int block_copy_task_entry(AioTask *task)
|
||||
BlockCopyMethod method = t->method;
|
||||
int ret;
|
||||
|
||||
ret = block_copy_do_copy(s, t->offset, t->bytes, &method, &error_is_read);
|
||||
ret = block_copy_do_copy(s, t->req.offset, t->req.bytes, &method,
|
||||
&error_is_read);
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&s->lock) {
|
||||
if (s->method == t->method) {
|
||||
@ -596,10 +567,10 @@ static coroutine_fn int block_copy_task_entry(AioTask *task)
|
||||
t->call_state->error_is_read = error_is_read;
|
||||
}
|
||||
} else if (s->progress) {
|
||||
progress_work_done(s->progress, t->bytes);
|
||||
progress_work_done(s->progress, t->req.bytes);
|
||||
}
|
||||
}
|
||||
co_put_to_shres(s->mem, t->bytes);
|
||||
co_put_to_shres(s->mem, t->req.bytes);
|
||||
block_copy_task_end(t, ret);
|
||||
|
||||
return ret;
|
||||
@ -679,6 +650,18 @@ static int block_copy_is_cluster_allocated(BlockCopyState *s, int64_t offset,
|
||||
}
|
||||
}
|
||||
|
||||
void block_copy_reset(BlockCopyState *s, int64_t offset, int64_t bytes)
|
||||
{
|
||||
QEMU_LOCK_GUARD(&s->lock);
|
||||
|
||||
bdrv_reset_dirty_bitmap(s->copy_bitmap, offset, bytes);
|
||||
if (s->progress) {
|
||||
progress_set_remaining(s->progress,
|
||||
bdrv_get_dirty_count(s->copy_bitmap) +
|
||||
s->in_flight_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset bits in copy_bitmap starting at offset if they represent unallocated
|
||||
* data in the image. May reset subsequent contiguous bits.
|
||||
@ -699,14 +682,7 @@ int64_t block_copy_reset_unallocated(BlockCopyState *s,
|
||||
bytes = clusters * s->cluster_size;
|
||||
|
||||
if (!ret) {
|
||||
qemu_co_mutex_lock(&s->lock);
|
||||
bdrv_reset_dirty_bitmap(s->copy_bitmap, offset, bytes);
|
||||
if (s->progress) {
|
||||
progress_set_remaining(s->progress,
|
||||
bdrv_get_dirty_count(s->copy_bitmap) +
|
||||
s->in_flight_bytes);
|
||||
}
|
||||
qemu_co_mutex_unlock(&s->lock);
|
||||
block_copy_reset(s, offset, bytes);
|
||||
}
|
||||
|
||||
*count = bytes;
|
||||
@ -753,22 +729,22 @@ block_copy_dirty_clusters(BlockCopyCallState *call_state)
|
||||
trace_block_copy_skip_range(s, offset, bytes);
|
||||
break;
|
||||
}
|
||||
if (task->offset > offset) {
|
||||
trace_block_copy_skip_range(s, offset, task->offset - offset);
|
||||
if (task->req.offset > offset) {
|
||||
trace_block_copy_skip_range(s, offset, task->req.offset - offset);
|
||||
}
|
||||
|
||||
found_dirty = true;
|
||||
|
||||
ret = block_copy_block_status(s, task->offset, task->bytes,
|
||||
ret = block_copy_block_status(s, task->req.offset, task->req.bytes,
|
||||
&status_bytes);
|
||||
assert(ret >= 0); /* never fail */
|
||||
if (status_bytes < task->bytes) {
|
||||
if (status_bytes < task->req.bytes) {
|
||||
block_copy_task_shrink(task, status_bytes);
|
||||
}
|
||||
if (qatomic_read(&s->skip_unallocated) &&
|
||||
!(ret & BDRV_BLOCK_ALLOCATED)) {
|
||||
block_copy_task_end(task, 0);
|
||||
trace_block_copy_skip_range(s, task->offset, task->bytes);
|
||||
trace_block_copy_skip_range(s, task->req.offset, task->req.bytes);
|
||||
offset = task_end(task);
|
||||
bytes = end - offset;
|
||||
g_free(task);
|
||||
@ -789,11 +765,11 @@ block_copy_dirty_clusters(BlockCopyCallState *call_state)
|
||||
}
|
||||
}
|
||||
|
||||
ratelimit_calculate_delay(&s->rate_limit, task->bytes);
|
||||
ratelimit_calculate_delay(&s->rate_limit, task->req.bytes);
|
||||
|
||||
trace_block_copy_process(s, task->offset);
|
||||
trace_block_copy_process(s, task->req.offset);
|
||||
|
||||
co_get_from_shres(s->mem, task->bytes);
|
||||
co_get_from_shres(s->mem, task->req.bytes);
|
||||
|
||||
offset = task_end(task);
|
||||
bytes = end - offset;
|
||||
@ -861,8 +837,8 @@ static int coroutine_fn block_copy_common(BlockCopyCallState *call_state)
|
||||
* Check that there is no task we still need to
|
||||
* wait to complete
|
||||
*/
|
||||
ret = block_copy_wait_one(s, call_state->offset,
|
||||
call_state->bytes);
|
||||
ret = reqlist_wait_one(&s->reqs, call_state->offset,
|
||||
call_state->bytes, &s->lock);
|
||||
if (ret == 0) {
|
||||
/*
|
||||
* No pending tasks, but check again the bitmap in this
|
||||
@ -870,7 +846,7 @@ static int coroutine_fn block_copy_common(BlockCopyCallState *call_state)
|
||||
* between this and the critical section in
|
||||
* block_copy_dirty_clusters().
|
||||
*
|
||||
* block_copy_wait_one return value 0 also means that it
|
||||
* reqlist_wait_one return value 0 also means that it
|
||||
* didn't release the lock. So, we are still in the same
|
||||
* critical section, not interrupted by any concurrent
|
||||
* access to state.
|
||||
|
@ -33,10 +33,37 @@
|
||||
#include "block/block-copy.h"
|
||||
|
||||
#include "block/copy-before-write.h"
|
||||
#include "block/reqlist.h"
|
||||
|
||||
#include "qapi/qapi-visit-block-core.h"
|
||||
|
||||
typedef struct BDRVCopyBeforeWriteState {
|
||||
BlockCopyState *bcs;
|
||||
BdrvChild *target;
|
||||
|
||||
/*
|
||||
* @lock: protects access to @access_bitmap, @done_bitmap and
|
||||
* @frozen_read_reqs
|
||||
*/
|
||||
CoMutex lock;
|
||||
|
||||
/*
|
||||
* @access_bitmap: represents areas allowed for reading by fleecing user.
|
||||
* Reading from non-dirty areas leads to -EACCES.
|
||||
*/
|
||||
BdrvDirtyBitmap *access_bitmap;
|
||||
|
||||
/*
|
||||
* @done_bitmap: represents areas that was successfully copied to @target by
|
||||
* copy-before-write operations.
|
||||
*/
|
||||
BdrvDirtyBitmap *done_bitmap;
|
||||
|
||||
/*
|
||||
* @frozen_read_reqs: current read requests for fleecing user in bs->file
|
||||
* node. These areas must not be rewritten by guest.
|
||||
*/
|
||||
BlockReqList frozen_read_reqs;
|
||||
} BDRVCopyBeforeWriteState;
|
||||
|
||||
static coroutine_fn int cbw_co_preadv(
|
||||
@ -46,10 +73,20 @@ static coroutine_fn int cbw_co_preadv(
|
||||
return bdrv_co_preadv(bs->file, offset, bytes, qiov, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Do copy-before-write operation.
|
||||
*
|
||||
* On failure guest request must be failed too.
|
||||
*
|
||||
* On success, we also wait for all in-flight fleecing read requests in source
|
||||
* node, and it's guaranteed that after cbw_do_copy_before_write() successful
|
||||
* return there are no such requests and they will never appear.
|
||||
*/
|
||||
static coroutine_fn int cbw_do_copy_before_write(BlockDriverState *bs,
|
||||
uint64_t offset, uint64_t bytes, BdrvRequestFlags flags)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
int ret;
|
||||
uint64_t off, end;
|
||||
int64_t cluster_size = block_copy_cluster_size(s->bcs);
|
||||
|
||||
@ -60,7 +97,17 @@ static coroutine_fn int cbw_do_copy_before_write(BlockDriverState *bs,
|
||||
off = QEMU_ALIGN_DOWN(offset, cluster_size);
|
||||
end = QEMU_ALIGN_UP(offset + bytes, cluster_size);
|
||||
|
||||
return block_copy(s->bcs, off, end - off, true);
|
||||
ret = block_copy(s->bcs, off, end - off, true);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&s->lock) {
|
||||
bdrv_set_dirty_bitmap(s->done_bitmap, off, end - off);
|
||||
reqlist_wait_all(&s->frozen_read_reqs, off, end - off, &s->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn cbw_co_pdiscard(BlockDriverState *bs,
|
||||
@ -108,6 +155,142 @@ static int coroutine_fn cbw_co_flush(BlockDriverState *bs)
|
||||
return bdrv_co_flush(bs->file->bs);
|
||||
}
|
||||
|
||||
/*
|
||||
* If @offset not accessible - return NULL.
|
||||
*
|
||||
* Otherwise, set @pnum to some bytes that accessible from @file (@file is set
|
||||
* to bs->file or to s->target). Return newly allocated BlockReq object that
|
||||
* should be than passed to cbw_snapshot_read_unlock().
|
||||
*
|
||||
* It's guaranteed that guest writes will not interact in the region until
|
||||
* cbw_snapshot_read_unlock() called.
|
||||
*/
|
||||
static BlockReq *cbw_snapshot_read_lock(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes,
|
||||
int64_t *pnum, BdrvChild **file)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
BlockReq *req = g_new(BlockReq, 1);
|
||||
bool done;
|
||||
|
||||
QEMU_LOCK_GUARD(&s->lock);
|
||||
|
||||
if (bdrv_dirty_bitmap_next_zero(s->access_bitmap, offset, bytes) != -1) {
|
||||
g_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
done = bdrv_dirty_bitmap_status(s->done_bitmap, offset, bytes, pnum);
|
||||
if (done) {
|
||||
/*
|
||||
* Special invalid BlockReq, that is handled in
|
||||
* cbw_snapshot_read_unlock(). We don't need to lock something to read
|
||||
* from s->target.
|
||||
*/
|
||||
*req = (BlockReq) {.offset = -1, .bytes = -1};
|
||||
*file = s->target;
|
||||
} else {
|
||||
reqlist_init_req(&s->frozen_read_reqs, req, offset, bytes);
|
||||
*file = bs->file;
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
static void cbw_snapshot_read_unlock(BlockDriverState *bs, BlockReq *req)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
|
||||
if (req->offset == -1 && req->bytes == -1) {
|
||||
g_free(req);
|
||||
return;
|
||||
}
|
||||
|
||||
QEMU_LOCK_GUARD(&s->lock);
|
||||
|
||||
reqlist_remove_req(req);
|
||||
g_free(req);
|
||||
}
|
||||
|
||||
static coroutine_fn int
|
||||
cbw_co_preadv_snapshot(BlockDriverState *bs, int64_t offset, int64_t bytes,
|
||||
QEMUIOVector *qiov, size_t qiov_offset)
|
||||
{
|
||||
BlockReq *req;
|
||||
BdrvChild *file;
|
||||
int ret;
|
||||
|
||||
/* TODO: upgrade to async loop using AioTask */
|
||||
while (bytes) {
|
||||
int64_t cur_bytes;
|
||||
|
||||
req = cbw_snapshot_read_lock(bs, offset, bytes, &cur_bytes, &file);
|
||||
if (!req) {
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
ret = bdrv_co_preadv_part(file, offset, cur_bytes,
|
||||
qiov, qiov_offset, 0);
|
||||
cbw_snapshot_read_unlock(bs, req);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bytes -= cur_bytes;
|
||||
offset += cur_bytes;
|
||||
qiov_offset += cur_bytes;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
cbw_co_snapshot_block_status(BlockDriverState *bs,
|
||||
bool want_zero, int64_t offset, int64_t bytes,
|
||||
int64_t *pnum, int64_t *map,
|
||||
BlockDriverState **file)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
BlockReq *req;
|
||||
int ret;
|
||||
int64_t cur_bytes;
|
||||
BdrvChild *child;
|
||||
|
||||
req = cbw_snapshot_read_lock(bs, offset, bytes, &cur_bytes, &child);
|
||||
if (!req) {
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
ret = bdrv_block_status(child->bs, offset, cur_bytes, pnum, map, file);
|
||||
if (child == s->target) {
|
||||
/*
|
||||
* We refer to s->target only for areas that we've written to it.
|
||||
* And we can not report unallocated blocks in s->target: this will
|
||||
* break generic block-status-above logic, that will go to
|
||||
* copy-before-write filtered child in this case.
|
||||
*/
|
||||
assert(ret & BDRV_BLOCK_ALLOCATED);
|
||||
}
|
||||
|
||||
cbw_snapshot_read_unlock(bs, req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int coroutine_fn cbw_co_pdiscard_snapshot(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
|
||||
WITH_QEMU_LOCK_GUARD(&s->lock) {
|
||||
bdrv_reset_dirty_bitmap(s->access_bitmap, offset, bytes);
|
||||
}
|
||||
|
||||
block_copy_reset(s->bcs, offset, bytes);
|
||||
|
||||
return bdrv_co_pdiscard(s->target, offset, bytes);
|
||||
}
|
||||
|
||||
static void cbw_refresh_filename(BlockDriverState *bs)
|
||||
{
|
||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
@ -145,11 +328,54 @@ static void cbw_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
}
|
||||
}
|
||||
|
||||
static bool cbw_parse_bitmap_option(QDict *options, BdrvDirtyBitmap **bitmap,
|
||||
Error **errp)
|
||||
{
|
||||
QDict *bitmap_qdict = NULL;
|
||||
BlockDirtyBitmap *bmp_param = NULL;
|
||||
Visitor *v = NULL;
|
||||
bool ret = false;
|
||||
|
||||
*bitmap = NULL;
|
||||
|
||||
qdict_extract_subqdict(options, &bitmap_qdict, "bitmap.");
|
||||
if (!qdict_size(bitmap_qdict)) {
|
||||
ret = true;
|
||||
goto out;
|
||||
}
|
||||
|
||||
v = qobject_input_visitor_new_flat_confused(bitmap_qdict, errp);
|
||||
if (!v) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
visit_type_BlockDirtyBitmap(v, NULL, &bmp_param, errp);
|
||||
if (!bmp_param) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
*bitmap = block_dirty_bitmap_lookup(bmp_param->node, bmp_param->name, NULL,
|
||||
errp);
|
||||
if (!*bitmap) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
out:
|
||||
qapi_free_BlockDirtyBitmap(bmp_param);
|
||||
visit_free(v);
|
||||
qobject_unref(bitmap_qdict);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
BdrvDirtyBitmap *copy_bitmap;
|
||||
BdrvDirtyBitmap *bitmap = NULL;
|
||||
int64_t cluster_size;
|
||||
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
||||
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
|
||||
@ -164,6 +390,10 @@ static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!cbw_parse_bitmap_option(options, &bitmap, errp)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bs->total_sectors = bs->file->bs->total_sectors;
|
||||
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
|
||||
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags);
|
||||
@ -171,14 +401,32 @@ static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &
|
||||
bs->file->bs->supported_zero_flags);
|
||||
|
||||
s->bcs = block_copy_state_new(bs->file, s->target, errp);
|
||||
s->bcs = block_copy_state_new(bs->file, s->target, bitmap, errp);
|
||||
if (!s->bcs) {
|
||||
error_prepend(errp, "Cannot create block-copy-state: ");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
copy_bitmap = block_copy_dirty_bitmap(s->bcs);
|
||||
bdrv_set_dirty_bitmap(copy_bitmap, 0, bdrv_dirty_bitmap_size(copy_bitmap));
|
||||
cluster_size = block_copy_cluster_size(s->bcs);
|
||||
|
||||
s->done_bitmap = bdrv_create_dirty_bitmap(bs, cluster_size, NULL, errp);
|
||||
if (!s->done_bitmap) {
|
||||
return -EINVAL;
|
||||
}
|
||||
bdrv_disable_dirty_bitmap(s->done_bitmap);
|
||||
|
||||
/* s->access_bitmap starts equal to bcs bitmap */
|
||||
s->access_bitmap = bdrv_create_dirty_bitmap(bs, cluster_size, NULL, errp);
|
||||
if (!s->access_bitmap) {
|
||||
return -EINVAL;
|
||||
}
|
||||
bdrv_disable_dirty_bitmap(s->access_bitmap);
|
||||
bdrv_dirty_bitmap_merge_internal(s->access_bitmap,
|
||||
block_copy_dirty_bitmap(s->bcs), NULL,
|
||||
true);
|
||||
|
||||
qemu_co_mutex_init(&s->lock);
|
||||
QLIST_INIT(&s->frozen_read_reqs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -187,6 +435,9 @@ static void cbw_close(BlockDriverState *bs)
|
||||
{
|
||||
BDRVCopyBeforeWriteState *s = bs->opaque;
|
||||
|
||||
bdrv_release_dirty_bitmap(s->access_bitmap);
|
||||
bdrv_release_dirty_bitmap(s->done_bitmap);
|
||||
|
||||
block_copy_state_free(s->bcs);
|
||||
s->bcs = NULL;
|
||||
}
|
||||
@ -204,6 +455,10 @@ BlockDriver bdrv_cbw_filter = {
|
||||
.bdrv_co_pdiscard = cbw_co_pdiscard,
|
||||
.bdrv_co_flush = cbw_co_flush,
|
||||
|
||||
.bdrv_co_preadv_snapshot = cbw_co_preadv_snapshot,
|
||||
.bdrv_co_pdiscard_snapshot = cbw_co_pdiscard_snapshot,
|
||||
.bdrv_co_snapshot_block_status = cbw_co_snapshot_block_status,
|
||||
|
||||
.bdrv_refresh_filename = cbw_refresh_filename,
|
||||
|
||||
.bdrv_child_perm = cbw_child_perm,
|
||||
|
94
block/curl.c
94
block/curl.c
@ -458,38 +458,51 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
||||
if (!state->curl) {
|
||||
return -EIO;
|
||||
}
|
||||
curl_easy_setopt(state->curl, CURLOPT_URL, s->url);
|
||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
|
||||
(long) s->sslverify);
|
||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYHOST,
|
||||
s->sslverify ? 2L : 0L);
|
||||
if (s->cookie) {
|
||||
curl_easy_setopt(state->curl, CURLOPT_COOKIE, s->cookie);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_URL, s->url) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
|
||||
(long) s->sslverify) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYHOST,
|
||||
s->sslverify ? 2L : 0L)) {
|
||||
goto err;
|
||||
}
|
||||
if (s->cookie) {
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_COOKIE, s->cookie)) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_TIMEOUT, (long)s->timeout) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION,
|
||||
(void *)curl_read_cb) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1)) {
|
||||
goto err;
|
||||
}
|
||||
curl_easy_setopt(state->curl, CURLOPT_TIMEOUT, (long)s->timeout);
|
||||
curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION,
|
||||
(void *)curl_read_cb);
|
||||
curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state);
|
||||
curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state);
|
||||
curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1);
|
||||
curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1);
|
||||
curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg);
|
||||
curl_easy_setopt(state->curl, CURLOPT_FAILONERROR, 1);
|
||||
|
||||
if (s->username) {
|
||||
curl_easy_setopt(state->curl, CURLOPT_USERNAME, s->username);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_USERNAME, s->username)) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
if (s->password) {
|
||||
curl_easy_setopt(state->curl, CURLOPT_PASSWORD, s->password);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_PASSWORD, s->password)) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
if (s->proxyusername) {
|
||||
curl_easy_setopt(state->curl,
|
||||
CURLOPT_PROXYUSERNAME, s->proxyusername);
|
||||
if (curl_easy_setopt(state->curl,
|
||||
CURLOPT_PROXYUSERNAME, s->proxyusername)) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
if (s->proxypassword) {
|
||||
curl_easy_setopt(state->curl,
|
||||
CURLOPT_PROXYPASSWORD, s->proxypassword);
|
||||
if (curl_easy_setopt(state->curl,
|
||||
CURLOPT_PROXYPASSWORD, s->proxypassword)) {
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Restrict supported protocols to avoid security issues in the more
|
||||
@ -499,18 +512,27 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
|
||||
* Restricting protocols is only supported from 7.19.4 upwards.
|
||||
*/
|
||||
#if LIBCURL_VERSION_NUM >= 0x071304
|
||||
curl_easy_setopt(state->curl, CURLOPT_PROTOCOLS, PROTOCOLS);
|
||||
curl_easy_setopt(state->curl, CURLOPT_REDIR_PROTOCOLS, PROTOCOLS);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_PROTOCOLS, PROTOCOLS) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_REDIR_PROTOCOLS, PROTOCOLS)) {
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_VERBOSE
|
||||
curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1)) {
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
state->s = s;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
curl_easy_cleanup(state->curl);
|
||||
state->curl = NULL;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* Called with s->mutex held. */
|
||||
@ -759,14 +781,19 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
// Get file size
|
||||
|
||||
if (curl_init_state(s, state) < 0) {
|
||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
||||
"curl library initialization failed.");
|
||||
goto out;
|
||||
}
|
||||
|
||||
s->accept_range = false;
|
||||
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1);
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION,
|
||||
curl_header_cb);
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s);
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_header_cb) ||
|
||||
curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s)) {
|
||||
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
|
||||
"curl library initialization failed.");
|
||||
goto out;
|
||||
}
|
||||
if (curl_easy_perform(state->curl))
|
||||
goto out;
|
||||
if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) {
|
||||
@ -879,9 +906,8 @@ static void curl_setup_preadv(BlockDriverState *bs, CURLAIOCB *acb)
|
||||
|
||||
snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
|
||||
trace_curl_setup_preadv(acb->bytes, start, state->range);
|
||||
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range);
|
||||
|
||||
if (curl_multi_add_handle(s->multi, state->curl) != CURLM_OK) {
|
||||
if (curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range) ||
|
||||
curl_multi_add_handle(s->multi, state->curl) != CURLM_OK) {
|
||||
state->acb[0] = NULL;
|
||||
acb->ret = -EIO;
|
||||
|
||||
|
@ -879,16 +879,25 @@ bool bdrv_dirty_bitmap_next_dirty_area(BdrvDirtyBitmap *bitmap,
|
||||
dirty_start, dirty_count);
|
||||
}
|
||||
|
||||
bool bdrv_dirty_bitmap_status(BdrvDirtyBitmap *bitmap, int64_t offset,
|
||||
int64_t bytes, int64_t *count)
|
||||
{
|
||||
return hbitmap_status(bitmap->bitmap, offset, bytes, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* bdrv_merge_dirty_bitmap: merge src into dest.
|
||||
* Ensures permissions on bitmaps are reasonable; use for public API.
|
||||
*
|
||||
* @backup: If provided, make a copy of dest here prior to merge.
|
||||
*
|
||||
* Returns true on success, false on failure. In case of failure bitmaps are
|
||||
* untouched.
|
||||
*/
|
||||
void bdrv_merge_dirty_bitmap(BdrvDirtyBitmap *dest, const BdrvDirtyBitmap *src,
|
||||
bool bdrv_merge_dirty_bitmap(BdrvDirtyBitmap *dest, const BdrvDirtyBitmap *src,
|
||||
HBitmap **backup, Error **errp)
|
||||
{
|
||||
bool ret;
|
||||
bool ret = false;
|
||||
|
||||
bdrv_dirty_bitmaps_lock(dest->bs);
|
||||
if (src->bs != dest->bs) {
|
||||
@ -916,6 +925,8 @@ out:
|
||||
if (src->bs != dest->bs) {
|
||||
bdrv_dirty_bitmaps_unlock(src->bs);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
76
block/io.c
76
block/io.c
@ -2203,6 +2203,7 @@ static int coroutine_fn bdrv_co_do_zero_pwritev(BdrvChild *child,
|
||||
|
||||
padding = bdrv_init_padding(bs, offset, bytes, &pad);
|
||||
if (padding) {
|
||||
assert(!(flags & BDRV_REQ_NO_WAIT));
|
||||
bdrv_make_request_serialising(req, align);
|
||||
|
||||
bdrv_padding_rmw_read(child, req, &pad, true);
|
||||
@ -2339,6 +2340,7 @@ int coroutine_fn bdrv_co_pwritev_part(BdrvChild *child,
|
||||
* serialize the request to prevent interactions of the
|
||||
* widened region with other transactions.
|
||||
*/
|
||||
assert(!(flags & BDRV_REQ_NO_WAIT));
|
||||
bdrv_make_request_serialising(&req, align);
|
||||
bdrv_padding_rmw_read(child, &req, &pad, false);
|
||||
}
|
||||
@ -3387,6 +3389,8 @@ static int coroutine_fn bdrv_co_copy_range_internal(
|
||||
/* TODO We can support BDRV_REQ_NO_FALLBACK here */
|
||||
assert(!(read_flags & BDRV_REQ_NO_FALLBACK));
|
||||
assert(!(write_flags & BDRV_REQ_NO_FALLBACK));
|
||||
assert(!(read_flags & BDRV_REQ_NO_WAIT));
|
||||
assert(!(write_flags & BDRV_REQ_NO_WAIT));
|
||||
|
||||
if (!dst || !dst->bs || !bdrv_is_inserted(dst->bs)) {
|
||||
return -ENOMEDIUM;
|
||||
@ -3650,3 +3654,75 @@ void bdrv_cancel_in_flight(BlockDriverState *bs)
|
||||
bs->drv->bdrv_cancel_in_flight(bs);
|
||||
}
|
||||
}
|
||||
|
||||
int coroutine_fn
|
||||
bdrv_co_preadv_snapshot(BdrvChild *child, int64_t offset, int64_t bytes,
|
||||
QEMUIOVector *qiov, size_t qiov_offset)
|
||||
{
|
||||
BlockDriverState *bs = child->bs;
|
||||
BlockDriver *drv = bs->drv;
|
||||
int ret;
|
||||
IO_CODE();
|
||||
|
||||
if (!drv) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
if (!drv->bdrv_co_preadv_snapshot) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
ret = drv->bdrv_co_preadv_snapshot(bs, offset, bytes, qiov, qiov_offset);
|
||||
bdrv_dec_in_flight(bs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int coroutine_fn
|
||||
bdrv_co_snapshot_block_status(BlockDriverState *bs,
|
||||
bool want_zero, int64_t offset, int64_t bytes,
|
||||
int64_t *pnum, int64_t *map,
|
||||
BlockDriverState **file)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
int ret;
|
||||
IO_CODE();
|
||||
|
||||
if (!drv) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
if (!drv->bdrv_co_snapshot_block_status) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
ret = drv->bdrv_co_snapshot_block_status(bs, want_zero, offset, bytes,
|
||||
pnum, map, file);
|
||||
bdrv_dec_in_flight(bs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int coroutine_fn
|
||||
bdrv_co_pdiscard_snapshot(BlockDriverState *bs, int64_t offset, int64_t bytes)
|
||||
{
|
||||
BlockDriver *drv = bs->drv;
|
||||
int ret;
|
||||
IO_CODE();
|
||||
|
||||
if (!drv) {
|
||||
return -ENOMEDIUM;
|
||||
}
|
||||
|
||||
if (!drv->bdrv_co_pdiscard_snapshot) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
bdrv_inc_in_flight(bs);
|
||||
ret = drv->bdrv_co_pdiscard_snapshot(bs, offset, bytes);
|
||||
bdrv_dec_in_flight(bs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ block_ss.add(files(
|
||||
'qcow2.c',
|
||||
'quorum.c',
|
||||
'raw-format.c',
|
||||
'reqlist.c',
|
||||
'snapshot.c',
|
||||
'snapshot-access.c',
|
||||
'throttle-groups.c',
|
||||
'throttle.c',
|
||||
'vhdx-endian.c',
|
||||
|
@ -263,7 +263,6 @@ BdrvDirtyBitmap *block_dirty_bitmap_merge(const char *node, const char *target,
|
||||
BlockDriverState *bs;
|
||||
BdrvDirtyBitmap *dst, *src, *anon;
|
||||
BlockDirtyBitmapMergeSourceList *lst;
|
||||
Error *local_err = NULL;
|
||||
|
||||
GLOBAL_STATE_CODE();
|
||||
|
||||
@ -303,9 +302,7 @@ BdrvDirtyBitmap *block_dirty_bitmap_merge(const char *node, const char *target,
|
||||
abort();
|
||||
}
|
||||
|
||||
bdrv_merge_dirty_bitmap(anon, src, NULL, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
if (!bdrv_merge_dirty_bitmap(anon, src, NULL, errp)) {
|
||||
dst = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
@ -276,6 +276,10 @@ static bool coroutine_fn handle_write(BlockDriverState *bs, int64_t offset,
|
||||
int64_t end = offset + bytes;
|
||||
int64_t prealloc_start, prealloc_end;
|
||||
int ret;
|
||||
uint32_t file_align = bs->file->bs->bl.request_alignment;
|
||||
uint32_t prealloc_align = MAX(s->opts.prealloc_align, file_align);
|
||||
|
||||
assert(QEMU_IS_ALIGNED(prealloc_align, file_align));
|
||||
|
||||
if (!has_prealloc_perms(bs)) {
|
||||
/* We don't have state neither should try to recover it */
|
||||
@ -320,9 +324,14 @@ static bool coroutine_fn handle_write(BlockDriverState *bs, int64_t offset,
|
||||
|
||||
/* Now we want new preallocation, as request writes beyond s->file_end. */
|
||||
|
||||
prealloc_start = want_merge_zero ? MIN(offset, s->file_end) : s->file_end;
|
||||
prealloc_end = QEMU_ALIGN_UP(end + s->opts.prealloc_size,
|
||||
s->opts.prealloc_align);
|
||||
prealloc_start = QEMU_ALIGN_UP(
|
||||
want_merge_zero ? MIN(offset, s->file_end) : s->file_end,
|
||||
file_align);
|
||||
prealloc_end = QEMU_ALIGN_UP(
|
||||
MAX(prealloc_start, end) + s->opts.prealloc_size,
|
||||
prealloc_align);
|
||||
|
||||
want_merge_zero = want_merge_zero && (prealloc_start <= offset);
|
||||
|
||||
ret = bdrv_co_pwrite_zeroes(
|
||||
bs->file, prealloc_start, prealloc_end - prealloc_start,
|
||||
|
85
block/reqlist.c
Normal file
85
block/reqlist.c
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* reqlist API
|
||||
*
|
||||
* Copyright (C) 2013 Proxmox Server Solutions
|
||||
* Copyright (c) 2021 Virtuozzo International GmbH.
|
||||
*
|
||||
* Authors:
|
||||
* Dietmar Maurer (dietmar@proxmox.com)
|
||||
* Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/range.h"
|
||||
|
||||
#include "block/reqlist.h"
|
||||
|
||||
void reqlist_init_req(BlockReqList *reqs, BlockReq *req, int64_t offset,
|
||||
int64_t bytes)
|
||||
{
|
||||
assert(!reqlist_find_conflict(reqs, offset, bytes));
|
||||
|
||||
*req = (BlockReq) {
|
||||
.offset = offset,
|
||||
.bytes = bytes,
|
||||
};
|
||||
qemu_co_queue_init(&req->wait_queue);
|
||||
QLIST_INSERT_HEAD(reqs, req, list);
|
||||
}
|
||||
|
||||
BlockReq *reqlist_find_conflict(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes)
|
||||
{
|
||||
BlockReq *r;
|
||||
|
||||
QLIST_FOREACH(r, reqs, list) {
|
||||
if (ranges_overlap(offset, bytes, r->offset, r->bytes)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool coroutine_fn reqlist_wait_one(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes, CoMutex *lock)
|
||||
{
|
||||
BlockReq *r = reqlist_find_conflict(reqs, offset, bytes);
|
||||
|
||||
if (!r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qemu_co_queue_wait(&r->wait_queue, lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void coroutine_fn reqlist_wait_all(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes, CoMutex *lock)
|
||||
{
|
||||
while (reqlist_wait_one(reqs, offset, bytes, lock)) {
|
||||
/* continue */
|
||||
}
|
||||
}
|
||||
|
||||
void coroutine_fn reqlist_shrink_req(BlockReq *req, int64_t new_bytes)
|
||||
{
|
||||
if (new_bytes == req->bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(new_bytes > 0 && new_bytes < req->bytes);
|
||||
|
||||
req->bytes = new_bytes;
|
||||
qemu_co_queue_restart_all(&req->wait_queue);
|
||||
}
|
||||
|
||||
void coroutine_fn reqlist_remove_req(BlockReq *req)
|
||||
{
|
||||
QLIST_REMOVE(req, list);
|
||||
qemu_co_queue_restart_all(&req->wait_queue);
|
||||
}
|
132
block/snapshot-access.c
Normal file
132
block/snapshot-access.c
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* snapshot_access block driver
|
||||
*
|
||||
* Copyright (c) 2022 Virtuozzo International GmbH.
|
||||
*
|
||||
* Author:
|
||||
* Sementsov-Ogievskiy Vladimir <vsementsov@virtuozzo.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "block/block_int.h"
|
||||
|
||||
static coroutine_fn int
|
||||
snapshot_access_co_preadv_part(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes,
|
||||
QEMUIOVector *qiov, size_t qiov_offset,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
if (flags) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
return bdrv_co_preadv_snapshot(bs->file, offset, bytes, qiov, qiov_offset);
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
snapshot_access_co_block_status(BlockDriverState *bs,
|
||||
bool want_zero, int64_t offset,
|
||||
int64_t bytes, int64_t *pnum,
|
||||
int64_t *map, BlockDriverState **file)
|
||||
{
|
||||
return bdrv_co_snapshot_block_status(bs->file->bs, want_zero, offset,
|
||||
bytes, pnum, map, file);
|
||||
}
|
||||
|
||||
static int coroutine_fn snapshot_access_co_pdiscard(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes)
|
||||
{
|
||||
return bdrv_co_pdiscard_snapshot(bs->file->bs, offset, bytes);
|
||||
}
|
||||
|
||||
static int coroutine_fn
|
||||
snapshot_access_co_pwrite_zeroes(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static coroutine_fn int
|
||||
snapshot_access_co_pwritev_part(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes,
|
||||
QEMUIOVector *qiov, size_t qiov_offset,
|
||||
BdrvRequestFlags flags)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
|
||||
static void snapshot_access_refresh_filename(BlockDriverState *bs)
|
||||
{
|
||||
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename),
|
||||
bs->file->bs->filename);
|
||||
}
|
||||
|
||||
static int snapshot_access_open(BlockDriverState *bs, QDict *options, int flags,
|
||||
Error **errp)
|
||||
{
|
||||
bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
|
||||
BDRV_CHILD_DATA | BDRV_CHILD_PRIMARY,
|
||||
false, errp);
|
||||
if (!bs->file) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bs->total_sectors = bs->file->bs->total_sectors;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void snapshot_access_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||
BdrvChildRole role,
|
||||
BlockReopenQueue *reopen_queue,
|
||||
uint64_t perm, uint64_t shared,
|
||||
uint64_t *nperm, uint64_t *nshared)
|
||||
{
|
||||
/*
|
||||
* Currently, we don't need any permissions. If bs->file provides
|
||||
* snapshot-access API, we can use it.
|
||||
*/
|
||||
*nperm = 0;
|
||||
*nshared = BLK_PERM_ALL;
|
||||
}
|
||||
|
||||
BlockDriver bdrv_snapshot_access_drv = {
|
||||
.format_name = "snapshot-access",
|
||||
|
||||
.bdrv_open = snapshot_access_open,
|
||||
|
||||
.bdrv_co_preadv_part = snapshot_access_co_preadv_part,
|
||||
.bdrv_co_pwritev_part = snapshot_access_co_pwritev_part,
|
||||
.bdrv_co_pwrite_zeroes = snapshot_access_co_pwrite_zeroes,
|
||||
.bdrv_co_pdiscard = snapshot_access_co_pdiscard,
|
||||
.bdrv_co_block_status = snapshot_access_co_block_status,
|
||||
|
||||
.bdrv_refresh_filename = snapshot_access_refresh_filename,
|
||||
|
||||
.bdrv_child_perm = snapshot_access_child_perm,
|
||||
};
|
||||
|
||||
static void snapshot_access_init(void)
|
||||
{
|
||||
bdrv_register(&bdrv_snapshot_access_drv);
|
||||
}
|
||||
|
||||
block_init(snapshot_access_init);
|
@ -434,12 +434,16 @@ static const AIOCBInfo trim_aiocb_info = {
|
||||
static void ide_trim_bh_cb(void *opaque)
|
||||
{
|
||||
TrimAIOCB *iocb = opaque;
|
||||
BlockBackend *blk = iocb->s->blk;
|
||||
|
||||
iocb->common.cb(iocb->common.opaque, iocb->ret);
|
||||
|
||||
qemu_bh_delete(iocb->bh);
|
||||
iocb->bh = NULL;
|
||||
qemu_aio_unref(iocb);
|
||||
|
||||
/* Paired with an increment in ide_issue_trim() */
|
||||
blk_dec_in_flight(blk);
|
||||
}
|
||||
|
||||
static void ide_issue_trim_cb(void *opaque, int ret)
|
||||
@ -509,6 +513,9 @@ BlockAIOCB *ide_issue_trim(
|
||||
IDEState *s = opaque;
|
||||
TrimAIOCB *iocb;
|
||||
|
||||
/* Paired with a decrement in ide_trim_bh_cb() */
|
||||
blk_inc_in_flight(s->blk);
|
||||
|
||||
iocb = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque);
|
||||
iocb->s = s;
|
||||
iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb);
|
||||
|
@ -112,7 +112,8 @@ typedef enum {
|
||||
|
||||
/*
|
||||
* If we need to wait for other requests, just fail immediately. Used
|
||||
* only together with BDRV_REQ_SERIALISING.
|
||||
* only together with BDRV_REQ_SERIALISING. Used only with requests aligned
|
||||
* to request_alignment (corresponding assertions are in block/io.c).
|
||||
*/
|
||||
BDRV_REQ_NO_WAIT = 0x400,
|
||||
|
||||
|
@ -25,6 +25,7 @@ typedef struct BlockCopyState BlockCopyState;
|
||||
typedef struct BlockCopyCallState BlockCopyCallState;
|
||||
|
||||
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
|
||||
const BdrvDirtyBitmap *bitmap,
|
||||
Error **errp);
|
||||
|
||||
/* Function should be called prior any actual copy request */
|
||||
@ -34,6 +35,7 @@ void block_copy_set_progress_meter(BlockCopyState *s, ProgressMeter *pm);
|
||||
|
||||
void block_copy_state_free(BlockCopyState *s);
|
||||
|
||||
void block_copy_reset(BlockCopyState *s, int64_t offset, int64_t bytes);
|
||||
int64_t block_copy_reset_unallocated(BlockCopyState *s,
|
||||
int64_t offset, int64_t *count);
|
||||
|
||||
|
@ -597,6 +597,30 @@ struct BlockDriver {
|
||||
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
|
||||
int64_t *map, BlockDriverState **file);
|
||||
|
||||
/*
|
||||
* Snapshot-access API.
|
||||
*
|
||||
* Block-driver may provide snapshot-access API: special functions to access
|
||||
* some internal "snapshot". The functions are similar with normal
|
||||
* read/block_status/discard handler, but don't have any specific handling
|
||||
* in generic block-layer: no serializing, no alignment, no tracked
|
||||
* requests. So, block-driver that realizes these APIs is fully responsible
|
||||
* for synchronization between snapshot-access API and normal IO requests.
|
||||
*
|
||||
* TODO: To be able to support qcow2's internal snapshots, this API will
|
||||
* need to be extended to:
|
||||
* - be able to select a specific snapshot
|
||||
* - receive the snapshot's actual length (which may differ from bs's
|
||||
* length)
|
||||
*/
|
||||
int coroutine_fn (*bdrv_co_preadv_snapshot)(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes, QEMUIOVector *qiov, size_t qiov_offset);
|
||||
int coroutine_fn (*bdrv_co_snapshot_block_status)(BlockDriverState *bs,
|
||||
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
|
||||
int64_t *map, BlockDriverState **file);
|
||||
int coroutine_fn (*bdrv_co_pdiscard_snapshot)(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes);
|
||||
|
||||
/*
|
||||
* Invalidate any cached meta-data.
|
||||
*/
|
||||
|
@ -33,6 +33,15 @@
|
||||
* the I/O API.
|
||||
*/
|
||||
|
||||
int coroutine_fn bdrv_co_preadv_snapshot(BdrvChild *child,
|
||||
int64_t offset, int64_t bytes, QEMUIOVector *qiov, size_t qiov_offset);
|
||||
int coroutine_fn bdrv_co_snapshot_block_status(BlockDriverState *bs,
|
||||
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
|
||||
int64_t *map, BlockDriverState **file);
|
||||
int coroutine_fn bdrv_co_pdiscard_snapshot(BlockDriverState *bs,
|
||||
int64_t offset, int64_t bytes);
|
||||
|
||||
|
||||
int coroutine_fn bdrv_co_preadv(BdrvChild *child,
|
||||
int64_t offset, int64_t bytes, QEMUIOVector *qiov,
|
||||
BdrvRequestFlags flags);
|
||||
|
@ -77,7 +77,7 @@ void bdrv_dirty_bitmap_set_persistence(BdrvDirtyBitmap *bitmap,
|
||||
bool persistent);
|
||||
void bdrv_dirty_bitmap_set_inconsistent(BdrvDirtyBitmap *bitmap);
|
||||
void bdrv_dirty_bitmap_set_busy(BdrvDirtyBitmap *bitmap, bool busy);
|
||||
void bdrv_merge_dirty_bitmap(BdrvDirtyBitmap *dest, const BdrvDirtyBitmap *src,
|
||||
bool bdrv_merge_dirty_bitmap(BdrvDirtyBitmap *dest, const BdrvDirtyBitmap *src,
|
||||
HBitmap **backup, Error **errp);
|
||||
void bdrv_dirty_bitmap_skip_store(BdrvDirtyBitmap *bitmap, bool skip);
|
||||
bool bdrv_dirty_bitmap_get(BdrvDirtyBitmap *bitmap, int64_t offset);
|
||||
@ -115,6 +115,8 @@ int64_t bdrv_dirty_bitmap_next_zero(BdrvDirtyBitmap *bitmap, int64_t offset,
|
||||
bool bdrv_dirty_bitmap_next_dirty_area(BdrvDirtyBitmap *bitmap,
|
||||
int64_t start, int64_t end, int64_t max_dirty_count,
|
||||
int64_t *dirty_start, int64_t *dirty_count);
|
||||
bool bdrv_dirty_bitmap_status(BdrvDirtyBitmap *bitmap, int64_t offset,
|
||||
int64_t bytes, int64_t *count);
|
||||
BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
|
||||
Error **errp);
|
||||
|
||||
|
75
include/block/reqlist.h
Normal file
75
include/block/reqlist.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* reqlist API
|
||||
*
|
||||
* Copyright (C) 2013 Proxmox Server Solutions
|
||||
* Copyright (c) 2021 Virtuozzo International GmbH.
|
||||
*
|
||||
* Authors:
|
||||
* Dietmar Maurer (dietmar@proxmox.com)
|
||||
* Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#ifndef REQLIST_H
|
||||
#define REQLIST_H
|
||||
|
||||
#include "qemu/coroutine.h"
|
||||
|
||||
/*
|
||||
* The API is not thread-safe and shouldn't be. The struct is public to be part
|
||||
* of other structures and protected by third-party locks, see
|
||||
* block/block-copy.c for example.
|
||||
*/
|
||||
|
||||
typedef struct BlockReq {
|
||||
int64_t offset;
|
||||
int64_t bytes;
|
||||
|
||||
CoQueue wait_queue; /* coroutines blocked on this req */
|
||||
QLIST_ENTRY(BlockReq) list;
|
||||
} BlockReq;
|
||||
|
||||
typedef QLIST_HEAD(, BlockReq) BlockReqList;
|
||||
|
||||
/*
|
||||
* Initialize new request and add it to the list. Caller must be sure that
|
||||
* there are no conflicting requests in the list.
|
||||
*/
|
||||
void reqlist_init_req(BlockReqList *reqs, BlockReq *req, int64_t offset,
|
||||
int64_t bytes);
|
||||
/* Search for request in the list intersecting with @offset/@bytes area. */
|
||||
BlockReq *reqlist_find_conflict(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes);
|
||||
|
||||
/*
|
||||
* If there are no intersecting requests return false. Otherwise, wait for the
|
||||
* first found intersecting request to finish and return true.
|
||||
*
|
||||
* @lock is passed to qemu_co_queue_wait()
|
||||
* False return value proves that lock was released at no point.
|
||||
*/
|
||||
bool coroutine_fn reqlist_wait_one(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes, CoMutex *lock);
|
||||
|
||||
/*
|
||||
* Wait for all intersecting requests. It just calls reqlist_wait_one() in a
|
||||
* loop, caller is responsible to stop producing new requests in this region
|
||||
* in parallel, otherwise reqlist_wait_all() may never return.
|
||||
*/
|
||||
void coroutine_fn reqlist_wait_all(BlockReqList *reqs, int64_t offset,
|
||||
int64_t bytes, CoMutex *lock);
|
||||
|
||||
/*
|
||||
* Shrink request and wake all waiting coroutines (maybe some of them are not
|
||||
* intersecting with shrunk request).
|
||||
*/
|
||||
void coroutine_fn reqlist_shrink_req(BlockReq *req, int64_t new_bytes);
|
||||
|
||||
/*
|
||||
* Remove request and wake all waiting coroutines. Do not release any memory.
|
||||
*/
|
||||
void coroutine_fn reqlist_remove_req(BlockReq *req);
|
||||
|
||||
#endif /* REQLIST_H */
|
@ -340,6 +340,18 @@ bool hbitmap_next_dirty_area(const HBitmap *hb, int64_t start, int64_t end,
|
||||
int64_t max_dirty_count,
|
||||
int64_t *dirty_start, int64_t *dirty_count);
|
||||
|
||||
/*
|
||||
* bdrv_dirty_bitmap_status:
|
||||
* @hb: The HBitmap to operate on
|
||||
* @start: The bit to start from
|
||||
* @count: Number of bits to proceed
|
||||
* @pnum: Out-parameter. How many bits has same value starting from @start
|
||||
*
|
||||
* Returns true if bitmap is dirty at @start, false otherwise.
|
||||
*/
|
||||
bool hbitmap_status(const HBitmap *hb, int64_t start, int64_t count,
|
||||
int64_t *pnum);
|
||||
|
||||
/**
|
||||
* hbitmap_iter_next:
|
||||
* @hbi: HBitmapIter to operate on.
|
||||
|
@ -2914,13 +2914,14 @@
|
||||
# @blkreplay: Since 4.2
|
||||
# @compress: Since 5.0
|
||||
# @copy-before-write: Since 6.2
|
||||
# @snapshot-access: Since 7.0
|
||||
#
|
||||
# Since: 2.9
|
||||
##
|
||||
{ 'enum': 'BlockdevDriver',
|
||||
'data': [ 'blkdebug', 'blklogwrites', 'blkreplay', 'blkverify', 'bochs',
|
||||
'cloop', 'compress', 'copy-before-write', 'copy-on-read', 'dmg',
|
||||
'file', 'ftp', 'ftps', 'gluster',
|
||||
'file', 'snapshot-access', 'ftp', 'ftps', 'gluster',
|
||||
{'name': 'host_cdrom', 'if': 'HAVE_HOST_BLOCK_DEVICE' },
|
||||
{'name': 'host_device', 'if': 'HAVE_HOST_BLOCK_DEVICE' },
|
||||
'http', 'https', 'iscsi',
|
||||
@ -4171,11 +4172,19 @@
|
||||
#
|
||||
# @target: The target for copy-before-write operations.
|
||||
#
|
||||
# @bitmap: If specified, copy-before-write filter will do
|
||||
# copy-before-write operations only for dirty regions of the
|
||||
# bitmap. Bitmap size must be equal to length of file and
|
||||
# target child of the filter. Note also, that bitmap is used
|
||||
# only to initialize internal bitmap of the process, so further
|
||||
# modifications (or removing) of specified bitmap doesn't
|
||||
# influence the filter. (Since 7.0)
|
||||
#
|
||||
# Since: 6.2
|
||||
##
|
||||
{ 'struct': 'BlockdevOptionsCbw',
|
||||
'base': 'BlockdevOptionsGenericFormat',
|
||||
'data': { 'target': 'BlockdevRef' } }
|
||||
'data': { 'target': 'BlockdevRef', '*bitmap': 'BlockDirtyBitmap' } }
|
||||
|
||||
##
|
||||
# @BlockdevOptions:
|
||||
@ -4259,6 +4268,7 @@
|
||||
'rbd': 'BlockdevOptionsRbd',
|
||||
'replication': { 'type': 'BlockdevOptionsReplication',
|
||||
'if': 'CONFIG_REPLICATION' },
|
||||
'snapshot-access': 'BlockdevOptionsGenericFormat',
|
||||
'ssh': 'BlockdevOptionsSsh',
|
||||
'throttle': 'BlockdevOptionsThrottle',
|
||||
'vdi': 'BlockdevOptionsGenericFormat',
|
||||
|
@ -744,6 +744,7 @@ class TestCommitWithFilters(iotests.QMPTestCase):
|
||||
pattern_file)
|
||||
self.assertFalse('Pattern verification failed' in result)
|
||||
|
||||
@iotests.skip_if_unsupported(['throttle'])
|
||||
def setUp(self):
|
||||
qemu_img('create', '-f', iotests.imgfmt, self.img0, '64M')
|
||||
qemu_img('create', '-f', iotests.imgfmt, self.img1, '64M')
|
||||
|
@ -106,6 +106,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -566,6 +582,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -819,6 +851,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -1279,6 +1327,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -1532,6 +1596,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -1992,6 +2072,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -2245,6 +2341,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -2705,6 +2817,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -2958,6 +3086,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -3418,6 +3562,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -3671,6 +3831,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -4131,6 +4307,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -4384,6 +4576,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
@ -4844,6 +5052,22 @@ write -P0x67 0x3fe0000 0x20000
|
||||
{"return": ""}
|
||||
{
|
||||
"bitmaps": {
|
||||
"backup-top": [
|
||||
{
|
||||
"busy": false,
|
||||
"count": 67108864,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
},
|
||||
{
|
||||
"busy": false,
|
||||
"count": 458752,
|
||||
"granularity": 65536,
|
||||
"persistent": false,
|
||||
"recording": false
|
||||
}
|
||||
],
|
||||
"drive0": [
|
||||
{
|
||||
"busy": false,
|
||||
|
@ -20,7 +20,7 @@
|
||||
# bail out, setting up .notrun file
|
||||
_notrun()
|
||||
{
|
||||
echo "$*" >"$OUTPUT_DIR/$seq.notrun"
|
||||
echo "$*" >"$TEST_DIR/$seq.notrun"
|
||||
echo "$seq not run: $*"
|
||||
status=0
|
||||
exit
|
||||
@ -739,14 +739,14 @@ _img_info()
|
||||
#
|
||||
_casenotrun()
|
||||
{
|
||||
echo " [case not run] $*" >>"$OUTPUT_DIR/$seq.casenotrun"
|
||||
echo " [case not run] $*" >>"$TEST_DIR/$seq.casenotrun"
|
||||
}
|
||||
|
||||
# just plain bail out
|
||||
#
|
||||
_fail()
|
||||
{
|
||||
echo "$*" | tee -a "$OUTPUT_DIR/$seq.full"
|
||||
echo "$*" | tee -a "$TEST_DIR/$seq.full"
|
||||
echo "(see $seq.full for details)"
|
||||
status=1
|
||||
exit 1
|
||||
|
@ -85,7 +85,6 @@ qemu_print = os.environ.get('PRINT_QEMU', False)
|
||||
|
||||
imgfmt = os.environ.get('IMGFMT', 'raw')
|
||||
imgproto = os.environ.get('IMGPROTO', 'file')
|
||||
output_dir = os.environ.get('OUTPUT_DIR', '.')
|
||||
|
||||
try:
|
||||
test_dir = os.environ['TEST_DIR']
|
||||
@ -279,6 +278,9 @@ def qemu_io(*args):
|
||||
'''Run qemu-io and return the stdout data'''
|
||||
return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args))[0]
|
||||
|
||||
def qemu_io_pipe_and_status(*args):
|
||||
return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args))
|
||||
|
||||
def qemu_io_log(*args):
|
||||
result = qemu_io(*args)
|
||||
log(result, filters=[filter_testfiles, filter_qemu_io])
|
||||
@ -1239,7 +1241,7 @@ def notrun(reason):
|
||||
# Each test in qemu-iotests has a number ("seq")
|
||||
seq = os.path.basename(sys.argv[0])
|
||||
|
||||
with open('%s/%s.notrun' % (output_dir, seq), 'w', encoding='utf-8') \
|
||||
with open('%s/%s.notrun' % (test_dir, seq), 'w', encoding='utf-8') \
|
||||
as outfile:
|
||||
outfile.write(reason + '\n')
|
||||
logger.warning("%s not run: %s", seq, reason)
|
||||
@ -1254,7 +1256,7 @@ def case_notrun(reason):
|
||||
# Each test in qemu-iotests has a number ("seq")
|
||||
seq = os.path.basename(sys.argv[0])
|
||||
|
||||
with open('%s/%s.casenotrun' % (output_dir, seq), 'a', encoding='utf-8') \
|
||||
with open('%s/%s.casenotrun' % (test_dir, seq), 'a', encoding='utf-8') \
|
||||
as outfile:
|
||||
outfile.write(' [case not run] ' + reason + '\n')
|
||||
|
||||
|
@ -66,7 +66,7 @@ class TestEnv(ContextManager['TestEnv']):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
|
||||
env_variables = ['PYTHONPATH', 'TEST_DIR', 'SOCK_DIR', 'SAMPLE_IMG_DIR',
|
||||
'OUTPUT_DIR', 'PYTHON', 'QEMU_PROG', 'QEMU_IMG_PROG',
|
||||
'PYTHON', 'QEMU_PROG', 'QEMU_IMG_PROG',
|
||||
'QEMU_IO_PROG', 'QEMU_NBD_PROG', 'QSD_PROG',
|
||||
'QEMU_OPTIONS', 'QEMU_IMG_OPTIONS',
|
||||
'QEMU_IO_OPTIONS', 'QEMU_IO_OPTIONS_NO_FMT',
|
||||
@ -106,7 +106,6 @@ class TestEnv(ContextManager['TestEnv']):
|
||||
TEST_DIR
|
||||
SOCK_DIR
|
||||
SAMPLE_IMG_DIR
|
||||
OUTPUT_DIR
|
||||
"""
|
||||
|
||||
# Path where qemu goodies live in this source tree.
|
||||
@ -134,8 +133,6 @@ class TestEnv(ContextManager['TestEnv']):
|
||||
os.path.join(self.source_iotests,
|
||||
'sample_images'))
|
||||
|
||||
self.output_dir = os.getcwd() # OUTPUT_DIR
|
||||
|
||||
def init_binaries(self) -> None:
|
||||
"""Init binary path variables:
|
||||
PYTHON (for bash tests)
|
||||
|
@ -259,9 +259,6 @@ class TestRunner(ContextManager['TestRunner']):
|
||||
"""
|
||||
|
||||
f_test = Path(test)
|
||||
f_bad = Path(f_test.name + '.out.bad')
|
||||
f_notrun = Path(f_test.name + '.notrun')
|
||||
f_casenotrun = Path(f_test.name + '.casenotrun')
|
||||
f_reference = Path(self.find_reference(test))
|
||||
|
||||
if not f_test.exists():
|
||||
@ -276,9 +273,6 @@ class TestRunner(ContextManager['TestRunner']):
|
||||
description='No qualified output '
|
||||
f'(expected {f_reference})')
|
||||
|
||||
for p in (f_bad, f_notrun, f_casenotrun):
|
||||
silent_unlink(p)
|
||||
|
||||
args = [str(f_test.resolve())]
|
||||
env = self.env.prepare_subprocess(args)
|
||||
if mp:
|
||||
@ -288,6 +282,14 @@ class TestRunner(ContextManager['TestRunner']):
|
||||
env[d] = os.path.join(env[d], f_test.name)
|
||||
Path(env[d]).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
test_dir = env['TEST_DIR']
|
||||
f_bad = Path(test_dir, f_test.name + '.out.bad')
|
||||
f_notrun = Path(test_dir, f_test.name + '.notrun')
|
||||
f_casenotrun = Path(test_dir, f_test.name + '.casenotrun')
|
||||
|
||||
for p in (f_notrun, f_casenotrun):
|
||||
silent_unlink(p)
|
||||
|
||||
t0 = time.time()
|
||||
with f_bad.open('w', encoding="utf-8") as f:
|
||||
with subprocess.Popen(args, cwd=str(f_test.parent), env=env,
|
||||
@ -365,7 +367,10 @@ class TestRunner(ContextManager['TestRunner']):
|
||||
description=res.description)
|
||||
|
||||
if res.casenotrun:
|
||||
print(res.casenotrun)
|
||||
if self.tap:
|
||||
print('#' + res.casenotrun.replace('\n', '\n#'))
|
||||
else:
|
||||
print(res.casenotrun)
|
||||
|
||||
return res
|
||||
|
||||
|
@ -23,12 +23,14 @@
|
||||
# Creator/Owner: John Snow <jsnow@redhat.com>
|
||||
|
||||
import iotests
|
||||
from iotests import log, qemu_img, qemu_io, qemu_io_silent
|
||||
from iotests import log, qemu_img, qemu_io, qemu_io_silent, \
|
||||
qemu_io_pipe_and_status
|
||||
|
||||
iotests.script_initialize(
|
||||
supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk', 'vhdx', 'raw'],
|
||||
supported_fmts=['qcow2'],
|
||||
supported_platforms=['linux'],
|
||||
required_fmts=['copy-before-write'],
|
||||
unsupported_imgopts=['compat']
|
||||
)
|
||||
|
||||
patterns = [('0x5d', '0', '64k'),
|
||||
@ -49,12 +51,30 @@ remainder = [('0xd5', '0x108000', '32k'), # Right-end of partial-left [1]
|
||||
('0xdc', '32M', '32k'), # Left-end of partial-right [2]
|
||||
('0xcd', '0x3ff0000', '64k')] # patterns[3]
|
||||
|
||||
def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
def do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path,
|
||||
fleece_img_path, nbd_sock_path=None,
|
||||
target_img_path=None,
|
||||
bitmap=False):
|
||||
push_backup = target_img_path is not None
|
||||
assert (nbd_sock_path is not None) != push_backup
|
||||
if push_backup:
|
||||
assert use_cbw
|
||||
|
||||
log('--- Setting up images ---')
|
||||
log('')
|
||||
|
||||
assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0
|
||||
assert qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') == 0
|
||||
if bitmap:
|
||||
assert qemu_img('bitmap', '--add', base_img_path, 'bitmap0') == 0
|
||||
|
||||
if use_snapshot_access_filter:
|
||||
assert use_cbw
|
||||
assert qemu_img('create', '-f', 'raw', fleece_img_path, '64M') == 0
|
||||
else:
|
||||
assert qemu_img('create', '-f', 'qcow2', fleece_img_path, '64M') == 0
|
||||
|
||||
if push_backup:
|
||||
assert qemu_img('create', '-f', 'qcow2', target_img_path, '64M') == 0
|
||||
|
||||
for p in patterns:
|
||||
qemu_io('-f', iotests.imgfmt,
|
||||
@ -81,27 +101,46 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
log('')
|
||||
|
||||
|
||||
# create tmp_node backed by src_node
|
||||
log(vm.qmp('blockdev-add', {
|
||||
'driver': 'qcow2',
|
||||
'node-name': tmp_node,
|
||||
'file': {
|
||||
if use_snapshot_access_filter:
|
||||
log(vm.qmp('blockdev-add', {
|
||||
'node-name': tmp_node,
|
||||
'driver': 'file',
|
||||
'filename': fleece_img_path,
|
||||
},
|
||||
'backing': src_node,
|
||||
}))
|
||||
}))
|
||||
else:
|
||||
# create tmp_node backed by src_node
|
||||
log(vm.qmp('blockdev-add', {
|
||||
'driver': 'qcow2',
|
||||
'node-name': tmp_node,
|
||||
'file': {
|
||||
'driver': 'file',
|
||||
'filename': fleece_img_path,
|
||||
},
|
||||
'backing': src_node,
|
||||
}))
|
||||
|
||||
# Establish CBW from source to fleecing node
|
||||
if use_cbw:
|
||||
log(vm.qmp('blockdev-add', {
|
||||
fl_cbw = {
|
||||
'driver': 'copy-before-write',
|
||||
'node-name': 'fl-cbw',
|
||||
'file': src_node,
|
||||
'target': tmp_node
|
||||
}))
|
||||
}
|
||||
|
||||
if bitmap:
|
||||
fl_cbw['bitmap'] = {'node': src_node, 'name': 'bitmap0'}
|
||||
|
||||
log(vm.qmp('blockdev-add', fl_cbw))
|
||||
|
||||
log(vm.qmp('qom-set', path=qom_path, property='drive', value='fl-cbw'))
|
||||
|
||||
if use_snapshot_access_filter:
|
||||
log(vm.qmp('blockdev-add', {
|
||||
'driver': 'snapshot-access',
|
||||
'node-name': 'fl-access',
|
||||
'file': 'fl-cbw',
|
||||
}))
|
||||
else:
|
||||
log(vm.qmp('blockdev-backup',
|
||||
job_id='fleecing',
|
||||
@ -109,25 +148,47 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
target=tmp_node,
|
||||
sync='none'))
|
||||
|
||||
log('')
|
||||
log('--- Setting up NBD Export ---')
|
||||
log('')
|
||||
export_node = 'fl-access' if use_snapshot_access_filter else tmp_node
|
||||
|
||||
nbd_uri = 'nbd+unix:///%s?socket=%s' % (tmp_node, nbd_sock_path)
|
||||
log(vm.qmp('nbd-server-start',
|
||||
{'addr': {'type': 'unix',
|
||||
'data': {'path': nbd_sock_path}}}))
|
||||
if push_backup:
|
||||
log('')
|
||||
log('--- Starting actual backup ---')
|
||||
log('')
|
||||
|
||||
log(vm.qmp('nbd-server-add', device=tmp_node))
|
||||
log(vm.qmp('blockdev-add', **{
|
||||
'driver': iotests.imgfmt,
|
||||
'node-name': 'target',
|
||||
'file': {
|
||||
'driver': 'file',
|
||||
'filename': target_img_path
|
||||
}
|
||||
}))
|
||||
log(vm.qmp('blockdev-backup', device=export_node,
|
||||
sync='full', target='target',
|
||||
job_id='push-backup', speed=1))
|
||||
else:
|
||||
log('')
|
||||
log('--- Setting up NBD Export ---')
|
||||
log('')
|
||||
|
||||
log('')
|
||||
log('--- Sanity Check ---')
|
||||
log('')
|
||||
nbd_uri = 'nbd+unix:///%s?socket=%s' % (export_node, nbd_sock_path)
|
||||
log(vm.qmp('nbd-server-start',
|
||||
{'addr': { 'type': 'unix',
|
||||
'data': { 'path': nbd_sock_path } } }))
|
||||
|
||||
for p in patterns + zeroes:
|
||||
cmd = 'read -P%s %s %s' % p
|
||||
log(cmd)
|
||||
assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
|
||||
log(vm.qmp('nbd-server-add', device=export_node))
|
||||
|
||||
log('')
|
||||
log('--- Sanity Check ---')
|
||||
log('')
|
||||
|
||||
for p in patterns + zeroes:
|
||||
cmd = 'read -P%s %s %s' % p
|
||||
log(cmd)
|
||||
out, ret = qemu_io_pipe_and_status('-r', '-f', 'raw', '-c', cmd,
|
||||
nbd_uri)
|
||||
if ret != 0:
|
||||
print(out)
|
||||
|
||||
log('')
|
||||
log('--- Testing COW ---')
|
||||
@ -138,6 +199,23 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
log(cmd)
|
||||
log(vm.hmp_qemu_io(qom_path, cmd, qdev=True))
|
||||
|
||||
if push_backup:
|
||||
# Check that previous operations were done during backup, not after
|
||||
# If backup is already finished, it's possible that it was finished
|
||||
# even before hmp qemu_io write, and we didn't actually test
|
||||
# copy-before-write operation. This should not happen, as we use
|
||||
# speed=1. But worth checking.
|
||||
result = vm.qmp('query-block-jobs')
|
||||
assert len(result['return']) == 1
|
||||
|
||||
result = vm.qmp('block-job-set-speed', device='push-backup', speed=0)
|
||||
assert result == {'return': {}}
|
||||
|
||||
log(vm.event_wait(name='BLOCK_JOB_COMPLETED',
|
||||
match={'data': {'device': 'push-backup'}}),
|
||||
filters=[iotests.filter_qmp_event])
|
||||
log(vm.qmp('blockdev-del', node_name='target'))
|
||||
|
||||
log('')
|
||||
log('--- Verifying Data ---')
|
||||
log('')
|
||||
@ -145,13 +223,25 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
for p in patterns + zeroes:
|
||||
cmd = 'read -P%s %s %s' % p
|
||||
log(cmd)
|
||||
assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0
|
||||
args = ['-r', '-c', cmd]
|
||||
if push_backup:
|
||||
args += [target_img_path]
|
||||
else:
|
||||
args += ['-f', 'raw', nbd_uri]
|
||||
out, ret = qemu_io_pipe_and_status(*args)
|
||||
if ret != 0:
|
||||
print(out)
|
||||
|
||||
log('')
|
||||
log('--- Cleanup ---')
|
||||
log('')
|
||||
|
||||
if not push_backup:
|
||||
log(vm.qmp('nbd-server-stop'))
|
||||
|
||||
if use_cbw:
|
||||
if use_snapshot_access_filter:
|
||||
log(vm.qmp('blockdev-del', node_name='fl-access'))
|
||||
log(vm.qmp('qom-set', path=qom_path, property='drive', value=src_node))
|
||||
log(vm.qmp('blockdev-del', node_name='fl-cbw'))
|
||||
else:
|
||||
@ -160,7 +250,6 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
assert e is not None
|
||||
log(e, filters=[iotests.filter_qmp_event])
|
||||
|
||||
log(vm.qmp('nbd-server-stop'))
|
||||
log(vm.qmp('blockdev-del', node_name=tmp_node))
|
||||
vm.shutdown()
|
||||
|
||||
@ -177,17 +266,37 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
|
||||
log('Done')
|
||||
|
||||
|
||||
def test(use_cbw):
|
||||
def test(use_cbw, use_snapshot_access_filter,
|
||||
nbd_sock_path=None, target_img_path=None, bitmap=False):
|
||||
with iotests.FilePath('base.img') as base_img_path, \
|
||||
iotests.FilePath('fleece.img') as fleece_img_path, \
|
||||
iotests.FilePath('nbd.sock',
|
||||
base_dir=iotests.sock_dir) as nbd_sock_path, \
|
||||
iotests.VM() as vm:
|
||||
do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm)
|
||||
do_test(vm, use_cbw, use_snapshot_access_filter, base_img_path,
|
||||
fleece_img_path, nbd_sock_path, target_img_path,
|
||||
bitmap=bitmap)
|
||||
|
||||
def test_pull(use_cbw, use_snapshot_access_filter, bitmap=False):
|
||||
with iotests.FilePath('nbd.sock',
|
||||
base_dir=iotests.sock_dir) as nbd_sock_path:
|
||||
test(use_cbw, use_snapshot_access_filter, nbd_sock_path, None,
|
||||
bitmap=bitmap)
|
||||
|
||||
def test_push():
|
||||
with iotests.FilePath('target.img') as target_img_path:
|
||||
test(True, True, None, target_img_path)
|
||||
|
||||
|
||||
log('=== Test backup(sync=none) based fleecing ===\n')
|
||||
test(False)
|
||||
test_pull(False, False)
|
||||
|
||||
log('=== Test filter based fleecing ===\n')
|
||||
test(True)
|
||||
log('=== Test cbw-filter based fleecing ===\n')
|
||||
test_pull(True, False)
|
||||
|
||||
log('=== Test fleecing-format based fleecing ===\n')
|
||||
test_pull(True, True)
|
||||
|
||||
log('=== Test fleecing-format based fleecing with bitmap ===\n')
|
||||
test_pull(True, True, bitmap=True)
|
||||
|
||||
log('=== Test push backup with fleecing ===\n')
|
||||
test_push()
|
||||
|
@ -52,8 +52,8 @@ read -P0 0x3fe0000 64k
|
||||
--- Cleanup ---
|
||||
|
||||
{"return": {}}
|
||||
{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
|
||||
{"return": {}}
|
||||
{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
|
||||
{"return": {}}
|
||||
|
||||
--- Confirming writes ---
|
||||
@ -67,7 +67,7 @@ read -P0xdc 32M 32k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
|
||||
Done
|
||||
=== Test filter based fleecing ===
|
||||
=== Test cbw-filter based fleecing ===
|
||||
|
||||
--- Setting up images ---
|
||||
|
||||
@ -137,3 +137,222 @@ read -P0xdc 32M 32k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
|
||||
Done
|
||||
=== Test fleecing-format based fleecing ===
|
||||
|
||||
--- Setting up images ---
|
||||
|
||||
Done
|
||||
|
||||
--- Launching VM ---
|
||||
|
||||
Done
|
||||
|
||||
--- Setting up Fleecing Graph ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Setting up NBD Export ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Sanity Check ---
|
||||
|
||||
read -P0x5d 0 64k
|
||||
read -P0xd5 1M 64k
|
||||
read -P0xdc 32M 64k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
read -P0 0x00f8000 32k
|
||||
read -P0 0x2010000 32k
|
||||
read -P0 0x3fe0000 64k
|
||||
|
||||
--- Testing COW ---
|
||||
|
||||
write -P0xab 0 64k
|
||||
{"return": ""}
|
||||
write -P0xad 0x00f8000 64k
|
||||
{"return": ""}
|
||||
write -P0x1d 0x2008000 64k
|
||||
{"return": ""}
|
||||
write -P0xea 0x3fe0000 64k
|
||||
{"return": ""}
|
||||
|
||||
--- Verifying Data ---
|
||||
|
||||
read -P0x5d 0 64k
|
||||
read -P0xd5 1M 64k
|
||||
read -P0xdc 32M 64k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
read -P0 0x00f8000 32k
|
||||
read -P0 0x2010000 32k
|
||||
read -P0 0x3fe0000 64k
|
||||
|
||||
--- Cleanup ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Confirming writes ---
|
||||
|
||||
read -P0xab 0 64k
|
||||
read -P0xad 0x00f8000 64k
|
||||
read -P0x1d 0x2008000 64k
|
||||
read -P0xea 0x3fe0000 64k
|
||||
read -P0xd5 0x108000 32k
|
||||
read -P0xdc 32M 32k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
|
||||
Done
|
||||
=== Test fleecing-format based fleecing with bitmap ===
|
||||
|
||||
--- Setting up images ---
|
||||
|
||||
Done
|
||||
|
||||
--- Launching VM ---
|
||||
|
||||
Done
|
||||
|
||||
--- Setting up Fleecing Graph ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Setting up NBD Export ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Sanity Check ---
|
||||
|
||||
read -P0x5d 0 64k
|
||||
read -P0xd5 1M 64k
|
||||
read -P0xdc 32M 64k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
read -P0 0x00f8000 32k
|
||||
read failed: Invalid argument
|
||||
|
||||
read -P0 0x2010000 32k
|
||||
read failed: Invalid argument
|
||||
|
||||
read -P0 0x3fe0000 64k
|
||||
read failed: Invalid argument
|
||||
|
||||
|
||||
--- Testing COW ---
|
||||
|
||||
write -P0xab 0 64k
|
||||
{"return": ""}
|
||||
write -P0xad 0x00f8000 64k
|
||||
{"return": ""}
|
||||
write -P0x1d 0x2008000 64k
|
||||
{"return": ""}
|
||||
write -P0xea 0x3fe0000 64k
|
||||
{"return": ""}
|
||||
|
||||
--- Verifying Data ---
|
||||
|
||||
read -P0x5d 0 64k
|
||||
read -P0xd5 1M 64k
|
||||
read -P0xdc 32M 64k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
read -P0 0x00f8000 32k
|
||||
read failed: Invalid argument
|
||||
|
||||
read -P0 0x2010000 32k
|
||||
read failed: Invalid argument
|
||||
|
||||
read -P0 0x3fe0000 64k
|
||||
read failed: Invalid argument
|
||||
|
||||
|
||||
--- Cleanup ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Confirming writes ---
|
||||
|
||||
read -P0xab 0 64k
|
||||
read -P0xad 0x00f8000 64k
|
||||
read -P0x1d 0x2008000 64k
|
||||
read -P0xea 0x3fe0000 64k
|
||||
read -P0xd5 0x108000 32k
|
||||
read -P0xdc 32M 32k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
|
||||
Done
|
||||
=== Test push backup with fleecing ===
|
||||
|
||||
--- Setting up images ---
|
||||
|
||||
Done
|
||||
|
||||
--- Launching VM ---
|
||||
|
||||
Done
|
||||
|
||||
--- Setting up Fleecing Graph ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Starting actual backup ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Testing COW ---
|
||||
|
||||
write -P0xab 0 64k
|
||||
{"return": ""}
|
||||
write -P0xad 0x00f8000 64k
|
||||
{"return": ""}
|
||||
write -P0x1d 0x2008000 64k
|
||||
{"return": ""}
|
||||
write -P0xea 0x3fe0000 64k
|
||||
{"return": ""}
|
||||
{"data": {"device": "push-backup", "len": 67108864, "offset": 67108864, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
|
||||
{"return": {}}
|
||||
|
||||
--- Verifying Data ---
|
||||
|
||||
read -P0x5d 0 64k
|
||||
read -P0xd5 1M 64k
|
||||
read -P0xdc 32M 64k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
read -P0 0x00f8000 32k
|
||||
read -P0 0x2010000 32k
|
||||
read -P0 0x3fe0000 64k
|
||||
|
||||
--- Cleanup ---
|
||||
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
{"return": {}}
|
||||
|
||||
--- Confirming writes ---
|
||||
|
||||
read -P0xab 0 64k
|
||||
read -P0xad 0x00f8000 64k
|
||||
read -P0x1d 0x2008000 64k
|
||||
read -P0xea 0x3fe0000 64k
|
||||
read -P0xd5 0x108000 32k
|
||||
read -P0xdc 32M 32k
|
||||
read -P0xcd 0x3ff0000 64k
|
||||
|
||||
Done
|
||||
|
@ -301,6 +301,39 @@ bool hbitmap_next_dirty_area(const HBitmap *hb, int64_t start, int64_t end,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hbitmap_status(const HBitmap *hb, int64_t start, int64_t count,
|
||||
int64_t *pnum)
|
||||
{
|
||||
int64_t next_dirty, next_zero;
|
||||
|
||||
assert(start >= 0);
|
||||
assert(count > 0);
|
||||
assert(start + count <= hb->orig_size);
|
||||
|
||||
next_dirty = hbitmap_next_dirty(hb, start, count);
|
||||
if (next_dirty == -1) {
|
||||
*pnum = count;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (next_dirty > start) {
|
||||
*pnum = next_dirty - start;
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(next_dirty == start);
|
||||
|
||||
next_zero = hbitmap_next_zero(hb, start, count);
|
||||
if (next_zero == -1) {
|
||||
*pnum = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(next_zero > start);
|
||||
*pnum = next_zero - start;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hbitmap_empty(const HBitmap *hb)
|
||||
{
|
||||
return hb->count == 0;
|
||||
|
Loading…
Reference in New Issue
Block a user