* Mon May 26 2025 Miroslav Rezanina <mrezanin@redhat.com> - 10.0.0-4
- kvm-block-Expand-block-status-mode-from-bool-to-flags.patch [RHEL-88435 RHEL-88437] - kvm-file-posix-gluster-Handle-zero-block-status-hint-bet.patch [RHEL-88435 RHEL-88437] - kvm-block-Let-bdrv_co_is_zero_fast-consolidate-adjacent-.patch [RHEL-88435 RHEL-88437] - kvm-block-Add-new-bdrv_co_is_all_zeroes-function.patch [RHEL-88435 RHEL-88437] - kvm-iotests-Improve-iotest-194-to-mirror-data.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Minor-refactoring.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Pass-full-sync-mode-rather-than-bool-to-inter.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Allow-QMP-override-to-declare-target-already-.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Drop-redundant-zero_target-parameter.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Skip-pre-zeroing-destination-if-it-is-already.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Skip-writing-zeroes-when-target-is-already-ze.patch [RHEL-88435 RHEL-88437] - kvm-iotests-common.rc-add-disk_usage-function.patch [RHEL-88435 RHEL-88437] - kvm-tests-Add-iotest-mirror-sparse-for-recent-patches.patch [RHEL-88435 RHEL-88437] - kvm-mirror-Reduce-I-O-when-destination-is-detect-zeroes-.patch [RHEL-88435 RHEL-88437] - Resolves: RHEL-88435 (--migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1]) - Resolves: RHEL-88437 (Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1])
This commit is contained in:
		
							parent
							
								
									9c8774ec77
								
							
						
					
					
						commit
						f696e6c6e2
					
				
							
								
								
									
										143
									
								
								kvm-block-Add-new-bdrv_co_is_all_zeroes-function.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								kvm-block-Add-new-bdrv_co_is_all_zeroes-function.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | ||||
| From 659dd2d1f7b1facbf9c548468c1b50237f7aa8e4 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:21 -0500 | ||||
| Subject: [PATCH 04/14] block: Add new bdrv_co_is_all_zeroes() function | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [4/14] 404590dbec0b1113872a7eb1bfe5af0450fe6a28 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| There are some optimizations that require knowing if an image starts | ||||
| out as reading all zeroes, such as making blockdev-mirror faster by | ||||
| skipping the copying of source zeroes to the destination.  The | ||||
| existing bdrv_co_is_zero_fast() is a good building block for answering | ||||
| this question, but it tends to give an answer of 0 for a file we just | ||||
| created via QMP 'blockdev-create' or similar (such as 'qemu-img create | ||||
| -f raw').  Why?  Because file-posix.c insists on allocating a tiny
 | ||||
| header to any file rather than leaving it 100% sparse, due to some | ||||
| filesystems that are unable to answer alignment probes on a hole.  But | ||||
| teaching file-posix.c to read the tiny header doesn't scale - the | ||||
| problem of a small header is also visible when libvirt sets up an NBD | ||||
| client to a just-created file on a migration destination host. | ||||
| 
 | ||||
| So, we need a wrapper function that handles a bit more complexity in a | ||||
| common manner for all block devices - when the BDS is mostly a hole, | ||||
| but has a small non-hole header, it is still worth the time to read | ||||
| that header and check if it reads as all zeroes before giving up and | ||||
| returning a pessimistic answer. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-19-eblake@redhat.com> | ||||
| (cherry picked from commit 52726096707c5c8b90597c445de897fa64d56e73) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/io.c               | 62 ++++++++++++++++++++++++++++++++++++++++ | ||||
|  include/block/block-io.h |  2 ++ | ||||
|  2 files changed, 64 insertions(+) | ||||
| 
 | ||||
| diff --git a/block/io.c b/block/io.c
 | ||||
| index 64f4b1d22a..b6fc07e1dc 100644
 | ||||
| --- a/block/io.c
 | ||||
| +++ b/block/io.c
 | ||||
| @@ -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); | ||||
|   | ||||
| @@ -2778,6 +2782,64 @@ int coroutine_fn bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset,
 | ||||
|      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);
 | ||||
| +    }
 | ||||
| +
 | ||||
