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-bitma...
=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