[Libguestfs] [PATCH 6/9] Introduce filters.

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

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

This commit introduces the <nbdkit-filter.h> header file, the manual
page, the ‘filterdir’ directory (like ‘plugindir’), and the ‘filters/’
source directory which will contain both example and real filters.
---
 Makefile.am             |   2 +-
 TODO                    |  17 +-
 configure.ac            |   3 +-
 docs/Makefile.am        |   9 +-
 docs/nbdkit-filter.pod  | 477 ++++++++++++++++++++++++++++++++++++++++++++++++
 docs/nbdkit-plugin.pod  |   3 +-
 docs/nbdkit.pod         |   3 +-
 filters/Makefile.am     |  33 ++++
 include/Makefile.am     |   4 +-
 include/nbdkit-filter.h | 144 +++++++++++++++
 include/nbdkit-plugin.h |   2 +
 src/Makefile.am         |   5 +-
 src/main.c              |   1 +
 src/nbdkit.pc.in        |   1 +
 14 files changed, 682 insertions(+), 22 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..81f0079
--- /dev/null
+++ b/docs/nbdkit-filter.pod
@@ -0,0 +1,477 @@
+=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>.  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> 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<.get_size>
+
+ int64_t (*get_size) (struct nbdkit_next *next, 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 *next, void *nxdata,
+                   void *handle);
+ int (*can_flush) (struct nbdkit_next *next, void *nxdata,
+                   void *handle);
+ int (*is_rotational) (struct nbdkit_next *next,
+                       void *nxdata,
+                       void *handle);
+ int (*can_trim) (struct nbdkit_next *next, 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 *next, 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 *next, 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 *next, 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 *next, 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 *next, 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..3b37db8 100644
--- a/docs/nbdkit.pod
+++ b/docs/nbdkit.pod
@@ -856,6 +856,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 +896,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..8c49b5e
--- /dev/null
+++ b/include/nbdkit-filter.h
@@ -0,0 +1,144 @@
+/* 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 {
+  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);
+
+  int64_t (*get_size) (struct nbdkit_next *next, void *nxdata,
+                       void *handle);
+
+  int (*can_write) (struct nbdkit_next *next, void *nxdata,
+                    void *handle);
+  int (*can_flush) (struct nbdkit_next *next, void *nxdata,
+                    void *handle);
+  int (*is_rotational) (struct nbdkit_next *next,
+                        void *nxdata,
+                        void *handle);
+  int (*can_trim) (struct nbdkit_next *next, void *nxdata,
+                   void *handle);
+
+  int (*pread) (struct nbdkit_next *next, void *nxdata,
+                void *handle, void *buf, uint32_t count, uint64_t offset);
+  int (*pwrite) (struct nbdkit_next *next, void *nxdata,
+                 void *handle,
+                 const void *buf, uint32_t count, uint64_t offset);
+  int (*flush) (struct nbdkit_next *next, void *nxdata,
+                void *handle);
+  int (*trim) (struct nbdkit_next *next, void *nxdata,
+               void *handle, uint32_t count, uint64_t offset);
+  int (*zero) (struct nbdkit_next *next, 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/src/Makefile.am b/src/Makefile.am
index 1f05eab..6033fe5 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

@@ -47,13 +48,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/main.c b/src/main.c
index b3e6bad..4790c46 100644
--- a/src/main.c
+++ b/src/main.c
@@ -179,6 +179,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
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@
--
2.15.1