| +    /*
 | ||||
| +     * 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, | ||||
|                                        int64_t bytes, int64_t *pnum) | ||||
|  { | ||||
| diff --git a/include/block/block-io.h b/include/block/block-io.h
 | ||||
| index b49e0537dd..b99cc98d26 100644
 | ||||
| --- a/include/block/block-io.h
 | ||||
| +++ b/include/block/block-io.h
 | ||||
| @@ -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, | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										689
									
								
								kvm-block-Expand-block-status-mode-from-bool-to-flags.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										689
									
								
								kvm-block-Expand-block-status-mode-from-bool-to-flags.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,689 @@ | ||||
| From cb945ccd11d37c959f590ae5661ffe5b73f372a7 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:18 -0500 | ||||
| Subject: [PATCH 01/14] block: Expand block status mode from bool to flags | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [1/14] 12507ca1dbe44640e4a8baded4d5afd5fb4ed615 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| This patch is purely mechanical, changing bool want_zero into an | ||||
| unsigned int for bitwise-or of flags.  As of this patch, all | ||||
| implementations are unchanged (the old want_zero==true is now | ||||
| mode==BDRV_WANT_PRECISE which is a superset of BDRV_WANT_ZERO); but | ||||
| the callers in io.c that used to pass want_zero==false are now | ||||
| prepared for future driver changes that can now distinguish bewteen | ||||
| BDRV_WANT_ZERO vs. BDRV_WANT_ALLOCATED.  The next patch will actually | ||||
| change the file-posix driver along those lines, now that we have | ||||
| more-specific hints. | ||||
| 
 | ||||
| As for the background why this patch is useful: right now, the | ||||
| file-posix driver recognizes that if allocation is being queried, the | ||||
| entire image can be reported as allocated (there is no backing file to | ||||
| refer to) - but this throws away information on whether the entire | ||||
| image reads as zero (trivially true if lseek(SEEK_HOLE) at offset 0 | ||||
| returns -ENXIO, a bit more complicated to prove if the raw file was | ||||
| created with 'qemu-img create' since we intentionally allocate a small | ||||
| chunk of all-zero data to help with alignment probing).  Later patches | ||||
| will add a generic algorithm for seeing if an entire file reads as | ||||
| zeroes. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-16-eblake@redhat.com> | ||||
| (cherry picked from commit c33159dec79069514f78faecfe268439226b0f5b) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/blkdebug.c                 |  6 ++-- | ||||
|  block/copy-before-write.c        |  4 +-- | ||||
|  block/coroutines.h               |  4 +-- | ||||
|  block/file-posix.c               |  4 +-- | ||||
|  block/gluster.c                  |  4 +-- | ||||
|  block/io.c                       | 51 ++++++++++++++++---------------- | ||||
|  block/iscsi.c                    |  6 ++-- | ||||
|  block/nbd.c                      |  4 +-- | ||||
|  block/null.c                     |  6 ++-- | ||||
|  block/parallels.c                |  6 ++-- | ||||
|  block/qcow.c                     |  2 +- | ||||
|  block/qcow2.c                    |  6 ++-- | ||||
|  block/qed.c                      |  6 ++-- | ||||
|  block/quorum.c                   |  4 +-- | ||||
|  block/raw-format.c               |  4 +-- | ||||
|  block/rbd.c                      |  6 ++-- | ||||
|  block/snapshot-access.c          |  4 +-- | ||||
|  block/vdi.c                      |  4 +-- | ||||
|  block/vmdk.c                     |  2 +- | ||||
|  block/vpc.c                      |  2 +- | ||||
|  block/vvfat.c                    |  6 ++-- | ||||
|  include/block/block-common.h     | 11 +++++++ | ||||
|  include/block/block_int-common.h | 27 +++++++++-------- | ||||
|  include/block/block_int-io.h     |  4 +-- | ||||
|  tests/unit/test-block-iothread.c |  2 +- | ||||
|  25 files changed, 99 insertions(+), 86 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/blkdebug.c b/block/blkdebug.c
 | ||||
| index 1c1967f8e0..c54aee0c84 100644
 | ||||
| --- a/block/blkdebug.c
 | ||||
| +++ b/block/blkdebug.c
 | ||||
| @@ -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; | ||||
|   | ||||
| diff --git a/block/copy-before-write.c b/block/copy-before-write.c
 | ||||
| index fd470f5f92..2badb3a885 100644
 | ||||
| --- a/block/copy-before-write.c
 | ||||
| +++ b/block/copy-before-write.c
 | ||||
| @@ -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) | ||||
|  { | ||||
| diff --git a/block/coroutines.h b/block/coroutines.h
 | ||||
| index 79e5efbf75..892646bb7a 100644
 | ||||
| --- a/block/coroutines.h
 | ||||
| +++ b/block/coroutines.h
 | ||||
| @@ -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, | ||||
| diff --git a/block/file-posix.c b/block/file-posix.c
 | ||||
| index 0d85123d0f..0c6569742f 100644
 | ||||
| --- a/block/file-posix.c
 | ||||
| +++ b/block/file-posix.c
 | ||||
| @@ -3266,7 +3266,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, | ||||
| @@ -3282,7 +3282,7 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
 | ||||
|          return ret; | ||||
|      } | ||||
|   | ||||
| -    if (!want_zero) {
 | ||||
| +    if (mode != BDRV_WANT_PRECISE) {
 | ||||
|          *pnum = bytes; | ||||
|          *map = offset; | ||||
|          *file = bs; | ||||
| diff --git a/block/gluster.c b/block/gluster.c
 | ||||
| index c6d25ae733..8197b0ecef 100644
 | ||||
| --- a/block/gluster.c
 | ||||
| +++ b/block/gluster.c
 | ||||
| @@ -1465,7 +1465,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, | ||||
| @@ -1482,7 +1482,7 @@ static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
 | ||||
|          return ret; | ||||
|      } | ||||
|   | ||||
| -    if (!want_zero) {
 | ||||
| +    if (mode != BDRV_WANT_PRECISE) {
 | ||||
|          *pnum = bytes; | ||||
|          *map = offset; | ||||
|          *file = bs; | ||||
| diff --git a/block/io.c b/block/io.c
 | ||||
| index ccec11386b..e328402adc 100644
 | ||||
| --- a/block/io.c
 | ||||
| +++ b/block/io.c
 | ||||
| @@ -2364,10 +2364,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 +2385,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 +2474,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 +2486,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 +2546,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 +2558,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 +2567,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 +2625,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 +2652,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 +2669,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 +2732,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); | ||||
|  } | ||||
|   | ||||
| @@ -2765,8 +2764,9 @@ int coroutine_fn bdrv_co_is_zero_fast(BlockDriverState *bs, int64_t offset,
 | ||||
|          return 1; | ||||
|      } | ||||
|   | ||||
| -    ret = bdrv_co_common_block_status_above(bs, NULL, false, false, offset,
 | ||||
| -                                            bytes, &pnum, NULL, NULL, NULL);
 | ||||
| +    ret = bdrv_co_common_block_status_above(bs, NULL, false, BDRV_WANT_ZERO,
 | ||||
| +                                            offset, bytes, &pnum, NULL, NULL,
 | ||||
| +                                            NULL);
 | ||||
|   | ||||
|      if (ret < 0) { | ||||
|          return ret; | ||||
| @@ -2782,9 +2782,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 +2817,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) { | ||||
| @@ -3714,8 +3715,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) | ||||
|  { | ||||
| @@ -3733,7 +3734,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); | ||||
|   | ||||
| diff --git a/block/iscsi.c b/block/iscsi.c
 | ||||
| index 2f0f4dac09..15b96ee880 100644
 | ||||
| --- a/block/iscsi.c
 | ||||
| +++ b/block/iscsi.c
 | ||||
| @@ -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; | ||||
| diff --git a/block/nbd.c b/block/nbd.c
 | ||||
| index 887841bc81..d5a2b21c6d 100644
 | ||||
| --- a/block/nbd.c
 | ||||
| +++ b/block/nbd.c
 | ||||
| @@ -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 }; | ||||
| diff --git a/block/null.c b/block/null.c
 | ||||
| index dc0b1fdbd9..4e448d593d 100644
 | ||||
| --- a/block/null.c
 | ||||
| +++ b/block/null.c
 | ||||
| @@ -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; | ||||
| diff --git a/block/parallels.c b/block/parallels.c
 | ||||
| index 347ca127f3..3a375e2a8a 100644
 | ||||
| --- a/block/parallels.c
 | ||||
| +++ b/block/parallels.c
 | ||||
| @@ -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; | ||||
| diff --git a/block/qcow.c b/block/qcow.c
 | ||||
| index da8ad4d243..8a3e7591a9 100644
 | ||||
| --- a/block/qcow.c
 | ||||
| +++ b/block/qcow.c
 | ||||
| @@ -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) | ||||
|  { | ||||
| diff --git a/block/qcow2.c b/block/qcow2.c
 | ||||
| index b6ade4755d..9fc96ba99a 100644
 | ||||
| --- a/block/qcow2.c
 | ||||
| +++ b/block/qcow2.c
 | ||||
| @@ -2147,9 +2147,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; | ||||
| diff --git a/block/qed.c b/block/qed.c
 | ||||
| index ac24449ffb..4a36fb3929 100644
 | ||||
| --- a/block/qed.c
 | ||||
| +++ b/block/qed.c
 | ||||
| @@ -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); | ||||
| diff --git a/block/quorum.c b/block/quorum.c
 | ||||
| index 30747a6df9..ed8ce801ee 100644
 | ||||
| --- a/block/quorum.c
 | ||||
| +++ b/block/quorum.c
 | ||||
| @@ -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, | ||||
| diff --git a/block/raw-format.c b/block/raw-format.c
 | ||||
| index e08526e2ec..df16ac1ea2 100644
 | ||||
| --- a/block/raw-format.c
 | ||||
| +++ b/block/raw-format.c
 | ||||
| @@ -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; | ||||
| diff --git a/block/rbd.c b/block/rbd.c
 | ||||
| index af984fb7db..4f3d42a8e7 100644
 | ||||
| --- a/block/rbd.c
 | ||||
| +++ b/block/rbd.c
 | ||||
| @@ -1504,9 +1504,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; | ||||
| diff --git a/block/snapshot-access.c b/block/snapshot-access.c
 | ||||
| index 71ac83c01f..17ed2402db 100644
 | ||||
| --- a/block/snapshot-access.c
 | ||||
| +++ b/block/snapshot-access.c
 | ||||
| @@ -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); | ||||
|  } | ||||
|   | ||||
| diff --git a/block/vdi.c b/block/vdi.c
 | ||||
| index a2da6ecab0..3ddc62a569 100644
 | ||||
| --- a/block/vdi.c
 | ||||
| +++ b/block/vdi.c
 | ||||
| @@ -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; | ||||
| diff --git a/block/vmdk.c b/block/vmdk.c
 | ||||
| index 2adec49912..9c7ab037e1 100644
 | ||||
| --- a/block/vmdk.c
 | ||||
| +++ b/block/vmdk.c
 | ||||
| @@ -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) | ||||
|  { | ||||
| diff --git a/block/vpc.c b/block/vpc.c
 | ||||
| index 0309e319f6..801ff5793f 100644
 | ||||
| --- a/block/vpc.c
 | ||||
| +++ b/block/vpc.c
 | ||||
| @@ -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) | ||||
| diff --git a/block/vvfat.c b/block/vvfat.c
 | ||||
| index 91d69b3cc8..814796d918 100644
 | ||||
| --- a/block/vvfat.c
 | ||||
| +++ b/block/vvfat.c
 | ||||
| @@ -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; | ||||
| diff --git a/include/block/block-common.h b/include/block/block-common.h
 | ||||
| index 0b831ef87b..c8c626daea 100644
 | ||||
| --- a/include/block/block-common.h
 | ||||
| +++ b/include/block/block-common.h
 | ||||
| @@ -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 { | ||||
| diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h
 | ||||
| index ebb4e56a50..a9c0daa2a4 100644
 | ||||
| --- a/include/block/block_int-common.h
 | ||||
| +++ b/include/block/block_int-common.h
 | ||||
| @@ -608,15 +608,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. | ||||
| @@ -628,8 +629,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); | ||||
|   | ||||
|      /* | ||||
| @@ -653,8 +654,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); | ||||
| diff --git a/include/block/block_int-io.h b/include/block/block_int-io.h
 | ||||
| index 4a7cf2b4fd..4f94eb3c5a 100644
 | ||||
| --- a/include/block/block_int-io.h
 | ||||
| +++ b/include/block/block_int-io.h
 | ||||
| @@ -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); | ||||
|   | ||||
| diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c
 | ||||
| index 2b358eaaa8..e26b3be593 100644
 | ||||
| --- a/tests/unit/test-block-iothread.c
 | ||||
| +++ b/tests/unit/test-block-iothread.c
 | ||||
| @@ -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) | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
| @ -0,0 +1,90 @@ | ||||
| From e101b9872f9b3f6c5e128f29d7c3bb91faca362b Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:20 -0500 | ||||
| Subject: [PATCH 03/14] block: Let bdrv_co_is_zero_fast consolidate adjacent | ||||
|  extents | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [3/14] 4520f7ef5bcc5803413541e6f48bf750af3d31e0 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Some BDS drivers have a cap on how much block status they can supply | ||||
| in one query (for example, NBD talking to an older server cannot | ||||
| inspect more than 4G per query; and qcow2 tends to cap its answers | ||||
| rather than cross a cluster boundary of an L1 table).  Although the | ||||
| existing callers of bdrv_co_is_zero_fast are not passing in that large | ||||
| of a 'bytes' parameter, an upcoming caller wants to query the entire | ||||
| image at once, and will thus benefit from being able to treat adjacent | ||||
| zero regions in a coalesced manner, rather than claiming the region is | ||||
| non-zero merely because pnum was truncated and didn't match the | ||||
| incoming bytes. | ||||
| 
 | ||||
| While refactoring this into a loop, note that there is no need to | ||||
| assign pnum prior to calling bdrv_co_common_block_status_above() (it | ||||
| is guaranteed to be assigned deeper in the callstack). | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-18-eblake@redhat.com> | ||||
| (cherry picked from commit 31bf15d97dd1d205a3b264675f9a1b3bd1939068) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/io.c | 27 +++++++++++++++------------ | ||||
|  1 file changed, 15 insertions(+), 12 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/io.c b/block/io.c
 | ||||
| index e328402adc..64f4b1d22a 100644
 | ||||
| --- a/block/io.c
 | ||||
| +++ b/block/io.c
 | ||||
| @@ -2751,28 +2751,31 @@ 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;
 | ||||
| -    }
 | ||||
| -
 | ||||
| -    ret = bdrv_co_common_block_status_above(bs, NULL, false, BDRV_WANT_ZERO,
 | ||||
| -                                            offset, bytes, &pnum, NULL, NULL,
 | ||||
| -                                            NULL);
 | ||||
| +    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 < 0) {
 | ||||
| +            return ret;
 | ||||
| +        }
 | ||||
| +        if (!(ret & BDRV_BLOCK_ZERO)) {
 | ||||
| +            return 0;
 | ||||
| +        }
 | ||||
| +        offset += pnum;
 | ||||
| +        bytes -= pnum;
 | ||||
|      } | ||||
|   | ||||
| -    return (pnum == bytes) && (ret & BDRV_BLOCK_ZERO);
 | ||||
| +    return 1;
 | ||||
|  } | ||||
|   | ||||
|  int coroutine_fn bdrv_co_is_allocated(BlockDriverState *bs, int64_t offset, | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
| @ -0,0 +1,64 @@ | ||||
| From f8d89f67817fa362a3b8ed0721775e353dac8f18 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:19 -0500 | ||||
| Subject: [PATCH 02/14] file-posix, gluster: Handle zero block status hint | ||||
|  better | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [2/14] c40cd3f8cda2ea1646d90fd174b5f0dbd3e1a50b (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Although the previous patch to change 'bool want_zero' into a bitmask | ||||
| made no semantic change, it is now time to differentiate.  When the | ||||
| caller specifically wants to know what parts of the file read as zero, | ||||
| we need to use lseek and actually reporting holes, rather than | ||||
| short-circuiting and advertising full allocation. | ||||
| 
 | ||||
| This change will be utilized in later patches to let mirroring | ||||
| optimize for the case when the destination already reads as zeroes. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-17-eblake@redhat.com> | ||||
| (cherry picked from commit a6a0a7fb0e327d17594c971b4a39de14e025b415) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/file-posix.c | 3 ++- | ||||
|  block/gluster.c    | 2 +- | ||||
|  2 files changed, 3 insertions(+), 2 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/file-posix.c b/block/file-posix.c
 | ||||
| index 0c6569742f..dea7b09b6c 100644
 | ||||
| --- a/block/file-posix.c
 | ||||
| +++ b/block/file-posix.c
 | ||||
| @@ -3282,7 +3282,8 @@ static int coroutine_fn raw_co_block_status(BlockDriverState *bs,
 | ||||
|          return ret; | ||||
|      } | ||||
|   | ||||
| -    if (mode != BDRV_WANT_PRECISE) {
 | ||||
| +    if (!(mode & BDRV_WANT_ZERO)) {
 | ||||
| +        /* There is no backing file - all bytes are allocated in this file.  */
 | ||||
