pluginloading: add support for whitelisting based on plugin or source module name...
authorTim-Philipp Müller <tim.muller@collabora.co.uk>
Thu, 27 May 2010 11:36:10 +0000 (12:36 +0100)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Wed, 23 Jun 2010 16:56:51 +0000 (17:56 +0100)
This feature is primarily intended for use in plugin modules' unit tests.

Consider the following situation: gst-plugins-good is built against an
installed GStreamer core. An older version of gst-plugins-good is also
installed in that prefix, along with random other plugin modules. Now,
when doing 'make check' in the just-built gst-plugins-good tree, we
want to only load plugins from GStreamer core, gst-plugins-base, and
gst-plugins-good, but not random other modules (we don't want any unit
tests to fail just because some module in gst-plugins-bad has a broken
plugin_init, for example). Also, we want to only load gst-plugins-good
modules from the locally-built source tree, but not any of the older
gst-plugins-good modules installed. This is usually assured by loading
the ones in the source tree first (by adding that path first to the
right environment variables), but it gets tricky when plugins are
moved, removed, merged, or renamed, or the plugin filename changes.

Note that 'make check' should really work right without doing
'make install' or uninstalling the old gst-plugins-good package (or
any other gst-plugins-foo package) first.

Enter GST_PLUGIN_LOADING_WHITELIST. This environment variable may
contain source-package@path-prefix pairs separated by the platform
search path separator (G_SEARCHPATH_SEPARATOR_S). The source package
and path prefix are separated by the '@' character. The path prefix is
entirely optional, as is the '@' separator if no path is given.

It is also possible to filter based on plugin names instead of the name
of the source-package by specifying one or more plugin names separated
by commas before the optional path prefix.

In short, the following match patterns are possible:

   plugin1,plugin2@pathprefix or
   plugin1,plugin2@* or just
   plugin1,plugin2 or
   source-package@pathprefix or
   source-package@* or just
   source-package

So for our gst-plugins-good unit test example above, we  would set the
environment variable on *nix to something like this (will likely be a
relative path in practice):
gstreamer:gst-plugins-base:gst-plugins-good@/path/to/src/gst-plugins-good

Fixes #619815 and #619717.

gst/gst_private.h
gst/gstplugin.c

index 423e937..7ca9410 100644 (file)
@@ -73,6 +73,11 @@ struct _GstPluginPrivate {
   GstStructure *cache_data;
 };
 
+gboolean priv_gst_plugin_loading_have_whitelist (void);
+
+gboolean priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc,
+                                              const gchar   * filename);
+
 gboolean _priv_plugin_deps_env_vars_changed (GstPlugin * plugin);
 gboolean _priv_plugin_deps_files_changed (GstPlugin * plugin);
 
index 325a5b3..8a5b196 100644 (file)
@@ -72,6 +72,7 @@
 static guint _num_static_plugins;       /* 0    */
 static GstPluginDesc *_static_plugins;  /* NULL */
 static gboolean _gst_plugin_inited;
+static gchar **_plugin_loading_whitelist;       /* NULL */
 
 /* static variables for segfault handling of plugin loading */
 static char *_gst_plugin_fault_handler_filename = NULL;
