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