|          *pnum = bytes; | ||||
|          *map = offset; | ||||
|          *file = bs; | ||||
| diff --git a/block/gluster.c b/block/gluster.c
 | ||||
| index 8197b0ecef..e702666cbc 100644
 | ||||
| --- a/block/gluster.c
 | ||||
| +++ b/block/gluster.c
 | ||||
| @@ -1482,7 +1482,7 @@ static int coroutine_fn qemu_gluster_co_block_status(BlockDriverState *bs,
 | ||||
|          return ret; | ||||
|      } | ||||
|   | ||||
| -    if (mode != BDRV_WANT_PRECISE) {
 | ||||
| +    if (!(mode & BDRV_WANT_ZERO)) {
 | ||||
|          *pnum = bytes; | ||||
|          *map = offset; | ||||
|          *file = bs; | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										42
									
								
								kvm-iotests-Improve-iotest-194-to-mirror-data.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								kvm-iotests-Improve-iotest-194-to-mirror-data.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| From 11b46a271d73631177f59ff581a408f967c30fb9 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:22 -0500 | ||||
| Subject: [PATCH 05/14] iotests: Improve iotest 194 to mirror data | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [5/14] 9f1fd3c7d4332ac310af4eb37e8f2122f6324294 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Mirroring a completely sparse image to a sparse destination should be | ||||
| practically instantaneous.  It isn't yet, but the test will be more | ||||
| realistic if it has some non-zero to mirror as well as the holes. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-20-eblake@redhat.com> | ||||
| (cherry picked from commit eb89627899bb84148d272394e885725eff456ae9) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  tests/qemu-iotests/194 | 1 + | ||||
|  1 file changed, 1 insertion(+) | ||||
| 
 | ||||
| diff --git a/tests/qemu-iotests/194 b/tests/qemu-iotests/194
 | ||||
| index c0ce82dd25..d0b9c084f5 100755
 | ||||
| --- a/tests/qemu-iotests/194
 | ||||
| +++ b/tests/qemu-iotests/194
 | ||||
| @@ -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...') | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										68
									
								
								kvm-iotests-common.rc-add-disk_usage-function.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								kvm-iotests-common.rc-add-disk_usage-function.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| From d8ed5039981b1eb81d229d8ee672d5ee28862e92 Mon Sep 17 00:00:00 2001 | ||||
| From: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com> | ||||
| Date: Fri, 9 May 2025 15:40:29 -0500 | ||||
| Subject: [PATCH 12/14] iotests/common.rc: add disk_usage function | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [12/14] 0a007f9d09f01b50cf4edeb8ac8217356b2cb5d2 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Move the definition from iotests/250 to common.rc.  This is used to | ||||
| detect real disk usage of sparse files.  In particular, we want to use | ||||
| it for checking subclusters-based discards. | ||||
| 
 | ||||
| Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com> | ||||
| Reviewed-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com> | ||||
| Reviewed-by: Alberto Garcia <berto@igalia.com> | ||||
| Message-ID: <20240913163942.423050-6-andrey.drobyshev@virtuozzo.com> | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-27-eblake@redhat.com> | ||||
| (cherry picked from commit be9bac072ede6e6aa27079f59efcf17b56bd7b26) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  tests/qemu-iotests/250       | 5 ----- | ||||
|  tests/qemu-iotests/common.rc | 6 ++++++ | ||||
|  2 files changed, 6 insertions(+), 5 deletions(-) | ||||
| 
 | ||||
| diff --git a/tests/qemu-iotests/250 b/tests/qemu-iotests/250
 | ||||
| index af48f83aba..c0a0dbc0ff 100755
 | ||||
| --- a/tests/qemu-iotests/250
 | ||||
| +++ b/tests/qemu-iotests/250
 | ||||
| @@ -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 | ||||
| diff --git a/tests/qemu-iotests/common.rc b/tests/qemu-iotests/common.rc
 | ||||
| index 95c12577dd..237f746af8 100644
 | ||||
| --- a/tests/qemu-iotests/common.rc
 | ||||
| +++ b/tests/qemu-iotests/common.rc
 | ||||
| @@ -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 | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										295
									
								
								kvm-mirror-Allow-QMP-override-to-declare-target-already-.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								kvm-mirror-Allow-QMP-override-to-declare-target-already-.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,295 @@ | ||||
| From bc4571743fc3bbb829101fbf294615e3b8fb3577 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:25 -0500 | ||||
| Subject: [PATCH 08/14] mirror: Allow QMP override to declare target already | ||||
|  zero | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [8/14] 4ad06d67db8c43df4e6e0b8f929b1d8c19e4b338 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| QEMU has an optimization for a just-created drive-mirror destination | ||||
| that is not possible for blockdev-mirror (which can't create the | ||||
| destination) - any time we know the destination starts life as all | ||||
| zeroes, we can skip a pre-zeroing pass on the destination.  Recent | ||||
| patches have added an improved heuristic for detecting if a file | ||||
| contains all zeroes, and we plan to use that heuristic in upcoming | ||||
| patches.  But since a heuristic cannot quickly detect all scenarios, | ||||
| and there may be cases where the caller is aware of information that | ||||
| QEMU cannot learn quickly, it makes sense to have a way to tell QEMU | ||||
| to assume facts about the destination that can make the mirror | ||||
| operation faster.  Given our existing example of "qemu-img convert | ||||
| --target-is-zero", it is time to expose this override in QMP for
 | ||||
| blockdev-mirror as well. | ||||
| 
 | ||||
| This patch results in some slight redundancy between the older | ||||
| s->zero_target (set any time mode==FULL and the destination image was | ||||
| not just created - ie. clear if drive-mirror is asking to skip the | ||||
| pre-zero pass) and the newly-introduced s->target_is_zero (in addition | ||||
| to the QMP override, it is set when drive-mirror creates the | ||||
| destination image); this will be cleaned up in the next patch. | ||||
| 
 | ||||
| There is also a subtlety that we must consider.  When drive-mirror is | ||||
| passing target_is_zero on behalf of a just-created image, we know the | ||||
| image is sparse (skipping the pre-zeroing keeps it that way), so it | ||||
| doesn't matter whether the destination also has "discard":"unmap" and | ||||
| "detect-zeroes":"unmap".  But now that we are letting the user set the | ||||
| knob for target-is-zero, if the user passes a pre-existing file that | ||||
| is fully allocated, it is fine to leave the file fully allocated under | ||||
| "detect-zeroes":"on", but if the file is open with | ||||
| "detect-zeroes":"unmap", we should really be trying harder to punch | ||||
| holes in the destination for every region of zeroes copied from the | ||||
| source.  The easiest way to do this is to still run the pre-zeroing | ||||
| pass (turning the entire destination file sparse before populating | ||||
| just the allocated portions of the source), even though that currently | ||||
| results in double I/O to the portions of the file that are allocated. | ||||
| A later patch will add further optimizations to reduce redundant | ||||
| zeroing I/O during the mirror operation. | ||||
| 
 | ||||
| Since "target-is-zero":true is designed for optimizations, it is okay | ||||
| to silently ignore the parameter rather than erroring if the user ever | ||||
| sets the parameter in a scenario where the mirror job can't exploit it | ||||
| (for example, when doing "sync":"top" instead of "sync":"full", we | ||||
| can't pre-zero, so setting the parameter won't make a speed | ||||
| difference).
 | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Acked-by: Markus Armbruster <armbru@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-23-eblake@redhat.com> | ||||
| Reviewed-by: Sunny Zhu <sunnyzhyy@qq.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit d17a34bfb94bda3a89d7320ae67255ded1d8c939) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c                         | 27 ++++++++++++++++++++++---- | ||||
|  blockdev.c                             | 18 ++++++++++------- | ||||
|  include/block/block_int-global-state.h |  3 ++- | ||||
|  qapi/block-core.json                   |  8 +++++++- | ||||
|  tests/unit/test-block-iothread.c       |  2 +- | ||||
|  5 files changed, 44 insertions(+), 14 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index 2599b75d09..4dcb50c81a 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -55,6 +55,8 @@ typedef struct MirrorBlockJob {
 | ||||
|      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()). | ||||
| @@ -844,12 +846,26 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|      BlockDriverState *target_bs = blk_bs(s->target); | ||||
|      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);
 | ||||
