Make it possible for the client to choose when to manually request
meta contexts when using nbd_set_opt_mode().  By itself, this patch
allows us to test how a server reacts to NBD_OPT_SET_META_CONTEXT
without a previous negotiation of structured headers; then an upcoming
patch will also add APIs for choosing when to negotiate OPT_STARTTLS
and OPT_STRUCTURED_REPLY for even more fine-grained testing control.
Most users won't need this API (the automatic defaults are good
enough), but when writing a server, this level of integration testing
can be useful.  Similar to the existing nbd_opt_list_meta_context
API, also offer a variant that takes an explicit query list.

As this is a new API, the unit test in C is included in this patch;
there is no point in splitting it as reordering the patches would not
compile.  However, porting the tests to other languages will be done
in the next commit.
---
 generator/API.ml                             | 207 +++++++++++++++-
 generator/states-newstyle-opt-meta-context.c |  39 ++--
 generator/states-newstyle.c                  |   1 +
 lib/opt.c                                    |  70 +++++-
 tests/Makefile.am                            |  14 ++
 tests/opt-set-meta-queries.c                 | 149 ++++++++++++
 tests/opt-set-meta.c                         | 234 +++++++++++++++++++
 .gitignore                                   |   2 +
 8 files changed, 690 insertions(+), 26 deletions(-)
 create mode 100644 tests/opt-set-meta-queries.c
 create mode 100644 tests/opt-set-meta.c

diff --git a/generator/API.ml b/generator/API.ml
index 0f70dcd1..9ffe6516 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -845,17 +845,20 @@   "set_request_meta_context", {
 is true; however the extra step of negotiating meta contexts is
 not always desirable: performing both info and go on the same
 export works without needing to re-negotiate contexts on the
-second call; and even when using just L<nbd_opt_info(3)>, it
-can be faster to collect the server's results by relying on the
-callback function passed to L<nbd_opt_list_meta_context(3)> than
-a series of post-process calls to L<nbd_can_meta_context(3)>.
+second call; integration testing of other servers may benefit
+from manual invocation of L<nbd_opt_set_meta_context(3)> at
+other times in the negotiation sequence; and even when using just
+L<nbd_opt_info(3)>, it can be faster to collect the server's
+results by relying on the callback function passed to
+L<nbd_opt_list_meta_context(3)> than a series of post-process
+calls to L<nbd_can_meta_context(3)>.

 Note that this control has no effect if the server does not
 negotiate structured replies, or if the client did not request
 any contexts via L<nbd_add_meta_context(3)>.  Setting this
 control to false may cause L<nbd_block_status(3)> to fail.";
     see_also = [Link "set_opt_mode"; Link "opt_go"; Link "opt_info";
-                Link "opt_list_meta_context";
+                Link "opt_list_meta_context"; Link "opt_set_meta_context";
                 Link "get_structured_replies_negotiated";
                 Link "get_request_meta_context"; Link "can_meta_context"];
   };
@@ -1095,6 +1098,7 @@   "set_opt_mode", {
     see_also = [Link "get_opt_mode"; Link "aio_is_negotiating";
                 Link "opt_abort"; Link "opt_go"; Link "opt_list";
                 Link "opt_info"; Link "opt_list_meta_context";
+                Link "opt_set_meta_context";
                 Link "aio_connect"];
   };

@@ -1121,7 +1125,9 @@   "opt_go", {

 By default, libnbd will automatically request all meta contexts
 registered by L<nbd_add_meta_context(3)> as part of this call; but
-this can be suppressed with L<nbd_set_request_meta_context(3)>.
+this can be suppressed with L<nbd_set_request_meta_context(3)>,
+particularly if L<nbd_opt_set_meta_context(3)> was used earlier
+in the negotiation sequence.

 If this fails, the server may still be in negotiation, where it is
 possible to attempt another option such as a different export name;
@@ -1129,7 +1135,8 @@   "opt_go", {
     example = Some "examples/list-exports.c";
     see_also = [Link "set_opt_mode"; Link "aio_opt_go"; Link "opt_abort";
                 Link "set_export_name"; Link "connect_uri"; Link "opt_info";
-                Link "add_meta_context"; Link "set_request_meta_context"];
+                Link "add_meta_context"; Link "set_request_meta_context";
+                Link "opt_set_meta_context"];
   };

   "opt_abort", {
@@ -1268,7 +1275,8 @@   "opt_list_meta_context", {
     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"];
+                Link "opt_go"; Link "set_export_name";
+                Link "opt_set_meta_context"];
   };

   "opt_list_meta_context_queries", {
@@ -1321,6 +1329,118 @@   "opt_list_meta_context_queries", {
                 Link "opt_go"; Link "set_export_name"];
   };

