In the recent NBD protocol extensions to add 64-bit commands [1], an
additional option was added to allow NBD_CMD_BLOCK_STATUS to pass a
client payload instructing the server to filter its answers in nbd.git
commit e6f3b94a (mainly useful when the client requests more than one
meta context with NBD_OPT_SET_META_CONTEXT). This patch lays the
groundwork by exposing servers that advertise this capability,
although libnbd does not yet actually utilize it until the next patch.
At the time this patch was written, qemu-nbd was also patched to
provide such support; hence, an interop/ test shows the API in action.
[1]
https://github.com/NetworkBlockDevice/nbd/blob/extension-ext-header/doc/
Signed-off-by: Eric Blake <eblake(a)redhat.com>
Reviewed-by: Richard W.M. Jones <rjones(a)redhat.com>
---
v4: rebase to earlier changes, s/can/has/ [Rich]
---
info/nbdinfo.pod | 8 ++
lib/nbd-protocol.h | 7 ++
generator/API.ml | 18 +++++
lib/flags.c | 12 +++
examples/server-flags.c | 7 +-
interop/Makefile.am | 6 ++
interop/block-status-payload.c | 126 ++++++++++++++++++++++++++++++++
interop/block-status-payload.sh | 68 +++++++++++++++++
.gitignore | 1 +
info/can.c | 5 ++
info/show.c | 9 ++-
11 files changed, 265 insertions(+), 2 deletions(-)
create mode 100644 interop/block-status-payload.c
create mode 100755 interop/block-status-payload.sh
diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod
index 2bdd573f..9b1962da 100644
--- a/info/nbdinfo.pod
+++ b/info/nbdinfo.pod
@@ -171,6 +171,11 @@ for supporting block status commands).
Test if server supports extended headers (a prerequisite for
supporting 64-bit commands; implies structured replies as well).
+=item nbdinfo --has block-status-payload URI
+
+Test if server has support for passing a client payload to limit the
+response to a block status command.
+
=item nbdinfo --is rotational URI
Test if the server export is backed by something which behaves like a
@@ -373,6 +378,8 @@ When using I<--list>, the default is I<--no-content>
(since
downloading from each export is expensive). To enable content probing
use I<--list --content>.
+=item B<--has block-status-payload>
+
=item B<--has extended-headers>
=item B<--has structured-reply>
@@ -385,6 +392,7 @@ indicate an error querying the flag).
For further information see the L<NBD
protocol|https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto....
and the following libnbd functions:
+L<nbd_can_block_status_payload(3)>,
L<nbd_get_extended_headers_negotiated(3)>,
L<nbd_get_structured_replies_negotiated(3)>.
diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index b5a28ae4..e5288a17 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -113,6 +113,7 @@ struct nbd_fixed_new_option_reply {
#define NBD_FLAG_CAN_MULTI_CONN (1U << 8)
#define NBD_FLAG_SEND_CACHE (1U << 10)
#define NBD_FLAG_SEND_FAST_ZERO (1U << 11)
+#define NBD_FLAG_BLOCK_STATUS_PAYLOAD (1U << 12)
/* NBD options (new style handshake only). */
#define NBD_OPT_EXPORT_NAME 1
@@ -204,6 +205,12 @@ struct nbd_request_ext {
uint64_t count; /* Request effect or payload length. */
} NBD_ATTRIBUTE_PACKED;
+/* Extended request payload for NBD_CMD_BLOCK_STATUS, when supported. */
+struct nbd_block_status_payload {
+ uint64_t length; /* Effective length of client request */
+ /* followed by array of uint32_t ids */
+} NBD_ATTRIBUTE_PACKED;
+
/* Simple reply (server -> client). */
struct nbd_simple_reply {
uint32_t magic; /* NBD_SIMPLE_REPLY_MAGIC. */
diff --git a/generator/API.ml b/generator/API.ml
index a02a8a2d..6228a4e5 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -2327,6 +2327,23 @@ "can_fast_zero", {
example = Some "examples/server-flags.c";
};
+ "can_block_status_payload", {
+ default_call with
+ args = []; ret = RBool;
+ permitted_states = [ Negotiating; Connected; Closed ];
+ shortdesc = "does the server support the block status payload flag?";
+ longdesc = "\
+Returns true if the server supports the use of the
+C<LIBNBD_CMD_FLAG_PAYLOAD_LEN> flag to allow filtering of the
+block status command. Returns
+false if the server does not. Note that this will never return
+true if L<nbd_get_extended_headers_negotiated(3)> is false."
+^ non_blocking_test_call_description;
+ see_also = [SectionLink "Flag calls"; Link "opt_info";
+ Link "get_extended_headers_negotiated"];
+ example = Some "examples/server-flags.c";
+ };
+
"can_df", {
default_call with
args = []; ret = RBool;
@@ -4183,6 +4200,7 @@ let first_version =
"get_extended_headers_negotiated", (1, 18);
"opt_extended_headers", (1, 18);
"aio_opt_extended_headers", (1, 18);
+ "can_block_status_payload", (1, 18);
(* These calls are proposed for a future version of libnbd, but
* have not been added to any released version so far.
diff --git a/lib/flags.c b/lib/flags.c
index be880acf..7e6ddedd 100644
--- a/lib/flags.c
+++ b/lib/flags.c
@@ -66,6 +66,12 @@ nbd_internal_set_size_and_flags (struct nbd_handle *h,
eflags &= ~NBD_FLAG_SEND_DF;
}
+ if (eflags & NBD_FLAG_BLOCK_STATUS_PAYLOAD && !h->extended_headers) {
+ debug (h, "server lacks extended headers, ignoring claim "
+ "of block status payload");
+ eflags &= ~NBD_FLAG_BLOCK_STATUS_PAYLOAD;
+ }
+
if (eflags & NBD_FLAG_SEND_FAST_ZERO &&
!(eflags & NBD_FLAG_SEND_WRITE_ZEROES)) {
debug (h, "server lacks write zeroes, ignoring claim of fast zero");
@@ -213,6 +219,12 @@ nbd_unlocked_can_cache (struct nbd_handle *h)
return get_flag (h, NBD_FLAG_SEND_CACHE);
}
+int
+nbd_unlocked_can_block_status_payload (struct nbd_handle *h)
+{
+ return get_flag (h, NBD_FLAG_BLOCK_STATUS_PAYLOAD);
+}
+
int
nbd_unlocked_can_meta_context (struct nbd_handle *h, const char *name)
{
diff --git a/examples/server-flags.c b/examples/server-flags.c
index d156aced..f53b86ed 100644
--- a/examples/server-flags.c
+++ b/examples/server-flags.c
@@ -78,8 +78,13 @@ main (int argc, char *argv[])
PRINT_FLAG (nbd_can_multi_conn);
PRINT_FLAG (nbd_can_trim);
PRINT_FLAG (nbd_can_zero);
-#if LIBNBD_HAVE_NBD_CAN_FAST_ZERO /* Added in 1.2 */
+#if LIBNBD_HAVE_NBD_CAN_FAST_ZERO
+ /* Added in 1.2 */
PRINT_FLAG (nbd_can_fast_zero);
+#endif
+#if LIBNBD_HAVE_NBD_CAN_BLOCK_STATUS_PAYLOAD
+ /* Added in 1.18 */
+ PRINT_FLAG (nbd_can_block_status_payload);
#endif
PRINT_FLAG (nbd_is_read_only);
PRINT_FLAG (nbd_is_rotational);
diff --git a/interop/Makefile.am b/interop/Makefile.am
index 3947ce3d..d6485adf 100644
--- a/interop/Makefile.am
+++ b/interop/Makefile.am
@@ -27,6 +27,7 @@ EXTRA_DIST = \
list-exports-test-dir/disk2 \
structured-read.sh \
opt-extended-headers.sh \
+ block-status-payload.sh \
$(NULL)
TESTS_ENVIRONMENT = \
@@ -138,6 +139,7 @@ check_PROGRAMS += \
block-status-64 \
structured-read \
opt-extended-headers \
+ block-status-payload \
$(NULL)
TESTS += \
interop-qemu-nbd \
@@ -150,6 +152,7 @@ TESTS += \
structured-read.sh \
interop-qemu-block-size.sh \
opt-extended-headers.sh \
+ block-status-payload.sh \
$(NULL)
interop_qemu_nbd_SOURCES = \
@@ -247,6 +250,9 @@ structured_read_LDADD = $(top_builddir)/lib/libnbd.la
opt_extended_headers_SOURCES = opt-extended-headers.c
opt_extended_headers_LDADD = $(top_builddir)/lib/libnbd.la
+block_status_payload_SOURCES = block-status-payload.c
+block_status_payload_LDADD = $(top_builddir)/lib/libnbd.la
+
endif HAVE_QEMU_NBD
#----------------------------------------------------------------------
diff --git a/interop/block-status-payload.c b/interop/block-status-payload.c
new file mode 100644
index 00000000..9603dfe5
--- /dev/null
+++ b/interop/block-status-payload.c
@@ -0,0 +1,126 @@
+/* NBD client library in userspace
+ * Copyright Red Hat
+ *
+ * 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 interaction with qemu using block status payload filtering. */
+
+#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>
+
+#include "array-size.h"
+
+static const char *contexts[] = {
+ "base:allocation",
+ "qemu:allocation-depth",
+ "qemu:dirty-bitmap:bitmap0",
+ "qemu:dirty-bitmap:bitmap1",
+};
+
+static int
+cb (void *opaque, const char *metacontext, uint64_t offset,
+ nbd_extent *entries, size_t len, int *error)
+{
+ /* Adjust seen according to which context was visited */
+ unsigned int *seen = opaque;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE (contexts); i++)
+ if (strcmp (contexts[i], metacontext) == 0)
+ break;
+ *seen |= 1 << i;
+ return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int64_t exportsize;
+ unsigned int seen;
+ size_t i;
+ int r;
+
+ if (argc < 2) {
+ fprintf (stderr, "%s qemu-nbd [args ...]\n", argv[0]);
+ exit (EXIT_FAILURE);
+ }
+
+ nbd = nbd_create ();
+ if (nbd == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ assert (ARRAY_SIZE (contexts) == 4);
+ for (i = 0; i < ARRAY_SIZE (contexts); i++) {
+ if (nbd_add_meta_context (nbd, contexts[i]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (nbd_connect_systemd_socket_activation (nbd, &argv[1]) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ r = nbd_can_block_status_payload (nbd);
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (r != 1) {
+ fprintf (stderr, "expecting block status payload support from qemu\n");
+ exit (EXIT_FAILURE);
+ }
+
+ exportsize = nbd_get_size (nbd);
+ if (exportsize == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* An unfiltered call should see all four contexts */
+ seen = 0;
+ if (nbd_block_status_64 (nbd, exportsize, 0,
+ (nbd_extent64_callback) { .callback = cb,
+ .user_data = &seen },
+ 0) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ assert (seen == 0xf);
+
+ /* FIXME: Test filtered calls once the API is added */
+ 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/block-status-payload.sh b/interop/block-status-payload.sh
new file mode 100755
index 00000000..a12cfc8a
--- /dev/null
+++ b/interop/block-status-payload.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+# nbd client library in userspace
+# Copyright Red Hat
+#
+# 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 use of block status payload for server filtering
+
+source ../tests/functions.sh
+set -e
+set -x
+
+requires qemu-img bitmap --help
+# This test uses the qemu-nbd -A and -B options.
+requires qemu-nbd -A -BA --version
+
+file="block-status-payload.qcow2"
+rm -f $file
+cleanup_fn rm -f $file
+
+# Create sparse file with two bitmaps.
+qemu-img create -f qcow2 $file 1M
+qemu-img bitmap --add --enable -f qcow2 $file bitmap0
+qemu-img bitmap --add --enable -f qcow2 $file bitmap1
+
+# Unconditional part of test: qemu should not advertise block status payload
+# support if extended headers are not in use
+nbdsh -c '
+h.set_request_extended_headers(False)
+h.add_meta_context("base:allocation")
+h.add_meta_context("qemu:allocation-depth")
+h.add_meta_context("qemu:dirty-bitmap:bitmap0")
+h.add_meta_context("qemu:dirty-bitmap:bitmap1")
+h.set_opt_mode(True)
+args = ["qemu-nbd", "-f", "qcow2", "-A",
"-B", "bitmap0", "-B", "bitmap1",
+ "'"$file"'"]
+h.connect_systemd_socket_activation(args)
+assert h.aio_is_negotiating() is True
+assert h.get_extended_headers_negotiated() is False
+# Flag not available until info or go
+try:
+ h.can_block_status_payload()
+ assert False
+except nbd.Error:
+ pass
+h.opt_info()
+assert h.can_block_status_payload() is False
+assert h.can_meta_context("base:allocation") is True
+h.opt_abort()
+'
+
+# Conditional part of test: if qemu is new enough to support extended
+# headers, we assume it can also support block status payload.
+requires nbdinfo --can extended-headers -- [ qemu-nbd -r -f qcow2 "$file" ]
+$VG ./block-status-payload \
+ qemu-nbd -f qcow2 -A -B bitmap0 -B bitmap1 $file
diff --git a/.gitignore b/.gitignore
index c0761b25..cc45d61b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,6 +103,7 @@ Makefile.in
/info/nbdinfo.1
/install-sh
/interop/block-status-64
+/interop/block-status-payload
/interop/dirty-bitmap
/interop/interop-nbd-server
/interop/interop-nbd-server-tls
diff --git a/info/can.c b/info/can.c
index 8547ef04..6bb36fb1 100644
--- a/info/can.c
+++ b/info/can.c
@@ -72,6 +72,11 @@ do_can (void)
else if (strcasecmp (can, "rotational") == 0)
feature = nbd_is_rotational (nbd);
+ else if (strcasecmp (can, "block status payload") == 0 ||
+ strcasecmp (can, "block-status-payload") == 0 ||
+ strcasecmp (can, "block_status_payload") == 0)
+ feature = nbd_can_block_status_payload (nbd);
+
else if (strcasecmp (can, "cache") == 0)
feature = nbd_can_cache (nbd);
diff --git a/info/show.c b/info/show.c
index 920bbb0a..8914f927 100644
--- a/info/show.c
+++ b/info/show.c
@@ -54,7 +54,7 @@ show_one_export (struct nbd_handle *nbd, const char *desc,
char *uri = NULL;
int is_rotational, is_read_only;
int can_cache, can_df, can_fast_zero, can_flush, can_fua,
- can_multi_conn, can_trim, can_zero;
+ can_multi_conn, can_trim, can_zero, can_block_status_payload;
int64_t block_minimum, block_preferred, block_maximum;
string_vector contexts = empty_vector;
bool show_context = false;
@@ -120,6 +120,7 @@ show_one_export (struct nbd_handle *nbd, const char *desc,
can_multi_conn = nbd_can_multi_conn (nbd);
can_trim = nbd_can_trim (nbd);
can_zero = nbd_can_zero (nbd);
+ can_block_status_payload = nbd_can_block_status_payload (nbd);
block_minimum = nbd_get_block_size (nbd, LIBNBD_SIZE_MINIMUM);
block_preferred = nbd_get_block_size (nbd, LIBNBD_SIZE_PREFERRED);
block_maximum = nbd_get_block_size (nbd, LIBNBD_SIZE_MAXIMUM);
@@ -161,6 +162,8 @@ show_one_export (struct nbd_handle *nbd, const char *desc,
if (is_read_only >= 0)
fprintf (fp, "\t%s: %s\n", "is_read_only",
is_read_only ? "true" : "false");
+ if (can_block_status_payload >= 0)
+ show_boolean ("can_block_status_payload", can_block_status_payload);
if (can_cache >= 0)
show_boolean ("can_cache", can_cache);
if (can_df >= 0)
@@ -230,6 +233,10 @@ show_one_export (struct nbd_handle *nbd, const char *desc,
if (is_read_only >= 0)
fprintf (fp, "\t\"%s\": %s,\n",
"is_read_only", is_read_only ? "true" :
"false");
+ if (can_block_status_payload >= 0)
+ fprintf (fp, "\t\"%s\": %s,\n",
+ "can_block_status_payload",
+ can_block_status_payload ? "true" : "false");
if (can_cache >= 0)
fprintf (fp, "\t\"%s\": %s,\n",
"can_cache", can_cache ? "true" : "false");
--
2.41.0