|   | ||||
|      bdrv_graph_co_rdlock(); | ||||
|      bs = s->mirror_top_bs->backing->bs; | ||||
|      bdrv_graph_co_rdunlock(); | ||||
|   | ||||
| -    if (s->zero_target) {
 | ||||
| +    if (s->zero_target && (!s->target_is_zero || 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 (s->target_is_zero) 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 punch holes, then our only recourse is to write the
 | ||||
| +         * entire image.
 | ||||
| +         */
 | ||||
|          if (!bdrv_can_write_zeroes_with_unmap(target_bs)) { | ||||
|              bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length); | ||||
|              return 0; | ||||
| @@ -1714,7 +1730,7 @@ static BlockJob *mirror_start_job(
 | ||||
|                               uint32_t granularity, int64_t buf_size, | ||||
|                               MirrorSyncMode sync_mode, | ||||
|                               BlockMirrorBackingMode backing_mode, | ||||
| -                             bool zero_target,
 | ||||
| +                             bool zero_target, bool target_is_zero,
 | ||||
|                               BlockdevOnError on_source_error, | ||||
|                               BlockdevOnError on_target_error, | ||||
|                               bool unmap, | ||||
| @@ -1883,6 +1899,7 @@ static BlockJob *mirror_start_job(
 | ||||
|      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); | ||||
| @@ -2011,7 +2028,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 zero_target, bool target_is_zero,
 | ||||
|                    BlockdevOnError on_source_error, | ||||
|                    BlockdevOnError on_target_error, | ||||
|                    bool unmap, const char *filter_node_name, | ||||
| @@ -2034,7 +2051,8 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
 | ||||
|   | ||||
|      mirror_start_job(job_id, bs, creation_flags, target, replaces, | ||||
|                       speed, granularity, buf_size, mode, backing_mode, | ||||
| -                     zero_target, on_source_error, on_target_error, unmap,
 | ||||
| +                     zero_target,
 | ||||
| +                     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); | ||||
|  } | ||||
| @@ -2062,6 +2080,7 @@ 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_SYNC_MODE_TOP, MIRROR_LEAVE_BACKING_CHAIN, false, | ||||
| +                     false,
 | ||||
|                       on_error, on_error, true, cb, opaque, | ||||
|                       &commit_active_job_driver, base, auto_complete, | ||||
|                       filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND, | ||||
| diff --git a/blockdev.c b/blockdev.c
 | ||||
| index 1d1f27cfff..2e2fed539e 100644
 | ||||
| --- a/blockdev.c
 | ||||
| +++ b/blockdev.c
 | ||||
| @@ -2798,7 +2798,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 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, | ||||
| @@ -2909,11 +2909,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,
 | ||||
| +    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);
 | ||||
| +                 target_is_zero, on_source_error, on_target_error, unmap,
 | ||||
| +                 filter_node_name, copy_mode, errp);
 | ||||
|  } | ||||
|   | ||||
|  void qmp_drive_mirror(DriveMirror *arg, Error **errp) | ||||
| @@ -2928,6 +2927,7 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
 | ||||
|      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); | ||||
| @@ -3044,6 +3044,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
 | ||||
|      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(); | ||||
|   | ||||
|   | ||||
| @@ -3055,7 +3057,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, zero_target, target_is_zero,
 | ||||
|                             arg->has_speed, arg->speed, | ||||
|                             arg->has_granularity, arg->granularity, | ||||
|                             arg->has_buf_size, arg->buf_size, | ||||
| @@ -3085,6 +3087,7 @@ 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; | ||||
| @@ -3115,7 +3118,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,
 | ||||
| +                           zero_target, 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, | ||||
| diff --git a/include/block/block_int-global-state.h b/include/block/block_int-global-state.h
 | ||||
| index eb2d92a226..8cf0003ce7 100644
 | ||||
| --- a/include/block/block_int-global-state.h
 | ||||
| +++ b/include/block/block_int-global-state.h
 | ||||
| @@ -140,6 +140,7 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
 | ||||
|   * @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 +160,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 zero_target, bool target_is_zero,
 | ||||
|                    BlockdevOnError on_source_error, | ||||
|                    BlockdevOnError on_target_error, | ||||
|                    bool unmap, const char *filter_node_name, | ||||
| diff --git a/qapi/block-core.json b/qapi/block-core.json
 | ||||
| index b1937780e1..7f70ec6d3c 100644
 | ||||
| --- a/qapi/block-core.json
 | ||||
| +++ b/qapi/block-core.json
 | ||||
| @@ -2538,6 +2538,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:: | ||||
| @@ -2557,7 +2562,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 } | ||||
|   | ||||
|  ## | ||||
| diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c
 | ||||
