From: Eric Blake <eblake@redhat.com>
To: qemu-devel@nongnu.org
Cc: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>,
Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>,
Kevin Wolf <kwolf@redhat.com>, Hanna Reitz <hreitz@redhat.com>,
qemu-block@nongnu.org (open list:Network Block Dev...)
Subject: [PULL v2 02/15] qemu-nbd: Support -s and -l at the same time
Date: Mon, 12 Oct 2026 16:29:06 -0500 [thread overview]
Message-ID: <20261012212945.84275-19-eblake@redhat.com> (raw)
In-Reply-To: <20261012212945.84275-17-eblake@redhat.com>
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>
Message-ID: <20260408021636.794794-2-eblake@redhat.com>
Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
[eblake: improve test with Vladimir's suggestions, add lock to satisfy TSAN]
Signed-off-by: Eric Blake <eblake@redhat.com>
---
docs/tools/qemu-nbd.rst | 7 +-
qemu-nbd.c | 22 ++++-
tests/qemu-iotests/tests/qemu-nbd-snapshots | 81 +++++++++++++++++++
.../qemu-iotests/tests/qemu-nbd-snapshots.out | 39 +++++++++
4 files changed, 143 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..fcca88932a3 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,19 @@ int main(int argc, char **argv)
blk_set_enable_write_cache(blk, !writethrough);
+ orig_bs = bs;
+ if (flags & BDRV_O_SNAPSHOT) {
+ WITH_GRAPH_RDLOCK_GUARD() {
+ 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..ce62e97877b
--- /dev/null
+++ b/tests/qemu-iotests/tests/qemu-nbd-snapshots
@@ -0,0 +1,81 @@
+#!/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' -c 'r -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
+$QEMU_IO -c 'r -P 1 3M 1M' -f raw \
+ nbd:unix:$nbd_unix_socket 2>&1 | _filter_qemu_io | _filter_testdir
+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' -c 'r -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..47289c30f1d
--- /dev/null
+++ b/tests/qemu-iotests/tests/qemu-nbd-snapshots.out
@@ -0,0 +1,39 @@
+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 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)
+qemu-io: can't open device nbd:unix:SOCK_DIR/qemu-nbd.sock: Could not open image: Permission denied
+
+=== 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)
+read 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.56.0
next prev parent reply other threads:[~2026-10-12 21:30 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 21:29 [PULL v2 00/15] NBD patches through 2026-10-08 Eric Blake
2026-10-12 21:29 ` Eric Blake [this message]
2026-10-14 8:02 ` Richard Henderson
2026-10-14 18:07 ` 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=20261012212945.84275-19-eblake@redhat.com \
--to=eblake@redhat.com \
--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.