Prove that we can round-trip a block status request larger than 4G
through a new-enough qemu-nbd. Also serves as a unit test of our shim
for converting internal 64-bit representation back to the older 32-bit
nbd_block_status callback interface.
---
interop/Makefile.am | 6 ++
interop/large-status.c | 186 ++++++++++++++++++++++++++++++++++++++++
interop/large-status.sh | 49 +++++++++++
.gitignore | 1 +
4 files changed, 242 insertions(+)
create mode 100644 interop/large-status.c
create mode 100755 interop/large-status.sh
diff --git a/interop/Makefile.am b/interop/Makefile.am
index 5e0ea1ed..84c8fbbb 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -21,6 +21,7 @@ EXTRA_DIST = \
dirty-bitmap.sh \
interop-qemu-storage-daemon.sh \
interop-qemu-block-size.sh \
+ large-status.sh \
list-exports-nbd-config \
list-exports-test-dir/disk1 \
list-exports-test-dir/disk2 \
@@ -134,6 +135,7 @@ check_PROGRAMS += \
list-exports-qemu-nbd \
socket-activation-qemu-nbd \
dirty-bitmap \
+ large-status \
structured-read \
opt-extended-headers \
$(NULL)
@@ -144,6 +146,7 @@ TESTS += \
list-exports-qemu-nbd \
socket-activation-qemu-nbd \
dirty-bitmap.sh \
+ large-status.sh \
structured-read.sh \
interop-qemu-block-size.sh \
opt-extended-headers.sh \
@@ -235,6 +238,9 @@ socket_activation_qemu_nbd_LDADD = $(top_builddir)/lib/libnbd.la
dirty_bitmap_SOURCES = dirty-bitmap.c
dirty_bitmap_LDADD = $(top_builddir)/lib/libnbd.la
+large_status_SOURCES = large-status.c
+large_status_LDADD = $(top_builddir)/lib/libnbd.la
+
structured_read_SOURCES = structured-read.c
structured_read_LDADD = $(top_builddir)/lib/libnbd.la
diff --git a/interop/large-status.c b/interop/large-status.c
new file mode 100644
index 00000000..44a911d7
--- /dev/null
+++ b/interop/large-status.c
@@ -0,0 +1,186 @@
+/* NBD client library in userspace
+ * Copyright (C) 2013-2022 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Test 64-bit block status with qemu. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <libnbd.h>
+
+static const char *bitmap;
+
+struct data {
+ bool req_one; /* input: true if req_one was passed to request */
+ int count; /* input: count of expected remaining calls */
+ bool seen_base; /* output: true if base:allocation encountered */
+ bool seen_dirty; /* output: true if qemu:dirty-bitmap encountered */
+};
+
+static int
+cb32 (void *opaque, const char *metacontext, uint64_t offset,
+ uint32_t *entries, size_t len, int *error)
+{
+ struct data *data = opaque;
+
+ assert (offset == 0);
+ assert (data->count-- > 0);
+
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0) {
+ assert (!data->seen_base);
+ data->seen_base = true;
+
+ /* Data block offset 0 size 64k, remainder is hole */
+ assert (len == 4);
+ assert (entries[0] == 65536);
+ assert (entries[1] == 0);
+ /* libnbd had to truncate qemu's >4G answer */
+ assert (entries[2] == 4227858432);
+ assert (entries[3] == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ }
+ else if (strcmp (metacontext, bitmap) == 0) {
+ assert (!data->seen_dirty);
+ data->seen_dirty = true;
+
+ /* Dirty block at offset 5G-64k, remainder is clean */
+ /* libnbd had to truncate qemu's >4G answer */
+ assert (len == 2);
+ assert (entries[0] == 4227858432);
+ assert (entries[1] == 0);
+ }
+ else {
+ fprintf (stderr, "unexpected context %s\n", metacontext);
+ exit (EXIT_FAILURE);
+ }
+ return 0;
+}
+
+static int
+cb64 (void *opaque, const char *metacontext, uint64_t offset,
+ nbd_extent *entries, size_t len, int *error)
+{
+ struct data *data = opaque;
+
+ assert (offset == 0);
+ assert (data->count-- > 0);
+
+ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0) {
+ assert (!data->seen_base);
+ data->seen_base = true;
+
+ /* Data block offset 0 size 64k, remainder is hole */
+ assert (len == 2);
+ assert (entries[0].length == 65536);
+ assert (entries[0].flags == 0);
+ assert (entries[1].length == 5368643584ULL);
+ assert (entries[1].flags == (LIBNBD_STATE_HOLE|LIBNBD_STATE_ZERO));
+ }
+ else if (strcmp (metacontext, bitmap) == 0) {
+ assert (!data->seen_dirty);
+ data->seen_dirty = true;
+
+ /* Dirty block at offset 5G-64k, remainder is clean */
+ assert (len == 2);
+ assert (entries[0].length == 5368643584ULL);
+ assert (entries[0].flags == 0);
+ assert (entries[1].length == 65536);
+ assert (entries[1].flags == 1);
+ }
+ else {
+ fprintf (stderr, "unexpected context %s\n", metacontext);
+ exit (EXIT_FAILURE);
+ }
+ return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int64_t exportsize;
+ struct data data;
+
+ if (argc < 3) {
+ fprintf (stderr, "%s bitmap qemu-nbd [args ...]\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+ bitmap = argv[1];
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION);
+ nbd_add_meta_context (nbd, bitmap);
+
+ if (nbd_connect_systemd_socket_activation (nbd, &argv[2]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ exportsize = nbd_get_size (nbd);
+ if (exportsize == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ if (nbd_get_extended_headers_negotiated (nbd) != 1) {
+ fprintf (stderr, "skipping: qemu-nbd lacks extended headers\n");
+ exit (77);
+ }
+
+ /* Prove that we can round-trip a >4G block status request */
+ data = (struct data) { .count = 2, };
+ if (nbd_block_status_64 (nbd, exportsize, 0,
+ (nbd_extent64_callback) { .callback = cb64,
+ .user_data = &data },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (data.seen_base && data.seen_dirty);
+
+ /* Check libnbd's handling of a >4G response through older interface */
+ data = (struct data) { .count = 2, };
+ if (nbd_block_status (nbd, exportsize, 0,
+ (nbd_extent_callback) { .callback = cb32,
+ .user_data = &data },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (data.seen_base && data.seen_dirty);
+
+ if (nbd_shutdown (nbd, 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_close (nbd);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/interop/large-status.sh b/interop/large-status.sh
new file mode 100755
index 00000000..86271a41
--- /dev/null
+++ b/interop/large-status.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright (C) 2019-2022 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+# Test reading qemu dirty-bitmap.
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-img bitmap --help
+requires qemu-nbd --version
+
+# This test uses the qemu-nbd -B option.
+if ! qemu-nbd --help | grep -sq -- -B; then
+ echo "$0: skipping because qemu-nbd does not support the -B option"
+ exit 77
+fi
+
+files="large-status.qcow2"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Create mostly-sparse file with intentionally different data vs. dirty areas
+# (64k data, 5G-64k hole,zero; 5G-64k clean, 64k dirty)
+qemu-img create -f qcow2 large-status.qcow2 5G
+qemu-img bitmap --add --enable -f qcow2 large-status.qcow2 bitmap0
+qemu-io -f qcow2 -c "w -z $((5*1024*1024*1024 - 64*1024)) 64k" \
+ large-status.qcow2
+qemu-img bitmap --disable -f qcow2 large-status.qcow2 bitmap0
+qemu-io -f qcow2 -c 'w 0 64k' large-status.qcow2
+
+# Run the test.
+$VG ./large-status qemu:dirty-bitmap:bitmap0 \
+ qemu-nbd -f qcow2 -B bitmap0 large-status.qcow2
diff --git a/.gitignore b/.gitignore
index 35e65335..67bcee58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,6 +109,7 @@ Makefile.in
/interop/interop-qemu-nbd
/interop/interop-qemu-nbd-tls-certs
/interop/interop-qemu-nbd-tls-psk
+/interop/large-status
/interop/list-exports-nbd-server
/interop/list-exports-nbdkit
/interop/list-exports-qemu-nbd
--
2.38.1