mirror of
https://github.com/qemu/qemu.git
synced 2026-02-04 02:24:38 +00:00
During a rebase operation data is copied from the backing chain into the target image using a loop, and each iteration looks for a contiguous region of allocated data of at most IO_BUF_SIZE (2 MB). Once that region is found, and in order to avoid partial writes, its boundaries are extended so they are aligned to the (sub)clusters of the target image (see commit12df580b). This operation can however result in a region that exceeds the maximum allowed IO_BUF_SIZE, crashing qemu-img. This can be easily reproduced when the source image has a smaller cluster size than the target image: base <- int <- active $ qemu-img create -f qcow2 base.qcow2 4M $ qemu-img create -f qcow2 -F qcow2 -b base.qcow2 -o cluster_size=1M int.qcow2 $ qemu-img create -f qcow2 -F qcow2 -b int.qcow2 -o cluster_size=2M active.qcow2 $ qemu-io -c "write -P 0xff 1M 2M" int.qcow2 $ qemu-img rebase -F qcow2 -b base.qcow2 active.qcow2 qemu-img: qemu-img.c:4102: img_rebase: Assertion `written + pnum <= IO_BUF_SIZE' failed. Aborted Cc: qemu-stable <qemu-stable@nongnu.org> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3174 Fixes:12df580b3b("qemu-img: rebase: avoid unnecessary COW operations") Signed-off-by: Alberto Garcia <berto@igalia.com> Message-ID: <20251107091834.383781-1-berto@igalia.com> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
370 lines
11 KiB
Bash
Executable File
370 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# group: rw backing auto quick
|
|
#
|
|
# Rebasing COW images
|
|
#
|
|
# Copyright (C) 2009 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=kwolf@redhat.com
|
|
|
|
seq=`basename $0`
|
|
echo "QA output created by $seq"
|
|
|
|
status=1 # failure is the default!
|
|
|
|
_cleanup()
|
|
{
|
|
_cleanup_test_img
|
|
_rm_test_img "$TEST_DIR/t.$IMGFMT.base_old"
|
|
_rm_test_img "$TEST_DIR/t.$IMGFMT.base_new"
|
|
|
|
_rm_test_img "$TEST_DIR/subdir/t.$IMGFMT"
|
|
_rm_test_img "$TEST_DIR/subdir/t.$IMGFMT.base_old"
|
|
_rm_test_img "$TEST_DIR/subdir/t.$IMGFMT.base_new"
|
|
rmdir "$TEST_DIR/subdir" 2> /dev/null
|
|
}
|
|
trap "_cleanup; exit \$status" 0 1 2 3 15
|
|
|
|
# get standard environment, filters and checks
|
|
. ./common.rc
|
|
. ./common.filter
|
|
. ./common.pattern
|
|
|
|
# Currently only qcow2 and qed support rebasing
|
|
_supported_fmt qcow2 qed
|
|
_supported_proto file
|
|
_supported_os Linux
|
|
|
|
CLUSTER_SIZE=65536
|
|
|
|
# Cluster allocations to be tested:
|
|
#
|
|
# Backing (old) 11 -- 11 -- 11 -- 11 --
|
|
# Backing (new) 22 22 -- -- 22 22 -- --
|
|
# COW image 33 33 33 33 -- -- -- --
|
|
#
|
|
# The pattern is written twice to have both an alloc -> non-alloc and a
|
|
# non-alloc -> alloc transition in the COW image.
|
|
|
|
echo "Creating backing file"
|
|
echo
|
|
|
|
TEST_IMG_SAVE="$TEST_IMG"
|
|
TEST_IMG="$TEST_IMG.base_old"
|
|
|
|
_make_test_img 1G
|
|
io_pattern writev 0 $CLUSTER_SIZE $((2 * CLUSTER_SIZE)) 8 0x11
|
|
|
|
TEST_IMG="$TEST_IMG_SAVE.base_new"
|
|
|
|
echo "Creating new backing file"
|
|
echo
|
|
|
|
_make_test_img 1G
|
|
io_pattern writev 0 $((2 * CLUSTER_SIZE)) $((4 * CLUSTER_SIZE)) 4 0x22
|
|
|
|
|
|
TEST_IMG="$TEST_IMG_SAVE"
|
|
|
|
echo "Creating COW image"
|
|
echo
|
|
|
|
_make_test_img -b "$TEST_IMG.base_old" -F $IMGFMT 1G
|
|
io_pattern writev 0 $((4 * CLUSTER_SIZE)) 0 1 0x33
|
|
io_pattern writev $((8 * CLUSTER_SIZE)) $((4 * CLUSTER_SIZE)) 0 1 0x33
|
|
|
|
echo "Read before the rebase to make sure everything is set up correctly"
|
|
echo
|
|
io_pattern readv $((0 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((1 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((2 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((3 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((4 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((5 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((6 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((7 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((8 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((9 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((10 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((11 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((12 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((13 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((14 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((15 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
|
|
echo
|
|
echo Rebase and test again
|
|
echo
|
|
$QEMU_IMG rebase -b "$TEST_IMG.base_new" -F $IMGFMT "$TEST_IMG"
|
|
io_pattern readv $((0 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((1 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((2 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((3 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((4 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((5 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((6 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((7 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((8 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((9 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((10 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((11 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x33
|
|
io_pattern readv $((12 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((13 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
io_pattern readv $((14 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x11
|
|
io_pattern readv $((15 * CLUSTER_SIZE)) $CLUSTER_SIZE 0 1 0x00
|
|
|
|
echo
|
|
echo "=== Test rebase in a subdirectory of the working directory ==="
|
|
echo
|
|
|
|
# Clean up the old images beforehand so they do not interfere with
|
|
# this test
|
|
_cleanup
|
|
|
|
mkdir "$TEST_DIR/subdir"
|
|
|
|
# Relative to the overlay
|
|
BASE_OLD_OREL="t.$IMGFMT.base_old"
|
|
BASE_NEW_OREL="t.$IMGFMT.base_new"
|
|
|
|
# Relative to $TEST_DIR (which is going to be our working directory)
|
|
OVERLAY_WREL="subdir/t.$IMGFMT"
|
|
|
|
BASE_OLD="$TEST_DIR/subdir/$BASE_OLD_OREL"
|
|
BASE_NEW="$TEST_DIR/subdir/$BASE_NEW_OREL"
|
|
OVERLAY="$TEST_DIR/$OVERLAY_WREL"
|
|
|
|
# Test done here:
|
|
#
|
|
# Backing (old): 11 11 -- 11
|
|
# Backing (new): -- 22 22 11
|
|
# Overlay: -- -- -- --
|
|
#
|
|
# Rebasing works, we have verified that above. Here, we just want to
|
|
# see that rebasing is done for the correct target backing file.
|
|
|
|
TEST_IMG=$BASE_OLD _make_test_img 1M
|
|
TEST_IMG=$BASE_NEW _make_test_img 1M
|
|
TEST_IMG=$OVERLAY _make_test_img -b "$BASE_OLD_OREL" -F $IMGFMT 1M
|
|
|
|
echo
|
|
|
|
$QEMU_IO "$BASE_OLD" \
|
|
-c "write -P 0x11 $((0 * CLUSTER_SIZE)) $((2 * CLUSTER_SIZE))" \
|
|
-c "write -P 0x11 $((3 * CLUSTER_SIZE)) $((1 * CLUSTER_SIZE))" \
|
|
| _filter_qemu_io
|
|
|
|
$QEMU_IO "$BASE_NEW" \
|
|
-c "write -P 0x22 $((1 * CLUSTER_SIZE)) $((2 * CLUSTER_SIZE))" \
|
|
-c "write -P 0x11 $((3 * CLUSTER_SIZE)) $((1 * CLUSTER_SIZE))" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
|
|
pushd "$TEST_DIR" >/dev/null
|
|
$QEMU_IMG rebase -f "$IMGFMT" -b "$BASE_NEW_OREL" -F $IMGFMT "$OVERLAY_WREL"
|
|
popd >/dev/null
|
|
|
|
# Verify the backing path is correct
|
|
TEST_IMG=$OVERLAY _img_info | grep '^backing file:'
|
|
|
|
echo
|
|
|
|
# Verify the data is correct
|
|
$QEMU_IO "$OVERLAY" \
|
|
-c "read -P 0x11 $((0 * CLUSTER_SIZE)) $CLUSTER_SIZE" \
|
|
-c "read -P 0x11 $((1 * CLUSTER_SIZE)) $CLUSTER_SIZE" \
|
|
-c "read -P 0x00 $((2 * CLUSTER_SIZE)) $CLUSTER_SIZE" \
|
|
-c "read -P 0x11 $((3 * CLUSTER_SIZE)) $CLUSTER_SIZE" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
|
|
# Verify that cluster #3 is not allocated (because it is the same in
|
|
# $BASE_OLD and $BASE_NEW)
|
|
$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
|
|
|
|
# Check that rebase within the chain is working when
|
|
# overlay_size > old_backing_size
|
|
#
|
|
# base_new <-- base_old <-- overlay
|
|
#
|
|
# Backing (new): 11 11 11 11 11
|
|
# Backing (old): 22 22 22 22
|
|
# Overlay: -- -- -- -- --
|
|
#
|
|
# As a result, overlay should contain data identical to base_old, with the
|
|
# last cluster remaining unallocated.
|
|
|
|
echo
|
|
echo "=== Test rebase within one backing chain ==="
|
|
echo
|
|
|
|
echo "Creating backing chain"
|
|
echo
|
|
|
|
TEST_IMG=$BASE_NEW _make_test_img $(( CLUSTER_SIZE * 5 ))
|
|
TEST_IMG=$BASE_OLD _make_test_img -b "$BASE_NEW" -F $IMGFMT \
|
|
$(( CLUSTER_SIZE * 4 ))
|
|
TEST_IMG=$OVERLAY _make_test_img -b "$BASE_OLD" -F $IMGFMT \
|
|
$(( CLUSTER_SIZE * 5 ))
|
|
|
|
echo
|
|
echo "Fill backing files with data"
|
|
echo
|
|
|
|
$QEMU_IO "$BASE_NEW" -c "write -P 0x11 0 $(( CLUSTER_SIZE * 5 ))" \
|
|
| _filter_qemu_io
|
|
$QEMU_IO "$BASE_OLD" -c "write -P 0x22 0 $(( CLUSTER_SIZE * 4 ))" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
echo "Check the last cluster is zeroed in overlay before the rebase"
|
|
echo
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x00 $(( CLUSTER_SIZE * 4 )) $CLUSTER_SIZE" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
echo "Rebase onto another image in the same chain"
|
|
echo
|
|
|
|
$QEMU_IMG rebase -b "$BASE_NEW" -F $IMGFMT "$OVERLAY"
|
|
|
|
echo "Verify that data is read the same before and after rebase"
|
|
echo
|
|
|
|
# Verify the first 4 clusters are still read the same as in the old base
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x22 0 $(( CLUSTER_SIZE * 4 ))" \
|
|
| _filter_qemu_io
|
|
# Verify the last cluster still reads as zeroes
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x00 $(( CLUSTER_SIZE * 4 )) $CLUSTER_SIZE" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
|
|
# Check that rebase within the chain is working when
|
|
# overlay cluster size > backings cluster size
|
|
# (here overlay cluster size == 2 * backings cluster size)
|
|
#
|
|
# base_new <-- base_old <-- overlay
|
|
#
|
|
# Backing (new): -- -- -- -- -- --
|
|
# Backing (old): -- 11 -- -- 22 --
|
|
# Overlay: |-- --|-- --|-- --|
|
|
#
|
|
# We should end up having 1st and 3rd cluster allocated, and their halves
|
|
# being read as zeroes.
|
|
|
|
echo
|
|
echo "=== Test rebase with different cluster sizes ==="
|
|
echo
|
|
|
|
echo "Creating backing chain"
|
|
echo
|
|
|
|
TEST_IMG=$BASE_NEW _make_test_img $(( CLUSTER_SIZE * 6 ))
|
|
TEST_IMG=$BASE_OLD _make_test_img -b "$BASE_NEW" -F $IMGFMT \
|
|
$(( CLUSTER_SIZE * 6 ))
|
|
CLUSTER_SIZE=$(( CLUSTER_SIZE * 2 )) TEST_IMG=$OVERLAY \
|
|
_make_test_img -b "$BASE_OLD" -F $IMGFMT $(( CLUSTER_SIZE * 6 ))
|
|
|
|
TEST_IMG=$OVERLAY _img_info | grep -v '^backing file format:'
|
|
|
|
echo
|
|
echo "Fill backing files with data"
|
|
echo
|
|
|
|
$QEMU_IO "$BASE_OLD" -c "write -P 0x11 $CLUSTER_SIZE $CLUSTER_SIZE" \
|
|
-c "write -P 0x22 $(( CLUSTER_SIZE * 4 )) $CLUSTER_SIZE" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
echo "Rebase onto another image in the same chain"
|
|
echo
|
|
|
|
$QEMU_IMG rebase -b "$BASE_NEW" -F $IMGFMT "$OVERLAY"
|
|
|
|
echo "Verify that data is read the same before and after rebase"
|
|
echo
|
|
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x00 0 $CLUSTER_SIZE" \
|
|
-c "read -P 0x11 $CLUSTER_SIZE $CLUSTER_SIZE" \
|
|
-c "read -P 0x00 $(( CLUSTER_SIZE * 2 )) $(( CLUSTER_SIZE * 2 ))" \
|
|
-c "read -P 0x22 $(( CLUSTER_SIZE * 4 )) $CLUSTER_SIZE" \
|
|
-c "read -P 0x00 $(( CLUSTER_SIZE * 5 )) $CLUSTER_SIZE" \
|
|
| _filter_qemu_io
|
|
|
|
echo
|
|
echo "Verify that untouched cluster remains unallocated"
|
|
echo
|
|
|
|
$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
|
|
|
|
# Check that the region to copy to the overlay during a rebase
|
|
# operation does not exceed the I/O buffer size.
|
|
#
|
|
# backing_new <-- backing_old <-- overlay
|
|
#
|
|
# Backing (new): -- -- -- -- <-- Empty image, size 4MB
|
|
# Backing (old):|--|ff|ff|--| <-- 4 clusters, 1MB each
|
|
# Overlay: |-- --|-- --| <-- 2 clusters, 2MB each
|
|
#
|
|
# The data at [1MB, 3MB) must be copied from the old backing image to
|
|
# the overlay. However the rebase code will extend that region to the
|
|
# overlay's (sub)cluster boundaries to avoid CoW (see commit 12df580b).
|
|
# This test checks that IO_BUF_SIZE (2 MB) is taken into account.
|
|
|
|
echo
|
|
echo "=== Test that the region to copy does not exceed 2MB (IO_BUF_SIZE) ==="
|
|
echo
|
|
|
|
echo "Creating backing chain"
|
|
echo
|
|
|
|
TEST_IMG=$BASE_NEW _make_test_img 4M
|
|
TEST_IMG=$BASE_OLD CLUSTER_SIZE=1M _make_test_img -b "$BASE_NEW" -F $IMGFMT
|
|
TEST_IMG=$OVERLAY CLUSTER_SIZE=2M _make_test_img -b "$BASE_OLD" -F $IMGFMT
|
|
|
|
echo
|
|
echo "Writing data to region [1MB, 3MB)"
|
|
echo
|
|
|
|
$QEMU_IO "$BASE_OLD" -c "write -P 0xff 1M 2M" | _filter_qemu_io
|
|
|
|
echo
|
|
echo "Rebasing"
|
|
echo
|
|
|
|
$QEMU_IMG rebase -b "$BASE_NEW" -F $IMGFMT "$OVERLAY"
|
|
|
|
echo "Verifying the data"
|
|
echo
|
|
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x00 0 1M" | _filter_qemu_io
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0xff 1M 2M" | _filter_qemu_io
|
|
$QEMU_IO "$OVERLAY" -c "read -P 0x00 3M 1M" | _filter_qemu_io
|
|
|
|
$QEMU_IMG map "$OVERLAY" | _filter_qemu_img_map
|
|
|
|
echo
|
|
|
|
# success, all done
|
|
echo "*** done"
|
|
rm -f $seq.full
|
|
status=0
|