Rather than always relying on the implicit global list managed by
nbd_add_meta_context() and nbd_clear_meta_contexts(), we can take the
user's list of contexts as a direct parameter. This finally makes use
of the non-NULL queries parameter of the nbd_internal_set_querylist()
added in a previous patch.
The C test is added along with this commit (applying the new test
independently wouldn't compile), but the language bindings are done
separately for ease of review.
---
generator/API.ml | 99 ++++++++++++-
generator/states-newstyle-opt-meta-context.c | 8 +-
lib/opt.c | 25 +++-
tests/Makefile.am | 5 +
tests/opt-list-meta-queries.c | 145 +++++++++++++++++++
.gitignore | 1 +
6 files changed, 272 insertions(+), 11 deletions(-)
create mode 100644 tests/opt-list-meta-queries.c
diff --git a/generator/API.ml b/generator/API.ml
index 7be870a4..0c72c356 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -1169,12 +1169,15 @@ "opt_list_meta_context", {
default_call with
args = [ Closure context_closure ]; ret = RInt;
permitted_states = [ Negotiating ];
- shortdesc = "request the server to list available meta contexts";
+ shortdesc = "list available meta contexts, using implicit query list";
longdesc = "\
Request that the server list available meta contexts associated with
the export previously specified by the most recent
-L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>. This can only be
-used if L<nbd_set_opt_mode(3)> enabled option mode.
+L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>, and with a
+list of queries from prior calls to L<nbd_add_meta_context(3)>
+(see L<nbd_opt_list_meta_context_queries(3)> if you want to supply
+an explicit query list instead). This can only be used if
+L<nbd_set_opt_mode(3)> enabled option mode.
The NBD protocol allows a client to decide how many queries to ask
the server. Rather than taking that list of queries as a parameter
@@ -1211,10 +1214,61 @@ "opt_list_meta_context", {
replies that might be advertised, so client code should be aware that
a server may send a lengthy list.";
see_also = [Link "set_opt_mode"; Link
"aio_opt_list_meta_context";
+ Link "opt_list_meta_context_queries";
Link "add_meta_context"; Link "clear_meta_contexts";
Link "opt_go"; Link "set_export_name"];
};
+ "opt_list_meta_context_queries", {
+ default_call with
+ args = [ StringList "queries"; Closure context_closure ]; ret = RInt;
+ permitted_states = [ Negotiating ];
+ shortdesc = "list available meta contexts, using explicit query list";
+ longdesc = "\
+Request that the server list available meta contexts associated with
+the export previously specified by the most recent
+L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>, and with an
+explicit list of queries provided as a parameter (see
+L<nbd_opt_list_meta_context(3)> if you want to reuse an
+implicit query list instead). This can only be used if
+L<nbd_set_opt_mode(3)> enabled option mode.
+
+The NBD protocol allows a client to decide how many queries to ask
+the server. For this function, the list is explicit in the C<queries>
+parameter. When the list is empty, a server will typically reply with all
+contexts that it supports; when the list is non-empty, the server
+will reply only with supported contexts that match the client's
+request. Note that a reply by the server might be encoded to
+represent several feasible contexts within one string, rather than
+multiple strings per actual context name that would actually succeed
+during L<nbd_opt_go(3)>; so it is still necessary to use
+L<nbd_can_meta_context(3)> after connecting to see which contexts
+are actually supported.
+
+The C<context> function is called once per server reply, with any
+C<user_data> passed to this function, and with C<name> supplied by
+the server. Remember that it is not safe to call
+L<nbd_add_meta_context(3)> from within the context of the
+callback function; rather, your code must copy any C<name> needed for
+later use after this function completes. At present, the return value
+of the callback is ignored, although a return of -1 should be avoided.
+
+For convenience, when this function succeeds, it returns the number
+of replies returned by the server.
+
+Not all servers understand this request, and even when it is understood,
+the server might intentionally send an empty list because it does not
+support the requested context, or may encounter a failure after
+delivering partial results. Thus, this function may succeed even when
+no contexts are reported, or may fail but have a non-empty list. Likewise,
+the NBD protocol does not specify an upper bound for the number of
+replies that might be advertised, so client code should be aware that
+a server may send a lengthy list.";
+ see_also = [Link "set_opt_mode"; Link
"aio_opt_list_meta_context_queries";
+ Link "opt_list_meta_context";
+ Link "opt_go"; Link "set_export_name"];
+ };
+
"add_meta_context", {
default_call with
args = [ String "name" ]; ret = RErr;
@@ -2599,11 +2653,14 @@ "aio_opt_list_meta_context", {
args = [ Closure context_closure ]; ret = RInt;
optargs = [ OClosure completion_closure ];
permitted_states = [ Negotiating ];
- shortdesc = "request the server to list available meta contexts";
+ shortdesc = "request list of available meta contexts, using implicit
query";
longdesc = "\
Request that the server list available meta contexts associated with
the export previously specified by the most recent
-L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>. This can only be
+L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>, and with a
+list of queries from prior calls to L<nbd_add_meta_context(3)>
+(see L<nbd_aio_opt_list_meta_context_queries(3)> if you want to
+supply an explicit query list instead). This can only be
used if L<nbd_set_opt_mode(3)> enabled option mode.
To determine when the request completes, wait for
@@ -2614,7 +2671,35 @@ "aio_opt_list_meta_context", {
server returns an error (as is done by the return value of the
synchronous counterpart) is only possible with a completion
callback.";
- see_also = [Link "set_opt_mode"; Link "opt_list_meta_context"];
+ see_also = [Link "set_opt_mode"; Link "opt_list_meta_context";
+ Link "aio_opt_list_meta_context_queries"];
+ };
+
+ "aio_opt_list_meta_context_queries", {
+ default_call with
+ args = [ StringList "queries"; Closure context_closure ]; ret = RInt;
+ optargs = [ OClosure completion_closure ];
+ permitted_states = [ Negotiating ];
+ shortdesc = "request list of available meta contexts, using explicit
query";
+ longdesc = "\
+Request that the server list available meta contexts associated with
+the export previously specified by the most recent
+L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>, and with an
+explicit list of queries provided as a parameter (see
+L<nbd_aio_opt_list_meta_context(3)> if you want to reuse an
+implicit query list instead). This can only be
+used if L<nbd_set_opt_mode(3)> enabled option mode.
+
+To determine when the request completes, wait for
+L<nbd_aio_is_connecting(3)> to return false. Or supply the optional
+C<completion_callback> which will be invoked as described in
+L<libnbd(3)/Completion callbacks>, except that it is automatically
+retired regardless of return value. Note that detecting whether the
+server returns an error (as is done by the return value of the
+synchronous counterpart) is only possible with a completion
+callback.";
+ see_also = [Link "set_opt_mode"; Link
"opt_list_meta_context_queries";
+ Link "aio_opt_list_meta_context"];
};
"aio_pread", {
@@ -3347,6 +3432,8 @@ let first_version =
"stats_chunks_sent", (1, 16);
"stats_bytes_received", (1, 16);
"stats_chunks_received", (1, 16);
+ "opt_list_meta_context_queries", (1, 16);
+ "aio_opt_list_meta_context_queries", (1, 16);
(* These calls are proposed for a future version of libnbd, but
* have not been added to any released version so far.
diff --git a/generator/states-newstyle-opt-meta-context.c
b/generator/states-newstyle-opt-meta-context.c
index 31e84f35..1eb9fbe7 100644
--- a/generator/states-newstyle-opt-meta-context.c
+++ b/generator/states-newstyle-opt-meta-context.c
@@ -61,14 +61,14 @@ NEWSTYLE.OPT_META_CONTEXT.START:
SET_NEXT_STATE (%^OPT_GO.START);
return 0;
}
+ if (nbd_internal_set_querylist (h, NULL) == -1) {
+ SET_NEXT_STATE (%.DEAD);
+ return 0;
+ }
}
assert (!h->meta_valid);
- if (nbd_internal_set_querylist (h, NULL) == -1) {
- SET_NEXT_STATE (%.DEAD);
- return 0;
- }
/* Calculate the length of the option request data. */
len = 4 /* exportname len */ + strlen (h->export_name) + 4 /* nr queries */;
for (i = 0; i < h->querylist.len; ++i)
diff --git a/lib/opt.c b/lib/opt.c
index d9114f4b..cfdabb9e 100644
--- a/lib/opt.c
+++ b/lib/opt.c
@@ -197,12 +197,21 @@ context_complete (void *opaque, int *err)
int
nbd_unlocked_opt_list_meta_context (struct nbd_handle *h,
nbd_context_callback *context)
+{
+ return nbd_unlocked_opt_list_meta_context_queries (h, NULL, context);
+}
+
+/* Issue NBD_OPT_LIST_META_CONTEXT and wait for the reply. */
+int
+nbd_unlocked_opt_list_meta_context_queries (struct nbd_handle *h,
+ char **queries,
+ nbd_context_callback *context)
{
struct context_helper s = { .context = *context };
nbd_context_callback l = { .callback = context_visitor, .user_data = &s };
nbd_completion_callback c = { .callback = context_complete, .user_data = &s };
- if (nbd_unlocked_aio_opt_list_meta_context (h, &l, &c) == -1)
+ if (nbd_unlocked_aio_opt_list_meta_context_queries (h, queries, &l, &c) == -1)
return -1;
SET_CALLBACK_TO_NULL (*context);
@@ -285,12 +294,26 @@ int
nbd_unlocked_aio_opt_list_meta_context (struct nbd_handle *h,
nbd_context_callback *context,
nbd_completion_callback *complete)
+{
+ return nbd_unlocked_aio_opt_list_meta_context_queries (h, NULL, context,
+ complete);
+}
+
+/* Issue NBD_OPT_LIST_META_CONTEXT without waiting. */
+int
+nbd_unlocked_aio_opt_list_meta_context_queries (struct nbd_handle *h,
+ char **queries,
+ nbd_context_callback *context,
+ nbd_completion_callback *complete)
{
if ((h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE) == 0) {
set_error (ENOTSUP, "server is not using fixed newstyle protocol");
return -1;
}
+ if (nbd_internal_set_querylist (h, queries) == -1)
+ return -1;
+
assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
h->opt_cb.fn.context = *context;
SET_CALLBACK_TO_NULL (*context);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f6a2c110..dfb7f8bd 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -214,6 +214,7 @@ check_PROGRAMS += \
opt-list \
opt-info \
opt-list-meta \
+ opt-list-meta-queries \
connect-systemd-socket-activation \
connect-unix \
connect-tcp \
@@ -280,6 +281,7 @@ TESTS += \
opt-list \
opt-info \
opt-list-meta \
+ opt-list-meta-queries \
connect-systemd-socket-activation \
connect-unix \
connect-tcp \
@@ -538,6 +540,9 @@ opt_info_LDADD = $(top_builddir)/lib/libnbd.la
opt_list_meta_SOURCES = opt-list-meta.c
opt_list_meta_LDADD = $(top_builddir)/lib/libnbd.la
+opt_list_meta_queries_SOURCES = opt-list-meta-queries.c
+opt_list_meta_queries_LDADD = $(top_builddir)/lib/libnbd.la
+
connect_systemd_socket_activation_SOURCES = \
connect-systemd-socket-activation.c \
requires.c \
diff --git a/tests/opt-list-meta-queries.c b/tests/opt-list-meta-queries.c
new file mode 100644
index 00000000..8570f967
--- /dev/null
+++ b/tests/opt-list-meta-queries.c
@@ -0,0 +1,145 @@
+/* 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 behavior of nbd_opt_list_meta_context_queries. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+
+#include <libnbd.h>
+
+struct progress {
+ int count;
+ bool seen;
+};
+
+static int
+check (void *user_data, const char *name)
+{
+ struct progress *p = user_data;
+
+ p->count++;
+ if (strcmp (name, LIBNBD_CONTEXT_BASE_ALLOCATION) == 0)
+ p->seen = true;
+ return 0;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct nbd_handle *nbd;
+ int r;
+ struct progress p;
+ char *args[] = { "nbdkit", "-s", "--exit-with-parent",
"-v",
+ "memory", "size=1M", NULL };
+ nbd_context_callback ctx = { .callback = check,
+ .user_data = &p};
+
+ /* Get into negotiating state. */
+ nbd = nbd_create ();
+ if (nbd == NULL ||
+ nbd_set_opt_mode (nbd, true) == -1 ||
+ nbd_connect_command (nbd, args) == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* A NULL query is an error (C-only test) */
+ p = (struct progress) { .count = 0 };
+ r = nbd_opt_list_meta_context_queries (nbd, NULL, ctx);
+ if (r != -1 || nbd_get_errno () != EFAULT) {
+ fprintf (stderr, "expected EFAULT for NULL query list\n");
+ exit (EXIT_FAILURE);
+ }
+ if (p.count != 0 || p.seen) {
+ fprintf (stderr, "unexpected use of callback on failure\n");
+ exit (EXIT_FAILURE);
+ }
+
+ /* First pass: empty query should give at least "base:allocation".
+ * The explicit query overrides a non-empty nbd_add_meta_context.
+ */
+ p = (struct progress) { .count = 0 };
+ if (nbd_add_meta_context (nbd, "x-nosuch:") == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ {
+ char *empty[] = { NULL };
+ r = nbd_opt_list_meta_context_queries (nbd, empty, ctx);
+ }
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (r != p.count) {
+ fprintf (stderr, "inconsistent return value %d, expected %d\n", r,
p.count);
+ exit (EXIT_FAILURE);
+ }
+ if (r < 1 || !p.seen) {
+ fprintf (stderr, "server did not reply with base:allocation\n");
+ exit (EXIT_FAILURE);
+ }
+
+ /* Second pass: bogus query has no response. */
+ p = (struct progress) { .count = 0 };
+ r = nbd_clear_meta_contexts (nbd);
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ {
+ char *nosuch[] = { "x-nosuch:", NULL };
+ r = nbd_opt_list_meta_context_queries (nbd, nosuch, ctx);
+ }
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (r != 0 || p.count != 0 || p.seen) {
+ fprintf (stderr, "expecting no contexts, got %d\n", r);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Third pass: specific query should have one match. */
+ p = (struct progress) { .count = 0 };
+ {
+ char *pair[] = { "x-nosuch:", LIBNBD_CONTEXT_BASE_ALLOCATION, NULL };
+ r = nbd_opt_list_meta_context_queries (nbd, pair, ctx);
+ }
+ if (r == -1) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (r != 1 || p.count != 1 || !p.seen) {
+ fprintf (stderr, "expecting exactly one context, got %d\n", r);
+ exit (EXIT_FAILURE);
+ }
+
+ nbd_opt_abort (nbd);
+ nbd_close (nbd);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/.gitignore b/.gitignore
index bc49860d..272d1ec1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -233,6 +233,7 @@ Makefile.in
/tests/opt-info
/tests/opt-list
/tests/opt-list-meta
+/tests/opt-list-meta-queries
/tests/pki/
/tests/pread-initialize
/tests/private-data
--
2.37.3