[Libguestfs] [nbdkit PATCH 2/2] nbd: Add shared=true parameter

Eric Blake
Sat, 25 May 2019 22:32:24 -0000

qemu-nbd defaults to permitting only a single NBD client. Of course,
you can run qemu-nbd -t to work around it, but other servers may have
similar restrictions, at which point nbdkit's nbd plugin can provide
the automatic fanout to multiple clients via a single server link.

Note that the shared=true parameter makes the previously-added retry=N
parameter more useful, as it is much easier to run into the scenario
where the server has not created the socket in time for
.config_complete, when compared to a later .open from nbdkit's first
client.

Signed-off-by: Eric Blake <eblake@redhat.com>
---
 plugins/nbd/nbdkit-nbd-plugin.pod |  18 ++++-
 plugins/nbd/nbd.c                 | 110 ++++++++++++++++++++----------
 2 files changed, 88 insertions(+), 40 deletions(-)

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod
b/plugins/nbd/nbdkit-nbd-plugin.pod
index 4f9efa1..8dd8681 100644
--- a/plugins/nbd/nbdkit-nbd-plugin.pod
+++ b/plugins/nbd/nbdkit-nbd-plugin.pod
@@ -5,7 +5,7 @@ nbdkit-nbd-plugin - nbdkit nbd plugin
 =head1 SYNOPSIS

  nbdkit nbd { socket=SOCKNAME | hostname=HOST [port=PORT] } [export=NAME]
-    [retry=N]
+    [retry=N] [shared=BOOL]

 =head1 DESCRIPTION

@@ -56,6 +56,15 @@ empty string).
 If the initial connection attempt to the server fails, retry up to
 B<N> times more after a second delay between tries (default 0).

+=item B<shared=>BOOL
+
+If this parameter is false (default), the plugin will open a distinct
+connection to the server for each client making a connection to
+nbdkit, and the remote server need not be running at the time nbdkit
+is started. If this parameter is set to true, the plugin will open a
+single connection to the server when nbdkit is first started, and all
+clients to nbdkit will share that single connection.
+
 =back

 =head1 EXAMPLES
@@ -76,10 +85,13 @@ that the old server exits.
 Combine nbdkit's partition filter with qemu-nbd's ability to visit
 qcow2 files (nbdkit does not have a native qcow2 plugin), performing
 the same task as the deprecated C<qemu-nbd -P 1 -f qcow2
-/path/to/image.qcow2> command:
+/path/to/image.qcow2> command. Allow multiple clients, even though
+C<qemu-nbd> without B<-t> normally quits after the first client,
and
+utilize a 5-second retry to give qemu-nbd time to create the socket:

  ( sock=`mktemp -u`
-   nbdkit --exit-with-parent --filter=partition nbd socket=$sock partition=1
&
+   nbdkit --exit-with-parent --filter=partition nbd socket=$sock \
+     shared=1 retry=5 partition=1 &
    exec qemu-nbd -k $sock -f qcow2 /path/to/image.qcow2 )

 Conversely, expose the contents of export I<foo> from a new style
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index 6665cd9..9389a55 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -57,6 +57,37 @@
 #include "byte-swapping.h"
 #include "cleanup.h"

+/* The per-transaction details */
+struct transaction {
+  uint64_t cookie;
+  sem_t sem;
+  void *buf;
+  uint64_t offset;
+  uint32_t count;
+  uint32_t err;
+  struct nbdkit_extents *extents;
+  struct transaction *next;
+};
+
+/* The per-connection handle */
+struct handle {
+  /* These fields are read-only once initialized */
+  int fd;
+  int flags;
+  int64_t size;
+  bool structured;
+  bool extents;
+  pthread_t reader;
+
+  /* Prevents concurrent threads from interleaving writes to server */
+  pthread_mutex_t write_lock;
+
+  pthread_mutex_t trans_lock; /* Covers access to all fields below */
+  struct transaction *trans;
+  uint64_t unique;
+  bool dead;
+};
+
 /* Connect to server via absolute name of Unix socket */
 static char *sockname;

