[Libguestfs] [PATCH nbdkit 5/5] nbd: Implement command= and socket-fd= parameters.

Richard W.M. Jones rjones at redhat.com
Tue Jun 30 15:26:41 UTC 2020


[NOT WORKING - DEADLOCKS FOR UNCLEAR REASONS]

* command='qemu-nbd -f qcow2 /path/to/qcow2' runs qemu-nbd (using
  systemd socket activation) as a subprocess.

* socket-fd=5 allows the parent process to set up the NBD socket and
  pass the file descriptor down to nbdkit.

These correspond closely to the libnbd functions
nbd_connect_systemd_socket_activation and nbd_connect_socket.

There is another function, nbd_connect_command, for connecting to a
subprocess using stdin/stdout, but it didn't seem worth wiring that up
at the moment as it can only be used to run a child nbdkit process,
and it's unclear why that would be useful for end users (perhaps more
useful for testing however).
---
 plugins/nbd/nbdkit-nbd-plugin.pod | 62 ++++++++++++++++++++--------
 tests/Makefile.am                 |  2 +
 plugins/nbd/nbd.c                 | 38 ++++++++++++++++--
 tests/test-nbd-qcow2.sh           | 67 +++++++++++++++++++++++++++++++
 4 files changed, 148 insertions(+), 21 deletions(-)

diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 5653703f..513c2433 100644
--- a/plugins/nbd/nbdkit-nbd-plugin.pod
+++ b/plugins/nbd/nbdkit-nbd-plugin.pod
@@ -4,8 +4,10 @@ nbdkit-nbd-plugin - proxy / forward to another NBD server
 
 =head1 SYNOPSIS
 
