[Libguestfs] [libnbd PATCH v2 01/13] api: Add nbd_set_full_info and friends

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

Add APIs to make it possible to request and display NBD_INFO_NAME and
NBD_INFO_DESCRIPTION, which will enable fixing one of the todo items
in nbdinfo when not used in --list mode.  Note that at least qemu-nbd
does not advertise name and description during NBD_OPT_GO unless the
client requests it first; at the same time, always requesting it only
slows down handshaking, so exposing a knob seems better than always
doing this check (contrast that with NBD_INFO_BLOCK_SIZE, which
affects correctness of data access).
---
 lib/internal.h                     | 13 ++++-
 lib/nbd-protocol.h                 | 10 +++-
 generator/API.ml                   | 81 +++++++++++++++++++++++++++++-
 generator/states-newstyle-opt-go.c | 49 +++++++++++++++---
 lib/handle.c                       | 61 ++++++++++++++++++++++
 5 files changed, 204 insertions(+), 10 deletions(-)

diff --git a/lib/internal.h b/lib/internal.h
index 6106af3..aa7e9a2 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -104,6 +104,9 @@ struct nbd_handle {
   size_t nr_exports;
   struct export *exports;

+  /* Full info mode. */
+  bool full_info;
+
   /* Global flags from the server. */
   uint16_t gflags;

@@ -120,6 +123,10 @@ struct nbd_handle {
   uint32_t block_preferred;
   uint32_t block_maximum;

+  /* Server canonical name and description, or NULL if not advertised. */
+  char *canonical_name;
+  char *description;
+
   /* Flags set by the state machine to tell what protocol and whether
    * TLS was negotiated.
    */
@@ -180,6 +187,10 @@ struct nbd_handle {
         } __attribute__((packed)) server;
         struct nbd_fixed_new_option_reply_info_export export;
         struct nbd_fixed_new_option_reply_info_block_size block_size;
+        struct {
+          struct nbd_fixed_new_option_reply_info_name_or_desc info;
+          char str[NBD_MAX_STRING];
+        } __attribute__((packed)) name_desc;
         struct {
           struct nbd_fixed_new_option_reply_meta_context context;
           char str[NBD_MAX_STRING];
@@ -205,7 +216,7 @@ struct nbd_handle {
     uint32_t cflags;
     uint32_t len;
     uint16_t nrinfos;
-    uint16_t info;
+    uint16_t info[3];
     uint32_t nrqueries;
   } sbuf;

diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index 627bc6e..e5d6404 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -154,6 +154,14 @@ struct nbd_fixed_new_option_reply_info_export {
   uint16_t eflags;              /* per-export flags */
 } NBD_ATTRIBUTE_PACKED;

+/* NBD_INFO_NAME or NBD_INFO_DESCRIPTION reply (follows
+ * fixed_new_option_reply).
+ */
+struct nbd_fixed_new_option_reply_info_name_or_desc {
+  uint16_t info;                /* NBD_INFO_NAME, NBD_INFO_DESCRIPTION */
+  /* followed by a string name or description */
+} NBD_ATTRIBUTE_PACKED;
+
 /* NBD_INFO_BLOCK_SIZE reply (follows fixed_new_option_reply). */
 struct nbd_fixed_new_option_reply_info_block_size {
   uint16_t info;                /* NBD_INFO_BLOCK_SIZE */
@@ -165,7 +173,7 @@ struct nbd_fixed_new_option_reply_info_block_size {
 /* NBD_REP_SERVER reply (follows fixed_new_option_reply). */
 struct nbd_fixed_new_option_reply_server {
   uint32_t export_name_len;     /* length of export name */
-  /* followed by a string export name and description*/
+  /* followed by a string export name and description */
 } NBD_ATTRIBUTE_PACKED;

 /* NBD_REP_META_CONTEXT reply (follows fixed_new_option_reply). */
diff --git a/generator/API.ml b/generator/API.ml
index 2f3b57d..eff0ac4 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -294,8 +294,81 @@ to a URI that includes an export name.";
     args = []; ret = RString;
     shortdesc = "get the export name";
     longdesc = "\
-Get the export name associated with the handle.";
-  see_also = [Link "set_export_name"; Link "connect_uri"];
+Get the export name associated with the handle.  This is the name
+that libnbd requests; see L<nbd_get_canonical_export_name(3)> for
+determining if the server has a different canonical name for the
+given export (most common when requesting the default export name
+of an empty string C<\"\">)";
+    see_also = [Link "set_export_name"; Link "connect_uri";
+                Link "get_canonical_export_name"];
+  };
+
+  "set_full_info", {
+    default_call with
+    args = [Bool "request"]; ret = RErr;
+    permitted_states = [ Created ];
+    shortdesc = "control whether NBD_OPT_GO requests extra details";
+    longdesc = "\
+By default, when connecting to an export, libnbd only requests the
+details it needs to service data operations.  The NBD server says
+that a server can supply optional information, such as a canonical
+name of the export (see L<nbd_get_canonical_export_name(3)>) or
+a description of the export (see L<nbd_get_export_description(3)>),
+but that a hint from the client makes it more likely for this
+extra information to be provided.  This function controls whether
+libnbd will provide that hint.
+
+Note that even when full info is requested, the server is not
+obligated to reply with all information that libnbd requested.
+Similarly, libnbd will ignore any optional server information that
+libnbd has not yet been taught to recognize.";
+    see_also = [Link "get_full_info"; Link
"get_canonical_export_name";
+                Link "get_export_description"];
+  };
+
+  "get_full_info", {
+    default_call with
+    args = []; ret = RBool;
+    permitted_states = [];
+    shortdesc = "see if NBD_OPT_GO requests extra details";
+    longdesc = "\
+Return the state of the full info request flag on this handle.";
+    see_also = [Link "set_full_info"];
+  };
+
+  "get_canonical_export_name", {
+    default_call with
+    args = []; ret = RString;
+    permitted_states = [ Connected; Closed ];
+    shortdesc = "return the canonical export name, if the server has
one";
+    longdesc = "\
+The NBD protocol permits a server to report an optional canonical
+export name which may differ from the client's request (as set by
+L<nbd_set_export_name(3)> or L<nbd_connect_uri(3)>.  This function
+accesses any name returned by the server; it may be the same as
+the client request, but is more likely to differ when the client
+requested a connection to the default export name (an empty string
+C<\"\">).
+
+Some servers are unlikely to report a canonical name unless the
+client specifically hinted about wanting it, via
L<nbd_set_full_info(3)>.";
+    see_also = [Link "set_full_info"; Link
"get_export_name"];
+  };
+
+  "get_export_description", {
+    default_call with
+    args = []; ret = RString;
+    permitted_states = [ Connected; Closed ];
+    shortdesc = "return the export description, if the server has
one";
+    longdesc = "\
+The NBD protocol permits a server to report an optional export
+description.  This function reports any description reported by
+the server.
+
+Some servers are unlikely to report description unless the
+client specifically hinted about wanting it, via L<nbd_set_full_info(3)>.
+For L<qemu-nbd(8)>, a description is set with I<-D>.";
+    see_also = [Link "set_full_info"];
   };

   "set_tls", {
@@ -2364,6 +2437,10 @@ let first_version = [
   "get_list_export_name", (1, 4);
   "get_list_export_description", (1, 4);
   "get_block_size", (1, 4);
+  "set_full_info", (1, 4);
+  "get_full_info", (1, 4);
+  "get_canonical_export_name", (1, 4);
+  "get_export_description", (1, 4);

   (* 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-go.c
b/generator/states-newstyle-opt-go.c
index 99a1dcd..74f092e 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -20,11 +20,13 @@

 STATE_MACHINE {
  NEWSTYLE.OPT_GO.START:
+  uint16_t nrinfos = h->full_info ? 3 : 1;
+
   h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
   h->sbuf.option.option = htobe32 (NBD_OPT_GO);
   h->sbuf.option.optlen      htobe32 (/* exportnamelen */ 4 + strlen
(h->export_name)
-             + /* nrinfos */ 2 + /* INFO_BLOCK_SIZE */ 2);
+             + sizeof nrinfos + 2 * nrinfos);
   h->wbuf = &h->sbuf;
   h->wlen = sizeof h->sbuf.option;
   h->wflags = MSG_MORE;
@@ -57,23 +59,29 @@ STATE_MACHINE {
   return 0;

  NEWSTYLE.OPT_GO.SEND_EXPORT:
+  uint16_t nrinfos = h->full_info ? 3 : 1;
+
   switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return 0;
   case 0:
-    h->sbuf.nrinfos = htobe16 (1);
+    h->sbuf.nrinfos = htobe16 (nrinfos);
     h->wbuf = &h->sbuf;
-    h->wlen = 2;
+    h->wlen = sizeof h->sbuf.nrinfos;
     SET_NEXT_STATE (%SEND_NRINFOS);
   }
   return 0;

  NEWSTYLE.OPT_GO.SEND_NRINFOS:
+  uint16_t nrinfos = h->full_info ? 3 : 1;
+
   switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return 0;
   case 0:
-    h->sbuf.info = htobe16 (NBD_INFO_BLOCK_SIZE);
+    h->sbuf.info[0] = htobe16 (NBD_INFO_BLOCK_SIZE);
+    h->sbuf.info[1] = htobe16 (NBD_INFO_NAME);
+    h->sbuf.info[2] = htobe16 (NBD_INFO_DESCRIPTION);
     h->wbuf = &h->sbuf;
-    h->wlen = 2;
+    h->wlen = sizeof h->sbuf.info[0] * nrinfos;
     SET_NEXT_STATE (%SEND_INFO);
   }
   return 0;
@@ -154,8 +162,37 @@ STATE_MACHINE {
           return 0;
         }
         break;
+      case NBD_INFO_NAME:
+        if (len > sizeof h->sbuf.or.payload.name_desc.info +
NBD_MAX_STRING ||
+            len < sizeof h->sbuf.or.payload.name_desc.info) {
+          SET_NEXT_STATE (%.DEAD);
+          set_error (0, "handshake: incorrect NBD_INFO_NAME option reply
length");
+          return 0;
+        }
+        free (h->canonical_name);
+        h->canonical_name = strndup (h->sbuf.or.payload.name_desc.str,
len - 2);
+        if (h->canonical_name == NULL) {
+          SET_NEXT_STATE (%.DEAD);
+          set_error (errno, "strndup");
+          return 0;
+        }
+        break;
+      case NBD_INFO_DESCRIPTION:
+        if (len > sizeof h->sbuf.or.payload.name_desc.info +
NBD_MAX_STRING ||
+            len < sizeof h->sbuf.or.payload.name_desc.info) {
+          SET_NEXT_STATE (%.DEAD);
+          set_error (0, "handshake: incorrect NBD_INFO_DESCRIPTION option
reply length");
+          return 0;
+        }
+        free (h->description);
+        h->description = strndup (h->sbuf.or.payload.name_desc.str, len -
2);
+        if (h->description == NULL) {
+          SET_NEXT_STATE (%.DEAD);
+          set_error (errno, "strndup");
+          return 0;
+        }
+        break;
       default:
-        /* XXX Handle other info types, like NBD_INFO_DESCRIPTION */
         debug (h, "skipping unknown NBD_REP_INFO type %d",
                be16toh (h->sbuf.or.payload.export.info));
         break;
diff --git a/lib/handle.c b/lib/handle.c
index 210ac7d..28c30dd 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -136,6 +136,8 @@ nbd_close (struct nbd_handle *h)
     free (h->exports[i].description);
   }
   free (h->exports);
+  free (h->canonical_name);
+  free (h->description);
   free_cmd_list (h->cmds_to_issue);
   free_cmd_list (h->cmds_in_flight);
   free_cmd_list (h->cmds_done);
@@ -232,6 +234,65 @@ nbd_unlocked_get_export_name (struct nbd_handle *h)
   return copy;
 }

+int
+nbd_unlocked_set_full_info (struct nbd_handle *h, bool request)
+{
+  h->full_info = request;
+  return 0;
+}
+
+int
+nbd_unlocked_get_full_info (struct nbd_handle *h)
+{
+  return h->full_info;
+}
+
+char *
+nbd_unlocked_get_canonical_export_name (struct nbd_handle *h)
+{
+  char *r;
+
+  if (h->eflags == 0) {
+    set_error (EINVAL, "server has not returned export flags, "
+               "you need to connect to the server first");
+    return NULL;
+  }
+  if (h->canonical_name == NULL) {
+    set_error (ENOTSUP, "server did not advertise a canonical name");
+    return NULL;
+  }
+
+  r = strdup (h->canonical_name);
+  if (r == NULL) {
+    set_error (errno, "strdup");
+    return NULL;
+  }
+  return r;
+}
+
+char *
+nbd_unlocked_get_export_description (struct nbd_handle *h)
+{
+  char *r;
+
+  if (h->eflags == 0) {
+    set_error (EINVAL, "server has not returned export flags, "
+               "you need to connect to the server first");
+    return NULL;
+  }
+  if (h->description == NULL) {
+    set_error (ENOTSUP, "server did not advertise a description");
+    return NULL;
+  }
+
+  r = strdup (h->description);
+  if (r == NULL) {
+    set_error (errno, "strdup");
+    return NULL;
+  }
+  return r;
+}
+
 int
 nbd_unlocked_set_list_exports (struct nbd_handle *h, bool list)
 {
--
2.28.0