From: "Richard W.M. Jones" <rjones(a)redhat.com>
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.
Message-Id: <20180117205356.8699-7-rjones(a)redhat.com>
[eblake: update for FUA flag support]
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
Makefile.am | 2 +-
TODO | 17 +-
configure.ac | 3 +-
docs/Makefile.am | 9 +-
docs/nbdkit-filter.pod | 528 ++++++++++++++++++++++++++++++++++++++++++++++++
docs/nbdkit-plugin.pod | 3 +-
docs/nbdkit.pod | 3 +-
filters/Makefile.am | 33 +++
include/Makefile.am | 4 +-
include/nbdkit-filter.h | 147 ++++++++++++++
include/nbdkit-plugin.h | 2 +
src/Makefile.am | 5 +-
src/main.c | 1 +
src/nbdkit.pc.in | 1 +
14 files changed, 736 insertions(+), 22 deletions(-)
create mode 100644 docs/nbdkit-filter.pod
create mode 100644 filters/Makefile.am
create mode 100644 include/nbdkit-filter.h
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..330b8a4
--- /dev/null
+++ b/docs/nbdkit-filter.pod
@@ -0,0 +1,528 @@
+=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, must be fully thread safe, and have
+tighter rules regarding what callbacks may do. While there is a
+guarantee that plugins written against an older version of nbdkit will
+still work with newer versions, filters do not have the same stability
+guarantee, and nbdkit may refuse to use a filter that was compiled
+against a different version rather than risk misbehavior.
+
+=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,
+ uint32_t flags);
+
+This intercepts the plugin C<.pread> method and can be used to read or
+modify data read by the plugin.
+
+At this time, flags will be 0 on input, and the filter should not pass
+any flags to C<next->pread>.
+
+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,
+ uint32_t flags);
+
+This intercepts the plugin C<.pwrite> method and can be used to modify
+data written by the plugin.
+
+At this time, flags may include C<NBDKIT_FLAG_FUA> on input based on
+the result of C<.can_flush>. In turn, the filter may only pass
+C<NBDKIT_FLAG_FUA> on to C<next->pwrite> if C<next->can_flush>
+returned true.
+
+This function will not be called if C<.can_write> returned false; in
+turn, the filter should not call C<next->pwrite> if C<next->can_write>
+did not return true.
+
+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, uint32_t flags);
+
+This intercepts the plugin C<.flush> method and can be used to modify
+flush requests. This will only b
+
+At this time, flags will be 0 on input, and the filter should not pass
+any flags to C<next->flush>.
+
+This function will not be called if C<.can_flush> returned false; in
+turn, the filter should not call C<next->flush> if C<next->can_flush>
+did not return true.
+
+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, uint32_t flags);
+
+This intercepts the plugin C<.trim> method and can be used to modify
+trim requests.
+
+At this time, flags may include C<NBDKIT_FLAG_FUA> on input based on
+the result of C<.can_flush>. In turn, the filter may only pass
+C<NBDKIT_FLAG_FUA> on to C<next->trim> if C<next->can_flush>
+returned true.
+
+This function will not be called if C<.can_trim> returned false; in
+turn, the filter should not call C<next->trim> if C<next->can_trim>
+did not return true.
+
+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,
+ uint32_t flags);
+
+This intercepts the plugin C<.zero> method and can be used to modify
+zero requests.
+
+At this time, flags may include C<NBDKIT_FLAG_MAY_TRIM>
+unconditionally, and C<NBDKIT_FLAG_FUA> based on the result of
+C<.can_flush>. In turn, when calling C<next->zero>, the filter may
+pass C<NBDKIT_FLAG_MAY_TRIM> unconditionally, but may only pass
+C<NBDKIT_FLAG_FUA> if C<next->can_flush> returned true.
+
+This function will not be called if C<.can_write> returned false; in
+turn, the filter should not call C<next->zero> if C<next->can_write>
+did not return true.
+
+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>; however,
+unlike plugins, this function must not return the C<EOPNOTSUPP>
+error (the code guarantees that C<next->zero> will have already
+done a fallback to C<next->write> rather than fail with that
+particular error, and the fallback must not be performed more
+than once).
+
+=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..27d2b2c
--- /dev/null
+++ b/include/nbdkit-filter.h
@@ -0,0 +1,147 @@
+/* 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.
+ */
+
+/* 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,
+ uint32_t flags);
+ int (*pwrite) (void *nxdata, const void *buf, uint32_t count,
+ uint64_t offset, uint32_t flags);
+ int (*flush) (void *nxdata, uint32_t flags);
+ int (*trim) (void *nxdata, uint32_t count, uint64_t offset, uint32_t flags);
+ int (*zero) (void *nxdata, uint32_t count, uint64_t offset, uint32_t flags);
+};
+
+struct nbdkit_filter {
+ /* Do not set these fields directly; use NBDKIT_REGISTER_FILTER.
+ * They exist so that we can recognize 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,
+ uint32_t flags);
+ int (*pwrite) (struct nbdkit_next *next, void *nxdata,
+ void *handle,
+ const void *buf, uint32_t count, uint64_t offset,
+ uint32_t flags);
+ int (*flush) (struct nbdkit_next *next, void *nxdata,
+ void *handle, uint32_t flags);
+ int (*trim) (struct nbdkit_next *next, void *nxdata,
+ void *handle, uint32_t count, uint64_t offset, uint32_t flags);
+ int (*zero) (struct nbdkit_next *next, void *nxdata,
+ void *handle, uint32_t count, uint64_t offset, uint32_t flags);
+};
+
+#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.14.3