From: Eric Blake <eblake@redhat.com>
To: qemu-devel@nongnu.org
Cc: qemu-block@nongnu.org, "Denis V. Lunev" <den@openvz.org>,
Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>,
Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>,
Kevin Wolf <kwolf@redhat.com>, Hanna Reitz <hreitz@redhat.com>
Subject: [PATCH] qemu-nbd: Add --all-bitmaps option
Date: Mon, 19 Oct 2026 17:22:02 -0500 [thread overview]
Message-ID: <20261019222205.2028202-2-eblake@redhat.com> (raw)
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 <eblake@redhat.com>
CC: Denis V. Lunev <den@openvz.org>
---
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
next reply other threads:[~2026-10-19 22:22 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 22:22 Eric Blake [this message]
2026-10-20 10:57 ` [PATCH] qemu-nbd: Add --all-bitmaps option Denis V. Lunev
2026-10-30 11:01 ` Kevin Wolf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261019222205.2028202-2-eblake@redhat.com \
--to=eblake@redhat.com \
--cc=den@openvz.org \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=pierrick.bouvier@oss.qualcomm.com \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=vsementsov@yandex-team.ru \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.