qemu-img: convert: flush target image after copying

With 'qemu-img convert', when using a cache mode with 'cache.no-flush
= off', the expectation is that the data is flushed to the disk, which
happens since commit 80ccf93b88 ("qemu-img: let 'qemu-img convert'
flush data"). This expectation also encompasses that failure to do so
is properly reported, so ensure that 'qemu-img convert' exits with a
non-zero status code when the target image cannot be flushed.

In particular, the reproducer below [0], based on a report by
Ing. Alfonso Kuen Arroyo [1], would result in
> rc=0
> Content mismatch at offset XYZ!
before this change, and in
> rc=1
> Content mismatch at offset XYZ!
after this change.

Note that this change does not affect the (default) 'unsafe' cache
mode, because bdrv_co_flush() still skips flush to disk for that cache
mode.

[0]:
> #!/bin/bash -x
> dd if=/dev/urandom of=src.raw bs=1M count=500
> truncate -s 500M dst.img
> LOOP=$(losetup --find --show dst.img)
> SIZE=$(blockdev --getsz $LOOP)
> dd if=/dev/zero of=$LOOP bs=1M count=500 conv=fsync status=none
> sync
> echo 3 > /proc/sys/vm/drop_caches
> dmsetup create flk --table "0 $SIZE flakey $LOOP 0 5 5"
> qemu-img convert -t writeback -p -r 100M -f raw -O raw src.raw /dev/mapper/flk
> echo rc=$?
> sleep 5
> dmsetup remove flk
> qemu-img compare src.raw $LOOP
> losetup -d $LOOP
> rm src.raw dst.img

[1]: https://lore.proxmox.com/pve-devel/178961792132.43804.17173233935297903311@idkmanager.com/

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
Message-ID: <20260930132140.2342574-5-f.ebner@proxmox.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
Fiona Ebner authored and Eric Blake committed 2026-10-16 09:08:52 -05:00
1 parent c6c8bdc21a
commit 901ac73874
2 files changed
+9 -2

No files matched your search

+7 -1
View File
@@ -2245,7 +2245,7 @@ static int img_convert(const img_cmd_t *ccmd, int argc, char **argv)
Error *local_err = NULL; Error *local_err = NULL;
bool writethrough, src_writethrough, image_opts = false, bool writethrough, src_writethrough, image_opts = false,
skip_create = false, progress = false, tgt_image_opts = false; skip_create = false, progress = false, tgt_image_opts = false;
int64_t ret = -EINVAL; int64_t ret = -EINVAL, ret2;
bool force_share = false; bool force_share = false;
bool explict_min_sparse = false; bool explict_min_sparse = false;
bool bitmaps = false; bool bitmaps = false;
@@ -2872,6 +2872,12 @@ static int img_convert(const img_cmd_t *ccmd, int argc, char **argv)
ret = convert_copy_bitmaps(blk_bs(s.src[0]), out_bs, skip_broken); ret = convert_copy_bitmaps(blk_bs(s.src[0]), out_bs, skip_broken);
} }
ret2 = blk_flush(s.target);
if (ret2 < 0) {
error_report("Failed to flush target image: %s", strerror(-ret2));
ret = ret != 0 ? ret : ret2;
}
out: out:
if (!ret) { if (!ret) {
qemu_progress_print(100, 0); qemu_progress_print(100, 0);
+2 -1
View File
@@ -267,9 +267,10 @@ qemu-img convert exited with status 0
=== failing flush after convert with 'writeback' cache mode === === failing flush after convert with 'writeback' cache mode ===
qemu-img: Failed to flush target image: Input/output error
qemu-img: Failed flush for 'json:{ /* filtered */ }' during close: Input/output error qemu-img: Failed flush for 'json:{ /* filtered */ }' during close: Input/output error
qemu-img: Failed to flush the L2 table cache: Input/output error qemu-img: Failed to flush the L2 table cache: Input/output error
qemu-img: Failed to flush the refcount block cache: Input/output error qemu-img: Failed to flush the refcount block cache: Input/output error
qemu-img: Failed flush for 'json:{ /* filtered */ }' during close: Input/output error qemu-img: Failed flush for 'json:{ /* filtered */ }' during close: Input/output error
qemu-img convert exited with status 0 qemu-img convert exited with status 1
*** done *** done