@@ -73,9 +104,18 @@ static const char *export;
 /* Number of retries */
 static unsigned long retry;

+/* True to share single server connection among all clients */
+static bool shared;
+static struct handle *shared_handle;
+
+static struct handle *nbd_open_handle (int readonly);
+static void nbd_close_handle (struct handle *h);
+
 static void
 nbd_unload (void)
 {
+  if (shared)
+    nbd_close_handle (shared_handle);
   free (sockname);
   free (servname);
 }
@@ -89,6 +129,7 @@ static int
 nbd_config (const char *key, const char *value)
 {
   char *end;
+  int r;

   if (strcmp (key, "socket") == 0) {
     /* See FILENAMES AND PATHS in nbdkit-plugin(3) */
@@ -111,6 +152,12 @@ nbd_config (const char *key, const char *value)
       return -1;
     }
   }
+  else if (strcmp (key, "shared") == 0) {
+    r = nbdkit_parse_bool (value);
+    if (r == -1)
+      return -1;
+    shared = r;
+  }
   else {
     nbdkit_error ("unknown parameter '%s'", key);
     return -1;
@@ -157,6 +204,9 @@ nbd_config_complete (void)

   if (!export)
     export = "";
+
+  if (shared && (shared_handle = nbd_open_handle (false)) == NULL)
+    return -1;
   return 0;
 }

@@ -168,37 +218,6 @@ nbd_config_complete (void)

 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL

-/* The per-transaction details */
-struct transaction {
-  uint64_t cookie;
-  sem_t sem;
-  void *buf;
-  uint64_t offset;
-  uint32_t count;
-  uint32_t err;
-  struct nbdkit_extents *extents;
-  struct transaction *next;
-};
-
-/* The per-connection handle */
-struct handle {
-  /* These fields are read-only once initialized */
-  int fd;
-  int flags;
-  int64_t size;
-  bool structured;
-  bool extents;
-  pthread_t reader;
-
-  /* Prevents concurrent threads from interleaving writes to server */
-  pthread_mutex_t write_lock;
-
-  pthread_mutex_t trans_lock; /* Covers access to all fields below */
-  struct transaction *trans;
-  uint64_t unique;
-  bool dead;
-};
-
 /* Read an entire buffer, returning 0 on success or -1 with errno set. */
 static int
 read_full (int fd, void *buf, size_t len)
@@ -960,9 +979,9 @@ nbd_connect_tcp (void)
   return fd;
 }

-/* Create the per-connection handle. */
-static void *
-nbd_open (int readonly)
+/* Create the shared or per-connection handle. */
+static struct handle *
+nbd_open_handle (int readonly)
 {
   struct handle *h;
   struct old_handshake old;
@@ -1091,12 +1110,19 @@ nbd_open (int readonly)
   return NULL;
 }

+/* Create the per-connection handle. */
+static void *
+nbd_open (int readonly)
+{
+  if (shared)
+    return shared_handle;
+  return nbd_open_handle (readonly);
+}
+
 /* Free up the per-connection handle. */
 static void
-nbd_close (void *handle)
+nbd_close_handle (struct handle *h)
 {
-  struct handle *h = handle;
-
   if (!h->dead) {
     nbd_request_raw (h, 0, NBD_CMD_DISC, 0, 0, 0, NULL);
     shutdown (h->fd, SHUT_WR);
@@ -1109,6 +1135,16 @@ nbd_close (void *handle)
   free (h);
 }

+/* Free up the per-connection handle. */
+static void
+nbd_close (void *handle)
+{
+  struct handle *h = handle;
+
+  if (!shared)
+    nbd_close_handle (h);
+}
+
 /* Get the file size. */
 static int64_t
 nbd_get_size (void *handle)
--
2.20.1