From: Eric Blake <eblake@redhat.com>
To: qemu-devel@nongnu.org
Cc: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>,
Pierrick Bouvier <pierrick.bouvier@linaro.org>,
Kevin Wolf <kwolf@redhat.com>, Hanna Reitz <hreitz@redhat.com>,
qemu-block@nongnu.org (open list:Network Block Dev...)
Subject: [PATCH] qemu-nbd: Support -s and -l at the same time
Date: Tue, 7 Apr 2026 16:36:17 -0500 [thread overview]
Message-ID: <20260407213652.769100-2-eblake@redhat.com> (raw)
qemu-nbd already has the ability to wrap a read-write layer on top of
the export of a read-only image (-s) and the ability to export an
internal snapshot rather than the main image in a qcow2 file (-l
SNAPID). But it gave a confusing error message when trying to mix the
two, because the snapshot overlay created first no longer has access
to the internal snapshots of the image being wrapped.
WIth a bit of finesse, this useful scenario is now supported.
https://gitlab.com/qemu-project/qemu/-/work_items/159
Signed-off-by: Eric Blake <eblake@redhat.com>
---
docs/tools/qemu-nbd.rst | 7 +-
qemu-nbd.c | 20 ++++-
tests/qemu-iotests/tests/qemu-nbd-snapshots | 79 +++++++++++++++++++
.../qemu-iotests/tests/qemu-nbd-snapshots.out | 34 ++++++++
4 files changed, 134 insertions(+), 6 deletions(-)
create mode 100755 tests/qemu-iotests/tests/qemu-nbd-snapshots
create mode 100644 tests/qemu-iotests/tests/qemu-nbd-snapshots.out
diff --git a/docs/tools/qemu-nbd.rst b/docs/tools/qemu-nbd.rst
index f82ea5fd77b..336b4aa46e7 100644
--- a/docs/tools/qemu-nbd.rst
+++ b/docs/tools/qemu-nbd.rst
@@ -95,9 +95,10 @@ driver options if :option:`--image-opts` is specified.
.. option:: -l, --load-snapshot=SNAPSHOT_PARAM
- Load an internal snapshot inside *filename* and export it
- as an read-only device, SNAPSHOT_PARAM format is
- ``snapshot.id=[ID],snapshot.name=[NAME]`` or ``[ID_OR_NAME]``
+ Load an internal snapshot inside *filename* and export it.
+ SNAPSHOT_PARAM format is ``snapshot.id=[ID],snapshot.name=[NAME]``
+ or ``[ID_OR_NAME]``. The export is read-only unless ``-s`` is also
+ in use.
.. option:: --cache=CACHE
diff --git a/qemu-nbd.c b/qemu-nbd.c
index ed5895861bb..128486a0ec2 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -534,6 +534,7 @@ int main(int argc, char **argv)
{
BlockBackend *blk;
BlockDriverState *bs;
+ BlockDriverState *orig_bs;
uint64_t dev_offset = 0;
bool readonly = false;
bool disconnect = false;
@@ -716,7 +717,7 @@ int main(int argc, char **argv)
} else {
sn_id_or_name = optarg;
}
- /* fall through */
+ break;
case 'r':
readonly = true;
flags &= ~BDRV_O_RDWR;
@@ -1110,6 +1111,15 @@ int main(int argc, char **argv)
bdrv_init();
atexit(qemu_nbd_shutdown);
+ /*
+ * -s and -l can be used together to create read-write wrapper
+ * around an internal snapshot. Otherwise, -l in isolation implies
+ * read-only.
+ */
+ if ((sn_opts || sn_id_or_name) && (flags & BDRV_O_SNAPSHOT) == 0) {
+ readonly = true;
+ flags &= ~BDRV_O_RDWR;
+ }
opts.srcpath = argv[optind];
if (imageOpts) {
QemuOpts *o;
@@ -1155,13 +1165,17 @@ int main(int argc, char **argv)
blk_set_enable_write_cache(blk, !writethrough);
+ orig_bs = bs;
+ if (flags & BDRV_O_SNAPSHOT) {
+ orig_bs = bs->backing->bs;
+ }
if (sn_opts) {
- ret = bdrv_snapshot_load_tmp(bs,
+ ret = bdrv_snapshot_load_tmp(orig_bs,
qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID),
qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME),
&local_err);
} else if (sn_id_or_name) {
- ret = bdrv_snapshot_load_tmp_by_id_or_name(bs, sn_id_or_name,
+ ret = bdrv_snapshot_load_tmp_by_id_or_name(orig_bs, sn_id_or_name,
&local_err);
}
if (ret < 0) {
diff --git a/tests/qemu-iotests/tests/qemu-nbd-snapshots b/tests/qemu-iotests/tests/qemu-nbd-snapshots
new file mode 100755
index 00000000000..de405e43720
--- /dev/null
+++ b/tests/qemu-iotests/tests/qemu-nbd-snapshots
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+# group: rw quick
+#
+# Test qemu-nbd snapshot handling
+#
+# Copyright (C) Red Hat, Inc.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+seq="$(basename $0)"
+echo "QA output created by $seq"
+
+status=1 # failure is the default!
+
+_cleanup()
+{
+ _cleanup_test_img
+ nbd_server_stop
+}
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+# get standard environment, filters and checks
+cd ..
+. ./common.rc
+. ./common.filter
+. ./common.nbd
+
+_supported_fmt qcow2
+_supported_proto file
+_supported_os Linux
+_require_command QEMU_NBD
+
+echo
+echo "=== Initial image setup ==="
+echo
+
+# Create qcow2 image with one internal snapshot
+_make_test_img 10M
+$QEMU_IO -c 'w -P 1 3M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
+$QEMU_IMG snapshot -c snap -f $IMGFMT "$TEST_IMG" | _filter_qemu_img
+$QEMU_IO -c 'w -P 2 3M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
+
+echo
+echo "=== Main image in snapshot mode ==="
+echo
+
+nbd_server_start_unix_socket -s -f qcow2 "$TEST_IMG"
+$QEMU_IO -c 'r -P 2 3M 1M' -c 'w -P 3 3M 1M' -f raw \
+ nbd:unix:$nbd_unix_socket | _filter_qemu_io
+nbd_server_stop
+
+echo
+echo "=== Read-only snapshot access ==="
+echo
+
+nbd_server_start_unix_socket -l snap -f qcow2 "$TEST_IMG"
+$QEMU_IO -r -c 'r -P 1 3M 1M' -f raw \
+ nbd:unix:$nbd_unix_socket | _filter_qemu_io
+nbd_server_stop
+
+echo
+echo "=== Combine -s and -l for read-write internal snapshot access ==="
+echo
+
+nbd_server_start_unix_socket -s -l snap -f qcow2 "$TEST_IMG"
+$QEMU_IO -c 'r -P 1 3M 1M' -c 'w -P 3 3M 1M' -f raw \
+ nbd:unix:$nbd_unix_socket | _filter_qemu_io
+nbd_server_stop
+
+echo
+echo "=== Check that original image is unchanged ==="
+echo
+
+$QEMU_IO -c 'r -P 2 3M 1M' -f $IMGFMT "$TEST_IMG" | _filter_qemu_io
+
+# success, all done
+echo '*** done'
+rm -f $seq.full
+status=0
diff --git a/tests/qemu-iotests/tests/qemu-nbd-snapshots.out b/tests/qemu-iotests/tests/qemu-nbd-snapshots.out
new file mode 100644
index 00000000000..6bce9d90253
--- /dev/null
+++ b/tests/qemu-iotests/tests/qemu-nbd-snapshots.out
@@ -0,0 +1,34 @@
+QA output created by qemu-nbd-snapshots
+
+=== Initial image setup ===
+
+Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=10485760
+wrote 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+=== Main image in snapshot mode ===
+
+read 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+=== Read-only snapshot access ===
+
+read 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+=== Combine -s and -l for read-write internal snapshot access ===
+
+read 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+wrote 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+
+=== Check that original image is unchanged ===
+
+read 1048576/1048576 bytes at offset 3145728
+1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
+*** done
--
2.53.0
next reply other threads:[~2026-04-08 18:49 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-07 21:36 Eric Blake [this message]
-- strict thread matches above, loose matches on Subject: below --
2026-04-08 2:16 [PATCH] qemu-nbd: Support -s and -l at the same time Eric Blake
2026-04-08 8:03 ` Vladimir Sementsov-Ogievskiy
2026-10-06 16:18 ` Eric Blake
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=20260407213652.769100-2-eblake@redhat.com \
--to=eblake@redhat.com \
--cc=hreitz@redhat.com \
--cc=kwolf@redhat.com \
--cc=pierrick.bouvier@linaro.org \
--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.