+  "opt_set_meta_context", {
+    default_call with
+    args = [ Closure context_closure ]; ret = RInt;
+    permitted_states = [ Negotiating ];
+    shortdesc = "select specific meta contexts, using implicit query list";
+    longdesc = "\
+Request that the server supply all recognized meta contexts
+registered through prior calls to L<nbd_add_meta_context(3)>, in
+conjunction 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.  Normally, this function is redundant, as L<nbd_opt_go(3)>
+automatically does the same task if structured replies have
+already been negotiated.  But manual control over meta context
+requests can be useful for fine-grained testing of how a server
+handles unusual negotiation sequences.  Often, use of this
+function is coupled with L<nbd_set_request_meta_context(3)> to
+bypass the automatic context request normally performed by
+L<nbd_opt_go(3)>.
+
+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
+to this function, libnbd reuses the current list of requested meta
+contexts as set by L<nbd_add_meta_context(3)>; you can use
+L<nbd_clear_meta_contexts(3)> to set up a different list of queries
+(see L<nbd_opt_set_meta_context_queries(3)> to pass an explicit
+list of contexts instead).  Since this function is primarily
+designed for testing servers, libnbd does not prevent the use
+of this function on an empty list or when
+L<nbd_set_request_structured_replies(3)> has disabled structured
+replies, in order to see how a server behaves.
+
+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.  Additionally, each server name will remain visible through
+L<nbd_can_meta_context(3)> until the next attempt at
+L<nbd_set_export_name(3)> or L<nbd_opt_set_meta_context(3)>, as
+well as L<nbd_opt_go(3)> or L<nbd_opt_info(3)> that trigger an
+automatic meta context request.  Remember that it is not safe to
+call any C<nbd_*> APIs from within the context of the callback
+function.  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.";
+    see_also = [Link "set_opt_mode"; Link "aio_opt_set_meta_context";
+                Link "add_meta_context"; Link "clear_meta_contexts";
+                Link "opt_set_meta_context_queries";
+                Link "opt_go"; Link "set_export_name";
+                Link "opt_list_meta_context"; Link "set_request_meta_context";
+                Link "can_meta_context"];
+  };
+
+  "opt_set_meta_context_queries", {
+    default_call with
+    args = [ StringList "queries"; Closure context_closure ]; ret = RInt;
+    permitted_states = [ Negotiating ];
+    shortdesc = "select specific meta contexts, using explicit query list";
+    longdesc = "\
+Request that the server supply all recognized meta contexts
+passed in through C<queries>, in conjunction 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.  Normally, this
+function is redundant, as L<nbd_opt_go(3)> automatically does
+the same task if structured replies have already been
+negotiated.  But manual control over meta context requests can
+be useful for fine-grained testing of how a server handles
+unusual negotiation sequences.  Often, use of this function is
+coupled with L<nbd_set_request_meta_context(3)> to bypass the
+automatic context request normally performed by L<nbd_opt_go(3)>.
+
+The NBD protocol allows a client to decide how many queries to ask
+the server.  This function takes an explicit list of queries; to
+instead reuse an implicit list, see L<nbd_opt_set_meta_context(3)>.
+Since this function is primarily designed for testing servers,
+libnbd does not prevent the use of this function on an empty
+list or when L<nbd_set_request_structured_replies(3)> has
+disabled structured replies, in order to see how a server behaves.
+
+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.  Additionally, each server name will remain visible through
+L<nbd_can_meta_context(3)> until the next attempt at
+L<nbd_set_export_name(3)> or L<nbd_opt_set_meta_context(3)>, as
+well as L<nbd_opt_go(3)> or L<nbd_opt_info(3)> that trigger an
+automatic meta context request.  Remember that it is not safe to
+call any C<nbd_*> APIs from within the context of the callback
+function.  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.";
+    see_also = [Link "set_opt_mode"; Link "aio_opt_set_meta_context_queries";
+                Link "opt_set_meta_context";
+                Link "opt_go"; Link "set_export_name";
+                Link "opt_list_meta_context_queries";
+                Link "set_request_meta_context"; Link "can_meta_context"];
+  };
+
   "add_meta_context", {
     default_call with
     args = [ String "name" ]; ret = RErr;
@@ -1999,7 +2119,7 @@   "can_meta_context", {
     see_also = [SectionLink "Flag calls"; Link "opt_info";
                 Link "add_meta_context";
                 Link "block_status"; Link "aio_block_status";
-                Link "set_request_meta_context"];
+                Link "set_request_meta_context"; Link "opt_set_meta_context"];
   };

   "get_protocol", {
@@ -2755,6 +2875,71 @@   "aio_opt_list_meta_context_queries", {
                 Link "aio_opt_list_meta_context"];
   };

