Commit 0bb4f47124 for qemu.org

commit 0bb4f47124b60102d10ec6b6ad45385f5a3bbc3d
Author: Denis V. Lunev <den@openvz.org>
Date:   Fri Aug 14 23:02:11 2026 +0200

    parallels: report the stored dirty bitmaps in qemu-img info

    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>
    Acked-by: Markus Armbruster <armbru@redhat.com>

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, &parallels_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