mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-24 03:59:52 +00:00
8e958260c5
This function preallocates metadata structures and then extends the image to its new size, but that new size calculation is wrong because it doesn't take into account that the host_offset variable is always cluster-aligned. This problem can be reproduced with preallocation=metadata when the original size is not cluster-aligned but the new size is. In this case the final image size will be shorter than expected. qemu-img create -f qcow2 img.qcow2 31k qemu-img resize --preallocation=metadata img.qcow2 128k Signed-off-by: Alberto Garcia <berto@igalia.com> Message-Id: <adeb8b059917b141d5f5b3bd2a016262d3052c79.1599833007.git.berto@igalia.com> Reviewed-by: Max Reitz <mreitz@redhat.com> [mreitz: Mark compat=0.10 unsupported for iotest 125] Signed-off-by: Max Reitz <mreitz@redhat.com>
203 lines
7.5 KiB
Bash
Executable File
203 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Test preallocated growth of qcow2 images
|
|
#
|
|
# Copyright (C) 2017 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/>.
|
|
#
|
|
|
|
# creator
|
|
owner=mreitz@redhat.com
|
|
|
|
seq=$(basename $0)
|
|
echo "QA output created by $seq"
|
|
|
|
status=1 # failure is the default!
|
|
|
|
_cleanup()
|
|
{
|
|
_cleanup_test_img
|
|
}
|
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
|
|
|
get_image_size_on_host()
|
|
{
|
|
echo $(($(stat -c '%b * %B' "$TEST_IMG_FILE")))
|
|
}
|
|
|
|
# get standard environment and filters
|
|
. ./common.rc
|
|
. ./common.filter
|
|
|
|
_supported_fmt qcow2
|
|
_supported_proto file
|
|
# Growing a file with a backing file (without preallocation=full or
|
|
# =falloc) requires zeroing the newly added area, which is impossible
|
|
# to do quickly for v2 images, and hence is unsupported.
|
|
_unsupported_imgopts 'compat=0.10'
|
|
|
|
if [ -z "$TEST_IMG_FILE" ]; then
|
|
TEST_IMG_FILE=$TEST_IMG
|
|
fi
|
|
|
|
# Test whether we are running on a broken XFS version. There is this
|
|
# bug:
|
|
|
|
# $ rm -f foo
|
|
# $ touch foo
|
|
# $ block_size=4096 # Your FS's block size
|
|
# $ fallocate -o $((block_size / 2)) -l $block_size foo
|
|
# $ LANG=C xfs_bmap foo | grep hole
|
|
# 1: [8..15]: hole
|
|
#
|
|
# The problem is that the XFS driver rounds down the offset and
|
|
# rounds up the length to the block size, but independently. As
|
|
# such, it only allocates the first block in the example above,
|
|
# even though it should allocate the first two blocks (because our
|
|
# request is to fallocate something that touches both the first
|
|
# two blocks).
|
|
#
|
|
# This means that when you then write to the beginning of the
|
|
# second block, the disk usage of the first two blocks grows.
|
|
#
|
|
# That is precisely what fallocate() promises, though: That when you
|
|
# write to an area that you have fallocated, no new blocks will have
|
|
# to be allocated.
|
|
|
|
touch "$TEST_IMG_FILE"
|
|
# Assuming there is no FS with a block size greater than 64k
|
|
fallocate -o 65535 -l 2 "$TEST_IMG_FILE"
|
|
len0=$(get_image_size_on_host)
|
|
|
|
# Write to something that in theory we have just fallocated
|
|
# (Thus, the on-disk size should not increase)
|
|
poke_file "$TEST_IMG_FILE" 65536 42
|
|
len1=$(get_image_size_on_host)
|
|
|
|
if [ $len1 -gt $len0 ]; then
|
|
_notrun "the test filesystem's fallocate() is broken"
|
|
fi
|
|
|
|
rm -f "$TEST_IMG_FILE"
|
|
|
|
# Generally, we create some image with or without existing preallocation and
|
|
# then resize it. Then we write some data into the image and verify that its
|
|
# size does not change if we have used preallocation.
|
|
|
|
# With a cluster size of 512 B, one L2 table covers 64 * 512 B = 32 kB.
|
|
# One cluster of the L1 table covers 64 * 32 kB = 2 MB.
|
|
# There are multiple cases we want to test:
|
|
# (1) Grow an image without having to allocate a new L2 table.
|
|
# (2) Grow an image, having to allocate a new L2 table.
|
|
# (3) Grow an image, having to grow the L1 table.
|
|
# Therefore, we create an image that is 48 kB below 2 MB. Then:
|
|
# (1) We resize it to 2 MB - 32 kB. (+ 16 kB)
|
|
# (2) We resize it to 2 MB. (+ 48 kB)
|
|
# (3) We resize it to 2 MB + 32 kB. (+ 80 kB)
|
|
|
|
# in B
|
|
CREATION_SIZE=$((2 * 1024 * 1024 - 48 * 1024))
|
|
|
|
# 512 is the actual test -- but it's good to test 64k as well, just to be sure.
|
|
for cluster_size in 512 64k; do
|
|
# in kB
|
|
for GROWTH_SIZE in 16 48 80; do
|
|
for create_mode in off metadata falloc full; do
|
|
for growth_mode in off metadata falloc full; do
|
|
echo "--- cluster_size=$cluster_size growth_size=$GROWTH_SIZE create_mode=$create_mode growth_mode=$growth_mode ---"
|
|
|
|
_make_test_img -o "preallocation=$create_mode,cluster_size=$cluster_size" ${CREATION_SIZE}
|
|
$QEMU_IMG resize -f "$IMGFMT" --preallocation=$growth_mode "$TEST_IMG" +${GROWTH_SIZE}K
|
|
|
|
host_size_0=$(get_image_size_on_host)
|
|
file_length_0=$(stat -c '%s' "$TEST_IMG_FILE")
|
|
|
|
$QEMU_IO -c "write 0 $CREATION_SIZE" "$TEST_IMG" | _filter_qemu_io
|
|
|
|
host_size_1=$(get_image_size_on_host)
|
|
file_length_1=$(stat -c '%s' "$TEST_IMG_FILE")
|
|
|
|
$QEMU_IO -c "write $CREATION_SIZE ${GROWTH_SIZE}K" "$TEST_IMG" | _filter_qemu_io
|
|
|
|
host_size_2=$(get_image_size_on_host)
|
|
file_length_2=$(stat -c '%s' "$TEST_IMG_FILE")
|
|
|
|
# Test creation preallocation: Compare #0 against #1
|
|
if [ $create_mode != off ]; then
|
|
# The image length should not have grown
|
|
if [ $file_length_1 -gt $file_length_0 ]; then
|
|
echo "ERROR (create): Image length has grown from $file_length_0 to $file_length_1"
|
|
fi
|
|
if [ $create_mode != metadata ]; then
|
|
# The host size should not have grown either
|
|
if [ $host_size_1 -gt $host_size_0 ]; then
|
|
echo "ERROR (create): Host size has grown from $host_size_0 to $host_size_1"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Test resize preallocation: Compare #2 against #1
|
|
if [ $growth_mode != off ]; then
|
|
# The image length should not have grown
|
|
if [ $file_length_2 -gt $file_length_1 ]; then
|
|
echo "ERROR (grow): Image length has grown from $file_length_1 to $file_length_2"
|
|
fi
|
|
if [ $growth_mode != metadata ]; then
|
|
# The host size should not have grown either
|
|
if [ $host_size_2 -gt $host_size_1 ]; then
|
|
echo "ERROR (grow): Host size has grown from $host_size_1 to $host_size_2"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
echo
|
|
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
|
|
for dst_size in 96k 128k; do
|
|
for prealloc in metadata full; do
|
|
echo "--- Resizing image from $orig_size to $dst_size (preallocation=$prealloc) ---"
|
|
_make_test_img -F raw -b "$TEST_IMG.base" -o cluster_size=64k "$orig_size"
|
|
$QEMU_IMG resize -f "$IMGFMT" --preallocation="$prealloc" "$TEST_IMG" "$dst_size"
|
|
# 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
|
|
done
|
|
done
|
|
|
|
# success, all done
|
|
echo '*** done'
|
|
rm -f $seq.full
|
|
status=0
|