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