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

Reply via email to