- nbdkit nbd { hostname=HOST [port=PORT] |
+ nbdkit nbd { connect=COMMAND |
+              hostname=HOST [port=PORT] |
               socket=SOCKNAME |
+              socket-fd=FD |
               [uri=]URI }
             [export=NAME] [retry=N] [shared=BOOL]
             [tls=MODE] [tls-certificates=DIR] [tls-verify=BOOL]
@@ -38,11 +40,21 @@ With L<qemu-nbd(8)>, read and write qcow2 files with nbdkit.
 
 =head1 PARAMETERS
 
-One of B<socket>, B<hostname> (optionally with B<port>), or B<uri>
-must be given to specify which NBD server to forward to:
+One of B<socket>, B<hostname> (optionally with B<port>), B<uri>,
+B<socket-fd> or B<command> must be given to specify which NBD server
+to forward to:
 
 =over 4
 
+=item B<command=>COMMAND
+
+Run an NBD server, usually L<qemu-nbd(8)>, as an external command.
+See L</EXAMPLES> below.  The C<COMMAND> is a string with the command
+name and arguments, so you normally need to quote it to protect it
+from the shell.  This uses the libnbd API
+L<nbd_connect_systemd_socket_activation(3)>.  This option implies
+C<shared=true>.
+
 =item B<hostname=>HOST
 
 Connect to the NBD server at the remote C<HOST> using a TCP socket.
@@ -56,6 +68,12 @@ When C<hostname> is supplied, use C<PORT> instead of the default port
 
 Connect to the NBD server using Unix domain socket C<SOCKNAME>.
 
+=item B<socket-fd=>FD
+
+Connect to the NBD server over a socket file descriptor inherited by
+nbdkit.  This uses the libnbd API L<nbd_connect_socket(3)>.  This
+option implies C<shared=true>.
+
 =item [B<uri=>]URI
 
 When C<uri> is supplied, decode C<URI> to determine the address to
@@ -85,11 +103,16 @@ embedded in the URI instead.
 If the initial connection attempt to the server fails, retry up to
 C<N> times more after a one-second delay between tries (default 0).
 
+=item B<shared=false>
+
 =item B<shared=true>
 
-By default the plugin will open a new connection to the server for
-each client making a connection to nbdkit.  The remote server does not
-have to be started until immediately before the first nbdkit client
+If using C<command> or C<socket-fd> modes then this defaults to true,
+otherwise false.
+
+If false the plugin will open a new connection to the server for each
+client making a connection to nbdkit.  The remote server does not have
+to be started until immediately before the first nbdkit client
 connects.
 
 If this parameter is set to C<true>, the plugin will open a single
@@ -160,24 +183,29 @@ that the old server exits.
  │ new client │ ────────▶│ nbdkit │ ───────────▶│ old server │
  └────────────┘   TCP    └────────┘    Unix     └────────────┘
 
+=head2 Use qemu-nbd to open a qcow2 file
+
+Run qemu-nbd as the server, allowing you to read and write qcow2 files
+(since nbdkit does not have a native qcow2 plugin).  This allows you
+to use nbdkit filters on top, see the next example.
+
+ nbdkit nbd command='qemu-nbd -f qcow2 /path/to/image.qcow2'
+
+qemu-nbd is cleaned up when nbdkit exits.
+
 =head2 Add nbdkit-partition-filter to qemu-nbd
 
-Combine nbdkit's partition filter with L<qemu-nbd(8)>’s ability to
-visit qcow2 files (since nbdkit does not have a native qcow2 plugin).
+Combine L<nbdkit-partition-filter(1)> with L<qemu-nbd(8)>’s ability to
+visit qcow2 files:
+
+ nbdkit --filter=partition nbd \
+        command='qemu-nbd -f qcow2 /path/to/image.qcow2' \
+        partition=1
 
 This performs the same task as the deprecated qemu-nbd I<-P> option:
 
  qemu-nbd -P 1 -f qcow2 /path/to/image.qcow2
 
-Also this allows multiple clients, even though C<qemu-nbd> without
-I<-t> normally quits after the first client, and utilizes a 5-second
-retry to give qemu-nbd time to create the socket:
-
- ( sock=`mktemp -u`
-   nbdkit --exit-with-parent --filter=partition nbd \
-     nbd+unix:///\?socket=$sock shared=1 retry=5 partition=1 &
-   exec qemu-nbd -k $sock -f qcow2 /path/to/image.qcow2 )
-
 =head2 Convert newstyle server for oldstyle-only client
 
 Expose the contents of export C<foo> from a newstyle server with
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9ab6a7af..ea3a5b6e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -681,11 +681,13 @@ if HAVE_LIBNBD
 LIBGUESTFS_TESTS += test-nbd
 TESTS += \
 	test-nbd-extents.sh \
+	test-nbd-qcow2.sh \
 	test-nbd-tls.sh \
 	test-nbd-tls-psk.sh \
 	$(NULL)
 EXTRA_DIST += \
 	test-nbd-extents.sh \
+	test-nbd-qcow2.sh \
 	test-nbd-tls.sh \
 	test-nbd-tls-psk.sh \
 	$(NULL)
diff --git a/plugins/nbd/nbd.c b/plugins/nbd/nbd.c
index fc2d7d0c..3b7a5eef 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -87,6 +87,12 @@ static char *sockname;
 static const char *hostname;
 static const char *port;
 
+/* Connect to a command. */
+static const char *command;
+
+/* Connect to a socket file descriptor. */
+static int socket_fd = -1;
+
 /* Name of export on remote server, default '', ignored for oldstyle */
 static const char *export;
 
@@ -141,6 +147,16 @@ nbdplug_config (const char *key, const char *value)
     port = value;
   else if (strcmp (key, "uri") == 0)
     uri = value;
+  else if (strcmp (key, "command") == 0)
+    command = value;
+  else if (strcmp (key, "socket-fd") == 0) {
+    if (nbdkit_parse_int ("socket-fd", value, &socket_fd) == -1)
+      return -1;
+    if (socket_fd < 0) {
+      nbdkit_error ("socket-fd must be >= 0");
+      return -1;
+    }
+  }
   else if (strcmp (key, "export") == 0)
     export = value;
   else if (strcmp (key, "retry") == 0) {
@@ -196,16 +212,16 @@ nbdplug_config (const char *key, const char *value)
 static int
 nbdplug_config_complete (void)
 {
-  int c = !!sockname + !!hostname + !!uri;
+  int c = !!sockname + !!hostname + !!uri + !!command + (socket_fd >= 0);
 
   /* Check the user passed exactly one connection parameter. */
   if (c > 1) {
-    nbdkit_error ("cannot mix Unix ‘socket’, TCP ‘hostname’/‘port’ "
-                  "and ‘uri’ parameters");
+    nbdkit_error ("cannot mix Unix ‘socket’, TCP ‘hostname’/‘port’, "
+                  "‘command’, ‘socket-fd’ and ‘uri’ parameters");
     return -1;
   }
   if (c == 0) {
-    nbdkit_error ("exactly one of ‘socket’, ‘hostname’ "
+    nbdkit_error ("exactly one of ‘socket’, ‘hostname’, ‘command’, ‘socket-fd’ "
                   "and ‘uri’ parameters must be specified");
     return -1;
   }
@@ -242,6 +258,12 @@ nbdplug_config_complete (void)
     if (!port)
       port = "10809";
   }
+  else if (command) {
+    shared = true;
+  }
+  else if (socket_fd >= 0) {
+    shared = true;
+  }
   else {
     abort ();         /* can't happen, if checks above were correct */
   }
@@ -279,6 +301,8 @@ nbdplug_config_complete (void)
   "socket=<SOCKNAME>      The Unix socket to connect to.\n" \
   "hostname=<HOST>        The hostname for the TCP socket to connect to.\n" \
   "port=<PORT>            TCP port or service name to use (default 10809).\n" \
+  "command=<COMMAND>      qemu-nbd command to run.\n" \
+  "socket-fd=<FD>         Socket file descriptor to connect to.\n" \
   "export=<NAME>          Export name to connect to (default \"\").\n" \
   "retry=<N>              Retry connection up to N seconds (default 0).\n" \
   "shared=<BOOL>          True to share one server connection among all clients,\n" \
@@ -492,6 +516,12 @@ nbdplug_open_handle (int readonly)
     r = nbd_connect_unix (h->nbd, sockname);
   else if (hostname)
     r = nbd_connect_tcp (h->nbd, hostname, port);
+  else if (command) {
+    char *argv[] = { "/bin/sh", "-c", (char *) command, NULL };
+    r = nbd_connect_systemd_socket_activation (h->nbd, argv);
+  }
+  else if (socket_fd >= 0)
+    r = nbd_connect_socket (h->nbd, socket_fd);
   else
     abort ();
   if (r == -1) {
diff --git a/tests/test-nbd-qcow2.sh b/tests/test-nbd-qcow2.sh
new file mode 100755
index 00000000..630ba2e5
--- /dev/null
+++ b/tests/test-nbd-qcow2.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-2020 Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+source ./functions.sh
+set -e
+set -x
+
+requires test -f disk
+requires guestfish --version
+requires qemu-img --version
+requires qemu-nbd --version
+
+disk=nbd-qcow2-disk.qcow2
+pid=nbd-qcow2.pid
+sock=`mktemp -u`
+files="$disk $pid $sock"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Create a qcow2 disk for testing.
+qemu-img convert -f raw disk -O qcow2 $disk
+
+# Run qemu-nbd via nbdkit with the partition filter.
+start_nbdkit -P $pid -U $sock \
+       --filter=partition \
+       nbd command="qemu-nbd -f qcow2 $disk" \
+       partition=1
+
+qemu-img info "nbd:unix:$sock"
+
+# See if we can open the disk which should be unpartitioned.
+guestfish -v -x --format=raw -a "nbd://?socket=$sock" -m /dev/sda <<EOF
+  cat /hello.txt
+
+  # Write something.
+  write /test.txt "hello"
+  cat /test.txt
+EOF
-- 
2.25.0




More information about the Libguestfs mailing list