This allows you to pass in a file descriptor to the plugin, eg:
$ exec 6<>/var/tmp/fedora-36.img
$ ./nbdkit file fd=6 --run 'nbdinfo $uri'
Note this was previously possible on most platforms using /dev/fd/<N>,
but not all platforms that have file descriptors support this
(although it is POSIX) and it seems better to make this explicit.
---
plugins/file/nbdkit-file-plugin.pod | 22 +++++-
tests/Makefile.am | 2 +
plugins/file/file.c | 108 +++++++++++++++++++---------
tests/test-file-fd.sh | 65 +++++++++++++++++
4 files changed, 160 insertions(+), 37 deletions(-)
diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod
index 49c330663..dadbe9432 100644
--- a/plugins/file/nbdkit-file-plugin.pod
+++ b/plugins/file/nbdkit-file-plugin.pod
@@ -11,6 +11,10 @@ nbdkit-file-plugin - nbdkit file plugin
nbdkit file dir=DIRECTORY
+=for paragraph
+
+ nbdkit file fd=FILE_DESCRIPTOR
+
=head1 DESCRIPTION
C<nbdkit-file-plugin> is a file serving plugin for L<nbdkit(1)>.
@@ -22,10 +26,16 @@ If you use the C<dir> parameter the plugin works in a different
mode
where it serves files from the given C<DIRECTORY>, chosen by the
client using the NBD export name.
+If you use the C<fd> parameter then you can pass the file descriptor
+of a single disk to the plugin, inherited from the parent process.
+This can be useful where special permissions / capabilities are needed
+to open the file or you want to run nbdkit in a sandboxed environment.
+
=head1 PARAMETERS
-Either B<file> or B<dir> must be given which controls the mode of the
-plugin, either serving a single file or the files in a directory.
+B<file>, B<dir> or B<fd> must be given. This controls the mode of the
+plugin, either serving a single file, the files in a directory, or a
+single file descriptor.
=over 4
@@ -72,6 +82,14 @@ read-ahead. See also L<posix_fadvise(2)>.
The default is C<normal>.
+=item B<fd=>FILE_DESCRIPTOR
+
+(nbdkit E<ge> 1.34, not Windows)
+
+The parameter is the number of a file descriptor. Serve the file or
+device already open on this file descriptor. The file descriptor is
+usually inherited from the parent process.
+
=item [B<file=>]FILENAME
Serve the file named C<FILENAME>. A local block device name can also
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 3dc428540..3667a1dea 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -732,10 +732,12 @@ EXTRA_DIST += \
TESTS += \
test-file.sh \
test-file-readonly.sh \
+ test-file-fd.sh \
$(NULL)
EXTRA_DIST += \
test-file.sh \
test-file-readonly.sh \
+ test-file-fd.sh \
$(NULL)
LIBGUESTFS_TESTS += test-file-block
diff --git a/plugins/file/file.c b/plugins/file/file.c
index f673ec132..4e41e738a 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -70,9 +70,15 @@
#include "isaligned.h"
#include "fdatasync.h"
-static enum { mode_none, mode_filename, mode_directory } mode = mode_none;
+static enum {
+ mode_none,
+ mode_filename,
+ mode_directory,
+ mode_filedesc,
+} mode = mode_none;
static char *filename = NULL;
static char *directory = NULL;
+static int filedesc = -1;
/* posix_fadvise mode: -1 = don't set it, or POSIX_FADV_*. */
static int fadvise_mode =
@@ -184,7 +190,7 @@ file_config (const char *key, const char *value)
if (mode != mode_none) {
wrong_mode:
nbdkit_error ("%s parameter can only appear once on the command line",
- "file|dir");
+ "file|dir|fd");
return -1;
}
mode = mode_filename;
@@ -202,6 +208,17 @@ file_config (const char *key, const char *value)
if (!directory)
return -1;
}
+ else if (strcmp (key, "fd") == 0) {
+ if (mode != mode_none) goto wrong_mode;
+ mode = mode_filedesc;
+ assert (filedesc == -1);
+ if (nbdkit_parse_int ("fd", value, &filedesc) == -1)
+ return -1;
+ if (filedesc <= 2) {
+ nbdkit_error ("file descriptor must be >= 3");
+ return -1;
+ }
+ }
else if (strcmp (key, "fadvise") == 0) {
/* As this is a hint, if the kernel doesn't support the feature
* ignore the parameter.
@@ -263,18 +280,26 @@ file_config_complete (void)
struct stat sb;
if (mode == mode_none) {
- nbdkit_error ("you must supply either [file=]<FILENAME> or "
- "dir=<DIRNAME> parameter after the plugin name "
+ nbdkit_error ("you must supply [file=]<FILENAME>, "
+ "dir=<DIRNAME> or fd=<FD> "
+ "parameter after the plugin name "
"on the command line");
return -1;
}
if (mode == mode_filename) {
assert (filename != NULL);
assert (directory == NULL);
+ assert (filedesc == -1);
}
if (mode == mode_directory) {
assert (filename == NULL);
assert (directory != NULL);
+ assert (filedesc == -1);
+ }
+ if (mode == mode_filedesc) {
+ assert (filename == NULL);
+ assert (directory == NULL);
+ assert (filedesc >= 3);
}
/* Sanity check now, rather than waiting for first client open.
@@ -299,6 +324,8 @@ file_config_complete (void)
return -1;
}
break;
+ case mode_filedesc:
+ break;
default: abort ();
}
@@ -341,6 +368,7 @@ file_list_exports (int readonly, int default_only,
switch (mode) {
case mode_filename:
+ case mode_filedesc:
return nbdkit_add_export (exports, "", NULL);
case mode_directory:
@@ -408,59 +436,69 @@ file_open (int readonly)
const char *file;
int dfd = -1;
+ h = malloc (sizeof *h);
+ if (h == NULL) {
+ nbdkit_error ("malloc: %m");
+ return NULL;
+ }
+ h->can_write = !readonly;
+ h->fd = -1;
+
switch (mode) {
case mode_directory:
file = nbdkit_export_name ();
if (strchr (file, '/')) {
nbdkit_error ("exportname cannot contain /");
+ free (h);
errno = EINVAL;
return NULL;
}
dfd = open (directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dfd == -1) {
nbdkit_error ("open %s: %m", directory);
+ free (h);
return NULL;
}
break;
case mode_filename:
file = filename;
break;
+ case mode_filedesc:
+ h->fd = dup (filedesc);
+ if (h->fd == -1) {
+ nbdkit_error ("dup fd=%d: %m", filedesc);
+ free (h);
+ return NULL;
+ }
+ file = "<file descriptor>"; /* This is needed for error messages. */
+ break;
default: abort ();
}
- h = malloc (sizeof *h);
- if (h == NULL) {
- nbdkit_error ("malloc: %m");
- if (dfd != -1)
- close (dfd);
- return NULL;
- }
-
- flags = O_CLOEXEC|O_NOCTTY;
- if (readonly) {
- flags |= O_RDONLY;
- h->can_write = false;
- }
- else {
- flags |= O_RDWR;
- h->can_write = true;
- }
-
- h->fd = openat (dfd, file, flags);
- if (h->fd == -1 && !readonly) {
- nbdkit_debug ("open O_RDWR failed, falling back to read-only: %s: %m",
- file);
- flags = (flags & ~O_ACCMODE) | O_RDONLY;
- h->fd = openat (dfd, file, flags);
- h->can_write = false;
- }
if (h->fd == -1) {
- nbdkit_error ("open: %s: %m", file);
- if (dfd != -1)
- close (dfd);
- free (h);
- return NULL;
+ flags = O_CLOEXEC|O_NOCTTY;
+ if (readonly)
+ flags |= O_RDONLY;
+ else
+ flags |= O_RDWR;
+
+ h->fd = openat (dfd, file, flags);
+ if (h->fd == -1 && !readonly) {
+ nbdkit_debug ("open O_RDWR failed, falling back to read-only: %s: %m",
+ file);
+ flags = (flags & ~O_ACCMODE) | O_RDONLY;
+ h->fd = openat (dfd, file, flags);
+ h->can_write = false;
+ }
+ if (h->fd == -1) {
+ nbdkit_error ("open: %s: %m", file);
+ if (dfd != -1)
+ close (dfd);
+ free (h);
+ return NULL;
+ }
}
+
if (dfd != -1)
close (dfd);
diff --git a/tests/test-file-fd.sh b/tests/test-file-fd.sh
new file mode 100755
index 000000000..6c98ad9e5
--- /dev/null
+++ b/tests/test-file-fd.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2013-2022 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.
+
+# Test the file plugin fd parameter.
+
+source ./functions.sh
+set -e
+set -x
+
+requires_plugin file
+requires_nbdsh_uri
+requires $TRUNCATE --version
+
+sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX)
+files="file-fd.pid file-fd.img $sock"
+rm -f $files
+cleanup_fn rm -f $files
+
+$TRUNCATE -s 16384 file-fd.img
+exec 6<>file-fd.img
+start_nbdkit -P file-fd.pid -U $sock file fd=6
+
+nbdsh -u "nbd+unix://?socket=$sock" -c '
+assert not h.is_read_only()
+assert h.get_size() == 16384
+
+buf0 = bytearray(1024)
+buf1 = b"1" * 1024
+buf2 = b"2" * 1024
+h.pwrite(buf1 + buf2 + buf1 + buf2, 1024)
+buf = h.pread(8192, 0)
+assert buf == buf0 + buf1 + buf2 + buf1 + buf2 + buf0*3
+h.zero(4096, 1024)
+buf = h.pread(8192, 0)
+assert buf == buf0*8
+'
--
2.37.0.rc2