[Libguestfs] [PATCH nbdkit filters-v2 2/5] Introduce filters.

Richard W.M. Jones rjones at redhat.com
Fri Jan 19 15:23:33 UTC 2018


Filters can be placed in front of plugins to modify their behaviour.

This commit adds the <nbdkit-filter.h> header file, the manual page,
the ‘filterdir’ directory (like ‘plugindir’), the ‘filters/’ source
directory which will contain the actual filters, the ‘--filters’
parameter, and the filters backend logic.
---
 Makefile.am             |   2 +-
 TODO                    |  17 +-
 configure.ac            |   3 +-
 docs/Makefile.am        |   9 +-
 docs/nbdkit-filter.pod  | 501 ++++++++++++++++++++++++++++++++++++
 docs/nbdkit-plugin.pod  |   3 +-
 docs/nbdkit.pod         |  24 +-
 filters/Makefile.am     |  33 +++
 include/Makefile.am     |   4 +-
 include/nbdkit-filter.h | 149 +++++++++++
 include/nbdkit-plugin.h |   2 +
 nbdkit.in               |  17 +-
 src/Makefile.am         |   6 +-
 src/filters.c           | 659 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/internal.h          |  21 +-
 src/main.c              | 115 ++++++++-
 src/nbdkit.pc.in        |   1 +
 src/plugins.c           |  11 +-
 18 files changed, 1535 insertions(+), 42 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index f3c88b0..9c5b4c3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -49,7 +49,7 @@ SUBDIRS = \
 	src
 
 if HAVE_PLUGINS
-SUBDIRS += plugins
+SUBDIRS += plugins filters
 endif
 
 SUBDIRS += tests
diff --git a/TODO b/TODO
index 0c027e2..0955db7 100644
--- a/TODO
+++ b/TODO
@@ -34,10 +34,8 @@ nbdkit there is no compelling reason unless the result is better than
 qemu-nbd.  For the majority of users it would be better if they were
 directed to qemu-nbd for these use cases.
 
-Filters
--------
-
-It should be possible to layer filters over plugins to do things like:
+Suggestions for filters
+-----------------------
 
 * adding artificial delays (see wdelay/rdelay options in the file
   plugin)
@@ -50,17 +48,6 @@ It should be possible to layer filters over plugins to do things like:
 
 * export a single partition (like qemu-nbd -P)
 
-A possible syntax would be:
-
-  nbdkit --filter=delay [--filter=...] file file=foo wdelay=10
-
-The filter(s) intercept all plugin calls and can either return, return
-an error, or pass the call down to the next layer in the stack (and
-eventually to the plugin).  By intercepting the .config call the
-filter can process its own parameters from the command line (wdelay=10
-in the example above), and by intercepting the .pread, .pwrite methods
-the filter could inject the delaying behaviour.
-
 Composing nbdkit
 ----------------
 
diff --git a/configure.ac b/configure.ac
index a2950f6..7032614 100644
--- a/configure.ac
+++ b/configure.ac
@@ -181,7 +181,7 @@ AS_IF([test "x$POD2MAN" != "xno"],[
 AM_CONDITIONAL([HAVE_POD2MAN], [test "x$POD2MAN" != "xno"])
 
 AC_ARG_ENABLE([plugins],
-    [AS_HELP_STRING([--disable-plugins], [disable all bundled plugins])])
+    [AS_HELP_STRING([--disable-plugins], [disable all bundled plugins and filters])])
 AM_CONDITIONAL([HAVE_PLUGINS], [test "x$enable_plugins" != "xno"])
 
 dnl Check for Perl, for embedding in the perl plugin.
@@ -512,6 +512,7 @@ AC_CONFIG_FILES([Makefile
                  plugins/tar/Makefile
                  plugins/vddk/Makefile
                  plugins/xz/Makefile
+                 filters/Makefile
                  src/Makefile
                  src/nbdkit.pc
                  tests/Makefile])
diff --git a/docs/Makefile.am b/docs/Makefile.am
index 323f48d..d2330fb 100644
--- a/docs/Makefile.am
+++ b/docs/Makefile.am
@@ -33,6 +33,7 @@
 EXTRA_DIST = \
 	nbdkit.pod \
 	nbdkit-plugin.pod
+	nbdkit-filter.pod
 
 CLEANFILES = *~
 
@@ -40,7 +41,8 @@ if HAVE_POD2MAN
 
 man_MANS = \
 	nbdkit.1 \
-	nbdkit-plugin.3
+	nbdkit-plugin.3 \
+	nbdkit-filter.3
 CLEANFILES += $(man_MANS)
 
 nbdkit.1: nbdkit.pod
@@ -53,4 +55,9 @@ nbdkit-plugin.3: nbdkit-plugin.pod
 	if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
 	mv $@.t $@
 
+nbdkit-filter.3: nbdkit-filter.pod
+	$(POD2MAN) $(POD2MAN_ARGS) --section=3 --name=nbdkit-filter $< $@.t && \
+	if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \
+	mv $@.t $@
+
 endif
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
new file mode 100644
index 0000000..75157ef
--- /dev/null
+++ b/docs/nbdkit-filter.pod
@@ -0,0 +1,501 @@
+=encoding utf8
+
+=head1 NAME
+
+nbdkit-filter - How to write nbdkit filters
+
+=head1 SYNOPSIS
+
+ #include <nbdkit-filter.h>
+ 
+ static int
+ myfilter_config (nbdkit_next_config *next, void *nxdata,
+                  const char *key, const char *value)
+ {
+   if (strcmp (key, "myparameter") == 0) {
+     // ...
+     return 0;
+   }
+   else {
+     // pass through to next filter or plugin
+     return next (nxdata, key, value);
+   }
+ }
+ 
+ static struct nbdkit_filter filter = {
+   .name              = "filter",
+   .config            = myfilter_config,
+   /* etc */
+ };
+ 
+ NBDKIT_REGISTER_FILTER(filter)
+
+When this has been compiled to a shared library, do:
+
+ nbdkit [--args ...] --filter=./myfilter.so plugin [key=value ...]
+
+When debugging, use the I<-fv> options:
+
+ nbdkit -fv --filter=./myfilter.so plugin [key=value ...]
+
+=head1 DESCRIPTION
+
+One or more nbdkit filters can be placed in front of an nbdkit plugin
+to modify the behaviour of the plugin.  This manual page describes how
+to create an nbdkit filter.
+
+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).
+
+Different filters can be stacked:
+
+     NBD     ┌─────────┐    ┌─────────┐          ┌────────┐
+  client ───▶│ filter1 │───▶│ filter2 │── ─ ─ ──▶│ plugin │
+ request     └─────────┘    └─────────┘          └────────┘
+
+Each filter intercepts plugin functions (see L<nbdkit-plugin(3)>) and
+can call the next filter or plugin in the chain, modifying parameters,
+calling before the filter function, in the middle or after.  Filters
+may even short-cut the chain.  As an example, to process its own
+parameters the filter can intercept the C<.config> method:
+
+ static int
+ myfilter_config (nbdkit_next_config *next, void *nxdata,
+                  const char *key, const char *value)
+ {
+   if (strcmp (key, "myparameter") == 0) {
+     // ...
+     // here you would handle this key, value
+     // ...
+     return 0;
+   }
+   else {
+     // pass through to next filter or plugin
+     return next (nxdata, key, value);
+   }
+ }
+ 
+ static struct nbdkit_filter filter = {
+   // ...
+   .config            = myfilter_config,
+   // ...
+ };
+
+The call to C<next (nxdata, ...)> calls the C<.config> method of the
+next filter or plugin in the chain.  In the example above any
+instances of C<myparameter=...> on the command line would not be seen
+by the plugin.
+
+To see example filters, take a look at the source of nbdkit, in the
+C<filters> directory.
+
+Filters must be written in C and must be fully thread safe.
+
+=head1 C<nbdkit-filter.h>
+
+All filters should start by including this header file:
+
+ #include <nbdkit-filter.h>
+
+=head1 C<struct nbdkit_filter>
+
+All filters must define and register one C<struct nbdkit_filter>,
+which contains the name of the filter and pointers to plugin methods
+that the filter wants to intercept.
+
+ static struct nbdkit_filter filter = {
+   .name              = "filter",
+   .longname          = "My Filter",
+   .description       = "This is my great filter for nbdkit",
+   .config            = myfilter_config,
+   /* etc */
+ };
+ 
+ NBDKIT_REGISTER_FILTER(filter)
+
+The C<.name> field is the name of the filter.  This is the only field
+which is required.
+
+=head1 NEXT PLUGIN
+
+F<nbdkit-filter.h> defines two function types (C<nbdkit_next_config>,
+C<nbdkit_next_config_complete>) and a structure called C<struct
+nbdkit_next_ops>.  These abstract the next plugin or filter in the
+chain.  There is also an opaque pointer C<nxdata> which must be passed
+along when calling these functions.
+
+The filter’s C<.config> and C<.config_complete> methods may only call
+the next C<.config> or C<.config_complete> method in the chain
+(optionally).
+
+The filter’s C<.open> and C<.close> methods are called when a new
+connection is opened or an old connection closed, and these have no
+C<next> parameter because they cannot be short-circuited.
+
+The filter’s other methods like C<.get_size>, C<.pread> etc ― always
+called in the context of a connection ― are passed a pointer to
+C<struct nbdkit_next_ops> which contains a subset of the plugin
+methods that can be called during a connection.  It is possible for a
+filter to issue (for example) extra read calls in response to a single
+C<.pwrite> call.
+
+You can modify parameters when you call the C<next> function.  However
+be careful when modifying strings because for some methods
+(eg. C<.config>) the plugin may save the string pointer that you pass
+along.  So you may have to ensure that the string is not freed for the
+lifetime of the server.
+
+Note that if your filter registers a callback but in that callback it
+doesn't call the C<next> function then the corresponding method in the
+plugin will never be called.
+
+=head1 CALLBACKS
+
+C<struct nbdkit_filter> has some static fields describing the filter
+and optional callback functions which can be used to intercept plugin
+methods.
+
+=head2 C<.name>
+
+ const char *name;
+
+This field (a string) is required, and B<must> contain only ASCII
+alphanumeric characters and be unique amongst all filters.
+
+=head2 C<.version>
+
+ const char *version;
+
+Filters may optionally set a version string which is displayed in help
+and debugging output.
+
+=head2 C<.longname>
+
+ const char *longname;
+
+An optional free text name of the filter.  This field is used in error
+messages.
+
+=head2 C<.description>
+
+ const char *description;
+
+An optional multi-line description of the filter.
+
+=head2 C<.load>
+
+ void load (void);
+
+This is called once just after the filter is loaded into memory.  You
+can use this to perform any global initialization needed by the
+filter.
+
+=head2 C<.unload>
+
+ void unload (void);
+
+This may be called once just before the filter is unloaded from
+memory.  Note that it's not guaranteed that C<.unload> will always be
+called (eg. the server might be killed or segfault), so you should try
+to make the filter as robust as possible by not requiring cleanup.
+See also L<nbdkit-plugin(3)/SHUTDOWN>.
+
+=head2 C<.config>
+
+ int (*config) (nbdkit_next_config *next, void *nxdata,
+                const char *key, const char *value);
+
+This intercepts the plugin C<.config> method and can be used by the
+filter to parse its own command line parameters.  You should try to
+make sure that command line parameter keys that the filter uses do not
+conflict with ones that could be used by a plugin.
+
+If there is an error, C<.config> should call C<nbdkit_error> with an
+error message and return C<-1>.
+
+=head2 C<.config_complete>
+
+ int (*config_complete) (nbdkit_next_config_complete *next, void *nxdata);
+
+This intercepts the plugin C<.config_complete> method and can be used
+to ensure that all parameters needed by the filter were supplied on
+the command line.
+
+If there is an error, C<.config_complete> should call C<nbdkit_error>
+with an error message and return C<-1>.
+
+=head2 C<.config_help>
+
+ const char *config_help;
+
+This optional multi-line help message should summarize any
+C<key=value> parameters that it takes.  It does I<not> need to repeat
+what already appears in C<.description>.
+
+If the filter doesn't take any config parameters you should probably
+omit this.
+
+=head2 C<.open>
+
+ void * (*open) (int readonly);
+
+This is called when a new client connection is opened and can be used
+to allocate any per-connection data structures needed by the filter.
+The handle (which is not the same as the plugin handle) is passed back
+to other filter callbacks and could be freed in the C<.close>
+callback.
+
+Note that the handle is completely opaque to nbdkit, but it must not
+be NULL.
+
+If there is an error, C<.open> should call C<nbdkit_error> with an
+error message and return C<NULL>.
+
+=head2 C<.close>
+
+ void (*close) (void *handle);
+
+This is called when the client closes the connection.  It should clean
+up any per-connection resources used by the filter.
+
+=head2 C<.prepare>
+
+=head2 C<.finalize>
+
+  int (*prepare) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                  void *handle);
+  int (*finalize) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                   void *handle);
+
+These two methods can be used to perform any necessary operations just
+before the data serving phase starts (C<.prepare>) or just after the
+data serving phase ends (C<.finalize>).
+
+For example if you need to scan the underlying disk to check for a
+partition table, you could do it in your C<.prepare> method (calling
+the plugin's C<.pread> method via C<next_ops>).  Or if you need to
+cleanly update superblock data in the image on close you can do it in
+your C<.finalize> method (calling the plugin's C<.pwrite> method).
+Doing these things in the filter's C<.open> or C<.close> method is not
+possible.
+
+If there is an error, both callbacks should call C<nbdkit_error> with
+an error message and return C<-1>.
+
+=head2 C<.get_size>
+
+ int64_t (*get_size) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                      void *handle);
+
+This intercepts the plugin C<.get_size> method and can be used to read
+or modify the apparent size of the block device that the NBD client
+will see.
+
+The returned size must be E<ge> 0.  If there is an error, C<.get_size>
+should call C<nbdkit_error> with an error message and return C<-1>.
+
+=head2 C<.can_write>
+
+=head2 C<.can_flush>
+
+=head2 C<.is_rotational>
+
+=head2 C<.can_trim>
+
+ int (*can_write) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                   void *handle);
+ int (*can_flush) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                   void *handle);
+ int (*is_rotational) (struct nbdkit_next_ops *next_ops,
+                       void *nxdata,
+                       void *handle);
+ int (*can_trim) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                  void *handle);
+
+These intercept the corresponding plugin methods.
+
+If there is an error, the callback should call C<nbdkit_error> with an
+error message and return C<-1>.
+
+=head2 C<.pread>
+
+ int (*pread) (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle, void *buf, uint32_t count, uint64_t offset);
+
+This intercepts the plugin C<.pread> method and can be used to read or
+modify data read by the plugin.
+
+If there is an error (including a short read which couldn't be
+recovered from), C<.pread> should call C<nbdkit_error> with an error
+message B<and> set C<errno>, then return C<-1>.
+
+=head2 C<.pwrite>
+
+ int (*pwrite) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                void *handle,
+                const void *buf, uint32_t count, uint64_t offset);
+
+This intercepts the plugin C<.pwrite> method and can be used to modify
+data written by the plugin.
+
+If there is an error (including a short write which couldn't be
+recovered from), C<.pwrite> should call C<nbdkit_error> with an error
+message B<and> set C<errno>, then return C<-1>.
+
+=head2 C<.flush>
+
+ int (*flush) (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle);
+
+This intercepts the plugin C<.flush> method and can be used to modify
+flush requests.
+
+If there is an error, C<.flush> should call C<nbdkit_error> with an
+error message B<and> set C<errno>, then return C<-1>.
+
+=head2 C<.trim>
+
+ int (*trim) (struct nbdkit_next_ops *next_ops, void *nxdata,
+              void *handle, uint32_t count, uint64_t offset);
+
+This intercepts the plugin C<.trim> method and can be used to modify
+trim requests.
+
+If there is an error, C<.trim> should call C<nbdkit_error> with an
+error message B<and> set C<errno>, then return C<-1>.
+
+=head2 C<.zero>
+
+ int (*zero) (struct nbdkit_next_ops *next_ops, void *nxdata,
+              void *handle, uint32_t count, uint64_t offset, int may_trim);
+
+This intercepts the plugin C<.zero> method and can be used to modify
+zero requests.
+
+If there is an error, C<.zero> should call C<nbdkit_error> with an
+error message B<and> set C<errno>, then return C<-1>.
+
+=head1 THREADS
+
+Because filters can be mixed and used with any plugin and thus any
+threading model supported by L<nbdkit-plugin(3)>, filters must be
+thread safe.  They must be able to handle concurrent requests even on
+the same handle.
+
+Filters may have to use pthread primitives like mutexes to achieve
+this.
+
+=head1 DEBUGGING
+
+Run the server with I<-f> and I<-v> options so it doesn't fork and you
+can see debugging information:
+
+ nbdkit -fv --filter=./myfilter.so plugin [key=value [key=value [...]]]
+
+To print debugging information from within the filter, call
+C<nbdkit_debug>, which has the following prototype and works like
+L<printf(3)>:
+
+ void nbdkit_debug (const char *fs, ...);
+ void nbdkit_vdebug (const char *fs, va_list args);
+
+For convenience, C<nbdkit_debug> preserves the value of C<errno>.
+Note that C<nbdkit_debug> only prints things when the server is in
+verbose mode (I<-v> option).
+
+=head1 INSTALLING THE FILTER
+
+The filter is a C<*.so> file and possibly a manual page.  You can of
+course install the filter C<*.so> file wherever you want, and users
+will be able to use it by running:
+
+ nbdkit --filter=/path/to/filter.so plugin [args]
+
+However B<if> the shared library has a name of the form
+C<nbdkit-I<name>-filter.so> B<and if> the library is installed in the
+C<$filterdir> directory, then users can be run it by only typing:
+
+ nbdkit --filter=name plugin [args]
+
+The location of the C<$filterdir> directory is set when nbdkit is
+compiled and can be found by doing:
+
+ nbdkit --dump-config
+
+If using the pkg-config/pkgconf system then you can also find the
+filter directory at compile time by doing:
+
+ pkgconf nbdkit --variable=filterdir
+
+=head1 PKG-CONFIG/PKGCONF
+
+nbdkit provides a pkg-config/pkgconf file called C<nbdkit.pc> which
+should be installed on the correct path when the nbdkit development
+environment is installed.  You can use this in autoconf
+F<configure.ac> scripts to test for the development environment:
+
+ PKG_CHECK_MODULES([NBDKIT], [nbdkit >= 1.2.3])
+
+The above will fail unless nbdkit E<ge> 1.2.3 and the header file is
+installed, and will set C<NBDKIT_CFLAGS> and C<NBDKIT_LIBS>
+appropriately for compiling filters.
+
+You can also run pkg-config/pkgconf directly, for example:
+
+ if ! pkgconf nbdkit --exists; then
+   echo "you must install the nbdkit development environment"
+   exit 1
+ fi
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-plugin(1)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2013-2018 Red Hat Inc.
+
+=head1 LICENSE
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item *
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+=item *
+
+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.
+
+=item *
+
+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.
+
+=back
+
+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.
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 9abf75f..3cafc42 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -692,6 +692,7 @@ and then users will be able to run it like this:
 =head1 SEE ALSO
 
 L<nbdkit(1)>,
+L<nbdkit-filter(3)>,
 L<nbdkit-example1-plugin(1)>,
 L<nbdkit-example2-plugin(1)>,
 L<nbdkit-example3-plugin(1)>,
@@ -711,7 +712,7 @@ Pino Toscano
 
 =head1 COPYRIGHT
 
-Copyright (C) 2013-2017 Red Hat Inc.
+Copyright (C) 2013-2018 Red Hat Inc.
 
 =head1 LICENSE
 
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 1687ac9..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
@@ -856,6 +875,7 @@ L</SOCKET ACTIVATION>.
 Other nbdkit manual pages:
 
 L<nbdkit-plugin(3)>,
+L<nbdkit-filter(3)>,
 L<nbdkit-curl-plugin(1)>,
 L<nbdkit-example1-plugin(1)>,
 L<nbdkit-example2-plugin(1)>,
@@ -895,7 +915,7 @@ Pino Toscano
 
 =head1 COPYRIGHT
 
-Copyright (C) 2013-2017 Red Hat Inc.
+Copyright (C) 2013-2018 Red Hat Inc.
 
 =head1 LICENSE
 
diff --git a/filters/Makefile.am b/filters/Makefile.am
new file mode 100644
index 0000000..ed1580b
--- /dev/null
+++ b/filters/Makefile.am
@@ -0,0 +1,33 @@
+# 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.
+
+#SUBDIRS =
diff --git a/include/Makefile.am b/include/Makefile.am
index 7d54215..deccc6b 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -30,4 +30,6 @@
 # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.
 
-include_HEADERS = nbdkit-plugin.h
+include_HEADERS = \
+	nbdkit-plugin.h \
+	nbdkit-filter.h
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
new file mode 100644
index 0000000..af79e33
--- /dev/null
+++ b/include/nbdkit-filter.h
@@ -0,0 +1,149 @@
+/* nbdkit
+ * Copyright (C) 2013-2017 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.
+ */
+
+/* See nbdkit-filter(3) for documentation and how to write a filter. */
+
+#ifndef NBDKIT_FILTER_H
+#define NBDKIT_FILTER_H
+
+/* This header also defines some useful functions like nbdkit_debug
+ * and nbdkit_parse_size which are appropriate for filters to use.
+ */
+#include <nbdkit-plugin.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NBDKIT_FILTER_API_VERSION 1
+
+typedef int nbdkit_next_config (void *nxdata,
+                                const char *key, const char *value);
+typedef int nbdkit_next_config_complete (void *nxdata);
+
+struct nbdkit_next_ops {
+  int64_t (*get_size) (void *nxdata);
+
+  int (*can_write) (void *nxdata);
+  int (*can_flush) (void *nxdata);
+  int (*is_rotational) (void *nxdata);
+  int (*can_trim) (void *nxdata);
+
+  int (*pread) (void *nxdata, void *buf, uint32_t count, uint64_t offset);
+  int (*pwrite) (void *nxdata,
+                 const void *buf, uint32_t count, uint64_t offset);
+  int (*flush) (void *nxdata);
+  int (*trim) (void *nxdata, uint32_t count, uint64_t offset);
+  int (*zero) (void *nxdata, uint32_t count, uint64_t offset, int may_trim);
+};
+
+struct nbdkit_filter {
+  /* Do not set these fields directly; use NBDKIT_REGISTER_FILTER.
+   * They exist so that we can support filters compiled against
+   * one version of the header with a runtime compiled against a
+   * different version with more (or fewer) fields.
+   */
+  uint64_t _struct_size;
+  int _api_version;
+
+  /* New fields will only be added at the end of the struct. */
+  const char *name;
+  const char *longname;
+  const char *version;
+  const char *description;
+
+  void (*load) (void);
+  void (*unload) (void);
+
+  int (*config) (nbdkit_next_config *next, void *nxdata,
+                 const char *key, const char *value);
+  int (*config_complete) (nbdkit_next_config_complete *next, void *nxdata);
+  const char *config_help;
+
+  void * (*open) (int readonly);
+  void (*close) (void *handle);
+
+  int (*prepare) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                  void *handle);
+  int (*finalize) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                   void *handle);
+
+  int64_t (*get_size) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                       void *handle);
+
+  int (*can_write) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                    void *handle);
+  int (*can_flush) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                    void *handle);
+  int (*is_rotational) (struct nbdkit_next_ops *next_ops,
+                        void *nxdata,
+                        void *handle);
+  int (*can_trim) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                   void *handle);
+
+  int (*pread) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                void *handle, void *buf, uint32_t count, uint64_t offset);
+  int (*pwrite) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                 void *handle,
+                 const void *buf, uint32_t count, uint64_t offset);
+  int (*flush) (struct nbdkit_next_ops *next_ops, void *nxdata,
+                void *handle);
+  int (*trim) (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle, uint32_t count, uint64_t offset);
+  int (*zero) (struct nbdkit_next_ops *next_ops, void *nxdata,
+               void *handle, uint32_t count, uint64_t offset, int may_trim);
+};
+
+#ifndef NBDKIT_CXX_LANG_C
+#ifdef __cplusplus
+#define NBDKIT_CXX_LANG_C extern "C"
+#else
+#define NBDKIT_CXX_LANG_C /* nothing */
+#endif
+#endif
+
+#define NBDKIT_REGISTER_FILTER(filter)                                  \
+  NBDKIT_CXX_LANG_C                                                     \
+  struct nbdkit_filter *                                                \
+  filter_init (void)                                                    \
+  {                                                                     \
+    (filter)._struct_size = sizeof (filter);                            \
+    (filter)._api_version = NBDKIT_API_VERSION;                         \
+    return &(filter);                                                   \
+  }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NBDKIT_FILTER_H */
diff --git a/include/nbdkit-plugin.h b/include/nbdkit-plugin.h
index 2ec3b15..13541e5 100644
--- a/include/nbdkit-plugin.h
+++ b/include/nbdkit-plugin.h
@@ -111,11 +111,13 @@ extern char *nbdkit_absolute_path (const char *path);
 extern int64_t nbdkit_parse_size (const char *str);
 extern int nbdkit_read_password (const char *value, char **password);
 
+#ifndef NBDKIT_CXX_LANG_C
 #ifdef __cplusplus
 #define NBDKIT_CXX_LANG_C extern "C"
 #else
 #define NBDKIT_CXX_LANG_C /* nothing */
 #endif
+#endif
 
 #define NBDKIT_REGISTER_PLUGIN(plugin)                                  \
   NBDKIT_CXX_LANG_C                                                     \
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 1f05eab..ae16fde 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,6 +31,7 @@
 # SUCH DAMAGE.
 
 plugindir = $(libdir)/nbdkit/plugins
+filterdir = $(libdir)/nbdkit/filters
 
 sbin_PROGRAMS = nbdkit
 
@@ -39,6 +40,7 @@ nbdkit_SOURCES = \
 	connections.c \
 	crypto.c \
 	errors.c \
+	filters.c \
 	internal.h \
 	locks.c \
 	main.c \
@@ -47,13 +49,15 @@ nbdkit_SOURCES = \
 	sockets.c \
 	threadlocal.c \
 	utils.c \
-	$(top_srcdir)/include/nbdkit-plugin.h
+	$(top_srcdir)/include/nbdkit-plugin.h \
+	$(top_srcdir)/include/nbdkit-filter.h
 
 nbdkit_CPPFLAGS = \
 	-Dbindir=\"$(bindir)\" \
 	-Dlibdir=\"$(libdir)\" \
 	-Dmandir=\"$(mandir)\" \
 	-Dplugindir=\"$(plugindir)\" \
+	-Dfilterdir=\"$(filterdir)\" \
 	-Dsbindir=\"$(sbindir)\" \
 	-Dsysconfdir=\"$(sysconfdir)\" \
 	-I$(top_srcdir)/include
diff --git a/src/filters.c b/src/filters.c
new file mode 100644
index 0000000..9a2022c
--- /dev/null
+++ b/src/filters.c
@@ -0,0 +1,659 @@
+/* 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 of ‘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, 0);
+}
+
+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, 0);
+}
+
+static int
+next_flush (void *nxdata)
+{
+  struct b_conn *b_conn = nxdata;
+  return b_conn->b->flush (b_conn->b, b_conn->conn, 0);
+}
+
+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, 0);
+}
+
+static int
+next_zero (void *nxdata, uint32_t count, uint64_t offset, int may_trim)
+{
+  struct b_conn *b_conn = nxdata;
+  uint32_t f = 0;
+
+  if (may_trim)
+    f |= NBDKIT_FLAG_MAY_TRIM;
+
+  return b_conn->b->zero (b_conn->b, b_conn->conn, count, offset, f);
+}
+
+static struct nbdkit_next_ops next_ops = {
+  .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 int
+filter_prepare (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 ("prepare");
+
+  if (f->filter.prepare)
+    return f->filter.prepare (&next_ops, &nxdata, handle);
+  else
+    return f->backend.next->prepare (f->backend.next, conn);
+}
+
+static int
+filter_finalize (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 ("finalize");
+
+  if (f->filter.finalize)
+    return f->filter.finalize (&next_ops, &nxdata, handle);
+  else
+    return f->backend.next->finalize (f->backend.next, conn);
+}
+
+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_ops, &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_ops, &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_ops, &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_ops, &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_ops, &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,
+              uint32_t flags)
+{
+  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 };
+
+  assert (flags == 0);
+
+  debug ("pread count=%" PRIu32 " offset=%" PRIu64, count, offset);
+
+  if (f->filter.pread)
+    return f->filter.pread (&next_ops, &nxdata, handle,
+                            buf, count, offset);
+  else
+    return f->backend.next->pread (f->backend.next, conn,
+                                   buf, count, offset, flags);
+}
+
+static int
+filter_pwrite (struct backend *b, struct connection *conn,
+               const void *buf, uint32_t count, uint64_t offset,
+               uint32_t flags)
+{
+  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 };
+  bool fua = flags & NBDKIT_FLAG_FUA;
+
+  assert (!(flags & ~NBDKIT_FLAG_FUA));
+
+  debug ("pwrite count=%" PRIu32 " offset=%" PRIu64 " fua=%d",
+         count, offset, fua);
+
+  if (f->filter.pwrite)
+    return f->filter.pwrite (&next_ops, &nxdata, handle,
+                             buf, count, offset);
+  else
+    return f->backend.next->pwrite (f->backend.next, conn,
+                                    buf, count, offset, flags);
+}
+
+static int
+filter_flush (struct backend *b, struct connection *conn, uint32_t flags)
+{
+  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 };
+
+  assert (flags == 0);
+
+  debug ("flush");
+
+  if (f->filter.flush)
+    return f->filter.flush (&next_ops, &nxdata, handle);
+  else
+    return f->backend.next->flush (f->backend.next, conn, flags);
+}
+
+static int
+filter_trim (struct backend *b, struct connection *conn,
+             uint32_t count, uint64_t offset,
+             uint32_t flags)
+{
+  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 };
+
+  assert (flags == 0);
+
+  debug ("trim count=%" PRIu32 " offset=%" PRIu64, count, offset);
+
+  if (f->filter.trim)
+    return f->filter.trim (&next_ops, &nxdata, handle, count, offset);
+  else
+    return f->backend.next->trim (f->backend.next, conn, count, offset, flags);
+}
+
+static int
+filter_zero (struct backend *b, struct connection *conn,
+             uint32_t count, uint64_t offset, uint32_t flags)
+{
+  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 };
+  int may_trim = (flags & NBDKIT_FLAG_MAY_TRIM) != 0;
+
+  assert (!(flags & ~(NBDKIT_FLAG_MAY_TRIM | NBDKIT_FLAG_FUA)));
+
+  debug ("zero count=%" PRIu32 " offset=%" PRIu64 " may_trim=%d",
+         count, offset, may_trim);
+
+  if (f->filter.zero)
+    return f->filter.zero (&next_ops, &nxdata, handle,
+                           count, offset, may_trim);
+  else
+    return f->backend.next->zero (f->backend.next, conn,
+                                  count, offset, flags);
+}
+
+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,
+  .prepare = filter_prepare,
+  .finalize = filter_finalize,
+  .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 dbcd89c..3cbfde5 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -41,6 +41,7 @@
 #include <pthread.h>
 
 #include "nbdkit-plugin.h"
+#include "nbdkit-filter.h"
 
 #ifdef __APPLE__
 #define UNIX_PATH_MAX 104
@@ -118,6 +119,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);
@@ -152,8 +154,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 *);
@@ -180,7 +193,11 @@ struct backend {
   int (*zero) (struct backend *, struct connection *conn, uint32_t count, uint64_t offset, uint32_t flags);
 };
 
-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_init_thread_model (void);
diff --git a/src/main.c b/src/main.c
index 90d464a..29332c4 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"
@@ -179,6 +181,7 @@ dump_config (void)
   printf ("%s=%s\n", "mandir", mandir);
   printf ("%s=%s\n", "name", PACKAGE_NAME);
   printf ("%s=%s\n", "plugindir", plugindir);
+  printf ("%s=%s\n", "filterdir", filterdir);
   printf ("%s=%s\n", "root_tls_certificates_dir", root_tls_certificates_dir);
   printf ("%s=%s\n", "sbindir", sbindir);
 #ifdef HAVE_LIBSELINUX
@@ -205,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 ();
 
@@ -244,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",
@@ -496,24 +516,47 @@ 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);
+  }
   lock_init_thread_model ();
 
   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 +618,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 +658,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 +696,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/nbdkit.pc.in b/src/nbdkit.pc.in
index cbb301d..fe8f511 100644
--- a/src/nbdkit.pc.in
+++ b/src/nbdkit.pc.in
@@ -3,6 +3,7 @@ exec_prefix=@exec_prefix@
 libdir=@libdir@
 includedir=@includedir@
 plugindir=@libdir@/nbdkit/plugins
+filterdir=@libdir@/nbdkit/filters
 
 Name: @PACKAGE_NAME@
 Version: @PACKAGE_VERSION@
diff --git a/src/plugins.c b/src/plugins.c
index da11c2c..dac2280 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);
@@ -536,7 +537,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;
@@ -551,11 +552,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'.
@@ -631,7 +634,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




More information about the Libguestfs mailing list