| index e26b3be593..54aed8252c 100644
 | ||||
| --- a/tests/unit/test-block-iothread.c
 | ||||
| +++ b/tests/unit/test-block-iothread.c
 | ||||
| @@ -755,7 +755,7 @@ static void test_propagate_mirror(void)
 | ||||
|   | ||||
|      /* Start a mirror job */ | ||||
|      mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0, | ||||
| -                 MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false,
 | ||||
| +                 MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false, false,
 | ||||
|                   BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, | ||||
|                   false, "filter_node", MIRROR_COPY_MODE_BACKGROUND, | ||||
|                   &error_abort); | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										241
									
								
								kvm-mirror-Drop-redundant-zero_target-parameter.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								kvm-mirror-Drop-redundant-zero_target-parameter.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,241 @@ | ||||
| From db1a158312c2b94af1c1a50e0f13ace6ae58f0b6 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:26 -0500 | ||||
| Subject: [PATCH 09/14] mirror: Drop redundant zero_target parameter | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [9/14] b4cbd267c81b4758f59e0d51b947fd450caf6ef5 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| The two callers to a mirror job (drive-mirror and blockdev-mirror) set | ||||
| zero_target precisely when sync mode == FULL, with the one exception | ||||
| that drive-mirror skips zeroing the target if it was newly created and | ||||
| reads as zero.  But given the previous patch, that exception is | ||||
| equally captured by target_is_zero. | ||||
| 
 | ||||
| Meanwhile, there is another slight wrinkle, fortunately caught by | ||||
| iotest 185: if the caller uses "sync":"top" but the source has no | ||||
| backing file, the code in blockdev.c was changing sync to be FULL, but | ||||
| only after it had set zero_target=false.  In mirror.c, prior to recent | ||||
| patches, this didn't matter: the only places that inspected sync were | ||||
| setting is_none_mode (both TOP and FULL had set that to false), and | ||||
| mirror_start() setting base = mode == MIRROR_SYNC_MODE_TOP ? | ||||
| bdrv_backing_chain_next(bs) : NULL.  But now that we are passing sync | ||||
| around, the slammed sync mode would result in a new pre-zeroing pass | ||||
| even when the user had passed "sync":"top" in an effort to skip | ||||
| pre-zeroing.  Fortunately, the assignment of base when bs has no | ||||
| backing chain still works out to NULL if we don't slam things.  So | ||||
| with the forced change of sync ripped out of blockdev.c, the sync mode | ||||
| is passed through the full callstack unmolested, and we can now | ||||
| reliably reconstruct the same settings as what used to be passed in by | ||||
| zero_target=false, without the redundant parameter. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-24-eblake@redhat.com> | ||||
| Reviewed-by: Sunny Zhu <sunnyzhyy@qq.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| [eblake: Fix regression in iotest 185] | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| (cherry picked from commit 253b43a29077de9266351e120c600a73b82e9c49) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c                         | 13 +++++-------- | ||||
|  blockdev.c                             | 19 ++++--------------- | ||||
|  include/block/block_int-global-state.h |  3 +-- | ||||
|  tests/unit/test-block-iothread.c       |  2 +- | ||||
|  4 files changed, 11 insertions(+), 26 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index 4dcb50c81a..d04db85883 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -53,8 +53,6 @@ typedef struct MirrorBlockJob {
 | ||||
|      Error *replace_blocker; | ||||
|      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; | ||||
|      /* | ||||
| @@ -854,7 +852,9 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|      bs = s->mirror_top_bs->backing->bs; | ||||
|      bdrv_graph_co_rdunlock(); | ||||
|   | ||||
| -    if (s->zero_target && (!s->target_is_zero || punch_holes)) {
 | ||||
| +    if (s->sync_mode == MIRROR_SYNC_MODE_TOP) {
 | ||||
| +        /* In TOP mode, there is no benefit to a pre-zeroing pass.  */
 | ||||
| +    } else if (!s->target_is_zero || punch_holes) {
 | ||||
|          /* | ||||
|           * Here, we are in FULL mode; our goal is to avoid writing | ||||
|           * zeroes if the destination already reads as zero, except | ||||
| @@ -1730,7 +1730,7 @@ static BlockJob *mirror_start_job(
 | ||||
|                               uint32_t granularity, int64_t buf_size, | ||||
|                               MirrorSyncMode sync_mode, | ||||
|                               BlockMirrorBackingMode backing_mode, | ||||
| -                             bool zero_target, bool target_is_zero,
 | ||||
| +                             bool target_is_zero,
 | ||||
|                               BlockdevOnError on_source_error, | ||||
|                               BlockdevOnError on_target_error, | ||||
|                               bool unmap, | ||||
| @@ -1898,7 +1898,6 @@ static BlockJob *mirror_start_job(
 | ||||
|      s->on_target_error = on_target_error; | ||||
|      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; | ||||
| @@ -2028,7 +2027,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,
 | ||||
| +                  bool target_is_zero,
 | ||||
|                    BlockdevOnError on_source_error, | ||||
|                    BlockdevOnError on_target_error, | ||||
|                    bool unmap, const char *filter_node_name, | ||||
| @@ -2051,7 +2050,6 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
 | ||||
|   | ||||
|      mirror_start_job(job_id, bs, creation_flags, target, replaces, | ||||
|                       speed, granularity, buf_size, mode, backing_mode, | ||||
| -                     zero_target,
 | ||||
|                       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); | ||||
| @@ -2080,7 +2078,6 @@ 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_SYNC_MODE_TOP, MIRROR_LEAVE_BACKING_CHAIN, false, | ||||
| -                     false,
 | ||||
|                       on_error, on_error, true, cb, opaque, | ||||
|                       &commit_active_job_driver, base, auto_complete, | ||||
|                       filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND, | ||||
| diff --git a/blockdev.c b/blockdev.c
 | ||||
| index 2e2fed539e..0fa8813efe 100644
 | ||||
| --- a/blockdev.c
 | ||||
| +++ b/blockdev.c
 | ||||
| @@ -2798,7 +2798,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 target_is_zero,
 | ||||
|                                     bool has_speed, int64_t speed, | ||||
|                                     bool has_granularity, uint32_t granularity, | ||||
|                                     bool has_buf_size, int64_t buf_size, | ||||
| @@ -2865,10 +2865,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); | ||||
| @@ -2910,7 +2906,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
 | ||||
|       * 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,
 | ||||
| +                 speed, granularity, buf_size, sync, backing_mode,
 | ||||
|                   target_is_zero, on_source_error, on_target_error, unmap, | ||||
|                   filter_node_name, copy_mode, errp); | ||||
|  } | ||||
| @@ -2926,7 +2922,6 @@ 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; | ||||
|   | ||||
| @@ -3041,9 +3036,6 @@ 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(); | ||||
| @@ -3057,7 +3049,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, target_is_zero,
 | ||||
| +                           backing_mode, target_is_zero,
 | ||||
|                             arg->has_speed, arg->speed, | ||||
|                             arg->has_granularity, arg->granularity, | ||||
|                             arg->has_buf_size, arg->buf_size, | ||||
| @@ -3094,7 +3086,6 @@ void qmp_blockdev_mirror(const char *job_id,
 | ||||
|      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); | ||||
| @@ -3107,8 +3098,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); | ||||
| @@ -3118,7 +3107,7 @@ void qmp_blockdev_mirror(const char *job_id,
 | ||||
|   | ||||
|      blockdev_mirror_common(job_id, bs, target_bs, | ||||
|                             replaces, sync, backing_mode, | ||||
| -                           zero_target, has_target_is_zero && target_is_zero,
 | ||||
| +                           has_target_is_zero && target_is_zero,
 | ||||
|                             has_speed, speed, | ||||
|                             has_granularity, granularity, | ||||
|                             has_buf_size, buf_size, | ||||
| diff --git a/include/block/block_int-global-state.h b/include/block/block_int-global-state.h
 | ||||
| index 8cf0003ce7..d21bd7fd2f 100644
 | ||||
| --- a/include/block/block_int-global-state.h
 | ||||
| +++ b/include/block/block_int-global-state.h
 | ||||
| @@ -139,7 +139,6 @@ 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. | ||||
| @@ -160,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,
 | ||||
| +                  bool target_is_zero,
 | ||||
|                    BlockdevOnError on_source_error, | ||||
|                    BlockdevOnError on_target_error, | ||||
|                    bool unmap, const char *filter_node_name, | ||||
| diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c
 | ||||
| index 54aed8252c..e26b3be593 100644
 | ||||
| --- a/tests/unit/test-block-iothread.c
 | ||||
| +++ b/tests/unit/test-block-iothread.c
 | ||||
| @@ -755,7 +755,7 @@ static void test_propagate_mirror(void)
 | ||||
|   | ||||
|      /* Start a mirror job */ | ||||
|      mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0, | ||||
| -                 MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false, false,
 | ||||
| +                 MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false,
 | ||||
|                   BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT, | ||||
|                   false, "filter_node", MIRROR_COPY_MODE_BACKGROUND, | ||||
|                   &error_abort); | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										90
									
								
								kvm-mirror-Minor-refactoring.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								kvm-mirror-Minor-refactoring.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | ||||
