[Libguestfs] [PATCH 7/9] Implement filters.

Richard W.M. Jones
Wed, 17 Jan 2018 20:53:54 -0000

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