X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gst%2Fgstplugin.c;h=7799dfa3e61683488746d691c1fa386481dd8190;hb=993eda50045e3b6f86a5439f8fe82bd447ac4b69;hp=11fe18114db85c9e02159242183b0a6a987260fb;hpb=72e0b732f6f4a727355d7699f87442abaadcea06;p=platform%2Fupstream%2Fgstreamer.git diff --git a/gst/gstplugin.c b/gst/gstplugin.c index 11fe181..7799dfa 100644 --- a/gst/gstplugin.c +++ b/gst/gstplugin.c @@ -49,6 +49,9 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif + +#include "gst_private.h" + #include #include #ifdef HAVE_DIRENT_H @@ -59,8 +62,8 @@ #endif #include #include +#include -#include "gst_private.h" #include "glib-compat-private.h" #include @@ -70,14 +73,11 @@ 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; -#ifndef HAVE_WIN32 -static gboolean _gst_plugin_fault_handler_is_setup = FALSE; -#endif - /* list of valid licenses. * One of these must be specified or the plugin won't be loaded * Contact gstreamer-devel@lists.sourceforge.net if your license should be @@ -104,18 +104,19 @@ static const gchar *valid_licenses[] = { }; static GstPlugin *gst_plugin_register_func (GstPlugin * plugin, - const GstPluginDesc * desc); + const GstPluginDesc * desc, gpointer user_data); static void gst_plugin_desc_copy (GstPluginDesc * dest, const GstPluginDesc * src); -static void gst_plugin_desc_free (GstPluginDesc * desc); +static void gst_plugin_ext_dep_free (GstPluginDep * dep); G_DEFINE_TYPE (GstPlugin, gst_plugin, GST_TYPE_OBJECT); static void gst_plugin_init (GstPlugin * plugin) { - /* do nothing, needed because of G_DEFINE_TYPE */ + plugin->priv = + G_TYPE_INSTANCE_GET_PRIVATE (plugin, GST_TYPE_PLUGIN, GstPluginPrivate); } static void @@ -125,7 +126,7 @@ gst_plugin_finalize (GObject * object) GstRegistry *registry = gst_registry_get_default (); GList *g; - GST_DEBUG ("finalizing plugin %p", plugin); + GST_DEBUG ("finalizing plugin %" GST_PTR_FORMAT, plugin); for (g = registry->plugins; g; g = g->next) { if (g->data == (gpointer) plugin) { g_warning ("removing plugin that is still in registry"); @@ -133,7 +134,14 @@ gst_plugin_finalize (GObject * object) } g_free (plugin->filename); g_free (plugin->basename); - gst_plugin_desc_free (&plugin->desc); + + 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); } @@ -141,7 +149,9 @@ gst_plugin_finalize (GObject * object) static void gst_plugin_class_init (GstPluginClass * klass) { - G_OBJECT_CLASS (klass)->finalize = GST_DEBUG_FUNCPTR (gst_plugin_finalize); + G_OBJECT_CLASS (klass)->finalize = gst_plugin_finalize; + + g_type_class_add_private (klass, sizeof (GstPluginPrivate)); } GQuark @@ -154,35 +164,6 @@ gst_plugin_error_quark (void) return quark; } -#ifndef GST_REMOVE_DEPRECATED -/* this function can be called in the GCC constructor extension, before - * the _gst_plugin_initialize() was called. In that case, we store the - * plugin description in a list to initialize it when we open the main - * module later on. - * When the main module is known, we can register the plugin right away. - */ -void -_gst_plugin_register_static (GstPluginDesc * desc) -{ - g_return_if_fail (desc != NULL); - - if (!_gst_plugin_inited) { - /* We can't use any GLib functions here, since g_thread_init hasn't been - * called yet, and we can't call it here either, or programs that don't - * guard their g_thread_init calls in main() will just abort */ - ++_num_static_plugins; - _static_plugins = - realloc (_static_plugins, _num_static_plugins * sizeof (GstPluginDesc)); - /* assume strings in the GstPluginDesc are static const or live forever */ - _static_plugins[_num_static_plugins - 1] = *desc; - } else { - gst_plugin_register_static (desc->major_version, desc->minor_version, - desc->name, desc->description, desc->plugin_init, desc->version, - desc->license, desc->source, desc->package, desc->origin); - } -} -#endif - /** * gst_plugin_register_static: * @major_version: the major version number of the GStreamer core that the @@ -214,12 +195,12 @@ _gst_plugin_register_static (GstPluginDesc * desc) */ gboolean gst_plugin_register_static (gint major_version, gint minor_version, - const gchar * name, gchar * description, GstPluginInitFunc init_func, + 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, + init_func, version, license, source, package, origin, NULL, }; GstPlugin *plugin; gboolean res = FALSE; @@ -237,8 +218,77 @@ gst_plugin_register_static (gint major_version, gint minor_version, 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) { + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + if (gst_plugin_register_func (plugin, &desc, NULL) != NULL) { + GST_INFO ("registered static plugin \"%s\"", name); + res = gst_default_registry_add_plugin (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: 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. + * + * Since: 0.10.24 + * + */ +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_newv (GST_TYPE_PLUGIN, 0, NULL); + if (gst_plugin_register_func (plugin, &desc, user_data) != NULL) { GST_INFO ("registered static plugin \"%s\"", name); res = gst_default_registry_add_plugin (plugin); GST_INFO ("added static plugin \"%s\", result: %d", name, res); @@ -249,10 +299,20 @@ gst_plugin_register_static (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) { @@ -271,6 +331,122 @@ _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); +} + +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 = (hash << 1) ^ 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 :) @@ -302,7 +478,8 @@ gst_plugin_check_version (gint major, gint minor) } static GstPlugin * -gst_plugin_register_func (GstPlugin * plugin, const GstPluginDesc * desc) +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) @@ -331,11 +508,24 @@ gst_plugin_register_func (GstPlugin * plugin, const GstPluginDesc * desc) gst_plugin_desc_copy (&plugin->desc, desc); - if (!((desc->plugin_init) (plugin))) { - if (GST_CAT_DEFAULT) - GST_WARNING ("plugin \"%s\" failed to initialise", plugin->filename); - plugin->module = NULL; - return NULL; + /* 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", plugin->filename); + return NULL; + } + } else { + if (!((desc->plugin_init) (plugin))) { + if (GST_CAT_DEFAULT) + GST_WARNING ("plugin \"%s\" failed to initialise", plugin->filename); + return NULL; + } } if (GST_CAT_DEFAULT) @@ -346,6 +536,7 @@ gst_plugin_register_func (GstPlugin * plugin, const GstPluginDesc * desc) #ifdef HAVE_SIGACTION static struct sigaction oldaction; +static gboolean _gst_plugin_fault_handler_is_setup = FALSE; /* * _gst_plugin_fault_handler_restore: @@ -411,7 +602,7 @@ _gst_plugin_fault_handler_setup (void) sigaction (SIGSEGV, &action, &oldaction); } -#else +#else /* !HAVE_SIGACTION */ static void _gst_plugin_fault_handler_restore (void) { @@ -421,9 +612,45 @@ static void _gst_plugin_fault_handler_setup (void) { } -#endif +#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; -static void _gst_plugin_fault_handler_setup (); + /* 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 GStaticMutex gst_plugin_loading_mutex = G_STATIC_MUTEX_INIT; @@ -445,12 +672,15 @@ 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; gpointer ptr; - struct stat file_status; + GStatBuf file_status; GstRegistry *registry; + gboolean new_plugin = TRUE; + GModuleFlags flags; g_return_val_if_fail (filename != NULL, NULL); @@ -460,11 +690,12 @@ gst_plugin_load_file (const gchar * filename, GError ** error) plugin = gst_registry_lookup (registry, filename); if (plugin) { if (plugin->module) { + /* already loaded */ g_static_mutex_unlock (&gst_plugin_loading_mutex); return plugin; } else { - gst_object_unref (plugin); - plugin = NULL; + /* load plugin and update fields */ + new_plugin = FALSE; } } @@ -488,7 +719,17 @@ gst_plugin_load_file (const gchar * filename, GError ** error) goto return_error; } - module = g_module_open (filename, G_MODULE_BIND_LOCAL); + 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 ()); @@ -502,14 +743,6 @@ gst_plugin_load_file (const gchar * filename, GError ** error) goto return_error; } - plugin = g_object_new (GST_TYPE_PLUGIN, NULL); - - plugin->module = module; - plugin->filename = g_strdup (filename); - plugin->basename = g_path_get_basename (filename); - plugin->file_mtime = file_status.st_mtime; - plugin->file_size = file_status.st_size; - ret = g_module_symbol (module, "gst_plugin_desc", &ptr); if (!ret) { GST_DEBUG ("Could not find plugin entry point in \"%s\"", filename); @@ -520,17 +753,48 @@ gst_plugin_load_file (const gchar * filename, GError ** error) g_module_close (module); goto return_error; } - plugin->orig_desc = (GstPluginDesc *) ptr; - - /* check plugin description: complain about bad values but accept them, to - * maintain backwards compatibility (FIXME: 0.11) */ - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, name, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, description, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, version, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, license, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, source, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, package, filename); - CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, origin, filename); + + 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 + * maintain backwards compatibility (FIXME: 0.11) */ + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, name, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, description, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, version, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, license, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, source, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, package, filename); + CHECK_PLUGIN_DESC_FIELD (plugin->orig_desc, origin, filename); + + if (plugin->orig_desc->release_datetime != NULL && + !check_release_datetime (plugin->orig_desc->release_datetime)) { + GST_ERROR ("GstPluginDesc for '%s' has invalid datetime '%s'", + filename, plugin->orig_desc->release_datetime); + plugin->orig_desc->release_datetime = NULL; + } + } GST_LOG ("Plugin %p for file \"%s\" prepared, calling entry function...", plugin, filename); @@ -542,7 +806,7 @@ gst_plugin_load_file (const gchar * filename, GError ** error) GST_LOG ("Plugin %p for file \"%s\" prepared, registering...", plugin, filename); - if (!gst_plugin_register_func (plugin, plugin->orig_desc)) { + if (!gst_plugin_register_func (plugin, plugin->orig_desc, NULL)) { /* remove signal handler */ _gst_plugin_fault_handler_restore (); GST_DEBUG ("gst_plugin_register_func failed for plugin \"%s\"", filename); @@ -552,7 +816,6 @@ gst_plugin_load_file (const gchar * filename, GError ** error) GST_PLUGIN_ERROR_MODULE, "File \"%s\" appears to be a GStreamer plugin, but it failed to initialize", filename); - g_module_close (module); goto return_error; } @@ -561,8 +824,10 @@ gst_plugin_load_file (const gchar * filename, GError ** error) _gst_plugin_fault_handler_filename = NULL; GST_INFO ("plugin \"%s\" loaded", plugin->filename); - gst_object_ref (plugin); - gst_default_registry_add_plugin (plugin); + if (new_plugin) { + gst_object_ref (plugin); + gst_default_registry_add_plugin (plugin); + } g_static_mutex_unlock (&gst_plugin_loading_mutex); return plugin; @@ -582,22 +847,14 @@ 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); - /* maybe intern the description too, just for convenience? */ - dest->description = g_strdup (src->description); + 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); -} - -/* unused */ -static void -gst_plugin_desc_free (GstPluginDesc * desc) -{ - g_free (desc->description); - memset (desc, 0, sizeof (GstPluginDesc)); + dest->release_datetime = g_intern_string (src->release_datetime); } /** @@ -762,6 +1019,49 @@ gst_plugin_is_loaded (GstPlugin * plugin) 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: The cached data as a #GstStructure or %NULL. + * + * Since: 0.10.24 + */ +G_CONST_RETURN 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: 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. + * + * Since: 0.10.24 + */ +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: @@ -1032,3 +1332,526 @@ gst_plugin_list_free (GList * list) } 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 GList * +gst_plugin_ext_dep_extract_env_vars_paths (GstPlugin * plugin, + GstPluginDep * dep) +{ + gchar **evars; + GList *paths = NULL; + + 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_list_find_custom (paths, full_path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", full_path); + paths = g_list_prepend (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", + g_list_length (paths)); + + return paths; +} + +static guint +gst_plugin_ext_dep_get_hash_from_stat_entry (GStatBuf * s) +{ + if (!(s->st_mode & (S_IFDIR | S_IFREG))) + 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 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) << 1; + 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; + 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); + partial_names = !!(flags & GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX); + + /* 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 = (hash + fhash) << 1; + 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; + GList *scan_paths; + guint scan_hash = 0; + + GST_LOG_OBJECT (plugin, "start"); + + paths_are_default_only = + dep->flags & GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY; + + scan_paths = gst_plugin_ext_dep_extract_env_vars_paths (plugin, dep); + + if (scan_paths == NULL || !paths_are_default_only) { + gchar **paths; + + for (paths = dep->paths; paths != NULL && *paths != NULL; ++paths) { + const gchar *path = *paths; + + if (!g_list_find_custom (scan_paths, path, (GCompareFunc) strcmp)) { + GST_LOG_OBJECT (plugin, "path: '%s'", path); + scan_paths = g_list_prepend (scan_paths, g_strdup (path)); + } else { + GST_LOG_OBJECT (plugin, "path: '%s' (duplicate, ignoring)", path); + } + } + } + + /* not that the order really matters, but it makes debugging easier */ + scan_paths = g_list_reverse (scan_paths); + + while (scan_paths != NULL) { + const gchar *path = scan_paths->data; + + scan_hash += gst_plugin_ext_dep_scan_path_with_filenames (plugin, path, + (const gchar **) dep->names, dep->flags); + scan_hash = scan_hash << 1; + + g_free (scan_paths->data); + scan_paths = g_list_delete_link (scan_paths, scan_paths); + } + + 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: NULL-terminated array of environent 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: NULL-terminated array of directories/paths where dependent files + * may be. + * @names: 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. + * + * Since: 0.10.22 + */ +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: one or more environent 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: one ore more directory paths (separated by ':' or ';' or ','), + * or NULL. Example: "/usr/lib/mystuff/plugins" + * @names: 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). + * + * Since: 0.10.22 + */ +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); +}