Commit a5675f39 authored by Alberto Garcia's avatar Alberto Garcia Committed by Max Reitz
Browse files

qcow2: Fix preallocation on images with unaligned sizes



When resizing an image with qcow2_co_truncate() using the falloc or
full preallocation modes the code assumes that both the old and new
sizes are cluster-aligned.

There are two problems with this:

  1) The calculation of how many clusters are involved does not always
     get the right result.

     Example: creating a 60KB image and resizing it (with
     preallocation=full) to 80KB won't allocate the second cluster.

  2) No copy-on-write is performed, so in the previous example if
     there is a backing file then the first 60KB of the first cluster
     won't be filled with data from the backing file.

This patch fixes both issues.

Signed-off-by: default avatarAlberto Garcia <berto@igalia.com>
Message-Id: <20200617140036.20311-1-berto@igalia.com>
Reviewed-by: default avatarEric Blake <eblake@redhat.com>
Signed-off-by: default avatarMax Reitz <mreitz@redhat.com>
parent e8de7ba9
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -4239,8 +4239,8 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
            old_file_size = ROUND_UP(old_file_size, s->cluster_size);
        }

        nb_new_data_clusters = DIV_ROUND_UP(offset - old_length,
                                            s->cluster_size);
        nb_new_data_clusters = (ROUND_UP(offset, s->cluster_size) -
            start_of_cluster(s, old_length)) >> s->cluster_bits;

        /* This is an overestimation; we will not actually allocate space for
         * these in the file but just make sure the new refcount structures are
@@ -4317,10 +4317,21 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
            int64_t nb_clusters = MIN(
                nb_new_data_clusters,
                s->l2_slice_size - offset_to_l2_slice_index(s, guest_offset));
            QCowL2Meta allocation = {
            unsigned cow_start_length = offset_into_cluster(s, guest_offset);
            QCowL2Meta allocation;
            guest_offset = start_of_cluster(s, guest_offset);
            allocation = (QCowL2Meta) {
                .offset       = guest_offset,
                .alloc_offset = host_offset,
                .nb_clusters  = nb_clusters,
                .cow_start    = {
                    .offset       = 0,
                    .nb_bytes     = cow_start_length,
                },
                .cow_end      = {
                    .offset       = nb_clusters << s->cluster_bits,
                    .nb_bytes     = 0,
                },
            };
            qemu_co_queue_init(&allocation.dependent_requests);

+24 −0
Original line number Diff line number Diff line
@@ -164,6 +164,30 @@ for GROWTH_SIZE in 16 48 80; do
done
done

# Test image resizing using preallocation and unaligned offsets
$QEMU_IMG create -f raw "$TEST_IMG.base" 128k | _filter_img_create
$QEMU_IO -c 'write -q -P 1 0 128k' -f raw "$TEST_IMG.base"
for orig_size in 31k 33k; do
    echo "--- Resizing image from $orig_size to 96k ---"
    _make_test_img -F raw -b "$TEST_IMG.base" -o cluster_size=64k "$orig_size"
    $QEMU_IMG resize -f "$IMGFMT" --preallocation=full "$TEST_IMG" 96k
    # The first part of the image should contain data from the backing file
    $QEMU_IO -c "read -q -P 1 0 ${orig_size}" "$TEST_IMG"
    # The resized part of the image should contain zeroes
    $QEMU_IO -c "read -q -P 0 ${orig_size} 63k" "$TEST_IMG"
    # If the image does not have an external data file we can also verify its
    # actual size. The resized image should have 7 clusters:
    # header, L1 table, L2 table, refcount table, refcount block, 2 data clusters
    if ! _get_data_file "$TEST_IMG" > /dev/null; then
        expected_file_length=$((65536 * 7))
        file_length=$(stat -c '%s' "$TEST_IMG_FILE")
        if [ "$file_length" != "$expected_file_length" ]; then
            echo "ERROR: file length $file_length (expected $expected_file_length)"
        fi
    fi
    echo
done

# success, all done
echo '*** done'
rm -f $seq.full
+9 −0
Original line number Diff line number Diff line
@@ -767,4 +767,13 @@ wrote 2048000/2048000 bytes at offset 0
wrote 81920/81920 bytes at offset 2048000
80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)

Formatting 'TEST_DIR/t.IMGFMT.base', fmt=raw size=131072
--- Resizing image from 31k to 96k ---
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=31744 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw
Image resized.

--- Resizing image from 33k to 96k ---
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33792 backing_file=TEST_DIR/t.IMGFMT.base backing_fmt=raw
Image resized.

*** done