| From e95294aecc606deacf716861d716f9178b132ed8 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:23 -0500 | ||||
| Subject: [PATCH 06/14] mirror: Minor refactoring | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [6/14] 22a87aca8033f3f5d10fd224dc0786633a4d040f (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Commit 5791ba52 (v9.2) pre-initialized ret in mirror_dirty_init to | ||||
| silence a false positive compiler warning, even though in all code | ||||
| paths where ret is used, it was guaranteed to be reassigned | ||||
| beforehand.  But since the function returns -errno, and -1 is not | ||||
| always the right errno, it's better to initialize to -EIO. | ||||
| 
 | ||||
| An upcoming patch wants to track two bitmaps in | ||||
| do_sync_target_write(); this will be easier if the current variables | ||||
| related to the dirty bitmap are renamed. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-21-eblake@redhat.com> | ||||
| (cherry picked from commit 870f8963cf1a84f8ec929b05a6d68906974a76c5) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c | 22 +++++++++++----------- | ||||
|  1 file changed, 11 insertions(+), 11 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index a53582f17b..34c6c5252e 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -841,7 +841,7 @@ 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; | ||||
|   | ||||
|      bdrv_graph_co_rdlock(); | ||||
| @@ -1341,7 +1341,7 @@ 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;
 | ||||
|   | ||||
|      if (!QEMU_IS_ALIGNED(offset, job->granularity) && | ||||
|          bdrv_dirty_bitmap_get(job->dirty_bitmap, offset)) | ||||
| @@ -1388,11 +1388,11 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
 | ||||
|       * Tails are either clean or shrunk, so for bitmap resetting | ||||
|       * we safely align the range down. | ||||
|       */ | ||||
| -    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);
 | ||||
|      } | ||||
|   | ||||
|      job_progress_increase_remaining(&job->common.job, bytes); | ||||
| @@ -1430,10 +1430,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); | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										139
									
								
								kvm-mirror-Pass-full-sync-mode-rather-than-bool-to-inter.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								kvm-mirror-Pass-full-sync-mode-rather-than-bool-to-inter.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | ||||
| From db0b92495a4e774caafaaa148e778b575112bad2 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:24 -0500 | ||||
| Subject: [PATCH 07/14] mirror: Pass full sync mode rather than bool to | ||||
|  internals | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [7/14] e8872e3edad069ee6c76f6104c1bc277c025b5ac (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Out of the five possible values for MirrorSyncMode, INCREMENTAL and | ||||
| BITMAP are already rejected up front in mirror_start, leaving NONE, | ||||
| TOP, and FULL as the remaining values that the code was collapsing | ||||
| into a single bool is_none_mode.  Furthermore, mirror_dirty_init() is | ||||
| only reachable for modes TOP and FULL, as further guided by | ||||
| s->zero_target.  However, upcoming patches want to further optimize | ||||
| the pre-zeroing pass of a sync=full mirror in mirror_dirty_init(), | ||||
| while avoiding that pass on a sync=top action.  Instead of throwing | ||||
| away context by collapsing these two values into | ||||
| s->is_none_mode=false, it is better to pass s->sync_mode throughout | ||||
| the entire operation.  For active commit, the desired semantics match | ||||
| sync mode TOP. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-22-eblake@redhat.com> | ||||
| Reviewed-by: Sunny Zhu <sunnyzhyy@qq.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit 9474d97bd7421b4fe7c806ab0949697514d11e88) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c | 24 ++++++++++++------------ | ||||
|  1 file changed, 12 insertions(+), 12 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index 34c6c5252e..2599b75d09 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -51,7 +51,7 @@ 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; | ||||
| @@ -723,9 +723,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) { | ||||
| @@ -1020,7 +1021,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; | ||||
| @@ -1711,6 +1712,7 @@ 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, | ||||
|                               BlockdevOnError on_source_error, | ||||
| @@ -1719,7 +1721,7 @@ static BlockJob *mirror_start_job(
 | ||||
|                               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,7 +1880,7 @@ 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; | ||||
|      qatomic_set(&s->copy_mode, copy_mode); | ||||
| @@ -2015,7 +2017,6 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
 | ||||
|                    bool unmap, const char *filter_node_name, | ||||
|                    MirrorCopyMode copy_mode, Error **errp) | ||||
|  { | ||||
| -    bool is_none_mode;
 | ||||
|      BlockDriverState *base; | ||||
|   | ||||
|      GLOBAL_STATE_CODE(); | ||||
| @@ -2028,14 +2029,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,
 | ||||
| +                     zero_target, on_source_error, on_target_error, unmap,
 | ||||
| +                     NULL, NULL, &mirror_job_driver, base, false,
 | ||||
|                       filter_node_name, true, copy_mode, false, errp); | ||||
|  } | ||||
|   | ||||
| @@ -2061,9 +2061,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) { | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
| @ -0,0 +1,58 @@ | ||||
| From 9fedd14da6f1dc7aa3f0711d86f722397d080993 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Tue, 13 May 2025 17:00:45 -0500 | ||||
| Subject: [PATCH 14/14] mirror: Reduce I/O when destination is | ||||
|  detect-zeroes:unmap | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [14/14] d4ba9d88a8da00f82c2ba7ebf050152fbe1e2465 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| If we are going to punch holes in the mirror destination even for the | ||||
| portions where the source image is unallocated, it is nicer to treat | ||||
| the entire image as dirty and punch as we go, rather than pre-zeroing | ||||
| the entire image just to re-do I/O to the allocated portions of the | ||||
| image. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250513220142.535200-2-eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit 9abfc81246c9cc1845080eec5920779961187c07) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c | 13 +++++++++---- | ||||
|  1 file changed, 9 insertions(+), 4 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index 724318f037..c2c5099c95 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -920,11 +920,16 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|           * 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, then our only
 | ||||
| -         * recourse is to mark the entire image dirty.  The act of
 | ||||
| -         * pre-zeroing will populate the zero bitmap.
 | ||||
| +         * 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)) {
 | ||||
| +        if (!bdrv_can_write_zeroes_with_unmap(target_bs) || punch_holes) {
 | ||||
|              bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length); | ||||
|              return 0; | ||||
|          } | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										180
									
								
								kvm-mirror-Skip-pre-zeroing-destination-if-it-is-already.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								kvm-mirror-Skip-pre-zeroing-destination-if-it-is-already.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,180 @@ | ||||
| From 92a033b6c8394c8efb5b881cbbe463eeff5711cd Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:27 -0500 | ||||
| Subject: [PATCH 10/14] mirror: Skip pre-zeroing destination if it is already | ||||
|  zero | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [10/14] 5d86d9c763a1bb49fab591a45e211d3be819ccfe (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| When doing a sync=full mirroring, we can skip pre-zeroing the | ||||
| destination if it already reads as zeroes and we are not also trying | ||||
| to punch holes due to detect-zeroes.  With this patch, there are fewer | ||||
| scenarios that have to pass in an explicit target-is-zero, while still | ||||
| resulting in a sparse destination remaining sparse. | ||||
| 
 | ||||
| A later patch will then further improve things to skip writing to the | ||||
| destination for parts of the image where the source is zero; but even | ||||
| with just this patch, it is possible to see a difference for any | ||||
| source that does not report itself as fully allocated, coupled with a | ||||
| destination BDS that can quickly report that it already reads as zero. | ||||
| (For a source that reports as fully allocated, such as a file, the | ||||
| rest of mirror_dirty_init() still sets the entire dirty bitmap to | ||||
| true, so even though we avoided the pre-zeroing, we are not yet | ||||
| avoiding all redundant I/O). | ||||
| 
 | ||||
| Iotest 194 detects the difference made by this patch: for a file | ||||
| source (where block status reports the entire image as allocated, and | ||||
| therefore we end up writing zeroes everywhere in the destination | ||||
| anyways), the job length remains the same.  But for a qcow2 source and | ||||
| a destination that reads as all zeroes, the dirty bitmap changes to | ||||
| just tracking the allocated portions of the source, which results in | ||||
| faster completion and smaller job statistics.  For the test to pass | ||||
| with both ./check -file and -qcow2, a new python filter is needed to | ||||
| mask out the now-varying job amounts (this matches the shell filters | ||||
| _filter_block_job_{offset,len} in common.filter).  A later test will | ||||
| also be added which further validates expected sparseness, so it does | ||||
| not matter that 194 is no longer explicitly looking at how many bytes | ||||
| were copied. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-25-eblake@redhat.com> | ||||
| Reviewed-by: Sunny Zhu <sunnyzhyy@qq.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit 181a63667adf16c35b57e446def3e41c70f1fea6) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c                | 24 ++++++++++++++++-------- | ||||
|  tests/qemu-iotests/194        |  6 ++++-- | ||||
|  tests/qemu-iotests/194.out    |  4 ++-- | ||||
|  tests/qemu-iotests/iotests.py | 12 +++++++++++- | ||||
|  4 files changed, 33 insertions(+), 13 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index d04db85883..bca99ec206 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -848,23 +848,31 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|          target_bs->detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP && | ||||
|          bdrv_can_write_zeroes_with_unmap(target_bs); | ||||
|   | ||||
| +    /* Determine if the image is already zero, regardless of sync mode.  */
 | ||||
