NBD patches for 2025-05-14

- Eric Blake: fix blockdev-mirror to no longer inflate sparse destination
   that already reads as zero
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmglUT0ACgkQp6FrSiUn
 Q2ozXwgAqm4crl7r7b5jFHUS2nbJbdxhJR7GuW5oOlt9In4kXNL8T31SP5tFhfyq
 inPl9wbLuvOHyi+NyMK9Wi3XYrHJ26U0PsmSBk2DFF9SvplV+ekUpFNhd6suf7nE
 NK97y6Pv6H+KLlrUI8Z4bkRnZnSCIHYpGmS04ehXLodCaWjVOQ+xfXL8g7LprttU
 7xOLRtvW+vEV0TDs2WfjpWmzdqSGB2TVNB6u2a3tRkHGV9LHV1IyBJTs/7m5s/La
 UwKt8joUYBw54k6ZeE2JFrhoOPE8W7AzWZJmKnlYopgh7TxWnwVhFPMDSF3/4ffr
 ma1nVP6C1zyH4Wi7cw3GRjZktErIww==
 =A3FA
 -----END PGP SIGNATURE-----

Merge tag 'pull-nbd-2025-05-14' of https://repo.or.cz/qemu/ericb into staging

NBD patches for 2025-05-14

- Eric Blake: fix blockdev-mirror to no longer inflate sparse destination
  that already reads as zero

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmglUT0ACgkQp6FrSiUn
# Q2ozXwgAqm4crl7r7b5jFHUS2nbJbdxhJR7GuW5oOlt9In4kXNL8T31SP5tFhfyq
# inPl9wbLuvOHyi+NyMK9Wi3XYrHJ26U0PsmSBk2DFF9SvplV+ekUpFNhd6suf7nE
# NK97y6Pv6H+KLlrUI8Z4bkRnZnSCIHYpGmS04ehXLodCaWjVOQ+xfXL8g7LprttU
# 7xOLRtvW+vEV0TDs2WfjpWmzdqSGB2TVNB6u2a3tRkHGV9LHV1IyBJTs/7m5s/La
# UwKt8joUYBw54k6ZeE2JFrhoOPE8W7AzWZJmKnlYopgh7TxWnwVhFPMDSF3/4ffr
# ma1nVP6C1zyH4Wi7cw3GRjZktErIww==
# =A3FA
# -----END PGP SIGNATURE-----
# gpg: Signature made Wed 14 May 2025 22:28:13 EDT
# gpg:                using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full]
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full]
# gpg:                 aka "[jpeg image of size 6874]" [full]
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* tag 'pull-nbd-2025-05-14' of https://repo.or.cz/qemu/ericb:
  mirror: Reduce I/O when destination is detect-zeroes:unmap
  tests: Add iotest mirror-sparse for recent patches
  iotests/common.rc: add disk_usage function
  mirror: Skip writing zeroes when target is already zero
  mirror: Skip pre-zeroing destination if it is already zero
  mirror: Drop redundant zero_target parameter
  mirror: Allow QMP override to declare target already zero
  mirror: Pass full sync mode rather than bool to internals
  mirror: Minor refactoring
  iotests: Improve iotest 194 to mirror data
  block: Add new bdrv_co_is_all_zeroes() function
  block: Let bdrv_co_is_zero_fast consolidate adjacent extents
  file-posix, gluster: Handle zero block status hint better
  block: Expand block status mode from bool to flags

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2025-05-15 13:42:09 -04:00
commit 599f2762ed
37 changed files with 856 additions and 164 deletions

View file

