qcow2: Allow resize of images with internal snapshots

We originally refused to allow resize of images with internal
snapshots because the v2 image format did not require the tracking of
snapshot size, making it impossible to safely revert to a snapshot
with a different size than the current view of the image.  But the
snapshot size tracking was rectified in v3, and our recent fixes to
qemu-img amend (see 0a85af35) guarantee that we always have a valid
snapshot size.  Thus, we no longer need to artificially limit image
resizes, but it does become one more thing that would prevent a
downgrade back to v2.  And now that we support different-sized
snapshots, it's also easy to fix reverting to a snapshot to apply the
new size.

Upgrade iotest 61 to cover this (we previously had NO coverage of
refusal to resize while snapshots exist).  Note that the amend process
can fail but still have effects: in particular, since we break things
into upgrade, resize, downgrade, a failure during resize does not roll
back changes made during upgrade, nor does failure in downgrade roll
back a resize.  But this situation is pre-existing even without this
patch; and without journaling, the best we could do is minimize the
chance of partial failure by collecting all changes prior to doing any
writes - which adds a lot of complexity but could still fail with EIO.
On the other hand, we are careful that even if we have partial
modification but then fail, the image is left viable (that is, we are
careful to sequence things so that after each successful cluster
write, there may be transient leaked clusters but no corrupt
metadata).  And complicating the code to make it more transaction-like
is not worth the effort: a user can always request multiple 'qemu-img
amend' changing one thing each, if they need finer-grained control
over detecting the first failure than what they get by letting qemu
decide how to sequence multiple changes.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Message-Id: <20200428192648.749066-3-eblake@redhat.com>
Signed-off-by: Max Reitz <mreitz@redhat.com>
This commit is contained in:
Eric Blake 2020-04-28 14:26:47 -05:00 committed by Max Reitz
parent a3aeeab557
commit 7fa140abf6
4 changed files with 101 additions and 7 deletions

View File

@ -23,6 +23,7 @@
*/
#include "qemu/osdep.h"
#include "sysemu/block-backend.h"
#include "qapi/error.h"
#include "qcow2.h"
#include "qemu/bswap.h"
@ -775,10 +776,21 @@ int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id)
}
if (sn->disk_size != bs->total_sectors * BDRV_SECTOR_SIZE) {
error_report("qcow2: Loading snapshots with different disk "
"size is not implemented");
ret = -ENOTSUP;
goto fail;
BlockBackend *blk = blk_new_with_bs(bs, BLK_PERM_RESIZE, BLK_PERM_ALL,
&local_err);
if (!blk) {
error_report_err(local_err);
ret = -ENOTSUP;
goto fail;
}
ret = blk_truncate(blk, sn->disk_size, true, PREALLOC_MODE_OFF, 0,
&local_err);
blk_unref(blk);
if (ret < 0) {
error_report_err(local_err);
goto fail;
}
}
/*

View File

@ -3989,9 +3989,12 @@ static int coroutine_fn qcow2_co_truncate(BlockDriverState *bs, int64_t offset,
qemu_co_mutex_lock(&s->lock);
/* cannot proceed if image has snapshots */
if (s->nb_snapshots) {
error_setg(errp, "Can't resize an image which has snapshots");
/*
* Even though we store snapshot size for all images, it was not
* required until v3, so it is not safe to proceed for v2.
*/
if (s->nb_snapshots && s->qcow_version < 3) {
error_setg(errp, "Can't resize a v2 image which has snapshots");
ret = -ENOTSUP;
goto fail;
}
@ -5005,6 +5008,7 @@ static int qcow2_downgrade(BlockDriverState *bs, int target_version,
BDRVQcow2State *s = bs->opaque;
int current_version = s->qcow_version;
int ret;
int i;
/* This is qcow2_downgrade(), not qcow2_upgrade() */
assert(target_version < current_version);
@ -5022,6 +5026,21 @@ static int qcow2_downgrade(BlockDriverState *bs, int target_version,
return -ENOTSUP;
}
/*
* If any internal snapshot has a different size than the current
* image size, or VM state size that exceeds 32 bits, downgrading
* is unsafe. Even though we would still use v3-compliant output
* to preserve that data, other v2 programs might not realize
* those optional fields are important.
*/
for (i = 0; i < s->nb_snapshots; i++) {
if (s->snapshots[i].vm_state_size > UINT32_MAX ||
s->snapshots[i].disk_size != bs->total_sectors * BDRV_SECTOR_SIZE) {
error_setg(errp, "Internal snapshots prevent downgrade of image");
return -ENOTSUP;
}
}
/* clear incompatible features */
if (s->incompatible_features & QCOW2_INCOMPAT_DIRTY) {
ret = qcow2_mark_clean(bs);

View File

@ -111,6 +111,41 @@ $PYTHON qcow2.py "$TEST_IMG" dump-header
$QEMU_IO -c "read -P 0x2a 42M 64k" "$TEST_IMG" | _filter_qemu_io
_check_test_img
echo
echo "=== Testing resize with snapshots ==="
echo
_make_test_img -o "compat=0.10" 32M
$QEMU_IO -c "write -P 0x2a 24M 64k" "$TEST_IMG" | _filter_qemu_io
$QEMU_IMG snapshot -c foo "$TEST_IMG"
$QEMU_IMG resize "$TEST_IMG" 64M &&
echo "unexpected pass"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
$QEMU_IMG amend -o "compat=1.1,size=128M" "$TEST_IMG" ||
echo "unexpected fail"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
$QEMU_IMG snapshot -c bar "$TEST_IMG"
$QEMU_IMG resize --shrink "$TEST_IMG" 64M ||
echo "unexpected fail"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
$QEMU_IMG amend -o "compat=0.10,size=32M" "$TEST_IMG" &&
echo "unexpected pass"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
$QEMU_IMG snapshot -a bar "$TEST_IMG" ||
echo "unexpected fail"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
$QEMU_IMG snapshot -d bar "$TEST_IMG"
$QEMU_IMG amend -o "compat=0.10,size=32M" "$TEST_IMG" ||
echo "unexpected fail"
$PYTHON qcow2.py "$TEST_IMG" dump-header | grep '^\(version\|size\|nb_snap\)'
_check_test_img
echo
echo "=== Testing dirty lazy_refcounts=off ==="
echo

View File

@ -271,6 +271,34 @@ read 65536/65536 bytes at offset 44040192
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
No errors were found on the image.
=== Testing resize with snapshots ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=33554432
wrote 65536/65536 bytes at offset 25165824
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
qemu-img: Can't resize a v2 image which has snapshots
version 2
size 33554432
nb_snapshots 1
version 3
size 134217728
nb_snapshots 1
Image resized.
version 3
size 67108864
nb_snapshots 2
qemu-img: Internal snapshots prevent downgrade of image
version 3
size 33554432
nb_snapshots 2
version 3
size 134217728
nb_snapshots 2
version 2
size 33554432
nb_snapshots 1
No errors were found on the image.
=== Testing dirty lazy_refcounts=off ===
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=67108864