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 _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs