* [PATCH v8 0/3] parallels: report the stored dirty bitmaps in qemu-img info
@ 2026-09-13 19:52 Denis V. Lunev
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Denis V. Lunev @ 2026-09-13 19:52 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, Denis V. Lunev, Stefan Hajnoczi, Markus Armbruster
What is left of "[PATCH v7 00/25] parallels: Add full dirty bitmap
support" after the rest of it went upstream. The other 22 patches are
merged as 209b2afafa ("Merge tag 'pull-parallels-2026-09-11' of
https://gitlab.com/dlunev/qemu into staging"), so only the qemu-img
info reporting patch and the two iotests which consume it are here.
The numbers have shifted: 19, 21 and 22 of v7 are 1, 2 and 3 here.
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
v7 was:
https://lore.kernel.org/qemu-devel/20260903144143.2328870-1-den@openvz.org/
v8:
1: On Markus' review. @inconsistent reads "is in an inconsistent
state and cannot be used". @bitmaps drops the clause about bitmaps
which are not written out yet and becomes "A list of parallels
bitmap details", matching ImageInfoSpecificQCow2, and the struct
description loses its stray "dirty". ParallelsBitmapInfo moved
next to Qcow2BitmapInfo, out of the ImageInfoSpecificFOO run.
2, 3: Unchanged.
Denis V. Lunev (3):
parallels: report the stored dirty bitmaps in qemu-img info
iotests: cover storing a parallels dirty bitmap
iotests: cover the qemu-img bitmap operations on parallels
block/parallels-ext.c | 26 +++
block/parallels.c | 20 ++
block/parallels.h | 3 +
qapi/block-core.json | 45 +++-
tests/qemu-iotests/tests/parallels-bitmap | 217 +++++++++++++++++-
tests/qemu-iotests/tests/parallels-bitmap.out | 57 +++++
tests/qemu-iotests/tests/parallels-checks | 6 +
tests/qemu-iotests/tests/parallels-checks.out | 19 ++
8 files changed, 390 insertions(+), 3 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v8 1/3] parallels: report the stored dirty bitmaps in qemu-img info
2026-09-13 19:52 [PATCH v8 0/3] parallels: report the stored dirty bitmaps in qemu-img info Denis V. Lunev
@ 2026-09-13 19:52 ` Denis V. Lunev
2026-09-15 7:58 ` Markus Armbruster
2026-10-06 15:11 ` Eric Blake
2026-09-13 19:53 ` [PATCH v8 2/3] iotests: cover storing a parallels dirty bitmap Denis V. Lunev
2026-09-13 19:53 ` [PATCH v8 3/3] iotests: cover the qemu-img bitmap operations on parallels Denis V. Lunev
2 siblings, 2 replies; 7+ messages in thread
From: Denis V. Lunev @ 2026-09-13 19:52 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, Stefan Hajnoczi, Eric Blake,
Markus Armbruster
From: Denis V. Lunev <den@openvz.org>
Nothing tells which persistent bitmaps an image carries. qemu-img info
says nothing about them, and the only other way to see one is to export
the image over NBD and ask for a bitmap by name, which needs the name
beforehand.
Add ImageInfoSpecificParallels with the bitmaps and their granularity,
in the same way as qcow2 reports the contents of its bitmap directory:
Format specific information:
bitmaps:
[0]:
name: b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45
granularity: 65536
For running VM process the list is built on the fly from in-memory
list. This is the best we can do. FormatExtension could be dead in
the image while VMs are running as all bitmaps are cleared on
non-clean VM stop.
A bitmap of an image which was not closed correctly is inconsistent
and can not be used. Report that as well, the way qcow2 reports its
in-use flag, so such a bitmap is not listed as a valid one.
The section is left empty for an image with no persistent dirty
bitmaps, and an empty section is not printed, so the human readable
output of "qemu-img info" and "info block" is unchanged.
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Cc: Eric Blake <eblake@redhat.com>
Cc: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
block/parallels-ext.c | 26 +++++++++++
block/parallels.c | 20 +++++++++
block/parallels.h | 3 ++
qapi/block-core.json | 45 ++++++++++++++++++-
tests/qemu-iotests/tests/parallels-checks | 6 +++
tests/qemu-iotests/tests/parallels-checks.out | 19 ++++++++
6 files changed, 117 insertions(+), 2 deletions(-)
diff --git a/block/parallels-ext.c b/block/parallels-ext.c
index f687f2da7c..8b8035af91 100644
--- a/block/parallels-ext.c
+++ b/block/parallels-ext.c
@@ -736,3 +736,29 @@ parallels_co_remove_persistent_dirty_bitmap(BlockDriverState *bs,
return ret;
}
+
+void parallels_get_bitmap_info_list(BlockDriverState *bs,
+ ParallelsBitmapInfoList **info_list)
+{
+ BdrvDirtyBitmap *bitmap;
+ ParallelsBitmapInfoList **tail = info_list;
+
+ *info_list = NULL;
+
+ FOR_EACH_DIRTY_BITMAP(bs, bitmap) {
+ ParallelsBitmapInfo *info;
+
+ if (!bdrv_dirty_bitmap_get_persistence(bitmap)) {
+ continue;
+ }
+
+ info = g_new0(ParallelsBitmapInfo, 1);
+ info->name = g_strdup(bdrv_dirty_bitmap_name(bitmap));
+ info->granularity = bdrv_dirty_bitmap_granularity(bitmap);
+ if (bdrv_dirty_bitmap_inconsistent(bitmap)) {
+ info->has_inconsistent = true;
+ info->inconsistent = true;
+ }
+ QAPI_LIST_APPEND(tail, info);
+ }
+}
diff --git a/block/parallels.c b/block/parallels.c
index 2a5ceb8978..e7d65d0458 100644
--- a/block/parallels.c
+++ b/block/parallels.c
@@ -1624,6 +1624,25 @@ static bool parallels_is_support_dirty_bitmaps(BlockDriverState *bs)
return 1;
}
+static ImageInfoSpecific * GRAPH_RDLOCK
+parallels_get_specific_info(BlockDriverState *bs, Error **errp)
+{
+ ImageInfoSpecificParallels *parallels_info;
+ ImageInfoSpecific *spec_info;
+
+ parallels_info = g_new0(ImageInfoSpecificParallels, 1);
+ parallels_get_bitmap_info_list(bs, ¶llels_info->bitmaps);
+ parallels_info->has_bitmaps = !!parallels_info->bitmaps;
+
+ spec_info = g_new(ImageInfoSpecific, 1);
+ *spec_info = (ImageInfoSpecific){
+ .type = IMAGE_INFO_SPECIFIC_KIND_PARALLELS,
+ .u.parallels.data = parallels_info,
+ };
+
+ return spec_info;
+}
+
static BlockDriver bdrv_parallels = {
.format_name = "parallels",
.instance_size = sizeof(BDRVParallelsState),
@@ -1653,6 +1672,7 @@ static BlockDriver bdrv_parallels = {
parallels_co_can_store_new_dirty_bitmap,
.bdrv_co_remove_persistent_dirty_bitmap =
parallels_co_remove_persistent_dirty_bitmap,
+ .bdrv_get_specific_info = parallels_get_specific_info,
};
static void bdrv_parallels_init(void)
diff --git a/block/parallels.h b/block/parallels.h
index 27d8c3ac83..012f47320b 100644
--- a/block/parallels.h
+++ b/block/parallels.h
@@ -32,6 +32,7 @@
#ifndef BLOCK_PARALLELS_H
#define BLOCK_PARALLELS_H
#include "qemu/coroutine.h"
+#include "qapi/qapi-types-block-core.h"
#define HEADS_NUMBER 16
#define SEC_IN_CYL 32
@@ -111,5 +112,7 @@ parallels_co_can_store_new_dirty_bitmap(BlockDriverState *bs, const char *name,
int coroutine_fn GRAPH_RDLOCK
parallels_co_remove_persistent_dirty_bitmap(BlockDriverState *bs,
const char *name, Error **errp);
+void parallels_get_bitmap_info_list(BlockDriverState *bs,
+ ParallelsBitmapInfoList **info_list);
#endif
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 199efc1e00..aed2888147 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -188,6 +188,16 @@
'*extent-size-hint': 'size'
} }
+##
+# @ImageInfoSpecificParallels:
+#
+# @bitmaps: A list of parallels bitmap details
+#
+# Since: 11.2
+##
+{ 'struct': 'ImageInfoSpecificParallels',
+ 'data': { '*bitmaps': ['ParallelsBitmapInfo'] } }
+
##
# @ImageInfoSpecificKind:
#
@@ -197,10 +207,12 @@
#
# @file: Since 8.0
#
+# @parallels: Since 11.2
+#
# Since: 1.7
##
{ 'enum': 'ImageInfoSpecificKind',
- 'data': [ 'qcow2', 'vmdk', 'luks', 'rbd', 'file' ] }
+ 'data': [ 'qcow2', 'vmdk', 'luks', 'rbd', 'file', 'parallels' ] }
##
# @ImageInfoSpecificQCow2Wrapper:
@@ -255,6 +267,16 @@
{ 'struct': 'ImageInfoSpecificFileWrapper',
'data': { 'data': 'ImageInfoSpecificFile' } }
+##
+# @ImageInfoSpecificParallelsWrapper:
+#
+# @data: image information specific to Parallels
+#
+# Since: 11.2
+##
+{ 'struct': 'ImageInfoSpecificParallelsWrapper',
+ 'data': { 'data': 'ImageInfoSpecificParallels' } }
+
##
# @ImageInfoSpecific:
#
@@ -273,7 +295,8 @@
'vmdk': 'ImageInfoSpecificVmdkWrapper',
'luks': 'ImageInfoSpecificLUKSWrapper',
'rbd': 'ImageInfoSpecificRbdWrapper',
- 'file': 'ImageInfoSpecificFileWrapper'
+ 'file': 'ImageInfoSpecificFileWrapper',
+ 'parallels': 'ImageInfoSpecificParallelsWrapper'
} }
##
@@ -751,6 +774,24 @@
'data': {'name': 'str', 'granularity': 'uint32',
'flags': ['Qcow2BitmapInfoFlags'] } }
+##
+# @ParallelsBitmapInfo:
+#
+# Parallels bitmap information.
+#
+# @name: the name of the bitmap
+#
+# @granularity: granularity of the bitmap in bytes
+#
+# @inconsistent: true if the bitmap is in an inconsistent state and
+# cannot be used
+#
+# Since: 11.2
+##
+{ 'struct': 'ParallelsBitmapInfo',
+ 'data': { 'name': 'str', 'granularity': 'uint32',
+ '*inconsistent': 'bool' } }
+
##
# @BlockLatencyHistogramInfo:
#
diff --git a/tests/qemu-iotests/tests/parallels-checks b/tests/qemu-iotests/tests/parallels-checks
index 3e9928c9c3..ca89ed5d62 100755
--- a/tests/qemu-iotests/tests/parallels-checks
+++ b/tests/qemu-iotests/tests/parallels-checks
@@ -382,6 +382,9 @@ echo "== dirty a single granule of the bitmap =="
file_size=`stat --printf="%s" "$TEST_IMG"`
echo "file size: $file_size"
+echo "== the bitmap is reported by qemu-img info =="
+_img_info --format-specific
+
echo "== the extension and its bitmap data are not a leak =="
_check_test_img
@@ -425,6 +428,9 @@ $QEMU_IMG bitmap --add -f $IMGFMT "$TEST_IMG" $BITMAP 2>&1 | _filter_testdir
echo "== pretend the image was not closed correctly =="
poke_file "$TEST_IMG" "$INUSE_OFFSET" "\x59\x6e\x6f\x74"
+echo "== a read-only open still reports it, marked inconsistent =="
+_img_info --format-specific
+
echo "== the bitmap is stale, so it can not be used and is dropped =="
$QEMU_IMG bitmap --clear -f $IMGFMT "$TEST_IMG" $BITMAP 2>&1 | _filter_testdir
diff --git a/tests/qemu-iotests/tests/parallels-checks.out b/tests/qemu-iotests/tests/parallels-checks.out
index 871a88d924..f6806f82fa 100644
--- a/tests/qemu-iotests/tests/parallels-checks.out
+++ b/tests/qemu-iotests/tests/parallels-checks.out
@@ -241,6 +241,15 @@ qemu-img: Operation add on bitmap bitmap0 failed: Bitmap name must be a UUID to
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
file size: 4194304
+== the bitmap is reported by qemu-img info ==
+image: TEST_DIR/t.IMGFMT
+file format: IMGFMT
+virtual size: 4 MiB (4194304 bytes)
+Format specific information:
+ bitmaps:
+ [0]:
+ name: b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45
+ granularity: 65536
== the extension and its bitmap data are not a leak ==
No errors were found on the image.
== extend image by 1 cluster ==
@@ -273,6 +282,16 @@ Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
wrote 65536/65536 bytes at offset 0
64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
== pretend the image was not closed correctly ==
+== a read-only open still reports it, marked inconsistent ==
+image: TEST_DIR/t.IMGFMT
+file format: IMGFMT
+virtual size: 4 MiB (4194304 bytes)
+Format specific information:
+ bitmaps:
+ [0]:
+ name: b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45
+ granularity: 65536
+ inconsistent: true
== the bitmap is stale, so it can not be used and is dropped ==
Repairing image was not closed correctly
qemu-img: Operation clear on bitmap b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45 failed: Bitmap 'b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45' is inconsistent and cannot be used
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v8 2/3] iotests: cover storing a parallels dirty bitmap
2026-09-13 19:52 [PATCH v8 0/3] parallels: report the stored dirty bitmaps in qemu-img info Denis V. Lunev
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
@ 2026-09-13 19:53 ` Denis V. Lunev
2026-09-13 19:53 ` [PATCH v8 3/3] iotests: cover the qemu-img bitmap operations on parallels Denis V. Lunev
2 siblings, 0 replies; 7+ messages in thread
From: Denis V. Lunev @ 2026-09-13 19:53 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, Denis V. Lunev, Stefan Hajnoczi
From: Denis V. Lunev <den@openvz.org>
parallels-bitmap covers loading a bitmap and parsing the Format
Extension. Storing one is the other direction and is not covered at all.
Nothing compares a stored bitmap with the writes it describes: test 165
compares a hash across a VM restart, which the same code produces and
consumes, so a serialization wrong in both directions passes it. Write
known ranges and read the bitmap back over NBD, in the shapes the
storage code decides between: short ranges, a chunk dirty as a whole, a
dirty run shorter than the cluster which serializes it, and a dirty
tail.
A bitmap which does not fit is refused when it is created, as the L1
table shares the Format Extension cluster with the feature headers of
the bitmaps already there. One which fits an empty cluster does not fit
next to seven others, and those seven have to survive the refusal.
The rest is read out of the image file, as that is what the next open
will see: a removal has to reach it when the command is given rather
than when the node is closed, and the in use flag has to go with an
inactivation and come back when the node is activated again. A bitmap
which can not be written used to be lost in silence, so blkdebug fails
the write of its data cluster: the loss has to be reported while the
guest data still goes through, and the flag has to survive the failed
inactivation.
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qemu-iotests/tests/parallels-bitmap | 178 +++++++++++++++++-
tests/qemu-iotests/tests/parallels-bitmap.out | 45 +++++
2 files changed, 222 insertions(+), 1 deletion(-)
diff --git a/tests/qemu-iotests/tests/parallels-bitmap b/tests/qemu-iotests/tests/parallels-bitmap
index 814b56b32c..be0ba8185d 100755
--- a/tests/qemu-iotests/tests/parallels-bitmap
+++ b/tests/qemu-iotests/tests/parallels-bitmap
@@ -25,7 +25,8 @@ import uuid
from qemu.utils import VerboseProcessError
import iotests
-from iotests import qemu_nbd_popen, qemu_img, qemu_img_map, log, file_path
+from iotests import qemu_nbd_popen, qemu_img, qemu_img_create, qemu_img_map, \
+ qemu_io, log, file_path, QemuStorageDaemon
iotests.script_initialize(supported_fmts=['parallels'])
@@ -258,3 +259,178 @@ def check_multi_l1():
check_multi_l1()
+
+
+# Storing a bitmap. The driver has to produce the extension itself here,
+# so the images are real ones rather than hand crafted clusters.
+
+INUSE_MAGIC = 0x746F6E59
+INUSE_OFF = 0x2c
+EXT_OFF = 0x38
+TRACKS_OFF = 0x1c
+
+stored = file_path('stored')
+copy = file_path('copy')
+STORED = '9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69'
+OTHER = '11111111-2222-3333-4444-555555555555'
+
+
+def read_header(off, fmt):
+ with open(stored, 'rb') as f:
+ return struct.unpack_from(fmt, f.read(0x40), off)[0]
+
+
+def in_use():
+ return read_header(INUSE_OFF, '<I') == INUSE_MAGIC
+
+
+def bitmaps_in_file():
+ """The bitmaps the image file holds right now, node open or not."""
+ ext_off = read_header(EXT_OFF, '<Q') * 512
+ if ext_off == 0:
+ return []
+ with open(stored, 'rb') as f:
+ f.seek(ext_off)
+ ext = f.read(read_header(TRACKS_OFF, '<I') * 512)
+
+ names, pos = [], EH_SIZE
+ while pos + FH_SIZE <= len(ext):
+ magic, _, data_size, _ = struct.unpack_from('<QQII', ext, pos)
+ if magic == 0:
+ break
+ if magic == BITMAP_MAGIC:
+ names.append(struct.unpack_from('<16s', ext, pos + FH_SIZE + 8)[0])
+ pos += FH_SIZE + ((data_size + 7) & ~7)
+ return names
+
+
+def bitmaps_reported():
+ info = iotests.qemu_img_info('-f', iotests.imgfmt, stored)
+ data = info.get('format-specific', {}).get('data', {})
+ return [b['name'] for b in data.get('bitmaps', [])]
+
+
+def make_image(*bitmaps, size='8M', cluster_size=None):
+ opts = ['-o', f'cluster_size={cluster_size}'] if cluster_size else []
+ qemu_img_create('-f', iotests.imgfmt, *opts, stored, size)
+ for name in bitmaps:
+ qemu_img('bitmap', '--add', '-f', iotests.imgfmt, stored, name)
+
+
+def refuse(reason, size):
+ """Add a fine grained bitmap which the Format Extension can not hold."""
+ log(f'--- {reason}')
+ make_image(size=size, cluster_size=512)
+ res = qemu_img('bitmap', '--add', '-g', '512', '-f', iotests.imgfmt,
+ stored, STORED, check=False)
+ log(iotests.filter_testfiles(res.stdout).strip())
+
+
+def open_node():
+ daemon = QemuStorageDaemon(qmp=True)
+ daemon.cmd('blockdev-add',
+ {'driver': iotests.imgfmt, 'node-name': 'n0',
+ 'file': {'driver': 'file', 'filename': stored}})
+ return daemon
+
+
+def stored_dirty(name, size, *writes):
+ """Dirty the given ranges, then read the stored bitmap back over NBD."""
+ log(f'--- {name}')
+ make_image(STORED, size=size)
+ qemu_io('-f', iotests.imgfmt,
+ *[arg for w in writes for arg in ('-c', w)], stored)
+
+ opts = f'driver=nbd,server.type=unix,server.path={nbd_sock}' \
+ f',x-dirty-bitmap=qemu:dirty-bitmap:{STORED}'
+ with qemu_nbd_popen('--read-only', f'--socket={nbd_sock}',
+ f'--bitmap={STORED}', '-f', iotests.imgfmt, stored):
+ mapping = qemu_img_map('--image-opts', opts)
+
+ for chunk in mapping:
+ if not chunk['data']:
+ log(' dirty %d MiB + %d KiB' % (chunk['start'] // (1 << 20),
+ chunk['length'] // 1024))
+
+
+# A bitmap is stored as an L1 table of clusters holding its serialized
+# bits, with an entry of one standing for a chunk which is dirty as a
+# whole. Both of those have to describe exactly what was written.
+stored_dirty('three separate ranges', '64M',
+ 'write -P 0x11 0 64k', 'write -P 0x22 4M 128k',
+ 'write -P 0x33 40M 64k')
+stored_dirty('the whole image', '8M', 'write -P 0x11 0 8M')
+stored_dirty('less than a cluster', '4M', 'write -P 0x11 0 1M')
+stored_dirty('the tail of the image', '8M', 'write -P 0x11 6M 2M')
+
+# The L1 table shares the Format Extension cluster with the feature
+# headers and the end of features marker, and a small cluster with a fine
+# granularity does not leave room for it. The marker needs room of its own
+# behind the L1 table, so a bitmap which fills the cluster to within a
+# header of the end can not be stored either.
+refuse('a bitmap too large for the Format Extension cluster', '4G')
+refuse('a bitmap leaving no room for the end of features marker', '104M')
+
+# The cluster holds every bitmap of the image at once, so what still fits
+# depends on what is stored there already.
+log('--- a bitmap which does not fit next to the ones already stored')
+make_image(size='64M', cluster_size=512)
+for i in range(1, 9):
+ bm_uuid = '9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a%02d' % i
+ add_out = qemu_img('bitmap', '--add', '-f', iotests.imgfmt, stored,
+ bm_uuid, check=False)
+ if add_out.returncode != 0:
+ log(' bitmap %d refused' % i)
+ log(' ' + iotests.filter_testfiles(add_out.stdout).strip())
+ break
+
+log('--- the ones which did fit are all still there')
+log(' %d bitmaps stored' % len(bitmaps_reported()))
+
+log('--- removing a bitmap reaches the image right away')
+make_image(STORED, OTHER)
+qsd = open_node()
+log(f' stored before: {len(bitmaps_in_file())} bitmaps')
+qsd.cmd('block-dirty-bitmap-remove', {'node': 'n0', 'name': STORED})
+log(f' stored after: {len(bitmaps_in_file())} bitmaps, '
+ 'without a clean close')
+qsd.stop()
+
+log('--- the in use flag comes back when a node is activated again')
+make_image(STORED)
+qsd = open_node()
+log(f' in use while open: {in_use()}')
+qsd.cmd('blockdev-set-active', {'node-name': 'n0', 'active': False})
+log(f' in use after deactivation: {in_use()}')
+qsd.cmd('blockdev-set-active', {'node-name': 'n0', 'active': True})
+log(f' in use after activation: {in_use()}')
+qsd.stop()
+
+# The bitmap data cluster is allocated right behind the only data cluster,
+# so it starts at sector 4096 of a 4M image with the default cluster size.
+log('--- a bitmap which can not be written is reported, not lost in silence')
+make_image(STORED, size='4M')
+err_opts = (f'driver={iotests.imgfmt},file.driver=blkdebug,'
+ f'file.image.driver=file,file.image.filename={stored},'
+ f'file.inject-error.0.event=none,'
+ f'file.inject-error.0.iotype=write,'
+ f'file.inject-error.0.sector=4096')
+err_out = qemu_io('--image-opts', err_opts, '-c', 'write -P 0x11 0 64k',
+ check=False)
+for err_line in iotests.filter_generated_node_ids(err_out.stdout).splitlines():
+ if 'Lost persistent bitmaps' in err_line:
+ log(' ' + err_line.strip())
+log(f' in use after the failure: {in_use()}')
+
+log('--- but the guest data went through, and the image says it is in use')
+read_out = qemu_io('-f', iotests.imgfmt, '-r', '-c', 'read -P 0x11 0 64k',
+ stored)
+log(read_out.stdout, filters=[iotests.filter_generated_node_ids,
+ iotests.filter_testfiles,
+ iotests.filter_qemu_io])
+log(' %d bitmaps stored' % len(bitmaps_reported()))
+
+log('--- and the image is still readable')
+info_out = qemu_img('info', '-f', iotests.imgfmt, stored, check=False)
+log('image opened' if info_out.returncode == 0
+ else iotests.filter_testfiles(info_out.stdout).strip())
diff --git a/tests/qemu-iotests/tests/parallels-bitmap.out b/tests/qemu-iotests/tests/parallels-bitmap.out
index ebfc0acb4d..994c67390e 100644
--- a/tests/qemu-iotests/tests/parallels-bitmap.out
+++ b/tests/qemu-iotests/tests/parallels-bitmap.out
@@ -47,3 +47,48 @@ image opened
Start NBD server
dirty 32-64 GiB
Kill NBD server
+--- three separate ranges
+Start NBD server
+Kill NBD server
+ dirty 0 MiB + 64 KiB
+ dirty 4 MiB + 128 KiB
+ dirty 40 MiB + 64 KiB
+--- the whole image
+Start NBD server
+Kill NBD server
+ dirty 0 MiB + 8192 KiB
+--- less than a cluster
+Start NBD server
+Kill NBD server
+ dirty 0 MiB + 1024 KiB
+--- the tail of the image
+Start NBD server
+Kill NBD server
+ dirty 6 MiB + 2048 KiB
+--- a bitmap too large for the Format Extension cluster
+qemu-img: Operation add on bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69 failed: Bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69 with granularity 512 does not fit into the Format Extension cluster: every bitmap of the image would need 16464 bytes of it, 488 are available
+--- a bitmap leaving no room for the end of features marker
+qemu-img: Operation add on bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69 failed: Bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69 with granularity 512 does not fit into the Format Extension cluster: every bitmap of the image would need 496 bytes of it, 488 are available
+--- a bitmap which does not fit next to the ones already stored
+ bitmap 8 refused
+ qemu-img: Operation add on bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a08 failed: Bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a08 with granularity 65536 does not fit into the Format Extension cluster: every bitmap of the image would need 536 bytes of it, 488 are available
+--- the ones which did fit are all still there
+ 7 bitmaps stored
+--- removing a bitmap reaches the image right away
+ stored before: 2 bitmaps
+ stored after: 1 bitmaps, without a clean close
+--- the in use flag comes back when a node is activated again
+ in use while open: True
+ in use after deactivation: False
+ in use after activation: True
+--- a bitmap which can not be written is reported, not lost in silence
+ qemu-io: Lost persistent bitmaps during inactivation of node 'NODE_NAME': Can't save dirty bitmap 9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69: IO error: Input/output error
+ in use after the failure: True
+--- but the guest data went through, and the image says it is in use
+qemu-io: warning: Dropping the Format Extension of node 'NODE_NAME', which does not look like one: Wrong parallels Format Extension magic: 0x1111111111111111, expected: 0xab234cef23dcea87
+read 65536/65536 bytes at offset 0
+64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+ 0 bitmaps stored
+--- and the image is still readable
+image opened
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v8 3/3] iotests: cover the qemu-img bitmap operations on parallels
2026-09-13 19:52 [PATCH v8 0/3] parallels: report the stored dirty bitmaps in qemu-img info Denis V. Lunev
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
2026-09-13 19:53 ` [PATCH v8 2/3] iotests: cover storing a parallels dirty bitmap Denis V. Lunev
@ 2026-09-13 19:53 ` Denis V. Lunev
2 siblings, 0 replies; 7+ messages in thread
From: Denis V. Lunev @ 2026-09-13 19:53 UTC (permalink / raw)
To: qemu-devel; +Cc: qemu-block, Denis V. Lunev, Stefan Hajnoczi
From: Denis V. Lunev <den@openvz.org>
qemu-img-bitmaps drives convert --bitmaps and every qemu-img bitmap
sub-command, but for qcow2 alone. It is built on a backing chain and a
rebase, which parallels has neither of, so half of it could not run
there and the format is left with no coverage of any of this.
Cover what applies in the parallels test instead. A copy made with
--bitmaps has to carry them, and raw has to refuse them. --merge and
--clear leave the bitmap count alone, --remove-all sweeps it to zero,
and --remove-all --add in one invocation leaves only the bitmap which
was just added.
An image which was not closed correctly is left out. parallels-checks
covers that already, next to the code which drops the bitmaps.
Cc: Stefan Hajnoczi <stefanha@redhat.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qemu-iotests/tests/parallels-bitmap | 39 +++++++++++++++++++
tests/qemu-iotests/tests/parallels-bitmap.out | 12 ++++++
2 files changed, 51 insertions(+)
diff --git a/tests/qemu-iotests/tests/parallels-bitmap b/tests/qemu-iotests/tests/parallels-bitmap
index be0ba8185d..8ae9314114 100755
--- a/tests/qemu-iotests/tests/parallels-bitmap
+++ b/tests/qemu-iotests/tests/parallels-bitmap
@@ -273,6 +273,7 @@ stored = file_path('stored')
copy = file_path('copy')
STORED = '9b7b8b2e-1c4a-4d3f-8e6a-1f2d3c4b5a69'
OTHER = '11111111-2222-3333-4444-555555555555'
+MERGED = '22222222-3333-4444-5555-666666666666'
def read_header(off, fmt):
@@ -434,3 +435,41 @@ log('--- and the image is still readable')
info_out = qemu_img('info', '-f', iotests.imgfmt, stored, check=False)
log('image opened' if info_out.returncode == 0
else iotests.filter_testfiles(info_out.stdout).strip())
+
+
+# qemu-img-bitmaps drives these for qcow2 alone, as it is built on a
+# backing chain and a rebase, which parallels has neither of.
+
+log('--- bitmaps can not be preserved into raw')
+make_image(STORED)
+raw_out = qemu_img('convert', '--bitmaps', '-f', iotests.imgfmt, '-O',
+ 'raw', stored, copy, check=False)
+log(' ' + iotests.filter_testfiles(raw_out.stdout).strip())
+
+log('--- convert --bitmaps carries the bitmaps into the copy')
+make_image(STORED, OTHER)
+qemu_io('-f', iotests.imgfmt, '-c', 'write -P 0x11 1M 64k', stored)
+qemu_img('convert', '--bitmaps', '-f', iotests.imgfmt, '-O', iotests.imgfmt,
+ stored, copy)
+copy_info = iotests.qemu_img_info('-f', iotests.imgfmt, copy)
+copy_data = copy_info.get('format-specific', {}).get('data', {})
+log(' %d bitmaps in the copy' % len(copy_data.get('bitmaps', [])))
+
+log('--- merging one bitmap into a new one')
+qemu_img('bitmap', '--add', '--merge', STORED, '-f', iotests.imgfmt,
+ stored, MERGED)
+log(' %d bitmaps stored' % len(bitmaps_reported()))
+
+log('--- clearing a bitmap keeps it in the image')
+qemu_img('bitmap', '--clear', '-f', iotests.imgfmt, stored, STORED)
+log(' %d bitmaps stored' % len(bitmaps_reported()))
+
+log('--- remove-all sweeps every bitmap of the image')
+qemu_img('bitmap', '--remove-all', '-f', iotests.imgfmt, stored)
+log(' %d bitmaps after remove-all' % len(bitmaps_reported()))
+
+log('--- remove-all with --add leaves only the new bitmap')
+make_image(STORED, OTHER)
+qemu_img('bitmap', '--remove-all', '--add', '-f', iotests.imgfmt,
+ stored, MERGED)
+log(' ' + ' '.join(bitmaps_reported()))
diff --git a/tests/qemu-iotests/tests/parallels-bitmap.out b/tests/qemu-iotests/tests/parallels-bitmap.out
index 994c67390e..df6f676106 100644
--- a/tests/qemu-iotests/tests/parallels-bitmap.out
+++ b/tests/qemu-iotests/tests/parallels-bitmap.out
@@ -92,3 +92,15 @@ read 65536/65536 bytes at offset 0
0 bitmaps stored
--- and the image is still readable
image opened
+--- bitmaps can not be preserved into raw
+ qemu-img: Format driver 'raw' does not support bitmaps
+--- convert --bitmaps carries the bitmaps into the copy
+ 2 bitmaps in the copy
+--- merging one bitmap into a new one
+ 3 bitmaps stored
+--- clearing a bitmap keeps it in the image
+ 3 bitmaps stored
+--- remove-all sweeps every bitmap of the image
+ 0 bitmaps after remove-all
+--- remove-all with --add leaves only the new bitmap
+ 22222222-3333-4444-5555-666666666666
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v8 1/3] parallels: report the stored dirty bitmaps in qemu-img info
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
@ 2026-09-15 7:58 ` Markus Armbruster
2026-10-06 15:11 ` Eric Blake
1 sibling, 0 replies; 7+ messages in thread
From: Markus Armbruster @ 2026-09-15 7:58 UTC (permalink / raw)
To: Denis V. Lunev; +Cc: qemu-devel, qemu-block, Stefan Hajnoczi, Eric Blake
"Denis V. Lunev" <den@openvz.org> writes:
> From: Denis V. Lunev <den@openvz.org>
>
> Nothing tells which persistent bitmaps an image carries. qemu-img info
> says nothing about them, and the only other way to see one is to export
> the image over NBD and ask for a bitmap by name, which needs the name
> beforehand.
>
> Add ImageInfoSpecificParallels with the bitmaps and their granularity,
> in the same way as qcow2 reports the contents of its bitmap directory:
>
> Format specific information:
> bitmaps:
> [0]:
> name: b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45
> granularity: 65536
>
> For running VM process the list is built on the fly from in-memory
> list. This is the best we can do. FormatExtension could be dead in
> the image while VMs are running as all bitmaps are cleared on
> non-clean VM stop.
>
> A bitmap of an image which was not closed correctly is inconsistent
> and can not be used. Report that as well, the way qcow2 reports its
> in-use flag, so such a bitmap is not listed as a valid one.
>
> The section is left empty for an image with no persistent dirty
> bitmaps, and an empty section is not printed, so the human readable
> output of "qemu-img info" and "info block" is unchanged.
>
> Cc: Stefan Hajnoczi <stefanha@redhat.com>
> Cc: Eric Blake <eblake@redhat.com>
> Cc: Markus Armbruster <armbru@redhat.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
QAPI schema
Acked-by: Markus Armbruster <armbru@redhat.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v8 1/3] parallels: report the stored dirty bitmaps in qemu-img info
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
2026-09-15 7:58 ` Markus Armbruster
@ 2026-10-06 15:11 ` Eric Blake
2026-10-06 15:20 ` Denis V. Lunev
1 sibling, 1 reply; 7+ messages in thread
From: Eric Blake @ 2026-10-06 15:11 UTC (permalink / raw)
To: Denis V. Lunev; +Cc: qemu-devel, qemu-block, Stefan Hajnoczi, Markus Armbruster
On Sun, Sep 13, 2026 at 09:52:59PM +0200, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
>
> Nothing tells which persistent bitmaps an image carries. qemu-img info
> says nothing about them, and the only other way to see one is to export
> the image over NBD and ask for a bitmap by name, which needs the name
> beforehand.
I would amend that statement, "s/an image/a parallels image/"; because
existing 'qemu-img info' definitely tells you what bitmaps a qcow2
image has:
$ qemu-img create -f qcow2 base.qcow2 100M
Formatting 'base.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=104857600 lazy_refcounts=off refcount_bits=16
$ qemu-img bitmap --add -f qcow2 base.qcow2 bm1
$ qemu-img bitmap --add -f qcow2 base.qcow2 bm2
$ qemu-img info base.qcow2
image: base.qcow2
file format: qcow2
virtual size: 100 MiB (104857600 bytes)
disk size: 220 KiB
cluster_size: 65536
Format specific information:
compat: 1.1
compression type: zlib
lazy refcounts: false
bitmaps:
[0]:
flags:
[0]: auto
name: bm1
granularity: 65536
[1]:
flags:
[0]: auto
name: bm2
granularity: 65536
refcount bits: 16
...
Also, the NBD protocol indeed allows a client to query what
metacontexts an image supports, which can be used to expose bitmaps;
and both 'qemu-nbd -L' and libnbd's 'nbdinfo' are happy to read that
information. But, at present, qemu-nbd limits us to exposing that on
a per-bitmap basis, rather than having an easy way to export all
bitmaps at once. For example,
$ qemu-nbd -L
exports available: 1
export: ''
...
available meta contexts: 4
base:allocation
qemu:allocation-depth
qemu:dirty-bitmap:bm2
qemu:dirty-bitmap:bm1
$ nbdinfo nbd://localhost
protocol: newstyle-fixed without TLS, using extended packets
export="":
...
contexts:
base:allocation
qemu:allocation-depth
qemu:dirty-bitmap:bm2
qemu:dirty-bitmap:bm1
...
So I will probably submit a patch to add a command-line flag to
qemu-nbd to expose all known bitmaps without having to do a -B option
per name, on top of this series.
>
> Add ImageInfoSpecificParallels with the bitmaps and their granularity,
> in the same way as qcow2 reports the contents of its bitmap directory:
>
> Format specific information:
> bitmaps:
> [0]:
> name: b2c9e1a4-5d3f-4e8b-9a7c-6f0d1e2b3a45
> granularity: 65536
>
> For running VM process the list is built on the fly from in-memory
> list. This is the best we can do. FormatExtension could be dead in
> the image while VMs are running as all bitmaps are cleared on
> non-clean VM stop.
>
> A bitmap of an image which was not closed correctly is inconsistent
> and can not be used. Report that as well, the way qcow2 reports its
> in-use flag, so such a bitmap is not listed as a valid one.
>
> The section is left empty for an image with no persistent dirty
> bitmaps, and an empty section is not printed, so the human readable
> output of "qemu-img info" and "info block" is unchanged.
>
> Cc: Stefan Hajnoczi <stefanha@redhat.com>
> Cc: Eric Blake <eblake@redhat.com>
> Cc: Markus Armbruster <armbru@redhat.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> ---
> block/parallels-ext.c | 26 +++++++++++
> block/parallels.c | 20 +++++++++
> block/parallels.h | 3 ++
> qapi/block-core.json | 45 ++++++++++++++++++-
> tests/qemu-iotests/tests/parallels-checks | 6 +++
> tests/qemu-iotests/tests/parallels-checks.out | 19 ++++++++
> 6 files changed, 117 insertions(+), 2 deletions(-)
>
> +++ b/qapi/block-core.json
> +##
> +# @ParallelsBitmapInfo:
> +#
> +# Parallels bitmap information.
> +#
> +# @name: the name of the bitmap
> +#
> +# @granularity: granularity of the bitmap in bytes
> +#
> +# @inconsistent: true if the bitmap is in an inconsistent state and
> +# cannot be used
> +#
> +# Since: 11.2
> +##
> +{ 'struct': 'ParallelsBitmapInfo',
> + 'data': { 'name': 'str', 'granularity': 'uint32',
> + '*inconsistent': 'bool' } }
Should @inconsistent mention that it is default false, since it is
listed as optional? Or is there any reason why we should not output an
explicit value of false and make the field non-optional?
--
Eric Blake, Principal Software Engineer
Red Hat, Inc.
Virtualization: qemu.org | libguestfs.org
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v8 1/3] parallels: report the stored dirty bitmaps in qemu-img info
2026-10-06 15:11 ` Eric Blake
@ 2026-10-06 15:20 ` Denis V. Lunev
0 siblings, 0 replies; 7+ messages in thread
From: Denis V. Lunev @ 2026-10-06 15:20 UTC (permalink / raw)
To: Eric Blake, Denis V. Lunev
Cc: qemu-devel, qemu-block, Stefan Hajnoczi, Markus Armbruster
On 10/6/26 17:11, Eric Blake wrote:
> On Sun, Sep 13, 2026 at 09:52:59PM +0200, Denis V. Lunev wrote:
>> From: Denis V. Lunev <den@openvz.org>
>>
>> Nothing tells which persistent bitmaps an image carries. qemu-img info
>> says nothing about them, and the only other way to see one is to export
>> the image over NBD and ask for a bitmap by name, which needs the name
>> beforehand.
> I would amend that statement, "s/an image/a parallels image/"; because
> existing 'qemu-img info' definitely tells you what bitmaps a qcow2
> image has:
>
> $ qemu-img create -f qcow2 base.qcow2 100M
> Formatting 'base.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=104857600 lazy_refcounts=off refcount_bits=16
> $ qemu-img bitmap --add -f qcow2 base.qcow2 bm1
> $ qemu-img bitmap --add -f qcow2 base.qcow2 bm2
> $ qemu-img info base.qcow2
> image: base.qcow2
> file format: qcow2
> virtual size: 100 MiB (104857600 bytes)
> disk size: 220 KiB
> cluster_size: 65536
> Format specific information:
> compat: 1.1
> compression type: zlib
> lazy refcounts: false
> bitmaps:
> [0]:
> flags:
> [0]: auto
> name: bm1
> granularity: 65536
> [1]:
> flags:
> [0]: auto
> name: bm2
> granularity: 65536
> refcount bits: 16
> ...
>
>
> Also, the NBD protocol indeed allows a client to query what
> metacontexts an image supports, which can be used to expose bitmaps;
> and both 'qemu-nbd -L' and libnbd's 'nbdinfo' are happy to read that
> information. But, at present, qemu-nbd limits us to exposing that on
> a per-bitmap basis, rather than having an easy way to export all
> bitmaps at once. For example,
>
> $ qemu-nbd -L
> exports available: 1
> export: ''
> ...
> available meta contexts: 4
> base:allocation
> qemu:allocation-depth
> qemu:dirty-bitmap:bm2
> qemu:dirty-bitmap:bm1
> $ nbdinfo nbd://localhost
> protocol: newstyle-fixed without TLS, using extended packets
> export="":
> ...
> contexts:
> base:allocation
> qemu:allocation-depth
> qemu:dirty-bitmap:bm2
> qemu:dirty-bitmap:bm1
> ...
>
> So I will probably submit a patch to add a command-line flag to
> qemu-nbd to expose all known bitmaps without having to do a -B option
> per name, on top of this series.
That is a good point. I have done similar with
commit 7e8cc6c886, "qemu-img: add sub-command --remove-all to 'qemu-img
bitmap'".
Den
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-07 8:58 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-13 19:52 [PATCH v8 0/3] parallels: report the stored dirty bitmaps in qemu-img info Denis V. Lunev
2026-09-13 19:52 ` [PATCH v8 1/3] " Denis V. Lunev
2026-09-15 7:58 ` Markus Armbruster
2026-10-06 15:11 ` Eric Blake
2026-10-06 15:20 ` Denis V. Lunev
2026-09-13 19:53 ` [PATCH v8 2/3] iotests: cover storing a parallels dirty bitmap Denis V. Lunev
2026-09-13 19:53 ` [PATCH v8 3/3] iotests: cover the qemu-img bitmap operations on parallels Denis V. Lunev
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.