1be5c7
From be54c6206b0f0a19e0ffe6a058f4f97277027a17 Mon Sep 17 00:00:00 2001
1be5c7
From: Hanna Reitz <hreitz@redhat.com>
1be5c7
Date: Tue, 5 Apr 2022 15:46:50 +0200
1be5c7
Subject: [PATCH 3/6] qcow2: Improve refcount structure rebuilding
1be5c7
1be5c7
RH-Author: Hanna Reitz <hreitz@redhat.com>
1be5c7
RH-MergeRequest: 171: qcow2: Improve refcount structure rebuilding
1be5c7
RH-Commit: [1/4] 0bb78f7735a0730204670ae5ec2e040ad1d23942
1be5c7
RH-Bugzilla: 1519071
1be5c7
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
1be5c7
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
1be5c7
RH-Acked-by: Eric Blake <eblake@redhat.com>
1be5c7
1be5c7
When rebuilding the refcount structures (when qemu-img check -r found
1be5c7
errors with refcount = 0, but reference count > 0), the new refcount
1be5c7
table defaults to being put at the image file end[1].  There is no good
1be5c7
reason for that except that it means we will not have to rewrite any
1be5c7
refblocks we already wrote to disk.
1be5c7
1be5c7
Changing the code to rewrite those refblocks is not too difficult,
1be5c7
though, so let us do that.  That is beneficial for images on block
1be5c7
devices, where we cannot really write beyond the end of the image file.
1be5c7
1be5c7
Use this opportunity to add extensive comments to the code, and refactor
1be5c7
it a bit, getting rid of the backwards-jumping goto.
1be5c7
1be5c7
[1] Unless there is something allocated in the area pointed to by the
1be5c7
    last refblock, so we have to write that refblock.  In that case, we
1be5c7
    try to put the reftable in there.
1be5c7
1be5c7
Buglink: https://bugzilla.redhat.com/show_bug.cgi?id=1519071
1be5c7
Closes: https://gitlab.com/qemu-project/qemu/-/issues/941
1be5c7
Reviewed-by: Eric Blake <eblake@redhat.com>
1be5c7
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
1be5c7
Message-Id: <20220405134652.19278-2-hreitz@redhat.com>
1be5c7
(cherry picked from commit a8c07ec287554dcefd33733f0e5888a281ddc95e)
1be5c7
Signed-off-by: Hanna Reitz <hreitz@redhat.com>
1be5c7
---
1be5c7
 block/qcow2-refcount.c | 332 +++++++++++++++++++++++++++++------------
1be5c7
 1 file changed, 235 insertions(+), 97 deletions(-)
1be5c7
1be5c7
diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c
1be5c7
index 4614572252..555d8ba5ac 100644
1be5c7
--- a/block/qcow2-refcount.c
1be5c7
+++ b/block/qcow2-refcount.c
1be5c7
@@ -2435,111 +2435,140 @@ static int64_t alloc_clusters_imrt(BlockDriverState *bs,
1be5c7
 }
1be5c7
 
1be5c7
 /*
1be5c7
- * Creates a new refcount structure based solely on the in-memory information
1be5c7
- * given through *refcount_table. All necessary allocations will be reflected
1be5c7
- * in that array.
1be5c7
+ * Helper function for rebuild_refcount_structure().
1be5c7
  *
1be5c7
- * On success, the old refcount structure is leaked (it will be covered by the
1be5c7
- * new refcount structure).
1be5c7
+ * Scan the range of clusters [first_cluster, end_cluster) for allocated
1be5c7
+ * clusters and write all corresponding refblocks to disk.  The refblock
1be5c7
+ * and allocation data is taken from the in-memory refcount table
1be5c7
+ * *refcount_table[] (of size *nb_clusters), which is basically one big
1be5c7
+ * (unlimited size) refblock for the whole image.
1be5c7
+ *
1be5c7
+ * For these refblocks, clusters are allocated using said in-memory
1be5c7
+ * refcount table.  Care is taken that these allocations are reflected
1be5c7
+ * in the refblocks written to disk.
1be5c7
+ *
1be5c7
+ * The refblocks' offsets are written into a reftable, which is
1be5c7
+ * *on_disk_reftable_ptr[] (of size *on_disk_reftable_entries_ptr).  If
1be5c7
+ * that reftable is of insufficient size, it will be resized to fit.
1be5c7
+ * This reftable is not written to disk.
1be5c7
+ *
1be5c7
+ * (If *on_disk_reftable_ptr is not NULL, the entries within are assumed
1be5c7
+ * to point to existing valid refblocks that do not need to be allocated
1be5c7
+ * again.)
1be5c7
+ *
1be5c7
+ * Return whether the on-disk reftable array was resized (true/false),
1be5c7
+ * or -errno on error.
1be5c7
  */
