Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 0/5] Introduce filters.
Rebased filters patch. Requires current git master + the locks / thread model fix (https://www.redhat.com/archives/libguestfs/2018-January/msg00128.html) So a few changes here since last time: The "introduce filters" and "implement filters" patches are squashed together. I introduced a concept of .prepare and .finalize. These run before and after the data serving phase respectively. They're not a lot of use for plugins because you can just use .open and .close instead, and therefore I didn't expose them to plugins (only internally in the backend). However for filters they make more sense since they give you a place to prepare and finalize. They're necessary also for filters because filter.open could not call the next open method, save the (plugin) handle, then construct an nbdkit_next_ops object, and call pread. It _has_ to be split into two operations. This patch series is incomplete in at least three ways: - We still need a way for filter.open to call down to the next open function, so it can adjust the readonly flag (for copyonwrite filter, TBD). - Partition filter is obviously not complete. - Need to write a copyonwrite filter. Rich.
Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 1/5] backend: Add prepare and finalize methods.
These methods are called just before and just after the data serving phase respectively. They are unused in this commit, but make more sense when used from filters. --- src/connections.c | 37 +++++++++++++++++++++++++++++++++++++ src/internal.h | 2 ++ src/plugins.c | 18 ++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/connections.c b/src/connections.c index e225b5c..be19996 100644 --- a/src/connections.c +++ b/src/connections.c @@ -93,6 +93,8 @@ static struct connection *new_connection (int sockin, int sockout, static void free_connection (struct connection *conn); static int negotiate_handshake (struct connection *conn); static int recv_request_send_reply (struct connection *conn); +static int prepare (struct connection *conn); +static int finalize (struct connection *conn); /* Don't call these raw socket functions directly. Use conn->recv etc. */ static int raw_recv (struct connection *, void *buf, size_t len); @@ -250,6 +252,10 @@ _handle_single_connection (int sockin, int sockout) if (negotiate_handshake (conn) == -1) goto done; + /* Prepare for data serving phase. */ + if (prepare (conn) == -1) + goto done; + if (!nworkers) { /* No need for a separate thread. */ debug ("handshake complete, processing requests serially"); @@ -300,6 +306,10 @@ _handle_single_connection (int sockin, int sockout) free (workers); } + /* Finalize connection after data serving phase. */ + if (finalize (conn) == -1) + goto done; + r = get_status (conn); done: debug ("connection cleanup with final status %d", r); @@ -775,6 +785,33 @@ negotiate_handshake (struct connection *conn) return r; } +static int +prepare (struct connection *conn) +{ + int r; + + lock_request (conn); + r = backend->prepare (backend, conn); + unlock_request (conn); + + return r; +} + +static int +finalize (struct connection *conn) +{ + int r; + + lock_request (conn); + if (backend) + r = backend->finalize (backend, conn); + else + r = 0; + unlock_request (conn); + + return r; +} + static bool valid_range (struct connection *conn, uint64_t offset, uint32_t count) { diff --git a/src/internal.h b/src/internal.h index 8047b3b..dbcd89c 100644 --- a/src/internal.h +++ b/src/internal.h @@ -165,6 +165,8 @@ struct backend { void (*config_complete) (struct backend *); int (*errno_is_preserved) (struct backend *); int (*open) (struct backend *, struct connection *conn, int readonly); + int (*prepare) (struct backend *, struct connection *conn); + int (*finalize) (struct backend *, struct connection *conn); void (*close) (struct backend *, struct connection *conn); int64_t (*get_size) (struct backend *, struct connection *conn); int (*can_write) (struct backend *, struct connection *conn); diff --git a/src/plugins.c b/src/plugins.c index 137bae3..da11c2c 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -251,6 +251,22 @@ plugin_open (struct backend *b, struct connection *conn, int readonly) return 0; } +/* We don't expose .prepare and .finalize to plugins since they aren't + * necessary. Plugins can easily do the same work in .open and + * .close. + */ +static int +plugin_prepare (struct backend *b, struct connection *conn) +{ + return 0; +} + +static int +plugin_finalize (struct backend *b, struct connection *conn) +{ + return 0; +} + static void plugin_close (struct backend *b, struct connection *conn) { @@ -503,6 +519,8 @@ static struct backend plugin_functions = { .config_complete = plugin_config_complete, .errno_is_preserved = plugin_errno_is_preserved, .open = plugin_open, + .prepare = plugin_prepare, + .finalize = plugin_finalize, .close = plugin_close, .get_size = plugin_get_size, .can_write = plugin_can_write, -- 2.15.1
Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 2/5] Introduce filters.
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
Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 3/5] filters: Add nbdkit-offset-filter.
This very basic filter allows you to select an offset and range within a plugin, for example: nbdkit --filter=offset file file=foo offset=1M range=100M which serves the byte range [ 1M .. 101M-1 ] from file ‘foo’. --- TODO | 2 - configure.ac | 1 + filters/Makefile.am | 3 +- filters/offset/Makefile.am | 62 ++++++++++++++ filters/offset/nbdkit-offset-filter.pod | 99 +++++++++++++++++++++ filters/offset/offset.c | 147 ++++++++++++++++++++++++++++++++ 6 files changed, 311 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 0955db7..8eda0d7 100644 --- a/TODO +++ b/TODO @@ -44,8 +44,6 @@ Suggestions for filters * copy-on-write, a popular feature in other servers -* export a subset using offset/size - * export a single partition (like qemu-nbd -P) Composing nbdkit diff --git a/configure.ac b/configure.ac index 7032614..4892dc4 100644 --- a/configure.ac +++ b/configure.ac @@ -513,6 +513,7 @@ AC_CONFIG_FILES([Makefile plugins/vddk/Makefile plugins/xz/Makefile filters/Makefile + filters/offset/Makefile src/Makefile src/nbdkit.pc tests/Makefile]) diff --git a/filters/Makefile.am b/filters/Makefile.am index ed1580b..91fbe6c 100644 --- a/filters/Makefile.am +++ b/filters/Makefile.am @@ -30,4 +30,5 @@ # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. -#SUBDIRS +SUBDIRS = \ + offset diff --git a/filters/offset/Makefile.am b/filters/offset/Makefile.am new file mode 100644 index 0000000..f6e253c --- /dev/null +++ b/filters/offset/Makefile.am @@ -0,0 +1,62 @@ +# nbdkit +# Copyright (C) 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. + +EXTRA_DIST = nbdkit-offset-filter.pod + +CLEANFILES = *~ + +filterdir = $(libdir)/nbdkit/filters + +filter_LTLIBRARIES = nbdkit-offset-filter.la + +nbdkit_offset_filter_la_SOURCES = \ + offset.c \ + $(top_srcdir)/include/nbdkit-filter.h + +nbdkit_offset_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include +nbdkit_offset_filter_la_CFLAGS = \ + $(WARNINGS_CFLAGS) +nbdkit_offset_filter_la_LDFLAGS = \ + -module -avoid-version -shared + +if HAVE_POD2MAN + +man_MANS = nbdkit-offset-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-offset-filter.1: nbdkit-offset-filter.pod + $(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \ + if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \ + mv $@.t $@ + +endif diff --git a/filters/offset/nbdkit-offset-filter.pod b/filters/offset/nbdkit-offset-filter.pod new file mode 100644 index 0000000..131a321 --- /dev/null +++ b/filters/offset/nbdkit-offset-filter.pod @@ -0,0 +1,99 @@ +=encoding utf8 + +=head1 NAME + +nbdkit-offset-filter - nbdkit offset filter + +=head1 SYNOPSIS + + nbdkit --filter=offset plugin offset=OFFSET range=LENGTH [plugin-args...] + +=head1 DESCRIPTION + +C<nbdkit-offset-filter> is a filter that limits requests to the byte +range C<[offset .. offset+range-1]> within another plugin. + +=head1 PARAMETERS + +=over 4 + +=item B<offset=OFFSET> + +The start offset. + +This parameter is required. + +=item B<range=LENGTH> + +The length of data to serve. + +This is optional. If not given then the range is served starting from +the offset through to the end of the underlying file/device. + +=back + +Note it is an error if the range parameter is supplied and +C<offset+range> is larger than the size of data served by the +underlying plugin. + +=head1 EXAMPLE + +Using L<nbdkit-file-plugin(1)>, serve the file C<disk.img> starting at +offset C<1M>. The total length served is C<100M> (the underlying file +must therefore be at least C<101M> in length): + + nbdkit --filter=offset file file=disk.img offset=1M range=100M + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-file-plugin(1)>, +L<nbdkit-filter(3)>. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 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/filters/offset/offset.c b/filters/offset/offset.c new file mode 100644 index 0000000..cd0c89b --- /dev/null +++ b/filters/offset/offset.c @@ -0,0 +1,147 @@ +/* nbdkit + * Copyright (C) 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 <nbdkit-filter.h> + +static int64_t offset = -1, range = -1; + +/* Called for each key=value passed on the command line. */ +static int +offset_config (nbdkit_next_config *next, void *nxdata, + const char *key, const char *value) +{ + if (strcmp (key, "offset") == 0) { + offset = nbdkit_parse_size (value); + if (offset == -1) + return -1; + return 0; + } + else if (strcmp (key, "range") == 0) { + range = nbdkit_parse_size (value); + if (range == -1) + return -1; + return 0; + } + else + return next (nxdata, key, value); +} + +/* Check the user did pass both parameters. */ +static int +offset_config_complete (nbdkit_next_config_complete *next, void *nxdata) +{ + if (offset == -1) { + nbdkit_error ("you must supply the offset parameter on the command line"); + return -1; + } + + return next (nxdata); +} + +#define offset_config_help \ + "offset=<OFFSET> (required) The start offset to serve.\n" \ + "range=<LENGTH> The total size to serve." + +/* Get the file size. */ +static int64_t +offset_get_size (struct nbdkit_next *next, void *nxdata, + void *handle) +{ + int64_t real_size = next->get_size (nxdata); + + if (range >= 0) { + if (offset + range > real_size) { + nbdkit_error ("offset + range is larger than the real size of the underlying file or device"); + return -1; + } + return range; + } + else + return real_size - offset; +} + +/* Read data. */ +static int +offset_pread (struct nbdkit_next *next, void *nxdata, + void *handle, void *buf, uint32_t count, uint64_t offs) +{ + return next->pread (nxdata, buf, count, offs + offset); +} + +/* Write data. */ +static int +offset_pwrite (struct nbdkit_next *next, void *nxdata, + void *handle, + const void *buf, uint32_t count, uint64_t offs) +{ + return next->pwrite (nxdata, buf, count, offs + offset); +} + +/* Trim data. */ +static int +offset_trim (struct nbdkit_next *next, void *nxdata, + void *handle, uint32_t count, uint64_t offs) +{ + return next->trim (nxdata, count, offs + offset); +} + +/* Zero data. */ +static int +offset_zero (struct nbdkit_next *next, void *nxdata, + void *handle, uint32_t count, uint64_t offs, int may_trim) +{ + return next->zero (nxdata, count, offs + offset, may_trim); +} + +static struct nbdkit_filter filter = { + .name = "offset", + .longname = "nbdkit offset filter", + .version = PACKAGE_VERSION, + .config = offset_config, + .config_complete = offset_config_complete, + .config_help = offset_config_help, + .get_size = offset_get_size, + .pread = offset_pread, + .pwrite = offset_pwrite, + .trim = offset_trim, + .zero = offset_zero, +}; + +NBDKIT_REGISTER_FILTER(filter) -- 2.15.1
Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 4/5] filters: Move rdelay/wdelay from file plugin to new delay filter.
Previously the file plugin supported ‘rdelay’ and ‘wdelay’ parameters for injecting delays (for testing) into read and write requests. This moves the functionality to a new delay filter so that it can be used with any plugin. --- TODO | 3 - configure.ac | 1 + filters/Makefile.am | 1 + filters/delay/Makefile.am | 62 +++++++++++++ filters/delay/delay.c | 161 ++++++++++++++++++++++++++++++++++ filters/delay/nbdkit-delay-filter.pod | 88 +++++++++++++++++++ plugins/file/file.c | 76 ++-------------- plugins/file/nbdkit-file-plugin.pod | 14 +-- tests/test-parallel-file.sh | 4 +- tests/test-parallel-nbd.sh | 1 + 10 files changed, 324 insertions(+), 87 deletions(-) diff --git a/TODO b/TODO index 8eda0d7..3ec45fd 100644 --- a/TODO +++ b/TODO @@ -37,9 +37,6 @@ directed to qemu-nbd for these use cases. Suggestions for filters ----------------------- -* adding artificial delays (see wdelay/rdelay options in the file - plugin) - * injecting artificial errors for testing clients * copy-on-write, a popular feature in other servers diff --git a/configure.ac b/configure.ac index 4892dc4..9376a2e 100644 --- a/configure.ac +++ b/configure.ac @@ -513,6 +513,7 @@ AC_CONFIG_FILES([Makefile plugins/vddk/Makefile plugins/xz/Makefile filters/Makefile + filters/delay/Makefile filters/offset/Makefile src/Makefile src/nbdkit.pc diff --git a/filters/Makefile.am b/filters/Makefile.am index 91fbe6c..d4aa6c0 100644 --- a/filters/Makefile.am +++ b/filters/Makefile.am @@ -31,4 +31,5 @@ # SUCH DAMAGE. SUBDIRS = \ + delay \ offset diff --git a/filters/delay/Makefile.am b/filters/delay/Makefile.am new file mode 100644 index 0000000..5b20b69 --- /dev/null +++ b/filters/delay/Makefile.am @@ -0,0 +1,62 @@ +# nbdkit +# Copyright (C) 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. + +EXTRA_DIST = nbdkit-delay-filter.pod + +CLEANFILES = *~ + +filterdir = $(libdir)/nbdkit/filters + +filter_LTLIBRARIES = nbdkit-delay-filter.la + +nbdkit_delay_filter_la_SOURCES = \ + delay.c \ + $(top_srcdir)/include/nbdkit-filter.h + +nbdkit_delay_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include +nbdkit_delay_filter_la_CFLAGS = \ + $(WARNINGS_CFLAGS) +nbdkit_delay_filter_la_LDFLAGS = \ + -module -avoid-version -shared + +if HAVE_POD2MAN + +man_MANS = nbdkit-delay-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-delay-filter.1: nbdkit-delay-filter.pod + $(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \ + if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \ + mv $@.t $@ + +endif diff --git a/filters/delay/delay.c b/filters/delay/delay.c new file mode 100644 index 0000000..c8d9ef2 --- /dev/null +++ b/filters/delay/delay.c @@ -0,0 +1,161 @@ +/* nbdkit + * Copyright (C) 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 <time.h> + +#include <nbdkit-filter.h> + +static int rdelayms = 0; /* read delay (milliseconds) */ +static int wdelayms = 0; /* write delay (milliseconds) */ + +static int +parse_delay (const char *value) +{ + size_t len = strlen (value); + int r; + + if (len > 2 && strcmp (&value[len-2], "ms") == 0) { + if (sscanf (value, "%d", &r) == 1) + return r; + else { + nbdkit_error ("cannot parse rdelay/wdelay milliseconds parameter: %s", + value); + return -1; + } + } + else { + if (sscanf (value, "%d", &r) == 1) + return r * 1000; + else { + nbdkit_error ("cannot parse rdelay/wdelay seconds parameter: %s", + value); + return -1; + } + } +} + +static void +delay (int ms) +{ + if (ms > 0) { + const struct timespec ts = { + .tv_sec = ms / 1000, + .tv_nsec = (ms * 1000000) % 1000000000 + }; + nanosleep (&ts, NULL); + } +} + +static void +read_delay (void) +{ + delay (rdelayms); +} + +static void +write_delay (void) +{ + delay (wdelayms); +} + +/* Called for each key=value passed on the command line. */ +static int +delay_config (nbdkit_next_config *next, void *nxdata, + const char *key, const char *value) +{ + if (strcmp (key, "rdelay") == 0) { + rdelayms = parse_delay (value); + if (rdelayms == -1) + return -1; + return 0; + } + else if (strcmp (key, "wdelay") == 0) { + wdelayms = parse_delay (value); + if (wdelayms == -1) + return -1; + return 0; + } + else + return next (nxdata, key, value); +} + +#define delay_config_help \ + "rdelay=<NN>[ms] Read delay in seconds/milliseconds.\n" \ + "wdelay=<NN>[ms] Write delay in seconds/milliseconds." \ + +/* Read data. */ +static int +delay_pread (struct nbdkit_next *next, void *nxdata, + void *handle, void *buf, uint32_t count, uint64_t offset) +{ + read_delay (); + return next->pread (nxdata, buf, count, offset); +} + +/* Write data. */ +static int +delay_pwrite (struct nbdkit_next *next, void *nxdata, + void *handle, + const void *buf, uint32_t count, uint64_t offset) +{ + write_delay (); + return next->pwrite (nxdata, buf, count, offset); +} + +/* Zero data. */ +static int +delay_zero (struct nbdkit_next *next, void *nxdata, + void *handle, uint32_t count, uint64_t offset, int may_trim) +{ + write_delay (); + return next->zero (nxdata, count, offset, may_trim); +} + +static struct nbdkit_filter filter = { + .name = "delay", + .longname = "nbdkit delay filter", + .version = PACKAGE_VERSION, + .config = delay_config, + .config_help = delay_config_help, + .pread = delay_pread, + .pwrite = delay_pwrite, + .zero = delay_zero, +}; + +NBDKIT_REGISTER_FILTER(filter) diff --git a/filters/delay/nbdkit-delay-filter.pod b/filters/delay/nbdkit-delay-filter.pod new file mode 100644 index 0000000..10aba94 --- /dev/null +++ b/filters/delay/nbdkit-delay-filter.pod @@ -0,0 +1,88 @@ +=encoding utf8 + +=head1 NAME + +nbdkit-delay-filter - nbdkit delay filter + +=head1 SYNOPSIS + + nbdkit --filter=delay plugin rdelay=SECS wdelay=SECS [plugin-args...] + + nbdkit --filter=delay plugin rdelay=<NN>ms wdelay=<NN>ms [plugin-args...] + +=head1 DESCRIPTION + +C<nbdkit-delay-filter> is a filter that delays read and write requests +by some seconds or milliseconds. This is used to simulate a slow or +remote server, or to test certain kinds of race conditions in Linux. + +=head1 PARAMETERS + +=over 4 + +=item B<rdelay=SECS> + +=item B<rdelay=E<lt>NNE<gt>ms> + +The optional read delay in seconds or milliseconds. + +=item B<wdelay=SECS> + +=item B<wdelay=E<lt>NNE<gt>ms> + +The optional write delay in seconds or milliseconds. + +=back + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-filter(3)>. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 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/plugins/file/file.c b/plugins/file/file.c index 4a91251..1fe4191 100644 --- a/plugins/file/file.c +++ b/plugins/file/file.c @@ -40,7 +40,6 @@ #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> -#include <time.h> #include <errno.h> #include <nbdkit-plugin.h> @@ -50,8 +49,6 @@ #endif static char *filename = NULL; -static int rdelayms = 0; /* read delay (milliseconds) */ -static int wdelayms = 0; /* write delay (milliseconds) */ static void file_unload (void) @@ -59,56 +56,6 @@ file_unload (void) free (filename); } -static int -parse_delay (const char *value) -{ - size_t len = strlen (value); - int r; - - if (len > 2 && strcmp (&value[len-2], "ms") == 0) { - if (sscanf (value, "%d", &r) == 1) - return r; - else { - nbdkit_error ("cannot parse rdelay/wdelay milliseconds parameter: %s", - value); - return -1; - } - } - else { - if (sscanf (value, "%d", &r) == 1) - return r * 1000; - else { - nbdkit_error ("cannot parse rdelay/wdelay seconds parameter: %s", - value); - return -1; - } - } -} - -static void -delay (int ms) -{ - if (ms > 0) { - const struct timespec ts = { - .tv_sec = ms / 1000, - .tv_nsec = (ms * 1000000) % 1000000000 - }; - nanosleep (&ts, NULL); - } -} - -static void -read_delay (void) -{ - delay (rdelayms); -} - -static void -write_delay (void) -{ - delay (wdelayms); -} - /* Called for each key=value passed on the command line. This plugin * only accepts file=<filename>, which is required. */ @@ -122,15 +69,10 @@ file_config (const char *key, const char *value) if (!filename) return -1; } - else if (strcmp (key, "rdelay") == 0) { - rdelayms = parse_delay (value); - if (rdelayms == -1) - return -1; - } - else if (strcmp (key, "wdelay") == 0) { - wdelayms = parse_delay (value); - if (wdelayms == -1) - return -1; + else if (strcmp (key, "rdelay") == 0 || + strcmp (key, "wdelay") == 0) { + nbdkit_error ("add --filter=delay on the command line"); + return -1; } else { nbdkit_error ("unknown parameter '%s'", key); @@ -157,9 +99,7 @@ file_config_complete (void) } #define file_config_help \ - "file=<FILENAME> (required) The filename to serve.\n" \ - "rdelay=<NN>[ms] Read delay in seconds/milliseconds.\n" \ - "wdelay=<NN>[ms] Write delay in seconds/milliseconds." \ + "file=<FILENAME> (required) The filename to serve." \ /* The per-connection handle. */ struct handle { @@ -241,8 +181,6 @@ file_pread (void *handle, void *buf, uint32_t count, uint64_t offset) { struct handle *h = handle; - read_delay (); - while (count > 0) { ssize_t r = pread (h->fd, buf, count, offset); if (r == -1) { @@ -267,8 +205,6 @@ file_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset) { struct handle *h = handle; - write_delay (); - while (count > 0) { ssize_t r = pwrite (h->fd, buf, count, offset); if (r == -1) { @@ -292,8 +228,6 @@ file_zero (void *handle, uint32_t count, uint64_t offset, int may_trim) #endif int r = -1; - write_delay (); - #ifdef FALLOC_FL_PUNCH_HOLE if (may_trim) { r = fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod index a2d1e6a..016513a 100644 --- a/plugins/file/nbdkit-file-plugin.pod +++ b/plugins/file/nbdkit-file-plugin.pod @@ -31,21 +31,13 @@ This parameter is required. =item B<rdelay=E<lt>NNE<gt>ms> -Delay reads for C<SECS> seconds or C<NN> milliseconds. -This is used to simulate a slow or remote server, or to -test certain kinds of race conditions in Linux. - -The default is no delay. - =item B<wdelay=SECS> =item B<wdelay=E<lt>NNE<gt>ms> -Delay writes for C<SECS> seconds or C<NN> milliseconds. -This is used to simulate a slow or remote server, or to -test certain kinds of race conditions in Linux. - -The default is no delay. +These plugin parameters have been moved to the +L<nbdkit-delay-filter(1)> filter. Modify the command line to add +I<--filter=delay> in order to use these parameters. =back diff --git a/tests/test-parallel-file.sh b/tests/test-parallel-file.sh index 79a60ac..b9e5f40 100755 --- a/tests/test-parallel-file.sh +++ b/tests/test-parallel-file.sh @@ -49,7 +49,7 @@ $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c "aio_read -P 1 0 1" -c aio_flush \ trap 'rm -f test-parallel-file.out' 0 1 2 3 15 # With --threads=1, the write should complete first because it was issued first -nbdkit -v -t 1 -U - file file=file-data wdelay=2 rdelay=1 --run ' +nbdkit -v -t 1 -U - --filter delay file file=file-data wdelay=2 rdelay=1 --run ' $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c "aio_read -P 1 0 1" -c aio_flush $nbd ' | tee test-parallel-file.out if test "$(grep '1/1' test-parallel-file.out)" != \ @@ -59,7 +59,7 @@ read 1/1 bytes at offset 0"; then fi # With default --threads, the faster read should complete first -nbdkit -v -U - file file=file-data wdelay=2 rdelay=1 --run ' +nbdkit -v -U - --filter delay file file=file-data wdelay=2 rdelay=1 --run ' $QEMU_IO -f raw -c "aio_write -P 2 1 1" -c "aio_read -P 1 0 1" -c aio_flush $nbd ' | tee test-parallel-file.out if test "$(grep '1/1' test-parallel-file.out)" != \ diff --git a/tests/test-parallel-nbd.sh b/tests/test-parallel-nbd.sh index f8e5071..d87573d 100755 --- a/tests/test-parallel-nbd.sh +++ b/tests/test-parallel-nbd.sh @@ -54,6 +54,7 @@ trap 'rm -f test-parallel-nbd.out test-parallel-nbd.sock' 0 1 2 3 15 ( rm -f test-parallel-nbd.sock nbdkit --exit-with-parent -v -U test-parallel-nbd.sock \ + --filter delay \ file file=file-data wdelay=2 rdelay=1 & # With --threads=1, the write should complete first because it was issued first -- 2.15.1
Richard W.M. Jones
2018-Jan-19 15:23 UTC
[Libguestfs] [PATCH nbdkit filters-v2 5/5] INCOMPLETE filters: Add nbdkit-partition-filter.
This can be used to filter a single partition from a disk image. --- TODO | 2 - configure.ac | 1 + filters/Makefile.am | 3 +- filters/delay/delay.c | 12 +-- filters/offset/nbdkit-offset-filter.pod | 17 ++- filters/offset/offset.c | 20 ++-- filters/partition/Makefile.am | 62 +++++++++++ filters/partition/nbdkit-partition-filter.pod | 94 +++++++++++++++++ filters/partition/partition.c | 145 ++++++++++++++++++++++++++ 9 files changed, 336 insertions(+), 20 deletions(-) diff --git a/TODO b/TODO index 3ec45fd..b1f922e 100644 --- a/TODO +++ b/TODO @@ -41,8 +41,6 @@ Suggestions for filters * copy-on-write, a popular feature in other servers -* export a single partition (like qemu-nbd -P) - Composing nbdkit ---------------- diff --git a/configure.ac b/configure.ac index 9376a2e..fd71ca6 100644 --- a/configure.ac +++ b/configure.ac @@ -515,6 +515,7 @@ AC_CONFIG_FILES([Makefile filters/Makefile filters/delay/Makefile filters/offset/Makefile + filters/partition/Makefile src/Makefile src/nbdkit.pc tests/Makefile]) diff --git a/filters/Makefile.am b/filters/Makefile.am index d4aa6c0..d918b81 100644 --- a/filters/Makefile.am +++ b/filters/Makefile.am @@ -32,4 +32,5 @@ SUBDIRS = \ delay \ - offset + offset \ + partition diff --git a/filters/delay/delay.c b/filters/delay/delay.c index c8d9ef2..66b7e56 100644 --- a/filters/delay/delay.c +++ b/filters/delay/delay.c @@ -121,30 +121,30 @@ delay_config (nbdkit_next_config *next, void *nxdata, /* Read data. */ static int -delay_pread (struct nbdkit_next *next, void *nxdata, +delay_pread (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, void *buf, uint32_t count, uint64_t offset) { read_delay (); - return next->pread (nxdata, buf, count, offset); + return next_ops->pread (nxdata, buf, count, offset); } /* Write data. */ static int -delay_pwrite (struct nbdkit_next *next, void *nxdata, +delay_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, const void *buf, uint32_t count, uint64_t offset) { write_delay (); - return next->pwrite (nxdata, buf, count, offset); + return next_ops->pwrite (nxdata, buf, count, offset); } /* Zero data. */ static int -delay_zero (struct nbdkit_next *next, void *nxdata, +delay_zero (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, uint32_t count, uint64_t offset, int may_trim) { write_delay (); - return next->zero (nxdata, count, offset, may_trim); + return next_ops->zero (nxdata, count, offset, may_trim); } static struct nbdkit_filter filter = { diff --git a/filters/offset/nbdkit-offset-filter.pod b/filters/offset/nbdkit-offset-filter.pod index 131a321..a95c2a6 100644 --- a/filters/offset/nbdkit-offset-filter.pod +++ b/filters/offset/nbdkit-offset-filter.pod @@ -36,7 +36,7 @@ Note it is an error if the range parameter is supplied and C<offset+range> is larger than the size of data served by the underlying plugin. -=head1 EXAMPLE +=head1 EXAMPLES Using L<nbdkit-file-plugin(1)>, serve the file C<disk.img> starting at offset C<1M>. The total length served is C<100M> (the underlying file @@ -44,11 +44,26 @@ must therefore be at least C<101M> in length): nbdkit --filter=offset file file=disk.img offset=1M range=100M +One way to serve a single partition from a disk is to find the start +and length of the partition, eg using: + + $ parted disk.img -- unit b print + ... + Number Start End Size Type File system Flags + 1 65536B 104792575B 104727040B primary ext2 + +You can then serve the partition only using: + + nbdkit --filter=offset file file=disk.img offset=65536 range=104727040 + +An easier way to do this is with L<nbdkit-partition-filter(1)>. + =head1 SEE ALSO L<nbdkit(1)>, L<nbdkit-file-plugin(1)>, L<nbdkit-filter(3)>. +L<nbdkit-partition-filter(1)>. =head1 AUTHORS diff --git a/filters/offset/offset.c b/filters/offset/offset.c index cd0c89b..892d5ec 100644 --- a/filters/offset/offset.c +++ b/filters/offset/offset.c @@ -81,10 +81,10 @@ offset_config_complete (nbdkit_next_config_complete *next, void *nxdata) /* Get the file size. */ static int64_t -offset_get_size (struct nbdkit_next *next, void *nxdata, +offset_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle) { - int64_t real_size = next->get_size (nxdata); + int64_t real_size = next_ops->get_size (nxdata); if (range >= 0) { if (offset + range > real_size) { @@ -99,35 +99,35 @@ offset_get_size (struct nbdkit_next *next, void *nxdata, /* Read data. */ static int -offset_pread (struct nbdkit_next *next, void *nxdata, +offset_pread (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, void *buf, uint32_t count, uint64_t offs) { - return next->pread (nxdata, buf, count, offs + offset); + return next_ops->pread (nxdata, buf, count, offs + offset); } /* Write data. */ static int -offset_pwrite (struct nbdkit_next *next, void *nxdata, +offset_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, const void *buf, uint32_t count, uint64_t offs) { - return next->pwrite (nxdata, buf, count, offs + offset); + return next_ops->pwrite (nxdata, buf, count, offs + offset); } /* Trim data. */ static int -offset_trim (struct nbdkit_next *next, void *nxdata, +offset_trim (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, uint32_t count, uint64_t offs) { - return next->trim (nxdata, count, offs + offset); + return next_ops->trim (nxdata, count, offs + offset); } /* Zero data. */ static int -offset_zero (struct nbdkit_next *next, void *nxdata, +offset_zero (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, uint32_t count, uint64_t offs, int may_trim) { - return next->zero (nxdata, count, offs + offset, may_trim); + return next_ops->zero (nxdata, count, offs + offset, may_trim); } static struct nbdkit_filter filter = { diff --git a/filters/partition/Makefile.am b/filters/partition/Makefile.am new file mode 100644 index 0000000..0464e7c --- /dev/null +++ b/filters/partition/Makefile.am @@ -0,0 +1,62 @@ +# nbdkit +# Copyright (C) 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. + +EXTRA_DIST = nbdkit-partition-filter.pod + +CLEANFILES = *~ + +filterdir = $(libdir)/nbdkit/filters + +filter_LTLIBRARIES = nbdkit-partition-filter.la + +nbdkit_partition_filter_la_SOURCES = \ + partition.c \ + $(top_srcdir)/include/nbdkit-filter.h + +nbdkit_partition_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include +nbdkit_partition_filter_la_CFLAGS = \ + $(WARNINGS_CFLAGS) +nbdkit_partition_filter_la_LDFLAGS = \ + -module -avoid-version -shared + +if HAVE_POD2MAN + +man_MANS = nbdkit-partition-filter.1 +CLEANFILES += $(man_MANS) + +nbdkit-partition-filter.1: nbdkit-partition-filter.pod + $(POD2MAN) $(POD2MAN_ARGS) --section=1 --name=`basename $@ .1` $< $@.t && \ + if grep 'POD ERROR' $@.t; then rm $@.t; exit 1; fi && \ + mv $@.t $@ + +endif diff --git a/filters/partition/nbdkit-partition-filter.pod b/filters/partition/nbdkit-partition-filter.pod new file mode 100644 index 0000000..133e73d --- /dev/null +++ b/filters/partition/nbdkit-partition-filter.pod @@ -0,0 +1,94 @@ +=encoding utf8 + +=head1 NAME + +nbdkit-partition-filter - nbdkit partition filter + +=head1 SYNOPSIS + + nbdkit --filter=partition plugin partition=PART [plugin-args...] + +=head1 DESCRIPTION + +C<nbdkit-partition-filter> is a filter that limits requests to a +single partition within a disk image that is served by another plugin. + +Partition numbers are specified by the required C<partition> +parameter, and count from 1. + +This works like the C<qemu-nbd -P> option. + +=head1 PARAMETERS + +=over 4 + +=item B<partition=PART> + +The partition number to serve, counting from 1. + +This parameter is required. + +=back + +=head1 EXAMPLE + +F<disk.img> is a partitioned disk image (eg. a virtual machine disk +image). To serve the first partition only use: + + nbdkit --filter=partition file file=disk.img partition=1 + +=head1 SEE ALSO + +L<nbdkit(1)>, +L<nbdkit-file-plugin(1)>, +L<nbdkit-filter(3)>. +L<nbdkit-offset-filter(1)>, +L<parted(8)>. + +=head1 AUTHORS + +Richard W.M. Jones + +=head1 COPYRIGHT + +Copyright (C) 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/filters/partition/partition.c b/filters/partition/partition.c new file mode 100644 index 0000000..2e18173 --- /dev/null +++ b/filters/partition/partition.c @@ -0,0 +1,145 @@ +/* nbdkit + * Copyright (C) 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 <nbdkit-filter.h> + +static int partnum = -1; + +/* Called for each key=value passed on the command line. */ +static int +partition_config (nbdkit_next_config *next, void *nxdata, + const char *key, const char *value) +{ + if (strcmp (key, "partition") == 0) { + if (sscanf (value, "%d", &partnum) != 1 || partnum <= 0) { + nbdkit_error ("invalid partition number"); + return -1; + } + return 0; + } + else + return next (nxdata, key, value); +} + +/* Check the user did pass partition number. */ +static int +partition_config_complete (nbdkit_next_config_complete *next, void *nxdata) +{ + if (partnum == -1) { + nbdkit_error ("you must supply the partition parameter on the command line"); + return -1; + } + + return next (nxdata); +} + +#define partition_config_help \ + "partition=<PART> (required) The partition number (counting from 1)." + +#if 0 +/* Get the file size. */ +static int64_t +offset_get_size (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle) +{ + int64_t real_size = next_ops->get_size (nxdata); + + if (range >= 0) { + if (offset + range > real_size) { + nbdkit_error ("offset + range is larger than the real size of the underlying file or device"); + return -1; + } + return range; + } + else + return real_size - offset; +} + +/* Read data. */ +static int +offset_pread (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, void *buf, uint32_t count, uint64_t offs) +{ + return next_ops->pread (nxdata, buf, count, offs + offset); +} + +/* Write data. */ +static int +offset_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, + const void *buf, uint32_t count, uint64_t offs) +{ + return next_ops->pwrite (nxdata, buf, count, offs + offset); +} + +/* Trim data. */ +static int +offset_trim (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, uint32_t count, uint64_t offs) +{ + return next_ops->trim (nxdata, count, offs + offset); +} + +/* Zero data. */ +static int +offset_zero (struct nbdkit_next_ops *next_ops, void *nxdata, + void *handle, uint32_t count, uint64_t offs, int may_trim) +{ + return next_ops->zero (nxdata, count, offs + offset, may_trim); +} +#endif + +static struct nbdkit_filter filter = { + .name = "partition", + .longname = "nbdkit partition filter", + .version = PACKAGE_VERSION, + .config = partition_config, + .config_complete = partition_config_complete, + .config_help = partition_config_help, +#if 0 + .get_size = offset_get_size, + .pread = offset_pread, + .pwrite = offset_pwrite, + .trim = offset_trim, + .zero = offset_zero, +#endif +}; + +NBDKIT_REGISTER_FILTER(filter) -- 2.15.1
Eric Blake
2018-Jan-19 16:22 UTC
Re: [Libguestfs] [PATCH nbdkit filters-v2 2/5] Introduce filters.
On 01/19/2018 09:23 AM, Richard W.M. Jones wrote:> 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. > ---> +++ 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)How about a filter that intentionally disables WRITE_ZEROES and/or FUA support, if for no other reason than for testing sane fallbacks and comparing speed differences?> +++ b/docs/nbdkit-filter.pod > @@ -0,0 +1,501 @@ > +=encoding utf8 > + > +=head1 NAME > + > +nbdkit-filter - How to write nbdkit filters > + > +=head1 SYNOPSIS> +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.Should we mention that the rules on filter semantics are stricter than for plugins, and that unlike plugins, we don't guarantee stable cross-version API?> +=head1 CALLBACKS> +=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>.The rule on non-zero size matches plugins, but I'm not sure if there is a technical reason why we have that restriction. Down the road, when I get the upstream NBD resize proposal finalized, I can see the ability to create an image with size 0, then use resize to update it, as something that would be nice to support.> + > +=head2 C<.pread> > + > + int (*pread) (struct nbdkit_next_ops *next_ops, void *nxdata, > + void *handle, void *buf, uint32_t count, uint64_t offset);Wrong signature, missing the uint32_t flags that the backend interface has, and that I'm adding for plugins API_VERSION 2 for FUA support.> +=head2 C<.zero> > + > + int (*zero) (struct nbdkit_next_ops *next_ops, void *nxdata, > + void *handle, uint32_t count, uint64_t offset, int may_trim); > +And so on (uint32_t flags instead of int may_trim).> +=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.Would it ever make sense to allow a filter to intentionally request a lower concurrency level than the underlying plugin? At connection time, you'd compute the threading level as the lowest level requested by any element of the chain; it might make it easier to write plugins that aren't fully parallel (such filters may penalize the speed that can otherwise be achieved by using the plugin in isolation - but again, it may be useful for testing). That would imply adding a backend.thread_model() callback, which needs to be exposed to filters but not to plugins (plugins continue to use the #define at compile time); if the filter does not define the .thread_model callback, it defaults to fully-parallel; but if it does define the callback, it can result in something less concurrent.> +++ 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)>. > +Does it ever make sense to provide the same filter more than once on the command line? Or are we stating that a given filter can only be used once in the chain?> +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); > +};Your documentation matches your code, and did not pick up my potential changes to add 'uint32_t flags' everywhere. I'll reply shortly with what my rebase looks like on top of yours... -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Eric Blake
2018-Jan-19 16:23 UTC
[Libguestfs] [nbdkit PATCH] Update filters to support FUA flags.
From: "Richard W.M. Jones" <rjones@redhat.com> This patch may be worth squashing? --- docs/nbdkit-filter.pod | 65 +++++++++++++++++++++++++++++++++++++++++++------ include/nbdkit-filter.h | 29 ++++++++++++---------- src/filters.c | 55 ++++++++++++++++++++--------------------- 3 files changed, 101 insertions(+), 48 deletions(-) diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod index 75157ef..a050a1d 100644 --- a/docs/nbdkit-filter.pod +++ b/docs/nbdkit-filter.pod @@ -89,7 +89,12 @@ 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. +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> @@ -320,11 +325,15 @@ 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); + 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_ops->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>. @@ -333,11 +342,21 @@ message B<and> set C<errno>, then return C<-1>. int (*pwrite) (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, - const void *buf, uint32_t count, uint64_t offset); + 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_ops->pwrite> if C<next_ops->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_ops->pwrite> if C<next_ops->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>. @@ -345,35 +364,67 @@ 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); + void *handle, uint32_t flags); This intercepts the plugin C<.flush> method and can be used to modify flush requests. +At this time, flags will be 0 on input, and the filter should not pass +any flags to C<next_ops->flush>. + +This function will not be called if C<.can_flush> returned false; in +turn, the filter should not call C<next_ops->flush> if C<next_ops->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_ops *next_ops, void *nxdata, - void *handle, uint32_t count, uint64_t offset); + 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_ops->trim> if C<next_ops->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_ops->trim> if C<next_ops->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_ops *next_ops, void *nxdata, - void *handle, uint32_t count, uint64_t offset, int may_trim); + 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_ops->zero>, the filter may +pass C<NBDKIT_FLAG_MAY_TRIM> unconditionally, but may only pass +C<NBDKIT_FLAG_FUA> if C<next_ops->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_ops->zero> if C<next_ops->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>. +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_ops->zero> will have already +done a fallback to C<next_ops->write> rather than fail with that +particular error, and the fallback must not be performed more +than once). =head1 THREADS diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h index af79e33..c8b3c8c 100644 --- a/include/nbdkit-filter.h +++ b/include/nbdkit-filter.h @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2013-2017 Red Hat Inc. + * Copyright (C) 2013-2018 Red Hat Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,17 +59,18 @@ struct nbdkit_next_ops { 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); + 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 support filters compiled against + * 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. */ @@ -112,16 +113,18 @@ struct nbdkit_filter { void *handle); int (*pread) (struct nbdkit_next_ops *next_ops, void *nxdata, - void *handle, void *buf, uint32_t count, uint64_t offset); + void *handle, void *buf, uint32_t count, uint64_t offset, + uint32_t flags); int (*pwrite) (struct nbdkit_next_ops *next_ops, void *nxdata, void *handle, - const void *buf, uint32_t count, uint64_t offset); + const void *buf, uint32_t count, uint64_t offset, + uint32_t flags); int (*flush) (struct nbdkit_next_ops *next_ops, void *nxdata, - void *handle); + void *handle, uint32_t flags); int (*trim) (struct nbdkit_next_ops *next_ops, void *nxdata, - void *handle, uint32_t count, uint64_t offset); + void *handle, uint32_t count, uint64_t offset, uint32_t flags); int (*zero) (struct nbdkit_next_ops *next_ops, void *nxdata, - void *handle, uint32_t count, uint64_t offset, int may_trim); + void *handle, uint32_t count, uint64_t offset, uint32_t flags); }; #ifndef NBDKIT_CXX_LANG_C diff --git a/src/filters.c b/src/filters.c index 9a2022c..9768f20 100644 --- a/src/filters.c +++ b/src/filters.c @@ -280,43 +280,40 @@ next_can_trim (void *nxdata) } static int -next_pread (void *nxdata, void *buf, uint32_t count, uint64_t offset) +next_pread (void *nxdata, void *buf, uint32_t count, uint64_t offset, + uint32_t flags) { struct b_conn *b_conn = nxdata; - return b_conn->b->pread (b_conn->b, b_conn->conn, buf, count, offset, 0); + return b_conn->b->pread (b_conn->b, b_conn->conn, buf, count, offset, flags); } static int -next_pwrite (void *nxdata, const void *buf, uint32_t count, uint64_t offset) +next_pwrite (void *nxdata, const void *buf, uint32_t count, uint64_t offset, + uint32_t flags) { struct b_conn *b_conn = nxdata; - return b_conn->b->pwrite (b_conn->b, b_conn->conn, buf, count, offset, 0); + return b_conn->b->pwrite (b_conn->b, b_conn->conn, buf, count, offset, flags); } static int -next_flush (void *nxdata) +next_flush (void *nxdata, uint32_t flags) { struct b_conn *b_conn = nxdata; - return b_conn->b->flush (b_conn->b, b_conn->conn, 0); + return b_conn->b->flush (b_conn->b, b_conn->conn, flags); } static int -next_trim (void *nxdata, uint32_t count, uint64_t offset) +next_trim (void *nxdata, uint32_t count, uint64_t offset, uint32_t flags) { struct b_conn *b_conn = nxdata; - return b_conn->b->trim (b_conn->b, b_conn->conn, count, offset, 0); + return b_conn->b->trim (b_conn->b, b_conn->conn, count, offset, flags); } static int -next_zero (void *nxdata, uint32_t count, uint64_t offset, int may_trim) +next_zero (void *nxdata, uint32_t count, uint64_t offset, uint32_t flags) { 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); + return b_conn->b->zero (b_conn->b, b_conn->conn, count, offset, flags); } static struct nbdkit_next_ops next_ops = { @@ -448,11 +445,12 @@ filter_pread (struct backend *b, struct connection *conn, assert (flags == 0); - debug ("pread count=%" PRIu32 " offset=%" PRIu64, count, offset); + debug ("pread count=%" PRIu32 " offset=%" PRIu64 " flags=0x%" PRIx32, + count, offset, flags); if (f->filter.pread) return f->filter.pread (&next_ops, &nxdata, handle, - buf, count, offset); + buf, count, offset, flags); else return f->backend.next->pread (f->backend.next, conn, buf, count, offset, flags); @@ -470,12 +468,12 @@ filter_pwrite (struct backend *b, struct connection *conn, assert (!(flags & ~NBDKIT_FLAG_FUA)); - debug ("pwrite count=%" PRIu32 " offset=%" PRIu64 " fua=%d", - count, offset, fua); + debug ("pwrite count=%" PRIu32 " offset=%" PRIu64 " flags=0x%" PRIx32, + count, offset, flags); if (f->filter.pwrite) return f->filter.pwrite (&next_ops, &nxdata, handle, - buf, count, offset); + buf, count, offset, flags); else return f->backend.next->pwrite (f->backend.next, conn, buf, count, offset, flags); @@ -490,10 +488,10 @@ filter_flush (struct backend *b, struct connection *conn, uint32_t flags) assert (flags == 0); - debug ("flush"); + debug ("flush flags=0x%" PRIx32, flags); if (f->filter.flush) - return f->filter.flush (&next_ops, &nxdata, handle); + return f->filter.flush (&next_ops, &nxdata, handle, flags); else return f->backend.next->flush (f->backend.next, conn, flags); } @@ -509,10 +507,12 @@ filter_trim (struct backend *b, struct connection *conn, assert (flags == 0); - debug ("trim count=%" PRIu32 " offset=%" PRIu64, count, offset); + debug ("trim count=%" PRIu32 " offset=%" PRIu64 " flags=0x%" PRIx32, + count, offset, flags); if (f->filter.trim) - return f->filter.trim (&next_ops, &nxdata, handle, count, offset); + return f->filter.trim (&next_ops, &nxdata, handle, count, offset, + flags); else return f->backend.next->trim (f->backend.next, conn, count, offset, flags); } @@ -524,16 +524,15 @@ filter_zero (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 }; - 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); + debug ("zero count=%" PRIu32 " offset=%" PRIu64 " flags=0x%" PRIx32, + count, offset, flags); if (f->filter.zero) return f->filter.zero (&next_ops, &nxdata, handle, - count, offset, may_trim); + count, offset, flags); else return f->backend.next->zero (f->backend.next, conn, count, offset, flags); -- 2.14.3
Eric Blake
2018-Jan-19 16:39 UTC
Re: [Libguestfs] [PATCH nbdkit filters-v2 3/5] filters: Add nbdkit-offset-filter.
On 01/19/2018 09:23 AM, Richard W.M. Jones wrote:> This very basic filter allows you to select an offset and range within > a plugin, for example: > > nbdkit --filter=offset file file=foo offset=1M range=100M > > which serves the byte range [ 1M .. 101M-1 ] from file ‘foo’.I wonder - would it be possible to write a filter that performs concatenation? I know we don't support multiple uses of a plugin in the same nbdkit process, but it might be possible to have a filter that allows '0..m-1' to forward to the normal plugin, and 'm..n-1' to forward to a forked nbdkit started on the nbd plugin wrapping the second use of the normal plugin (ie. you can't concatenate two uses of the nbd plugin, but you CAN concatenate the nbd plugin and any other plugin, with the end appearance of using the file plugin twice from a single nbdkit command line). Anyways, that's food for another patch, and I only thought about it as the inverse of what this filter is doing.> +=over 4 > + > +=item B<offset=OFFSET> > + > +The start offset. > + > +This parameter is required.Can we support an implicit offset=0 if range= is provided (that is, the filter requires that at least one parameter appears)? Can we support negative offsets as meaning relative to the end of the underlying file?> + > +=item B<range=LENGTH> > + > +The length of data to serve. > + > +This is optional. If not given then the range is served starting from > +the offset through to the end of the underlying file/device.Should we support negative ranges as a way to trim off that much of the tail of the file?> + > +=back > + > +Note it is an error if the range parameter is supplied and > +C<offset+range> is larger than the size of data served by the > +underlying plugin.Is it also an error if offset exceeds the size of the file? Is there a technical limitation why range has to be non-zero, or can we allow serving a 0-length file (may be relevant later on when resize support comes into play)? -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Eric Blake
2018-Jan-19 16:53 UTC
Re: [Libguestfs] [PATCH nbdkit filters-v2 5/5] INCOMPLETE filters: Add nbdkit-partition-filter.
On 01/19/2018 09:23 AM, Richard W.M. Jones wrote:> This can be used to filter a single partition from a disk image. > --- > TODO | 2 - > configure.ac | 1 + > filters/Makefile.am | 3 +- > filters/delay/delay.c | 12 +-- > filters/offset/nbdkit-offset-filter.pod | 17 ++- > filters/offset/offset.c | 20 ++-- > filters/partition/Makefile.am | 62 +++++++++++ > filters/partition/nbdkit-partition-filter.pod | 94 +++++++++++++++++ > filters/partition/partition.c | 145 ++++++++++++++++++++++++++ > 9 files changed, 336 insertions(+), 20 deletions(-) > > diff --git a/TODO b/TODO > index 3ec45fd..b1f922e 100644 > --- a/TODO> +++ b/filters/delay/delay.c > @@ -121,30 +121,30 @@ delay_config (nbdkit_next_config *next, void *nxdata, > > /* Read data. */ > static int > -delay_pread (struct nbdkit_next *next, void *nxdata, > +delay_pread (struct nbdkit_next_ops *next_ops, void *nxdata, > void *handle, void *buf, uint32_t count, uint64_t offset)Squashed into the wrong patch?> +++ b/filters/offset/offset.c > @@ -81,10 +81,10 @@ offset_config_complete (nbdkit_next_config_complete *next, void *nxdata) > > /* Get the file size. */ > static int64_t > -offset_get_size (struct nbdkit_next *next, void *nxdata, > +offset_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,Again, rebase churn not squashed correctly?> +++ b/filters/partition/partition.c > @@ -0,0 +1,145 @@> +/* Called for each key=value passed on the command line. */ > +static int > +partition_config (nbdkit_next_config *next, void *nxdata, > + const char *key, const char *value) > +{ > + if (strcmp (key, "partition") == 0) { > + if (sscanf (value, "%d", &partnum) != 1 || partnum <= 0) {I'm not a fan of scanf("%d") - it has undefined behavior on integer overflow.> +#define partition_config_help \ > + "partition=<PART> (required) The partition number (counting from 1)." > + > +#if 0Hence the RFC :) -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Eric Blake
2018-Jan-19 20:47 UTC
Re: [Libguestfs] [PATCH nbdkit filters-v2 2/5] Introduce filters.
On 01/19/2018 09:23 AM, Richard W.M. Jones wrote:> 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. > ---> include/Makefile.am | 4 +- > include/nbdkit-filter.h | 149 +++++++++++ > include/nbdkit-plugin.h | 2 +> +++ b/include/nbdkit-filter.h> + > +#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>Should we stick the useful functions in a new header <nbdkit-common.h> that both nbdkit-plugin.h and nbdkit-filter.h include? I'm worried that the games we play with NBDKIT_API_VERSION in nbdkit-plugin.h for maintaining back-compat there may someday have awkward interactions in this file.> + > +#ifdef __cplusplus > +extern "C" { > +#endif > + > +#define NBDKIT_FILTER_API_VERSION 1On the other hand, you're setting NBDKIT_FILTER_API_VERSION separately from NBDKIT_API_VERSION (for plugins), as we could bump one but not the other in a given nbdkit release (and if so, it makes for our next release to have PLUGIN version 2 but FILTER version 1, once filters and FUA flags settle, where the highest version in each file shares the same backend semantics). Does it ever make sense for a single .so file to call both NBDKIT_REGISTER_PLUGIN() and NBDKIT_REGISTER_FILTER() in the same library? If not, one benefit of having the common code in a separate nbdkit-common.h is that we could do things like: nbdkit-common.h: #if !defined NBDKIT_PLUGIN_H || !defined NBDKIT_FILTER_H #error this file should not be directly included #endif nbdkit-plugin.h: #ifdef NBDKIT_FILTER_H #error cannot mix filter and plugin in one code base #endif nbdkit-filter.h: #ifdef NBDKIT_PLUGIN_H #error cannot mix filter and plugin in one code base #endif> + > +#ifndef NBDKIT_CXX_LANG_C > +#ifdef __cplusplus > +#define NBDKIT_CXX_LANG_C extern "C" > +#else > +#define NBDKIT_CXX_LANG_C /* nothing */ > +#endif > +#endifand a common header would be a nicer place for things like NBDKIT_CXX_LANG_C, rather than having to repeat our #ifndef definitions in two files. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org
Possibly Parallel Threads
- [PATCH nbdkit filters-v3 0/7] Introduce filters.
- [nbdkit PATCH v3 00/15] Add FUA support to nbdkit
- [nbdkit PATCH 0/2] RFC: tweak error handling, add log filter
- [PATCH nbdkit] server: Implement extents/can_extents calls for plugins and filters.
- [PATCH 0/9] Add filters to nbdkit.