[Libguestfs] [libnbd PATCH v2 05/13] api: Add nbd_set_opt_mode

Eric Blake
Fri, 14 Aug 2020 22:00:24 -0000

Now that we have a new NEGOTIATING state, we need a knob for the user
to request moving into that state.  This patch does not actually alter
the state machine, but one thing at a time.  However, the libnbd docs
now mention several commands that will be added in future commits.
(Hmm - our generator refuses to compile in-tree links in API.ml that
don't resolve, but not links in libnbd.pod.)

One thing we can test at this point: an oldstyle server will never
get into opt mode; that will only be possible for a newstyle server.
---
 docs/libnbd.pod                               | 38 ++++++++++++++-
 lib/internal.h                                |  3 ++
 generator/API.ml                              | 46 +++++++++++++++++--
 generator/states-newstyle-opt-go.c            |  1 +
 generator/states-newstyle-opt-list.c          |  1 +
 .../states-newstyle-opt-set-meta-context.c    |  3 +-
 generator/states-newstyle-opt-starttls.c      |  1 +
 .../states-newstyle-opt-structured-reply.c    |  1 +
 lib/opt.c                                     | 41 +++++++++++++++++
 lib/Makefile.am                               |  3 +-
 tests/oldstyle.c                              | 29 ++++++++----
 11 files changed, 151 insertions(+), 16 deletions(-)
 create mode 100644 lib/opt.c

diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index fc66888..4ee0815 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -447,6 +447,40 @@ subprocess.  This works very similarly to
L<nbd_connect_command(3)>
 described above, but you must use
 L<nbd_connect_systemd_socket_activation(3)> instead.

+=head1 CONTROLLING NEGOTIATION
+
+By default, when beginning a connection, libnbd will handle all
+negotiation with the server, using only the configuration
+(eg. L<nbd_set_export_name(3)> or L<nbd_add_meta_context(3)>) that
was
+requested before the connection attempt; this phase continues until
+L<nbd_aio_is_connecting(3)> no longer returns true, at which point,
+either data commands are ready to use or else the connection has
+failed with an error.
+
+But there are scenarios in which it is useful to also control the
+handshaking commands sent during negotiation, such as asking the
+server for a list of available exports prior to selecting which one to
+use.  This is done by calling L<nbd_set_opt_mode(3)> before
+connecting; then after requesting a connection, the state machine will
+pause at L<nbd_aio_is_negotiating(3)> at any point that the user can
+decide which handshake command to send next.  Note that the
+negotiation state is only reachable from newstyle servers; older
+servers cannot negotiate and will progress all the way to the ready
+state.
+
+When the negotiating state is reached, you can initiate option
+commands such as L<nbd_opt_list(3)> or their asynchronous equivalents,
+as well as alter configuration such as export name that previously had
+to be set before connection.  Since the NBD protocol does not allow
+parallel negotiating commands, no cookie is involved, and you can
+track completion of each command when the state is no longer
+L<nbd_aio_is_connecting(3)>.  If L<nbd_opt_go(3)> fails but the
+connection is still live, you will be back in negotiation state, where
+you can request a different export name and try again.  Exiting the
+negotiation state is only possible with a successful L<nbd_opt_go(3)>
+which moves to the data phase, or L<nbd_opt_abort(3)> which performs a
+clean shutdown of the connection by skipping the data phase.
+
 =head1 EXPORTS AND FLAGS

 It is possible for NBD servers to serve different content on different
@@ -562,8 +596,8 @@
L<https://github.com/libguestfs/libnbd/blob/master/interop/dirty-bitmap.c>
 =head2 Issuing multiple in-flight requests

 NBD servers which properly implement the specification can handle
-multiple requests in flight over the same connection at the same time.
-Libnbd supports this when using the low level API.
+multiple data requests in flight over the same connection at the same
+time.  Libnbd supports this when using the low level API.

 To use it you simply issue more requests as needed (eg. using calls
 like L<nbd_aio_pread(3)>, L<nbd_aio_pwrite(3)>) without waiting for
previous
diff --git a/lib/internal.h b/lib/internal.h
index 186d677..5f495fb 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -99,6 +99,9 @@ struct nbd_handle {
   int uri_allow_tls;
   bool uri_allow_local_file;

+  /* Option negotiation mode. */
+  bool opt_mode;
+
   /* List exports mode. */
   bool list_exports;
   size_t nr_exports;
diff --git a/generator/API.ml b/generator/API.ml
index a363117..3dd94f6 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -681,6 +681,40 @@ the time of compilation.";
                 Link "aio_is_created"; Link
"aio_is_ready"];
   };

