[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