Right now, we require the user to supply potential metacontext names
in advance, without knowing what the server actually supports until
after the connection or nbd_opt_info call is complete. But the NBD
protocol also supports a client being able to query what contexts a
server supports, including where a client asks with a prefix and the
server replies back with all answers that match the prefix.
Not to mention this will allow nbdinfo to gain full feature parity
with 'qemu-nbd --list'.
---
lib/internal.h | 1 +
generator/API.ml | 84 +++++++++++++++++++-
generator/states-newstyle-opt-meta-context.c | 80 +++++++++++++++----
generator/states-newstyle.c | 3 +
lib/opt.c | 73 +++++++++++++++++
TODO | 11 +++
6 files changed, 234 insertions(+), 18 deletions(-)
diff --git a/lib/internal.h b/lib/internal.h
index cde5dcd..ad1eeb9 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -77,6 +77,7 @@ struct command_cb {
nbd_extent_callback extent;
nbd_chunk_callback chunk;
nbd_list_callback list;
+ nbd_context_callback context;
} fn;
nbd_completion_callback completion;
};
diff --git a/generator/API.ml b/generator/API.ml
index 938ace4..358ec38 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -142,8 +142,13 @@ let list_closure = {
cbname = "list";
cbargs = [ CBString "name"; CBString "description" ]
}
+let context_closure = {
+ cbname = "context";
+ cbargs = [ CBString "name" ]
+}
let all_closures = [ chunk_closure; completion_closure;
- debug_closure; extent_closure; list_closure ]
+ debug_closure; extent_closure; list_closure;
+ context_closure ]
(* Enums. *)
let tls_enum = {
@@ -852,7 +857,7 @@ to end the connection without finishing negotiation.";
example = Some "examples/list-exports.c";
see_also = [Link "get_opt_mode"; Link "aio_is_negotiating";
Link "opt_abort"; Link "opt_go"; Link
"opt_list";
- Link "opt_info"];
+ Link "opt_info"; Link "opt_list_meta_context"];
};
"get_opt_mode", {
@@ -960,6 +965,56 @@ corresponding L<nbd_opt_go(3)> would succeed.";
Link "set_export_name"];
};
+ "opt_list_meta_context", {
+ default_call with
+ args = [ Closure context_closure ]; ret = RInt;
+ permitted_states = [ Negotiating ];
+ shortdesc = "request the server to list available meta contexts";
+ longdesc = "\
+Request that the server list available meta contexts associated 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.
+
+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.
+When the list is empty, a server will typically reply with all
+contexts that it supports; when the list is non-empty, the server
+will reply only with supported contexts that match the client's
+request. Note that a reply by the server might be encoded to
+represent several feasible contexts on one string, rather than an
+actual context name that would actually succeed during
+L<nbd_opt_go(3)>; so it is still necessary to use
+L<nbd_can_meta_context(3)> after connecting to see which contexts
+are actually supported.
+
+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. Remember that it is not safe to call
+L<nbd_add_meta_context(3)> from within the context of the
+callback function; rather, your code must copy any C<name> needed for
+later use after this function completes. 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. Likewise,
+the NBD protocol does not specify an upper bound for the number of
+replies that might be advertised, so client code should be aware that
+a server may send a lengthy list.";
+ see_also = [Link "set_opt_mode"; Link
"aio_opt_list_meta_context";
+ Link "add_meta_context"; Link "clear_meta_contexts";
+ Link "opt_go"; Link "set_export_name"];
+ };
+
"add_meta_context", {
default_call with
args = [ String "name" ]; ret = RErr;
@@ -2233,6 +2288,29 @@ callback.";
see_also = [Link "set_opt_mode"; Link "opt_info"; Link
"is_read_only"];
};
+ "aio_opt_list_meta_context", {
+ default_call with
+ args = [ Closure context_closure ]; ret = RInt;
+ optargs = [ OClosure completion_closure ];
+ permitted_states = [ Negotiating ];
+ shortdesc = "request the server to list available meta contexts";
+ longdesc = "\
+Request that the server list available meta contexts associated 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.
+
+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_list_meta_context"];
+ };
+
"aio_pread", {
default_call with
args = [ BytesPersistOut ("buf", "count"); UInt64
"offset" ];
@@ -2884,6 +2962,8 @@ let first_version = [
"get_nr_meta_contexts", (1, 6);
"get_meta_context", (1, 6);
"clear_meta_contexts", (1, 6);
+ "opt_list_meta_context", (1, 6);
+ "aio_opt_list_meta_context", (1, 6);
(* 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 0dc48af..ce878ee 100644
--- a/generator/states-newstyle-opt-meta-context.c
+++ b/generator/states-newstyle-opt-meta-context.c
@@ -21,18 +21,28 @@
STATE_MACHINE {
NEWSTYLE.OPT_META_CONTEXT.START:
size_t i, nr_queries;
- uint32_t len;
+ uint32_t len, opt;
/* If the server doesn't support SRs then we must skip this group.
* Also we skip the group if the client didn't request any metadata
- * contexts.
+ * contexts, when doing SET (but an empty LIST is okay).
*/
assert (h->gflags & LIBNBD_HANDSHAKE_FLAG_FIXED_NEWSTYLE);
nbd_internal_reset_size_and_flags (h);
- if (!h->structured_replies ||
- nbd_internal_string_list_length (h->request_meta_contexts) == 0) {
- SET_NEXT_STATE (%^OPT_GO.START);
- return 0;
+ if (h->opt_current == NBD_OPT_LIST_META_CONTEXT) {
+ assert (h->opt_mode);
+ assert (h->structured_replies);
+ assert (CALLBACK_IS_NOT_NULL (h->opt_cb.fn.context));
+ opt = h->opt_current;
+ }
+ else {
+ assert (CALLBACK_IS_NULL (h->opt_cb.fn.context));
+ opt = NBD_OPT_SET_META_CONTEXT;
+ if (!h->structured_replies ||
+ nbd_internal_string_list_length (h->request_meta_contexts) == 0) {
+ SET_NEXT_STATE (%^OPT_GO.START);
+ return 0;
+ }
}
assert (h->meta_contexts == NULL);
@@ -44,7 +54,7 @@ STATE_MACHINE {
len += 4 /* length of query */ + strlen (h->request_meta_contexts[i]);
h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
- h->sbuf.option.option = htobe32 (NBD_OPT_SET_META_CONTEXT);
+ h->sbuf.option.option = htobe32 (opt);
h->sbuf.option.optlen = htobe32 (len);
h->wbuf = &h->sbuf;
h->wlen = sizeof (h->sbuf.option);
@@ -98,7 +108,8 @@ STATE_MACHINE {
return 0;
NEWSTYLE.OPT_META_CONTEXT.PREPARE_NEXT_QUERY:
- const char *query = h->request_meta_contexts[h->querynum];
+ const char *query = !h->request_meta_contexts ? NULL
+ : h->request_meta_contexts[h->querynum];
if (query == NULL) { /* end of list of requested meta contexts */
SET_NEXT_STATE (%PREPARE_FOR_REPLY);
@@ -140,10 +151,17 @@ STATE_MACHINE {
return 0;
NEWSTYLE.OPT_META_CONTEXT.RECV_REPLY:
+ uint32_t opt;
+
+ if (h->opt_current == NBD_OPT_LIST_META_CONTEXT)
+ opt = h->opt_current;
+ else
+ opt = NBD_OPT_SET_META_CONTEXT;
+
switch (recv_into_rbuf (h)) {
case -1: SET_NEXT_STATE (%.DEAD); return 0;
case 0:
- if (prepare_for_reply_payload (h, NBD_OPT_SET_META_CONTEXT) == -1) {
+ if (prepare_for_reply_payload (h, opt) == -1) {
SET_NEXT_STATE (%.DEAD);
return 0;
}
@@ -163,12 +181,25 @@ STATE_MACHINE {
uint32_t len;
const size_t maxpayload = sizeof h->sbuf.or.payload.context;
struct meta_context *meta_context;
+ uint32_t opt;
+ int err = 0;
+
+ if (h->opt_current == NBD_OPT_LIST_META_CONTEXT)
+ opt = h->opt_current;
+ else
+ opt = NBD_OPT_SET_META_CONTEXT;
reply = be32toh (h->sbuf.or.option_reply.reply);
len = be32toh (h->sbuf.or.option_reply.replylen);
switch (reply) {
case NBD_REP_ACK: /* End of list of replies. */
- SET_NEXT_STATE (%^OPT_GO.START);
+ 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);
break;
case NBD_REP_META_CONTEXT: /* A context. */
if (len > maxpayload)
@@ -194,21 +225,38 @@ STATE_MACHINE {
}
debug (h, "negotiated %s with context ID %" PRIu32,
meta_context->name, meta_context->context_id);
- meta_context->next = h->meta_contexts;
- h->meta_contexts = meta_context;
+ if (opt == NBD_OPT_LIST_META_CONTEXT) {
+ CALL_CALLBACK (h->opt_cb.fn.context, meta_context->name);
+ free (meta_context->name);
+ free (meta_context);
+ }
+ else {
+ meta_context->next = h->meta_contexts;
+ h->meta_contexts = meta_context;
+ }
}
SET_NEXT_STATE (%PREPARE_FOR_REPLY);
break;
default:
- /* Anything else is an error, ignore it */
+ /* Anything else is an error, ignore it for SET, report it for LIST */
if (handle_reply_error (h) == -1) {
SET_NEXT_STATE (%.DEAD);
return 0;
}
- debug (h, "handshake: unexpected error from "
- "NBD_OPT_SET_META_CONTEXT (%" PRIu32 ")", reply);
- SET_NEXT_STATE (%^OPT_GO.START);
+ if (opt == NBD_OPT_LIST_META_CONTEXT) {
+ err = ENOTSUP;
+ set_error (err, "unexpected response, possibly the server does not "
+ "support listing contexts");
+ CALL_CALLBACK (h->opt_cb.completion, &err);
+ nbd_internal_free_option (h);
+ SET_NEXT_STATE (%.NEGOTIATING);
+ }
+ else {
+ debug (h, "handshake: unexpected error from "
+ "NBD_OPT_SET_META_CONTEXT (%" PRIu32 ")", reply);
+ SET_NEXT_STATE (%^OPT_GO.START);
+ }
break;
}
return 0;
diff --git a/generator/states-newstyle.c b/generator/states-newstyle.c
index a0a5928..6fb7548 100644
--- a/generator/states-newstyle.c
+++ b/generator/states-newstyle.c
@@ -136,6 +136,9 @@ STATE_MACHINE {
}
SET_NEXT_STATE (%PREPARE_OPT_ABORT);
return 0;
+ case NBD_OPT_LIST_META_CONTEXT:
+ SET_NEXT_STATE (%OPT_META_CONTEXT.START);
+ return 0;
case 0:
break;
default:
diff --git a/lib/opt.c b/lib/opt.c
index 6ea8326..2317b72 100644
--- a/lib/opt.c
+++ b/lib/opt.c
@@ -32,6 +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)
+ FREE_CALLBACK (h->opt_cb.fn.context);
FREE_CALLBACK (h->opt_cb.completion);
}
@@ -166,6 +168,51 @@ nbd_unlocked_opt_list (struct nbd_handle *h, nbd_list_callback
*list)
return s.count;
}
+struct context_helper {
+ int count;
+ nbd_context_callback context;
+ int err;
+};
+static int
+context_visitor (void *opaque, const char *name)
+{
+ struct context_helper *h = opaque;
+ if (h->count < INT_MAX)
+ h->count++;
+ CALL_CALLBACK (h->context, name);
+ return 0;
+}
+static int
+context_complete (void *opaque, int *err)
+{
+ struct context_helper *h = opaque;
+ h->err = *err;
+ FREE_CALLBACK (h->context);
+ return 0;
+}
+
+/* Issue NBD_OPT_LIST_META_CONTEXT and wait for the reply. */
+int
+nbd_unlocked_opt_list_meta_context (struct nbd_handle *h,
+ 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_list_meta_context (h, &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 list 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,
@@ -230,3 +277,29 @@ nbd_unlocked_aio_opt_list (struct nbd_handle *h, nbd_list_callback
*list,
debug (h, "option queued, ignoring state machine failure");
return 0;
}
+
+/* Issue NBD_OPT_LIST_META_CONTEXT without waiting. */
+int
+nbd_unlocked_aio_opt_list_meta_context (struct nbd_handle *h,
+ 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 (!h->structured_replies) {
+ set_error (ENOTSUP, "server lacks structured replies");
+ 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_LIST_META_CONTEXT;
+ if (nbd_internal_run (h, cmd_issue) == -1)
+ debug (h, "option queued, ignoring state machine failure");
+ return 0;
+}
diff --git a/TODO b/TODO
index 4a0cd22..b6c676b 100644
--- a/TODO
+++ b/TODO
@@ -13,6 +13,17 @@ NBD resize extension.
TLS should properly shut down the session (calling gnutls_bye).
+Potential deadlock with nbd_add_meta_context: if a client sends enough
+requests to the server that it blocks while writing, but the server
+replies to requests as they come in rather than waiting for the end of
+the client request, then the server can likewise block in writing
+replies that libnbd is not yet reading. Not an issue for existing
+servers that don't have enough contexts to reply with enough data to
+fill buffers, but could be an issue with qemu-nbd if it is taught to
+exports many dirty bitmaps simultaneously. Revamping the
+states-newstyle-meta-context.c state machine to let libnbd handle
+NBD_REP_META_CONTEXT while still writing queries could be hairy.
+
Performance: Chart it over various buffer sizes and threads, as that
should make it easier to identify systematic issues.
--
2.28.0