+  "set_opt_mode", {
+    default_call with
+    args = [Bool "enable"]; ret = RErr;
+    permitted_states = [ Created ];
+    shortdesc = "control option mode, for pausing during option
negotiation";
+    longdesc = "\
+Set this flag to true in order to request that a connection command
+C<nbd_connect_*> will pause for negotiation options rather than
+proceeding all the way to the ready state, when communicating with a
+newstyle server.  This setting has no effect when connecting to an
+oldstyle server.
+
+When option mode is enabled, you have fine-grained control over which
+options are negotiated, compared to the default of the server
+negotiating everything on your behalf using settings made before
+starting the connection.  To leave the mode and proceed on to the
+ready state, you must use nbd_opt_go successfully; a failed
+C<nbd_opt_go> returns to the negotiating state to allow a change of
+export name before trying again.  You may also use nbd_opt_abort
+to end the connection without finishing negotiation.";
+    example = Some "examples/list-exports.c";
+    see_also = [Link "get_opt_mode"; Link
"aio_is_negotiating"];
+  };
+
+  "get_opt_mode", {
+    default_call with
+    args = []; ret = RBool;
+    may_set_error = false;
+    shortdesc = "return whether option mode was enabled";
+    longdesc = "\
+Return true if option negotiation mode was enabled on this handle.";
+    see_also = [Link "set_opt_mode"];
+  };
+
   "set_list_exports", {
     default_call with
     args = [Bool "list"]; ret = RErr;
@@ -889,7 +923,7 @@ the NBD URI.  This call parses the URI and calls
 L<nbd_set_export_name(3)> and L<nbd_set_tls(3)> and other
 calls as needed, followed by L<nbd_connect_tcp(3)> or
 L<nbd_connect_unix(3)>.  However, it is possible to override the
-export name portion of a URI by using C<nbd_set_opt_mode> to
+export name portion of a URI by using L<nbd_set_opt_mode(3)> to
 enable option mode, then using L<nbd_set_export_name(3)> as part
 of later negotiation.

@@ -1032,7 +1066,8 @@ Support for URIs that require TLS will fail if libnbd was
not
 compiled with gnutls; you can test whether this is the case
 with L<nbd_supports_tls(3)>.";
     see_also = [URLLink
"https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md";
-                Link "set_export_name"; Link "set_tls"];
+                Link "set_export_name"; Link "set_tls";
+                Link "set_opt_mode"];
   };

   "connect_unix", {
@@ -2151,10 +2186,11 @@ Return true if this connection is ready to start another
option
 negotiation command while handshaking with the server.  An option
 command will move back to the connecting state (see
 L<nbd_aio_is_connecting(3)>).  Note that this state cannot be
-reached unless requested by nbd_set_opt_mode, and even then
+reached unless requested by L<nbd_set_opt_mode(3)>, and even then
 it only works with newstyle servers; an oldstyle server will skip
 straight to L<nbd_aio_is_ready(3)>.";
-    see_also = [Link "aio_is_connecting"; Link
"aio_is_ready"];
+    see_also = [Link "aio_is_connecting"; Link
"aio_is_ready";
+                Link "set_opt_mode"];
   };

   "aio_is_ready", {
@@ -2465,6 +2501,8 @@ let first_version = [
   "get_full_info", (1, 4);
   "get_canonical_export_name", (1, 4);
   "get_export_description", (1, 4);
+  "set_opt_mode", (1, 4);
+  "get_opt_mode", (1, 4);
   "aio_is_negotiating", (1, 4);

   (* These calls are proposed for a future version of libnbd, but
diff --git a/generator/states-newstyle-opt-go.c
b/generator/states-newstyle-opt-go.c
index 74f092e..d696cae 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -22,6 +22,7 @@ STATE_MACHINE {
  NEWSTYLE.OPT_GO.START:
   uint16_t nrinfos = h->full_info ? 3 : 1;

+  assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
   h->sbuf.option.option = htobe32 (NBD_OPT_GO);
   h->sbuf.option.optlen diff --git a/generator/states-newstyle-opt-list.c
b/generator/states-newstyle-opt-list.c
index 80fcb1b..f2846b6 100644
--- a/generator/states-newstyle-opt-list.c
+++ b/generator/states-newstyle-opt-list.c
@@ -23,6 +23,7 @@

 STATE_MACHINE {
  NEWSTYLE.OPT_LIST.START:
+  assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   if (!h->list_exports) {
     SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
     return 0;
diff --git a/generator/states-newstyle-opt-set-meta-context.c
b/generator/states-newstyle-opt-set-meta-context.c
index 92fe26c..77fd022 100644
--- a/generator/states-newstyle-opt-set-meta-context.c
+++ b/generator/states-newstyle-opt-set-meta-context.c
@@ -1,5 +1,5 @@
 /* nbd client library in userspace: state machine
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 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
@@ -27,6 +27,7 @@ STATE_MACHINE {
    * Also we skip the group if the client didn't request any metadata
    * contexts.
    */
+  assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   if (!h->structured_replies ||
       h->request_meta_contexts == NULL ||
       nbd_internal_string_list_length (h->request_meta_contexts) == 0) {
diff --git a/generator/states-newstyle-opt-starttls.c
b/generator/states-newstyle-opt-starttls.c
index 2d74e5f..7162c7a 100644
--- a/generator/states-newstyle-opt-starttls.c
+++ b/generator/states-newstyle-opt-starttls.c
@@ -20,6 +20,7 @@

 STATE_MACHINE {
  NEWSTYLE.OPT_STARTTLS.START:
+  assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   /* If TLS was not requested we skip this option and go to the next one. */
   if (h->tls == LIBNBD_TLS_DISABLE) {
     SET_NEXT_STATE (%^OPT_LIST.START);
diff --git a/generator/states-newstyle-opt-structured-reply.c
b/generator/states-newstyle-opt-structured-reply.c
index cfe6e0d..58a76db 100644
--- a/generator/states-newstyle-opt-structured-reply.c
+++ b/generator/states-newstyle-opt-structured-reply.c
@@ -20,6 +20,7 @@

 STATE_MACHINE {
  NEWSTYLE.OPT_STRUCTURED_REPLY.START:
+  assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
   if (!h->request_sr) {
     SET_NEXT_STATE (%^OPT_SET_META_CONTEXT.START);
     return 0;
diff --git a/lib/opt.c b/lib/opt.c
new file mode 100644
index 0000000..306a2e9
--- /dev/null
+++ b/lib/opt.c
@@ -0,0 +1,41 @@
+/* NBD client library in userspace
+ * Copyright (C) 2020 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
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+
+#include "internal.h"
+
+int
+nbd_unlocked_set_opt_mode (struct nbd_handle *h, bool value)
+{
+  h->opt_mode = value;
+  return 0;
+}
+
+/* NB: may_set_error = false. */
+int
+nbd_unlocked_get_opt_mode (struct nbd_handle *h)
+{
+  return h->opt_mode;
+}
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 1c46c54..9fd6331 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,5 +1,5 @@
 # nbd client library in userspace
-# Copyright (C) 2013-2019 Red Hat Inc.
+# Copyright (C) 2013-2020 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
@@ -47,6 +47,7 @@ libnbd_la_SOURCES = \
 	internal.h \
 	is-state.c \
 	nbd-protocol.h \
+	opt.c \
 	poll.c \
 	protocol.c \
 	rw.c \
diff --git a/tests/oldstyle.c b/tests/oldstyle.c
index dc58d94..fd8bdc5 100644
--- a/tests/oldstyle.c
+++ b/tests/oldstyle.c
@@ -1,5 +1,5 @@
 /* NBD client library in userspace
- * Copyright (C) 2013-2019 Red Hat Inc.
+ * Copyright (C) 2013-2020 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
@@ -106,18 +106,26 @@ main (int argc, char *argv[])
   }
   nbd_close (nbd);

-  /* Now for a working connection */
+  /* Now for a working connection. Requesting an export name and opt_mode
+   * have no effects.
+   */
   nbd = nbd_create ();
-  if (nbd == NULL) {
-    fprintf (stderr, "%s\n", nbd_get_error ());
-    exit (EXIT_FAILURE);
-  }
-  if (nbd_connect_command (nbd, args) == -1) {
+  if (nbd == NULL ||
+      nbd_set_opt_mode (nbd, true) == -1 ||
+      nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) == -1 ||
+      nbd_set_export_name (nbd, "ignored") == -1 ||
+      nbd_connect_command (nbd, args) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }

-  /* Protocol should be "oldstyle", with no structured replies. */
+  /* Protocol should be "oldstyle", with no structured replies or
meta
+   * contexts.
+   */
+  if (nbd_aio_is_ready (nbd) != true) {
+    fprintf (stderr, "unexpected state after connection\n");
+    exit (EXIT_FAILURE);
+  }
   s = nbd_get_protocol (nbd);
   if (strcmp (s, "oldstyle") != 0) {
     fprintf (stderr,
@@ -129,6 +137,11 @@ main (int argc, char *argv[])
              "incorrect structured replies %" PRId64 ", expected
0\n", r);
     exit (EXIT_FAILURE);
   }
+  if ((r = nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION)) != 0) {
+    fprintf (stderr,
+             "incorrect meta context %" PRId64 ", expected
0\n", r);
+    exit (EXIT_FAILURE);
+  }

   if ((r = nbd_get_size (nbd)) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
--
2.28.0