gstreamer/gst/gstplugin.c
Tim-Philipp Müller bb48c4c9f6 plugin: load plugins with unknown license strings
We shouldn't fail to load plugins just because we don't
recognise the license string. It's not our job to validate
licenses, and the license list is outdated and ambiguous
anyway.

Also add MPL-2.0 to the list, and fix some defunct license
URLs in the code comments.

Get rid of the hard-coded skip index, doesn't really buy us
much versus just skipping with a few strlens, and is harder
to maintain.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/864>
2021-08-10 14:10:22 +01:00

1949 lines
58 KiB
C

/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
*
* gstplugin.c: Plugin subsystem for loading elements, types, and libs
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gstplugin
* @title: GstPlugin
* @short_description: Container for features loaded from a shared object module
* @see_also: #GstPluginFeature, #GstElementFactory
*
* GStreamer is extensible, so #GstElement instances can be loaded at runtime.
* A plugin system can provide one or more of the basic GStreamer
* #GstPluginFeature subclasses.
*
* A plugin should export a symbol `gst_plugin_desc` that is a
* struct of type #GstPluginDesc.
* the plugin loader will check the version of the core library the plugin was
* linked against and will create a new #GstPlugin. It will then call the
* #GstPluginInitFunc function that was provided in the
* `gst_plugin_desc`.
*
* Once you have a handle to a #GstPlugin (e.g. from the #GstRegistry), you
* can add any object that subclasses #GstPluginFeature.
*
* Usually plugins are always automatically loaded so you don't need to call
* gst_plugin_load() explicitly to bring it into memory. There are options to
* statically link plugins to an app or even use GStreamer without a plugin
* repository in which case gst_plugin_load() can be needed to bring the plugin
* into memory.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gst_private.h"
#include <glib/gstdio.h>
#include <sys/types.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <errno.h>
#include <string.h>
#include "glib-compat-private.h"
#include <gst/gst.h>
#ifdef G_OS_WIN32
#include <windows.h>
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#define GST_WINAPI_ONLY_APP
#endif
#endif
#define GST_CAT_DEFAULT GST_CAT_PLUGIN_LOADING
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;
/* List of known licenses:
* GPL: https://opensource.org/licenses/gpl-license
* LGPL: https://opensource.org/licenses/lgpl-license
* QPL: https://opensource.org/licenses/QPL-1.0
* MPL: https://opensource.org/licenses/MPL-1.1
* MPL-2.0: https://opensource.org/licenses/MPL-2.0
* MIT/X11: https://opensource.org/licenses/MIT
* 3-clause BSD: https://opensource.org/licenses/BSD-3-Clause
* Zero-Clause BSD: https://opensource.org/licenses/0BSD
* FIXME: update to use SPDX identifiers, or just remove entirely
*/
static const gchar known_licenses[] = "LGPL\000" /* GNU Lesser General Public License */
"GPL\000" /* GNU General Public License */
"QPL\000" /* Trolltech Qt Public License */
"GPL/QPL\000" /* Combi-license of GPL + QPL */
"MPL\000" /* MPL 1.1 license */
"MPL-2.0\000" /* MPL 2.0 license */
"BSD\000" /* 3-clause BSD license */
"MIT/X11\000" /* MIT/X11 license */
"0BSD\000" /* Zero-Clause BSD */
"Proprietary\000" /* Proprietary license */
GST_LICENSE_UNKNOWN; /* some other license */
static GstPlugin *gst_plugin_register_func (GstPlugin * plugin,
const GstPluginDesc * desc, gpointer user_data);
static void gst_plugin_desc_copy (GstPluginDesc * dest,
const GstPluginDesc * src);
static void gst_plugin_ext_dep_free (GstPluginDep * dep);
G_DEFINE_TYPE_WITH_PRIVATE (GstPlugin, gst_plugin, GST_TYPE_OBJECT);
static void
gst_plugin_init (GstPlugin * plugin)
{
plugin->priv = gst_plugin_get_instance_private (plugin);
}
static void
gst_plugin_finalize (GObject * object)
{
GstPlugin *plugin = GST_PLUGIN_CAST (object);
GST_DEBUG ("finalizing plugin %" GST_PTR_FORMAT, plugin);
/* FIXME: make registry add a weak ref instead */
#if 0
GstRegistry *registry = gst_registry_get ();
GList *g;
for (g = registry->plugins; g; g = g->next) {
if (g->data == (gpointer) plugin) {
g_warning ("removing plugin that is still in registry");
}
}
#endif
g_free (plugin->filename);
g_free (plugin->basename);
g_list_foreach (plugin->priv->deps, (GFunc) gst_plugin_ext_dep_free, NULL);
g_list_free (plugin->priv->deps);
plugin->priv->deps = NULL;
if (plugin->priv->cache_data) {
gst_structure_free (plugin->priv->cache_data);
}
G_OBJECT_CLASS (gst_plugin_parent_class)->finalize (object);
}
static void
gst_plugin_class_init (GstPluginClass * klass)
{
G_OBJECT_CLASS (klass)->finalize = gst_plugin_finalize;
}
GQuark
gst_plugin_error_quark (void)
{
static GQuark quark = 0;
if (!quark)
quark = g_quark_from_static_string ("gst_plugin_error");
return quark;
}
/**
* gst_plugin_register_static:
* @major_version: the major version number of the GStreamer core that the
* plugin was compiled for, you can just use GST_VERSION_MAJOR here
* @minor_version: the minor version number of the GStreamer core that the
* plugin was compiled for, you can just use GST_VERSION_MINOR here
* @name: a unique name of the plugin (ideally prefixed with an application- or
* library-specific namespace prefix in order to avoid name conflicts in
* case a similar plugin with the same name ever gets added to GStreamer)
* @description: description of the plugin
* @init_func: (scope call): pointer to the init function of this plugin.
* @version: version string of the plugin
* @license: effective license of plugin. Must be one of the approved licenses
* (see #GstPluginDesc above) or the plugin will not be registered.
* @source: source module plugin belongs to
* @package: shipped package plugin belongs to
* @origin: URL to provider of plugin
*
* Registers a static plugin, ie. a plugin which is private to an application
* or library and contained within the application or library (as opposed to
* being shipped as a separate module file).
*
* You must make sure that GStreamer has been initialised (with gst_init() or
* via gst_init_get_option_group()) before calling this function.
*
* Returns: %TRUE if the plugin was registered correctly, otherwise %FALSE.
*/
gboolean
gst_plugin_register_static (gint major_version, gint minor_version,
const gchar * name, const gchar * description, GstPluginInitFunc init_func,
const gchar * version, const gchar * license, const gchar * source,
const gchar * package, const gchar * origin)
{
GstPluginDesc desc = { major_version, minor_version, name, description,
init_func, version, license, source, package, origin, NULL,
};
GstPlugin *plugin;
gboolean res = FALSE;
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (description != NULL, FALSE);
g_return_val_if_fail (init_func != NULL, FALSE);
g_return_val_if_fail (version != NULL, FALSE);
g_return_val_if_fail (license != NULL, FALSE);
g_return_val_if_fail (source != NULL, FALSE);
g_return_val_if_fail (package != NULL, FALSE);
g_return_val_if_fail (origin != NULL, FALSE);
/* make sure gst_init() has been called */
g_return_val_if_fail (_gst_plugin_inited != FALSE, FALSE);
GST_LOG ("attempting to load static plugin \"%s\" now...", name);
plugin = g_object_new (GST_TYPE_PLUGIN, NULL);
if (gst_plugin_register_func (plugin, &desc, NULL) != NULL) {
GST_INFO ("registered static plugin \"%s\"", name);
res = gst_registry_add_plugin (gst_registry_get (), plugin);
GST_INFO ("added static plugin \"%s\", result: %d", name, res);
}
return res;
}
/**
* gst_plugin_register_static_full:
* @major_version: the major version number of the GStreamer core that the
* plugin was compiled for, you can just use GST_VERSION_MAJOR here
* @minor_version: the minor version number of the GStreamer core that the
* plugin was compiled for, you can just use GST_VERSION_MINOR here
* @name: a unique name of the plugin (ideally prefixed with an application- or
* library-specific namespace prefix in order to avoid name conflicts in
* case a similar plugin with the same name ever gets added to GStreamer)
* @description: description of the plugin
* @init_full_func: (scope call): pointer to the init function with user data
* of this plugin.
* @version: version string of the plugin
* @license: effective license of plugin. Must be one of the approved licenses
* (see #GstPluginDesc above) or the plugin will not be registered.
* @source: source module plugin belongs to
* @package: shipped package plugin belongs to
* @origin: URL to provider of plugin
* @user_data: gpointer to user data
*
* Registers a static plugin, ie. a plugin which is private to an application
* or library and contained within the application or library (as opposed to
* being shipped as a separate module file) with a #GstPluginInitFullFunc
* which allows user data to be passed to the callback function (useful
* for bindings).
*
* You must make sure that GStreamer has been initialised (with gst_init() or
* via gst_init_get_option_group()) before calling this function.
*
* Returns: %TRUE if the plugin was registered correctly, otherwise %FALSE.
*/
gboolean
gst_plugin_register_static_full (gint major_version, gint minor_version,
const gchar * name, const gchar * description,
GstPluginInitFullFunc init_full_func, const gchar * version,
const gchar * license, const gchar * source, const gchar * package,
const gchar * origin, gpointer user_data)
{
GstPluginDesc desc = { major_version, minor_version, name, description,
(GstPluginInitFunc) init_full_func, version, license, source, package,
origin, NULL,
};
GstPlugin *plugin;
gboolean res = FALSE;
g_return_val_if_fail (name != NULL, FALSE);
g_return_val_if_fail (description != NULL, FALSE);
g_return_val_if_fail (init_full_func != NULL, FALSE);
g_return_val_if_fail (version != NULL, FALSE);
g_return_val_if_fail (license != NULL, FALSE);
g_return_val_if_fail (source != NULL, FALSE);
g_return_val_if_fail (package != NULL, FALSE);
g_return_val_if_fail (origin != NULL, FALSE);
/* make sure gst_init() has been called */
g_return_val_if_fail (_gst_plugin_inited != FALSE, FALSE);
GST_LOG ("attempting to load static plugin \"%s\" now...", name);
plugin = g_object_new (GST_TYPE_PLUGIN, NULL);
if (gst_plugin_register_func (plugin, &desc, user_data) != NULL) {
GST_INFO ("registered static plugin \"%s\"", name);
res = gst_registry_add_plugin (gst_registry_get (), plugin);
GST_INFO ("added static plugin \"%s\", result: %d", name, res);
}
return res;
}
void
_priv_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) {
gst_plugin_register_static (_static_plugins[i].major_version,
_static_plugins[i].minor_version, _static_plugins[i].name,
_static_plugins[i].description, _static_plugins[i].plugin_init,
_static_plugins[i].version, _static_plugins[i].license,
_static_plugins[i].source, _static_plugins[i].package,
_static_plugins[i].origin);
}
if (_static_plugins) {
free (_static_plugins);
_static_plugins = NULL;
_num_static_plugins = 0;
}
}
/* 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 (const 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 (const 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);
}
guint32
priv_gst_plugin_loading_get_whitelist_hash (void)
{
guint32 hash = 0;
if (_plugin_loading_whitelist != NULL) {
gchar **w;
for (w = _plugin_loading_whitelist; *w != NULL; ++w)
hash ^= g_str_hash (*w);
}
return hash;
}
/* 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 :)
*/
static gboolean
gst_plugin_check_license (const gchar * license)
{
const gchar *l, *end = known_licenses + sizeof (known_licenses);
for (l = known_licenses; l < end; l += strlen (l) + 1) {
if (strcmp (license, l) == 0)
return TRUE;
}
return FALSE;
}
static gboolean
gst_plugin_check_version (gint major, gint minor)
{
/* return NULL if the major and minor version numbers are not compatible */
/* with ours. */
if (major != GST_VERSION_MAJOR || minor > GST_VERSION_MINOR)
return FALSE;
return TRUE;
}
static GstPlugin *
gst_plugin_register_func (GstPlugin * plugin, const GstPluginDesc * desc,
gpointer user_data)
{
if (!gst_plugin_check_version (desc->major_version, desc->minor_version)) {
if (GST_CAT_DEFAULT)
GST_WARNING ("plugin \"%s\" has incompatible version "
"(plugin: %d.%d, gst: %d.%d), not loading",
GST_STR_NULL (plugin->filename), desc->major_version,
desc->minor_version, GST_VERSION_MAJOR, GST_VERSION_MINOR);
return NULL;
}
if (!desc->license || !desc->description || !desc->source ||
!desc->package || !desc->origin) {
if (GST_CAT_DEFAULT)
GST_WARNING ("plugin \"%s\" has missing detail in GstPluginDesc, not "
"loading", GST_STR_NULL (plugin->filename));
return NULL;
}
if (!gst_plugin_check_license (desc->license)) {
if (GST_CAT_DEFAULT)
GST_WARNING ("plugin \"%s\" has unknown license \"%s\"",
GST_STR_NULL (plugin->filename), desc->license);
/* We still want to load the plugin, it's not our job to validate licenses */
}
if (GST_CAT_DEFAULT)
GST_LOG ("plugin \"%s\" looks good", GST_STR_NULL (plugin->filename));
gst_plugin_desc_copy (&plugin->desc, desc);
/* make resident so we're really sure it never gets unloaded again.
* Theoretically this is not needed, but practically it doesn't hurt.
* And we're rather safe than sorry. */
if (plugin->module)
g_module_make_resident (plugin->module);
if (user_data) {
if (!(((GstPluginInitFullFunc) (desc->plugin_init)) (plugin, user_data))) {
if (GST_CAT_DEFAULT)
GST_WARNING ("plugin \"%s\" failed to initialise",
GST_STR_NULL (plugin->filename));
return NULL;
}
} else {
if (!((desc->plugin_init) (plugin))) {
if (GST_CAT_DEFAULT)
GST_WARNING ("plugin \"%s\" failed to initialise",
GST_STR_NULL (plugin->filename));
return NULL;
}
}
if (GST_CAT_DEFAULT)
GST_LOG ("plugin \"%s\" initialised", GST_STR_NULL (plugin->filename));
return plugin;
}
#ifdef HAVE_SIGACTION
static struct sigaction oldaction;
static gboolean _gst_plugin_fault_handler_is_setup = FALSE;
/*
* _gst_plugin_fault_handler_restore:
* segfault handler restorer
*/
static void
_gst_plugin_fault_handler_restore (void)
{
if (!_gst_plugin_fault_handler_is_setup)
return;
_gst_plugin_fault_handler_is_setup = FALSE;
sigaction (SIGSEGV, &oldaction, NULL);
}
/*
* _gst_plugin_fault_handler_sighandler:
* segfault handler implementation
*/
static void
_gst_plugin_fault_handler_sighandler (int signum)
{
/* We need to restore the fault handler or we'll keep getting it */
_gst_plugin_fault_handler_restore ();
switch (signum) {
case SIGSEGV:
g_print ("\nERROR: ");
g_print ("Caught a segmentation fault while loading plugin file:\n");
g_print ("%s\n\n", _gst_plugin_fault_handler_filename);
g_print ("Please either:\n");
g_print ("- remove it and restart.\n");
g_print
("- run with --gst-disable-segtrap --gst-disable-registry-fork and debug.\n");
exit (-1);
break;
default:
g_print ("Caught unhandled signal on plugin loading\n");
break;
}
}
/*
* _gst_plugin_fault_handler_setup:
* sets up the segfault handler
*/
static void
_gst_plugin_fault_handler_setup (void)
{
struct sigaction action;
/* if asked to leave segfaults alone, just return */
if (!gst_segtrap_is_enabled ())
return;
if (_gst_plugin_fault_handler_is_setup)
return;
_gst_plugin_fault_handler_is_setup = TRUE;
memset (&action, 0, sizeof (action));
action.sa_handler = _gst_plugin_fault_handler_sighandler;
sigaction (SIGSEGV, &action, &oldaction);
}
#else /* !HAVE_SIGACTION */
static void
_gst_plugin_fault_handler_restore (void)
{
}
static void
_gst_plugin_fault_handler_setup (void)
{
}
#endif /* HAVE_SIGACTION */
/* g_time_val_from_iso8601() doesn't do quite what we want */
static gboolean
check_release_datetime (const gchar * date_time)
{
guint64 val;
/* we require YYYY-MM-DD or YYYY-MM-DDTHH:MMZ format */
if (!g_ascii_isdigit (*date_time))
return FALSE;
val = g_ascii_strtoull (date_time, (gchar **) & date_time, 10);
if (val < 2000 || val > 2100 || *date_time != '-')
return FALSE;
val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10);
if (val == 0 || val > 12 || *date_time != '-')
return FALSE;
val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10);
if (val == 0 || val > 32)
return FALSE;
/* end of string or date/time separator + HH:MMZ */
if (*date_time == 'T' || *date_time == ' ') {
val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10);
if (val > 24 || *date_time != ':')
return FALSE;
val = g_ascii_strtoull (date_time + 1, (gchar **) & date_time, 10);
if (val > 59 || *date_time != 'Z')
return FALSE;
++date_time;
}
return (*date_time == '\0');
}
static GMutex gst_plugin_loading_mutex;
#define CHECK_PLUGIN_DESC_FIELD(desc,field,fn) \
if (G_UNLIKELY ((desc)->field == NULL || *(desc)->field == '\0')) { \
g_warning ("Plugin description for '%s' has no valid %s field", fn, G_STRINGIFY (field)); \
g_set_error (error, GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE, \
"Plugin %s has invalid plugin description field '%s'", \
filename, G_STRINGIFY (field)); \
goto return_error; \
}
/**
* gst_plugin_load_file:
* @filename: (type filename): the plugin filename to load
* @error: pointer to a %NULL-valued GError
*
* Loads the given plugin and refs it. Caller needs to unref after use.
*
* Returns: (transfer full): a reference to the existing loaded GstPlugin, a
* reference to the newly-loaded GstPlugin, or %NULL if an error occurred.
*/
GstPlugin *
gst_plugin_load_file (const gchar * filename, GError ** error)
{
return _priv_gst_plugin_load_file_for_registry (filename, NULL, error);
}
static gchar *
extract_symname (const char *filename)
{
gchar *bname, *name, *symname;
const gchar *dot;
gsize prefix_len, len;
int i;
bname = g_path_get_basename (filename);
for (i = 0; bname[i]; ++i) {
if (bname[i] == '-')
bname[i] = '_';
}
if (g_str_has_prefix (bname, "libgst"))
prefix_len = 6;
else if (g_str_has_prefix (bname, "lib"))
prefix_len = 3;
else if (g_str_has_prefix (bname, "gst"))
prefix_len = 3;
else
prefix_len = 0; /* use whole name (minus suffix) as plugin name */
dot = g_utf8_strchr (bname, -1, '.');
if (dot)
len = dot - bname - prefix_len;
else
len = strlen (bname + prefix_len);
name = g_strndup (bname + prefix_len, len);
g_free (bname);
symname = g_strconcat ("gst_plugin_", name, "_get_desc", NULL);
g_free (name);
return symname;
}
/* Note: The return value is (transfer full) although we work with floating
* references here. If a new plugin instance is created, it is always sinked
* in the registry first and a new reference is returned
*/
GstPlugin *
_priv_gst_plugin_load_file_for_registry (const gchar * filename,
GstRegistry * registry, GError ** error)
{
const GstPluginDesc *desc;
GstPlugin *plugin;
gchar *symname;
GModule *module;
gboolean ret;
gpointer ptr;
GStatBuf file_status;
gboolean new_plugin = TRUE;
GModuleFlags flags;
g_return_val_if_fail (filename != NULL, NULL);
if (registry == NULL)
registry = gst_registry_get ();
g_mutex_lock (&gst_plugin_loading_mutex);
plugin = gst_registry_lookup (registry, filename);
if (plugin) {
if (plugin->module) {
/* already loaded */
g_mutex_unlock (&gst_plugin_loading_mutex);
return plugin;
} else {
/* load plugin and update fields */
new_plugin = FALSE;
}
}
GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "attempt to load plugin \"%s\"",
filename);
if (!g_module_supported ()) {
GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "module loading not supported");
g_set_error (error,
GST_PLUGIN_ERROR,
GST_PLUGIN_ERROR_MODULE, "Dynamic loading not supported");
goto return_error;
}
#if defined(GST_WINAPI_ONLY_APP)
/* plugins loaded by filename by Universal Windows Platform apps do not use
* an actual file with a path, they use a packaged (asset) library */
file_status.st_mtime = 0;
file_status.st_size = 0;
#else
if (g_stat (filename, &file_status)) {
GST_CAT_DEBUG (GST_CAT_PLUGIN_LOADING, "problem accessing file");
g_set_error (error,
GST_PLUGIN_ERROR,
GST_PLUGIN_ERROR_MODULE, "Problem accessing file %s: %s", filename,
g_strerror (errno));
goto return_error;
}
#endif
flags = G_MODULE_BIND_LOCAL;
/* libgstpython.so is the gst-python plugin loader. It needs to be loaded with
* G_MODULE_BIND_LAZY.
*
* Ideally there should be a generic way for plugins to specify that they
* need to be loaded with _LAZY.
* */
if (strstr (filename, "libgstpython"))
flags |= G_MODULE_BIND_LAZY;
module = g_module_open (filename, flags);
if (module == NULL) {
GST_CAT_WARNING (GST_CAT_PLUGIN_LOADING, "module_open failed: %s",
g_module_error ());
g_set_error (error,
GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE, "Opening module failed: %s",
g_module_error ());
/* If we failed to open the shared object, then it's probably because a
* plugin is linked against the wrong libraries. Print out an easy-to-see
* message in this case. */
g_warning ("Failed to load plugin '%s': %s", filename, g_module_error ());
goto return_error;
}
symname = extract_symname (filename);
ret = g_module_symbol (module, symname, &ptr);
if (ret) {
GstPluginDesc *(*get_desc) (void) = ptr;
ptr = get_desc ();
} else {
GST_DEBUG ("Could not find symbol '%s', falling back to gst_plugin_desc",
symname);
ret = g_module_symbol (module, "gst_plugin_desc", &ptr);
}
g_free (symname);
if (!ret) {
GST_DEBUG ("Could not find plugin entry point in \"%s\"", filename);
g_set_error (error,
GST_PLUGIN_ERROR,
GST_PLUGIN_ERROR_MODULE,
"File \"%s\" is not a GStreamer plugin", filename);
g_module_close (module);
goto return_error;
}
desc = (const 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_new (GST_TYPE_PLUGIN, 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;
if (new_plugin) {
/* check plugin description: complain about bad values and fail */
CHECK_PLUGIN_DESC_FIELD (desc, name, filename);
CHECK_PLUGIN_DESC_FIELD (desc, description, filename);
CHECK_PLUGIN_DESC_FIELD (desc, version, filename);
CHECK_PLUGIN_DESC_FIELD (desc, license, filename);
CHECK_PLUGIN_DESC_FIELD (desc, source, filename);
CHECK_PLUGIN_DESC_FIELD (desc, package, filename);
CHECK_PLUGIN_DESC_FIELD (desc, origin, filename);
if (desc->name != NULL && desc->name[0] == '"') {
g_warning ("Invalid plugin name '%s' - fix your GST_PLUGIN_DEFINE "
"(remove quotes around plugin name)", desc->name);
}
if (desc->release_datetime != NULL &&
!check_release_datetime (desc->release_datetime)) {
g_warning ("GstPluginDesc for '%s' has invalid datetime '%s'",
filename, desc->release_datetime);
g_set_error (error, GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE,
"Plugin %s has invalid plugin description field 'release_datetime'",
filename);
goto return_error;
}
}
GST_LOG ("Plugin %p for file \"%s\" prepared, calling entry function...",
plugin, filename);
/* this is where we load the actual .so, so let's trap SIGSEGV */
_gst_plugin_fault_handler_setup ();
_gst_plugin_fault_handler_filename = plugin->filename;
GST_LOG ("Plugin %p for file \"%s\" prepared, registering...",
plugin, filename);
if (!gst_plugin_register_func (plugin, desc, NULL)) {
/* remove signal handler */
_gst_plugin_fault_handler_restore ();
GST_DEBUG ("gst_plugin_register_func failed for plugin \"%s\"", filename);
/* plugin == NULL */
g_set_error (error,
GST_PLUGIN_ERROR,
GST_PLUGIN_ERROR_MODULE,
"File \"%s\" appears to be a GStreamer plugin, but it failed to initialize",
filename);
goto return_error;
}
/* remove signal handler */
_gst_plugin_fault_handler_restore ();
_gst_plugin_fault_handler_filename = NULL;
GST_INFO ("plugin \"%s\" loaded", plugin->filename);
if (new_plugin) {
gst_object_ref (plugin);
gst_registry_add_plugin (registry, plugin);
}
g_mutex_unlock (&gst_plugin_loading_mutex);
return plugin;
return_error:
{
if (plugin)
gst_object_unref (plugin);
g_mutex_unlock (&gst_plugin_loading_mutex);
return NULL;
}
}
static void
gst_plugin_desc_copy (GstPluginDesc * dest, const GstPluginDesc * src)
{
dest->major_version = src->major_version;
dest->minor_version = src->minor_version;
dest->name = g_intern_string (src->name);
dest->description = g_intern_string (src->description);
dest->plugin_init = src->plugin_init;
dest->version = g_intern_string (src->version);
dest->license = g_intern_string (src->license);
dest->source = g_intern_string (src->source);
dest->package = g_intern_string (src->package);
dest->origin = g_intern_string (src->origin);
dest->release_datetime = g_intern_string (src->release_datetime);
}
/**
* gst_plugin_get_name:
* @plugin: plugin to get the name of
*
* Get the short name of the plugin
*
* Returns: the name of the plugin
*/
const gchar *
gst_plugin_get_name (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.name;
}
/**
* gst_plugin_get_description:
* @plugin: plugin to get long name of
*
* Get the long descriptive name of the plugin
*
* Returns: the long name of the plugin
*/
const gchar *
gst_plugin_get_description (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.description;
}
/**
* gst_plugin_get_filename:
* @plugin: plugin to get the filename of
*
* get the filename of the plugin
*
* Returns: (type filename) (nullable): the filename of the plugin
*/
const gchar *
gst_plugin_get_filename (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->filename;
}
/**
* gst_plugin_get_version:
* @plugin: plugin to get the version of
*
* get the version of the plugin
*
* Returns: the version of the plugin
*/
const gchar *
gst_plugin_get_version (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.version;
}
/**
* gst_plugin_get_license:
* @plugin: plugin to get the license of
*
* get the license of the plugin
*
* Returns: the license of the plugin
*/
const gchar *
gst_plugin_get_license (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.license;
}
/**
* gst_plugin_get_source:
* @plugin: plugin to get the source of
*
* get the source module the plugin belongs to.
*
* Returns: the source of the plugin
*/
const gchar *
gst_plugin_get_source (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.source;
}
/**
* gst_plugin_get_package:
* @plugin: plugin to get the package of
*
* get the package the plugin belongs to.
*
* Returns: the package of the plugin
*/
const gchar *
gst_plugin_get_package (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.package;
}
/**
* gst_plugin_get_origin:
* @plugin: plugin to get the origin of
*
* get the URL where the plugin comes from
*
* Returns: the origin of the plugin
*/
const gchar *
gst_plugin_get_origin (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.origin;
}
/**
* gst_plugin_get_release_date_string:
* @plugin: plugin to get the release date of
*
* Get the release date (and possibly time) in form of a string, if available.
*
* For normal GStreamer plugin releases this will usually just be a date in
* the form of "YYYY-MM-DD", while pre-releases and builds from git may contain
* a time component after the date as well, in which case the string will be
* formatted like "YYYY-MM-DDTHH:MMZ" (e.g. "2012-04-30T09:30Z").
*
* There may be plugins that do not have a valid release date set on them.
*
* Returns: (nullable): the date string of the plugin, or %NULL if not
* available.
*/
const gchar *
gst_plugin_get_release_date_string (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, NULL);
return plugin->desc.release_datetime;
}
/**
* gst_plugin_is_loaded:
* @plugin: plugin to query
*
* queries if the plugin is loaded into memory
*
* Returns: %TRUE is loaded, %FALSE otherwise
*/
gboolean
gst_plugin_is_loaded (GstPlugin * plugin)
{
g_return_val_if_fail (plugin != NULL, FALSE);
return (plugin->module != NULL || plugin->filename == NULL);
}
/**
* gst_plugin_get_cache_data:
* @plugin: a plugin
*
* Gets the plugin specific data cache. If it is %NULL there is no cached data
* stored. This is the case when the registry is getting rebuilt.
*
* Returns: (transfer none) (nullable): The cached data as a
* #GstStructure or %NULL.
*/
const GstStructure *
gst_plugin_get_cache_data (GstPlugin * plugin)
{
g_return_val_if_fail (GST_IS_PLUGIN (plugin), NULL);
return plugin->priv->cache_data;
}
/**
* gst_plugin_set_cache_data:
* @plugin: a plugin
* @cache_data: (transfer full): a structure containing the data to cache
*
* Adds plugin specific data to cache. Passes the ownership of the structure to
* the @plugin.
*
* The cache is flushed every time the registry is rebuilt.
*/
void
gst_plugin_set_cache_data (GstPlugin * plugin, GstStructure * cache_data)
{
g_return_if_fail (GST_IS_PLUGIN (plugin));
g_return_if_fail (GST_IS_STRUCTURE (cache_data));
if (plugin->priv->cache_data) {
gst_structure_free (plugin->priv->cache_data);
}
plugin->priv->cache_data = cache_data;
}
#if 0
/**
* gst_plugin_feature_list:
* @plugin: plugin to query
* @filter: the filter to use
* @first: only return first match
* @user_data: user data passed to the filter function
*
* Runs a filter against all plugin features and returns a GList with
* the results. If the first flag is set, only the first match is
* returned (as a list with a single object).
*
* Returns: a GList of features, g_list_free after use.
*/
GList *
gst_plugin_feature_filter (GstPlugin * plugin,
GstPluginFeatureFilter filter, gboolean first, gpointer user_data)
{
GList *list;
GList *g;
list = gst_filter_run (plugin->features, (GstFilterFunc) filter, first,
user_data);
for (g = list; g; g = g->next) {
gst_object_ref (plugin);
}
return list;
}
typedef struct
{
GstPluginFeatureFilter filter;
gboolean first;
gpointer user_data;
GList *result;
}
FeatureFilterData;
static gboolean
_feature_filter (GstPlugin * plugin, gpointer user_data)
{
GList *result;
FeatureFilterData *data = (FeatureFilterData *) user_data;
result = gst_plugin_feature_filter (plugin, data->filter, data->first,
data->user_data);
if (result) {
data->result = g_list_concat (data->result, result);
return TRUE;
}
return FALSE;
}
/**
* gst_plugin_list_feature_filter:
* @list: a #GList of plugins to query
* @filter: the filter function to use
* @first: only return first match
* @user_data: user data passed to the filter function
*
* Runs a filter against all plugin features of the plugins in the given
* list and returns a GList with the results.
* If the first flag is set, only the first match is
* returned (as a list with a single object).
*
* Returns: a GList of features, g_list_free after use.
*/
GList *
gst_plugin_list_feature_filter (GList * list,
GstPluginFeatureFilter filter, gboolean first, gpointer user_data)
{
FeatureFilterData data;
GList *result;
data.filter = filter;
data.first = first;
data.user_data = user_data;
data.result = NULL;
result = gst_filter_run (list, (GstFilterFunc) _feature_filter, first, &data);
g_list_free (result);
return data.result;
}
/**
* gst_plugin_find_feature:
* @plugin: plugin to get the feature from
* @name: The name of the feature to find
* @type: The type of the feature to find
*
* Find a feature of the given name and type in the given plugin.
*
* Returns: a GstPluginFeature or %NULL if the feature was not found.
*/
GstPluginFeature *
gst_plugin_find_feature (GstPlugin * plugin, const gchar * name, GType type)
{
GList *walk;
GstPluginFeature *result = NULL;
GstTypeNameData data;
g_return_val_if_fail (name != NULL, NULL);
data.type = type;
data.name = name;
walk = gst_filter_run (plugin->features,
(GstFilterFunc) gst_plugin_feature_type_name_filter, TRUE, &data);
if (walk) {
result = GST_PLUGIN_FEATURE (walk->data);
gst_object_ref (result);
gst_plugin_feature_list_free (walk);
}
return result;
}
#endif
#if 0
static gboolean
gst_plugin_feature_name_filter (GstPluginFeature * feature, const gchar * name)
{
return !strcmp (name, GST_PLUGIN_FEATURE_NAME (feature));
}
#endif
#if 0
/**
* gst_plugin_find_feature_by_name:
* @plugin: plugin to get the feature from
* @name: The name of the feature to find
*
* Find a feature of the given name in the given plugin.
*
* Returns: a GstPluginFeature or %NULL if the feature was not found.
*/
GstPluginFeature *
gst_plugin_find_feature_by_name (GstPlugin * plugin, const gchar * name)
{
GList *walk;
GstPluginFeature *result = NULL;
g_return_val_if_fail (name != NULL, NULL);
walk = gst_filter_run (plugin->features,
(GstFilterFunc) gst_plugin_feature_name_filter, TRUE, (void *) name);
if (walk) {
result = GST_PLUGIN_FEATURE (walk->data);
gst_object_ref (result);
gst_plugin_feature_list_free (walk);
}
return result;
}
#endif
/**
* gst_plugin_load_by_name:
* @name: name of plugin to load
*
* Load the named plugin. Refs the plugin.
*
* Returns: (transfer full) (nullable): a reference to a loaded plugin, or
* %NULL on error.
*/
GstPlugin *
gst_plugin_load_by_name (const gchar * name)
{
GstPlugin *plugin, *newplugin;
GError *error = NULL;
GST_DEBUG ("looking up plugin %s in default registry", name);
plugin = gst_registry_find_plugin (gst_registry_get (), name);
if (plugin) {
GST_DEBUG ("loading plugin %s from file %s", name, plugin->filename);
newplugin = gst_plugin_load_file (plugin->filename, &error);
gst_object_unref (plugin);
if (!newplugin) {
GST_WARNING ("load_plugin error: %s", error->message);
g_error_free (error);
return NULL;
}
/* newplugin was reffed by load_file */
return newplugin;
}
GST_DEBUG ("Could not find plugin %s in registry", name);
return NULL;
}
/**
* gst_plugin_load:
* @plugin: (transfer none): plugin to load
*
* Loads @plugin. Note that the *return value* is the loaded plugin; @plugin is
* untouched. The normal use pattern of this function goes like this:
*
* |[
* GstPlugin *loaded_plugin;
* loaded_plugin = gst_plugin_load (plugin);
* // presumably, we're no longer interested in the potentially-unloaded plugin
* gst_object_unref (plugin);
* plugin = loaded_plugin;
* ]|
*
* Returns: (transfer full) (nullable): a reference to a loaded plugin, or
* %NULL on error.
*/
GstPlugin *
gst_plugin_load (GstPlugin * plugin)
{
GError *error = NULL;
GstPlugin *newplugin;
if (gst_plugin_is_loaded (plugin)) {
return gst_object_ref (plugin);
}
if (!(newplugin = gst_plugin_load_file (plugin->filename, &error)))
goto load_error;
return newplugin;
load_error:
{
GST_WARNING ("load_plugin error: %s", error->message);
g_error_free (error);
return NULL;
}
}
/**
* gst_plugin_list_free:
* @list: (transfer full) (element-type Gst.Plugin): list of #GstPlugin
*
* Unrefs each member of @list, then frees the list.
*/
void
gst_plugin_list_free (GList * list)
{
GList *g;
for (g = list; g; g = g->next) {
gst_object_unref (GST_PLUGIN_CAST (g->data));
}
g_list_free (list);
}
/* ===== plugin dependencies ===== */
/* Scenarios:
* ENV + xyz where ENV can contain multiple values separated by SEPARATOR
* xyz may be "" (if ENV contains path to file rather than dir)
* ENV + *xyz same as above, but xyz acts as suffix filter
* ENV + xyz* same as above, but xyz acts as prefix filter (is this needed?)
* ENV + *xyz* same as above, but xyz acts as strstr filter (is this needed?)
*
* same as above, with additional paths hard-coded at compile-time:
* - only check paths + ... if ENV is not set or yields not paths
* - always check paths + ... in addition to ENV
*
* When user specifies set of environment variables, he/she may also use e.g.
* "HOME/.mystuff/plugins", and we'll expand the content of $HOME with the
* remainder
*/
/* we store in registry:
* sets of:
* {
* - environment variables (array of strings)
* - last hash of env variable contents (uint) (so we can avoid doing stats
* if one of the env vars has changed; premature optimisation galore)
* - hard-coded paths (array of strings)
* - xyz filename/suffix/prefix strings (array of strings)
* - flags (int)
* - last hash of file/dir stats (int)
* }
* (= struct GstPluginDep)
*/
static guint
gst_plugin_ext_dep_get_env_vars_hash (GstPlugin * plugin, GstPluginDep * dep)
{
gchar **e;
guint hash;
/* there's no deeper logic to what we do here; all we want to know (when
* checking if the plugin needs to be rescanned) is whether the content of
* one of the environment variables in the list is different from when it
* was last scanned */
hash = 0;
for (e = dep->env_vars; e != NULL && *e != NULL; ++e) {
const gchar *val;
gchar env_var[256];
/* order matters: "val",NULL needs to yield a different hash than
* NULL,"val", so do a shift here whether the var is set or not */
hash = hash << 5;
/* want environment variable at beginning of string */
if (!g_ascii_isalnum (**e)) {
GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment "
"variable string: %s", *e);
continue;
}
/* user is allowed to specify e.g. "HOME/.pitivi/plugins" */
g_strlcpy (env_var, *e, sizeof (env_var));
g_strdelimit (env_var, "/\\", '\0');
if ((val = g_getenv (env_var)))
hash += g_str_hash (val);
}
return hash;
}
gboolean
_priv_plugin_deps_env_vars_changed (GstPlugin * plugin)
{
GList *l;
for (l = plugin->priv->deps; l != NULL; l = l->next) {
GstPluginDep *dep = l->data;
if (dep->env_hash != gst_plugin_ext_dep_get_env_vars_hash (plugin, dep))
return TRUE;
}
return FALSE;
}
static void
gst_plugin_ext_dep_extract_env_vars_paths (GstPlugin * plugin,
GstPluginDep * dep, GQueue * paths)
{
gchar **evars;
for (evars = dep->env_vars; evars != NULL && *evars != NULL; ++evars) {
const gchar *e;
gchar **components;
/* want environment variable at beginning of string */
if (!g_ascii_isalnum (**evars)) {
GST_WARNING_OBJECT (plugin, "string prefix is not a valid environment "
"variable string: %s", *evars);
continue;
}
/* user is allowed to specify e.g. "HOME/.pitivi/plugins", which we want to
* split into the env_var name component and the path component */
components = g_strsplit_set (*evars, "/\\", 2);
g_assert (components != NULL);
e = g_getenv (components[0]);
GST_LOG_OBJECT (plugin, "expanding %s = '%s' (path suffix: %s)",
components[0], GST_STR_NULL (e), GST_STR_NULL (components[1]));
if (components[1] != NULL) {
g_strdelimit (components[1], "/\\", G_DIR_SEPARATOR);
}
if (e != NULL && *e != '\0') {
gchar **arr;
guint i;
arr = g_strsplit (e, G_SEARCHPATH_SEPARATOR_S, -1);
for (i = 0; arr != NULL && arr[i] != NULL; ++i) {
gchar *full_path;
if (!g_path_is_absolute (arr[i])) {
GST_INFO_OBJECT (plugin, "ignoring environment variable content '%s'"
": either not an absolute path or not a path at all", arr[i]);
continue;
}
if (components[1] != NULL) {
full_path = g_build_filename (arr[i], components[1], NULL);
} else {
full_path = g_strdup (arr[i]);
}
if (!g_queue_find_custom (paths, full_path, (GCompareFunc) strcmp)) {
GST_LOG_OBJECT (plugin, "path: '%s'", full_path);
g_queue_push_tail (paths, full_path);
full_path = NULL;
} else {
GST_LOG_OBJECT (plugin, "path: '%s' (duplicate,ignoring)", full_path);
g_free (full_path);
}
}
g_strfreev (arr);
}
g_strfreev (components);
}
GST_LOG_OBJECT (plugin, "Extracted %d paths from environment", paths->length);
}
static guint
gst_plugin_ext_dep_get_hash_from_stat_entry (GStatBuf * s)
{
#ifdef S_IFBLK
if (!(s->st_mode & (S_IFDIR | S_IFREG | S_IFBLK | S_IFCHR)))
#else
/* MSVC does not have S_IFBLK */
if (!(s->st_mode & (S_IFDIR | S_IFREG | S_IFCHR)))
#endif
return (guint) - 1;
/* completely random formula */
return ((s->st_size << 3) + (s->st_mtime << 5)) ^ s->st_ctime;
}
static gboolean
gst_plugin_ext_dep_direntry_matches (GstPlugin * plugin, const gchar * entry,
const gchar ** filenames, GstPluginDependencyFlags flags)
{
/* no filenames specified, match all entries for now (could probably
* optimise by just taking the dir stat hash or so) */
if (filenames == NULL || *filenames == NULL || **filenames == '\0')
return TRUE;
while (*filenames != NULL) {
/* suffix match? */
if (((flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX)) &&
g_str_has_suffix (entry, *filenames)) {
return TRUE;
} else if (((flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_PREFIX)) &&
g_str_has_prefix (entry, *filenames)) {
return TRUE;
/* else it's an exact match that's needed */
} else if (strcmp (entry, *filenames) == 0) {
return TRUE;
}
GST_LOG ("%s does not match %s, flags=0x%04x", entry, *filenames, flags);
++filenames;
}
return FALSE;
}
static guint
gst_plugin_ext_dep_scan_dir_and_match_names (GstPlugin * plugin,
const gchar * path, const gchar ** filenames,
GstPluginDependencyFlags flags, int depth)
{
const gchar *entry;
gboolean recurse_dirs;
GError *err = NULL;
GDir *dir;
guint hash = 0;
recurse_dirs = ! !(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE);
dir = g_dir_open (path, 0, &err);
if (dir == NULL) {
GST_DEBUG_OBJECT (plugin, "g_dir_open(%s) failed: %s", path, err->message);
g_error_free (err);
return (guint) - 1;
}
/* FIXME: we're assuming here that we always get the directory entries in
* the same order, and not in a random order */
while ((entry = g_dir_read_name (dir))) {
gboolean have_match;
GStatBuf s;
gchar *full_path;
guint fhash;
have_match =
gst_plugin_ext_dep_direntry_matches (plugin, entry, filenames, flags);
/* avoid the stat if possible */
if (!have_match && !recurse_dirs)
continue;
full_path = g_build_filename (path, entry, NULL);
if (g_stat (full_path, &s) < 0) {
fhash = (guint) - 1;
GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path,
g_strerror (errno));
} else if (have_match) {
fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s);
GST_LOG_OBJECT (plugin, "stat: %s (result: %u)", full_path, fhash);
} else if ((s.st_mode & (S_IFDIR))) {
fhash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, full_path,
filenames, flags, depth + 1);
} else {
/* it's not a name match, we want to recurse, but it's not a directory */
g_free (full_path);
continue;
}
hash = hash + fhash;
g_free (full_path);
}
g_dir_close (dir);
return hash;
}
static guint
gst_plugin_ext_dep_scan_path_with_filenames (GstPlugin * plugin,
const gchar * path, const gchar ** filenames,
GstPluginDependencyFlags flags)
{
const gchar *empty_filenames[] = { "", NULL };
gboolean recurse_into_dirs, partial_names = FALSE;
guint i, hash = 0;
/* to avoid special-casing below (FIXME?) */
if (filenames == NULL || *filenames == NULL)
filenames = empty_filenames;
recurse_into_dirs = ! !(flags & GST_PLUGIN_DEPENDENCY_FLAG_RECURSE);
if ((flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX) ||
(flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_PREFIX))
partial_names = TRUE;
/* if we can construct the exact paths to check with the data we have, just
* stat them one by one; this is more efficient than opening the directory
* and going through each entry to see if it matches one of our filenames. */
if (!recurse_into_dirs && !partial_names) {
for (i = 0; filenames[i] != NULL; ++i) {
GStatBuf s;
gchar *full_path;
guint fhash;
full_path = g_build_filename (path, filenames[i], NULL);
if (g_stat (full_path, &s) < 0) {
fhash = (guint) - 1;
GST_LOG_OBJECT (plugin, "stat: %s (error: %s)", full_path,
g_strerror (errno));
} else {
fhash = gst_plugin_ext_dep_get_hash_from_stat_entry (&s);
GST_LOG_OBJECT (plugin, "stat: %s (result: %08x)", full_path, fhash);
}
hash += fhash;
g_free (full_path);
}
} else {
hash = gst_plugin_ext_dep_scan_dir_and_match_names (plugin, path,
filenames, flags, 0);
}
return hash;
}
static guint
gst_plugin_ext_dep_get_stat_hash (GstPlugin * plugin, GstPluginDep * dep)
{
gboolean paths_are_default_only;
gboolean paths_are_relative_to_exe;
GQueue scan_paths = G_QUEUE_INIT;
guint scan_hash = 0;
gchar *path;
GST_LOG_OBJECT (plugin, "start");
paths_are_default_only =
dep->flags & GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY;
paths_are_relative_to_exe =
dep->flags & GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_RELATIVE_TO_EXE;
gst_plugin_ext_dep_extract_env_vars_paths (plugin, dep, &scan_paths);
if (g_queue_is_empty (&scan_paths) || !paths_are_default_only) {
gchar **paths;
for (paths = dep->paths; paths != NULL && *paths != NULL; ++paths) {
const gchar *path = *paths;
gchar *full_path;
if (paths_are_relative_to_exe && !g_path_is_absolute (path)) {
gchar *appdir;
if (!_gst_executable_path) {
GST_FIXME_OBJECT (plugin,
"Path dependency %s relative to executable path but could not retrieve executable path",
path);
continue;
}
appdir = g_path_get_dirname (_gst_executable_path);
full_path = g_build_filename (appdir, path, NULL);
g_free (appdir);
} else {
full_path = g_strdup (path);
}
if (!g_queue_find_custom (&scan_paths, full_path, (GCompareFunc) strcmp)) {
GST_LOG_OBJECT (plugin, "path: '%s'", full_path);
g_queue_push_tail (&scan_paths, full_path);
} else {
GST_LOG_OBJECT (plugin, "path: '%s' (duplicate, ignoring)", full_path);
g_free (full_path);
}
}
}
while ((path = g_queue_pop_head (&scan_paths))) {
scan_hash += gst_plugin_ext_dep_scan_path_with_filenames (plugin, path,
(const gchar **) dep->names, dep->flags);
g_free (path);
}
GST_LOG_OBJECT (plugin, "done, scan_hash: %08x", scan_hash);
return scan_hash;
}
gboolean
_priv_plugin_deps_files_changed (GstPlugin * plugin)
{
GList *l;
for (l = plugin->priv->deps; l != NULL; l = l->next) {
GstPluginDep *dep = l->data;
if (dep->stat_hash != gst_plugin_ext_dep_get_stat_hash (plugin, dep))
return TRUE;
}
return FALSE;
}
static void
gst_plugin_ext_dep_free (GstPluginDep * dep)
{
g_strfreev (dep->env_vars);
g_strfreev (dep->paths);
g_strfreev (dep->names);
g_slice_free (GstPluginDep, dep);
}
static gboolean
gst_plugin_ext_dep_strv_equal (gchar ** arr1, gchar ** arr2)
{
if (arr1 == arr2)
return TRUE;
if (arr1 == NULL || arr2 == NULL)
return FALSE;
for (; *arr1 != NULL && *arr2 != NULL; ++arr1, ++arr2) {
if (strcmp (*arr1, *arr2) != 0)
return FALSE;
}
return (*arr1 == *arr2);
}
static gboolean
gst_plugin_ext_dep_equals (GstPluginDep * dep, const gchar ** env_vars,
const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags)
{
if (dep->flags != flags)
return FALSE;
return gst_plugin_ext_dep_strv_equal (dep->env_vars, (gchar **) env_vars) &&
gst_plugin_ext_dep_strv_equal (dep->paths, (gchar **) paths) &&
gst_plugin_ext_dep_strv_equal (dep->names, (gchar **) names);
}
/**
* gst_plugin_add_dependency:
* @plugin: a #GstPlugin
* @env_vars: (allow-none) (array zero-terminated=1): %NULL-terminated array of environment variables affecting the
* feature set of the plugin (e.g. an environment variable containing
* paths where to look for additional modules/plugins of a library),
* or %NULL. Environment variable names may be followed by a path component
* which will be added to the content of the environment variable, e.g.
* "HOME/.mystuff/plugins".
* @paths: (allow-none) (array zero-terminated=1): %NULL-terminated array of directories/paths where dependent files
* may be, or %NULL.
* @names: (allow-none) (array zero-terminated=1): %NULL-terminated array of file names (or file name suffixes,
* depending on @flags) to be used in combination with the paths from
* @paths and/or the paths extracted from the environment variables in
* @env_vars, or %NULL.
* @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE
*
* Make GStreamer aware of external dependencies which affect the feature
* set of this plugin (ie. the elements or typefinders associated with it).
*
* GStreamer will re-inspect plugins with external dependencies whenever any
* of the external dependencies change. This is useful for plugins which wrap
* other plugin systems, e.g. a plugin which wraps a plugin-based visualisation
* library and makes visualisations available as GStreamer elements, or a
* codec loader which exposes elements and/or caps dependent on what external
* codec libraries are currently installed.
*/
void
gst_plugin_add_dependency (GstPlugin * plugin, const gchar ** env_vars,
const gchar ** paths, const gchar ** names, GstPluginDependencyFlags flags)
{
GstPluginDep *dep;
GList *l;
g_return_if_fail (GST_IS_PLUGIN (plugin));
if ((env_vars == NULL || env_vars[0] == NULL) &&
(paths == NULL || paths[0] == NULL)) {
GST_DEBUG_OBJECT (plugin,
"plugin registered empty dependency set. Ignoring");
return;
}
for (l = plugin->priv->deps; l != NULL; l = l->next) {
if (gst_plugin_ext_dep_equals (l->data, env_vars, paths, names, flags)) {
GST_LOG_OBJECT (plugin, "dependency already registered");
return;
}
}
dep = g_slice_new (GstPluginDep);
dep->env_vars = g_strdupv ((gchar **) env_vars);
dep->paths = g_strdupv ((gchar **) paths);
dep->names = g_strdupv ((gchar **) names);
dep->flags = flags;
dep->env_hash = gst_plugin_ext_dep_get_env_vars_hash (plugin, dep);
dep->stat_hash = gst_plugin_ext_dep_get_stat_hash (plugin, dep);
plugin->priv->deps = g_list_append (plugin->priv->deps, dep);
GST_DEBUG_OBJECT (plugin, "added dependency:");
for (; env_vars != NULL && *env_vars != NULL; ++env_vars)
GST_DEBUG_OBJECT (plugin, " evar: %s", *env_vars);
for (; paths != NULL && *paths != NULL; ++paths)
GST_DEBUG_OBJECT (plugin, " path: %s", *paths);
for (; names != NULL && *names != NULL; ++names)
GST_DEBUG_OBJECT (plugin, " name: %s", *names);
}
/**
* gst_plugin_add_dependency_simple:
* @plugin: the #GstPlugin
* @env_vars: (allow-none): one or more environment variables (separated by ':', ';' or ','),
* or %NULL. Environment variable names may be followed by a path component
* which will be added to the content of the environment variable, e.g.
* "HOME/.mystuff/plugins:MYSTUFF_PLUGINS_PATH"
* @paths: (allow-none): one ore more directory paths (separated by ':' or ';' or ','),
* or %NULL. Example: "/usr/lib/mystuff/plugins"
* @names: (allow-none): one or more file names or file name suffixes (separated by commas),
* or %NULL
* @flags: optional flags, or #GST_PLUGIN_DEPENDENCY_FLAG_NONE
*
* Make GStreamer aware of external dependencies which affect the feature
* set of this plugin (ie. the elements or typefinders associated with it).
*
* GStreamer will re-inspect plugins with external dependencies whenever any
* of the external dependencies change. This is useful for plugins which wrap
* other plugin systems, e.g. a plugin which wraps a plugin-based visualisation
* library and makes visualisations available as GStreamer elements, or a
* codec loader which exposes elements and/or caps dependent on what external
* codec libraries are currently installed.
*
* Convenience wrapper function for gst_plugin_add_dependency() which
* takes simple strings as arguments instead of string arrays, with multiple
* arguments separated by predefined delimiters (see above).
*/
void
gst_plugin_add_dependency_simple (GstPlugin * plugin,
const gchar * env_vars, const gchar * paths, const gchar * names,
GstPluginDependencyFlags flags)
{
gchar **a_evars = NULL;
gchar **a_paths = NULL;
gchar **a_names = NULL;
if (env_vars)
a_evars = g_strsplit_set (env_vars, ":;,", -1);
if (paths)
a_paths = g_strsplit_set (paths, ":;,", -1);
if (names)
a_names = g_strsplit_set (names, ",", -1);
gst_plugin_add_dependency (plugin, (const gchar **) a_evars,
(const gchar **) a_paths, (const gchar **) a_names, flags);
if (a_evars)
g_strfreev (a_evars);
if (a_paths)
g_strfreev (a_paths);
if (a_names)
g_strfreev (a_names);
}