+  "aio_opt_set_meta_context", {
+    default_call with
+    args = [ Closure context_closure ]; ret = RInt;
+    optargs = [ OClosure completion_closure ];
+    permitted_states = [ Negotiating ];
+    shortdesc = "select specific meta contexts, with implicit query list";
+    longdesc = "\
+Request that the server supply all recognized meta contexts
+registered through prior calls to L<nbd_add_meta_context(3)>, in
+conjunction 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.  Normally, this function is redundant, as L<nbd_opt_go(3)>
+automatically does the same task if structured replies have
+already been negotiated.  But manual control over meta context
+requests can be useful for fine-grained testing of how a server
+handles unusual negotiation sequences.  Often, use of this
+function is coupled with L<nbd_set_request_meta_context(3)> to
+bypass the automatic context request normally performed by
+L<nbd_opt_go(3)>.
+
+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_set_meta_context";
+                Link "aio_opt_set_meta_context_queries"];
+  };
+
+  "aio_opt_set_meta_context_queries", {
+    default_call with
+    args = [ StringList "queries"; Closure context_closure ]; ret = RInt;
+    optargs = [ OClosure completion_closure ];
+    permitted_states = [ Negotiating ];
+    shortdesc = "select specific meta contexts, with explicit query list";
+    longdesc = "\
+Request that the server supply all recognized meta contexts
+passed in through C<queries>, in conjunction 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.  Normally, this
+function is redundant, as L<nbd_opt_go(3)> automatically does
+the same task if structured replies have already been
+negotiated.  But manual control over meta context requests can
+be useful for fine-grained testing of how a server handles
+unusual negotiation sequences.  Often, use of this function is
+coupled with L<nbd_set_request_meta_context(3)> to bypass the
+automatic context request normally performed by L<nbd_opt_go(3)>.
+
+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_set_meta_context_queries";
+                Link "aio_opt_set_meta_context"];
+  };
+
   "aio_pread", {
     default_call with
     args = [ BytesPersistOut ("buf", "count"); UInt64 "offset" ];
@@ -3489,6 +3674,10 @@ let first_version =
   "aio_opt_list_meta_context_queries", (1, 16);
   "set_request_meta_context", (1, 16);
   "get_request_meta_context", (1, 16);
+  "opt_set_meta_context", (1, 16);
+  "opt_set_meta_context_queries", (1, 16);
+  "aio_opt_set_meta_context", (1, 16);
+  "aio_opt_set_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 e124d06f..46fee15b 100644
--- a/generator/states-newstyle-opt-meta-context.c
+++ b/generator/states-newstyle-opt-meta-context.c
@@ -34,6 +34,8 @@  NEWSTYLE.OPT_META_CONTEXT.START:
    *     -> conditionally use SET, next state OPT_GO for NBD_OPT_GO
    *   nbd_opt_list_meta_context()
    *     -> unconditionally use LIST, next state NEGOTIATING
+   *   nbd_opt_set_meta_context()
+   *     -> unconditionally use SET, next state NEGOTIATING
    *
    * If SET is conditional, we skip it if h->request_meta is false, if
    * structured replies were not negotiated, or if no contexts to request.
@@ -41,7 +43,7 @@  NEWSTYLE.OPT_META_CONTEXT.START:
    * success, while LIST is stateless.
    * If OPT_GO is later successful, it populates h->exportsize and friends,
    * and also sets h->meta_valid if h->request_meta but we skipped SET here.
-   * There is a callback if and only if the command is LIST.
+   * There is a callback if and only if the command is unconditional.
    */
   assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   if (h->opt_current == NBD_OPT_LIST_META_CONTEXT) {
@@ -50,9 +52,12 @@  NEWSTYLE.OPT_META_CONTEXT.START:
     opt = h->opt_current;
   }
   else {
-    assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
+    if (h->opt_current == NBD_OPT_SET_META_CONTEXT)
+      assert (CALLBACK_IS_NOT_NULL (h->opt_cb.fn.context));
+    else
+      assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
     opt = NBD_OPT_SET_META_CONTEXT;
-    if (h->request_meta) {
+    if (h->request_meta || h->opt_current == opt) {
       for (i = 0; i < h->meta_contexts.len; ++i)
         free (h->meta_contexts.ptr[i].name);
       meta_vector_reset (&h->meta_contexts);
@@ -215,15 +220,15 @@  NEWSTYLE.OPT_META_CONTEXT.CHECK_REPLY:
   len = be32toh (h->sbuf.or.option_reply.replylen);
   switch (reply) {
   case NBD_REP_ACK:           /* End of list of replies. */
-    if (opt == NBD_OPT_LIST_META_CONTEXT) {
-      SET_NEXT_STATE (%.NEGOTIATING);
-      CALL_CALLBACK (h->opt_cb.completion, &err);
-      nbd_internal_free_option (h);
-    }
-    else {
-      SET_NEXT_STATE (%^OPT_GO.START);
+    if (opt == NBD_OPT_SET_META_CONTEXT)
       h->meta_valid = true;
+    if (opt == h->opt_current) {
+      SET_NEXT_STATE (%.NEGOTIATING);
+      CALL_CALLBACK (h->opt_cb.completion, &err);
+      nbd_internal_free_option (h);
     }
+    else
+      SET_NEXT_STATE (%^OPT_GO.START);
     break;
   case NBD_REP_META_CONTEXT:  /* A context. */
     if (len > maxpayload)
@@ -242,10 +247,10 @@  NEWSTYLE.OPT_META_CONTEXT.CHECK_REPLY:
       }
       debug (h, "negotiated %s with context ID %" PRIu32,
              meta_context.name, meta_context.context_id);
-      if (opt == NBD_OPT_LIST_META_CONTEXT) {
+      if (CALLBACK_IS_NOT_NULL (h->opt_cb.fn.context))
         CALL_CALLBACK (h->opt_cb.fn.context, meta_context.name);
+      if (opt == NBD_OPT_LIST_META_CONTEXT)
         free (meta_context.name);
-      }
       else if (meta_vector_append (&h->meta_contexts, meta_context) == -1) {
         set_error (errno, "realloc");
         free (meta_context.name);
@@ -256,25 +261,27 @@  NEWSTYLE.OPT_META_CONTEXT.CHECK_REPLY:
     SET_NEXT_STATE (%PREPARE_FOR_REPLY);
     break;
   default:
-    /* Anything else is an error, ignore it for SET, report it for LIST */
+    /* Anything else is an error, report it for explicit LIST/SET, ignore it
+     * for automatic progress (nbd_connect_*, nbd_opt_info, nbd_opt_go).
+     */
     if (handle_reply_error (h) == -1) {
       SET_NEXT_STATE (%.DEAD);
       return 0;
     }

-    if (opt == NBD_OPT_LIST_META_CONTEXT) {
+    if (opt == h->opt_current) {
       /* XXX Should we decode specific expected errors, like
        * REP_ERR_UNKNOWN to ENOENT or REP_ERR_TOO_BIG to ERANGE?
        */
       err = ENOTSUP;
       set_error (err, "unexpected response, possibly the server does not "
-                 "support listing contexts");
+                 "support meta contexts");
       CALL_CALLBACK (h->opt_cb.completion, &err);
       nbd_internal_free_option (h);
       SET_NEXT_STATE (%.NEGOTIATING);
     }
     else {
-      debug (h, "handshake: unexpected error from "
+      debug (h, "handshake: ignoring unexpected error from "
              "NBD_OPT_SET_META_CONTEXT (%" PRIu32 ")", reply);
       SET_NEXT_STATE (%^OPT_GO.START);
     }
diff --git a/generator/states-newstyle.c b/generator/states-newstyle.c
index e84823e3..60e0bc50 100644
--- a/generator/states-newstyle.c
+++ b/generator/states-newstyle.c
@@ -140,6 +140,7 @@  NEWSTYLE.START:
       SET_NEXT_STATE (%PREPARE_OPT_ABORT);
       return 0;
     case NBD_OPT_LIST_META_CONTEXT:
+    case NBD_OPT_SET_META_CONTEXT:
       SET_NEXT_STATE (%OPT_META_CONTEXT.START);
       return 0;
     case 0:
diff --git a/lib/opt.c b/lib/opt.c
index cfdabb9e..ee036235 100644
--- a/lib/opt.c
+++ b/lib/opt.c
@@ -32,7 +32,8 @@ nbd_internal_free_option (struct nbd_handle *h)
 {
   if (h->opt_current == NBD_OPT_LIST)
     FREE_CALLBACK (h->opt_cb.fn.list);
-  else if (h->opt_current == NBD_OPT_LIST_META_CONTEXT)
+  else if (h->opt_current == NBD_OPT_LIST_META_CONTEXT ||
+           h->opt_current == NBD_OPT_SET_META_CONTEXT)
     FREE_CALLBACK (h->opt_cb.fn.context);
   FREE_CALLBACK (h->opt_cb.completion);
 }
@@ -224,6 +225,37 @@ nbd_unlocked_opt_list_meta_context_queries (struct 
nbd_handle *h,
   return s.count;
 }

+/* Issue NBD_OPT_SET_META_CONTEXT and wait for the reply. */
+int
+nbd_unlocked_opt_set_meta_context (struct nbd_handle *h,
+                                   nbd_context_callback *context)
+{
+  return nbd_unlocked_opt_set_meta_context_queries (h, NULL, context);
+}
+
+/* Issue NBD_OPT_SET_META_CONTEXT and wait for the reply. */
+int
+nbd_unlocked_opt_set_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_set_meta_context_queries (h, queries, &l, &c) == -1)
+    return -1;
+
+  SET_CALLBACK_TO_NULL (*context);
+  if (wait_for_option (h) == -1)
+    return -1;
+  if (s.err) {
+    set_error (s.err, "server replied with error to set meta context request");
+    return -1;
+  }
+  return s.count;
+}
+
 /* Issue NBD_OPT_GO (or NBD_OPT_EXPORT_NAME) without waiting. */
 int
 nbd_unlocked_aio_opt_go (struct nbd_handle *h,
@@ -324,3 +356,39 @@ nbd_unlocked_aio_opt_list_meta_context_queries (struct 
nbd_handle *h,
     debug (h, "option queued, ignoring state machine failure");
   return 0;
 }
+
+/* Issue NBD_OPT_SET_META_CONTEXT without waiting. */
+int
+nbd_unlocked_aio_opt_set_meta_context (struct nbd_handle *h,
+                                       nbd_context_callback *context,
+                                       nbd_completion_callback *complete)
+{
+  return nbd_unlocked_aio_opt_set_meta_context_queries (h, NULL, context,
+                                                        complete);
+}
+
+/* Issue NBD_OPT_SET_META_CONTEXT without waiting. */
+int
+nbd_unlocked_aio_opt_set_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);
+  h->opt_cb.completion = *complete;
+  SET_CALLBACK_TO_NULL (*complete);
+  h->opt_current = NBD_OPT_SET_META_CONTEXT;
+  if (nbd_internal_run (h, cmd_issue) == -1)
+    debug (h, "option queued, ignoring state machine failure");
+  return 0;
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index dfb7f8bd..ec95495e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -215,6 +215,8 @@ check_PROGRAMS += \
        opt-info \
        opt-list-meta \
        opt-list-meta-queries \
+       opt-set-meta \
+       opt-set-meta-queries \
        connect-systemd-socket-activation \
        connect-unix \
        connect-tcp \
@@ -282,6 +284,8 @@ TESTS += \
        opt-info \
        opt-list-meta \
        opt-list-meta-queries \
+       opt-set-meta \
+       opt-set-meta-queries \
        connect-systemd-socket-activation \
        connect-unix \
        connect-tcp \
@@ -543,6 +547,16 @@ 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

+opt_set_meta_SOURCES = opt-set-meta.c
+opt_set_meta_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -I$(top_srcdir)/common/include \
+       $(NULL)
+opt_set_meta_LDADD = $(top_builddir)/lib/libnbd.la
+
+opt_set_meta_queries_SOURCES = opt-set-meta-queries.c
+opt_set_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-set-meta-queries.c b/tests/opt-set-meta-queries.c
new file mode 100644
index 00000000..0b162607
--- /dev/null
+++ b/tests/opt-set-meta-queries.c
@@ -0,0 +1,149 @@
+/* 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_set_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);
+  }
+
+  /* nbdkit does not match wildcard for SET, even though it does for LIST */
+  p = (struct progress) { .count = 0 };
+  {
+    char *base[] = { "base:", NULL };
+    r = nbd_opt_set_meta_context_queries (nbd, base, ctx);
+  }
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (r != p.count || r != 0 || p.seen) {
+    fprintf (stderr, "inconsistent return value %d, expected %d\n", r, 
p.count);
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Negotiating with no contexts is not an error, but selects nothing.
+   * An explicit empty list overrides a non-empty implicit list.
+   */
+  if (nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  p = (struct progress) { .count = 0 };
+  {
+    char *empty[] = { NULL };
+    r = nbd_opt_set_meta_context_queries (nbd, empty, ctx);
+  }
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (r != 0 || p.count || p.seen) {
+    fprintf (stderr, "expecting set_meta to select nothing\n");
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Request 2 with expectation of 1. */
+  p = (struct progress) { .count = 0 };
+  {
+    char *pair[] = { "x-nosuch:context", LIBNBD_CONTEXT_BASE_ALLOCATION, NULL 
};
+    r = nbd_opt_set_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 one context, got %d\n", r);
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 1) {
+    fprintf (stderr, "can_meta_context got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Transition to transmission phase with with set_request_meta_context off,
+   * our last set should remain active
+   */
+  if (nbd_set_request_meta_context (nbd, 0) == -1 ||
+      nbd_opt_go (nbd) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 1) {
+    fprintf (stderr, "can_meta_context got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  nbd_shutdown (nbd, 0);
+  nbd_close (nbd);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/tests/opt-set-meta.c b/tests/opt-set-meta.c
new file mode 100644
index 00000000..95f5a6d7
--- /dev/null
+++ b/tests/opt-set-meta.c
@@ -0,0 +1,234 @@
+/* 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_set_meta_context. */
+
+#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>
+
+#include "array-size.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;
+  /* Leave room for --no-sr in second process */
+  char *args[] = { "nbdkit", "-s", "--exit-with-parent", "-v",
+                   "memory", "size=1M", NULL, NULL };
+
+  /* First process, with structured replies. 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);
+  }
+
+  /* No contexts negotiated yet; can_meta should be error if any requested */
+  if ((r = nbd_get_structured_replies_negotiated (nbd)) != 1) {
+    fprintf (stderr, "structured replies got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+  if (nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != -1) {
+    fprintf (stderr, "can_meta_context got %d, expected -1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* FIXME: Once nbd_opt_structured_reply exists, check that set before
+   * SR fails server-side, then enable SR for rest of process.
+   */
+
+  /* nbdkit does not match wildcard for SET, even though it does for LIST */
+  if (nbd_clear_meta_contexts (nbd) == -1 ||
+      nbd_add_meta_context (nbd, "base:") == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  p = (struct progress) { .count = 0 };
+  r = nbd_opt_set_meta_context (nbd,
+                                (nbd_context_callback) { .callback = check,
+                                                         .user_data = &p});
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (r != p.count || r != 0 || p.seen) {
+    fprintf (stderr, "inconsistent return value %d, expected %d\n", r, 
p.count);
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Negotiating with no contexts is not an error, but selects nothing */
+  r = nbd_clear_meta_contexts (nbd);
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  p = (struct progress) { .count = 0 };
+  r = nbd_opt_set_meta_context (nbd,
+                                (nbd_context_callback) { .callback = check,
+                                                         .user_data = &p});
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (r != 0 || p.count || p.seen) {
+    fprintf (stderr, "expecting set_meta to select nothing\n");
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Request 2 with expectation of 1; with set_request_meta_context off */
+  if (nbd_add_meta_context (nbd, "x-nosuch:context") == -1 ||
+      nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) == -1 ||
+      nbd_set_request_meta_context (nbd, 0) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  p = (struct progress) { .count = 0 };
+  r = nbd_opt_set_meta_context (nbd,
+                                (nbd_context_callback) { .callback = check,
+                                                         .user_data = &p});
+  if (r == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if (r != 1 || p.count != 1 || !p.seen) {
+    fprintf (stderr, "expecting one context, got %d\n", r);
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 1) {
+    fprintf (stderr, "can_meta_context got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Transition to transmission phase; our last set should remain active */
+  if (nbd_clear_meta_contexts (nbd) == -1 ||
+      nbd_add_meta_context (nbd, "x-nosuch:context") == -1 ||
+      nbd_opt_go (nbd) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 1) {
+    fprintf (stderr, "can_meta_context got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Now too late to set; but should not lose earlier state */
+  p = (struct progress) { .count = 0 };
+  r = nbd_opt_set_meta_context (nbd,
+                                (nbd_context_callback) { .callback = check,
+                                                         .user_data = &p});
+  if (r != -1 || p.count || p.seen) {
+    fprintf (stderr, "expecting set_meta failure\n");
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 1) {
+    fprintf (stderr, "can_meta_context got %d, expected 1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  nbd_shutdown (nbd, 0);
+  nbd_close (nbd);
+
+  /* Second process, this time without structured replies server-side. */
+  args[ARRAY_SIZE (args) - 2] = (char *) "--no-sr";
+  nbd = nbd_create ();
+  if (nbd == NULL ||
+      nbd_set_opt_mode (nbd, true) == -1 ||
+      nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) == -1 ||
+      nbd_connect_command (nbd, args) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_get_structured_replies_negotiated (nbd)) != 0) {
+    fprintf (stderr, "structured replies got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Expect server-side failure here */
+  p = (struct progress) { .count = 0 };
+  r = nbd_opt_set_meta_context (nbd,
+                                (nbd_context_callback) { .callback = check,
+                                                         .user_data = &p});
+  if (r != -1 || p.count || p.seen) {
+    fprintf (stderr, "expecting set_meta failure\n");
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != -1) {
+    fprintf (stderr, "can_meta_context got %d, expected -1\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  /* Even though can_meta fails after failed SET, it returns 0 after go */
+  if (nbd_opt_go (nbd) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr, "can_meta_context got %d, expected 0\n", r);
+    exit (EXIT_FAILURE);
+  }
+
+  nbd_shutdown (nbd, 0);
+  nbd_close (nbd);
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/.gitignore b/.gitignore
index 272d1ec1..50e4616e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -234,6 +234,8 @@ Makefile.in
 /tests/opt-list
 /tests/opt-list-meta
 /tests/opt-list-meta-queries
+/tests/opt-set-meta
+/tests/opt-set-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