[Libguestfs] [PATCH nbdkit INCOMPLETE 3/6] filters: Implement --filter parameter to load filter(s).

Richard W.M. Jones rjones at redhat.com
Sun Jan 14 12:11:18 UTC 2018


---
 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




More information about the Libguestfs mailing list