From: Eric Blake <[email protected]> When exporting bitmaps over NBD, we previously required a user to pass one '-B name' per bitmap, which they would have to use 'qemu-img info' to learn the list of names to pass. More convenient is an option to export all possible bitmaps at once, as discussed here [1].
Implementation-wise, mixing --all-bitmaps and -B is harmless (grabbing all bitmap names already requires crawling the backing chain and deduplicating, so either the -B name is already detected as a duplicate from adding the name again, or it is an invalid name that will still cause a later error about bitmap not found). I also tested that mixing -s and --all-bitmaps still works (the temporary overlay image to make -s work has no bitmaps of its own, so starting the crawl for bitmap names at the original bs is just fine). Also, now that parallels can have bitmaps (and not just qcow2), a slight doc wording change for -B is warranted. [1] https://lists.nongnu.org/archive/html/qemu-devel/2026-10/msg01234.html Signed-off-by: Eric Blake <[email protected]> CC: Denis V. Lunev <[email protected]> Message-ID: <[email protected]> Reviewed-by: Denis V. Lunev <[email protected]> Reviewed-by: Kevin Wolf <[email protected]> Signed-off-by: Kevin Wolf <[email protected]> --- docs/tools/qemu-nbd.rst | 12 +++- qemu-nbd.c | 57 ++++++++++++++++++- tests/qemu-iotests/tests/qemu-img-bitmaps | 13 +++++ tests/qemu-iotests/tests/qemu-img-bitmaps.out | 12 ++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/docs/tools/qemu-nbd.rst b/docs/tools/qemu-nbd.rst index 336b4aa46e7..a966c282be2 100644 --- a/docs/tools/qemu-nbd.rst +++ b/docs/tools/qemu-nbd.rst @@ -83,9 +83,15 @@ driver options if :option:`--image-opts` is specified. .. option:: -B, --bitmap=NAME - If *filename* has a qcow2 persistent bitmap *NAME*, expose - that bitmap via the ``qemu:dirty-bitmap:NAME`` metadata context - accessible through NBD_OPT_SET_META_CONTEXT. + If *filename* has a persistent bitmap *NAME* (possibly via a backing + chain), expose that bitmap via the ``qemu:dirty-bitmap:NAME`` metadata + context accessible through NBD_OPT_SET_META_CONTEXT. + +.. option:: --all-bitmaps + + If *filename* has any persistent bitmaps (possibly via a backing + chain), expose one ``qemu:dirty-bitmap:NAME`` metadata context per + bitmap, accessible through NBD_OPT_SET_META_CONTEXT. .. option:: -s, --snapshot diff --git a/qemu-nbd.c b/qemu-nbd.c index fcca88932a3..fa28609d7dc 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -28,6 +28,7 @@ #include "system/runstate.h" /* for qemu_system_killed() prototype */ #include "block/block_int.h" #include "block/nbd.h" +#include "block/dirty-bitmap.h" #include "qemu/main-loop.h" #include "qemu/module.h" #include "qemu/option.h" @@ -71,6 +72,7 @@ #define QEMU_NBD_OPT_SELINUX_LABEL 266 #define QEMU_NBD_OPT_TLSHOSTNAME 267 #define QEMU_NBD_OPT_HANDSHAKE_LIMIT 268 +#define QEMU_NBD_OPT_ALL_BITMAPS 269 #define MBR_SIZE 512 @@ -109,6 +111,7 @@ static void usage(const char *name) " -o, --offset=OFFSET offset into the image\n" " -A, --allocation-depth expose the allocation depth\n" " -B, --bitmap=NAME expose a persistent dirty bitmap\n" +" --all-bitmaps expose all persistent dirty bitmaps\n" "\n" "General purpose options:\n" " -L, --list list exports available from another NBD server\n" @@ -530,6 +533,48 @@ static void qemu_nbd_shutdown(void) bdrv_close_all(); } +/* Helpers to implement --all-bitmaps option. */ +static bool is_duplicate_bitmap(BlockDirtyBitmapOrStrList *bitmaps, + const char *name) +{ + /* Treat an anonymous bitmap as a duplicate. */ + if (!name) { + return true; + } + for (; bitmaps; bitmaps = bitmaps->next) { + if (bitmaps->value->type == QTYPE_QSTRING && + strcmp(bitmaps->value->u.local, name) == 0) { + return true; + } + } + return false; +} + +static void collect_all_bitmaps(BlockDriverState *bs, + BlockDirtyBitmapOrStrList **bitmaps) +{ + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); + + for (; bs; bs = bdrv_filter_or_cow_bs(bs)) { + for (BdrvDirtyBitmap *bm = bdrv_dirty_bitmap_first(bs); bm; + bm = bdrv_dirty_bitmap_next(bm)) { + const char *name = bdrv_dirty_bitmap_name(bm); + BlockDirtyBitmapOrStr *el; + + if (is_duplicate_bitmap(*bitmaps, name)) { + continue; + } + el = g_new(BlockDirtyBitmapOrStr, 1); + *el = (BlockDirtyBitmapOrStr) { + .type = QTYPE_QSTRING, + .u.local = g_strdup(name), + }; + QAPI_LIST_PREPEND(*bitmaps, el); + } + } +} + int main(int argc, char **argv) { BlockBackend *blk; @@ -553,6 +598,7 @@ int main(int argc, char **argv) { "offset", required_argument, NULL, 'o' }, { "read-only", no_argument, NULL, 'r' }, { "allocation-depth", no_argument, NULL, 'A' }, + { "all-bitmaps", no_argument, NULL, QEMU_NBD_OPT_ALL_BITMAPS }, { "bitmap", required_argument, NULL, 'B' }, { "connect", required_argument, NULL, 'c' }, { "disconnect", no_argument, NULL, 'd' }, @@ -602,6 +648,7 @@ int main(int argc, char **argv) const char *export_description = NULL; BlockDirtyBitmapOrStrList *bitmaps = NULL; bool alloc_depth = false; + bool all_bitmaps = false; const char *tlscredsid = NULL; const char *tlshostname = NULL; bool imageOpts = false; @@ -725,6 +772,9 @@ int main(int argc, char **argv) case 'A': alloc_depth = true; break; + case QEMU_NBD_OPT_ALL_BITMAPS: + all_bitmaps = true; + break; case 'B': { BlockDirtyBitmapOrStr *el = g_new(BlockDirtyBitmapOrStr, 1); @@ -837,7 +887,8 @@ int main(int argc, char **argv) } if (export_name || export_description || dev_offset || opts.device || disconnect || fmt || sn_id_or_name || bitmaps || - alloc_depth || seen_aio || seen_discard || seen_cache) { + all_bitmaps || alloc_depth || seen_aio || seen_discard || + seen_cache) { error_report("List mode is incompatible with per-device settings"); exit(EXIT_FAILURE); } @@ -1185,6 +1236,10 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } + if (all_bitmaps) { + collect_all_bitmaps(orig_bs, &bitmaps); + } + bs->detect_zeroes = detect_zeroes; nbd_server_is_qemu_nbd(shared); diff --git a/tests/qemu-iotests/tests/qemu-img-bitmaps b/tests/qemu-iotests/tests/qemu-img-bitmaps index 979bc0d6c9c..5484fe6f424 100755 --- a/tests/qemu-iotests/tests/qemu-img-bitmaps +++ b/tests/qemu-iotests/tests/qemu-img-bitmaps @@ -131,6 +131,19 @@ $QEMU_IMG map --output=json --image-opts \ nbd_server_stop +# Repeat with the --all-bitmaps option +nbd_server_start_unix_socket -r -f qcow2 --all-bitmaps "$TEST_IMG" +$QEMU_IMG map --output=json --image-opts \ + "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b0" | _filter_qemu_img_map +$QEMU_IMG map --output=json --image-opts \ + "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b1" | _filter_qemu_img_map +$QEMU_IMG map --output=json --image-opts \ + "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b2" | _filter_qemu_img_map +$QEMU_IMG map --output=json --image-opts \ + "$IMG,x-dirty-bitmap=qemu:dirty-bitmap:b3" | _filter_qemu_img_map + +nbd_server_stop + echo echo "=== Check handling of inconsistent bitmap ===" echo diff --git a/tests/qemu-iotests/tests/qemu-img-bitmaps.out b/tests/qemu-iotests/tests/qemu-img-bitmaps.out index 99290d3ba5e..f2e97a090b0 100644 --- a/tests/qemu-iotests/tests/qemu-img-bitmaps.out +++ b/tests/qemu-iotests/tests/qemu-img-bitmaps.out @@ -103,6 +103,18 @@ Format specific information: === Check bitmap contents === +[{ "start": 0, "length": 3145728, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}, +{ "start": 3145728, "length": 1048576, "depth": 0, "present": false, "zero": false, "data": false, "compressed": false}, +{ "start": 4194304, "length": 6291456, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}] +[{ "start": 0, "length": 1048576, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}, +{ "start": 1048576, "length": 1048576, "depth": 0, "present": false, "zero": false, "data": false, "compressed": false}, +{ "start": 2097152, "length": 8388608, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}] +[{ "start": 0, "length": 2097152, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}, +{ "start": 2097152, "length": 1048576, "depth": 0, "present": false, "zero": false, "data": false, "compressed": false}, +{ "start": 3145728, "length": 7340032, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}] +[{ "start": 0, "length": 2097152, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}, +{ "start": 2097152, "length": 1048576, "depth": 0, "present": false, "zero": false, "data": false, "compressed": false}, +{ "start": 3145728, "length": 7340032, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}] [{ "start": 0, "length": 3145728, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}, { "start": 3145728, "length": 1048576, "depth": 0, "present": false, "zero": false, "data": false, "compressed": false}, { "start": 4194304, "length": 6291456, "depth": 0, "present": true, "zero": false, "data": true, "compressed": false, "offset": OFFSET}] -- 2.56.0