|      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->sync_mode == MIRROR_SYNC_MODE_TOP) {
 | ||||
| +    /* 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.  */ | ||||
| -    } else if (!s->target_is_zero || punch_holes) {
 | ||||
| +    } 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 (s->target_is_zero) 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 punch holes, then our only recourse is to write the
 | ||||
| -         * entire image.
 | ||||
| +         * 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 punch
 | ||||
| +         * holes, then our only recourse is to write the entire image.
 | ||||
|           */ | ||||
|          if (!bdrv_can_write_zeroes_with_unmap(target_bs)) { | ||||
|              bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length); | ||||
| diff --git a/tests/qemu-iotests/194 b/tests/qemu-iotests/194
 | ||||
| index d0b9c084f5..e114c0b269 100755
 | ||||
| --- a/tests/qemu-iotests/194
 | ||||
| +++ b/tests/qemu-iotests/194
 | ||||
| @@ -62,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}, | ||||
| @@ -88,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')) | ||||
| diff --git a/tests/qemu-iotests/194.out b/tests/qemu-iotests/194.out
 | ||||
| index 6940e809cd..d02655a514 100644
 | ||||
| --- a/tests/qemu-iotests/194.out
 | ||||
| +++ b/tests/qemu-iotests/194.out
 | ||||
| @@ -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... | ||||
| diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
 | ||||
| index 7292c8b342..05274772ce 100644
 | ||||
| --- a/tests/qemu-iotests/iotests.py
 | ||||
| +++ b/tests/qemu-iotests/iotests.py
 | ||||
