diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index da39e71c6..be9b9cd90 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -29,6 +29,7 @@ liblumibackend_a_SOURCES = \ $(liblumibackend_a_srcdir)/filehandlecache.c \ $(liblumibackend_a_srcdir)/interface.c \ $(liblumibackend_a_srcdir)/interfaceregistry.c \ + $(liblumibackend_a_srcdir)/plugin.c \ $(liblumibackend_a_srcdir)/config.c \ $(liblumibackend_a_srcdir)/config_typed.c \ $(liblumibackend_a_srcdir)/config_wordlist.c \ @@ -47,6 +48,7 @@ noinst_HEADERS += \ $(liblumibackend_a_srcdir)/interface.h \ $(liblumibackend_a_srcdir)/interfaceregistry.h \ $(liblumibackend_a_srcdir)/interfacedescriptor.h \ + $(liblumibackend_a_srcdir)/plugin.h \ $(liblumibackend_a_srcdir)/config.h \ $(liblumibackend_a_srcdir)/configentry.h \ $(liblumibackend_a_srcdir)/configitem.h \ diff --git a/src/backend/interfaceregistry.c b/src/backend/interfaceregistry.c index cc175dd0a..05e2224a1 100644 --- a/src/backend/interfaceregistry.c +++ b/src/backend/interfaceregistry.c @@ -29,6 +29,7 @@ +#include "backend/plugin.h" #include "backend/interfaceregistry.h" /** @@ -39,17 +40,20 @@ */ NOBUG_DEFINE_FLAG_PARENT (interface_all, backend); +NOBUG_DEFINE_FLAG_PARENT (plugin, interface_all); NOBUG_DEFINE_FLAG_PARENT (interfaceregistry, interface_all); NOBUG_DEFINE_FLAG_PARENT (interface, interface_all); PSplay lumiera_interfaceregistry; +PSplay lumiera_pluginregistry; lumiera_mutex lumiera_interface_mutex; static int -cmp_fn (const void* keya, const void* keyb); +lumiera_interface_cmp_fn (const void* keya, const void* keyb); static const void* -key_fn (const PSplaynode node); +lumiera_interface_key_fn (const PSplaynode node); + static LumieraInterfacenode @@ -92,13 +96,20 @@ lumiera_interfaceregistry_init (void) NOBUG_INIT_FLAG (interface_all); NOBUG_INIT_FLAG (interfaceregistry); NOBUG_INIT_FLAG (interface); + NOBUG_INIT_FLAG (plugin); + TRACE (interfaceregistry); REQUIRE (!lumiera_interfaceregistry); + REQUIRE (!lumiera_pluginregistry); - lumiera_interfaceregistry = psplay_new (cmp_fn, key_fn, NULL); + lumiera_interfaceregistry = psplay_new (lumiera_interface_cmp_fn, lumiera_interface_key_fn, NULL); if (!lumiera_interfaceregistry) LUMIERA_DIE (ERRNO); + lumiera_pluginregistry = psplay_new (lumiera_plugin_cmp_fn, lumiera_plugin_key_fn, NULL); + if (!lumiera_pluginregistry) + LUMIERA_DIE (ERRNO); + lumiera_recmutex_init (&lumiera_interface_mutex, "interfaceregistry", &NOBUG_FLAG(interfaceregistry)); } @@ -114,6 +125,13 @@ lumiera_interfaceregistry_destroy (void) if (lumiera_interfaceregistry) psplay_destroy (lumiera_interfaceregistry); lumiera_interfaceregistry = NULL; + + TODO ("provide a delete function for the psplay tree to tear it down"); + REQUIRE (psplay_nelements (lumiera_pluginregistry) == 0, "plugins still loaded at destroy"); + + if (lumiera_pluginregistry) + psplay_delete (lumiera_pluginregistry); + lumiera_pluginregistry = NULL; } @@ -216,7 +234,7 @@ lumiera_interfaceregistry_interface_find (const char* interface, unsigned versio static int -cmp_fn (const void* keya, const void* keyb) +lumiera_interface_cmp_fn (const void* keya, const void* keyb) { const LumieraInterface a = (const LumieraInterface)keya; const LumieraInterface b = (const LumieraInterface)keyb; @@ -243,12 +261,11 @@ cmp_fn (const void* keya, const void* keyb) static const void* -key_fn (const PSplaynode node) +lumiera_interface_key_fn (const PSplaynode node) { return ((LumieraInterfacenode)node)->interface; } - /* // Local Variables: // mode: C diff --git a/src/backend/plugin.c b/src/backend/plugin.c index f6ecd7649..1b708c6be 100644 --- a/src/backend/plugin.c +++ b/src/backend/plugin.c @@ -18,72 +18,72 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include -#include -#include -#include -#include -#include -#include -#include - #include "lib/safeclib.h" +#include "lib/psplay.h" +#include "lib/mutex.h" +#include "lib/error.h" +#include "backend/interfaceregistry.h" +#include "backend/config.h" #include "backend/plugin.h" +#include + +#include + /** * @file * Plugin loader. */ +extern PSplay lumiera_pluginregistry; /* TODO should be set by the build system to the actual plugin path */ -#define LUMIERA_PLUGIN_PATH "~/.lumiera/plugins:/usr/local/lib/lumiera/plugins:.libs" - -NOBUG_DEFINE_FLAG (lumiera_plugin); /* errors */ LUMIERA_ERROR_DEFINE(PLUGIN_DLOPEN, "Could not open plugin"); -LUMIERA_ERROR_DEFINE(PLUGIN_HOOK, "Hook function failed"); -LUMIERA_ERROR_DEFINE(PLUGIN_NFILE, "No such plugin"); -LUMIERA_ERROR_DEFINE(PLUGIN_NIFACE, "No such interface"); -LUMIERA_ERROR_DEFINE(PLUGIN_REVISION, "Interface revision too old"); /* supported (planned) plugin types and their file extensions */ +#define LUMIERA_PLUGIN_TYPES \ + LUMIERA_PLUGIN_TYPE(DYNLIB, ".so") + +/* + only .so dynlibs for now, later we may support some more + LUMIERA_PLUGIN_TYPE(LUA, ".lua") + LUMIERA_PLUGIN_TYPE(CSOURCE, ".c") +*/ + +#define LUMIERA_PLUGIN_TYPE(type, ext) LUMIERA_PLUGIN_##type, enum lumiera_plugin_type { - LUMIERA_PLUGIN_NULL, - LUMIERA_PLUGIN_DYNLIB, - LUMIERA_PLUGIN_CSOURCE + LUMIERA_PLUGIN_TYPES + LUMIERA_PLUGIN_NONE }; +#undef LUMIERA_PLUGIN_TYPE -static const struct -{ - const char* const ext; - enum lumiera_plugin_type type; -} lumiera_plugin_extensions [] = +#define LUMIERA_PLUGIN_TYPE(type, ext) ext, +static const char* const lumiera_plugin_ext[] = { - {"so", LUMIERA_PLUGIN_DYNLIB}, - {"o", LUMIERA_PLUGIN_DYNLIB}, - {"c", LUMIERA_PLUGIN_CSOURCE}, - /* extend here */ - {NULL, LUMIERA_PLUGIN_NULL} + LUMIERA_PLUGIN_TYPES + NULL }; +#undef LUMIERA_PLUGIN_TYPE -struct lumiera_plugin + + +struct lumiera_plugin_struct { - /* short name as queried ("effects/audio/normalize") used for sorting/finding */ - const char* name; + psplaynode node; /* long names as looked up ("/usr/local/lib/lumiera/plugins/effects/audio/normalize.so") */ - const char* pathname; + const char* name; /* use count for all interfaces of this plugin */ - unsigned use_count; + unsigned refcnt; /* time when the last open or close action happened */ time_t last; @@ -96,242 +96,124 @@ struct lumiera_plugin }; -/* global plugin registry */ -void* lumiera_plugin_registry = NULL; -/* plugin operations are protected by one big mutex */ -pthread_mutex_t lumiera_plugin_mutex = PTHREAD_MUTEX_INITIALIZER; - -/** - * the compare function for the registry tree. - * Compares the names of two struct lumiera_plugin. - * @return 0 if a and b are equal, just like strcmp. */ -static int -lumiera_plugin_name_cmp (const void* a, const void* b) -{ - return strcmp (((struct lumiera_plugin*) a)->name, ((struct lumiera_plugin*) b)->name); -} - -void -lumiera_init_plugin (void) -{ - NOBUG_INIT_FLAG (lumiera_plugin); -} - -/** - * Find and set pathname for the plugin. - * Searches through given path for given plugin, trying to find the file's location in the filesystem. - * If found, self->pathname will be set to the found plugin file. - * @param self The lumiera_plugin to open look for. - * @param path The path to search trough (paths seperated by ":") - * @return 0 on success. -1 on error, or if plugin not found in path. - */ int -lumiera_plugin_lookup (struct lumiera_plugin* self, const char* path) +lumiera_plugin_discover (LumieraPlugin (*callback_load)(const char* plugin), + int (*callback_register) (LumieraPlugin)) { - if (!path) - return -1; + TRACE (plugin); + REQUIRE (callback_load); + REQUIRE (callback_register); - if (strlen(path) > 1023) - return -1; /*TODO error handling*/ - - char tpath[1024]; - TODO("dunno if PATH_MAX may be undefined (in case arbitary lengths are supported), I check that later"); - char pathname[1024] = {0}; - char* tmp; - - strcpy(tpath, path); - - /*for each in path*/ - for (char* tok = strtok_r (tpath, ":", &tmp); tok; tok = strtok_r (NULL, ":", &tmp)) + /* construct glob trail {.so,.c,.foo} ... */ + static char* exts_globs = NULL; + if (!exts_globs) { - /*for each extension*/ - for (int i = 0; lumiera_plugin_extensions[i].ext; ++i) + size_t exts_sz = 4; /* / * { } \0 less one comma */ + const char*const* itr = lumiera_plugin_ext; + while (*itr) { - /* path/name.extension */ - int r = snprintf(pathname, 1024, "%s/%s.%s", tok, self->name, lumiera_plugin_extensions[i].ext); - if (r >= 1024) - return -1; /*TODO error handling, name too long*/ + exts_sz += strlen (*itr) + 1; + ++itr; + } - TRACE (lumiera_plugin, "trying %s", pathname); + exts_globs = lumiera_malloc (exts_sz); + *exts_globs = '\0'; - if (!access(pathname, R_OK)) + itr = lumiera_plugin_ext; + strcat (exts_globs, "/*{"); + + while (*itr) + { + strcat (exts_globs, *itr); + strcat (exts_globs, ","); + ++itr; + } + exts_globs[exts_sz-2] = '}'; + TRACE (plugin, "initialized extension glob to '%s'", exts_globs); + } + + const char* path; + unsigned i = 0; + int flags = GLOB_PERIOD|GLOB_BRACE|GLOB_TILDE_CHECK; + glob_t globs; + + while ((path = lumiera_config_wordlist_get_nth ("plugin.path", i, ":"))) + { + path = lumiera_tmpbuf_snprintf (SIZE_MAX,"%s%s", path, exts_globs); + TRACE (plugin, "globbing path '%s'", path); + int ret = glob (path, flags, NULL, &globs); + if (ret == GLOB_NOSPACE) + LUMIERA_DIE (NO_MEMORY); + + flags |= GLOB_APPEND; + ++i; + } + + LUMIERA_RECMUTEX_SECTION (plugin, &lumiera_interface_mutex) + { + for (char** itr = globs.gl_pathv; *itr; ++itr) + { + + if (!psplay_find (lumiera_pluginregistry, *itr, 100)) { - /* got it */ - TRACE (lumiera_plugin, "found %s", pathname); - self->pathname = lumiera_strndup (pathname, PATH_MAX); + TRACE (plugin, "found new plugin '%s'", *itr); - self->type = lumiera_plugin_extensions[i].type; - return 0; + LumieraPlugin plugin = callback_load (*itr); + if (plugin) + callback_register (plugin); } } } - return -1; /* plugin not found */ + + globfree (&globs); + + return !lumiera_error_peek (); } -struct lumiera_interface* -lumiera_interface_open (const char* name, const char* interface, size_t min_revision) + + + +LumieraPlugin +lumiera_plugin_load (const char* plugin) { - //REQUIRE (min_revision > sizeof(struct lumiera_interface), "try to use an empty interface eh?"); - REQUIRE (interface, "interface name must be given"); + TRACE (plugin); + UNIMPLEMENTED(); + (void) plugin; - pthread_mutex_lock (&lumiera_plugin_mutex); - - struct lumiera_plugin plugin; - struct lumiera_plugin** found; - - plugin.name = name; /* for searching */ - - found = tsearch (&plugin, &lumiera_plugin_registry, lumiera_plugin_name_cmp); - if (!found) - LUMIERA_DIE (NO_MEMORY); - - if (*found == &plugin) - { - NOTICE (lumiera_plugin, "new plugin"); - - /* now really create new item */ - *found = lumiera_malloc (sizeof (struct lumiera_plugin)); - - if (name) /* NULL is main app, no lookup needed */ - { - /*lookup for $LUMIERA_PLUGIN_PATH*/ - (*found)->name = lumiera_strndup (name, PATH_MAX); - - if (!!lumiera_plugin_lookup (*found, getenv("LUMIERA_PLUGIN_PATH")) -#ifdef LUMIERA_PLUGIN_PATH - /* else lookup for -DLUMIERA_PLUGIN_PATH */ - && !!lumiera_plugin_lookup (*found, LUMIERA_PLUGIN_PATH) -#endif - ) - { - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_NFILE); - goto elookup; - } - } - else - { - (*found)->name = NULL; - (*found)->pathname = NULL; - } - - (*found)->use_count = 0; - - PLANNED("if .so like then dlopen; else if .c like tcc compile"); - TODO("factor dlopen and dlsym out"); - - TRACE(lumiera_plugin, "trying to open %s", (*found)->pathname); - - (*found)->handle = dlopen ((*found)->pathname, RTLD_LAZY|RTLD_LOCAL); - if (!(*found)->handle) - { - ERROR (lumiera_plugin, "dlopen failed: %s", dlerror()); - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_DLOPEN); - goto edlopen; - } - - /* if the plugin defines a 'lumiera_plugin_init' function, we call it, must return 0 on success */ - int (*init)(void) = dlsym((*found)->handle, "lumiera_plugin_init"); - if (init && init()) - { - //ERROR (lumiera_plugin, "lumiera_plugin_init failed: %s: %s", name, interface); - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_HOOK); - goto einit; - } - } - /* we have the plugin, now get the interface descriptor */ - struct lumiera_interface* ret; - - dlerror(); - ret = dlsym ((*found)->handle, interface); - - const char *dlerr = dlerror(); - TRACE(lumiera_plugin, "%s", dlerr); - TODO ("need some way to tell 'interface not provided by plugin', maybe lumiera_plugin_error()?"); - if (dlerr) - { - //ERROR (lumiera_plugin, "plugin %s doesnt provide interface %s", name, interface); - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_NIFACE); - goto edlsym; - } - - /* is the interface older than required? */ - if (ret->size < min_revision) - { - ERROR (lumiera_plugin, "plugin %s provides older interface %s revision than required", name, interface); - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_REVISION); - goto erevision; - } - - ret->plugin = *found; - - /* if the interface provides a 'open' function, call it now, must return 0 on success */ - if (ret->open && ret->open()) - { - ERROR (lumiera_plugin, "open hook indicated an error"); - LUMIERA_ERROR_SET (lumiera_plugin, PLUGIN_HOOK); - goto eopen; - } - - (*found)->use_count++; - (*found)->last = time (NULL); - ret->use_count++; - - pthread_mutex_unlock (&lumiera_plugin_mutex); - return ret; - - /* Error cleanup */ - einit: - dlclose((*found)->handle); - eopen: - erevision: - edlsym: - edlopen: - elookup: - free ((char*)(*found)->name); - free (*found); - *found = &plugin; - tdelete (&plugin, &lumiera_plugin_registry, lumiera_plugin_name_cmp); - pthread_mutex_unlock (&lumiera_plugin_mutex); return NULL; } -void -lumiera_interface_close (void* ptr) + +int +lumiera_plugin_register (LumieraPlugin plugin) { - TRACE (lumiera_plugin, "%p", ptr); - if(!ptr) - return; + TRACE (plugin); + UNIMPLEMENTED(); - struct lumiera_interface* self = (struct lumiera_interface*) ptr; - - pthread_mutex_lock (&lumiera_plugin_mutex); - - struct lumiera_plugin* plugin = self->plugin; - - plugin->use_count--; - self->use_count--; - - /* if the interface provides a 'close' function, call it now */ - if (self->close) - self->close(); - - /* plugin not longer in use, unload it */ - if (!plugin->use_count) + LUMIERA_RECMUTEX_SECTION (plugin, &lumiera_interface_mutex) { - TODO ("we dont want to close here, instead store time of recent use and make a expire run, already planned in my head"); + (void) plugin; - /* if the plugin defines a 'lumiera_plugin_destroy' function, we call it */ - int (*destroy)(void) = dlsym(plugin->handle, "lumiera_plugin_destroy"); - if (destroy) - destroy(); - - /* and now cleanup */ - tdelete (plugin, &lumiera_plugin_registry, lumiera_plugin_name_cmp); - free ((char*)plugin->name); - dlclose(plugin->handle); - free (plugin); } - pthread_mutex_unlock (&lumiera_plugin_mutex); + return 0; } + + + + + +int +lumiera_plugin_cmp_fn (const void* keya, const void* keyb) +{ + return strcmp ((const char*)keya, (const char*)keyb); +} + + +const void* +lumiera_plugin_key_fn (const PSplaynode node) +{ + return ((LumieraPlugin)node)->name; +} + diff --git a/src/backend/plugin.h b/src/backend/plugin.h index 6696e395f..8c0e5e7ae 100644 --- a/src/backend/plugin.h +++ b/src/backend/plugin.h @@ -36,6 +36,7 @@ LUMIERA_ERROR_DECLARE(PLUGIN_DLOPEN); +LUMIERA_ERROR_DECLARE(PLUGIN_PLUGINPATH); NOBUG_DECLARE_FLAG (plugin); @@ -48,18 +49,11 @@ typedef struct lumiera_plugin_struct lumiera_plugin; typedef lumiera_plugin* LumieraPlugin; -/** - * Initialize the plugin system. - * always succeeds or aborts - */ -void -lumiera_pluginregistry_init (void); +int +lumiera_plugin_cmp_fn (const void* keya, const void* keyb); -/** - * destroy the plugin system, free all resources - */ -void -lumiera_pluginregistry_destroy (void); +const void* +lumiera_plugin_key_fn (const PSplaynode node); LumieraPlugin @@ -70,6 +64,8 @@ int lumiera_plugin_register (LumieraPlugin); + + /** * discover new plugins * traverses the configured plugin dirs and calls the callback_load function for any plugin