On Thu, May 25, 2023 at 08:01:07AM -0500, Eric Blake wrote: > In the recent NBD protocol extensions to add 64-bit commands [1], an > additional option was added to allow NBD_CMD_BLOCK_STATUS pass a
to pass > 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 <ebl...@redhat.com> > --- > info/nbdinfo.pod | 10 ++- > lib/nbd-protocol.h | 29 +++++--- > 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, 274 insertions(+), 17 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 9ea4a278..f5dc53fa 100644 > --- a/info/nbdinfo.pod > +++ b/info/nbdinfo.pod > @@ -178,6 +178,8 @@ rotating disk: accessing nearby blocks may be faster than > random > access and requests should be sorted to improve performance. Many > servers do not or cannot report this accurately. > > +=item nbdinfo --can block-status-payload URI Another case where "--has" sounds better ... > =item nbdinfo --can cache URI > > =item nbdinfo --can df URI > @@ -345,10 +347,10 @@ The command does not print anything. Instead it exits > with success > > For further information see the L<NBD > protocol|https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md> > -and the following libnbd functions: L<nbd_can_cache(3)>, > -L<nbd_can_df(3)>, L<nbd_can_fast_zero(3)>, L<nbd_can_flush(3)>, > -L<nbd_can_fua(3)>, L<nbd_can_multi_conn(3)>, L<nbd_can_trim(3)>, > -L<nbd_can_zero(3)>, L<nbd_is_read_only(3)>, > +and the following libnbd functions: L<nbd_can_block_status_payload(3)>, > +L<nbd_can_cache(3)>, L<nbd_can_df(3)>, L<nbd_can_fast_zero(3)>, > +L<nbd_can_flush(3)>, L<nbd_can_fua(3)>, L<nbd_can_multi_conn(3)>, > +L<nbd_can_trim(3)>, L<nbd_can_zero(3)>, L<nbd_is_read_only(3)>, > L<nbd_get_structured_replies_negotiated(3)>, > L<nbd_get_extended_headers_negotiated(3)>. > > diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h > index b6fa9b8a..9e358122 100644 > --- a/lib/nbd-protocol.h > +++ b/lib/nbd-protocol.h > @@ -102,17 +102,18 @@ struct nbd_fixed_new_option_reply { > #define NBD_FLAG_NO_ZEROES (1 << 1) > > /* Per-export flags. */ > -#define NBD_FLAG_HAS_FLAGS (1 << 0) > -#define NBD_FLAG_READ_ONLY (1 << 1) > -#define NBD_FLAG_SEND_FLUSH (1 << 2) > -#define NBD_FLAG_SEND_FUA (1 << 3) > -#define NBD_FLAG_ROTATIONAL (1 << 4) > -#define NBD_FLAG_SEND_TRIM (1 << 5) > -#define NBD_FLAG_SEND_WRITE_ZEROES (1 << 6) > -#define NBD_FLAG_SEND_DF (1 << 7) > -#define NBD_FLAG_CAN_MULTI_CONN (1 << 8) > -#define NBD_FLAG_SEND_CACHE (1 << 10) > -#define NBD_FLAG_SEND_FAST_ZERO (1 << 11) > +#define NBD_FLAG_HAS_FLAGS (1 << 0) > +#define NBD_FLAG_READ_ONLY (1 << 1) > +#define NBD_FLAG_SEND_FLUSH (1 << 2) > +#define NBD_FLAG_SEND_FUA (1 << 3) > +#define NBD_FLAG_ROTATIONAL (1 << 4) > +#define NBD_FLAG_SEND_TRIM (1 << 5) > +#define NBD_FLAG_SEND_WRITE_ZEROES (1 << 6) > +#define NBD_FLAG_SEND_DF (1 << 7) > +#define NBD_FLAG_CAN_MULTI_CONN (1 << 8) > +#define NBD_FLAG_SEND_CACHE (1 << 10) > +#define NBD_FLAG_SEND_FAST_ZERO (1 << 11) > +#define NBD_FLAG_BLOCK_STATUS_PAYLOAD (1 << 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 85625bbd..5a31ce3b 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 9a7a5967..f8c4cb7d 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 += \ > large-status \ > 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 fd81357b..a2d052bd 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -101,6 +101,7 @@ Makefile.in > /info/nbdinfo > /info/nbdinfo.1 > /install-sh > +/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 31c4a1ca..6dd68eeb 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"); Reviewed-by: Richard W.M. Jones <rjo...@redhat.com> -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs