---
docs/nbdkit.pod | 21 ++++++++-
nbdkit.in | 17 ++++++-
src/internal.h | 4 +-
src/main.c | 53 +++++++++++++++++++++-
src/plugins.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
5 files changed, 223 insertions(+), 10 deletions(-)
diff --git a/docs/nbdkit.pod b/docs/nbdkit.pod
index 3b37db8..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
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/internal.h b/src/internal.h
index 73bc09e..86cb0aa 100644
--- a/src/internal.h
+++ b/src/internal.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
@@ -40,6 +40,7 @@
#include <pthread.h>
#include "nbdkit-plugin.h"
+#include "nbdkit-filter.h"
#ifdef __APPLE__
#define UNIX_PATH_MAX 104
@@ -142,6 +143,7 @@ extern int crypto_negotiate_tls (struct connection *conn, int sockin,
int sockou
#define debug nbdkit_debug
/* plugins.c */
+extern void filter_register (const char *_filename, void *_dl, struct nbdkit_filter
*(*filter_init) (void));
extern void plugin_register (const char *_filename, void *_dl, struct nbdkit_plugin
*(*plugin_init) (void));
extern void plugin_cleanup (void);
extern const char *plugin_name (void);
diff --git a/src/main.c b/src/main.c
index 9b66d55..f8c46b0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -65,6 +65,7 @@
static int is_short_name (const char *);
static char *make_random_fifo (void);
static void open_plugin_so (const char *filename, int short_name);
+static void open_filter_so (const char *filename, int short_name);
static void start_serving (void);
static void set_up_signals (void);
static void run_command (void);
@@ -117,6 +118,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' },
@@ -151,7 +153,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"
@@ -242,6 +244,9 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
#endif
}
+ else if (strcmp (long_options[option_index].name, "filter") == 0) {
+ open_filter_so (optarg, is_short_name (optarg));
+ }
else if (strcmp (long_options[option_index].name, "run") == 0) {
if (socket_activation) {
fprintf (stderr, "%s: cannot use socket activation with --run
flag\n",
@@ -571,7 +576,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)
{
@@ -654,6 +659,50 @@ open_plugin_so (const char *name, int short_name)
free (filename);
}
+static void
+open_filter_so (const char *name, int short_name)
+{
+ 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. */
+ filter_register (filename, dl, filter_init);
+
+ if (free_filename)
+ free (filename);
+}
+
static void
start_serving (void)
{
diff --git a/src/plugins.c b/src/plugins.c
index 9b5d2d5..3600293 100644
--- a/src/plugins.c
+++ b/src/plugins.c
@@ -1,5 +1,5 @@
/* nbdkit
- * Copyright (C) 2013 Red Hat Inc.
+ * Copyright (C) 2013-2018 Red Hat Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -44,8 +44,20 @@
#include <dlfcn.h>
#include "nbdkit-plugin.h"
+#include "nbdkit-filter.h"
#include "internal.h"
+/* If there are filters then ‘filters’ below will point to a linked
+ * list of filters in the order in which they must be applied to
+ * requests.
+ */
+struct filter {
+ struct filter *next;
+ char *filename;
+ void *dl;
+ struct nbdkit_filter filter;
+};
+
static pthread_mutex_t connection_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t all_requests_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_rwlock_t unload_prevention_lock = PTHREAD_RWLOCK_INITIALIZER;
@@ -53,13 +65,111 @@ static pthread_rwlock_t unload_prevention_lock =
PTHREAD_RWLOCK_INITIALIZER;
/* Maximum read or write request that we will handle. */
#define MAX_REQUEST_SIZE (64 * 1024 * 1024)
-/* Currently the server can only load one plugin (see TODO). Hence we
- * can just use globals to store these.
+/* The server can only load one plugin. Hence we can just use globals
+ * to store these.
*/
+static struct filter *filters = NULL, *last_filter = NULL;
static char *filename;
static void *dl;
static struct nbdkit_plugin plugin;
+void
+filter_register (const char *_filename,
+ void *_dl, struct nbdkit_filter *(*filter_init) (void))
+{
+ const struct nbdkit_filter *_filter;
+ struct filter *f;
+ size_t i, len, size;
+
+ /* Allocate new entry in the linked list of filters. */
+ f = malloc (sizeof (*f));
+ if (f == NULL) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ if (last_filter)
+ last_filter->next = f;
+ else
+ filters = f;
+ last_filter = f;
+
+ f->next = NULL;
+ f->filename = strdup (_filename);
+ if (f->filename == NULL) {
+ perror ("strdup");
+ exit (EXIT_FAILURE);
+ }
+ 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 ();
+}
+
void
plugin_register (const char *_filename,
void *_dl, struct nbdkit_plugin *(*plugin_init) (void))
@@ -74,7 +184,7 @@ plugin_register (const char *_filename,
}
dl = _dl;
- debug ("registering %s", filename);
+ debug ("registering plugin %s", filename);
/* Call the initialization function which returns the address of the
* plugin's own 'struct nbdkit_plugin'.
@@ -150,7 +260,7 @@ plugin_register (const char *_filename,
exit (EXIT_FAILURE);
}
- debug ("registered %s (name %s)", filename, plugin.name);
+ debug ("registered plugin %s (name %s)", filename, plugin.name);
/* Call the on-load callback if it exists. */
debug ("%s: load", filename);
@@ -158,6 +268,22 @@ plugin_register (const char *_filename,
plugin.load ();
}
+static void
+cleanup_filters (struct filter *f)
+{
+ if (f) {
+ cleanup_filters (f->next);
+
+ debug ("%s: unload", f->filename);
+ if (f->filter.unload)
+ f->filter.unload ();
+
+ dlclose (f->dl);
+ free (f->filename);
+ free (f);
+ }
+}
+
void
plugin_cleanup (void)
{
@@ -176,6 +302,8 @@ plugin_cleanup (void)
free (filename);
filename = NULL;
+ cleanup_filters (filters);
+
pthread_rwlock_unlock (&unload_prevention_lock);
}
}
--
2.15.1