This can be used for creating temporary disks to thin clients, as a
kind of "remote tmpfs".
See also:
https://www.redhat.com/archives/libguestfs/2020-March/msg00134.html
---
plugins/data/nbdkit-data-plugin.pod | 1 +
plugins/file/nbdkit-file-plugin.pod | 1 +
plugins/linuxdisk/nbdkit-linuxdisk-plugin.pod | 7 +-
plugins/memory/nbdkit-memory-plugin.pod | 3 +-
plugins/tmpdisk/nbdkit-tmpdisk-plugin.pod | 157 ++++++
configure.ac | 2 +
plugins/tmpdisk/Makefile.am | 66 +++
tests/Makefile.am | 21 +
plugins/tmpdisk/tmpdisk.c | 451 ++++++++++++++++++
tests/test-tmpdisk.c | 157 ++++++
.gitignore | 1 +
11 files changed, 864 insertions(+), 3 deletions(-)
diff --git a/plugins/data/nbdkit-data-plugin.pod b/plugins/data/nbdkit-data-plugin.pod
index 057392d3..ef8d1e08 100644
--- a/plugins/data/nbdkit-data-plugin.pod
+++ b/plugins/data/nbdkit-data-plugin.pod
@@ -269,6 +269,7 @@ L<nbdkit-null-plugin(1)>,
L<nbdkit-partitioning-plugin(1)>,
L<nbdkit-pattern-plugin(1)>,
L<nbdkit-random-plugin(1)>,
+L<nbdkit-tmpdisk-plugin(1)>,
L<nbdkit-zero-plugin(1)>,
L<https://github.com/libguestfs/nbdkit/blob/master/plugins/data/disk2d...;,
L<https://en.wikipedia.org/wiki/Base64>.
diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod
index d538b127..0c1cfd57 100644
--- a/plugins/file/nbdkit-file-plugin.pod
+++ b/plugins/file/nbdkit-file-plugin.pod
@@ -111,6 +111,7 @@ L<nbdkit(1)>,
L<nbdkit-plugin(3)>,
L<nbdkit-split-plugin(1)>,
L<nbdkit-partitioning-plugin(1)>,
+L<nbdkit-tmpdisk-plugin(1)>,
L<nbdkit-noextents-filter(1)>.
=head1 AUTHORS
diff --git a/plugins/linuxdisk/nbdkit-linuxdisk-plugin.pod
b/plugins/linuxdisk/nbdkit-linuxdisk-plugin.pod
index 3cac883c..53f4d89d 100644
--- a/plugins/linuxdisk/nbdkit-linuxdisk-plugin.pod
+++ b/plugins/linuxdisk/nbdkit-linuxdisk-plugin.pod
@@ -24,7 +24,9 @@ symbolic links, block special devices etc.
To create a FAT-formatted virtual floppy disk, see
L<nbdkit-floppy-plugin(1)>. To create a CD/ISO, see
-L<nbdkit-iso-plugin(1)>.
+L<nbdkit-iso-plugin(1)>. To create an empty filesystem for each
+client that connects (like a "remote tmpfs") use
+L<nbdkit-tmpdisk-plugin(1)>.
=head1 EXAMPLES
@@ -184,7 +186,8 @@ L<nbdkit-file-plugin(1)>,
L<nbdkit-floppy-plugin(1)>,
L<nbdkit-iso-plugin(1)>,
L<nbdkit-partition-filter(1)>,
-L<nbdkit-partitioning-plugin(1)>.
+L<nbdkit-partitioning-plugin(1)>,
+L<nbdkit-tmpdisk-plugin(1)>,
=head1 AUTHORS
diff --git a/plugins/memory/nbdkit-memory-plugin.pod
b/plugins/memory/nbdkit-memory-plugin.pod
index ccdc017d..bc565c55 100644
--- a/plugins/memory/nbdkit-memory-plugin.pod
+++ b/plugins/memory/nbdkit-memory-plugin.pod
@@ -103,7 +103,8 @@ L<nbdkit-plugin(3)>,
L<nbdkit-loop(1)>,
L<nbdkit-data-plugin(1)>,
L<nbdkit-file-plugin(1)>,
-L<nbdkit-info-plugin(1)>.
+L<nbdkit-info-plugin(1)>,
+L<nbdkit-tmpdisk-plugin(1)>.
=head1 AUTHORS
diff --git a/plugins/tmpdisk/nbdkit-tmpdisk-plugin.pod
b/plugins/tmpdisk/nbdkit-tmpdisk-plugin.pod
new file mode 100644
index 00000000..925a5091
--- /dev/null
+++ b/plugins/tmpdisk/nbdkit-tmpdisk-plugin.pod
@@ -0,0 +1,157 @@
+=head1 NAME
+
+nbdkit-tmpdisk-plugin - create a fresh temporary filesystem for each client
+
+=head1 SYNOPSIS
+
+ nbdkit tmpdisk [size=]SIZE
+ [type=ext4|xfs|vfat|...] [label=LABEL]
+ [command=COMMAND]
+
+=head1 DESCRIPTION
+
+This L<nbdkit(1)> plugin is used for creating temporary filesystems
+for thin clients. Each time a client connects it will see a fresh,
+empty filesystem for its exclusive use. B<The filesystem is deleted>
+when the client disconnects.
+
+When a new client connects, a blank, sparse file of the required size
+is created in C<$TMPDIR> (or F</var/tmp>). L<mkfs(8)> is then run on
+the file to create the empty filesystem, and this filesystem is served
+to the client. Unlike L<nbdkit-linuxdisk-plugin(1)> each client of
+this plugin sees a different disk.
+
+The size of the disk is chosen using the C<size> parameter. The
+filesystem type is C<ext4> but this can be changed using the C<type>
+parameter (controlling the I<-t> option of mkfs).
+
+Instead of running mkfs you can run an arbitrary C<command> to create
+the disk.
+
+=head2 Example
+
+One use for this is to create a kind of "remote L<tmpfs(5)>" for thin
+clients. On the server you would run (see L<nbdkit-service(1)>):
+
+ nbdkit tmpdisk 16G
+
+and each thin client would have a file F</etc/rc.d/rc.local>
+containing:
+
+ nm-online
+ modprobe nbd
+ nbd-client server /dev/nbd0
+ mount /dev/nbd0 /var/scratch
+
+Clients would see a fresh, empty C</var/scratch> directory after boot.
+
+=head2 Security considerations
+
+Because each client gets a new disk, the amount of disk space
+required on the server can be as much as
+S<<< I<number of clients> × I<size parameter> >>>. It is
therefore
+best to limit the number of clients using L<nbdkit-limit-filter(1)> or
+take steps to limit where clients can connect from using
+L<nbdkit-ip-filter(1)>, firewalls, or TLS client certificates.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<command='>COMMANDB<'>
+
+Instead of running L<mkfs(8)> to create the initial filesystem, run
+C<COMMAND> (which usually must be quoted to protect it from the
+shell). The following shell variables may be used in C<COMMAND>:
+
+=over 4
+
+=item C<$disk>
+
+The absolute path of the file that the command must initialize. This
+file is created by nbdkit before the command runs.
+
+=item C<$label>
+
+The filesystem label (from the C<label> parameter).
+
+=item C<$size>
+
+The virtual size in bytes, which is the same as the file size.
+
+=item C<$type>
+
+The filesystem type (from the C<type> parameter), defaulting to
+C<ext4>. (Commands can ignore this if it is not relevant).
+
+=back
+
+=item B<label=>LABEL
+
+Select the filesystem label. The default is not set.
+
+=item [B<size=>]SIZE
+
+Specify the virtual size of the disk image.
+
+This parameter is required.
+
+C<size=> is a magic config key and may be omitted in most cases.
+See L<nbdkit(1)/Magic parameters>.
+
+=item B<type=>FS
+
+Select the filesystem type. The default is C<ext4>. Most
+non-networked, non-cluster filesystem types supported by the
+L<mkfs(8)> command can be used here.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item C<TMPDIR>
+
+The temporary disks for this plugin are created in this directory, one
+per connected client. If not set this defaults to F</var/tmp>.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$plugindir/nbdkit-tmpdisk-plugin.so>
+
+The plugin.
+
+Use C<nbdkit --dump-config> to find the location of C<$plugindir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-tmpdisk-plugin> first appeared in nbdkit 1.20.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(3)>,
+L<nbdkit-data-plugin(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-ip-filter(1)>,
+L<nbdkit-limit-filter(1)>,
+L<nbdkit-linuxdisk-plugin(1)>,
+L<nbdkit-memory-plugin(1)>,
+L<nbdkit-loop(1)>,
+L<nbdkit-tls(1)>,
+L<mkfs(8)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018-2020 Red Hat Inc.
diff --git a/configure.ac b/configure.ac
index dd9ca765..7b5e61e3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,6 +86,7 @@ non_lang_plugins="\
ssh \
streaming \
tar \
+ tmpdisk \
vddk \
zero \
"
@@ -1016,6 +1017,7 @@ AC_CONFIG_FILES([Makefile
plugins/streaming/Makefile
plugins/tar/Makefile
plugins/tcl/Makefile
+ plugins/tmpdisk/Makefile
plugins/vddk/Makefile
plugins/zero/Makefile
filters/Makefile
diff --git a/plugins/tmpdisk/Makefile.am b/plugins/tmpdisk/Makefile.am
new file mode 100644
index 00000000..2e487e92
--- /dev/null
+++ b/plugins/tmpdisk/Makefile.am
@@ -0,0 +1,66 @@
+# nbdkit
+# Copyright (C) 2017-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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-tmpdisk-plugin.pod
+
+plugin_LTLIBRARIES = nbdkit-tmpdisk-plugin.la
+
+nbdkit_tmpdisk_plugin_la_SOURCES = \
+ tmpdisk.c \
+ $(top_srcdir)/include/nbdkit-plugin.h \
+ $(NULL)
+
+nbdkit_tmpdisk_plugin_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/utils \
+ $(NULL)
+nbdkit_tmpdisk_plugin_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_tmpdisk_plugin_la_LDFLAGS = \
+ -module -avoid-version -shared \
+ -Wl,--version-script=$(top_srcdir)/plugins/plugins.syms \
+ $(NULL)
+nbdkit_tmpdisk_plugin_la_LIBADD = \
+ $(top_builddir)/common/utils/libutils.la \
+ $(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-tmpdisk-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-tmpdisk-plugin.1: nbdkit-tmpdisk-plugin.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 65dd148d..17f2c8da 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -766,6 +766,27 @@ test_streaming_SOURCES = test-streaming.c
test_streaming_CFLAGS = $(WARNINGS_CFLAGS) $(LIBNBD_CFLAGS)
test_streaming_LDADD = $(LIBNBD_LIBS)
+# tmpdisk plugin test.
+LIBGUESTFS_TESTS += test-tmpdisk
+
+test_tmpdisk_SOURCES = \
+ test-tmpdisk.c \
+ test.h \
+ $(NULL)
+test_tmpdisk_CPPFLAGS = \
+ -I$(top_srcdir)/common/utils
+test_tmpdisk_CFLAGS = \
+ $(WARNINGS_CFLAGS) \
+ $(LIBGUESTFS_CFLAGS) \
+ $(NULL)
+test_tmpdisk_LDFLAGS = \
+ $(top_builddir)/common/utils/libutils.la \
+ $(NULL)
+test_tmpdisk_LDADD = \
+ libtest.la \
+ $(LIBGUESTFS_LIBS) \
+ $(NULL)
+
if HAVE_VDDK
# VDDK plugin test.
# This only tests that the plugin can be loaded against a
diff --git a/plugins/tmpdisk/tmpdisk.c b/plugins/tmpdisk/tmpdisk.c
new file mode 100644
index 00000000..a5aacc9d
--- /dev/null
+++ b/plugins/tmpdisk/tmpdisk.c
@@ -0,0 +1,451 @@
+/* nbdkit
+ * Copyright (C) 2017-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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#define NBDKIT_API_VERSION 2
+#include <nbdkit-plugin.h>
+
+#include "cleanup.h"
+#include "utils.h"
+
+static const char *tmpdir = "/var/tmp";
+static int64_t size = -1;
+static const char *label = NULL;
+static const char *type = "ext4";
+
+static const char *command =
+ "labelopt='-L'\n"
+ "case \"$type\" in\n"
+ " ext?)\n"
+ " extra='-F' ;;\n"
+ " *fat|msdos)\n"
+ " extra='-I' ;;\n"
+ " ntfs)\n"
+ " extra='-Q -F'\n"
+ " labelopt='-n' ;;\n"
+ " xfs)\n"
+ " extra='-f' ;;\n"
+ "esac\n"
+ "if [ \"x$label\" = \"x\" ]; then\n"
+ " mkfs -t \"$type\" $extra \"$disk\"\n"
+ "else\n"
+ " mkfs -t \"$type\" $extra $labelopt \"$label\"
\"$disk\"\n"
+ "fi\n";
+
+static void
+tmpdisk_load (void)
+{
+ const char *s;
+
+ s = getenv ("TMPDIR");
+ if (s)
+ tmpdir = s;
+}
+
+static int
+tmpdisk_config (const char *key, const char *value)
+{
+ if (strcmp (key, "command") == 0) {
+ command = value;
+ }
+ else if (strcmp (key, "label") == 0) {
+ if (strcmp (value, "") == 0)
+ label = NULL;
+ else
+ label = value;
+ }
+ else if (strcmp (key, "size") == 0) {
+ size = nbdkit_parse_size (value);
+ if (size == -1)
+ return -1;
+ }
+ else if (strcmp (key, "type") == 0) {
+ type = value;
+ }
+ else {
+ nbdkit_error ("unknown parameter '%s'", key);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+tmpdisk_config_complete (void)
+{
+ if (size == -1) {
+ nbdkit_error ("size parameter is required");
+ return -1;
+ }
+
+ return 0;
+}
+
+#define tmpdisk_config_help \
+ "size=<SIZE> (required) Virtual filesystem size.\n" \
+ "label=<LABEL> The filesystem label.\n" \
+ "type=ext4|... The filesystem type.\n" \
+ "command=<COMMAND> Alternate command instead of mkfs."
+
+struct handle {
+ int fd;
+ bool can_punch_hole;
+};
+
+/* Multi-conn is absolutely unsafe! In this callback it is simply
+ * returning the default value (no multi-conn), that's to make it
+ * clear for future authors.
+ */
+static int
+tmpdisk_can_multi_conn (void *handle)
+{
+ return 0;
+}
+
+static int
+tmpdisk_can_trim (void *handle)
+{
+#ifdef FALLOC_FL_PUNCH_HOLE
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+/* Pretend we have native FUA support, but actually because all disks
+ * are temporary we will deliberately ignore flush/FUA operations.
+ */
+static int
+tmpdisk_can_fua (void *handle)
+{
+ return NBDKIT_FUA_NATIVE;
+}
+
+static int64_t
+tmpdisk_get_size (void *handle)
+{
+ return size;
+}
+
+/* This creates and runs the full "mkfs" (or whatever) command. */
+static int
+run_command (const char *disk)
+{
+ FILE *fp;
+ CLEANUP_FREE char *cmd = NULL;
+ size_t len = 0;
+ int r;
+
+ fp = open_memstream (&cmd, &len);
+ if (fp == NULL) {
+ nbdkit_error ("open_memstream: %m");
+ return -1;
+ }
+
+ /* Avoid stdin/stdout leaking (because of nbdkit -s). */
+ fprintf (fp, "exec </dev/null >/dev/null\n");
+
+ /* Set the shell variables. */
+ fprintf (fp, "disk=");
+ shell_quote (disk, fp);
+ putc ('\n', fp);
+ if (label) {
+ fprintf (fp, "label=");
+ shell_quote (label, fp);
+ putc ('\n', fp);
+ }
+ fprintf (fp, "size=%" PRIi64 "\n", size);
+ fprintf (fp, "type=");
+ shell_quote (type, fp);
+ putc ('\n', fp);
+
+ putc ('\n', fp);
+ fprintf (fp, "%s", command);
+
+ if (fclose (fp) == EOF) {
+ nbdkit_error ("memstream failed");
+ return -1;
+ }
+
+ r = system (cmd);
+ if (r == -1) {
+ nbdkit_error ("failed to execute command: %m");
+ return -1;
+ }
+ if (WIFEXITED (r) && WEXITSTATUS (r) != 0) {
+ nbdkit_error ("command exited with code %d", WEXITSTATUS (r));
+ return -1;
+ }
+ else if (WIFSIGNALED (r)) {
+ nbdkit_error ("command killed by signal %d", WTERMSIG (r));
+ return -1;
+ }
+ else if (WIFSTOPPED (r)) {
+ nbdkit_error ("command stopped by signal %d", WSTOPSIG (r));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void *
+tmpdisk_open (int readonly)
+{
+ struct handle *h;
+ CLEANUP_FREE char *disk = NULL;
+
+ h = malloc (sizeof *h);
+ if (h == NULL) {
+ nbdkit_error ("malloc: %m");
+ goto error;
+ }
+ h->fd = -1;
+ h->can_punch_hole = true;
+
+ /* Create the new disk image for this connection. */
+ if (asprintf (&disk, "%s/tmpdiskXXXXXX", tmpdir) == -1) {
+ nbdkit_error ("asprintf: %m");
+ goto error;
+ }
+
+#ifdef HAVE_MKOSTEMP
+ h->fd = mkostemp (disk, O_CLOEXEC);
+#else
+ /* Racy, fix your libc. */
+ h->fd = mkstemp (disk);
+ if (h->fd >= 0) {
+ h->fd = set_cloexec (h->fd);
+ if (h->fd == -1) {
+ int e = errno;
+ unlink (disk);
+ errno = e;
+ }
+ }
+#endif
+ if (h->fd == -1) {
+ nbdkit_error ("mkstemp: %m");
+ goto error;
+ }
+
+ /* Truncate the disk to a sparse file of the right size. */
+ if (ftruncate (h->fd, size) == -1) {
+ nbdkit_error ("ftruncate: %s: %m", disk);
+ goto error;
+ }
+
+ /* Now run the mkfs command. */
+ if (run_command (disk) == -1)
+ goto error;
+
+ /* We don't need the disk to appear in the filesystem since we hold
+ * a file descriptor and access it through that, so unlink the disk.
+ * This also ensures it is always cleaned up.
+ */
+ unlink (disk);
+
+ /* Return the handle. */
+ return h;
+
+ error:
+ if (h) {
+ if (h->fd >= 0) {
+ close (h->fd);
+ unlink (disk);
+ }
+ free (h);
+ }
+ return NULL;
+}
+
+static void
+tmpdisk_close (void *handle)
+{
+ struct handle *h = handle;
+
+ close (h->fd);
+ free (h);
+}
+
+/* Read data from the file. */
+static int
+tmpdisk_pread (void *handle, void *buf,
+ uint32_t count, uint64_t offset,
+ uint32_t flags)
+{
+ struct handle *h = handle;
+
+ while (count > 0) {
+ ssize_t r = pread (h->fd, buf, count, offset);
+ if (r == -1) {
+ nbdkit_error ("pread: %m");
+ return -1;
+ }
+ if (r == 0) {
+ nbdkit_error ("pread: unexpected end of file");
+ return -1;
+ }
+ buf += r;
+ count -= r;
+ offset += r;
+ }
+
+ return 0;
+}
+
+/* Write data to the file. */
+static int
+tmpdisk_pwrite (void *handle, const void *buf,
+ uint32_t count, uint64_t offset,
+ uint32_t flags)
+{
+ struct handle *h = handle;
+
+ while (count > 0) {
+ ssize_t r = pwrite (h->fd, buf, count, offset);
+ if (r == -1) {
+ nbdkit_error ("pwrite: %m");
+ return -1;
+ }
+ buf += r;
+ count -= r;
+ offset += r;
+ }
+
+ /* Deliberately ignore FUA if present in flags. */
+
+ return 0;
+}
+
+/* This plugin deliberately provides a null flush operation, because
+ * all of the disks created are temporary.
+ */
+static int
+tmpdisk_flush (void *handle, uint32_t flags)
+{
+ return 0;
+}
+
+#if defined (FALLOC_FL_PUNCH_HOLE)
+static int
+do_fallocate (int fd, int mode, off_t offset, off_t len)
+{
+ int r = fallocate (fd, mode, offset, len);
+ if (r == -1 && errno == ENODEV) {
+ /* kernel 3.10 fails with ENODEV for block device. Kernel >= 4.9 fails
+ * with EOPNOTSUPP in this case. Normalize errno to simplify callers.
+ */
+ errno = EOPNOTSUPP;
+ }
+ return r;
+}
+
+static bool
+is_enotsup (int err)
+{
+ return err == ENOTSUP || err == EOPNOTSUPP;
+}
+#endif
+
+/* Punch a hole in the file. */
+static int
+tmpdisk_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
+{
+#ifdef FALLOC_FL_PUNCH_HOLE
+ struct handle *h = handle;
+ int r;
+
+ if (h->can_punch_hole) {
+ r = do_fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
+ offset, count);
+ if (r == -1) {
+ /* Trim is advisory; we don't care if it fails for anything other
+ * than EIO or EPERM.
+ */
+ if (errno == EPERM || errno == EIO) {
+ nbdkit_error ("fallocate: %m");
+ return -1;
+ }
+
+ if (is_enotsup (EOPNOTSUPP))
+ h->can_punch_hole = false;
+
+ nbdkit_debug ("ignoring failed fallocate during trim: %m");
+ }
+ }
+#endif
+
+ /* Deliberately ignore FUA if present in flags. */
+
+ return 0;
+}
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+static struct nbdkit_plugin plugin = {
+ .name = "tmpdisk",
+ .version = PACKAGE_VERSION,
+
+ .load = tmpdisk_load,
+ .config = tmpdisk_config,
+ .config_complete = tmpdisk_config_complete,
+ .config_help = tmpdisk_config_help,
+ .magic_config_key = "size",
+
+ .can_multi_conn = tmpdisk_can_multi_conn,
+ .can_trim = tmpdisk_can_trim,
+ .can_fua = tmpdisk_can_fua,
+ .get_size = tmpdisk_get_size,
+
+ .open = tmpdisk_open,
+ .close = tmpdisk_close,
+ .pread = tmpdisk_pread,
+ .pwrite = tmpdisk_pwrite,
+ .flush = tmpdisk_flush,
+ .trim = tmpdisk_trim,
+
+ .errno_is_preserved = 1,
+};
+
+NBDKIT_REGISTER_PLUGIN(plugin)
diff --git a/tests/test-tmpdisk.c b/tests/test-tmpdisk.c
new file mode 100644
index 00000000..e96f1b82
--- /dev/null
+++ b/tests/test-tmpdisk.c
@@ -0,0 +1,157 @@
+/* nbdkit
+ * Copyright (C) 2013-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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <guestfs.h>
+
+#include "test.h"
+
+int
+main (int argc, char *argv[])
+{
+ guestfs_h *g1, *g2;
+ int r;
+ char *label;
+
+ /* Start nbdkit. */
+ if (test_start_nbdkit ("tmpdisk", "1G", "label=TEST",
NULL) == -1)
+ exit (EXIT_FAILURE);
+
+ /* We can open multiple connections and they should see different
+ * disks.
+ */
+ g1 = guestfs_create ();
+ if (g1 == NULL) {
+ perror ("guestfs_create");
+ exit (EXIT_FAILURE);
+ }
+ guestfs_set_identifier (g1, "g1");
+
+ r = guestfs_add_drive_opts (g1, "",
+ GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+ GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
+ GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
+ -1);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ if (guestfs_launch (g1) == -1)
+ exit (EXIT_FAILURE);
+
+ g2 = guestfs_create ();
+ if (g2 == NULL) {
+ perror ("guestfs_create");
+ exit (EXIT_FAILURE);
+ }
+ guestfs_set_identifier (g2, "g2");
+
+ r = guestfs_add_drive_opts (g2, "",
+ GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+ GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
+ GUESTFS_ADD_DRIVE_OPTS_SERVER, server,
+ -1);
+ if (r == -1)
+ exit (EXIT_FAILURE);
+
+ if (guestfs_launch (g2) == -1)
+ exit (EXIT_FAILURE);
+
+ /* But they should both see the same filesystem label. */
+ label = guestfs_vfs_label (g1, "/dev/sda");
+ if (!label)
+ exit (EXIT_FAILURE);
+ if (strcmp (label, "TEST") != 0) {
+ fprintf (stderr, "%s FAILED: unexpected label: %s\n",
+ program_name, label);
+ exit (EXIT_FAILURE);
+ }
+ free (label);
+
+ label = guestfs_vfs_label (g2, "/dev/sda");
+ if (!label)
+ exit (EXIT_FAILURE);
+ if (strcmp (label, "TEST") != 0) {
+ fprintf (stderr, "%s FAILED: unexpected label: %s\n",
+ program_name, label);
+ exit (EXIT_FAILURE);
+ }
+ free (label);
+
+ /* Mount both disks. */
+ if (guestfs_mount (g1, "/dev/sda", "/") == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_mount (g2, "/dev/sda", "/") == -1)
+ exit (EXIT_FAILURE);
+
+ /* Create some files and directories on each. */
+ if (guestfs_mkdir (g1, "/test1") == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_touch (g1, "/test1/file1") == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_mkdir (g2, "/test2") == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_touch (g2, "/test2/file2") == -1)
+ exit (EXIT_FAILURE);
+
+ if (guestfs_sync (g1) == -1 || guestfs_sync (g2) == -1)
+ exit (EXIT_FAILURE);
+
+ if (guestfs_is_file (g1, "/test1/file1") != 1) {
+ fprintf (stderr, "%s FAILED: /test1/file1 is not a file\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ if (guestfs_is_file (g2, "/test2/file2") != 1) {
+ fprintf (stderr, "%s FAILED: /test2/file2 is not a file\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Shut down the connection. */
+ if (guestfs_shutdown (g1) == -1)
+ exit (EXIT_FAILURE);
+ if (guestfs_shutdown (g2) == -1)
+ exit (EXIT_FAILURE);
+ guestfs_close (g1);
+ guestfs_close (g2);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/.gitignore b/.gitignore
index ae4aaf3c..ae4e5061 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,6 +134,7 @@ plugins/*/*.3
/tests/test-split
/tests/test-streaming
/tests/test-tcl
+/tests/test-tmpdisk
/tests/test-xz
/tests/test-xz-curl
/test-driver
--
2.25.0