LUMIERA.clone/src/lib/plugin.c

337 lines
10 KiB
C
Raw Normal View History

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 <unistd.h>
2007-07-15 02:23:37 +02:00
#include <search.h>
#include <string.h>
#include <dlfcn.h>
2007-07-17 04:40:07 +02:00
#include <pthread.h>
#include <time.h>
2007-07-15 02:23:37 +02:00
#include "plugin.h"
2007-07-17 04:40:07 +02:00
/* TODO should be set by the build system to the actual plugin path */
2007-08-18 05:05:38 +02:00
#define CINELERRA_PLUGIN_PATH "~/.cinelerra3/plugins:/usr/local/lib/cinelerra3/plugins:.libs"
2007-07-17 04:40:07 +02:00
2007-07-15 02:23:37 +02:00
NOBUG_DEFINE_FLAG (cinelerra_plugin);
/* errors */
2007-08-14 04:56:55 +02:00
CINELERRA_ERROR_DEFINE(PLUGIN_DLOPEN, "Could not open plugin");
CINELERRA_ERROR_DEFINE(PLUGIN_HOOK, "Hook function failed");
CINELERRA_ERROR_DEFINE(PLUGIN_NFILE, "No such plugin");
CINELERRA_ERROR_DEFINE(PLUGIN_NIFACE, "No such interface");
CINELERRA_ERROR_DEFINE(PLUGIN_REVISION, "Interface revision too old");
2007-07-15 02:23:37 +02:00
2007-07-17 04:40:07 +02:00
/*
supported (planned) plugin types and their file extensions
*/
enum cinelerra_plugin_type
{
CINELERRA_PLUGIN_NULL,
CINELERRA_PLUGIN_DYNLIB,
CINELERRA_PLUGIN_CSOURCE
};
static const struct
{
const char* const ext;
enum cinelerra_plugin_type type;
2007-07-17 04:40:07 +02:00
} cinelerra_plugin_extensions [] =
{
{"so", CINELERRA_PLUGIN_DYNLIB},
2007-08-14 04:56:55 +02:00
{"o", CINELERRA_PLUGIN_DYNLIB},
{"c", CINELERRA_PLUGIN_CSOURCE},
2007-07-17 04:40:07 +02:00
/* extend here */
{NULL, CINELERRA_PLUGIN_NULL}
2007-07-17 04:40:07 +02:00
};
2007-07-15 02:23:37 +02:00
struct cinelerra_plugin
{
/* 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;
2007-07-15 02:23:37 +02:00
/* use count for all interfaces of this plugin */
unsigned use_count;
/* time when the last open or close action happened */
2007-07-15 02:23:37 +02:00
time_t last;
2007-07-17 04:40:07 +02:00
/* kind of plugin */
enum cinelerra_plugin_type type;
2007-07-15 02:23:37 +02:00
/* dlopen handle */
void* handle;
};
2007-07-17 04:40:07 +02:00
2007-07-15 02:23:37 +02:00
/* global plugin registry */
void* cinelerra_plugin_registry = NULL;
/* plugin operations are protected by one big mutex */
pthread_mutex_t cinelerra_plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
/* the compare function for the registry tree */
2007-07-15 02:23:37 +02:00
static int
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);
}
/**
* Initialize the plugin system.
* always succeeds or aborts
*/
2007-08-18 05:05:38 +02:00
void
cinelerra_init_plugin (void)
{
NOBUG_INIT_FLAG (cinelerra_plugin);
}
2007-07-15 17:46:23 +02:00
2007-07-17 04:40:07 +02:00
int
cinelerra_plugin_lookup (struct cinelerra_plugin* self, const char* path)
{
2007-07-17 04:40:07 +02:00
if (!path)
return -1;
2007-07-17 04:40:07 +02:00
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))
2007-07-17 04:40:07 +02:00
{
/*for each extension*/
for (int i = 0; cinelerra_plugin_extensions[i].ext; ++i)
{
/* path/name.extension */
2007-08-18 05:05:38 +02:00
int r = snprintf(pathname, 1024, "%s/%s.%s", tok, self->name, cinelerra_plugin_extensions[i].ext);
2007-07-17 04:40:07 +02:00
if (r >= 1024)
return -1; /*TODO error handling, name too long*/
TRACE (cinelerra_plugin, "trying %s", pathname);
if (!access(pathname, R_OK))
{
/* got it */
2007-08-18 05:05:38 +02:00
TRACE (cinelerra_plugin, "found %s", pathname);
2007-07-17 04:40:07 +02:00
self->pathname = strdup (pathname);
if (!self->pathname) CINELERRA_DIE;
2007-07-17 04:40:07 +02:00
self->type = cinelerra_plugin_extensions[i].type;
return 0;
}
}
}
return -1; /* plugin not found */
}
/**
* Make an interface available.
* To use an interface provided by a plugin it must be opened first. It is allowed to open an interface more than once.
* Each open must be paired with a close.
* @param plugin name of the plugin to use.
* @param name name of the interface to open.
* @param min_revision the size of the interface structure is used as measure of a minimal required revision (new functions are appended at the end)
* @return handle to the interface or NULL in case of a error. The application shall cast this handle to the actual interface type.
*/
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(struct cinelerra_interface), "try to use an empty interface eh?");
REQUIRE (interface, "interface name must be given");
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-17 04:40:07 +02:00
plugin.name = name; /* for searching */
2007-07-15 02:23:37 +02:00
found = tsearch (&plugin, &cinelerra_plugin_registry, cinelerra_plugin_name_cmp);
if (!found) CINELERRA_DIE;
2007-07-15 02:23:37 +02:00
if (*found == &plugin)
{
NOTICE (cinelerra_plugin, "new plugin");
2007-07-17 04:40:07 +02:00
/* now really create new item */
2007-07-15 02:23:37 +02:00
*found = malloc (sizeof (struct cinelerra_plugin));
if (!*found) CINELERRA_DIE;
2007-07-15 02:23:37 +02:00
2007-07-17 04:40:07 +02:00
if (name) /* NULL is main app, no lookup needed */
{
2007-07-17 04:40:07 +02:00
/*lookup for $CINELERRA_PLUGIN_PATH*/
(*found)->name = strdup (name);
if (!(*found)->name) CINELERRA_DIE;
2007-07-17 04:40:07 +02:00
2007-08-18 05:05:38 +02:00
if (!!cinelerra_plugin_lookup (*found, getenv("CINELERRA_PLUGIN_PATH"))
2007-07-17 04:40:07 +02:00
#ifdef CINELERRA_PLUGIN_PATH
/* else lookup for -DCINELERRA_PLUGIN_PATH */
2007-08-18 05:05:38 +02:00
&& !!cinelerra_plugin_lookup (*found, CINELERRA_PLUGIN_PATH)
2007-07-17 04:40:07 +02:00
#endif
)
{
2007-08-18 05:05:38 +02:00
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_NFILE);
2007-07-17 04:40:07 +02:00
goto elookup;
}
}
else
{
(*found)->name = NULL;
(*found)->pathname = NULL;
}
2007-07-15 02:23:37 +02:00
(*found)->use_count = 0;
2007-07-17 04:40:07 +02:00
PLANNED("if .so like then dlopen; else if .c like tcc compile");
TODO("factor dlopen and dlsym out");
2007-07-17 04:40:07 +02:00
2007-08-18 05:05:38 +02:00
TRACE(cinelerra_plugin, "trying to open %s", (*found)->pathname);
(*found)->handle = dlopen ((*found)->pathname, RTLD_LAZY|RTLD_LOCAL);
2007-07-15 02:23:37 +02:00
if (!(*found)->handle)
{
ERROR (cinelerra_plugin, "dlopen failed: %s", dlerror());
2007-08-18 05:05:38 +02:00
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_DLOPEN);
2007-07-15 02:23:37 +02:00
goto edlopen;
}
/* 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())
{
2007-08-18 05:05:38 +02:00
//ERROR (cinelerra_plugin, "cinelerra_plugin_init failed: %s: %s", name, interface);
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_HOOK);
2007-07-15 02:23:37 +02:00
goto einit;
}
}
/* we have the plugin, now get the interface descriptor */
struct cinelerra_interface* ret;
2007-08-18 05:05:38 +02:00
dlerror();
2007-07-15 02:23:37 +02:00
ret = dlsym ((*found)->handle, interface);
2007-08-18 05:05:38 +02:00
const char *dlerr = dlerror();
TRACE(cinelerra_plugin, "%s", dlerr);
2007-07-15 02:23:37 +02:00
TODO ("need some way to tell 'interface not provided by plugin', maybe cinelerra_plugin_error()?");
2007-08-18 05:05:38 +02:00
if (dlerr)
2007-07-15 02:23:37 +02:00
{
2007-08-18 05:05:38 +02:00
//ERROR (cinelerra_plugin, "plugin %s doesnt provide interface %s", name, interface);
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_NIFACE);
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-08-18 05:05:38 +02:00
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_REVISION);
2007-07-15 02:23:37 +02:00
goto erevision;
}
ret->plugin = *found;
/* 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-08-18 05:05:38 +02:00
CINELERRA_ERROR_SET (cinelerra_plugin, PLUGIN_HOOK);
2007-07-15 02:23:37 +02:00
goto eopen;
}
(*found)->use_count++;
2007-07-17 04:40:07 +02:00
(*found)->last = time (NULL);
2007-07-15 02:23:37 +02:00
ret->use_count++;
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:
2007-07-17 04:40:07 +02:00
elookup:
2007-07-15 02:23:37 +02:00
free ((char*)(*found)->name);
free (*found);
*found = &plugin;
tdelete (&plugin, &cinelerra_plugin_registry, cinelerra_plugin_name_cmp);
2007-08-18 05:05:38 +02:00
pthread_mutex_unlock (&cinelerra_plugin_mutex);
2007-07-15 02:23:37 +02:00
return NULL;
}
/**
* Close an interface. Does not free associated resources
* Calling this function with self==NULL is legal. Every interface handle must be closed only once.
* @param self interface to be closed
*/
2007-07-15 02:23:37 +02:00
void
cinelerra_interface_close (void* ptr)
2007-07-15 02:23:37 +02:00
{
2007-08-18 05:05:38 +02:00
TRACE (cinelerra_plugin, "%p", ptr);
if(!ptr)
2007-07-15 02:23:37 +02:00
return;
struct cinelerra_interface* self = (struct cinelerra_interface*) ptr;
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 */
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);
}
pthread_mutex_unlock (&cinelerra_plugin_mutex);
2007-07-15 02:23:37 +02:00
}