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