@ -751,9 +751,9 @@ blkdebug_co_pdiscard(BlockDriverState *bs, int64_t offset, int64_t bytes)
}
static int coroutine_fn GRAPH_RDLOCK
blkdebug_co_block_status(BlockDriverState *bs, bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map,
BlockDriverState **file)
blkdebug_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{
int err;

View file

@ -291,8 +291,8 @@ cbw_co_preadv_snapshot(BlockDriverState *bs, int64_t offset, int64_t bytes,
}
static int coroutine_fn GRAPH_RDLOCK
cbw_co_snapshot_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset, int64_t bytes,
cbw_co_snapshot_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{

View file

@ -47,7 +47,7 @@ int coroutine_fn GRAPH_RDLOCK
bdrv_co_common_block_status_above(BlockDriverState *bs,
BlockDriverState *base,
bool include_base,
bool want_zero,
unsigned int mode,
int64_t offset,
int64_t bytes,
int64_t *pnum,
@ -78,7 +78,7 @@ int co_wrapper_mixed_bdrv_rdlock
bdrv_common_block_status_above(BlockDriverState *bs,
BlockDriverState *base,
bool include_base,
bool want_zero,
unsigned int mode,
int64_t offset,
int64_t bytes,
int64_t *pnum,

View file

@ -3273,7 +3273,7 @@ static int find_allocation(BlockDriverState *bs, off_t start,
* well exceed it.
*/
static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
bool want_zero,
unsigned int mode,
int64_t offset,
int64_t bytes, int64_t *pnum,
int64_t *map,
@ -3289,7 +3289,8 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
return ret;
}
if (!want_zero) {
if (!(mode & BDRV_WANT_ZERO)) {
/* There is no backing file - all bytes are allocated in this file. */
*pnum = bytes;
*map = offset;
*file = bs;

View file

@ -1461,7 +1461,7 @@ exit:
* (Based on raw_co_block_status() from file-posix.c.)
*/
static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
bool want_zero,
unsigned int mode,
int64_t offset,
int64_t bytes,
int64_t *pnum,
@ -1478,7 +1478,7 @@ static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
return ret;
}
if (!want_zero) {
if (!(mode & BDRV_WANT_ZERO)) {
*pnum = bytes;
*map = offset;
*file = bs;

View file

@ -38,10 +38,14 @@
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "system/replay.h"
#include "qemu/units.h"
/* Maximum bounce buffer for copy-on-read and write zeroes, in bytes */
#define MAX_BOUNCE_BUFFER (32768 << BDRV_SECTOR_BITS)
/* Maximum read size for checking if data reads as zero, in bytes */
#define MAX_ZERO_CHECK_BUFFER (128 * KiB)
static void coroutine_fn GRAPH_RDLOCK
bdrv_parent_cb_resize(BlockDriverState *bs);
@ -2364,10 +2368,8 @@ int bdrv_flush_all(void)
* Drivers not implementing the functionality are assumed to not support
* backing files, hence all their sectors are reported as allocated.
*
* If 'want_zero' is true, the caller is querying for mapping
* purposes, with a focus on valid BDRV_BLOCK_OFFSET_VALID, _DATA, and
* _ZERO where possible; otherwise, the result favors larger 'pnum',
* with a focus on accurate BDRV_BLOCK_ALLOCATED.
* 'mode' serves as a hint as to which results are favored; see the
* BDRV_WANT_* macros for details.
*
* If 'offset' is beyond the end of the disk image the return value is
* BDRV_BLOCK_EOF and 'pnum' is set to 0.
@ -2387,7 +2389,7 @@ int bdrv_flush_all(void)
* set to the host mapping and BDS corresponding to the guest offset.
*/
static int coroutine_fn GRAPH_RDLOCK
bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
bdrv_co_do_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map, BlockDriverState **file)
{
@ -2476,7 +2478,7 @@ bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
local_file = bs;
local_map = aligned_offset;
} else {
ret = bs->drv->bdrv_co_block_status(bs, want_zero, aligned_offset,
ret = bs->drv->bdrv_co_block_status(bs, mode, aligned_offset,
aligned_bytes, pnum, &local_map,
&local_file);
@ -2488,10 +2490,10 @@ bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
* the cache requires an RCU update, so double check here to avoid
* such an update if possible.
*
* Check want_zero, because we only want to update the cache when we
* Check mode, because we only want to update the cache when we
* have accurate information about what is zero and what is data.
*/
if (want_zero &&
if (mode == BDRV_WANT_PRECISE &&
ret == (BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID) &&
QLIST_EMPTY(&bs->children))
{
@ -2548,7 +2550,7 @@ bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
if (ret & BDRV_BLOCK_RAW) {
assert(ret & BDRV_BLOCK_OFFSET_VALID && local_file);
ret = bdrv_co_do_block_status(local_file, want_zero, local_map,
ret = bdrv_co_do_block_status(local_file, mode, local_map,
*pnum, pnum, &local_map, &local_file);
goto out;
}
@ -2560,7 +2562,7 @@ bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
if (!cow_bs) {
ret |= BDRV_BLOCK_ZERO;
} else if (want_zero) {
} else if (mode == BDRV_WANT_PRECISE) {
int64_t size2 = bdrv_co_getlength(cow_bs);
if (size2 >= 0 && offset >= size2) {
@ -2569,14 +2571,14 @@ bdrv_co_do_block_status(BlockDriverState *bs, bool want_zero,
}
}
if (want_zero && ret & BDRV_BLOCK_RECURSE &&
if (mode == BDRV_WANT_PRECISE && ret & BDRV_BLOCK_RECURSE &&
local_file && local_file != bs &&
(ret & BDRV_BLOCK_DATA) && !(ret & BDRV_BLOCK_ZERO) &&
(ret & BDRV_BLOCK_OFFSET_VALID)) {
int64_t file_pnum;
int ret2;
ret2 = bdrv_co_do_block_status(local_file, want_zero, local_map,
ret2 = bdrv_co_do_block_status(local_file, mode, local_map,
*pnum, &file_pnum, NULL, NULL);
if (ret2 >= 0) {
/* Ignore errors. This is just providing extra information, it
@ -2627,7 +2629,7 @@ int coroutine_fn
bdrv_co_common_block_status_above(BlockDriverState *bs,
BlockDriverState *base,
bool include_base,
bool want_zero,
unsigned int mode,
int64_t offset,
int64_t bytes,
int64_t *pnum,
@ -2654,7 +2656,7 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
return 0;
}
ret = bdrv_co_do_block_status(bs, want_zero, offset, bytes, pnum,
ret = bdrv_co_do_block_status(bs, mode, offset, bytes, pnum,
map, file);
++*depth;
if (ret < 0 || *pnum == 0 || ret & BDRV_BLOCK_ALLOCATED || bs == base) {
@ -2671,7 +2673,7 @@ bdrv_co_common_block_status_above(BlockDriverState *bs,
for (p = bdrv_filter_or_cow_bs(bs); include_base || p != base;
p = bdrv_filter_or_cow_bs(p))
{
ret = bdrv_co_do_block_status(p, want_zero, offset, bytes, pnum,
ret = bdrv_co_do_block_status(p, mode, offset, bytes, pnum,
map, file);
++*depth;
if (ret < 0) {
@ -2734,7 +2736,8 @@ int coroutine_fn bdrv_co_block_status_above(BlockDriverState *bs,
BlockDriverState **file)
{
IO_CODE();
return bdrv_co_common_block_status_above(bs, base, false, true, offset,
return bdrv_co_common_block_status_above(bs, base, false,
BDRV_WANT_PRECISE, offset,
bytes, pnum, map, file, NULL);
}
@ -2752,27 +2755,89 @@ int coroutine_fn bdrv_co_block_status(BlockDriverState *bs, int64_t offset,
* by @offset and @bytes is known to read as zeroes.
* Return 1 if that is the case, 0 otherwise and -errno on error.
* This test is meant to be fast rather than accurate so returning 0
* does not guarantee non-zero data.
* does not guarantee non-zero data; but a return of 1 is reliable.
*/
int coroutine_fn bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset,
int64_t bytes)
{
int ret;
int64_t pnum = bytes;
int64_t pnum;
IO_CODE();
if (!bytes) {
return 1;
while (bytes) {
ret = bdrv_co_common_block_status_above(bs, NULL, false,
BDRV_WANT_ZERO, offset, bytes,
&pnum, NULL, NULL, NULL);
if (ret < 0) {
return ret;
}
if (!(ret & BDRV_BLOCK_ZERO)) {
return 0;
}
offset += pnum;
bytes -= pnum;
}
ret = bdrv_co_common_block_status_above(bs, NULL, false, false, offset,
bytes, &pnum, NULL, NULL, NULL);
return 1;
}
/*
* Check @bs (and its backing chain) to see if the entire image is known
* to read as zeroes.
* Return 1 if that is the case, 0 otherwise and -errno on error.
* This test is meant to be fast rather than accurate so returning 0
* does not guarantee non-zero data; however, a return of 1 is reliable,
* and this function can report 1 in more cases than bdrv_co_is_zero_fast.
*/
int coroutine_fn bdrv_co_is_all_zeroes(BlockDriverState *bs)
{
int ret;
int64_t pnum, bytes;
char *buf;
QEMUIOVector local_qiov;
IO_CODE();
bytes = bdrv_co_getlength(bs);
if (bytes < 0) {
return bytes;
}
/* First probe - see if the entire image reads as zero */
ret = bdrv_co_common_block_status_above(bs, NULL, false, BDRV_WANT_ZERO,
0, bytes, &pnum, NULL, NULL,
NULL);
if (ret < 0) {
return ret;
}
if (ret & BDRV_BLOCK_ZERO) {
return bdrv_co_is_zero_fast(bs, pnum, bytes - pnum);
}
return (pnum == bytes) && (ret & BDRV_BLOCK_ZERO);
/*
* Because of the way 'blockdev-create' works, raw files tend to
* be created with a non-sparse region at the front to make
* alignment probing easier. If the block starts with only a
* small allocated region, it is still worth the effort to see if
* the rest of the image is still sparse, coupled with manually
* reading the first region to see if it reads zero after all.
*/
if (pnum > MAX_ZERO_CHECK_BUFFER) {
return 0;
}
ret = bdrv_co_is_zero_fast(bs, pnum, bytes - pnum);
if (ret <= 0) {
return ret;
}
/* Only the head of the image is unknown, and it's small. Read it. */
buf = qemu_blockalign(bs, pnum);
qemu_iovec_init_buf(&local_qiov, buf, pnum);
ret = bdrv_driver_preadv(bs, 0, pnum, &local_qiov, 0, 0);
if (ret >= 0) {
ret = buffer_is_zero(buf, pnum);
}
qemu_vfree(buf);
return ret;
}
int coroutine_fn bdrv_co_is_allocated(BlockDriverState *bs, int64_t offset,
@ -2782,9 +2847,9 @@ int coroutine_fn bdrv_co_is_allocated(BlockDriverState *bs, int64_t offset,
int64_t dummy;
IO_CODE();
ret = bdrv_co_common_block_status_above(bs, bs, true, false, offset,
bytes, pnum ? pnum : &dummy, NULL,
NULL, NULL);
ret = bdrv_co_common_block_status_above(bs, bs, true, BDRV_WANT_ALLOCATED,
offset, bytes, pnum ? pnum : &dummy,
NULL, NULL, NULL);
if (ret < 0) {
return ret;
}
@ -2817,7 +2882,8 @@ int coroutine_fn bdrv_co_is_allocated_above(BlockDriverState *bs,
int ret;
IO_CODE();
ret = bdrv_co_common_block_status_above(bs, base, include_base, false,
ret = bdrv_co_common_block_status_above(bs, base, include_base,
BDRV_WANT_ALLOCATED,
offset, bytes, pnum, NULL, NULL,
&depth);
if (ret < 0) {
@ -3698,8 +3764,8 @@ bdrv_co_preadv_snapshot(BdrvChild *child, int64_t offset, int64_t bytes,
}
int coroutine_fn
bdrv_co_snapshot_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset, int64_t bytes,
bdrv_co_snapshot_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
@ -3717,7 +3783,7 @@ bdrv_co_snapshot_block_status(BlockDriverState *bs,
}
bdrv_inc_in_flight(bs);
ret = drv->bdrv_co_snapshot_block_status(bs, want_zero, offset, bytes,
ret = drv->bdrv_co_snapshot_block_status(bs, mode, offset, bytes,
pnum, map, file);
bdrv_dec_in_flight(bs);

View file

@ -694,9 +694,9 @@ out_unlock:
static int coroutine_fn iscsi_co_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum,
int64_t *map,
unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
IscsiLun *iscsilun = bs->opaque;

View file

@ -51,10 +51,10 @@ typedef struct MirrorBlockJob {
BlockDriverState *to_replace;
/* Used to block operations on the drive-mirror-replace target */
Error *replace_blocker;
bool is_none_mode;
MirrorSyncMode sync_mode;
BlockMirrorBackingMode backing_mode;
/* Whether the target image requires explicit zero-initialization */
bool zero_target;
/* Whether the target should be assumed to be already zero initialized */
bool target_is_zero;
/*
* To be accesssed with atomics. Written only under the BQL (required by the
* current implementation of mirror_change()).
@ -73,6 +73,7 @@ typedef struct MirrorBlockJob {
size_t buf_size;
int64_t bdev_length;
unsigned long *cow_bitmap;
unsigned long *zero_bitmap;
BdrvDirtyBitmap *dirty_bitmap;
BdrvDirtyBitmapIter *dbi;
uint8_t *buf;
@ -108,9 +109,12 @@ struct MirrorOp {
int64_t offset;
uint64_t bytes;
/* The pointee is set by mirror_co_read(), mirror_co_zero(), and
* mirror_co_discard() before yielding for the first time */
/*
* These pointers are set by mirror_co_read(), mirror_co_zero(), and
* mirror_co_discard() before yielding for the first time
*/
int64_t *bytes_handled;
bool *io_skipped;
bool is_pseudo_op;
bool is_active_write;
@ -408,15 +412,34 @@ static void coroutine_fn mirror_co_read(void *opaque)
static void coroutine_fn mirror_co_zero(void *opaque)
{
MirrorOp *op = opaque;
int ret;
bool write_needed = true;
int ret = 0;
op->s->in_flight++;
op->s->bytes_in_flight += op->bytes;
*op->bytes_handled = op->bytes;
op->is_in_flight = true;
ret = blk_co_pwrite_zeroes(op->s->target, op->offset, op->bytes,
op->s->unmap ? BDRV_REQ_MAY_UNMAP : 0);
if (op->s->zero_bitmap) {
unsigned long end = DIV_ROUND_UP(op->offset + op->bytes,
op->s->granularity);
assert(QEMU_IS_ALIGNED(op->offset, op->s->granularity));
assert(QEMU_IS_ALIGNED(op->bytes, op->s->granularity) ||
op->offset + op->bytes == op->s->bdev_length);
if (find_next_zero_bit(op->s->zero_bitmap, end,
op->offset / op->s->granularity) == end) {
write_needed = false;
*op->io_skipped = true;
}
}
if (write_needed) {
ret = blk_co_pwrite_zeroes(op->s->target, op->offset, op->bytes,
op->s->unmap ? BDRV_REQ_MAY_UNMAP : 0);
}
if (ret >= 0 && op->s->zero_bitmap) {
bitmap_set(op->s->zero_bitmap, op->offset / op->s->granularity,
DIV_ROUND_UP(op->bytes, op->s->granularity));
}
mirror_write_complete(op, ret);
}
@ -435,29 +458,43 @@ static void coroutine_fn mirror_co_discard(void *opaque)
}
static unsigned mirror_perform(MirrorBlockJob *s, int64_t offset,
unsigned bytes, MirrorMethod mirror_method)
unsigned bytes, MirrorMethod mirror_method,
bool *io_skipped)
{
MirrorOp *op;
Coroutine *co;
int64_t bytes_handled = -1;
assert(QEMU_IS_ALIGNED(offset, s->granularity));
assert(QEMU_IS_ALIGNED(bytes, s->granularity) ||
offset + bytes == s->bdev_length);
op = g_new(MirrorOp, 1);
*op = (MirrorOp){
.s = s,
.offset = offset,
.bytes = bytes,
.bytes_handled = &bytes_handled,
.io_skipped = io_skipped,
};
qemu_co_queue_init(&op->waiting_requests);
switch (mirror_method) {
case MIRROR_METHOD_COPY:
if (s->zero_bitmap) {
bitmap_clear(s->zero_bitmap, offset / s->granularity,
DIV_ROUND_UP(bytes, s->granularity));
}
co = qemu_coroutine_create(mirror_co_read, op);
break;
case MIRROR_METHOD_ZERO:
/* s->zero_bitmap handled in mirror_co_zero */
co = qemu_coroutine_create(mirror_co_zero, op);
break;
case MIRROR_METHOD_DISCARD:
if (s->zero_bitmap) {
bitmap_clear(s->zero_bitmap, offset / s->granularity,
DIV_ROUND_UP(bytes, s->granularity));
}
co = qemu_coroutine_create(mirror_co_discard, op);
break;
default:
@ -568,6 +605,7 @@ static void coroutine_fn GRAPH_UNLOCKED mirror_iteration(MirrorBlockJob *s)
int ret = -1;
int64_t io_bytes;
int64_t io_bytes_acct;
bool io_skipped = false;
MirrorMethod mirror_method = MIRROR_METHOD_COPY;
assert(!(offset % s->granularity));
@ -611,8 +649,10 @@ static void coroutine_fn GRAPH_UNLOCKED mirror_iteration(MirrorBlockJob *s)
}
io_bytes = mirror_clip_bytes(s, offset, io_bytes);
io_bytes = mirror_perform(s, offset, io_bytes, mirror_method);
if (mirror_method != MIRROR_METHOD_COPY && write_zeroes_ok) {
io_bytes = mirror_perform(s, offset, io_bytes, mirror_method,
&io_skipped);
if (io_skipped ||
(mirror_method != MIRROR_METHOD_COPY && write_zeroes_ok)) {
io_bytes_acct = 0;
} else {
io_bytes_acct = io_bytes;
@ -723,9 +763,10 @@ static int mirror_exit_common(Job *job)
&error_abort);
if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) {
BlockDriverState *backing = s->is_none_mode ? src : s->base;
BlockDriverState *backing;
BlockDriverState *unfiltered_target = bdrv_skip_filters(target_bs);
backing = s->sync_mode == MIRROR_SYNC_MODE_NONE ? src : s->base;
if (bdrv_cow_bs(unfiltered_target) != backing) {
bdrv_set_backing_hd(unfiltered_target, backing, &local_err);
if (local_err) {
@ -841,15 +882,54 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
int64_t offset;
BlockDriverState *bs;
BlockDriverState *target_bs = blk_bs(s->target);
int ret = -1;
int ret = -EIO;
int64_t count;
bool punch_holes =
target_bs->detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP &&
bdrv_can_write_zeroes_with_unmap(target_bs);
int64_t bitmap_length = DIV_ROUND_UP(s->bdev_length, s->granularity);
/* Determine if the image is already zero, regardless of sync mode. */
s->zero_bitmap = bitmap_new(bitmap_length);
bdrv_graph_co_rdlock();
bs = s->mirror_top_bs->backing->bs;
if (s->target_is_zero) {
ret = 1;
} else {
ret = bdrv_co_is_all_zeroes(target_bs);
}
bdrv_graph_co_rdunlock();
if (s->zero_target) {
if (!bdrv_can_write_zeroes_with_unmap(target_bs)) {
/* Determine if a pre-zeroing pass is necessary. */
if (ret < 0) {
return ret;
} else if (s->sync_mode == MIRROR_SYNC_MODE_TOP) {
/*
* In TOP mode, there is no benefit to a pre-zeroing pass, but
* the zero bitmap can be set if the destination already reads
* as zero and we are not punching holes.
*/
if (ret > 0 && !punch_holes) {
bitmap_set(s->zero_bitmap, 0, bitmap_length);
}
} else if (ret == 0 || punch_holes) {
/*
* Here, we are in FULL mode; our goal is to avoid writing
* zeroes if the destination already reads as zero, except
* when we are trying to punch holes. This is possible if
* zeroing happened externally (ret > 0) or if we have a fast
* way to pre-zero the image (the dirty bitmap will be
* populated later by the non-zero portions, the same as for
* TOP mode). If pre-zeroing is not fast, or we need to visit
* the entire image in order to punch holes even in the
* non-allocated regions of the source, then just mark the
* entire image dirty and leave the zero bitmap clear at this
* point in time. Otherwise, it can be faster to pre-zero the
* image now, even if we re-write the allocated portions of
* the disk later, and the pre-zero pass will populate the
* zero bitmap.
*/
if (!bdrv_can_write_zeroes_with_unmap(target_bs) || punch_holes) {
bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length);
return 0;
}
@ -858,6 +938,7 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
for (offset = 0; offset < s->bdev_length; ) {
int bytes = MIN(s->bdev_length - offset,
QEMU_ALIGN_DOWN(INT_MAX, s->granularity));
bool ignored;
mirror_throttle(s);
@ -873,12 +954,15 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
continue;
}
mirror_perform(s, offset, bytes, MIRROR_METHOD_ZERO);
mirror_perform(s, offset, bytes, MIRROR_METHOD_ZERO, &ignored);
offset += bytes;
}
mirror_wait_for_all_io(s);
s->initial_zeroing_ongoing = false;
} else {
/* In FULL mode, and image already reads as zero. */
bitmap_set(s->zero_bitmap, 0, bitmap_length);
}
/* First part, loop on the sectors and initialize the dirty bitmap. */
@ -1020,7 +1104,7 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
mirror_free_init(s);
s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
if (!s->is_none_mode) {
if (s->sync_mode != MIRROR_SYNC_MODE_NONE) {
ret = mirror_dirty_init(s);
if (ret < 0 || job_is_cancelled(&s->common.job)) {
goto immediate_exit;
@ -1163,6 +1247,7 @@ immediate_exit:
assert(s->in_flight == 0);
qemu_vfree(s->buf);
g_free(s->cow_bitmap);
g_free(s->zero_bitmap);
g_free(s->in_flight_bitmap);
bdrv_dirty_iter_free(s->dbi);
@ -1341,7 +1426,8 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
{
int ret;
size_t qiov_offset = 0;
int64_t bitmap_offset, bitmap_end;
int64_t dirty_bitmap_offset, dirty_bitmap_end;
int64_t zero_bitmap_offset, zero_bitmap_end;
if (!QEMU_IS_ALIGNED(offset, job->granularity) &&
bdrv_dirty_bitmap_get(job->dirty_bitmap, offset))
@ -1385,31 +1471,54 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
}
/*
* Tails are either clean or shrunk, so for bitmap resetting
* we safely align the range down.
* Tails are either clean or shrunk, so for dirty bitmap resetting
* we safely align the range narrower. But for zero bitmap, round
* range wider for checking or clearing, and narrower for setting.
*/
bitmap_offset = QEMU_ALIGN_UP(offset, job->granularity);
bitmap_end = QEMU_ALIGN_DOWN(offset + bytes, job->granularity);
if (bitmap_offset < bitmap_end) {
bdrv_reset_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
bitmap_end - bitmap_offset);
dirty_bitmap_offset = QEMU_ALIGN_UP(offset, job->granularity);
dirty_bitmap_end = QEMU_ALIGN_DOWN(offset + bytes, job->granularity);
if (dirty_bitmap_offset < dirty_bitmap_end) {
bdrv_reset_dirty_bitmap(job->dirty_bitmap, dirty_bitmap_offset,
dirty_bitmap_end - dirty_bitmap_offset);
}
zero_bitmap_offset = offset / job->granularity;
zero_bitmap_end = DIV_ROUND_UP(offset + bytes, job->granularity);
job_progress_increase_remaining(&job->common.job, bytes);
job->active_write_bytes_in_flight += bytes;
switch (method) {
case MIRROR_METHOD_COPY:
if (job->zero_bitmap) {
bitmap_clear(job->zero_bitmap, zero_bitmap_offset,
zero_bitmap_end - zero_bitmap_offset);
}
ret = blk_co_pwritev_part(job->target, offset, bytes,
qiov, qiov_offset, flags);
break;
case MIRROR_METHOD_ZERO:
if (job->zero_bitmap) {
if (find_next_zero_bit(job->zero_bitmap, zero_bitmap_end,
zero_bitmap_offset) == zero_bitmap_end) {
ret = 0;
break;
}
}
assert(!qiov);
ret = blk_co_pwrite_zeroes(job->target, offset, bytes, flags);
if (job->zero_bitmap && ret >= 0) {
bitmap_set(job->zero_bitmap, dirty_bitmap_offset / job->granularity,
(dirty_bitmap_end - dirty_bitmap_offset) /
job->granularity);
}
break;
case MIRROR_METHOD_DISCARD:
if (job->zero_bitmap) {
bitmap_clear(job->zero_bitmap, zero_bitmap_offset,
zero_bitmap_end - zero_bitmap_offset);
}
assert(!qiov);
ret = blk_co_pdiscard(job->target, offset, bytes);
break;
@ -1430,10 +1539,10 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
* at function start, and they must be still dirty, as we've locked
* the region for in-flight op.
*/
bitmap_offset = QEMU_ALIGN_DOWN(offset, job->granularity);
bitmap_end = QEMU_ALIGN_UP(offset + bytes, job->granularity);
bdrv_set_dirty_bitmap(job->dirty_bitmap, bitmap_offset,
bitmap_end - bitmap_offset);
dirty_bitmap_offset = QEMU_ALIGN_DOWN(offset, job->granularity);
dirty_bitmap_end = QEMU_ALIGN_UP(offset + bytes, job->granularity);
bdrv_set_dirty_bitmap(job->dirty_bitmap, dirty_bitmap_offset,
dirty_bitmap_end - dirty_bitmap_offset);
qatomic_set(&job->actively_synced, false);
action = mirror_error_action(job, false, -ret);
@ -1711,15 +1820,16 @@ static BlockJob *mirror_start_job(
int creation_flags, BlockDriverState *target,
const char *replaces, int64_t speed,
uint32_t granularity, int64_t buf_size,
MirrorSyncMode sync_mode,
BlockMirrorBackingMode backing_mode,
bool zero_target,
bool target_is_zero,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
bool unmap,
BlockCompletionFunc *cb,
void *opaque,
const BlockJobDriver *driver,
bool is_none_mode, BlockDriverState *base,
BlockDriverState *base,
bool auto_complete, const char *filter_node_name,
bool is_mirror, MirrorCopyMode copy_mode,
bool base_ro,
@ -1878,9 +1988,9 @@ static BlockJob *mirror_start_job(
s->replaces = g_strdup(replaces);
s->on_source_error = on_source_error;
s->on_target_error = on_target_error;
s->is_none_mode = is_none_mode;
s->sync_mode = sync_mode;
s->backing_mode = backing_mode;
s->zero_target = zero_target;
s->target_is_zero = target_is_zero;
qatomic_set(&s->copy_mode, copy_mode);
s->base = base;
s->base_overlay = bdrv_find_overlay(bs, base);
@ -2009,13 +2119,12 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
int creation_flags, int64_t speed,
uint32_t granularity, int64_t buf_size,
MirrorSyncMode mode, BlockMirrorBackingMode backing_mode,
bool zero_target,
bool target_is_zero,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
bool unmap, const char *filter_node_name,
MirrorCopyMode copy_mode, Error **errp)
{
bool is_none_mode;
BlockDriverState *base;
GLOBAL_STATE_CODE();
@ -2028,14 +2137,13 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
}
bdrv_graph_rdlock_main_loop();
is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
base = mode == MIRROR_SYNC_MODE_TOP ? bdrv_backing_chain_next(bs) : NULL;
bdrv_graph_rdunlock_main_loop();
mirror_start_job(job_id, bs, creation_flags, target, replaces,
speed, granularity, buf_size, backing_mode, zero_target,
on_source_error, on_target_error, unmap, NULL, NULL,
&mirror_job_driver, is_none_mode, base, false,
speed, granularity, buf_size, mode, backing_mode,
target_is_zero, on_source_error, on_target_error, unmap,
NULL, NULL, &mirror_job_driver, base, false,
filter_node_name, true, copy_mode, false, errp);
}
@ -2061,9 +2169,9 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
job = mirror_start_job(
job_id, bs, creation_flags, base, NULL, speed, 0, 0,
MIRROR_LEAVE_BACKING_CHAIN, false,
MIRROR_SYNC_MODE_TOP, MIRROR_LEAVE_BACKING_CHAIN, false,
on_error, on_error, true, cb, opaque,
&commit_active_job_driver, false, base, auto_complete,
&commit_active_job_driver, base, auto_complete,
filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND,
base_read_only, errp);
if (!job) {

View file

@ -1397,8 +1397,8 @@ nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int64_t bytes)
}
static int coroutine_fn GRAPH_RDLOCK nbd_client_co_block_status(
BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map, BlockDriverState **file)
BlockDriverState *bs, unsigned int mode, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map, BlockDriverState **file)
{
int ret, request_ret;
NBDExtent64 extent = { 0 };

View file

@ -227,9 +227,9 @@ static int null_reopen_prepare(BDRVReopenState *reopen_state,
}
static int coroutine_fn null_co_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum,
int64_t *map,
unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
BDRVNullState *s = bs->opaque;

View file

@ -416,9 +416,9 @@ parallels_co_flush_to_os(BlockDriverState *bs)
}
static int coroutine_fn GRAPH_RDLOCK
parallels_co_block_status(BlockDriverState *bs, bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map,
BlockDriverState **file)
parallels_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{
BDRVParallelsState *s = bs->opaque;
int count;

View file

@ -530,7 +530,7 @@ get_cluster_offset(BlockDriverState *bs, uint64_t offset, int allocate,
}
static int coroutine_fn GRAPH_RDLOCK
qcow_co_block_status(BlockDriverState *bs, bool want_zero,
qcow_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{

View file

@ -2141,9 +2141,9 @@ static void qcow2_join_options(QDict *options, QDict *old_options)
}
static int coroutine_fn GRAPH_RDLOCK
qcow2_co_block_status(BlockDriverState *bs, bool want_zero, int64_t offset,
int64_t count, int64_t *pnum, int64_t *map,
BlockDriverState **file)
qcow2_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t count, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{
BDRVQcow2State *s = bs->opaque;
uint64_t host_offset;

View file

@ -833,9 +833,9 @@ fail:
}
static int coroutine_fn GRAPH_RDLOCK
bdrv_qed_co_block_status(BlockDriverState *bs, bool want_zero, int64_t pos,
int64_t bytes, int64_t *pnum, int64_t *map,
BlockDriverState **file)
bdrv_qed_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t pos, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{
BDRVQEDState *s = bs->opaque;
size_t len = MIN(bytes, SIZE_MAX);

View file

@ -1226,7 +1226,7 @@ static void quorum_child_perm(BlockDriverState *bs, BdrvChild *c,
* region contains zeroes, and BDRV_BLOCK_DATA otherwise.
*/
static int coroutine_fn GRAPH_RDLOCK
quorum_co_block_status(BlockDriverState *bs, bool want_zero,
quorum_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t count,
int64_t *pnum, int64_t *map, BlockDriverState **file)
{
@ -1238,7 +1238,7 @@ quorum_co_block_status(BlockDriverState *bs, bool want_zero,
for (i = 0; i < s->num_children; i++) {
int64_t bytes;
ret = bdrv_co_common_block_status_above(s->children[i]->bs, NULL, false,
want_zero, offset, count,
mode, offset, count,
&bytes, NULL, NULL, NULL);
if (ret < 0) {
quorum_report_bad(QUORUM_OP_TYPE_READ, offset, count,

View file

@ -283,8 +283,8 @@ fail:
}
static int coroutine_fn GRAPH_RDLOCK
raw_co_block_status(BlockDriverState *bs, bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map,
raw_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
BDRVRawState *s = bs->opaque;

View file

@ -1503,9 +1503,9 @@ static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
}
static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum,
int64_t *map,
unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
BDRVRBDState *s = bs->opaque;

View file

@ -41,11 +41,11 @@ snapshot_access_co_preadv_part(BlockDriverState *bs,
static int coroutine_fn GRAPH_RDLOCK
snapshot_access_co_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset,
unsigned int mode, 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,
return bdrv_co_snapshot_block_status(bs->file->bs, mode, offset,
bytes, pnum, map, file);
}

View file

@ -523,8 +523,8 @@ static int vdi_reopen_prepare(BDRVReopenState *state,
}
static int coroutine_fn GRAPH_RDLOCK
vdi_co_block_status(BlockDriverState *bs, bool want_zero, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map,
vdi_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum, int64_t *map,
BlockDriverState **file)
{
BDRVVdiState *s = (BDRVVdiState *)bs->opaque;

View file

@ -1777,7 +1777,7 @@ static inline uint64_t vmdk_find_offset_in_cluster(VmdkExtent *extent,
}
static int coroutine_fn GRAPH_RDLOCK
vmdk_co_block_status(BlockDriverState *bs, bool want_zero,
vmdk_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file)
{

View file

@ -726,7 +726,7 @@ fail:
}
static int coroutine_fn GRAPH_RDLOCK
vpc_co_block_status(BlockDriverState *bs, bool want_zero,
vpc_co_block_status(BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map,
BlockDriverState **file)

View file

@ -3134,9 +3134,9 @@ vvfat_co_pwritev(BlockDriverState *bs, int64_t offset, int64_t bytes,
}
static int coroutine_fn vvfat_co_block_status(BlockDriverState *bs,
bool want_zero, int64_t offset,
int64_t bytes, int64_t *n,
int64_t *map,
unsigned int mode,
int64_t offset, int64_t bytes,
int64_t *n, int64_t *map,
BlockDriverState **file)
{
*n = bytes;

View file

@ -2804,7 +2804,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
const char *replaces,
enum MirrorSyncMode sync,
BlockMirrorBackingMode backing_mode,
bool zero_target,
bool target_is_zero,
bool has_speed, int64_t speed,
bool has_granularity, uint32_t granularity,
bool has_buf_size, int64_t buf_size,
@ -2871,10 +2871,6 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
return;
}
if (!bdrv_backing_chain_next(bs) && sync == MIRROR_SYNC_MODE_TOP) {
sync = MIRROR_SYNC_MODE_FULL;
}
if (!replaces) {
/* We want to mirror from @bs, but keep implicit filters on top */
unfiltered_bs = bdrv_skip_implicit_filters(bs);
@ -2915,11 +2911,10 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
/* pass the node name to replace to mirror start since it's loose coupling
* and will allow to check whether the node still exist at mirror completion
*/
mirror_start(job_id, bs, target,
replaces, job_flags,
speed, granularity, buf_size, sync, backing_mode, zero_target,
on_source_error, on_target_error, unmap, filter_node_name,
copy_mode, errp);
mirror_start(job_id, bs, target, replaces, job_flags,
speed, granularity, buf_size, sync, backing_mode,
target_is_zero, on_source_error, on_target_error, unmap,
filter_node_name, copy_mode, errp);
}
void qmp_drive_mirror(DriveMirror *arg, Error **errp)
@ -2933,7 +2928,7 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
int flags;
int64_t size;
const char *format = arg->format;
bool zero_target;
bool target_is_zero;
int ret;
bs = qmp_get_root_bs(arg->device, errp);
@ -3047,9 +3042,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
}
bdrv_graph_rdlock_main_loop();
zero_target = (arg->sync == MIRROR_SYNC_MODE_FULL &&
(arg->mode == NEW_IMAGE_MODE_EXISTING ||
!bdrv_has_zero_init(target_bs)));
target_is_zero = (arg->mode != NEW_IMAGE_MODE_EXISTING &&
bdrv_has_zero_init(target_bs));
bdrv_graph_rdunlock_main_loop();
@ -3061,7 +3055,7 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
blockdev_mirror_common(arg->job_id, bs, target_bs,
arg->replaces, arg->sync,
backing_mode, zero_target,
backing_mode, target_is_zero,
arg->has_speed, arg->speed,
arg->has_granularity, arg->granularity,
arg->has_buf_size, arg->buf_size,
@ -3091,13 +3085,13 @@ void qmp_blockdev_mirror(const char *job_id,
bool has_copy_mode, MirrorCopyMode copy_mode,
bool has_auto_finalize, bool auto_finalize,
bool has_auto_dismiss, bool auto_dismiss,
bool has_target_is_zero, bool target_is_zero,
Error **errp)
{
BlockDriverState *bs;
BlockDriverState *target_bs;
AioContext *aio_context;
BlockMirrorBackingMode backing_mode = MIRROR_LEAVE_BACKING_CHAIN;
bool zero_target;
int ret;
bs = qmp_get_root_bs(device, errp);
@ -3110,8 +3104,6 @@ void qmp_blockdev_mirror(const char *job_id,
return;
}
zero_target = (sync == MIRROR_SYNC_MODE_FULL);
aio_context = bdrv_get_aio_context(bs);
ret = bdrv_try_change_aio_context(target_bs, aio_context, NULL, errp);
@ -3121,7 +3113,8 @@ void qmp_blockdev_mirror(const char *job_id,
blockdev_mirror_common(job_id, bs, target_bs,
replaces, sync, backing_mode,
zero_target, has_speed, speed,
has_target_is_zero && target_is_zero,
has_speed, speed,
has_granularity, granularity,
has_buf_size, buf_size,
has_on_source_error, on_source_error,

View file

@ -333,6 +333,17 @@ typedef enum {
#define BDRV_BLOCK_RECURSE 0x40
#define BDRV_BLOCK_COMPRESSED 0x80
/*
* Block status hints: the bitwise-or of these flags emphasize what
* the caller hopes to learn, and some drivers may be able to give
* faster answers by doing less work when the hint permits.
*/
#define BDRV_WANT_ZERO BDRV_BLOCK_ZERO
#define BDRV_WANT_OFFSET_VALID BDRV_BLOCK_OFFSET_VALID
#define BDRV_WANT_ALLOCATED BDRV_BLOCK_ALLOCATED
#define BDRV_WANT_PRECISE (BDRV_WANT_ZERO | BDRV_WANT_OFFSET_VALID | \
BDRV_WANT_OFFSET_VALID)
typedef QTAILQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
typedef struct BDRVReopenState {

View file

@ -161,6 +161,8 @@ bdrv_is_allocated_above(BlockDriverState *bs, BlockDriverState *base,
int coroutine_fn GRAPH_RDLOCK
bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset, int64_t bytes);
int coroutine_fn GRAPH_RDLOCK
bdrv_co_is_all_zeroes(BlockDriverState *bs);
int GRAPH_RDLOCK
bdrv_apply_auto_read_only(BlockDriverState *bs, const char *errmsg,

View file

@ -604,15 +604,16 @@ struct BlockDriver {
* according to the current layer, and should only need to set
* BDRV_BLOCK_DATA, BDRV_BLOCK_ZERO, BDRV_BLOCK_OFFSET_VALID,
* and/or BDRV_BLOCK_RAW; if the current layer defers to a backing
* layer, the result should be 0 (and not BDRV_BLOCK_ZERO). See
* block.h for the overall meaning of the bits. As a hint, the
* flag want_zero is true if the caller cares more about precise
* mappings (favor accurate _OFFSET_VALID/_ZERO) or false for
* overall allocation (favor larger *pnum, perhaps by reporting
* _DATA instead of _ZERO). The block layer guarantees input
* clamped to bdrv_getlength() and aligned to request_alignment,
* as well as non-NULL pnum, map, and file; in turn, the driver
* must return an error or set pnum to an aligned non-zero value.
* layer, the result should be 0 (and not BDRV_BLOCK_ZERO). The
* caller will synthesize BDRV_BLOCK_ALLOCATED based on the
* non-zero results. See block.h for the overall meaning of the
* bits. As a hint, the flags in @mode may include a bitwise-or
* of BDRV_WANT_ALLOCATED, BDRV_WANT_OFFSET_VALID, or
* BDRV_WANT_ZERO based on what the caller is looking for in the
* results. The block layer guarantees input clamped to
* bdrv_getlength() and aligned to request_alignment, as well as
* non-NULL pnum, map, and file; in turn, the driver must return
* an error or set pnum to an aligned non-zero value.
*
* Note that @bytes is just a hint on how big of a region the
* caller wants to inspect. It is not a limit on *pnum.
@ -624,8 +625,8 @@ struct BlockDriver {
* to clamping *pnum for return to its caller.
*/
int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_block_status)(
BlockDriverState *bs,
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
BlockDriverState *bs, unsigned int mode,
int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file);
/*
@ -649,8 +650,8 @@ struct BlockDriver {
QEMUIOVector *qiov, size_t qiov_offset);
int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_snapshot_block_status)(
BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map, BlockDriverState **file);
BlockDriverState *bs, unsigned int mode, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map, BlockDriverState **file);
int coroutine_fn GRAPH_RDLOCK_PTR (*bdrv_co_pdiscard_snapshot)(
BlockDriverState *bs, int64_t offset, int64_t bytes);

View file

@ -139,7 +139,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
* @buf_size: The amount of data that can be in flight at one time.
* @mode: Whether to collapse all images in the chain to the target.
* @backing_mode: How to establish the target's backing chain after completion.
* @zero_target: Whether the target should be explicitly zero-initialized
* @target_is_zero: Whether the target already is zero-initialized.
* @on_source_error: The action to take upon error reading from the source.
* @on_target_error: The action to take upon error writing to the target.
* @unmap: Whether to unmap target where source sectors only contain zeroes.
@ -159,7 +159,7 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
int creation_flags, int64_t speed,
uint32_t granularity, int64_t buf_size,
MirrorSyncMode mode, BlockMirrorBackingMode backing_mode,
bool zero_target,
bool target_is_zero,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
bool unmap, const char *filter_node_name,

View file

@ -38,8 +38,8 @@
int coroutine_fn GRAPH_RDLOCK bdrv_co_preadv_snapshot(BdrvChild *child,
int64_t offset, int64_t bytes, QEMUIOVector *qiov, size_t qiov_offset);
int coroutine_fn GRAPH_RDLOCK bdrv_co_snapshot_block_status(
BlockDriverState *bs, bool want_zero, int64_t offset, int64_t bytes,
int64_t *pnum, int64_t *map, BlockDriverState **file);
BlockDriverState *bs, unsigned int mode, int64_t offset,
int64_t bytes, int64_t *pnum, int64_t *map, BlockDriverState **file);
int coroutine_fn GRAPH_RDLOCK bdrv_co_pdiscard_snapshot(BlockDriverState *bs,
int64_t offset, int64_t bytes);

View file

@ -2542,6 +2542,11 @@
# disappear from the query list without user intervention.
# Defaults to true. (Since 3.1)
#
# @target-is-zero: Assume the destination reads as all zeroes before
# the mirror started. Setting this to true can speed up the
# mirror. Setting this to true when the destination is not
# actually all zero can corrupt the destination. (Since 10.1)
#
# Since: 2.6
#
# .. qmp-example::
@ -2561,7 +2566,8 @@
'*on-target-error': 'BlockdevOnError',
'*filter-node-name': 'str',
'*copy-mode': 'MirrorCopyMode',
'*auto-finalize': 'bool', '*auto-dismiss': 'bool' },
'*auto-finalize': 'bool', '*auto-dismiss': 'bool',
'*target-is-zero': 'bool'},
'allow-preconfig': true }
##

View file

@ -34,6 +34,7 @@ with iotests.FilePath('source.img') as source_img_path, \
img_size = '1G'
iotests.qemu_img_create('-f', iotests.imgfmt, source_img_path, img_size)
iotests.qemu_io('-f', iotests.imgfmt, '-c', 'write 512M 1M', source_img_path)
iotests.qemu_img_create('-f', iotests.imgfmt, dest_img_path, img_size)
iotests.log('Launching VMs...')
@ -61,7 +62,8 @@ with iotests.FilePath('source.img') as source_img_path, \
iotests.log('Waiting for `drive-mirror` to complete...')
iotests.log(source_vm.event_wait('BLOCK_JOB_READY'),
filters=[iotests.filter_qmp_event])
filters=[iotests.filter_qmp_event,
iotests.filter_block_job])
iotests.log('Starting migration...')
capabilities = [{'capability': 'events', 'state': True},
@ -87,7 +89,8 @@ with iotests.FilePath('source.img') as source_img_path, \
while True:
event2 = source_vm.event_wait('BLOCK_JOB_COMPLETED')
iotests.log(event2, filters=[iotests.filter_qmp_event])
iotests.log(event2, filters=[iotests.filter_qmp_event,
iotests.filter_block_job])
if event2['event'] == 'BLOCK_JOB_COMPLETED':
iotests.log('Stopping the NBD server on destination...')
iotests.log(dest_vm.qmp('nbd-server-stop'))

View file

@ -7,7 +7,7 @@ Launching NBD server on destination...
Starting `drive-mirror` on source...
{"return": {}}
Waiting for `drive-mirror` to complete...
{"data": {"device": "mirror-job0", "len": 1073741824, "offset": 1073741824, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"data": {"device": "mirror-job0", "len": "LEN", "offset": "OFFSET", "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
Starting migration...
{"return": {}}
{"execute": "migrate-start-postcopy", "arguments": {}}
@ -18,7 +18,7 @@ Starting migration...
{"data": {"status": "completed"}, "event": "MIGRATION", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
Gracefully ending the `drive-mirror` job on source...
{"return": {}}
{"data": {"device": "mirror-job0", "len": 1073741824, "offset": 1073741824, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
{"data": {"device": "mirror-job0", "len": "LEN", "offset": "OFFSET", "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
Stopping the NBD server on destination...
{"return": {}}
Wait for migration completion on target...

View file

@ -52,11 +52,6 @@ _unsupported_imgopts data_file
# bdrv_co_truncate(bs->file) call in qcow2_co_truncate(), which might succeed
# anyway.
disk_usage()
{
du --block-size=1 $1 | awk '{print $1}'
}
size=2100M
_make_test_img -o "cluster_size=1M,preallocation=metadata" $size

View file

@ -140,6 +140,12 @@ _optstr_add()
fi
}
# report real disk usage for sparse files
disk_usage()
{
du --block-size=1 "$1" | awk '{print $1}'
}
# Set the variables to the empty string to turn Valgrind off
# for specific processes, e.g.
# $ VALGRIND_QEMU_IO= ./check -qcow2 -valgrind 015

View file

@ -601,13 +601,23 @@ def filter_chown(msg):
return chown_re.sub("chown UID:GID", msg)
def filter_qmp_event(event):
'''Filter a QMP event dict'''
'''Filter the timestamp of a QMP event dict'''
event = dict(event)
if 'timestamp' in event:
event['timestamp']['seconds'] = 'SECS'
event['timestamp']['microseconds'] = 'USECS'
return event
def filter_block_job(event):
'''Filter the offset and length of a QMP block job event dict'''
event = dict(event)
if 'data' in event:
if 'offset' in event['data']:
event['data']['offset'] = 'OFFSET'
if 'len' in event['data']:
event['data']['len'] = 'LEN'
return event
def filter_qmp(qmsg, filter_fn):
'''Given a string filter, filter a QMP object's values.
filter_fn takes a (key, value) pair.'''

View file

@ -0,0 +1,125 @@
#!/usr/bin/env bash
# group: rw auto quick
#
# Test blockdev-mirror with raw sparse destination
#
# Copyright (C) 2025 Red Hat, Inc.
#
# 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/>.
#
seq="$(basename $0)"
echo "QA output created by $seq"
status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
_cleanup_qemu
}
trap "_cleanup; exit \$status" 0 1 2 3 15
# get standard environment, filters and checks
cd ..
. ./common.rc
. ./common.filter
. ./common.qemu
_supported_fmt qcow2 raw # Format of the source. dst is always raw file
_supported_proto file
_supported_os Linux
echo
echo "=== Initial image setup ==="
echo
TEST_IMG="$TEST_IMG.base" _make_test_img 20M
$QEMU_IO -c 'w 8M 2M' -f $IMGFMT "$TEST_IMG.base" | _filter_qemu_io
_launch_qemu \
-blockdev '{"driver":"file", "cache":{"direct":true, "no-flush":false},
"filename":"'"$TEST_IMG.base"'", "node-name":"src-file"}' \
-blockdev '{"driver":"'$IMGFMT'", "node-name":"src", "file":"src-file"}'
h1=$QEMU_HANDLE
_send_qemu_cmd $h1 '{"execute": "qmp_capabilities"}' 'return'
# Check several combinations; most should result in a sparse destination;
# the destination should only be fully allocated if pre-allocated
# and not punching holes due to detect-zeroes
# do_test creation discard zeroes result
do_test() {
creation=$1
discard=$2
zeroes=$3
expected=$4
echo
echo "=== Testing creation=$creation discard=$discard zeroes=$zeroes ==="
echo
rm -f $TEST_IMG
if test $creation = external; then
truncate --size=20M $TEST_IMG
else
_send_qemu_cmd $h1 '{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"'$TEST_IMG'",
"size":'$((20*1024*1024))', "preallocation":"'$creation'"},
"job-id":"job1"}}' 'concluded'
_send_qemu_cmd $h1 '{"execute": "job-dismiss", "arguments":
{"id": "job1"}}' 'return'
fi
_send_qemu_cmd $h1 '{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"'$TEST_IMG'", "aio":"threads",
"auto-read-only":true, "discard":"'$discard'",
"detect-zeroes":"'$zeroes'"}}' 'return'
_send_qemu_cmd $h1 '{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}' 'return'
_timed_wait_for $h1 '"ready"'
_send_qemu_cmd $h1 '{"execute": "job-complete", "arguments":
{"id":"job2"}}' 'return' \
| _filter_block_job_offset | _filter_block_job_len
_send_qemu_cmd $h1 '{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}' 'return' \
| _filter_block_job_offset | _filter_block_job_len
$QEMU_IMG compare -U -f $IMGFMT -F raw $TEST_IMG.base $TEST_IMG
result=$(disk_usage $TEST_IMG)
if test $result -lt $((3*1024*1024)); then
actual=sparse
elif test $result = $((20*1024*1024)); then
actual=full
else
actual=unknown
fi
echo "Destination is $actual; expected $expected"
}
do_test external ignore off sparse
do_test external unmap off sparse
do_test external unmap unmap sparse
do_test off ignore off sparse
do_test off unmap off sparse
do_test off unmap unmap sparse
do_test full ignore off full
do_test full unmap off sparse
do_test full unmap unmap sparse
_send_qemu_cmd $h1 '{"execute":"quit"}' ''
# success, all done
echo '*** done'
rm -f $seq.full
status=0

View file

@ -0,0 +1,365 @@
QA output created by mirror-sparse
=== Initial image setup ===
Formatting 'TEST_DIR/t.IMGFMT.base', fmt=IMGFMT size=20971520
wrote 2097152/2097152 bytes at offset 8388608
2 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
{"execute": "qmp_capabilities"}
{"return": {}}
=== Testing creation=external discard=ignore zeroes=off ===
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"ignore",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=external discard=unmap zeroes=off ===
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=external discard=unmap zeroes=unmap ===
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"unmap"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=off discard=ignore zeroes=off ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"off"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"ignore",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=off discard=unmap zeroes=off ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"off"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=off discard=unmap zeroes=unmap ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"off"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"unmap"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=full discard=ignore zeroes=off ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"full"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"ignore",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is full; expected full
=== Testing creation=full discard=unmap zeroes=off ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"full"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"off"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
=== Testing creation=full discard=unmap zeroes=unmap ===
{"execute": "blockdev-create", "arguments":
{"options": {"driver":"file", "filename":"TEST_DIR/t.IMGFMT",
"size":20971520, "preallocation":"full"},
"job-id":"job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job1"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job1"}}
{"execute": "job-dismiss", "arguments":
{"id": "job1"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job1"}}
{"return": {}}
{"execute": "blockdev-add", "arguments":
{"node-name": "dst", "driver":"file",
"filename":"TEST_DIR/t.IMGFMT", "aio":"threads",
"auto-read-only":true, "discard":"unmap",
"detect-zeroes":"unmap"}}
{"return": {}}
{"execute":"blockdev-mirror", "arguments":
{"sync":"full", "device":"src", "target":"dst",
"job-id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "created", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "running", "id": "job2"}}
{"return": {}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "ready", "id": "job2"}}
{"execute": "job-complete", "arguments":
{"id":"job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_READY", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"return": {}}
{"execute": "blockdev-del", "arguments":
{"node-name": "dst"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "waiting", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "pending", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "BLOCK_JOB_COMPLETED", "data": {"device": "job2", "len": LEN, "offset": OFFSET, "speed": 0, "type": "mirror"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "concluded", "id": "job2"}}
{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "JOB_STATUS_CHANGE", "data": {"status": "null", "id": "job2"}}
{"return": {}}
Images are identical.
Destination is sparse; expected sparse
{"execute":"quit"}
*** done

View file

@ -63,7 +63,7 @@ bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
}
static int coroutine_fn bdrv_test_co_block_status(BlockDriverState *bs,
bool want_zero,
unsigned int mode,
int64_t offset, int64_t count,
int64_t *pnum, int64_t *map,
BlockDriverState **file)