Commit 97dd2b5ce9 for qemu.org

commit 97dd2b5ce99dada6ed13a4280736c13a8df43710
Author: Denis V. Lunev <den@openvz.org>
Date:   Fri Oct 30 02:06:09 2026 +0100

    block/block-copy: merge the two backup scan steps into block_copy_prescan()

    block_copy_reset_unallocated() and block_copy_calculate_zero_bitmap()
    are both one step of the same scan and differ only in what they do with
    the answer. Fold them into block_copy_prescan(), which drops unallocated
    clusters from copy_bitmap while skip_unallocated is set, so
    backup_scan_source() loses its per-mode branch.

    sync=top now walks the dirty areas the way full and bitmap do, so it no
    longer queries what copy-before-write has already copied during the
    scan. A failed query still fails sync=top, as nothing else can tell
    which clusters to skip, and still leaves the area to be copied as data
    otherwise.

    Suggested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
    Signed-off-by: Denis V. Lunev <den@openvz.org>
    CC: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
    CC: John Snow <jsnow@redhat.com>
    CC: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
    Message-ID: <20261030010613.1612771-10-den@openvz.org>
    Reviewed-by: Eric Blake <eblake@redhat.com>
    Signed-off-by: Eric Blake <eblake@redhat.com>

diff --git a/block/backup.c b/block/backup.c
index 38b0838d69..60c00fca67 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -271,12 +271,7 @@ static int coroutine_fn backup_scan_source(BackupBlockJob *s)

         /* rdlock protects the block-status queries below */
         bdrv_graph_co_rdlock();
-        if (top) {
-            ret = block_copy_reset_unallocated(s->bcs, offset, &count);
-        } else {
-            block_copy_calculate_zero_bitmap(s->bcs, offset, &cached_end,
-                                             &count);
-        }
+        ret = block_copy_prescan(s->bcs, offset, &cached_end, &count);
         bdrv_graph_co_rdunlock();

         if (ret < 0) {
diff --git a/block/block-copy.c b/block/block-copy.c
index ad358f4d2f..78cc855b5b 100644
--- a/block/block-copy.c
+++ b/block/block-copy.c
@@ -153,8 +153,8 @@ typedef struct BlockCopyState {
      * This is indicated by skip_unallocated.
      *
      * In this case, block_copy() will query the source’s allocation status,
-     * skip unallocated regions, clear them in the copy_bitmap, and invoke
-     * block_copy_reset_unallocated() every time it does.
+     * skip unallocated regions and clear them in the copy_bitmap, as
+     * block_copy_prescan() does.
      */
     bool skip_unallocated; /* atomic */
     /* State fields that use a thread-safe API */
@@ -781,40 +781,17 @@ void block_copy_reset(BlockCopyState *s, int64_t offset, int64_t bytes)
 }

 /*
- * Reset bits in copy_bitmap starting at offset if they represent unallocated
- * data in the image. May reset subsequent contiguous bits.
- * @return 0 when the cluster at @offset was unallocated,
- *         1 otherwise, and -ret on error.
- */
-int64_t coroutine_fn block_copy_reset_unallocated(BlockCopyState *s,
-                                                  int64_t offset,
-                                                  int64_t *count)
-{
-    int ret;
-    int64_t clusters, bytes;
-
-    ret = block_copy_scan_status(s, offset, &clusters);
-    if (ret < 0) {
-        return ret;
-    }
-
-    bytes = clusters * s->cluster_size;
-
-    if (!ret) {
-        block_copy_reset(s, offset, bytes);
-    }
-
-    *count = bytes;
-    return ret;
-}
-
-/*
- * One scan step, like block_copy_reset_unallocated(); @cached_end
- * tracks progress across calls, @count how far @offset should move.
+ * One step of the scan run before copying: query the next dirty area, fill
+ * zero_bitmap and, while skip_unallocated is set, drop the clusters which
+ * are not allocated above the backing from copy_bitmap. @cached_end tracks
+ * progress across calls, @count how far @offset should move.
+ *
+ * A failed query only leaves the area unmarked, so it copies as data, but
+ * sync=top cannot tell what to skip and fails instead.
  */
-void coroutine_fn GRAPH_RDLOCK
-block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,
-                                 int64_t *cached_end, int64_t *count)
+int coroutine_fn GRAPH_RDLOCK
+block_copy_prescan(BlockCopyState *s, int64_t offset, int64_t *cached_end,
+                   int64_t *count)
 {
     int64_t max_bytes = QEMU_ALIGN_DOWN(BDRV_REQUEST_MAX_BYTES,
                                         s->cluster_size);
@@ -830,7 +807,7 @@ block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,

     if (!found) {
         *count = s->len - offset;
-        return;
+        return 0;
     }
     dirty_end = dirty_offset + dirty_bytes;

@@ -839,15 +816,21 @@ block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,
         int64_t query_offset = MAX(dirty_offset, *cached_end);
         int ret = block_copy_scan_status(s, query_offset, &clusters);

-        if (ret >= 0) {
-            *cached_end = query_offset + clusters * s->cluster_size;
-        } else {
-            /* Best-effort: leave this area unmarked; it copies as data. */
+        if (ret < 0) {
+            if (qatomic_read(&s->skip_unallocated)) {
+                return ret;
+            }
             *cached_end = dirty_end;
+        } else {
+            *cached_end = query_offset + clusters * s->cluster_size;
+            if (!ret && qatomic_read(&s->skip_unallocated)) {
+                block_copy_reset(s, query_offset, clusters * s->cluster_size);
+            }
         }
     }

     *count = MIN(*cached_end, dirty_end) - offset;
+    return 0;
 }

 /*
diff --git a/include/block/block-copy.h b/include/block/block-copy.h
index 3a61066b55..dc51316fcf 100644
--- a/include/block/block-copy.h
+++ b/include/block/block-copy.h
@@ -41,12 +41,9 @@ void block_copy_state_free(BlockCopyState *s);

 void block_copy_reset(BlockCopyState *s, int64_t offset, int64_t bytes);

-int64_t coroutine_fn GRAPH_RDLOCK
-block_copy_reset_unallocated(BlockCopyState *s, int64_t offset, int64_t *count);
-
-void coroutine_fn GRAPH_RDLOCK
-block_copy_calculate_zero_bitmap(BlockCopyState *s, int64_t offset,
-                                 int64_t *cached_end, int64_t *count);
+int coroutine_fn GRAPH_RDLOCK
+block_copy_prescan(BlockCopyState *s, int64_t offset, int64_t *cached_end,
+                   int64_t *count);

 int coroutine_fn block_copy(BlockCopyState *s, int64_t offset, int64_t bytes,
                             bool ignore_ratelimit, uint64_t timeout_ns,