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 | 510 ++++++++++++++++++++++++++++++++++++ docs/nbdkit-plugin.pod | 3 +- docs/nbdkit.pod | 24 +- filters/Makefile.am | 33 +++ include/Makefile.am | 3 +- include/nbdkit-filter.h | 143 ++++++++++ nbdkit.in | 17 +- src/Makefile.am | 6 +- src/filters.c | 679 ++++++++++++++++++++++++++++++++++++++++++++++++ src/internal.h | 21 +- src/main.c | 115 +++++++- src/nbdkit.pc.in | 1 + src/plugins.c | 11 +- 17 files changed, 1555 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..cec60c6 --- /dev/null +++ b/docs/nbdkit-filter.pod @@ -0,0 +1,510 @@ +=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 three function types +(C<nbdkit_next_config>, C<nbdkit_next_config_complete>, +C<nbdkit_next_open>) 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<.limit_thread_model> + + int (*limit_thread_model) (void); + +A filter may define this to make the plugin thread model more limited +if, for some reason, the filter is unable to handle parallel requests. +The callback, if defined, should return one of the +C<NBDKIT_THREAD_MODEL_*> values (see L<nbdkit-plugin(3)>). + +The final thread model is the smallest (ie. most serialized) out of +all the filters and the plugin. Filters cannot alter the thread model +to make it larger (more parallel). + +If this callback is not defined then the filter must be prepared to +handle fully parallel requests (like C<NBDKIT_THREAD_MODEL_PARALLEL>), +even multiple requests issued in parallel on the same connection. + +=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) (nbdkit_next_open *next, void *nxdata, + 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 +after opening the connection (C<.prepare>) or just before closing the +connection (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 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 bc8adff..eabe66f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -32,4 +32,5 @@ include_HEADERS = \ nbdkit-common.h \ - nbdkit-plugin.h + nbdkit-plugin.h \ + nbdkit-filter.h diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h new file mode 100644 index 0000000..0dbe4fa --- /dev/null +++ b/include/nbdkit-filter.h @@ -0,0 +1,143 @@ +/* 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 + +#include <nbdkit-common.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); +typedef int nbdkit_next_open (void *nxdata, + int readonly); + +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 (*limit_thread_model) (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) (nbdkit_next_open *next, void *nxdata, + 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); +}; + +#define NBDKIT_REGISTER_FILTER(filter) \ + NBDKIT_CXX_LANG_C \ + struct nbdkit_filter * \ + filter_init (void) \ + { \ + (filter)._struct_size = sizeof (filter); \ + (filter)._api_version = NBDKIT_FILTER_API_VERSION; \ + return &(filter); \ + } + +#ifdef __cplusplus +} +#endif + +#endif /* NBDKIT_FILTER_H */ 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..45c7794 --- /dev/null +++ b/src/filters.c @@ -0,0 +1,679 @@ +/* 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; +}; + +/* 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; +}; + +/* 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); +} + +static int +filter_thread_model (struct backend *b) +{ + struct backend_filter *f = container_of (b, struct backend_filter, backend); + int filter_thread_model = NBDKIT_THREAD_MODEL_PARALLEL; + int thread_model; + + if (f->filter.limit_thread_model) + filter_thread_model = f->filter.limit_thread_model (); + + thread_model = f->backend.next->thread_model (f->backend.next); + if (filter_thread_model < thread_model) /* more serialized */ + thread_model = filter_thread_model; + + return thread_model; +} + +/* These are actually passing through to the final plugin, hence + * the function names. + */ +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 +next_open (void *nxdata, int readonly) +{ + struct b_conn *b_conn = nxdata; + + return b_conn->b->open (b_conn->b, b_conn->conn, readonly); +} + +static int +filter_open (struct backend *b, struct connection *conn, int readonly) +{ + struct backend_filter *f = container_of (b, struct backend_filter, backend); + struct b_conn nxdata = { .b = f->backend.next, .conn = conn }; + void *handle; + + debug ("%s: open readonly=%d", f->filename, readonly); + + if (f->filter.open) { + handle = f->filter.open (next_open, &nxdata, readonly); + if (handle == NULL) + return -1; + connection_set_handle (conn, f->backend.i, handle); + return 0; + } + else + 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’). + */ + +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 = filter_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