| @@ -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.''' | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										355
									
								
								kvm-mirror-Skip-writing-zeroes-when-target-is-already-ze.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								kvm-mirror-Skip-writing-zeroes-when-target-is-already-ze.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,355 @@ | ||||
| From cc72e6ec30fb113b82fcdb61f79a0fae18e31e79 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:28 -0500 | ||||
| Subject: [PATCH 11/14] mirror: Skip writing zeroes when target is already zero | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [11/14] 82df7fcf94606e6b6570469d3c05666da5039408 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| When mirroring, the goal is to ensure that the destination reads the | ||||
| same as the source; this goal is met whether the destination is sparse | ||||
| or fully-allocated (except when explicitly punching holes, then merely | ||||
| reading zero is not enough to know if it is sparse, so we still want | ||||
| to punch the hole).  Avoiding a redundant write to zero (whether in | ||||
| the background because the zero cluster was marked in the dirty | ||||
| bitmap, or in the foreground because the guest is writing zeroes) when | ||||
| the destination already reads as zero makes mirroring faster, and | ||||
| avoids allocating the destination merely because the source reports as | ||||
| allocated. | ||||
| 
 | ||||
| The effect is especially pronounced when the source is a raw file. | ||||
| That's because when the source is a qcow2 file, the dirty bitmap only | ||||
| visits the portions of the source that are allocated, which tend to be | ||||
| non-zero.  But when the source is a raw file, | ||||
| bdrv_co_is_allocated_above() reports the entire file as allocated so | ||||
| mirror_dirty_init sets the entire dirty bitmap, and it is only later | ||||
| during mirror_iteration that we change to consulting the more precise | ||||
| bdrv_co_block_status_above() to learn where the source reads as zero. | ||||
| 
 | ||||
| Remember that since a mirror operation can write a cluster more than | ||||
| once (every time the guest changes the source, the destination is also | ||||
| changed to keep up), and the guest can change whether a given cluster | ||||
| reads as zero, is discarded, or has non-zero data over the course of | ||||
| the mirror operation, we can't take the shortcut of relying on | ||||
| s->target_is_zero (which is static for the life of the job) in | ||||
| mirror_co_zero() to see if the destination is already zero, because | ||||
| that information may be stale.  Any solution we use must be dynamic in | ||||
| the face of the guest writing or discarding a cluster while the mirror | ||||
| has been ongoing. | ||||
| 
 | ||||
| We could just teach mirror_co_zero() to do a block_status() probe of | ||||
| the destination, and skip the zeroes if the destination already reads | ||||
| as zero, but we know from past experience that extra block_status() | ||||
| calls are not always cheap (tmpfs, anyone?), especially when they are | ||||
| random access rather than linear.  Use of block_status() of the source | ||||
| by the background task in a linear fashion is not our bottleneck (it's | ||||
| a background task, after all); but since mirroring can be done while | ||||
| the source is actively being changed, we don't want a slow | ||||
| block_status() of the destination to occur on the hot path of the | ||||
| guest trying to do random-access writes to the source. | ||||
| 
 | ||||
| So this patch takes a slightly different approach: any time we have to | ||||
| track dirty clusters, we can also track which clusters are known to | ||||
| read as zero.  For sync=TOP or when we are punching holes from | ||||
| "detect-zeroes":"unmap", the zero bitmap starts out empty, but | ||||
| prevents a second write zero to a cluster that was already zero by an | ||||
| earlier pass; for sync=FULL when we are not punching holes, the zero | ||||
| bitmap starts out full if the destination reads as zero during | ||||
| initialization.  Either way, I/O to the destination can now avoid | ||||
| redundant write zero to a cluster that already reads as zero, all | ||||
| without having to do a block_status() per write on the destination. | ||||
| 
 | ||||
| With this patch, if I create a raw sparse destination file, connect it | ||||
| with QMP 'blockdev-add' while leaving it at the default "discard": | ||||
| "ignore", then run QMP 'blockdev-mirror' with "sync": "full", the | ||||
| destination remains sparse rather than fully allocated.  Meanwhile, a | ||||
| destination image that is already fully allocated remains so unless it | ||||
| was opened with "detect-zeroes": "unmap".  And any time writing zeroes | ||||
| is skipped, the job counters are not incremented. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-26-eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit 7e277545b90874171128804e256a538fb0e8dd7e) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  block/mirror.c | 107 ++++++++++++++++++++++++++++++++++++++++++------- | ||||
|  1 file changed, 93 insertions(+), 14 deletions(-) | ||||
| 
 | ||||
| diff --git a/block/mirror.c b/block/mirror.c
 | ||||
| index bca99ec206..724318f037 100644
 | ||||
| --- a/block/mirror.c
 | ||||
| +++ b/block/mirror.c
 | ||||
| @@ -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; | ||||
| @@ -847,8 +887,10 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|      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) { | ||||
| @@ -862,7 +904,14 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|      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.  */
 | ||||
| +        /*
 | ||||
| +         * 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 | ||||
| @@ -871,8 +920,9 @@ static int coroutine_fn GRAPH_UNLOCKED mirror_dirty_init(MirrorBlockJob *s)
 | ||||
|           * 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 punch
 | ||||
| -         * holes, then our only recourse is to write the entire image.
 | ||||
| +         * TOP mode).  If pre-zeroing is not fast, then our only
 | ||||
| +         * recourse is to mark the entire image dirty.  The act of
 | ||||
| +         * pre-zeroing will populate the zero bitmap.
 | ||||
|           */ | ||||
|          if (!bdrv_can_write_zeroes_with_unmap(target_bs)) { | ||||
|              bdrv_set_dirty_bitmap(s->dirty_bitmap, 0, s->bdev_length); | ||||
| @@ -883,6 +933,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); | ||||
|   | ||||
| @@ -898,12 +949,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.  */ | ||||
| @@ -1188,6 +1242,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); | ||||
|   | ||||
| @@ -1367,6 +1422,7 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
 | ||||
|      int ret; | ||||
|      size_t qiov_offset = 0; | ||||
|      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)) | ||||
| @@ -1410,8 +1466,9 @@ 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.
 | ||||
|       */ | ||||
|      dirty_bitmap_offset = QEMU_ALIGN_UP(offset, job->granularity); | ||||
|      dirty_bitmap_end = QEMU_ALIGN_DOWN(offset + bytes, job->granularity); | ||||
| @@ -1419,22 +1476,44 @@ do_sync_target_write(MirrorBlockJob *job, MirrorMethod method,
 | ||||
|          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; | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
							
								
								
									
										545
									
								
								kvm-tests-Add-iotest-mirror-sparse-for-recent-patches.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										545
									
								
								kvm-tests-Add-iotest-mirror-sparse-for-recent-patches.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,545 @@ | ||||
| From 2bb881df5b93f5534e5f0b91cf1ed3e0b524c2d3 Mon Sep 17 00:00:00 2001 | ||||
| From: Eric Blake <eblake@redhat.com> | ||||
| Date: Fri, 9 May 2025 15:40:30 -0500 | ||||
| Subject: [PATCH 13/14] tests: Add iotest mirror-sparse for recent patches | ||||
| 
 | ||||
| RH-Author: Eric Blake <eblake@redhat.com> | ||||
| RH-MergeRequest: 363: blockdev-mirror: More efficient handling of sparse mirrors | ||||
| RH-Jira: RHEL-88435 RHEL-88437 | ||||
| RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com> | ||||
| RH-Commit: [13/14] 474f12dfe9161c7e9f59cafde203e5183e2fc3f5 (ebblake/centos-qemu-kvm) | ||||
| 
 | ||||
| Prove that blockdev-mirror can now result in sparse raw destination | ||||
| files, regardless of whether the source is raw or qcow2.  By making | ||||
| this a separate test, it was possible to test effects of individual | ||||
| patches for the various pieces that all have to work together for a | ||||
| sparse mirror to be successful. | ||||
| 
 | ||||
| Note that ./check -file produces different job lengths than ./check | ||||
| -qcow2 (the test uses a filter to normalize); that's because when
 | ||||
| deciding how much of the image to be mirrored, the code looks at how | ||||
| much of the source image was allocated (for qcow2, this is only the | ||||
| written clusters; for raw, it is the entire file).  But the important | ||||
| part is that the destination file ends up smaller than 3M, rather than | ||||
| the 20M it used to be before this patch series. | ||||
| 
 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| Message-ID: <20250509204341.3553601-28-eblake@redhat.com> | ||||
| Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||||
| (cherry picked from commit c0ddcb2cbc146e64f666eaae4edc7b5db7e5814d) | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88435 | ||||
| Jira: https://issues.redhat.com/browse/RHEL-88437 | ||||
| Signed-off-by: Eric Blake <eblake@redhat.com> | ||||
| ---
 | ||||
|  tests/qemu-iotests/tests/mirror-sparse     | 125 +++++++ | ||||
|  tests/qemu-iotests/tests/mirror-sparse.out | 365 +++++++++++++++++++++ | ||||
|  2 files changed, 490 insertions(+) | ||||
|  create mode 100755 tests/qemu-iotests/tests/mirror-sparse | ||||
|  create mode 100644 tests/qemu-iotests/tests/mirror-sparse.out | ||||
| 
 | ||||
| diff --git a/tests/qemu-iotests/tests/mirror-sparse b/tests/qemu-iotests/tests/mirror-sparse
 | ||||
| new file mode 100755 | ||||
| index 0000000000..8c52a4e244
 | ||||
| --- /dev/null
 | ||||
| +++ b/tests/qemu-iotests/tests/mirror-sparse
 | ||||
| @@ -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
 | ||||
| diff --git a/tests/qemu-iotests/tests/mirror-sparse.out b/tests/qemu-iotests/tests/mirror-sparse.out
 | ||||
| new file mode 100644 | ||||
| index 0000000000..2103b891c3
 | ||||
| --- /dev/null
 | ||||
| +++ b/tests/qemu-iotests/tests/mirror-sparse.out
 | ||||
| @@ -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
 | ||||
| -- 
 | ||||
| 2.39.3 | ||||
| 
 | ||||
| @ -147,7 +147,7 @@ Obsoletes: %{name}-block-ssh <= %{epoch}:%{version}                    \ | ||||
| Summary: QEMU is a machine emulator and virtualizer | ||||
| Name: qemu-kvm | ||||
| Version: 10.0.0 | ||||
| Release: 3%{?rcrel}%{?dist}%{?cc_suffix} | ||||
| Release: 4%{?rcrel}%{?dist}%{?cc_suffix} | ||||
| # Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped | ||||
| # Epoch 15 used for RHEL 8 | ||||
| # Epoch 17 used for RHEL 9 (due to release versioning offset in RHEL 8.5) | ||||
| @ -202,6 +202,48 @@ Patch24: kvm-migration-postcopy-Spatial-locality-page-hint-for-pr.patch | ||||
| Patch25: kvm-meson-configure-add-valgrind-option-en-dis-able-valg.patch | ||||
| # Fixing s390x build issues | ||||
| Patch26: kvm-docs-Don-t-define-duplicate-label-in-qemu-block-driv.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch27: kvm-block-Expand-block-status-mode-from-bool-to-flags.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch28: kvm-file-posix-gluster-Handle-zero-block-status-hint-bet.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch29: kvm-block-Let-bdrv_co_is_zero_fast-consolidate-adjacent-.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch30: kvm-block-Add-new-bdrv_co_is_all_zeroes-function.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch31: kvm-iotests-Improve-iotest-194-to-mirror-data.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch32: kvm-mirror-Minor-refactoring.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch33: kvm-mirror-Pass-full-sync-mode-rather-than-bool-to-inter.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch34: kvm-mirror-Allow-QMP-override-to-declare-target-already-.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch35: kvm-mirror-Drop-redundant-zero_target-parameter.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch36: kvm-mirror-Skip-pre-zeroing-destination-if-it-is-already.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch37: kvm-mirror-Skip-writing-zeroes-when-target-is-already-ze.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch38: kvm-iotests-common.rc-add-disk_usage-function.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch39: kvm-tests-Add-iotest-mirror-sparse-for-recent-patches.patch | ||||
| # For RHEL-88435 - --migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1] | ||||
| # For RHEL-88437 - Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1] | ||||
| Patch40: kvm-mirror-Reduce-I-O-when-destination-is-detect-zeroes-.patch | ||||
| 
 | ||||
| %if %{have_clang} | ||||
| BuildRequires: clang | ||||
| @ -1284,6 +1326,26 @@ useradd -r -u 107 -g qemu -G kvm -d / -s /sbin/nologin \ | ||||
| %endif | ||||
| 
 | ||||
| %changelog | ||||
| * Mon May 26 2025 Miroslav Rezanina <mrezanin@redhat.com> - 10.0.0-4 | ||||
| - kvm-block-Expand-block-status-mode-from-bool-to-flags.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-file-posix-gluster-Handle-zero-block-status-hint-bet.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-block-Let-bdrv_co_is_zero_fast-consolidate-adjacent-.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-block-Add-new-bdrv_co_is_all_zeroes-function.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-iotests-Improve-iotest-194-to-mirror-data.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Minor-refactoring.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Pass-full-sync-mode-rather-than-bool-to-inter.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Allow-QMP-override-to-declare-target-already-.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Drop-redundant-zero_target-parameter.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Skip-pre-zeroing-destination-if-it-is-already.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Skip-writing-zeroes-when-target-is-already-ze.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-iotests-common.rc-add-disk_usage-function.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-tests-Add-iotest-mirror-sparse-for-recent-patches.patch [RHEL-88435 RHEL-88437] | ||||
| - kvm-mirror-Reduce-I-O-when-destination-is-detect-zeroes-.patch [RHEL-88435 RHEL-88437] | ||||
| - Resolves: RHEL-88435 | ||||
|   (--migrate-disks-detect-zeroes doesn't take effect for disk migration [rhel-10.1]) | ||||
| - Resolves: RHEL-88437 | ||||
|   (Disk size of target raw image is full allocated when doing mirror with default discard value [rhel-10.1]) | ||||
| 
 | ||||
| * Mon May 19 2025 Miroslav Rezanina <mrezanin@redhat.com> - 10.0.0-3 | ||||
| - kvm-migration-postcopy-Spatial-locality-page-hint-for-pr.patch [RHEL-85635] | ||||
| - kvm-meson-configure-add-valgrind-option-en-dis-able-valg.patch [RHEL-88457] | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user