aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2022-03-07 17:14:09 +0000
committerPeter Maydell <peter.maydell@linaro.org>2022-03-07 17:14:09 +0000
commitb49872aa8fc0f3f5a3036cc37aa2cb5c92866f33 (patch)
treecf90cae300466316659e0287db1800745bfa867a
parent9d662a6b22a0838a85c5432385f35db2488a33a5 (diff)
parent743da0b401cdc3ee94bc519975e339a3cdbe0ad1 (diff)
downloadqemu-b49872aa8fc0f3f5a3036cc37aa2cb5c92866f33.zip
qemu-b49872aa8fc0f3f5a3036cc37aa2cb5c92866f33.tar.gz
qemu-b49872aa8fc0f3f5a3036cc37aa2cb5c92866f33.tar.bz2
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>
-rw-r--r--MAINTAINERS5
-rw-r--r--block/block-copy.c150
-rw-r--r--block/copy-before-write.c265
-rw-r--r--block/curl.c92
-rw-r--r--block/dirty-bitmap.c15
-rw-r--r--block/io.c76
-rw-r--r--block/meson.build2
-rw-r--r--block/monitor/bitmap-qmp-cmds.c5
-rw-r--r--block/preallocate.c15
-rw-r--r--block/reqlist.c85
-rw-r--r--block/snapshot-access.c132
-rw-r--r--hw/ide/core.c7
-rw-r--r--include/block/block-common.h3
-rw-r--r--include/block/block-copy.h2
-rw-r--r--include/block/block_int-common.h24
-rw-r--r--include/block/block_int-io.h9
-rw-r--r--include/block/dirty-bitmap.h4
-rw-r--r--include/block/reqlist.h75
-rw-r--r--include/qemu/hbitmap.h12
-rw-r--r--qapi/block-core.json14
-rwxr-xr-xtests/qemu-iotests/0401
-rw-r--r--tests/qemu-iotests/257.out224
-rw-r--r--tests/qemu-iotests/common.rc6
-rw-r--r--tests/qemu-iotests/iotests.py8
-rw-r--r--tests/qemu-iotests/testenv.py5
-rw-r--r--tests/qemu-iotests/testrunner.py19
-rwxr-xr-xtests/qemu-iotests/tests/image-fleecing185
-rw-r--r--tests/qemu-iotests/tests/image-fleecing.out221
-rw-r--r--util/hbitmap.c33
29 files changed, 1499 insertions, 195 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 68adaac..4f0cc1e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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
diff --git a/block/block-copy.c b/block/block-copy.c
index ce11631..ef948dc 100644
--- a/block/block-copy.c
+++ b/block/block-copy.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.
- */
- CoQueue wait_queue; /* coroutines blocked on this task */
- /*
- * Only protect the case of parallel read while updating @bytes
- * value in block_copy_task_shrink().
+ * 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().
*/
- 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:
@@ -161,42 +157,6 @@ typedef struct 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 */
static int64_t block_copy_chunk_size(BlockCopyState *s)
{
switch (s->method) {
@@ -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.
diff --git a/block/copy-before-write.c b/block/copy-before-write.c
index 80b7684..a8a06fd 100644
--- a/block/copy-before-write.c
+++ b/block/copy-before-write.c
@@ -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,
diff --git a/block/curl.c b/block/curl.c
index 6a6cd72..1e0f609 100644
--- a/block/curl.c
+++ b/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 (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) {
- curl_easy_setopt(state->curl, CURLOPT_COOKIE, 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;
diff --git a/block/dirty-bitmap.c b/block/dirty-bitmap.c
index 0334b85..da1b911 100644
--- a/block/dirty-bitmap.c
+++ b/block/dirty-bitmap.c
@@ -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;
}
/**
diff --git a/block/io.c b/block/io.c
index efc011c..f0c8da6 100644
--- a/block/io.c
+++ b/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;
+}
diff --git a/block/meson.build b/block/meson.build
index e42bcb5..0b2a60c 100644
--- a/block/meson.build
+++ b/block/meson.build
@@ -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',
diff --git a/block/monitor/bitmap-qmp-cmds.c b/block/monitor/bitmap-qmp-cmds.c
index 972e8a0..8e35616 100644
--- a/block/monitor/bitmap-qmp-cmds.c
+++ b/block/monitor/bitmap-qmp-cmds.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;
}
diff --git a/block/preallocate.c b/block/preallocate.c
index 1d4233f..e15cb8c 100644
--- a/block/preallocate.c
+++ b/block/preallocate.c
@@ -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,
diff --git a/block/reqlist.c b/block/reqlist.c
new file mode 100644
index 0000000..08cb57c
--- /dev/null
+++ b/block/reqlist.c
@@ -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);
+}
diff --git a/block/snapshot-access.c b/block/snapshot-access.c
new file mode 100644
index 0000000..77b87c1
--- /dev/null
+++ b/block/snapshot-access.c
@@ -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);
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 33463d9..d667d0b 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -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);
diff --git a/include/block/block-common.h b/include/block/block-common.h
index 0c5dc4a..fdb7306 100644
--- a/include/block/block-common.h
+++ b/include/block/block-common.h
@@ -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,
diff --git a/include/block/block-copy.h b/include/block/block-copy.h
index 99370fa..68bbd34 100644
--- a/include/block/block-copy.h
+++ b/include/block/block-copy.h
@@ -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);
diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
index 5a04c77..8947aba 100644
--- a/include/block/block_int-common.h
+++ b/include/block/block_int-common.h
@@ -598,6 +598,30 @@ struct BlockDriver {
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.
*/
void coroutine_fn (*bdrv_co_invalidate_cache)(BlockDriverState *bs,
diff --git a/include/block/block_int-io.h b/include/block/block_int-io.h
index 3da5f01..bb45420 100644
--- a/include/block/block_int-io.h
+++ b/include/block/block_int-io.h
@@ -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);
diff --git a/include/block/dirty-bitmap.h b/include/block/dirty-bitmap.h
index 40950ae..6528336 100644
--- a/include/block/dirty-bitmap.h
+++ b/include/block/dirty-bitmap.h
@@ -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);
diff --git a/include/block/reqlist.h b/include/block/reqlist.h
new file mode 100644
index 0000000..5253497
--- /dev/null
+++ b/include/block/reqlist.h
@@ -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 */
diff --git a/include/qemu/hbitmap.h b/include/qemu/hbitmap.h
index 5e71b6d..5bd986a 100644
--- a/include/qemu/hbitmap.h
+++ b/include/qemu/hbitmap.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.
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 9a5a364..f13b5ff 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -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',
diff --git a/tests/qemu-iotests/040 b/tests/qemu-iotests/040
index 6af5ab9..0e1cfd7 100755
--- a/tests/qemu-iotests/040
+++ b/tests/qemu-iotests/040
@@ -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')
diff --git a/tests/qemu-iotests/257.out b/tests/qemu-iotests/257.out
index 50cbd8e..aa76131 100644
--- a/tests/qemu-iotests/257.out
+++ b/tests/qemu-iotests/257.out
@@ -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,
diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
index 3bfd94c..227e0a5 100644
--- a/tests/qemu-iotests/common.rc
+++ b/tests/qemu-iotests/common.rc
@@ -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
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 6027780..508adad 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -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')
diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py
index 0f32897..b11e943 100644
--- a/tests/qemu-iotests/testenv.py
+++ b/tests/qemu-iotests/testenv.py
@@ -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)
diff --git a/tests/qemu-iotests/testrunner.py b/tests/qemu-iotests/testrunner.py
index 9a94273..41083ff 100644
--- a/tests/qemu-iotests/testrunner.py
+++ b/tests/qemu-iotests/testrunner.py
@@ -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
diff --git a/tests/qemu-iotests/tests/image-fleecing b/tests/qemu-iotests/tests/image-fleecing
index a58b5a1..c562786 100755
--- a/tests/qemu-iotests/tests/image-fleecing
+++ b/tests/qemu-iotests/tests/image-fleecing
@@ -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
+
+ if push_backup:
+ log('')
+ log('--- Starting actual backup ---')
+ log('')
- 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}}}))
+ 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(vm.qmp('nbd-server-add', device=tmp_node))
+ 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 } } }))
- log('')
- log('--- Sanity Check ---')
- log('')
+ log(vm.qmp('nbd-server-add', device=export_node))
- 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('')
+ 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 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 filter based fleecing ===\n')
-test(True)
+log('=== Test push backup with fleecing ===\n')
+test_push()
diff --git a/tests/qemu-iotests/tests/image-fleecing.out b/tests/qemu-iotests/tests/image-fleecing.out
index e96d122..acfc89f 100644
--- a/tests/qemu-iotests/tests/image-fleecing.out
+++ b/tests/qemu-iotests/tests/image-fleecing.out
@@ -52,8 +52,150 @@ read -P0 0x3fe0000 64k
--- Cleanup ---
{"return": {}}
+{"return": {}}
{"data": {"device": "fleecing", "len": 67108864, "offset": 393216, "speed": 0, "type": "backup"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"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 cbw-filter based fleecing ===
+
+--- Setting up images ---
+
+Done
+
+--- Launching VM ---
+
+Done
+
+--- Setting up Fleecing Graph ---
+
+{"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": {}}
+
+--- 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 ===
+
+--- 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 ---
@@ -67,7 +209,7 @@ read -P0xdc 32M 32k
read -P0xcd 0x3ff0000 64k
Done
-=== Test filter based fleecing ===
+=== Test fleecing-format based fleecing with bitmap ===
--- Setting up images ---
@@ -82,6 +224,7 @@ Done
{"return": {}}
{"return": {}}
{"return": {}}
+{"return": {}}
--- Setting up NBD Export ---
@@ -95,8 +238,82 @@ 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 ---
@@ -108,6 +325,8 @@ 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 ---
diff --git a/util/hbitmap.c b/util/hbitmap.c
index 305b894..dd0501d 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -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;