@@ -329,10 +330,20 @@ gst_plugin_register_static_full (gint major_version, gint minor_version,
 void
 _gst_plugin_initialize (void)
 {
+  const gchar *whitelist;
   guint i;
 
   _gst_plugin_inited = TRUE;
 
+  whitelist = g_getenv ("GST_PLUGIN_LOADING_WHITELIST");
+  if (whitelist != NULL && *whitelist != '\0') {
+    _plugin_loading_whitelist = g_strsplit (whitelist,
+        G_SEARCHPATH_SEPARATOR_S, -1);
+    for (i = 0; _plugin_loading_whitelist[i] != NULL; ++i) {
+      GST_INFO ("plugins whitelist entry: %s", _plugin_loading_whitelist[i]);
+    }
+  }
+
   /* now register all static plugins */
   GST_INFO ("registering %u static plugins", _num_static_plugins);
   for (i = 0; i < _num_static_plugins; ++i) {
@@ -351,6 +362,107 @@ _gst_plugin_initialize (void)
   }
 }
 
+/* Whitelist entry format:
+ *
+ *   plugin1,plugin2@pathprefix or
+ *   plugin1,plugin2@* or just
+ *   plugin1,plugin2 or
+ *   source-package@pathprefix or
+ *   source-package@* or just
+ *   source-package
+ *
+ * ie. the bit before the path will be checked against both the plugin
+ * name and the plugin's source package name, to keep the format simple.
+ */
+static gboolean
+gst_plugin_desc_matches_whitelist_entry (GstPluginDesc * desc,
+    const gchar * filename, const gchar * pattern)
+{
+  const gchar *sep;
+  gboolean ret = FALSE;
+  gchar *name;
+
+  GST_LOG ("Whitelist pattern '%s', plugin: %s of %s@%s", pattern, desc->name,
+      desc->source, GST_STR_NULL (filename));
+
+  /* do we have a path prefix? */
+  sep = strchr (pattern, '@');
+  if (sep != NULL && strcmp (sep, "@*") != 0 && strcmp (sep, "@") != 0) {
+    /* paths are not canonicalised or treated with realpath() here. This
+     * should be good enough for our use case, since we just use the paths
+     * autotools uses, and those will be constructed from the same prefix. */
+    if (filename != NULL && !g_str_has_prefix (filename, sep + 1))
+      return FALSE;
+
+    GST_LOG ("%s matches path prefix %s", GST_STR_NULL (filename), sep + 1);
+  }
+
+  if (sep != NULL) {
+    name = g_strndup (pattern, (gsize) (sep - pattern));
+  } else {
+    name = g_strdup (pattern);
+  }
+
+  g_strstrip (name);
+  if (!g_ascii_isalnum (*name)) {
+    GST_WARNING ("Invalid whitelist pattern: %s", pattern);
+    goto done;
+  }
+
+  /* now check plugin names / source package name */
+  if (strchr (name, ',') == NULL) {
+    /* only a single name: either a plugin name or the source package name */
+    ret = (strcmp (desc->source, name) == 0 || strcmp (desc->name, name) == 0);
+  } else {
+    gchar **n, **names;
+
+    /* multiple names: assume these are plugin names */
+    names = g_strsplit (name, ",", -1);
+    for (n = names; n != NULL && *n != NULL; ++n) {
+      g_strstrip (*n);
+      if (strcmp (desc->name, *n) == 0) {
+        ret = TRUE;
+        break;
+      }
+    }
+    g_strfreev (names);
+  }
+
+  GST_LOG ("plugin / source package name match: %d", ret);
+
+done:
+
+  g_free (name);
+  return ret;
+}
+
+gboolean
+priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc,
+    const gchar * filename)
+{
+  gchar **entry;
+
+  if (_plugin_loading_whitelist == NULL)
+    return TRUE;
+
+  for (entry = _plugin_loading_whitelist; *entry != NULL; ++entry) {
+    if (gst_plugin_desc_matches_whitelist_entry (desc, filename, *entry)) {
+      GST_LOG ("Plugin %s is in whitelist", filename);
+      return TRUE;
+    }
+  }
+
+  GST_LOG ("Plugin %s (package %s, file %s) not in whitelist", desc->name,
+      desc->source, filename);
+  return FALSE;
+}
+
+gboolean
+priv_gst_plugin_loading_have_whitelist (void)
+{
+  return (_plugin_loading_whitelist != NULL);
+}
+
 /* this function could be extended to check if the plugin license matches the
  * applications license (would require the app to register its license somehow).
  * We'll wait for someone who's interested in it to code it :)
@@ -538,6 +650,7 @@ static GStaticMutex gst_plugin_loading_mutex = G_STATIC_MUTEX_INIT;
 GstPlugin *
 gst_plugin_load_file (const gchar * filename, GError ** error)
 {
+  GstPluginDesc *desc;
   GstPlugin *plugin;
   GModule *module;
   gboolean ret;
@@ -597,15 +710,6 @@ gst_plugin_load_file (const gchar * filename, GError ** error)
     goto return_error;
   }
 
-  if (new_plugin) {
-    plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL);
-    plugin->file_mtime = file_status.st_mtime;
-    plugin->file_size = file_status.st_size;
-    plugin->filename = g_strdup (filename);
-    plugin->basename = g_path_get_basename (filename);
-  }
-  plugin->module = module;
-
   ret = g_module_symbol (module, "gst_plugin_desc", &ptr);
   if (!ret) {
     GST_DEBUG ("Could not find plugin entry point in \"%s\"", filename);
@@ -616,7 +720,29 @@ gst_plugin_load_file (const gchar * filename, GError ** error)
     g_module_close (module);
     goto return_error;
   }
-  plugin->orig_desc = (GstPluginDesc *) ptr;
+
+  desc = (GstPluginDesc *) ptr;
+
+  if (priv_gst_plugin_loading_have_whitelist () &&
+      !priv_gst_plugin_desc_is_whitelisted (desc, filename)) {
+    GST_INFO ("Whitelist specified and plugin not in whitelist, not loading: "
+        "name=%s, package=%s, file=%s", desc->name, desc->source, filename);
+    g_set_error (error, GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE,
+        "Not loading plugin file \"%s\", not in whitelist", filename);
+    g_module_close (module);
+    goto return_error;
+  }
+
+  if (new_plugin) {
+    plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL);
+    plugin->file_mtime = file_status.st_mtime;
+    plugin->file_size = file_status.st_size;
+    plugin->filename = g_strdup (filename);
+    plugin->basename = g_path_get_basename (filename);
+  }
+
+  plugin->module = module;
+  plugin->orig_desc = desc;
 
   if (new_plugin) {
     /* check plugin description: complain about bad values but accept them, to