* command=qemu-nbd arg=-f arg=qcow2 arg=/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 | 73 ++++++++++++++++++++++++-------
tests/Makefile.am | 2 +
plugins/nbd/nbd.c | 51 +++++++++++++++++++--
tests/test-nbd-qcow2.sh | 67 ++++++++++++++++++++++++++++
4 files changed, 172 insertions(+), 21 deletions(-)
diff --git a/plugins/nbd/nbdkit-nbd-plugin.pod b/plugins/nbd/nbdkit-nbd-plugin.pod
index 5653703f..59cb8f32 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 { command=COMMAND [arg=ARG [...]] |
+ 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,32 @@ 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
+
+=item B<arg=>ARG
+
+Run an NBD server, usually L<qemu-nbd(8)>, as an external command.
+See L</EXAMPLES> below.
+
+C<COMMAND> is the program to run, followed by zero or more C<arg=ARG>
+parameters for each argument. For example:
+
+ nbdkit nbd command=qemu-nbd arg=-f arg=qcow2 arg=$PWD/disk.qcow2
+
+would run the command C<qemu-nbd -f qcow2 $PWD/disk.qcow2>. Because
+nbdkit may change directory before running the command, you may need
+to ensure that all file paths used in parameters (like the disk name
+above) are absolute paths.
+
+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 +79,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 +114,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 +194,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 arg=-f arg=qcow2 arg=/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 arg=-f arg=qcow2 arg=/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 91ed91e0..e446d52b 100644
--- a/plugins/nbd/nbd.c
+++ b/plugins/nbd/nbd.c
@@ -57,6 +57,9 @@
#include "byte-swapping.h"
#include "cleanup.h"
#include "utils.h"
+#include "vector.h"
+
+DEFINE_VECTOR_TYPE(string_vector, const char *);
/* The per-transaction details */
struct transaction {
@@ -86,6 +89,12 @@ static char *sockname;
static const char *hostname;
static const char *port;
+/* Connect to a command. */
+static string_vector command = empty_vector;
+
+/* 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;
@@ -114,6 +123,7 @@ nbdplug_unload (void)
free (sockname);
free (tls_certificates);
free (tls_psk);
+ free (command.ptr); /* the strings are statically allocated */
}
/* Called for each key=value passed on the command line. This plugin
@@ -140,6 +150,20 @@ nbdplug_config (const char *key, const char *value)
port = value;
else if (strcmp (key, "uri") == 0)
uri = value;
+ else if (strcmp (key, "command") == 0 || strcmp (key, "arg") == 0)
{
+ if (string_vector_append (&command, value) == -1) {
+ nbdkit_error ("realloc: %m");
+ return -1;
+ }
+ }
+ 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) {
@@ -195,16 +219,17 @@ 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.size > 0) + (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;
}
@@ -241,6 +266,17 @@ nbdplug_config_complete (void)
if (!port)
port = "10809";
}
+ else if (command.size > 0) {
+ /* Add NULL sentinel to the command. */
+ if (string_vector_append (&command, NULL) == -1) {
+ nbdkit_error ("realloc: %m");
+ return -1;
+ }
+ shared = true;
+ }
+ else if (socket_fd >= 0) {
+ shared = true;
+ }
else {
abort (); /* can't happen, if checks above were correct */
}
@@ -285,6 +321,9 @@ nbdplug_after_fork (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> Command to run.\n" \
+ "arg=<ARG> Parameters for command.\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" \
@@ -506,6 +545,10 @@ 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.size > 0)
+ r = nbd_connect_systemd_socket_activation (h->nbd, (char **) command.ptr);
+ 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..72aac544
--- /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 arg=-f arg=qcow2 arg="$PWD/$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