Also implements the --filters parameter.
---
docs/nbdkit.pod | 21 +-
nbdkit.in | 17 +-
src/Makefile.am | 1 +
src/filters.c | 606 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/internal.h | 23 ++-
src/main.c | 114 +++++++++--
src/plugins.c | 11 +-
7 files changed, 772 insertions(+), 21 deletions(-)
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 3b37db8..636eedc 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -7,7 +7,7 @@ nbdkit - A toolkit for creating NBD servers
=head1 SYNOPSIS
nbdkit [-e EXPORTNAME] [--exit-with-parent] [-f]
- [-g GROUP] [-i IPADDR]
+ [--filter=FILTER ...] [-g GROUP] [-i IPADDR]
[--newstyle] [--oldstyle] [-P PIDFILE] [-p PORT] [-r]
[--run CMD] [-s] [--selinux-label LABEL] [-t THREADS]
[--tls=off|on|require] [--tls-certificates /path/to/certificates]
@@ -119,6 +119,13 @@ not allowed with the oldstyle protocol.
I<Don't> fork into the background.
+=item B<--filter> FILTER
+
+Add a filter before the plugin. This option may be given one or more
+times to stack filters in front of the plugin. They are processed in
+the order they appear on the command line. See L</FILTERS> and
+L<nbdkit-filter(3)>.
+
=item B<-g> GROUP
=item B<--group> GROUP
@@ -354,6 +361,18 @@ languages. The file should be executable. For example:
(see L<nbdkit-perl-plugin(3)> for a full example).
+=head1 FILTERS
+
+One or more filters can be placed in front of an nbdkit plugin to
+modify the behaviour of the plugin, using the I<--filter> parameter.
+Filters can be used for example to limit requests to an offset/limit,
+add copy-on-write support, or inject delays or errors (for testing).
+
+Several existing filters are available in the C<$filterdir>. Use
+C<nbdkit --dump-config> to find the directory name.
+
+How to write filters is described in L<nbdkit-filter(3)>.
+
=head1 SOCKET ACTIVATION
nbdkit supports socket activation (sometimes called systemd socket
diff --git a/nbdkit.in b/nbdkit.in
index 20bc9c0..d4fe4e0 100644
--- a/nbdkit.in
+++ b/nbdkit.in
@@ -1,7 +1,7 @@
#!/bin/bash -
# @configure_input@
-# Copyright (C) 2017 Red Hat Inc.
+# Copyright (C) 2017-2018 Red Hat Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -79,6 +79,21 @@ while [ $# -gt 0 ]; do
shift
;;
+ # Filters can be rewritten if purely alphanumeric.
+ --filter)
+ args[$i]="--filter"
+ ((++i))
+ if [[ "$2" =~ ^[a-zA-Z0-9]+$ ]]; then
+ if [ -x "$b/filters/$2/.libs/nbdkit-$2-filter.so" ]; then
+ args[$i]="$b/filters/$2/.libs/nbdkit-$2-filter.so"
+ else
+ args[$i]="$2"
+ fi
+ fi
+ ((++i))
+ shift 2
+ ;;
+
# Anything else can be rewritten if it's purely alphanumeric,
# but there is only one module name so only rewrite once.
*)
diff --git a/src/Makefile.am b/src/Makefile.am
index 6033fe5..ae16fde 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -40,6 +40,7 @@ nbdkit_SOURCES = \
connections.c \
crypto.c \
errors.c \
+ filters.c \
internal.h \
locks.c \
main.c \
diff --git a/src/filters.c b/src/filters.c
new file mode 100644
index 0000000..675aedc
--- /dev/null
+++ b/src/filters.c
@@ -0,0 +1,606 @@
+/* nbdkit
+ * Copyright (C) 2013-2018 Red Hat Inc.
+ * All rights reserved.
+ *
+ * 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 <stdint.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <errno.h>
+
+#include <dlfcn.h>
+
+#include "nbdkit-filter.h"
+#include "internal.h"
+
+/* We extend the generic backend struct with extra fields relating
+ * to this filter.
+ */
+struct backend_filter {
+ struct backend backend;
+ char *filename;
+ void *dl;
+ struct nbdkit_filter filter;
+};
+
+/* Note this frees the whole chain. */
+static void
+filter_free (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ f->backend.next->free (f->backend.next);
+
+ /* Acquiring this lock prevents any filter callbacks from running
+ * simultaneously.
+ */
+ lock_unload ();
+
+ debug ("%s: unload", f->filename);
+ if (f->filter.unload)
+ f->filter.unload ();
+
+ dlclose (f->dl);
+ free (f->filename);
+
+ unlock_unload ();
+
+ free (f);
+}
+
+/* These are actually passing through to the final plugin, hence
+ * the function names.
+ */
+static int
+plugin_thread_model (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ return f->backend.next->thread_model (f->backend.next);
+}
+
+static int
+plugin_errno_is_preserved (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ return f->backend.next->errno_is_preserved (f->backend.next);
+}
+
+static const char *
+plugin_name (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ return f->backend.next->name (f->backend.next);
+}
+
+static const char *
+filter_name (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ return f->filter.name;
+}
+
+static const char *
+filter_version (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ return f->filter.version;
+}
+
+static void
+filter_usage (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ printf ("filter: %s", f->filter.name);
+ if (f->filter.longname)
+ printf (" (%s)", f->filter.longname);
+ printf ("\n");
+ printf ("(%s)", f->filename);
+ if (f->filter.description) {
+ printf ("\n");
+ printf ("%s\n", f->filter.description);
+ }
+ if (f->filter.config_help) {
+ printf ("\n");
+ printf ("%s\n", f->filter.config_help);
+ }
+}
+
+static void
+filter_dump_fields (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ f->backend.next->dump_fields (f->backend.next);
+}
+
+static int
+next_config (void *nxdata, const char *key, const char *value)
+{
+ struct backend *b = nxdata;
+ b->config (b, key, value);
+ return 0;
+}
+
+static void
+filter_config (struct backend *b, const char *key, const char *value)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ debug ("%s: config key=%s, value=%s",
+ f->filename, key, value);
+
+ if (f->filter.config) {
+ if (f->filter.config (next_config, f->backend.next, key, value) == -1)
+ exit (EXIT_FAILURE);
+ }
+ else
+ f->backend.next->config (f->backend.next, key, value);
+}
+
+static int
+next_config_complete (void *nxdata)
+{
+ struct backend *b = nxdata;
+ b->config_complete (b);
+ return 0;
+}
+
+static void
+filter_config_complete (struct backend *b)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+
+ debug ("%s: config_complete", f->filename);
+
+ if (f->filter.config_complete) {
+ if (f->filter.config_complete (next_config_complete, f->backend.next) == -1)
+ exit (EXIT_FAILURE);
+ }
+ else
+ f->backend.next->config_complete (f->backend.next);
+}
+
+static int
+filter_open (struct backend *b, struct connection *conn, int readonly)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = NULL;
+
+ debug ("%s: open readonly=%d", f->filename, readonly);
+
+ if (f->filter.open) {
+ handle = f->filter.open (readonly);
+ if (handle == NULL)
+ return -1;
+ }
+ connection_set_handle (conn, f->backend.i, handle);
+ return f->backend.next->open (f->backend.next, conn, readonly);
+}
+
+static void
+filter_close (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+
+ debug ("close");
+
+ if (f->filter.close)
+ f->filter.close (handle);
+ f->backend.next->close (f->backend.next, conn);
+}
+
+/* The next_functions structure contains pointers to backend
+ * functions. However because these functions are all expecting a
+ * backend and a connection, we cannot call them directly, but must
+ * write some next_* functions that unpack the two parameters from a
+ * single ‘void *nxdata’ struct pointer (‘b_conn’).
+ */
+
+/* Literally a backend + a connection pointer. This is the
+ * implementation if ‘void *nxdata’ in the filter API.
+ */
+struct b_conn {
+ struct backend *b;
+ struct connection *conn;
+};
+
+static int64_t
+next_get_size (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->get_size (b_conn->b, b_conn->conn);
+}
+
+static int
+next_can_write (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->can_write (b_conn->b, b_conn->conn);
+}
+
+static int
+next_can_flush (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->can_flush (b_conn->b, b_conn->conn);
+}
+
+static int
+next_is_rotational (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->is_rotational (b_conn->b, b_conn->conn);
+}
+
+static int
+next_can_trim (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->can_trim (b_conn->b, b_conn->conn);
+}
+
+static int
+next_pread (void *nxdata, void *buf, uint32_t count, uint64_t offset)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->pread (b_conn->b, b_conn->conn, buf, count, offset);
+}
+
+static int
+next_pwrite (void *nxdata, const void *buf, uint32_t count, uint64_t offset)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->pwrite (b_conn->b, b_conn->conn, buf, count, offset);
+}
+
+static int
+next_flush (void *nxdata)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->flush (b_conn->b, b_conn->conn);
+}
+
+static int
+next_trim (void *nxdata, uint32_t count, uint64_t offset)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->trim (b_conn->b, b_conn->conn, count, offset);
+}
+
+static int
+next_zero (void *nxdata, uint32_t count, uint64_t offset, int may_trim)
+{
+ struct b_conn *b_conn = nxdata;
+ return b_conn->b->zero (b_conn->b, b_conn->conn, count, offset, may_trim);
+}
+
+static struct nbdkit_next next_functions = {
+ .get_size = next_get_size,
+ .can_write = next_can_write,
+ .can_flush = next_can_flush,
+ .is_rotational = next_is_rotational,
+ .can_trim = next_can_trim,
+ .pread = next_pread,
+ .pwrite = next_pwrite,
+ .flush = next_flush,
+ .trim = next_trim,
+ .zero = next_zero,
+};
+
+static int64_t
+filter_get_size (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("get_size");
+
+ if (f->filter.get_size)
+ return f->filter.get_size (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->get_size (f->backend.next, conn);
+}
+
+static int
+filter_can_write (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("can_write");
+
+ if (f->filter.can_write)
+ return f->filter.can_write (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->can_write (f->backend.next, conn);
+}
+
+static int
+filter_can_flush (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("can_flush");
+
+ if (f->filter.can_flush)
+ return f->filter.can_flush (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->can_flush (f->backend.next, conn);
+}
+
+static int
+filter_is_rotational (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("is_rotational");
+
+ if (f->filter.is_rotational)
+ return f->filter.is_rotational (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->is_rotational (f->backend.next, conn);
+}
+
+static int
+filter_can_trim (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("can_trim");
+
+ if (f->filter.can_trim)
+ return f->filter.can_trim (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->can_trim (f->backend.next, conn);
+}
+
+static int
+filter_pread (struct backend *b, struct connection *conn,
+ void *buf, uint32_t count, uint64_t offset)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("pread count=%" PRIu32 " offset=%" PRIu64, count, offset);
+
+ if (f->filter.pread)
+ return f->filter.pread (&next_functions, &nxdata, handle,
+ buf, count, offset);
+ else
+ return f->backend.next->pread (f->backend.next, conn,
+ buf, count, offset);
+}
+
+static int
+filter_pwrite (struct backend *b, struct connection *conn,
+ const void *buf, uint32_t count, uint64_t offset)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("pwrite count=%" PRIu32 " offset=%" PRIu64, count, offset);
+
+ if (f->filter.pwrite)
+ return f->filter.pwrite (&next_functions, &nxdata, handle,
+ buf, count, offset);
+ else
+ return f->backend.next->pwrite (f->backend.next, conn,
+ buf, count, offset);
+}
+
+static int
+filter_flush (struct backend *b, struct connection *conn)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("flush");
+
+ if (f->filter.flush)
+ return f->filter.flush (&next_functions, &nxdata, handle);
+ else
+ return f->backend.next->flush (f->backend.next, conn);
+}
+
+static int
+filter_trim (struct backend *b, struct connection *conn,
+ uint32_t count, uint64_t offset)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("trim count=%" PRIu32 " offset=%" PRIu64, count, offset);
+
+ if (f->filter.trim)
+ return f->filter.trim (&next_functions, &nxdata, handle, count, offset);
+ else
+ return f->backend.next->trim (f->backend.next, conn, count, offset);
+}
+
+static int
+filter_zero (struct backend *b, struct connection *conn,
+ uint32_t count, uint64_t offset, int may_trim)
+{
+ struct backend_filter *f = container_of (b, struct backend_filter, backend);
+ void *handle = connection_get_handle (conn, f->backend.i);
+ struct b_conn nxdata = { .b = f->backend.next, .conn = conn };
+
+ debug ("zero count=%" PRIu32 " offset=%" PRIu64 "
may_trim=%d",
+ count, offset, may_trim);
+
+ if (f->filter.zero)
+ return f->filter.zero (&next_functions, &nxdata, handle,
+ count, offset, may_trim);
+ else
+ return f->backend.next->zero (f->backend.next, conn,
+ count, offset, may_trim);
+}
+
+static struct backend filter_functions = {
+ .free = filter_free,
+ .thread_model = plugin_thread_model,
+ .name = filter_name,
+ .plugin_name = plugin_name,
+ .usage = filter_usage,
+ .version = filter_version,
+ .dump_fields = filter_dump_fields,
+ .config = filter_config,
+ .config_complete = filter_config_complete,
+ .errno_is_preserved = plugin_errno_is_preserved,
+ .open = filter_open,
+ .close = filter_close,
+ .get_size = filter_get_size,
+ .can_write = filter_can_write,
+ .can_flush = filter_can_flush,
+ .is_rotational = filter_is_rotational,
+ .can_trim = filter_can_trim,
+ .pread = filter_pread,
+ .pwrite = filter_pwrite,
+ .flush = filter_flush,
+ .trim = filter_trim,
+ .zero = filter_zero,
+};
+
+/* Register and load a filter. */
+struct backend *
+filter_register (struct backend *next, size_t index, const char *filename,
+ void *dl, struct nbdkit_filter *(*filter_init) (void))
+{
+ struct backend_filter *f;
+ const struct nbdkit_filter *filter;
+ size_t i, len, size;
+
+ f = calloc (1, sizeof *f);
+ if (f == NULL) {
+ out_of_memory:
+ perror ("strdup");
+ exit (EXIT_FAILURE);
+ }
+
+ f->backend = filter_functions;
+ f->backend.next = next;
+ f->backend.i = index;
+ f->filename = strdup (filename);
+ if (f->filename == NULL) goto out_of_memory;
+ f->dl = dl;
+
+ debug ("registering filter %s", f->filename);
+
+ /* Call the initialization function which returns the address of the
+ * filter's own 'struct nbdkit_filter'.
+ */
+ filter = filter_init ();
+ if (!filter) {
+ fprintf (stderr, "%s: %s: filter registration function failed\n",
+ program_name, f->filename);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Check for incompatible future versions. */
+ if (filter->_api_version != 1) {
+ fprintf (stderr, "%s: %s: filter is incompatible with this version of nbdkit
(_api_version = %d)\n",
+ program_name, f->filename, filter->_api_version);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Since the filter might be much older than the current version of
+ * nbdkit, only copy up to the self-declared _struct_size of the
+ * filter and zero out the rest. If the filter is much newer then
+ * we'll only call the "old" fields.
+ */
+ size = sizeof f->filter; /* our struct */
+ memset (&f->filter, 0, size);
+ if (size > filter->_struct_size)
+ size = filter->_struct_size;
+ memcpy (&f->filter, filter, size);
+
+ /* Only filter.name is required. */
+ if (f->filter.name == NULL) {
+ fprintf (stderr, "%s: %s: filter must have a .name field\n",
+ program_name, f->filename);
+ exit (EXIT_FAILURE);
+ }
+
+ len = strlen (f->filter.name);
+ if (len == 0) {
+ fprintf (stderr, "%s: %s: filter.name field must not be empty\n",
+ program_name, f->filename);
+ exit (EXIT_FAILURE);
+ }
+ for (i = 0; i < len; ++i) {
+ if (!((f->filter.name[i] >= '0' && f->filter.name[i] <=
'9') ||
+ (f->filter.name[i] >= 'a' && f->filter.name[i] <=
'z') ||
+ (f->filter.name[i] >= 'A' && f->filter.name[i] <=
'Z'))) {
+ fprintf (stderr, "%s: %s: filter.name ('%s') field must contain only
ASCII alphanumeric characters\n",
+ program_name, f->filename, f->filter.name);
+ exit (EXIT_FAILURE);
+ }
+ }
+ /* Copy the module's name into local storage, so that filter.name
+ * survives past unload. */
+ if (!(f->filter.name = strdup (f->filter.name))) {
+ perror ("strdup");
+ exit (EXIT_FAILURE);
+ }
+
+ debug ("registered filter %s (name %s)", f->filename, f->filter.name);
+
+ /* Call the on-load callback if it exists. */
+ debug ("%s: load", f->filename);
+ if (f->filter.load)
+ f->filter.load ();
+
+ return (struct backend *) f;
+}
diff --git a/src/internal.h b/src/internal.h
index 5a68d59..291e657 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013-2017 Red Hat Inc.
+ * Copyright (C) 2013-2018 Red Hat Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -41,6 +41,7 @@
#include <pthread.h>
#include "nbdkit-plugin.h"
+#include "nbdkit-filter.h"
#ifdef __APPLE__
#define UNIX_PATH_MAX 104
@@ -115,6 +116,7 @@ extern volatile int quit;
extern int quit_fd;
extern struct backend *backend;
+#define for_each_backend(b) for (b = backend; b != NULL; b = b->next)
/* cleanup.c */
extern void cleanup_free (void *ptr);
@@ -149,8 +151,19 @@ extern int crypto_negotiate_tls (struct connection *conn, int sockin,
int sockou
/* errors.c */
#define debug nbdkit_debug
-/* plugins.c */
struct backend {
+ /* Next filter or plugin in the chain. This is always NULL for
+ * plugins and never NULL for filters.
+ */
+ struct backend *next;
+
+ /* A unique index used to fetch the handle from the connections
+ * object. The plugin (last in the chain) has index 0, and the
+ * filters have index 1, 2, ... depending how "far" they are from
+ * the plugin.
+ */
+ size_t i;
+
void (*free) (struct backend *);
int (*thread_model) (struct backend *);
const char *(*name) (struct backend *);
@@ -175,7 +188,11 @@ struct backend {
int (*zero) (struct backend *, struct connection *conn, uint32_t count, uint64_t
offset, int may_trim);
};
-extern struct backend *plugin_register (const char *_filename, void *_dl, struct
nbdkit_plugin *(*plugin_init) (void));
+/* plugins.c */
+extern struct backend *plugin_register (size_t index, const char *filename, void *dl,
struct nbdkit_plugin *(*plugin_init) (void));
+
+/* filters.c */
+extern struct backend *filter_register (struct backend *next, size_t index, const char
*filename, void *dl, struct nbdkit_filter *(*filter_init) (void));
/* locks.c */
extern void lock_connection (void);
diff --git a/src/main.c b/src/main.c
index 4790c46..38691c9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -64,7 +64,8 @@
static int is_short_name (const char *);
static char *make_random_fifo (void);
-static struct backend *open_plugin_so (const char *filename, int short_name);
+static struct backend *open_plugin_so (size_t i, const char *filename, int short_name);
+static struct backend *open_filter_so (struct backend *next, size_t i, const char
*filename, int short_name);
static void start_serving (void);
static void set_up_signals (void);
static void run_command (void);
@@ -120,6 +121,7 @@ static const struct option long_options[] = {
{ "export", 1, NULL, 'e' },
{ "export-name",1, NULL, 'e' },
{ "exportname", 1, NULL, 'e' },
+ { "filter", 1, NULL, 0 },
{ "foreground", 0, NULL, 'f' },
{ "no-fork", 0, NULL, 'f' },
{ "group", 1, NULL, 'g' },
@@ -154,7 +156,7 @@ usage (void)
{
printf ("nbdkit [--dump-config] [--dump-plugin]\n"
" [-e EXPORTNAME] [--exit-with-parent] [-f]\n"
- " [-g GROUP] [-i IPADDR]\n"
+ " [--filter=FILTER ...] [-g GROUP] [-i IPADDR]\n"
" [--newstyle] [--oldstyle] [-P PIDFILE] [-p PORT] [-r]\n"
" [--run CMD] [-s] [--selinux-label LABEL] [-t THREADS]\n"
" [--tls=off|on|require] [--tls-certificates
/path/to/certificates]\n"
@@ -206,6 +208,11 @@ main (int argc, char *argv[])
int short_name;
const char *filename;
char *p;
+ static struct filter_filename {
+ struct filter_filename *next;
+ const char *filename;
+ } *filter_filenames = NULL;
+ size_t i;
threadlocal_init ();
@@ -245,6 +252,18 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
#endif
}
+ else if (strcmp (long_options[option_index].name, "filter") == 0) {
+ struct filter_filename *t;
+
+ t = malloc (sizeof *t);
+ if (t == NULL) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ t->next = filter_filenames;
+ t->filename = optarg;
+ filter_filenames = t;
+ }
else if (strcmp (long_options[option_index].name, "run") == 0) {
if (socket_activation) {
fprintf (stderr, "%s: cannot use socket activation with --run
flag\n",
@@ -497,23 +516,46 @@ main (int argc, char *argv[])
}
}
- backend = open_plugin_so (filename, short_name);
+ /* Open the plugin (first) and then wrap the plugin with the
+ * filters. The filters are wrapped in reverse order that they
+ * appear on the command line so that in the end ‘backend’ points to
+ * the first filter on the command line.
+ */
+ backend = open_plugin_so (0, filename, short_name);
+ i = 1;
+ while (filter_filenames) {
+ struct filter_filename *t = filter_filenames;
+ const char *filename = t->filename;
+ int short_name = is_short_name (filename);
+
+ backend = open_filter_so (backend, i++, filename, short_name);
+
+ filter_filenames = t->next;
+ free (t);
+ }
if (help) {
+ struct backend *b;
+
usage ();
- printf ("\n%s:\n\n", filename);
- backend->usage (backend);
+ for_each_backend (b) {
+ printf ("\n");
+ b->usage (b);
+ }
exit (EXIT_SUCCESS);
}
if (version) {
const char *v;
+ struct backend *b;
display_version ();
- printf ("%s", backend->name (backend));
- if ((v = backend->version (backend)) != NULL)
- printf (" %s", v);
- printf ("\n");
+ for_each_backend (b) {
+ printf ("%s", b->name (b));
+ if ((v = b->version (b)) != NULL)
+ printf (" %s", v);
+ printf ("\n");
+ }
exit (EXIT_SUCCESS);
}
@@ -575,7 +617,7 @@ main (int argc, char *argv[])
exit (EXIT_SUCCESS);
}
-/* Is it a name relative to the plugindir? */
+/* Is it a plugin or filter name relative to the plugindir/filterdir? */
static int
is_short_name (const char *filename)
{
@@ -615,7 +657,7 @@ make_random_fifo (void)
}
static struct backend *
-open_plugin_so (const char *name, int short_name)
+open_plugin_so (size_t i, const char *name, int short_name)
{
struct backend *ret;
char *filename = (char *) name;
@@ -653,7 +695,55 @@ open_plugin_so (const char *name, int short_name)
}
/* Register the plugin. */
- ret = plugin_register (filename, dl, plugin_init);
+ ret = plugin_register (i, filename, dl, plugin_init);
+
+ if (free_filename)
+ free (filename);
+
+ return ret;
+}
+
+static struct backend *
+open_filter_so (struct backend *next, size_t i,
+ const char *name, int short_name)
+{
+ struct backend *ret;
+ char *filename = (char *) name;
+ int free_filename = 0;
+ void *dl;
+ struct nbdkit_filter *(*filter_init) (void);
+ char *error;
+
+ if (short_name) {
+ /* Short names are rewritten relative to the filterdir. */
+ if (asprintf (&filename,
+ "%s/nbdkit-%s-filter.so", filterdir, name) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ free_filename = 1;
+ }
+
+ dl = dlopen (filename, RTLD_NOW|RTLD_GLOBAL);
+ if (dl == NULL) {
+ fprintf (stderr, "%s: %s: %s\n", program_name, filename, dlerror ());
+ exit (EXIT_FAILURE);
+ }
+
+ /* Initialize the filter. See dlopen(3) to understand C weirdness. */
+ dlerror ();
+ *(void **) (&filter_init) = dlsym (dl, "filter_init");
+ if ((error = dlerror ()) != NULL) {
+ fprintf (stderr, "%s: %s: %s\n", program_name, name, error);
+ exit (EXIT_FAILURE);
+ }
+ if (!filter_init) {
+ fprintf (stderr, "%s: %s: invalid filter_init\n", program_name, name);
+ exit (EXIT_FAILURE);
+ }
+
+ /* Register the filter. */
+ ret = filter_register (next, i, filename, dl, filter_init);
if (free_filename)
free (filename);
diff --git a/src/plugins.c b/src/plugins.c
index e732587..6046320 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -102,10 +102,11 @@ plugin_usage (struct backend *b)
{
struct backend_plugin *p = container_of (b, struct backend_plugin, backend);
- printf ("%s", p->plugin.name);
+ printf ("plugin: %s", p->plugin.name);
if (p->plugin.longname)
printf (" (%s)", p->plugin.longname);
printf ("\n");
+ printf ("(%s)", p->filename);
if (p->plugin.description) {
printf ("\n");
printf ("%s\n", p->plugin.description);
@@ -489,7 +490,7 @@ static struct backend plugin_functions = {
/* Register and load a plugin. */
struct backend *
-plugin_register (const char *filename,
+plugin_register (size_t index, const char *filename,
void *dl, struct nbdkit_plugin *(*plugin_init) (void))
{
struct backend_plugin *p;
@@ -504,11 +505,13 @@ plugin_register (const char *filename,
}
p->backend = plugin_functions;
+ p->backend.next = NULL;
+ p->backend.i = index;
p->filename = strdup (filename);
if (p->filename == NULL) goto out_of_memory;
p->dl = dl;
- debug ("registering %s", p->filename);
+ debug ("registering plugin %s", p->filename);
/* Call the initialization function which returns the address of the
* plugin's own 'struct nbdkit_plugin'.
@@ -584,7 +587,7 @@ plugin_register (const char *filename,
exit (EXIT_FAILURE);
}
- debug ("registered %s (name %s)", p->filename, p->plugin.name);
+ debug ("registered plugin %s (name %s)", p->filename, p->plugin.name);
/* Call the on-load callback if it exists. */
debug ("%s: load", p->filename);
--
2.15.1