mirror of
https://github.com/qemu/qemu.git
synced 2025-08-15 13:47:03 +00:00
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: Alberto Garcia <berto@igalia.com> Message-Id: <20200617140036.20311-1-berto@igalia.com> Reviewed-by: Eric Blake <eblake@redhat.com> Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
parent
e8de7ba9ea
commit
a5675f3901
@ -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);
|
old_file_size = ROUND_UP(old_file_size, s->cluster_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
nb_new_data_clusters = DIV_ROUND_UP(offset - old_length,
|
nb_new_data_clusters = (ROUND_UP(offset, s->cluster_size) -
|
||||||
s->cluster_size);
|
start_of_cluster(s, old_length)) >> s->cluster_bits;
|
||||||
|
|
||||||
/* This is an overestimation; we will not actually allocate space for
|
/* This is an overestimation; we will not actually allocate space for
|
||||||
* these in the file but just make sure the new refcount structures are
|
* 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(
|
int64_t nb_clusters = MIN(
|
||||||
nb_new_data_clusters,
|
nb_new_data_clusters,
|
||||||
s->l2_slice_size - offset_to_l2_slice_index(s, guest_offset));
|
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,
|
.offset = guest_offset,
|
||||||
.alloc_offset = host_offset,
|
.alloc_offset = host_offset,
|
||||||
.nb_clusters = nb_clusters,
|
.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);
|
qemu_co_queue_init(&allocation.dependent_requests);
|
||||||
|
|
||||||
|
@ -164,6 +164,30 @@ for GROWTH_SIZE in 16 48 80; do
|
|||||||
done
|
done
|
||||||
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
|
# success, all done
|
||||||
echo '*** done'
|
echo '*** done'
|
||||||
rm -f $seq.full
|
rm -f $seq.full
|
||||||
|
@ -767,4 +767,13 @@ wrote 2048000/2048000 bytes at offset 0
|
|||||||
wrote 81920/81920 bytes at offset 2048000
|
wrote 81920/81920 bytes at offset 2048000
|
||||||
80 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
|
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
|
*** done
|
||||||
|
Loading…
Reference in New Issue
Block a user