2007-07-15 17:08:45 +02:00
|
|
|
/*
|
|
|
|
|
plugin.c - Cinelerra Plugin loader
|
|
|
|
|
|
|
|
|
|
Copyright (C) CinelerraCV
|
|
|
|
|
2007, Christian Thaeter <ct@pipapo.org>
|
|
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU General Public License as
|
|
|
|
|
published by the Free Software Foundation; either version 2 of the
|
|
|
|
|
License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
*/
|
2007-07-15 02:23:37 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <search.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <dlfcn.h>
|
|
|
|
|
|
|
|
|
|
#include "plugin.h"
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
/* TODO should be set by the build system to the actual plugin path */
|
|
|
|
|
#define CINELERRA_PLUGIN_PATH "~/.cinelerra3/plugins:/usr/local/lib/cinelerra3/plugins"
|
|
|
|
|
|
|
|
|
|
/* recognized extensions */
|
|
|
|
|
#define CINELERRA_PLUGIN_EXT "plugin:so"
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
For future:
|
|
|
|
|
#define CINELERRA_PLUGIN_EXT "plugin:so:cplugin:c"
|
|
|
|
|
imagine using C source plugins compiled with libtcc on the fly
|
|
|
|
|
*/
|
|
|
|
|
|
2007-07-15 02:23:37 +02:00
|
|
|
NOBUG_DEFINE_FLAG (cinelerra_plugin);
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
/* errors */
|
|
|
|
|
const char* CINELERRA_PLUGIN_SUCCESS = NULL;
|
|
|
|
|
const char* CINELERRA_PLUGIN_EALLOC = "Memory allocation failure";
|
2007-07-15 20:06:20 +02:00
|
|
|
const char* CINELERRA_PLUGIN_EDLOPEN = "Could not open plugin";
|
|
|
|
|
const char* CINELERRA_PLUGIN_EDHOOK = "Hook function failed";
|
2007-07-15 16:56:21 +02:00
|
|
|
const char* CINELERRA_PLUGIN_ENFOUND = "No such plugin";
|
|
|
|
|
const char* CINELERRA_PLUGIN_ENIFACE = "No such interface";
|
|
|
|
|
const char* CINELERRA_PLUGIN_EREVISION = "Interface revision too old";
|
2007-07-15 02:23:37 +02:00
|
|
|
|
|
|
|
|
struct cinelerra_plugin
|
|
|
|
|
{
|
2007-07-15 16:56:21 +02:00
|
|
|
/* short name as queried ("effects/audio/normalize") used for sorting/finding */
|
|
|
|
|
const char* name;
|
|
|
|
|
|
|
|
|
|
/* long names as looked up ("/usr/local/lib/cinelerra3/plugins/effects/audio/normalize.so") */
|
|
|
|
|
const char* pathname;
|
|
|
|
|
|
|
|
|
|
/* pointer to the extension part of 'pathname' */
|
|
|
|
|
const char* ext;
|
2007-07-15 02:23:37 +02:00
|
|
|
|
|
|
|
|
/* use count for all interfaces of this plugin */
|
|
|
|
|
unsigned use_count;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
/* time when the last open or close action happened */
|
2007-07-15 02:23:37 +02:00
|
|
|
time_t last;
|
|
|
|
|
|
|
|
|
|
/* dlopen handle */
|
|
|
|
|
void* handle;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* global plugin registry */
|
|
|
|
|
void* cinelerra_plugin_registry = NULL;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
/* plugin operations are protected by one big mutex */
|
|
|
|
|
pthread_mutex_t cinelerra_plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
|
|
|
|
|
/* Thread local storage, for now only the error state */
|
|
|
|
|
static pthread_key_t cinelerra_plugin_tls_error;
|
|
|
|
|
static pthread_once_t cinelerra_plugin_initialized = PTHREAD_ONCE_INIT;
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cinelerra_plugin_tls_init (void)
|
|
|
|
|
{
|
|
|
|
|
pthread_key_create (&cinelerra_plugin_tls_error, NULL);
|
|
|
|
|
}
|
2007-07-15 02:23:37 +02:00
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
/* the compare function for the registry tree */
|
2007-07-15 02:23:37 +02:00
|
|
|
static int
|
2007-07-15 16:56:21 +02:00
|
|
|
cinelerra_plugin_name_cmp (const void* a, const void* b)
|
2007-07-15 02:23:37 +02:00
|
|
|
{
|
|
|
|
|
return strcmp (((struct cinelerra_plugin*) a)->name, ((struct cinelerra_plugin*) b)->name);
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-15 17:46:23 +02:00
|
|
|
|
|
|
|
|
const char*
|
|
|
|
|
cinelerra_plugin_error ()
|
|
|
|
|
{
|
|
|
|
|
pthread_once (&cinelerra_plugin_initialized, cinelerra_plugin_tls_init);
|
|
|
|
|
|
|
|
|
|
const char* err = pthread_getspecific (cinelerra_plugin_tls_error);
|
|
|
|
|
pthread_setspecific (cinelerra_plugin_tls_error, CINELERRA_PLUGIN_SUCCESS);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
cinelerra_plugin_error_set (const char* err)
|
|
|
|
|
{
|
|
|
|
|
pthread_setspecific (cinelerra_plugin_tls_error, err);
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
const char*
|
|
|
|
|
cinelerra_plugin_lookup (struct cinelerra_plugin* self, const char* name, const char* path, const char* ext)
|
|
|
|
|
{
|
|
|
|
|
/*for each in path*/
|
|
|
|
|
/*for each extension*/
|
|
|
|
|
/*try to find name*/
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-15 02:23:37 +02:00
|
|
|
struct cinelerra_interface*
|
|
|
|
|
cinelerra_interface_open (const char* name, const char* interface, size_t min_revision)
|
|
|
|
|
{
|
|
|
|
|
REQUIRE (min_revision > sizeof(cinelerra_interface), "try to use an empty interface eh?");
|
2007-07-15 16:56:21 +02:00
|
|
|
REQUIRE (interface, "interface name must be given");
|
|
|
|
|
|
|
|
|
|
pthread_once (&cinelerra_plugin_initialized, cinelerra_plugin_tls_init);
|
2007-07-15 02:23:37 +02:00
|
|
|
|
2007-07-15 17:46:23 +02:00
|
|
|
pthread_mutex_lock (&cinelerra_plugin_mutex);
|
|
|
|
|
|
2007-07-15 02:23:37 +02:00
|
|
|
struct cinelerra_plugin plugin;
|
|
|
|
|
struct cinelerra_plugin** found;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
// if (name) (NULL means main app)
|
|
|
|
|
|
|
|
|
|
/*lookup for $CINELERRA_PLUGIN_PATH*/
|
|
|
|
|
|
|
|
|
|
/* else lookup for -DCINELERRA_PLUGIN_PATH */
|
|
|
|
|
#ifdef CINELERRA_PLUGIN_PATH
|
|
|
|
|
#endif
|
|
|
|
|
/* else fail */
|
|
|
|
|
|
2007-07-15 02:23:37 +02:00
|
|
|
plugin.name = name;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
found = tsearch (&plugin, &cinelerra_plugin_registry, cinelerra_plugin_name_cmp);
|
2007-07-15 02:23:37 +02:00
|
|
|
if (!found)
|
2007-07-15 20:06:20 +02:00
|
|
|
{
|
|
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EALLOC);
|
|
|
|
|
goto ealloc0;
|
|
|
|
|
}
|
2007-07-15 02:23:37 +02:00
|
|
|
|
|
|
|
|
if (*found == &plugin)
|
|
|
|
|
{
|
|
|
|
|
NOTICE (cinelerra_plugin, "new plugin");
|
|
|
|
|
|
|
|
|
|
/* create new item */
|
|
|
|
|
*found = malloc (sizeof (struct cinelerra_plugin));
|
|
|
|
|
if (!*found)
|
2007-07-15 20:06:20 +02:00
|
|
|
{
|
|
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EALLOC);
|
|
|
|
|
goto ealloc1;
|
|
|
|
|
}
|
2007-07-15 02:23:37 +02:00
|
|
|
|
|
|
|
|
(*found)->name = strdup (name);
|
|
|
|
|
if (!(*found)->name)
|
2007-07-15 20:06:20 +02:00
|
|
|
{
|
|
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EALLOC);
|
|
|
|
|
goto ealloc2;
|
|
|
|
|
}
|
2007-07-15 02:23:37 +02:00
|
|
|
|
|
|
|
|
(*found)->use_count = 0;
|
|
|
|
|
|
|
|
|
|
(*found)->handle = dlopen (name, RTLD_NOW|RTLD_LOCAL);
|
|
|
|
|
if (!(*found)->handle)
|
|
|
|
|
{
|
|
|
|
|
ERROR (cinelerra_plugin, "dlopen failed: %s", dlerror());
|
2007-07-15 20:06:20 +02:00
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EDLOPEN);
|
2007-07-15 02:23:37 +02:00
|
|
|
goto edlopen;
|
|
|
|
|
}
|
|
|
|
|
|
2007-07-15 20:06:20 +02:00
|
|
|
/* if the plugin defines a 'cinelerra_plugin_init' function, we call it, must return 0 on success */
|
2007-07-15 02:23:37 +02:00
|
|
|
int (*init)(void) = dlsym((*found)->handle, "cinelerra_plugin_init");
|
|
|
|
|
if (init && init())
|
|
|
|
|
{
|
|
|
|
|
ERROR (cinelerra_plugin, "cinelerra_plugin_init failed: %s: %s", name, interface);
|
2007-07-15 20:06:20 +02:00
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EHOOK);
|
2007-07-15 02:23:37 +02:00
|
|
|
goto einit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* we have the plugin, now get the interface descriptor */
|
|
|
|
|
struct cinelerra_interface* ret;
|
|
|
|
|
|
|
|
|
|
ret = dlsym ((*found)->handle, interface);
|
|
|
|
|
TODO ("need some way to tell 'interface not provided by plugin', maybe cinelerra_plugin_error()?");
|
|
|
|
|
if (!ret)
|
|
|
|
|
{
|
|
|
|
|
ERROR (cinelerra_plugin, "plugin %s doesnt provide interface %s", name, interface);
|
2007-07-15 20:06:20 +02:00
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_ENIFACE);
|
2007-07-15 02:23:37 +02:00
|
|
|
goto edlsym;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* is the interface older than required? */
|
|
|
|
|
if (ret->size < min_revision)
|
|
|
|
|
{
|
|
|
|
|
ERROR (cinelerra_plugin, "plugin %s provides older interface %s revision than required", name, interface);
|
2007-07-15 20:06:20 +02:00
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EREVISION);
|
2007-07-15 02:23:37 +02:00
|
|
|
goto erevision;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret->plugin = *found;
|
|
|
|
|
|
2007-07-15 20:06:20 +02:00
|
|
|
/* if the interface provides a 'open' function, call it now, must return 0 on success */
|
2007-07-15 02:23:37 +02:00
|
|
|
if (ret->open && ret->open())
|
|
|
|
|
{
|
|
|
|
|
ERROR (cinelerra_plugin, "open hook indicated an error");
|
2007-07-15 20:06:20 +02:00
|
|
|
cinelerra_plugin_error_set (CINELERRA_PLUGIN_EHOOK);
|
2007-07-15 02:23:37 +02:00
|
|
|
goto eopen;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(*found)->use_count++;
|
|
|
|
|
ret->use_count++;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
pthread_mutex_unlock (&cinelerra_plugin_mutex);
|
2007-07-15 02:23:37 +02:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
|
|
/* Error cleanup */
|
|
|
|
|
einit:
|
|
|
|
|
dlclose((*found)->handle);
|
|
|
|
|
eopen:
|
|
|
|
|
erevision:
|
|
|
|
|
edlsym:
|
|
|
|
|
edlopen:
|
|
|
|
|
free ((char*)(*found)->name);
|
|
|
|
|
ealloc2:
|
|
|
|
|
free (*found);
|
|
|
|
|
ealloc1:
|
|
|
|
|
*found = &plugin;
|
2007-07-15 16:56:21 +02:00
|
|
|
tdelete (&plugin, &cinelerra_plugin_registry, cinelerra_plugin_name_cmp);
|
|
|
|
|
ealloc0:
|
|
|
|
|
pthread_mutex_lock (&cinelerra_plugin_mutex);
|
2007-07-15 02:23:37 +02:00
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
cinelerra_interface_close (struct cinelerra_interface* self)
|
|
|
|
|
{
|
|
|
|
|
if(!self)
|
|
|
|
|
return;
|
|
|
|
|
|
2007-07-15 16:56:21 +02:00
|
|
|
pthread_mutex_lock (&cinelerra_plugin_mutex);
|
|
|
|
|
|
2007-07-15 02:23:37 +02:00
|
|
|
struct cinelerra_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)
|
|
|
|
|
{
|
|
|
|
|
TODO ("we dont want to close here, instead store time of recent use and make a expire run, already planned in my head");
|
|
|
|
|
|
|
|
|
|
/* if the plugin defines a 'cinelerra_plugin_destroy' function, we call it */
|
|
|
|
|
int (*destroy)(void) = dlsym(plugin->handle, "cinelerra_plugin_destroy");
|
|
|
|
|
if (destroy)
|
|
|
|
|
destroy();
|
|
|
|
|
|
|
|
|
|
/* and now cleanup */
|
2007-07-15 16:56:21 +02:00
|
|
|
tdelete (plugin, &cinelerra_plugin_registry, cinelerra_plugin_name_cmp);
|
2007-07-15 02:23:37 +02:00
|
|
|
free ((char*)plugin->name);
|
|
|
|
|
dlclose(plugin->handle);
|
|
|
|
|
free (plugin);
|
|
|
|
|
}
|
2007-07-15 16:56:21 +02:00
|
|
|
pthread_mutex_unlock (&cinelerra_plugin_mutex);
|
2007-07-15 02:23:37 +02:00
|
|
|
}
|