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:
Peter Maydell 2022-03-07 17:14:09 +00:00
commit b49872aa8f
29 changed files with 1501 additions and 197 deletions

View File

@ -2515,9 +2515,12 @@ F: block/stream.c
F: block/mirror.c F: block/mirror.c
F: qapi/job.json F: qapi/job.json
F: block/block-copy.c 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.h
F: block/copy-before-write.c F: block/copy-before-write.c
F: block/snapshot-access.c
F: include/block/aio_task.h F: include/block/aio_task.h
F: block/aio_task.c F: block/aio_task.c
F: util/qemu-co-shared-resource.c F: util/qemu-co-shared-resource.c

View File

@ -17,6 +17,7 @@
#include "trace.h" #include "trace.h"
#include "qapi/error.h" #include "qapi/error.h"
#include "block/block-copy.h" #include "block/block-copy.h"
#include "block/reqlist.h"
#include "sysemu/block-backend.h" #include "sysemu/block-backend.h"
#include "qemu/units.h" #include "qemu/units.h"
#include "qemu/coroutine.h" #include "qemu/coroutine.h"
@ -83,7 +84,6 @@ typedef struct BlockCopyTask {
*/ */
BlockCopyState *s; BlockCopyState *s;
BlockCopyCallState *call_state; BlockCopyCallState *call_state;
int64_t offset;
/* /*
* @method can also be set again in the while loop of * @method can also be set again in the while loop of
* block_copy_dirty_clusters(), but it is never accessed concurrently * block_copy_dirty_clusters(), but it is never accessed concurrently
@ -94,21 +94,17 @@ typedef struct BlockCopyTask {
BlockCopyMethod method; BlockCopyMethod method;
/* /*
* Fields whose state changes throughout the execution * Generally, req is protected by lock in BlockCopyState, Still req.offset
* Protected by lock in BlockCopyState. * 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 */ BlockReq req;
/*
* Only protect the case of parallel read while updating @bytes
* value in block_copy_task_shrink().
*/
int64_t bytes;
QLIST_ENTRY(BlockCopyTask) list;
} BlockCopyTask; } BlockCopyTask;
static int64_t task_end(BlockCopyTask *task) static int64_t task_end(BlockCopyTask *task)
{ {
return task->offset + task->bytes; return task->req.offset + task->req.bytes;
} }
typedef struct BlockCopyState { typedef struct BlockCopyState {
@ -136,7 +132,7 @@ typedef struct BlockCopyState {
CoMutex lock; CoMutex lock;
int64_t in_flight_bytes; int64_t in_flight_bytes;
BlockCopyMethod method; BlockCopyMethod method;
QLIST_HEAD(, BlockCopyTask) tasks; /* All tasks from all block-copy calls */ BlockReqList reqs;
QLIST_HEAD(, BlockCopyCallState) calls; QLIST_HEAD(, BlockCopyCallState) calls;
/* /*
* skip_unallocated: * skip_unallocated:
@ -160,42 +156,6 @@ typedef struct BlockCopyState {
RateLimit rate_limit; RateLimit rate_limit;
} BlockCopyState; } 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 */ /* Called with lock held */
static int64_t block_copy_chunk_size(BlockCopyState *s) 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); bytes = QEMU_ALIGN_UP(bytes, s->cluster_size);
/* region is dirty, so no existent tasks possible in it */ /* 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); bdrv_reset_dirty_bitmap(s->copy_bitmap, offset, bytes);
s->in_flight_bytes += 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, .task.func = block_copy_task_entry,
.s = s, .s = s,
.call_state = call_state, .call_state = call_state,
.offset = offset,
.bytes = bytes,
.method = s->method, .method = s->method,
}; };
qemu_co_queue_init(&task->wait_queue); reqlist_init_req(&s->reqs, &task->req, offset, bytes);
QLIST_INSERT_HEAD(&s->tasks, task, list);
return task; return task;
} }
@ -270,34 +227,34 @@ static void coroutine_fn block_copy_task_shrink(BlockCopyTask *task,
int64_t new_bytes) int64_t new_bytes)
{ {
QEMU_LOCK_GUARD(&task->s->lock); QEMU_LOCK_GUARD(&task->s->lock);
if (new_bytes == task->bytes) { if (new_bytes == task->req.bytes) {
return; 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, 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; reqlist_shrink_req(&task->req, new_bytes);
qemu_co_queue_restart_all(&task->wait_queue);
} }
static void coroutine_fn block_copy_task_end(BlockCopyTask *task, int ret) static void coroutine_fn block_copy_task_end(BlockCopyTask *task, int ret)
{ {
QEMU_LOCK_GUARD(&task->s->lock); QEMU_LOCK_GUARD(&task->s->lock);
task->s->in_flight_bytes -= task->bytes; task->s->in_flight_bytes -= task->req.bytes;
if (ret < 0) { 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) { if (task->s->progress) {
progress_set_remaining(task->s->progress, progress_set_remaining(task->s->progress,
bdrv_get_dirty_count(task->s->copy_bitmap) + bdrv_get_dirty_count(task->s->copy_bitmap) +
task->s->in_flight_bytes); 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) 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, BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
const BdrvDirtyBitmap *bitmap,
Error **errp) Error **errp)
{ {
ERRP_GUARD();
BlockCopyState *s; BlockCopyState *s;
int64_t cluster_size; int64_t cluster_size;
BdrvDirtyBitmap *copy_bitmap; BdrvDirtyBitmap *copy_bitmap;
@ -402,6 +361,17 @@ BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
return NULL; return NULL;
} }
bdrv_disable_dirty_bitmap(copy_bitmap); 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 * 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); ratelimit_init(&s->rate_limit);
qemu_co_mutex_init(&s->lock); qemu_co_mutex_init(&s->lock);
QLIST_INIT(&s->tasks); QLIST_INIT(&s->reqs);
QLIST_INIT(&s->calls); QLIST_INIT(&s->calls);
return s; return s;
@ -470,7 +440,7 @@ static coroutine_fn int block_copy_task_run(AioTaskPool *pool,
aio_task_pool_wait_slot(pool); aio_task_pool_wait_slot(pool);
if (aio_task_pool_status(pool) < 0) { 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); block_copy_task_end(task, -ECANCELED);
g_free(task); g_free(task);
return -ECANCELED; return -ECANCELED;
@ -583,7 +553,8 @@ static coroutine_fn int block_copy_task_entry(AioTask *task)
BlockCopyMethod method = t->method; BlockCopyMethod method = t->method;
int ret; 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) { WITH_QEMU_LOCK_GUARD(&s->lock) {
if (s->method == t->method) { 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; t->call_state->error_is_read = error_is_read;
} }
} else if (s->progress) { } 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); block_copy_task_end(t, ret);
return 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 * Reset bits in copy_bitmap starting at offset if they represent unallocated
* data in the image. May reset subsequent contiguous bits. * 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; bytes = clusters * s->cluster_size;
if (!ret) { if (!ret) {
qemu_co_mutex_lock(&s->lock); block_copy_reset(s, offset, bytes);
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);
} }
*count = bytes; *count = bytes;
@ -753,22 +729,22 @@ block_copy_dirty_clusters(BlockCopyCallState *call_state)
trace_block_copy_skip_range(s, offset, bytes); trace_block_copy_skip_range(s, offset, bytes);
break; break;
} }
if (task->offset > offset) { if (task->req.offset > offset) {
trace_block_copy_skip_range(s, offset, task->offset - offset); trace_block_copy_skip_range(s, offset, task->req.offset - offset);
} }
found_dirty = true; 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); &status_bytes);
assert(ret >= 0); /* never fail */ assert(ret >= 0); /* never fail */
if (status_bytes < task->bytes) { if (status_bytes < task->req.bytes) {
block_copy_task_shrink(task, status_bytes); block_copy_task_shrink(task, status_bytes);
} }
if (qatomic_read(&s->skip_unallocated) && if (qatomic_read(&s->skip_unallocated) &&
!(ret & BDRV_BLOCK_ALLOCATED)) { !(ret & BDRV_BLOCK_ALLOCATED)) {
block_copy_task_end(task, 0); 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); offset = task_end(task);
bytes = end - offset; bytes = end - offset;
g_free(task); 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); offset = task_end(task);
bytes = end - offset; 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 * Check that there is no task we still need to
* wait to complete * wait to complete
*/ */
ret = block_copy_wait_one(s, call_state->offset, ret = reqlist_wait_one(&s->reqs, call_state->offset,
call_state->bytes); call_state->bytes, &s->lock);
if (ret == 0) { if (ret == 0) {
/* /*
* No pending tasks, but check again the bitmap in this * 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 * between this and the critical section in
* block_copy_dirty_clusters(). * 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 * didn't release the lock. So, we are still in the same
* critical section, not interrupted by any concurrent * critical section, not interrupted by any concurrent
* access to state. * access to state.

View File

@ -33,10 +33,37 @@
#include "block/block-copy.h" #include "block/block-copy.h"
#include "block/copy-before-write.h" #include "block/copy-before-write.h"
#include "block/reqlist.h"
#include "qapi/qapi-visit-block-core.h"
typedef struct BDRVCopyBeforeWriteState { typedef struct BDRVCopyBeforeWriteState {
BlockCopyState *bcs; BlockCopyState *bcs;
BdrvChild *target; 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; } BDRVCopyBeforeWriteState;
static coroutine_fn int cbw_co_preadv( 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); 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, static coroutine_fn int cbw_do_copy_before_write(BlockDriverState *bs,
uint64_t offset, uint64_t bytes, BdrvRequestFlags flags) uint64_t offset, uint64_t bytes, BdrvRequestFlags flags)
{ {
BDRVCopyBeforeWriteState *s = bs->opaque; BDRVCopyBeforeWriteState *s = bs->opaque;
int ret;
uint64_t off, end; uint64_t off, end;
int64_t cluster_size = block_copy_cluster_size(s->bcs); 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); off = QEMU_ALIGN_DOWN(offset, cluster_size);
end = QEMU_ALIGN_UP(offset + bytes, 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, 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); 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) static void cbw_refresh_filename(BlockDriverState *bs)
{ {
pstrcpy(bs->exact_filename, sizeof(bs->exact_filename), 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, static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
Error **errp) Error **errp)
{ {
BDRVCopyBeforeWriteState *s = bs->opaque; 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, bs->file = bdrv_open_child(NULL, options, "file", bs, &child_of_bds,
BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
@ -164,6 +390,10 @@ static int cbw_open(BlockDriverState *bs, QDict *options, int flags,
return -EINVAL; return -EINVAL;
} }
if (!cbw_parse_bitmap_option(options, &bitmap, errp)) {
return -EINVAL;
}
bs->total_sectors = bs->file->bs->total_sectors; bs->total_sectors = bs->file->bs->total_sectors;
bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED | bs->supported_write_flags = BDRV_REQ_WRITE_UNCHANGED |
(BDRV_REQ_FUA & bs->file->bs->supported_write_flags); (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) & ((BDRV_REQ_FUA | BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK) &
bs->file->bs->supported_zero_flags); 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) { if (!s->bcs) {
error_prepend(errp, "Cannot create block-copy-state: "); error_prepend(errp, "Cannot create block-copy-state: ");
return -EINVAL; return -EINVAL;
} }
copy_bitmap = block_copy_dirty_bitmap(s->bcs); cluster_size = block_copy_cluster_size(s->bcs);
bdrv_set_dirty_bitmap(copy_bitmap, 0, bdrv_dirty_bitmap_size(copy_bitmap));
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; return 0;
} }
@ -187,6 +435,9 @@ static void cbw_close(BlockDriverState *bs)
{ {
BDRVCopyBeforeWriteState *s = bs->opaque; BDRVCopyBeforeWriteState *s = bs->opaque;
bdrv_release_dirty_bitmap(s->access_bitmap);
bdrv_release_dirty_bitmap(s->done_bitmap);
block_copy_state_free(s->bcs); block_copy_state_free(s->bcs);
s->bcs = NULL; s->bcs = NULL;
} }
@ -204,6 +455,10 @@ BlockDriver bdrv_cbw_filter = {
.bdrv_co_pdiscard = cbw_co_pdiscard, .bdrv_co_pdiscard = cbw_co_pdiscard,
.bdrv_co_flush = cbw_co_flush, .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_refresh_filename = cbw_refresh_filename,
.bdrv_child_perm = cbw_child_perm, .bdrv_child_perm = cbw_child_perm,

View File

@ -458,38 +458,51 @@ static int curl_init_state(BDRVCURLState *s, CURLState *state)
if (!state->curl) { if (!state->curl) {
return -EIO; return -EIO;
} }
curl_easy_setopt(state->curl, CURLOPT_URL, s->url); if (curl_easy_setopt(state->curl, CURLOPT_URL, s->url) ||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER, curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYPEER,
(long) s->sslverify); (long) s->sslverify) ||
curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYHOST, curl_easy_setopt(state->curl, CURLOPT_SSL_VERIFYHOST,
s->sslverify ? 2L : 0L); s->sslverify ? 2L : 0L)) {
if (s->cookie) { goto err;
curl_easy_setopt(state->curl, CURLOPT_COOKIE, s->cookie); }
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) { 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) { 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) { if (s->proxyusername) {
curl_easy_setopt(state->curl, if (curl_easy_setopt(state->curl,
CURLOPT_PROXYUSERNAME, s->proxyusername); CURLOPT_PROXYUSERNAME, s->proxyusername)) {
goto err;
}
} }
if (s->proxypassword) { if (s->proxypassword) {
curl_easy_setopt(state->curl, if (curl_easy_setopt(state->curl,
CURLOPT_PROXYPASSWORD, s->proxypassword); CURLOPT_PROXYPASSWORD, s->proxypassword)) {
goto err;
}
} }
/* Restrict supported protocols to avoid security issues in the more /* 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. * Restricting protocols is only supported from 7.19.4 upwards.
*/ */
#if LIBCURL_VERSION_NUM >= 0x071304 #if LIBCURL_VERSION_NUM >= 0x071304
curl_easy_setopt(state->curl, CURLOPT_PROTOCOLS, PROTOCOLS); if (curl_easy_setopt(state->curl, CURLOPT_PROTOCOLS, PROTOCOLS) ||
curl_easy_setopt(state->curl, CURLOPT_REDIR_PROTOCOLS, PROTOCOLS); curl_easy_setopt(state->curl, CURLOPT_REDIR_PROTOCOLS, PROTOCOLS)) {
goto err;
}
#endif #endif
#ifdef DEBUG_VERBOSE #ifdef DEBUG_VERBOSE
curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1); if (curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1)) {
goto err;
}
#endif #endif
} }
state->s = s; state->s = s;
return 0; return 0;
err:
curl_easy_cleanup(state->curl);
state->curl = NULL;
return -EIO;
} }
/* Called with s->mutex held. */ /* Called with s->mutex held. */
@ -759,14 +781,19 @@ static int curl_open(BlockDriverState *bs, QDict *options, int flags,
// Get file size // Get file size
if (curl_init_state(s, state) < 0) { if (curl_init_state(s, state) < 0) {
pstrcpy(state->errmsg, CURL_ERROR_SIZE,
"curl library initialization failed.");
goto out; goto out;
} }
s->accept_range = false; s->accept_range = false;
curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1); if (curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1) ||
curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_easy_setopt(state->curl, CURLOPT_HEADERFUNCTION, curl_header_cb) ||
curl_header_cb); curl_easy_setopt(state->curl, CURLOPT_HEADERDATA, s)) {
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)) if (curl_easy_perform(state->curl))
goto out; goto out;
if (curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) { 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); snprintf(state->range, 127, "%" PRIu64 "-%" PRIu64, start, end);
trace_curl_setup_preadv(acb->bytes, start, state->range); trace_curl_setup_preadv(acb->bytes, start, state->range);
curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); if (curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range) ||
curl_multi_add_handle(s->multi, state->curl) != CURLM_OK) {
if (curl_multi_add_handle(s->multi, state->curl) != CURLM_OK) {
state->acb[0] = NULL; state->acb[0] = NULL;
acb->ret = -EIO; acb->ret = -EIO;

View File

@ -879,16 +879,25 @@ bool bdrv_dirty_bitmap_next_dirty_area(BdrvDirtyBitmap *bitmap,
dirty_start, dirty_count); 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. * bdrv_merge_dirty_bitmap: merge src into dest.
* Ensures permissions on bitmaps are reasonable; use for public API. * Ensures permissions on bitmaps are reasonable; use for public API.
* *
* @backup: If provided, make a copy of dest here prior to merge. * @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) HBitmap **backup, Error **errp)
{ {
bool ret; bool ret = false;
bdrv_dirty_bitmaps_lock(dest->bs); bdrv_dirty_bitmaps_lock(dest->bs);
if (src->bs != dest->bs) { if (src->bs != dest->bs) {
@ -916,6 +925,8 @@ out:
if (src->bs != dest->bs) { if (src->bs != dest->bs) {
bdrv_dirty_bitmaps_unlock(src->bs); bdrv_dirty_bitmaps_unlock(src->bs);
} }
return ret;
} }
/** /**

View File

@ -2203,6 +2203,7 @@ static int coroutine_fn bdrv_co_do_zero_pwritev(BdrvChild *child,
padding = bdrv_init_padding(bs, offset, bytes, &pad); padding = bdrv_init_padding(bs, offset, bytes, &pad);
if (padding) { if (padding) {
assert(!(flags & BDRV_REQ_NO_WAIT));
bdrv_make_request_serialising(req, align); bdrv_make_request_serialising(req, align);
bdrv_padding_rmw_read(child, req, &pad, true); 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 * serialize the request to prevent interactions of the
* widened region with other transactions. * widened region with other transactions.
*/ */
assert(!(flags & BDRV_REQ_NO_WAIT));
bdrv_make_request_serialising(&req, align); bdrv_make_request_serialising(&req, align);
bdrv_padding_rmw_read(child, &req, &pad, false); 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 */ /* TODO We can support BDRV_REQ_NO_FALLBACK here */
assert(!(read_flags & BDRV_REQ_NO_FALLBACK)); assert(!(read_flags & BDRV_REQ_NO_FALLBACK));
assert(!(write_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)) { if (!dst || !dst->bs || !bdrv_is_inserted(dst->bs)) {
return -ENOMEDIUM; return -ENOMEDIUM;
@ -3650,3 +3654,75 @@ void bdrv_cancel_in_flight(BlockDriverState *bs)
bs->drv->bdrv_cancel_in_flight(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;
}

View File

@ -32,7 +32,9 @@ block_ss.add(files(
'qcow2.c', 'qcow2.c',
'quorum.c', 'quorum.c',
'raw-format.c', 'raw-format.c',
'reqlist.c',
'snapshot.c', 'snapshot.c',
'snapshot-access.c',
'throttle-groups.c', 'throttle-groups.c',
'throttle.c', 'throttle.c',
'vhdx-endian.c', 'vhdx-endian.c',

View File

@ -263,7 +263,6 @@ BdrvDirtyBitmap *block_dirty_bitmap_merge(const char *node, const char *target,
BlockDriverState *bs; BlockDriverState *bs;
BdrvDirtyBitmap *dst, *src, *anon; BdrvDirtyBitmap *dst, *src, *anon;
BlockDirtyBitmapMergeSourceList *lst; BlockDirtyBitmapMergeSourceList *lst;
Error *local_err = NULL;
GLOBAL_STATE_CODE(); GLOBAL_STATE_CODE();
@ -303,9 +302,7 @@ BdrvDirtyBitmap *block_dirty_bitmap_merge(const char *node, const char *target,
abort(); abort();
} }
bdrv_merge_dirty_bitmap(anon, src, NULL, &local_err); if (!bdrv_merge_dirty_bitmap(anon, src, NULL, errp)) {
if (local_err) {
error_propagate(errp, local_err);
dst = NULL; dst = NULL;
goto out; goto out;
} }

View File

@ -276,6 +276,10 @@ static bool coroutine_fn handle_write(BlockDriverState *bs, int64_t offset,
int64_t end = offset + bytes; int64_t end = offset + bytes;
int64_t prealloc_start, prealloc_end; int64_t prealloc_start, prealloc_end;
int ret; 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)) { if (!has_prealloc_perms(bs)) {
/* We don't have state neither should try to recover it */ /* 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. */ /* 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_start = QEMU_ALIGN_UP(
prealloc_end = QEMU_ALIGN_UP(end + s->opts.prealloc_size, want_merge_zero ? MIN(offset, s->file_end) : s->file_end,
s->opts.prealloc_align); 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( ret = bdrv_co_pwrite_zeroes(
bs->file, prealloc_start, prealloc_end - prealloc_start, bs->file, prealloc_start, prealloc_end - prealloc_start,

85
block/reqlist.c Normal file
View 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
View 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);

View File

@ -434,12 +434,16 @@ static const AIOCBInfo trim_aiocb_info = {
static void ide_trim_bh_cb(void *opaque) static void ide_trim_bh_cb(void *opaque)
{ {
TrimAIOCB *iocb = opaque; TrimAIOCB *iocb = opaque;
BlockBackend *blk = iocb->s->blk;
iocb->common.cb(iocb->common.opaque, iocb->ret); iocb->common.cb(iocb->common.opaque, iocb->ret);
qemu_bh_delete(iocb->bh); qemu_bh_delete(iocb->bh);
iocb->bh = NULL; iocb->bh = NULL;
qemu_aio_unref(iocb); 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) static void ide_issue_trim_cb(void *opaque, int ret)
@ -509,6 +513,9 @@ BlockAIOCB *ide_issue_trim(
IDEState *s = opaque; IDEState *s = opaque;
TrimAIOCB *iocb; 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 = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque);
iocb->s = s; iocb->s = s;
iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb); iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb);

View File

@ -112,7 +112,8 @@ typedef enum {
/* /*
* If we need to wait for other requests, just fail immediately. Used * 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, BDRV_REQ_NO_WAIT = 0x400,

View File

@ -25,6 +25,7 @@ typedef struct BlockCopyState BlockCopyState;
typedef struct BlockCopyCallState BlockCopyCallState; typedef struct BlockCopyCallState BlockCopyCallState;
BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target, BlockCopyState *block_copy_state_new(BdrvChild *source, BdrvChild *target,
const BdrvDirtyBitmap *bitmap,
Error **errp); Error **errp);
/* Function should be called prior any actual copy request */ /* 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_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 block_copy_reset_unallocated(BlockCopyState *s,
int64_t offset, int64_t *count); int64_t offset, int64_t *count);

View File

@ -597,6 +597,30 @@ struct BlockDriver {
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum, bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file); 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. * Invalidate any cached meta-data.
*/ */

View File

@ -33,6 +33,15 @@
* the I/O API. * 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, int coroutine_fn bdrv_co_preadv(BdrvChild *child,
int64_t offset, int64_t bytes, QEMUIOVector *qiov, int64_t offset, int64_t bytes, QEMUIOVector *qiov,
BdrvRequestFlags flags); BdrvRequestFlags flags);

View File

@ -77,7 +77,7 @@ void bdrv_dirty_bitmap_set_persistence(BdrvDirtyBitmap *bitmap,
bool persistent); bool persistent);
void bdrv_dirty_bitmap_set_inconsistent(BdrvDirtyBitmap *bitmap); void bdrv_dirty_bitmap_set_inconsistent(BdrvDirtyBitmap *bitmap);
void bdrv_dirty_bitmap_set_busy(BdrvDirtyBitmap *bitmap, bool busy); 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); HBitmap **backup, Error **errp);
void bdrv_dirty_bitmap_skip_store(BdrvDirtyBitmap *bitmap, bool skip); void bdrv_dirty_bitmap_skip_store(BdrvDirtyBitmap *bitmap, bool skip);
bool bdrv_dirty_bitmap_get(BdrvDirtyBitmap *bitmap, int64_t offset); 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, bool bdrv_dirty_bitmap_next_dirty_area(BdrvDirtyBitmap *bitmap,
int64_t start, int64_t end, int64_t max_dirty_count, int64_t start, int64_t end, int64_t max_dirty_count,
int64_t *dirty_start, int64_t *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, BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap_locked(BdrvDirtyBitmap *bitmap,
Error **errp); Error **errp);

75
include/block/reqlist.h Normal file
View 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 */

View File

@ -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 max_dirty_count,
int64_t *dirty_start, int64_t *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: * hbitmap_iter_next:
* @hbi: HBitmapIter to operate on. * @hbi: HBitmapIter to operate on.

View File

@ -2914,13 +2914,14 @@
# @blkreplay: Since 4.2 # @blkreplay: Since 4.2
# @compress: Since 5.0 # @compress: Since 5.0
# @copy-before-write: Since 6.2 # @copy-before-write: Since 6.2
# @snapshot-access: Since 7.0
# #
# Since: 2.9 # Since: 2.9
## ##
{ 'enum': 'BlockdevDriver', { 'enum': 'BlockdevDriver',
'data': [ 'blkdebug', 'blklogwrites', 'blkreplay', 'blkverify', 'bochs', 'data': [ 'blkdebug', 'blklogwrites', 'blkreplay', 'blkverify', 'bochs',
'cloop', 'compress', 'copy-before-write', 'copy-on-read', 'dmg', '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_cdrom', 'if': 'HAVE_HOST_BLOCK_DEVICE' },
{'name': 'host_device', 'if': 'HAVE_HOST_BLOCK_DEVICE' }, {'name': 'host_device', 'if': 'HAVE_HOST_BLOCK_DEVICE' },
'http', 'https', 'iscsi', 'http', 'https', 'iscsi',
@ -4171,11 +4172,19 @@
# #
# @target: The target for copy-before-write operations. # @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 # Since: 6.2
## ##
{ 'struct': 'BlockdevOptionsCbw', { 'struct': 'BlockdevOptionsCbw',
'base': 'BlockdevOptionsGenericFormat', 'base': 'BlockdevOptionsGenericFormat',
'data': { 'target': 'BlockdevRef' } } 'data': { 'target': 'BlockdevRef', '*bitmap': 'BlockDirtyBitmap' } }
## ##
# @BlockdevOptions: # @BlockdevOptions:
@ -4259,6 +4268,7 @@
'rbd': 'BlockdevOptionsRbd', 'rbd': 'BlockdevOptionsRbd',
'replication': { 'type': 'BlockdevOptionsReplication', 'replication': { 'type': 'BlockdevOptionsReplication',
'if': 'CONFIG_REPLICATION' }, 'if': 'CONFIG_REPLICATION' },
'snapshot-access': 'BlockdevOptionsGenericFormat',
'ssh': 'BlockdevOptionsSsh', 'ssh': 'BlockdevOptionsSsh',
'throttle': 'BlockdevOptionsThrottle', 'throttle': 'BlockdevOptionsThrottle',
'vdi': 'BlockdevOptionsGenericFormat', 'vdi': 'BlockdevOptionsGenericFormat',

View File

@ -744,6 +744,7 @@ class TestCommitWithFilters(iotests.QMPTestCase):
pattern_file) pattern_file)
self.assertFalse('Pattern verification failed' in result) self.assertFalse('Pattern verification failed' in result)
@iotests.skip_if_unsupported(['throttle'])
def setUp(self): def setUp(self):
qemu_img('create', '-f', iotests.imgfmt, self.img0, '64M') qemu_img('create', '-f', iotests.imgfmt, self.img0, '64M')
qemu_img('create', '-f', iotests.imgfmt, self.img1, '64M') qemu_img('create', '-f', iotests.imgfmt, self.img1, '64M')

View File

@ -106,6 +106,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -566,6 +582,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -819,6 +851,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -1279,6 +1327,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -1532,6 +1596,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -1992,6 +2072,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -2245,6 +2341,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -2705,6 +2817,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -2958,6 +3086,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -3418,6 +3562,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -3671,6 +3831,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -4131,6 +4307,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -4384,6 +4576,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,
@ -4844,6 +5052,22 @@ write -P0x67 0x3fe0000 0x20000
{"return": ""} {"return": ""}
{ {
"bitmaps": { "bitmaps": {
"backup-top": [
{
"busy": false,
"count": 67108864,
"granularity": 65536,
"persistent": false,
"recording": false
},
{
"busy": false,
"count": 458752,
"granularity": 65536,
"persistent": false,
"recording": false
}
],
"drive0": [ "drive0": [
{ {
"busy": false, "busy": false,

View File

@ -20,7 +20,7 @@
# bail out, setting up .notrun file # bail out, setting up .notrun file
_notrun() _notrun()
{ {
echo "$*" >"$OUTPUT_DIR/$seq.notrun" echo "$*" >"$TEST_DIR/$seq.notrun"
echo "$seq not run: $*" echo "$seq not run: $*"
status=0 status=0
exit exit
@ -739,14 +739,14 @@ _img_info()
# #
_casenotrun() _casenotrun()
{ {
echo " [case not run] $*" >>"$OUTPUT_DIR/$seq.casenotrun" echo " [case not run] $*" >>"$TEST_DIR/$seq.casenotrun"
} }
# just plain bail out # just plain bail out
# #
_fail() _fail()
{ {
echo "$*" | tee -a "$OUTPUT_DIR/$seq.full" echo "$*" | tee -a "$TEST_DIR/$seq.full"
echo "(see $seq.full for details)" echo "(see $seq.full for details)"
status=1 status=1
exit 1 exit 1

View File

@ -85,7 +85,6 @@ qemu_print = os.environ.get('PRINT_QEMU', False)
imgfmt = os.environ.get('IMGFMT', 'raw') imgfmt = os.environ.get('IMGFMT', 'raw')
imgproto = os.environ.get('IMGPROTO', 'file') imgproto = os.environ.get('IMGPROTO', 'file')
output_dir = os.environ.get('OUTPUT_DIR', '.')
try: try:
test_dir = os.environ['TEST_DIR'] test_dir = os.environ['TEST_DIR']
@ -279,6 +278,9 @@ def qemu_io(*args):
'''Run qemu-io and return the stdout data''' '''Run qemu-io and return the stdout data'''
return qemu_tool_pipe_and_status('qemu-io', qemu_io_wrap_args(args))[0] 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): def qemu_io_log(*args):
result = qemu_io(*args) result = qemu_io(*args)
log(result, filters=[filter_testfiles, filter_qemu_io]) log(result, filters=[filter_testfiles, filter_qemu_io])
@ -1239,7 +1241,7 @@ def notrun(reason):
# Each test in qemu-iotests has a number ("seq") # Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0]) 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: as outfile:
outfile.write(reason + '\n') outfile.write(reason + '\n')
logger.warning("%s not run: %s", seq, reason) 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") # Each test in qemu-iotests has a number ("seq")
seq = os.path.basename(sys.argv[0]) 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: as outfile:
outfile.write(' [case not run] ' + reason + '\n') outfile.write(' [case not run] ' + reason + '\n')

View File

@ -66,7 +66,7 @@ class TestEnv(ContextManager['TestEnv']):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
env_variables = ['PYTHONPATH', 'TEST_DIR', 'SOCK_DIR', 'SAMPLE_IMG_DIR', 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_IO_PROG', 'QEMU_NBD_PROG', 'QSD_PROG',
'QEMU_OPTIONS', 'QEMU_IMG_OPTIONS', 'QEMU_OPTIONS', 'QEMU_IMG_OPTIONS',
'QEMU_IO_OPTIONS', 'QEMU_IO_OPTIONS_NO_FMT', 'QEMU_IO_OPTIONS', 'QEMU_IO_OPTIONS_NO_FMT',
@ -106,7 +106,6 @@ class TestEnv(ContextManager['TestEnv']):
TEST_DIR TEST_DIR
SOCK_DIR SOCK_DIR
SAMPLE_IMG_DIR SAMPLE_IMG_DIR
OUTPUT_DIR
""" """
# Path where qemu goodies live in this source tree. # Path where qemu goodies live in this source tree.
@ -134,8 +133,6 @@ class TestEnv(ContextManager['TestEnv']):
os.path.join(self.source_iotests, os.path.join(self.source_iotests,
'sample_images')) 'sample_images'))
self.output_dir = os.getcwd() # OUTPUT_DIR
def init_binaries(self) -> None: def init_binaries(self) -> None:
"""Init binary path variables: """Init binary path variables:
PYTHON (for bash tests) PYTHON (for bash tests)

View File

@ -259,9 +259,6 @@ class TestRunner(ContextManager['TestRunner']):
""" """
f_test = Path(test) 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)) f_reference = Path(self.find_reference(test))
if not f_test.exists(): if not f_test.exists():
@ -276,9 +273,6 @@ class TestRunner(ContextManager['TestRunner']):
description='No qualified output ' description='No qualified output '
f'(expected {f_reference})') f'(expected {f_reference})')
for p in (f_bad, f_notrun, f_casenotrun):
silent_unlink(p)
args = [str(f_test.resolve())] args = [str(f_test.resolve())]
env = self.env.prepare_subprocess(args) env = self.env.prepare_subprocess(args)
if mp: if mp:
@ -288,6 +282,14 @@ class TestRunner(ContextManager['TestRunner']):
env[d] = os.path.join(env[d], f_test.name) env[d] = os.path.join(env[d], f_test.name)
Path(env[d]).mkdir(parents=True, exist_ok=True) 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() t0 = time.time()
with f_bad.open('w', encoding="utf-8") as f: with f_bad.open('w', encoding="utf-8") as f:
with subprocess.Popen(args, cwd=str(f_test.parent), env=env, with subprocess.Popen(args, cwd=str(f_test.parent), env=env,
@ -365,7 +367,10 @@ class TestRunner(ContextManager['TestRunner']):
description=res.description) description=res.description)
if res.casenotrun: if res.casenotrun:
print(res.casenotrun) if self.tap:
print('#' + res.casenotrun.replace('\n', '\n#'))
else:
print(res.casenotrun)
return res return res

View File

@ -23,12 +23,14 @@
# Creator/Owner: John Snow <jsnow@redhat.com> # Creator/Owner: John Snow <jsnow@redhat.com>
import iotests 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( iotests.script_initialize(
supported_fmts=['qcow2', 'qcow', 'qed', 'vmdk', 'vhdx', 'raw'], supported_fmts=['qcow2'],
supported_platforms=['linux'], supported_platforms=['linux'],
required_fmts=['copy-before-write'], required_fmts=['copy-before-write'],
unsupported_imgopts=['compat']
) )
patterns = [('0x5d', '0', '64k'), 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] ('0xdc', '32M', '32k'), # Left-end of partial-right [2]
('0xcd', '0x3ff0000', '64k')] # patterns[3] ('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('--- Setting up images ---')
log('') log('')
assert qemu_img('create', '-f', iotests.imgfmt, base_img_path, '64M') == 0 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: for p in patterns:
qemu_io('-f', iotests.imgfmt, 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('') log('')
# create tmp_node backed by src_node if use_snapshot_access_filter:
log(vm.qmp('blockdev-add', { log(vm.qmp('blockdev-add', {
'driver': 'qcow2', 'node-name': tmp_node,
'node-name': tmp_node,
'file': {
'driver': 'file', 'driver': 'file',
'filename': fleece_img_path, '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 # Establish CBW from source to fleecing node
if use_cbw: if use_cbw:
log(vm.qmp('blockdev-add', { fl_cbw = {
'driver': 'copy-before-write', 'driver': 'copy-before-write',
'node-name': 'fl-cbw', 'node-name': 'fl-cbw',
'file': src_node, 'file': src_node,
'target': tmp_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')) 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: else:
log(vm.qmp('blockdev-backup', log(vm.qmp('blockdev-backup',
job_id='fleecing', 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, target=tmp_node,
sync='none')) sync='none'))
log('') export_node = 'fl-access' if use_snapshot_access_filter else tmp_node
log('--- Setting up NBD Export ---')
log('')
nbd_uri = 'nbd+unix:///%s?socket=%s' % (tmp_node, nbd_sock_path) if push_backup:
log(vm.qmp('nbd-server-start', log('')
{'addr': {'type': 'unix', log('--- Starting actual backup ---')
'data': {'path': nbd_sock_path}}})) 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('') nbd_uri = 'nbd+unix:///%s?socket=%s' % (export_node, nbd_sock_path)
log('--- Sanity Check ---') log(vm.qmp('nbd-server-start',
log('') {'addr': { 'type': 'unix',
'data': { 'path': nbd_sock_path } } }))
for p in patterns + zeroes: log(vm.qmp('nbd-server-add', device=export_node))
cmd = 'read -P%s %s %s' % p
log(cmd) log('')
assert qemu_io_silent('-r', '-f', 'raw', '-c', cmd, nbd_uri) == 0 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('')
log('--- Testing COW ---') 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(cmd)
log(vm.hmp_qemu_io(qom_path, cmd, qdev=True)) 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('')
log('--- Verifying Data ---') log('--- Verifying Data ---')
log('') 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: for p in patterns + zeroes:
cmd = 'read -P%s %s %s' % p cmd = 'read -P%s %s %s' % p
log(cmd) 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('')
log('--- Cleanup ---') log('--- Cleanup ---')
log('') log('')
if not push_backup:
log(vm.qmp('nbd-server-stop'))
if use_cbw: 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('qom-set', path=qom_path, property='drive', value=src_node))
log(vm.qmp('blockdev-del', node_name='fl-cbw')) log(vm.qmp('blockdev-del', node_name='fl-cbw'))
else: 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 assert e is not None
log(e, filters=[iotests.filter_qmp_event]) log(e, filters=[iotests.filter_qmp_event])
log(vm.qmp('nbd-server-stop'))
log(vm.qmp('blockdev-del', node_name=tmp_node)) log(vm.qmp('blockdev-del', node_name=tmp_node))
vm.shutdown() vm.shutdown()
@ -177,17 +266,37 @@ def do_test(use_cbw, base_img_path, fleece_img_path, nbd_sock_path, vm):
log('Done') 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, \ with iotests.FilePath('base.img') as base_img_path, \
iotests.FilePath('fleece.img') as fleece_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: 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') log('=== Test backup(sync=none) based fleecing ===\n')
test(False) test_pull(False, False)
log('=== Test filter based fleecing ===\n') log('=== Test cbw-filter based fleecing ===\n')
test(True) 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()

View File

@ -52,8 +52,8 @@ read -P0 0x3fe0000 64k
--- Cleanup --- --- Cleanup ---
{"return": {}} {"return": {}}
{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"return": {}} {"return": {}}
{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"return": {}} {"return": {}}
--- Confirming writes --- --- Confirming writes ---
@ -67,7 +67,7 @@ read -P0xdc 32M 32k
read -P0xcd 0x3ff0000 64k read -P0xcd 0x3ff0000 64k
Done Done
=== Test filter based fleecing === === Test cbw-filter based fleecing ===
--- Setting up images --- --- Setting up images ---
@ -137,3 +137,222 @@ read -P0xdc 32M 32k
read -P0xcd 0x3ff0000 64k read -P0xcd 0x3ff0000 64k
Done 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

View File

@ -301,6 +301,39 @@ bool hbitmap_next_dirty_area(const HBitmap *hb, int64_t start, int64_t end,
return true; 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) bool hbitmap_empty(const HBitmap *hb)
{ {
return hb->count == 0; return hb->count == 0;