* [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption
@ 2026-11-05 16:15 Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 1/3] iotests/290: add test case to check 'discard-no-unref' option behavior Andrey Drobyshev
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Andrey Drobyshev @ 2026-11-05 16:15 UTC (permalink / raw)
To: qemu-block; +Cc: qemu-devel, kwolf, hreitz, jean-louis, andrey.drobyshev, den
v1 -> v2:
* Patch 1:
- Add _require_disk_usage;
- Clean $TEST_IMG.nounref;
- Fix -fuse test run;
- Drop R-b's;
* Patch 2, 3: add R-b's.
v1: https://lore.kernel.org/qemu-devel/20261027104441.830212-1-andrey.drobyshev@virtuozzo.com
Andrey Drobyshev (3):
iotests/290: add test case to check 'discard-no-unref' option behavior
qcow2: fix snapshot data corruption with discard-no-unref=on
iotests/290: add 'discard' and 'write -z -u' case with a snapshot
block/qcow2-cluster.c | 6 +++--
tests/qemu-iotests/290 | 54 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/290.out | 33 +++++++++++++++++++++++
3 files changed, 91 insertions(+), 2 deletions(-)
--
2.47.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/3] iotests/290: add test case to check 'discard-no-unref' option behavior
2026-11-05 16:15 [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Andrey Drobyshev
@ 2026-11-05 16:15 ` Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 2/3] qcow2: fix snapshot data corruption with discard-no-unref=on Andrey Drobyshev
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Andrey Drobyshev @ 2026-11-05 16:15 UTC (permalink / raw)
To: qemu-block; +Cc: qemu-devel, kwolf, hreitz, jean-louis, andrey.drobyshev, den
We basically fill 2 images with identical data and perform discard
operations with and without 'discard-no-unref' enabled. Then we check
that images still read identically, that their disk usage is the same
(i.e. fallocate(FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE) is called for
both) and that with the option enabled cluster is still marked as
allocated in "qemu-img map" output. We also check that the option
doesn't work with qcow2 v2 images.
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
---
tests/qemu-iotests/290 | 38 ++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/290.out | 28 ++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
diff --git a/tests/qemu-iotests/290 b/tests/qemu-iotests/290
index 341a25f96b5..cf4ff6c2bac 100755
--- a/tests/qemu-iotests/290
+++ b/tests/qemu-iotests/290
@@ -29,6 +29,7 @@ status=1 # failure is the default!
_cleanup()
{
_cleanup_test_img
+ _rm_test_img "$TEST_IMG.nounref"
}
trap "_cleanup; exit \$status" 0 1 2 3 15
@@ -40,6 +41,7 @@ _supported_fmt qcow2
_supported_proto file fuse
_supported_os Linux
_unsupported_imgopts 'compat=0.10' refcount_bits data_file compression_type
+_require_disk_usage
echo
echo "### Test 'qemu-io -c discard' on a QCOW2 image without a backing file"
@@ -90,6 +92,42 @@ for qcow2_compat in 0.10 1.1; do
$QEMU_IMG map "$TEST_IMG" | _filter_testdir
done
+echo
+echo "### Test 'qemu-io -c discard' with 'discard-no-unref' option enabled"
+echo
+
+echo "# Check that qcow2 v2 images don't support 'discard-no-unref' option"
+NOUNREF_IMG="$TEST_IMG.nounref"
+TEST_IMG="$NOUNREF_IMG" _make_test_img -o "compat=0.10" 128k
+# This should immediately fail with an error
+$QEMU_IO -c 'reopen -o discard-no-unref=on' "$NOUNREF_IMG" | _filter_qemu_io
+
+echo "# Create two compat=1.1 images and fill them with identical data"
+_make_test_img -o "compat=1.1" 128k
+TEST_IMG="$NOUNREF_IMG" _make_test_img -o "compat=1.1" 128k
+$QEMU_IO -c 'write -P 0xaa 0 128k' "$TEST_IMG" | _filter_qemu_io
+$QEMU_IO -c 'write -P 0xaa 0 128k' "$NOUNREF_IMG" | _filter_qemu_io
+
+echo "# Enable 'discard-no-unref' in one of them, discard 2nd cluster in both"
+$QEMU_IO -c 'discard 64k 64k' "$TEST_IMG" | _filter_qemu_io
+$QEMU_IO -c 'reopen -o discard-no-unref=on' \
+ -c 'discard 64k 64k' "$NOUNREF_IMG" | _filter_qemu_io
+
+echo "# Compare disk usage of the 2 images"
+# Don't check the exact disk usage values but rather that they're equal
+usage1=$(disk_usage "$TEST_IMG")
+usage2=$(disk_usage "$NOUNREF_IMG")
+echo "disk_usage($TEST_IMG) - disk_usage($NOUNREF_IMG) = $((usage1 - usage2))" \
+ | _filter_testdir | _filter_imgfmt
+
+echo "# Check that images are still identical"
+$QEMU_IMG compare "$TEST_IMG" "$NOUNREF_IMG"
+
+echo "# Output of qemu-img map for the image with dropped reference"
+$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map
+echo "# Output of qemu-img map for the image with kept reference"
+$QEMU_IMG map --output=json "$NOUNREF_IMG" | _filter_qemu_img_map
+
# success, all done
echo "*** done"
rm -f $seq.full
diff --git a/tests/qemu-iotests/290.out b/tests/qemu-iotests/290.out
index 22b476594fb..d8f8d5ff493 100644
--- a/tests/qemu-iotests/290.out
+++ b/tests/qemu-iotests/290.out
@@ -58,4 +58,32 @@ read 131072/131072 bytes at offset 0
128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
# Output of qemu-img map
Offset Length Mapped to File
+
+### Test 'qemu-io -c discard' with 'discard-no-unref' option enabled
+
+# Check that qcow2 v2 images don't support 'discard-no-unref' option
+Formatting 'TEST_DIR/t.IMGFMT.nounref', fmt=IMGFMT size=131072
+qemu-io: discard-no-unref is only supported since qcow2 version 3
+# Create two compat=1.1 images and fill them with identical data
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=131072
+Formatting 'TEST_DIR/t.IMGFMT.nounref', fmt=IMGFMT size=131072
+wrote 131072/131072 bytes at offset 0
+128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote 131072/131072 bytes at offset 0
+128 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+# Enable 'discard-no-unref' in one of them, discard 2nd cluster in both
+discard 65536/65536 bytes at offset 65536
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+discard 65536/65536 bytes at offset 65536
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+# Compare disk usage of the 2 images
+disk_usage(TEST_DIR/t.IMGFMT) - disk_usage(TEST_DIR/t.IMGFMT.nounref) = 0
+# Check that images are still identical
+Images are identical.
+# Output of qemu-img map for the image with dropped reference
+[{ "start": 0, "length": 65536, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
+{ "start": 65536, "length": 65536, "depth": 0, "present": true, "zero": true, "data": false, "compressed": false}]
+# Output of qemu-img map for the image with kept reference
+[{ "start": 0, "length": 65536, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
+{ "start": 65536, "length": 65536, "depth": 0, "present": true, "zero": true, "data": false, "compressed": false, "offset": OFFSET}]
*** done
--
2.47.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/3] qcow2: fix snapshot data corruption with discard-no-unref=on
2026-11-05 16:15 [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 1/3] iotests/290: add test case to check 'discard-no-unref' option behavior Andrey Drobyshev
@ 2026-11-05 16:15 ` Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 3/3] iotests/290: add 'discard' and 'write -z -u' case with a snapshot Andrey Drobyshev
2026-11-06 13:17 ` [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Kevin Wolf
3 siblings, 0 replies; 5+ messages in thread
From: Andrey Drobyshev @ 2026-11-05 16:15 UTC (permalink / raw)
To: qemu-block; +Cc: qemu-devel, kwolf, hreitz, jean-louis, andrey.drobyshev, den
Whenever option discard-no-unref=on is enabled, we bypass refcount
machinery and unconditionally punch hole in the data on 'discard' and
'write -z -u' paths. However, when this same cluster is also referenced
by an internal snapshot, such operations lead to problems. The active L2
table is copied before it is changed, so the snapshot metadata stays
correct, but the host range under it is punched out and the snapshot reads
zeroes there afterwards. qemu-img check finds nothing wrong.
Fix this by guarding the 'keep_reference' flag by the presence of the
QCOW_OFLAG_COPIED bit. I.e. only proceed with unconditional data
punching if we own this cluster exclusively.
Fixes: 42a2890a76f4 ("qcow2: add discard-no-unref option")
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
block/qcow2-cluster.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c
index e02fae6a0c6..cc473c7c796 100644
--- a/block/qcow2-cluster.c
+++ b/block/qcow2-cluster.c
@@ -1953,6 +1953,7 @@ discard_in_l2_slice(BlockDriverState *bs, uint64_t offset, uint64_t nb_clusters,
QCow2ClusterType cluster_type =
qcow2_get_cluster_type(bs, old_l2_entry);
bool keep_reference = (cluster_type != QCOW2_CLUSTER_COMPRESSED) &&
+ (old_l2_entry & QCOW_OFLAG_COPIED) &&
!full_discard &&
(s->discard_no_unref &&
type == QCOW2_DISCARD_REQUEST);
@@ -2097,8 +2098,9 @@ zero_in_l2_slice(BlockDriverState *bs, uint64_t offset,
QCow2ClusterType type = qcow2_get_cluster_type(bs, old_l2_entry);
bool unmap = (type == QCOW2_CLUSTER_COMPRESSED) ||
((flags & BDRV_REQ_MAY_UNMAP) && qcow2_cluster_is_allocated(type));
- bool keep_reference =
- (s->discard_no_unref && type != QCOW2_CLUSTER_COMPRESSED);
+ bool keep_reference = s->discard_no_unref &&
+ type != QCOW2_CLUSTER_COMPRESSED &&
+ (old_l2_entry & QCOW_OFLAG_COPIED);
uint64_t new_l2_entry = old_l2_entry;
uint64_t new_l2_bitmap = old_l2_bitmap;
--
2.47.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 3/3] iotests/290: add 'discard' and 'write -z -u' case with a snapshot
2026-11-05 16:15 [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 1/3] iotests/290: add test case to check 'discard-no-unref' option behavior Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 2/3] qcow2: fix snapshot data corruption with discard-no-unref=on Andrey Drobyshev
@ 2026-11-05 16:15 ` Andrey Drobyshev
2026-11-06 13:17 ` [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Kevin Wolf
3 siblings, 0 replies; 5+ messages in thread
From: Andrey Drobyshev @ 2026-11-05 16:15 UTC (permalink / raw)
To: qemu-block; +Cc: qemu-devel, kwolf, hreitz, jean-louis, andrey.drobyshev, den
Before the previous commit, issuing 'discard' and 'write -z -u'
operations with discard-no-unref=on option enabled led to data
corruption. Add a test to cover this case.
Signed-off-by: Andrey Drobyshev <andrey.drobyshev@virtuozzo.com>
Reviewed-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
---
tests/qemu-iotests/290 | 16 ++++++++++++++++
tests/qemu-iotests/290.out | 5 +++++
2 files changed, 21 insertions(+)
diff --git a/tests/qemu-iotests/290 b/tests/qemu-iotests/290
index cf4ff6c2bac..b9a71b0cee4 100755
--- a/tests/qemu-iotests/290
+++ b/tests/qemu-iotests/290
@@ -128,6 +128,22 @@ $QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map
echo "# Output of qemu-img map for the image with kept reference"
$QEMU_IMG map --output=json "$NOUNREF_IMG" | _filter_qemu_img_map
+echo
+echo "### Discard with 'discard-no-unref' on a cluster shared with a snapshot"
+echo
+
+_make_test_img 128k
+$QEMU_IO -c 'write -q -P 0xaa 0 128k' "$TEST_IMG" | _filter_qemu_io
+$QEMU_IMG snapshot -c snap "$TEST_IMG"
+QEMU_IO_OPTIONS=$QEMU_IO_OPTIONS_NO_FMT \
+$QEMU_IO -c 'discard -q 0 64k' -c 'write -q -z -u 64k 64k' --image-opts \
+ "driver=$IMGFMT,file.filename=$TEST_IMG,discard-no-unref=on" \
+ | _filter_qemu_io
+$QEMU_IO -c 'read -q -P 0 0 128k' "$TEST_IMG" | _filter_qemu_io
+$QEMU_IMG snapshot -a snap "$TEST_IMG"
+$QEMU_IO -c 'read -q -P 0xaa 0 128k' "$TEST_IMG" | _filter_qemu_io
+_check_test_img
+
# success, all done
echo "*** done"
rm -f $seq.full
diff --git a/tests/qemu-iotests/290.out b/tests/qemu-iotests/290.out
index d8f8d5ff493..264fbcb28ca 100644
--- a/tests/qemu-iotests/290.out
+++ b/tests/qemu-iotests/290.out
@@ -86,4 +86,9 @@ Images are identical.
# Output of qemu-img map for the image with kept reference
[{ "start": 0, "length": 65536, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET},
{ "start": 65536, "length": 65536, "depth": 0, "present": true, "zero": true, "data": false, "compressed": false, "offset": OFFSET}]
+
+### Discard with 'discard-no-unref' on a cluster shared with a snapshot
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=131072
+No errors were found on the image.
*** done
--
2.47.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption
2026-11-05 16:15 [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Andrey Drobyshev
` (2 preceding siblings ...)
2026-11-05 16:15 ` [PATCH v2 3/3] iotests/290: add 'discard' and 'write -z -u' case with a snapshot Andrey Drobyshev
@ 2026-11-06 13:17 ` Kevin Wolf
3 siblings, 0 replies; 5+ messages in thread
From: Kevin Wolf @ 2026-11-06 13:17 UTC (permalink / raw)
To: Andrey Drobyshev
Cc: qemu-block, qemu-devel, hreitz, jean-louis, den, qemu-stable
Am 05.11.2026 um 17:15 hat Andrey Drobyshev geschrieben:
> v1 -> v2:
>
> * Patch 1:
> - Add _require_disk_usage;
> - Clean $TEST_IMG.nounref;
> - Fix -fuse test run;
> - Drop R-b's;
>
> * Patch 2, 3: add R-b's.
>
> v1: https://lore.kernel.org/qemu-devel/20261027104441.830212-1-andrey.drobyshev@virtuozzo.com
>
> Andrey Drobyshev (3):
> iotests/290: add test case to check 'discard-no-unref' option behavior
> qcow2: fix snapshot data corruption with discard-no-unref=on
> iotests/290: add 'discard' and 'write -z -u' case with a snapshot
[Cc: qemu-stable]
Thanks, applied to the block branch.
Kevin
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-06 13:18 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-05 16:15 [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 1/3] iotests/290: add test case to check 'discard-no-unref' option behavior Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 2/3] qcow2: fix snapshot data corruption with discard-no-unref=on Andrey Drobyshev
2026-11-05 16:15 ` [PATCH v2 3/3] iotests/290: add 'discard' and 'write -z -u' case with a snapshot Andrey Drobyshev
2026-11-06 13:17 ` [PATCH v2 0/3] qcow2: fix discard-no-unref=on data corruption Kevin Wolf
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.