qemu-nbd: Support -s and -l at the same time

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>
This commit is contained in:
Eric Blake committed 2026-10-16 09:08:52 -05:00
1 parent b8c0e25800
commit 67a2606004
4 files changed
+143 -6

No files matched your search

+4 -3
View File
@@ -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
+19 -3
View File
@@ -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) {
+81
View File
@@ -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
@@ -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