1be5c7
-static int rebuild_refcount_structure(BlockDriverState *bs,
1be5c7
-                                      BdrvCheckResult *res,
1be5c7
-                                      void **refcount_table,
1be5c7
-                                      int64_t *nb_clusters)
1be5c7
+static int rebuild_refcounts_write_refblocks(
1be5c7
+        BlockDriverState *bs, void **refcount_table, int64_t *nb_clusters,
1be5c7
+        int64_t first_cluster, int64_t end_cluster,
1be5c7
+        uint64_t **on_disk_reftable_ptr, uint32_t *on_disk_reftable_entries_ptr
1be5c7
+    )
1be5c7
 {
1be5c7
     BDRVQcow2State *s = bs->opaque;
1be5c7
-    int64_t first_free_cluster = 0, reftable_offset = -1, cluster = 0;
1be5c7
+    int64_t cluster;
1be5c7
     int64_t refblock_offset, refblock_start, refblock_index;
1be5c7
-    uint32_t reftable_size = 0;
1be5c7
-    uint64_t *on_disk_reftable = NULL;
1be5c7
+    int64_t first_free_cluster = 0;
1be5c7
+    uint64_t *on_disk_reftable = *on_disk_reftable_ptr;
1be5c7
+    uint32_t on_disk_reftable_entries = *on_disk_reftable_entries_ptr;
1be5c7
     void *on_disk_refblock;
1be5c7
-    int ret = 0;
1be5c7
-    struct {
1be5c7
-        uint64_t reftable_offset;
1be5c7
-        uint32_t reftable_clusters;
1be5c7
-    } QEMU_PACKED reftable_offset_and_clusters;
1be5c7
-
1be5c7
-    qcow2_cache_empty(bs, s->refcount_block_cache);
1be5c7
+    bool reftable_grown = false;
1be5c7
+    int ret;
1be5c7
 
1be5c7
-write_refblocks:
1be5c7
-    for (; cluster < *nb_clusters; cluster++) {
1be5c7
+    for (cluster = first_cluster; cluster < end_cluster; cluster++) {
1be5c7
+        /* Check all clusters to find refblocks that contain non-zero entries */
1be5c7
         if (!s->get_refcount(*refcount_table, cluster)) {
1be5c7
             continue;
1be5c7
         }
1be5c7
 
1be5c7
+        /*
1be5c7
+         * This cluster is allocated, so we need to create a refblock
1be5c7
+         * for it.  The data we will write to disk is just the
1be5c7
+         * respective slice from *refcount_table, so it will contain
1be5c7
+         * accurate refcounts for all clusters belonging to this
1be5c7
+         * refblock.  After we have written it, we will therefore skip
1be5c7
+         * all remaining clusters in this refblock.
1be5c7
+         */
1be5c7
+
1be5c7
         refblock_index = cluster >> s->refcount_block_bits;
1be5c7
         refblock_start = refblock_index << s->refcount_block_bits;
1be5c7
 
1be5c7
-        /* Don't allocate a cluster in a refblock already written to disk */
1be5c7
-        if (first_free_cluster < refblock_start) {
1be5c7
-            first_free_cluster = refblock_start;
1be5c7
-        }
1be5c7
-        refblock_offset = alloc_clusters_imrt(bs, 1, refcount_table,
1be5c7
-                                              nb_clusters, &first_free_cluster);
1be5c7
-        if (refblock_offset < 0) {
1be5c7
-            fprintf(stderr, "ERROR allocating refblock: %s\n",
1be5c7
-                    strerror(-refblock_offset));
1be5c7
-            res->check_errors++;
1be5c7
-            ret = refblock_offset;
1be5c7
-            goto fail;
1be5c7
-        }
1be5c7
+        if (on_disk_reftable_entries > refblock_index &&
1be5c7
+            on_disk_reftable[refblock_index])
1be5c7
+        {
1be5c7
+            /*
1be5c7
+             * We can get here after a `goto write_refblocks`: We have a
1be5c7
+             * reftable from a previous run, and the refblock is already
1be5c7
+             * allocated.  No need to allocate it again.
1be5c7
+             */
1be5c7
+            refblock_offset = on_disk_reftable[refblock_index];
1be5c7
+        } else {
1be5c7
+            int64_t refblock_cluster_index;
1be5c7
 
1be5c7
-        if (reftable_size <= refblock_index) {
1be5c7
-            uint32_t old_reftable_size = reftable_size;
1be5c7
-            uint64_t *new_on_disk_reftable;
1be5c7
+            /* Don't allocate a cluster in a refblock already written to disk */
1be5c7
+            if (first_free_cluster < refblock_start) {
1be5c7
+                first_free_cluster = refblock_start;
1be5c7
+            }
1be5c7
+            refblock_offset = alloc_clusters_imrt(bs, 1, refcount_table,
1be5c7
+                                                  nb_clusters,
1be5c7
+                                                  &first_free_cluster);
1be5c7
+            if (refblock_offset < 0) {
1be5c7
+                fprintf(stderr, "ERROR allocating refblock: %s\n",
1be5c7
+                        strerror(-refblock_offset));
1be5c7
+                return refblock_offset;
1be5c7
+            }
1be5c7
 
1be5c7
-            reftable_size = ROUND_UP((refblock_index + 1) * REFTABLE_ENTRY_SIZE,
1be5c7
-                                     s->cluster_size) / REFTABLE_ENTRY_SIZE;
1be5c7
-            new_on_disk_reftable = g_try_realloc(on_disk_reftable,
1be5c7
-                                                 reftable_size *
1be5c7
-                                                 REFTABLE_ENTRY_SIZE);
1be5c7
-            if (!new_on_disk_reftable) {
1be5c7
-                res->check_errors++;
1be5c7
-                ret = -ENOMEM;
1be5c7
-                goto fail;
1be5c7
+            refblock_cluster_index = refblock_offset / s->cluster_size;
1be5c7
+            if (refblock_cluster_index >= end_cluster) {
1be5c7
+                /*
1be5c7
+                 * We must write the refblock that holds this refblock's
1be5c7
+                 * refcount
1be5c7
+                 */
1be5c7
+                end_cluster = refblock_cluster_index + 1;
1be5c7
             }
1be5c7
-            on_disk_reftable = new_on_disk_reftable;
1be5c7
 
1be5c7
-            memset(on_disk_reftable + old_reftable_size, 0,
1be5c7
-                   (reftable_size - old_reftable_size) * REFTABLE_ENTRY_SIZE);
1be5c7
+            if (on_disk_reftable_entries <= refblock_index) {
1be5c7
+                on_disk_reftable_entries =
1be5c7
+                    ROUND_UP((refblock_index + 1) * REFTABLE_ENTRY_SIZE,
1be5c7
+                             s->cluster_size) / REFTABLE_ENTRY_SIZE;
1be5c7
+                on_disk_reftable =
1be5c7
+                    g_try_realloc(on_disk_reftable,
1be5c7
+                                  on_disk_reftable_entries *
1be5c7
+                                  REFTABLE_ENTRY_SIZE);
1be5c7
+                if (!on_disk_reftable) {
1be5c7
+                    return -ENOMEM;
1be5c7
+                }
1be5c7
 
1be5c7
-            /* The offset we have for the reftable is now no longer valid;
1be5c7
-             * this will leak that range, but we can easily fix that by running
1be5c7
-             * a leak-fixing check after this rebuild operation */
1be5c7
-            reftable_offset = -1;
1be5c7
-        } else {
1be5c7
-            assert(on_disk_reftable);
1be5c7
-        }
1be5c7
-        on_disk_reftable[refblock_index] = refblock_offset;
1be5c7
+                memset(on_disk_reftable + *on_disk_reftable_entries_ptr, 0,
1be5c7
+                       (on_disk_reftable_entries -
1be5c7
+                        *on_disk_reftable_entries_ptr) *
1be5c7
+                       REFTABLE_ENTRY_SIZE);
1be5c7
 
1be5c7
-        /* If this is apparently the last refblock (for now), try to squeeze the
1be5c7
-         * reftable in */
1be5c7
-        if (refblock_index == (*nb_clusters - 1) >> s->refcount_block_bits &&
1be5c7
-            reftable_offset < 0)
1be5c7
-        {
1be5c7
-            uint64_t reftable_clusters = size_to_clusters(s, reftable_size *
1be5c7
-                                                          REFTABLE_ENTRY_SIZE);
1be5c7
-            reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,
1be5c7
-                                                  refcount_table, nb_clusters,
1be5c7
-                                                  &first_free_cluster);
1be5c7
-            if (reftable_offset < 0) {
1be5c7
-                fprintf(stderr, "ERROR allocating reftable: %s\n",
1be5c7
-                        strerror(-reftable_offset));
1be5c7
-                res->check_errors++;
1be5c7
-                ret = reftable_offset;
1be5c7
-                goto fail;
1be5c7
+                *on_disk_reftable_ptr = on_disk_reftable;
1be5c7
+                *on_disk_reftable_entries_ptr = on_disk_reftable_entries;
1be5c7
+
1be5c7
+                reftable_grown = true;
1be5c7
+            } else {
1be5c7
+                assert(on_disk_reftable);
1be5c7
             }
1be5c7
+            on_disk_reftable[refblock_index] = refblock_offset;
1be5c7
         }
1be5c7
 
1be5c7
+        /* Refblock is allocated, write it to disk */
1be5c7
+
1be5c7
         ret = qcow2_pre_write_overlap_check(bs, 0, refblock_offset,
1be5c7
                                             s->cluster_size, false);
1be5c7
         if (ret < 0) {
1be5c7
             fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
1be5c7
-            goto fail;
1be5c7
+            return ret;
1be5c7
         }
1be5c7
 
1be5c7
-        /* The size of *refcount_table is always cluster-aligned, therefore the
1be5c7
-         * write operation will not overflow */
1be5c7
+        /*
1be5c7
+         * The refblock is simply a slice of *refcount_table.
1be5c7
+         * Note that the size of *refcount_table is always aligned to
1be5c7
+         * whole clusters, so the write operation will not result in
1be5c7
+         * out-of-bounds accesses.
1be5c7
+         */
1be5c7
         on_disk_refblock = (void *)((char *) *refcount_table +
1be5c7
                                     refblock_index * s->cluster_size);
1be5c7
 
1be5c7
@@ -2547,23 +2576,99 @@ write_refblocks:
1be5c7
                           s->cluster_size);
1be5c7
         if (ret < 0) {
1be5c7
             fprintf(stderr, "ERROR writing refblock: %s\n", strerror(-ret));
1be5c7
-            goto fail;
1be5c7
+            return ret;
1be5c7
         }
1be5c7
 
1be5c7
-        /* Go to the end of this refblock */
1be5c7
+        /* This refblock is done, skip to its end */
1be5c7
         cluster = refblock_start + s->refcount_block_size - 1;
1be5c7
     }
1be5c7
 
1be5c7
-    if (reftable_offset < 0) {
1be5c7
-        uint64_t post_refblock_start, reftable_clusters;
1be5c7
+    return reftable_grown;
1be5c7
+}
1be5c7
+
1be5c7
+/*
1be5c7
+ * Creates a new refcount structure based solely on the in-memory information
1be5c7
+ * given through *refcount_table (this in-memory information is basically just
1be5c7
+ * the concatenation of all refblocks).  All necessary allocations will be
1be5c7
+ * reflected in that array.
1be5c7
+ *
1be5c7
+ * On success, the old refcount structure is leaked (it will be covered by the
1be5c7
+ * new refcount structure).
1be5c7
+ */
1be5c7
+static int rebuild_refcount_structure(BlockDriverState *bs,
1be5c7
+                                      BdrvCheckResult *res,
1be5c7
+                                      void **refcount_table,
1be5c7
+                                      int64_t *nb_clusters)
1be5c7
+{
1be5c7
+    BDRVQcow2State *s = bs->opaque;
1be5c7
+    int64_t reftable_offset = -1;
1be5c7
+    int64_t reftable_length = 0;
1be5c7
+    int64_t reftable_clusters;
1be5c7
+    int64_t refblock_index;
1be5c7
+    uint32_t on_disk_reftable_entries = 0;
1be5c7
+    uint64_t *on_disk_reftable = NULL;
1be5c7
+    int ret = 0;
1be5c7
+    int reftable_size_changed = 0;
1be5c7
+    struct {
1be5c7
+        uint64_t reftable_offset;
1be5c7
+        uint32_t reftable_clusters;
1be5c7
+    } QEMU_PACKED reftable_offset_and_clusters;
1be5c7
+
1be5c7
+    qcow2_cache_empty(bs, s->refcount_block_cache);
1be5c7
+
1be5c7
+    /*
1be5c7
+     * For each refblock containing entries, we try to allocate a
1be5c7
+     * cluster (in the in-memory refcount table) and write its offset
1be5c7
+     * into on_disk_reftable[].  We then write the whole refblock to
1be5c7
+     * disk (as a slice of the in-memory refcount table).
1be5c7
+     * This is done by rebuild_refcounts_write_refblocks().
1be5c7
+     *
1be5c7
+     * Once we have scanned all clusters, we try to find space for the
1be5c7
+     * reftable.  This will dirty the in-memory refcount table (i.e.
1be5c7
+     * make it differ from the refblocks we have already written), so we
1be5c7
+     * need to run rebuild_refcounts_write_refblocks() again for the
1be5c7
+     * range of clusters where the reftable has been allocated.
1be5c7
+     *
1be5c7
+     * This second run might make the reftable grow again, in which case
1be5c7
+     * we will need to allocate another space for it, which is why we
1be5c7
+     * repeat all this until the reftable stops growing.
1be5c7
+     *
1be5c7
+     * (This loop will terminate, because with every cluster the
1be5c7
+     * reftable grows, it can accomodate a multitude of more refcounts,
1be5c7
+     * so that at some point this must be able to cover the reftable
1be5c7
+     * and all refblocks describing it.)
1be5c7
+     *
1be5c7
+     * We then convert the reftable to big-endian and write it to disk.
1be5c7
+     *
1be5c7
+     * Note that we never free any reftable allocations.  Doing so would
1be5c7
+     * needlessly complicate the algorithm: The eventual second check
1be5c7
+     * run we do will clean up all leaks we have caused.
1be5c7
+     */
1be5c7
+
1be5c7
+    reftable_size_changed =
1be5c7
+        rebuild_refcounts_write_refblocks(bs, refcount_table, nb_clusters,
1be5c7
+                                          0, *nb_clusters,
1be5c7
+                                          &on_disk_reftable,
1be5c7
+                                          &on_disk_reftable_entries);
1be5c7
+    if (reftable_size_changed < 0) {
1be5c7
+        res->check_errors++;
1be5c7
+        ret = reftable_size_changed;
1be5c7
+        goto fail;
1be5c7
+    }
1be5c7
+
1be5c7
+    /*
1be5c7
+     * There was no reftable before, so rebuild_refcounts_write_refblocks()
1be5c7
+     * must have increased its size (from 0 to something).
1be5c7
+     */
1be5c7
+    assert(reftable_size_changed);
1be5c7
+
1be5c7
+    do {
1be5c7
+        int64_t reftable_start_cluster, reftable_end_cluster;
1be5c7
+        int64_t first_free_cluster = 0;
1be5c7
+
1be5c7
+        reftable_length = on_disk_reftable_entries * REFTABLE_ENTRY_SIZE;
1be5c7
+        reftable_clusters = size_to_clusters(s, reftable_length);
1be5c7
 
1be5c7
-        post_refblock_start = ROUND_UP(*nb_clusters, s->refcount_block_size);
1be5c7
-        reftable_clusters =
1be5c7
-            size_to_clusters(s, reftable_size * REFTABLE_ENTRY_SIZE);
1be5c7
-        /* Not pretty but simple */
1be5c7
-        if (first_free_cluster < post_refblock_start) {
1be5c7
-            first_free_cluster = post_refblock_start;
1be5c7
-        }
1be5c7
         reftable_offset = alloc_clusters_imrt(bs, reftable_clusters,
1be5c7
                                               refcount_table, nb_clusters,
1be5c7
                                               &first_free_cluster);
1be5c7
@@ -2575,24 +2680,55 @@ write_refblocks:
1be5c7
             goto fail;
1be5c7
         }
1be5c7
 
1be5c7
-        goto write_refblocks;
1be5c7
-    }
1be5c7
+        /*
1be5c7
+         * We need to update the affected refblocks, so re-run the
1be5c7
+         * write_refblocks loop for the reftable's range of clusters.
1be5c7
+         */
1be5c7
+        assert(offset_into_cluster(s, reftable_offset) == 0);
1be5c7
+        reftable_start_cluster = reftable_offset / s->cluster_size;
1be5c7
+        reftable_end_cluster = reftable_start_cluster + reftable_clusters;
1be5c7
+        reftable_size_changed =
1be5c7
+            rebuild_refcounts_write_refblocks(bs, refcount_table, nb_clusters,
1be5c7
+                                              reftable_start_cluster,
1be5c7
+                                              reftable_end_cluster,
1be5c7
+                                              &on_disk_reftable,
1be5c7
+                                              &on_disk_reftable_entries);
1be5c7
+        if (reftable_size_changed < 0) {
1be5c7
+            res->check_errors++;
1be5c7
+            ret = reftable_size_changed;
1be5c7
+            goto fail;
1be5c7
+        }
1be5c7
+
1be5c7
+        /*
1be5c7
+         * If the reftable size has changed, we will need to find a new
1be5c7
+         * allocation, repeating the loop.
1be5c7
+         */
1be5c7
+    } while (reftable_size_changed);
1be5c7
 
1be5c7
-    for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {
1be5c7
+    /* The above loop must have run at least once */
1be5c7
+    assert(reftable_offset >= 0);
1be5c7
+
1be5c7
+    /*
1be5c7
+     * All allocations are done, all refblocks are written, convert the
1be5c7
+     * reftable to big-endian and write it to disk.
1be5c7
+     */
1be5c7
+
1be5c7
+    for (refblock_index = 0; refblock_index < on_disk_reftable_entries;
1be5c7
+         refblock_index++)
1be5c7
+    {
1be5c7
         cpu_to_be64s(&on_disk_reftable[refblock_index]);
1be5c7
     }
1be5c7
 
1be5c7
-    ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset,
1be5c7
-                                        reftable_size * REFTABLE_ENTRY_SIZE,
1be5c7
+    ret = qcow2_pre_write_overlap_check(bs, 0, reftable_offset, reftable_length,
1be5c7
                                         false);
1be5c7
     if (ret < 0) {
1be5c7
         fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
1be5c7
         goto fail;
1be5c7
     }
1be5c7
 
1be5c7
-    assert(reftable_size < INT_MAX / REFTABLE_ENTRY_SIZE);
1be5c7
+    assert(reftable_length < INT_MAX);
1be5c7
     ret = bdrv_pwrite(bs->file, reftable_offset, on_disk_reftable,
1be5c7
-                      reftable_size * REFTABLE_ENTRY_SIZE);
1be5c7
+                      reftable_length);
1be5c7
     if (ret < 0) {
1be5c7
         fprintf(stderr, "ERROR writing reftable: %s\n", strerror(-ret));
1be5c7
         goto fail;
1be5c7
@@ -2601,7 +2737,7 @@ write_refblocks:
1be5c7
     /* Enter new reftable into the image header */
1be5c7
     reftable_offset_and_clusters.reftable_offset = cpu_to_be64(reftable_offset);
1be5c7
     reftable_offset_and_clusters.reftable_clusters =
1be5c7
-        cpu_to_be32(size_to_clusters(s, reftable_size * REFTABLE_ENTRY_SIZE));
1be5c7
+        cpu_to_be32(reftable_clusters);
1be5c7
     ret = bdrv_pwrite_sync(bs->file,
1be5c7
                            offsetof(QCowHeader, refcount_table_offset),
1be5c7
                            &reftable_offset_and_clusters,
1be5c7
@@ -2611,12 +2747,14 @@ write_refblocks:
1be5c7
         goto fail;
1be5c7
     }
1be5c7
 
1be5c7
-    for (refblock_index = 0; refblock_index < reftable_size; refblock_index++) {
1be5c7
+    for (refblock_index = 0; refblock_index < on_disk_reftable_entries;
1be5c7
+         refblock_index++)
1be5c7
+    {
1be5c7
         be64_to_cpus(&on_disk_reftable[refblock_index]);
1be5c7
     }
1be5c7
     s->refcount_table = on_disk_reftable;
1be5c7
     s->refcount_table_offset = reftable_offset;
1be5c7
-    s->refcount_table_size = reftable_size;
1be5c7
+    s->refcount_table_size = on_disk_reftable_entries;
1be5c7
     update_max_refcount_table_index(s);
1be5c7
 
1be5c7
     return 0;
1be5c7
-- 
1be5c7
2.27.0
1be5c7