You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1016 lines
30 KiB
C
1016 lines
30 KiB
C
/*
|
|
* tcmodule.c -- transcode module system, take two.
|
|
* (C) 2005-2010 - Francesco Romani <fromani -at- gmail -dot- com>
|
|
*
|
|
* This file is part of transcode, a video stream processing tool.
|
|
*
|
|
* transcode 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.
|
|
*
|
|
* transcode 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef HAVE_DLFCN_H
|
|
#include <dlfcn.h>
|
|
#else
|
|
# ifdef OS_DARWIN
|
|
# include "libdldarwin/dlfcn.h"
|
|
# endif
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "libtc.h"
|
|
#include "tccodecs.h"
|
|
#include "tcmodule-data.h"
|
|
#include "tcmodule-core.h"
|
|
#include "transcode.h"
|
|
|
|
#define TC_FACTORY_MAX_HANDLERS (16)
|
|
#define MOD_TYPE_MAX_LEN (TC_BUF_MIN * 2)
|
|
|
|
#define tc_module_init(module, features) \
|
|
(module)->klass->init(&((module)->instance), features)
|
|
#define tc_module_fini(module) \
|
|
(module)->klass->fini(&((module)->instance))
|
|
|
|
/* module entry point */
|
|
typedef const TCModuleClass* (*TCModuleEntry)(void);
|
|
|
|
typedef enum {
|
|
TC_DESCRIPTOR_FREE = 0, /* free to use */
|
|
TC_DESCRIPTOR_CREATED, /* reserved, but not yet registered */
|
|
TC_DESCRIPTOR_DONE, /* ok, all donw and ready to run */
|
|
} TCHandleStatus;
|
|
|
|
typedef struct tcmoduledescriptor_ TCModuleDescriptor;
|
|
struct tcmoduledescriptor_ {
|
|
const char *type; /* packed class + name using make_modtype below */
|
|
void *so_handle; /* used by dl*() stuff */
|
|
TCHandleStatus status;
|
|
TCModuleInfo info;
|
|
|
|
/* main copy of module class data.
|
|
* all instance pointers will refer to this. */
|
|
TCModuleClass klass;
|
|
|
|
int ref_count; /* how many instances are floating around? */
|
|
};
|
|
|
|
struct tcfactory_ {
|
|
const char *mod_path; /* base directory for plugin search */
|
|
int verbose;
|
|
|
|
TCModuleDescriptor descriptors[TC_FACTORY_MAX_HANDLERS];
|
|
int descriptor_count;
|
|
|
|
int instance_count;
|
|
};
|
|
|
|
/*************************************************************************
|
|
* dummy/fake default module class. Always fails complaining loudly. *
|
|
* Using this as default, every module class has already valid *
|
|
* (but sometimes useless) pointers to every method. *
|
|
*************************************************************************/
|
|
|
|
#define DUMMY_HEAVY_CHECK(self, method_name) do { \
|
|
if (self != NULL) { \
|
|
tc_log_warn(self->type, "critical: module doesn't provide" \
|
|
" %s method", method_name); \
|
|
} else { \
|
|
tc_log_error(__FILE__, "critical: %s method missing AND bad" \
|
|
" instance pointer", method_name); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define DUMMY_CHECK(self, method_name) do { \
|
|
if (self != NULL) { \
|
|
tc_log_warn(self->type, \
|
|
"module doesn't provide %s method", method_name); \
|
|
} else { \
|
|
tc_log_error(__FILE__, "%s method missing AND bad" \
|
|
" instance pointer", method_name); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
static int dummy_init(TCModuleInstance *self, uint32_t features)
|
|
{
|
|
DUMMY_HEAVY_CHECK(self, "initialization");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_fini(TCModuleInstance *self)
|
|
{
|
|
DUMMY_HEAVY_CHECK(self, "finalization");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_configure(TCModuleInstance *self,
|
|
const char *options, vob_t *vob)
|
|
{
|
|
DUMMY_HEAVY_CHECK(self, "configuration");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_stop(TCModuleInstance *self)
|
|
{
|
|
DUMMY_HEAVY_CHECK(self, "stopping");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_inspect(TCModuleInstance *self,
|
|
const char *param, const char **value)
|
|
{
|
|
DUMMY_HEAVY_CHECK(self, "inspect");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_encode_video(TCModuleInstance *self,
|
|
vframe_list_t *inframe,
|
|
vframe_list_t *outframe)
|
|
{
|
|
DUMMY_CHECK(self, "encode_video");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_encode_audio(TCModuleInstance *self,
|
|
aframe_list_t *inframe,
|
|
aframe_list_t *outframe)
|
|
{
|
|
DUMMY_CHECK(self, "encode_audio");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_decode_video(TCModuleInstance *self,
|
|
vframe_list_t *inframe,
|
|
vframe_list_t *outframe)
|
|
{
|
|
DUMMY_CHECK(self, "decode_video");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_decode_audio(TCModuleInstance *self,
|
|
aframe_list_t *inframe,
|
|
aframe_list_t *outframe)
|
|
{
|
|
DUMMY_CHECK(self, "decode_audio");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_filter_video(TCModuleInstance *self,
|
|
vframe_list_t *frame)
|
|
{
|
|
DUMMY_CHECK(self, "filter_video");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_filter_audio(TCModuleInstance *self,
|
|
aframe_list_t *frame)
|
|
{
|
|
DUMMY_CHECK(self, "filter_audio");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_multiplex(TCModuleInstance *self,
|
|
vframe_list_t *vframe, aframe_list_t *aframe)
|
|
{
|
|
DUMMY_CHECK(self, "multiplex");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
static int dummy_demultiplex(TCModuleInstance *self,
|
|
vframe_list_t *vframe, aframe_list_t *aframe)
|
|
{
|
|
DUMMY_CHECK(self, "demultiplex");
|
|
return TC_ERROR;
|
|
}
|
|
|
|
#undef DUMMY_HEAVY_CHECK
|
|
#undef DUMMY_CHECK
|
|
|
|
static const TCCodecID dummy_codecs_in[] = {
|
|
TC_CODEC_ANY, TC_CODEC_ERROR
|
|
};
|
|
static const TCCodecID dummy_codecs_out[] = {
|
|
TC_CODEC_ANY, TC_CODEC_ERROR
|
|
};
|
|
static const TCFormatID dummy_formats_in[] = {
|
|
TC_FORMAT_RAW, TC_FORMAT_ERROR
|
|
};
|
|
static const TCFormatID dummy_formats_out[] = {
|
|
TC_FORMAT_RAW, TC_FORMAT_ERROR
|
|
};
|
|
|
|
static TCModuleInfo dummy_info = {
|
|
.features = TC_MODULE_FEATURE_NONE,
|
|
.flags = TC_MODULE_FLAG_NONE,
|
|
.name = "dummy",
|
|
.version = "internal fake module class",
|
|
.description = "can't do anyhing",
|
|
.codecs_in = dummy_codecs_in,
|
|
.codecs_out = dummy_codecs_out,
|
|
.formats_in = dummy_formats_in,
|
|
.formats_out = dummy_formats_out
|
|
};
|
|
|
|
static const TCModuleClass dummy_class = {
|
|
.id = 0,
|
|
|
|
.info = &dummy_info,
|
|
|
|
.init = dummy_init,
|
|
.fini = dummy_fini,
|
|
.configure = dummy_configure,
|
|
.inspect = dummy_inspect,
|
|
.stop = dummy_stop,
|
|
|
|
.encode_audio = dummy_encode_audio,
|
|
.encode_video = dummy_encode_video,
|
|
.decode_audio = dummy_decode_audio,
|
|
.decode_video = dummy_decode_video,
|
|
.filter_audio = dummy_filter_audio,
|
|
.filter_video = dummy_filter_video,
|
|
|
|
.multiplex = dummy_multiplex,
|
|
.demultiplex = dummy_demultiplex
|
|
};
|
|
|
|
/*************************************************************************
|
|
* private helpers *
|
|
*************************************************************************/
|
|
|
|
/*
|
|
* translate_modclass:
|
|
* validate a module class name, represented by a given string.
|
|
*
|
|
* Parameters:
|
|
* modclass: a class nome to validate.
|
|
* Return Value:
|
|
* TC_MOFULE_FEATURE_* correspondent.
|
|
*/
|
|
static uint32_t translate_modclass(const char *modclass)
|
|
{
|
|
uint32_t ret = TC_MODULE_FEATURE_NONE;
|
|
|
|
if (modclass != NULL) {
|
|
if (!strcmp(modclass, "filter")) {
|
|
ret = TC_MODULE_FEATURE_FILTER;
|
|
} else if (!strcmp(modclass, "demultiplex")
|
|
|| !strcmp(modclass, "demux")) {
|
|
ret = TC_MODULE_FEATURE_DEMULTIPLEX;
|
|
} else if (!strcmp(modclass, "decode")) {
|
|
ret = TC_MODULE_FEATURE_DECODE;
|
|
} else if (!strcmp(modclass, "encode")) {
|
|
ret = TC_MODULE_FEATURE_ENCODE;
|
|
} else if (!strcmp(modclass, "multiplex")
|
|
|| !strcmp(modclass, "mplex")) {
|
|
ret = TC_MODULE_FEATURE_MULTIPLEX;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* FIXME: writeme */
|
|
static uint32_t translate_media(int media)
|
|
{
|
|
uint32_t ret = TC_MODULE_FEATURE_NONE;
|
|
|
|
switch (media) {
|
|
case TC_VIDEO:
|
|
ret = TC_MODULE_FEATURE_VIDEO;
|
|
break;
|
|
case TC_AUDIO:
|
|
ret = TC_MODULE_FEATURE_AUDIO;
|
|
break;
|
|
case TC_EXTRA:
|
|
ret = TC_MODULE_FEATURE_EXTRA;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* TCModuleDescriptorIter:
|
|
* generic iterator function on factory descriptors.
|
|
* In some different contexts, a iterator can be applied on all module
|
|
* descriptors in a given factory. Specific iterator functions can do
|
|
* arbitrary actions on descriptor data.
|
|
* See below to get some usage examples.
|
|
*
|
|
* Parameters:
|
|
* desc: pointer to a TCModuleDescriptor.
|
|
* userdata: opaque pointer to function-specific data.
|
|
* Return Value:
|
|
* 0 -> keep on going
|
|
* <0 -> stop iteration and return code verbatim
|
|
* >0 -> stop iteration and return current iteration index
|
|
* Side effects:
|
|
* Arbitrary, defined by specific function.
|
|
* Preconditions:
|
|
* given factory (but isn't guaranteed that also descriptors are) already
|
|
* initialized and contains valid data.
|
|
*/
|
|
typedef int (*TCModuleDescriptorIter)(TCModuleDescriptor *desc, void *userdata);
|
|
|
|
/*
|
|
* tc_foreach_descriptor:
|
|
* apply given iterator with given data to all internal descriptors,
|
|
* *both used and unused*.
|
|
*
|
|
* Parameters:
|
|
* factory: factory instance to use
|
|
* iterator: iterator to apply at factory descriptors
|
|
* userdata: opaque data to pass to iterator along with each descriptor
|
|
* index: pointer to an integer. If not NULL, will be filled
|
|
* with index of last descriptor elaborated
|
|
* Return Value:
|
|
* return code of the last execution of iterator.
|
|
* Side effects:
|
|
* None (see specific descriptor for this).
|
|
* Postconditions:
|
|
* If return value is 0, given iteratr wass applied to *all*
|
|
* descriptors in factory.
|
|
*/
|
|
static int tc_foreach_descriptor(TCFactory factory,
|
|
TCModuleDescriptorIter iterator,
|
|
void *userdata,
|
|
int *index)
|
|
{
|
|
int ret, i = 0;
|
|
|
|
if (!factory || !iterator) {
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < TC_FACTORY_MAX_HANDLERS; i++) {
|
|
ret = iterator(&(factory->descriptors[i]), userdata);
|
|
if (ret != 0) {
|
|
break;
|
|
}
|
|
}
|
|
/* iteration stopped, so we mark the interruption point */
|
|
if (ret != 0 && index != NULL) {
|
|
*index = i;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* descriptor_something: some iterator functions
|
|
*/
|
|
|
|
/*
|
|
* descriptor_match_modtype:
|
|
* verify the match for a given descriptor and a given module type.
|
|
*
|
|
* Parameters:
|
|
* desc: descriptor to verify
|
|
* modtype_: module type to look for.
|
|
* Return Value:
|
|
* 1 if given descriptor has given module type,
|
|
* 0 succesfull.
|
|
* -1 if a given parameter is bogus.
|
|
*/
|
|
static int descriptor_match_modtype(TCModuleDescriptor *desc,
|
|
void *modtype_)
|
|
{
|
|
char *modtype = modtype_;
|
|
if (!desc || !modtype) {
|
|
return -1;
|
|
}
|
|
if (desc->status == TC_DESCRIPTOR_DONE
|
|
&& desc->type != NULL
|
|
&& (strcmp(desc->type, modtype) == 0)) {
|
|
/* found it! */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* descriptor_is_free:
|
|
* verify the match for a given descriptor is an unitialized one.
|
|
*
|
|
* Parameters:
|
|
* desc: descriptor to verify
|
|
* unused: dummy parameter to achieve API conformancy.
|
|
* Return Value:
|
|
* 1 if given descriptor is a free one (uninitialized),
|
|
* 0 succesfull.
|
|
* -1 if a given parameter is bogus.
|
|
*/
|
|
static int descriptor_is_free(TCModuleDescriptor *desc, void *unused)
|
|
{
|
|
if (!desc) {
|
|
return -1;
|
|
}
|
|
if (desc->status == TC_DESCRIPTOR_FREE) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* descriptor_init:
|
|
* initialize a plugin descriptor with valid defaults.
|
|
*
|
|
* Parameters:
|
|
* desc: descriptor to initialize.
|
|
* unused: dummy parameter to achieve API conformancy.
|
|
* Return Value:
|
|
* 0 succesfull.
|
|
* -1 if a given parameter is bogus.
|
|
*/
|
|
static int descriptor_init(TCModuleDescriptor *desc, void *unused)
|
|
{
|
|
if (!desc) {
|
|
return -1;
|
|
}
|
|
|
|
desc->status = TC_DESCRIPTOR_FREE;
|
|
memcpy(&(desc->info), &dummy_info, sizeof(TCModuleInfo));
|
|
desc->klass.info = &(desc->info);
|
|
desc->type = NULL;
|
|
desc->so_handle = NULL;
|
|
desc->ref_count = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* descriptor_fini:
|
|
* finalize a plugin descriptor, releasing all acquired
|
|
* resources.
|
|
*
|
|
* Parameters:
|
|
* desc: descriptor to finalize.
|
|
* unused: dummy parameter to achieve API conformancy.
|
|
* Return Value:
|
|
* 1 if given descriptor has still some live instances around,
|
|
* 0 succesfull.
|
|
* -1 if a given parameter is bogus.
|
|
* Side effects:
|
|
* A plugin will be released and unloaded (via dlclose()).
|
|
*/
|
|
static int descriptor_fini(TCModuleDescriptor *desc, void *unused)
|
|
{
|
|
if (!desc) {
|
|
return -1;
|
|
}
|
|
|
|
/* can't finalize an descriptor with some living instances still around */
|
|
if (desc->ref_count > 0) {
|
|
return 1;
|
|
}
|
|
|
|
if (desc->status == TC_DESCRIPTOR_DONE) {
|
|
if (desc->type != NULL) {
|
|
tc_free((void*)desc->type); /* avoid const warning */
|
|
desc->type = NULL;
|
|
}
|
|
if (desc->so_handle != NULL) {
|
|
dlclose(desc->so_handle);
|
|
desc->so_handle = NULL;
|
|
}
|
|
desc->status = TC_DESCRIPTOR_FREE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* just a thin wrapper to adapt API */
|
|
static int find_by_modtype(TCFactory factory, const char *modtype)
|
|
{
|
|
int ret, id;
|
|
ret = tc_foreach_descriptor(factory, descriptor_match_modtype,
|
|
(void*)modtype, &id);
|
|
/* ret >= 1 -> found something */
|
|
return (ret >= 1) ?id : -1;
|
|
}
|
|
|
|
/* just a thin wrapper to adapt API */
|
|
static int find_first_free_descriptor(TCFactory factory)
|
|
{
|
|
int ret, id;
|
|
ret = tc_foreach_descriptor(factory, descriptor_is_free, NULL, &id);
|
|
/* ret >= 1 -> found something */
|
|
return (ret >= 1) ?id : -1;
|
|
}
|
|
|
|
/* Yeah, is that simple. Yet. ;) */
|
|
static void make_modtype(char *buf, size_t bufsize,
|
|
const char *modclass, const char *modname)
|
|
{
|
|
tc_snprintf(buf, bufsize, "%s:%s", modclass, modname);
|
|
}
|
|
|
|
/*
|
|
* tc_module_class_copy:
|
|
* copy a module class into another one. Can perform
|
|
* a soft (reference) copy or a hard (full) one.
|
|
* Only non-null function pointer to plugin operations
|
|
* will be copied.
|
|
* soft copy: make the two classes points to same real data.
|
|
* hard copy: make two independent copies duplicating the data.
|
|
*
|
|
* Parameters:
|
|
* klass: source class to be copied.
|
|
* nklass: class destionation of copy.
|
|
* soft_copy: boolean flag: if !0 do a soft copy,
|
|
* do an hard one otherwise.
|
|
* Return Value:
|
|
* 0 successfull
|
|
* -1 given (at least) a bad TCModuleClass reference
|
|
* 1 not enough memory to perform a full copy
|
|
* Postconditions:
|
|
* destination class is a copy of source class.
|
|
*/
|
|
|
|
#define COPY_IF_NOT_NULL(field) do { \
|
|
if (klass->field != NULL) { \
|
|
nklass->field = klass->field; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int tc_module_class_copy(const TCModuleClass *klass,
|
|
TCModuleClass *nklass)
|
|
{
|
|
if (!klass || !nklass) {
|
|
/* 'impossible' condition */
|
|
tc_log_error(__FILE__, "bad module class reference for setup: %s%s",
|
|
(!klass) ?"plugin class" :"",
|
|
(!nklass) ?"core class" :"");
|
|
return -1;
|
|
}
|
|
|
|
if (!klass->init || !klass->fini || !klass->configure || !klass->stop
|
|
|| !klass->inspect) {
|
|
/* should'nt happen */
|
|
tc_log_error(__FILE__, "can't setup a module class without "
|
|
"one or more mandatory methods");
|
|
return -1;
|
|
}
|
|
|
|
/* register only method really provided by given class */
|
|
nklass->init = klass->init;
|
|
nklass->fini = klass->fini;
|
|
nklass->configure = klass->configure;
|
|
nklass->stop = klass->stop;
|
|
nklass->inspect = klass->inspect;
|
|
|
|
nklass->info = klass->info;
|
|
|
|
COPY_IF_NOT_NULL(encode_audio);
|
|
COPY_IF_NOT_NULL(encode_video);
|
|
COPY_IF_NOT_NULL(decode_audio);
|
|
COPY_IF_NOT_NULL(decode_video);
|
|
COPY_IF_NOT_NULL(filter_audio);
|
|
COPY_IF_NOT_NULL(filter_video);
|
|
COPY_IF_NOT_NULL(multiplex);
|
|
COPY_IF_NOT_NULL(demultiplex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#undef COPY_IF_NOT_NULL
|
|
|
|
/*************************************************************************
|
|
* module versioning helpers *
|
|
*************************************************************************/
|
|
|
|
struct tcmodver {
|
|
int reserved;
|
|
int major;
|
|
int minor;
|
|
int micro;
|
|
};
|
|
|
|
static void expand_version(uint32_t version, struct tcmodver *modver)
|
|
{
|
|
modver->reserved = (version & 0xFF000000) >> 24;
|
|
modver->major = (version & 0x00FF0000) >> 16;
|
|
modver->minor = (version & 0x0000FF00) >> 8;
|
|
modver->micro = (version & 0x000000FF);
|
|
}
|
|
|
|
/*
|
|
* tc_module_version_matches:
|
|
* check compatibilty between the transcode core version and
|
|
* a supplied module version.
|
|
* Only a major version mismatch gives incompatibility (...yet).
|
|
*
|
|
* Parameters:
|
|
* modversion: the module version being checked.
|
|
* Return Value:
|
|
* 0 if module is incompatible with transcode core
|
|
* !0 otherwise.
|
|
* Side Effects:
|
|
* in case of incompatibilty (of any degree), messages are
|
|
* tc_log()'d out.
|
|
*/
|
|
static int tc_module_version_matches(uint32_t modversion)
|
|
{
|
|
struct tcmodver ver_core, ver_mod;
|
|
|
|
expand_version(TC_MODULE_VERSION, &ver_core);
|
|
expand_version(modversion, &ver_mod);
|
|
|
|
if (ver_core.reserved != ver_mod.reserved) {
|
|
tc_log_error(__FILE__, "internal version error");
|
|
return 0;
|
|
}
|
|
|
|
/* different major versions are a no-no */
|
|
if (ver_core.major != ver_mod.major) {
|
|
tc_log_error(__FILE__, "incompatible module version "
|
|
"(core=%i.%i.%i|module=%i.%i.%i)",
|
|
ver_core.major, ver_core.minor, ver_core.micro,
|
|
ver_mod.major, ver_mod.minor, ver_mod.micro);
|
|
return 0;
|
|
}
|
|
/* if you use different minor version, you've to know that */
|
|
if (ver_core.minor != ver_mod.minor) {
|
|
tc_log_error(__FILE__, "old module version "
|
|
"(core=%i.%i.%i|module=%i.%i.%i)",
|
|
ver_core.major, ver_core.minor, ver_core.micro,
|
|
ver_mod.major, ver_mod.minor, ver_mod.micro);
|
|
/* still compatible! */
|
|
}
|
|
/* different micro version are ok'd silently */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*************************************************************************
|
|
* main private helpers: _load and _unload *
|
|
*************************************************************************/
|
|
|
|
#define RETURN_IF_INVALID_STRING(str, msg, errval) do { \
|
|
if (!str || !strlen(str)) { \
|
|
tc_log_error(__FILE__, msg); \
|
|
return (errval); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define RETURN_IF_INVALID_QUIET(val, errval) do { \
|
|
if (!(val)) { \
|
|
return (errval); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define TC_LOG_DEBUG(fp, level, format, ...) do { \
|
|
if ((fp)->verbose >= level) { \
|
|
tc_log_info(__FILE__, format, __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
/*
|
|
* tc_load_module:
|
|
* load in a given factory a plugin needed for a given module.
|
|
* please note that here 'plugin' and 'module' terms are used
|
|
* interchangeably since a given module name from a given module
|
|
* class usually (almost always, even if such constraint doesn't
|
|
* exist) originates from a plugin with same class and same name.
|
|
*
|
|
* In other words, doesn't exist (yet, nor is planned) a plugin
|
|
* that can generate more than one module and/or more than one
|
|
* module class
|
|
*
|
|
* Parameters:
|
|
* factory: module factory to loads module in
|
|
* modclass: class of plugin to load
|
|
* modname: name of plugin to load
|
|
* Return Value:
|
|
* >= 0 identifier (slot) of newly loaded plugin
|
|
* -1 error occcurred (and notified via tc_log*())
|
|
* Side effects:
|
|
* a plugin (.so) is loaded into process
|
|
* Preconditions:
|
|
* none.
|
|
* Postconditions:
|
|
* none
|
|
*/
|
|
static int tc_load_module(TCFactory factory,
|
|
const char *modclass, const char *modname)
|
|
{
|
|
int id = -1, ret = -1;
|
|
char full_modpath[PATH_MAX];
|
|
char modtype[MOD_TYPE_MAX_LEN];
|
|
TCModuleEntry modentry = NULL;
|
|
TCModuleDescriptor *desc = NULL;
|
|
const TCModuleClass *nclass;
|
|
|
|
/* 'impossible' conditions */
|
|
RETURN_IF_INVALID_STRING(modclass, "empty module class", -1);
|
|
RETURN_IF_INVALID_STRING(modname, "empty module name", -1);
|
|
|
|
make_modtype(modtype, PATH_MAX, modclass, modname);
|
|
tc_snprintf(full_modpath, PATH_MAX, "%s/%s_%s.so",
|
|
factory->mod_path, modclass, modname);
|
|
|
|
id = find_first_free_descriptor(factory);
|
|
if (id == -1) {
|
|
/* should'nt happen */
|
|
tc_log_error(__FILE__, "already loaded the maximum number "
|
|
"of modules (%i)", TC_FACTORY_MAX_HANDLERS);
|
|
return -1;
|
|
}
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "using slot %i for plugin '%s'",
|
|
id, modtype);
|
|
desc = &(factory->descriptors[id]);
|
|
desc->ref_count = 0;
|
|
|
|
desc->so_handle = dlopen(full_modpath, RTLD_GLOBAL | RTLD_NOW);
|
|
if (!desc->so_handle) {
|
|
TC_LOG_DEBUG(factory, TC_INFO, "can't load module '%s'; reason: %s",
|
|
modtype, dlerror());
|
|
goto failed_dlopen;
|
|
}
|
|
desc->type = tc_strdup(modtype);
|
|
if (!desc->type) {
|
|
goto failed_strdup;
|
|
}
|
|
desc->status = TC_DESCRIPTOR_CREATED;
|
|
|
|
/* soft copy is enough here, since information will be overwritten */
|
|
tc_module_class_copy(&dummy_class, &(desc->klass));
|
|
|
|
modentry = dlsym(desc->so_handle, "tc_plugin_setup");
|
|
if (!modentry) {
|
|
TC_LOG_DEBUG(factory, TC_INFO, "module '%s' doesn't have new style"
|
|
" entry point", modtype);
|
|
goto failed_setup;
|
|
}
|
|
nclass = modentry();
|
|
|
|
if (!tc_module_version_matches(nclass->version)) {
|
|
/* reason already tc_log'd out */
|
|
goto failed_setup;
|
|
}
|
|
|
|
ret = tc_module_class_copy(nclass, &(desc->klass));
|
|
|
|
if (ret != 0) {
|
|
/* should'nt happen */
|
|
tc_log_error(__FILE__, "failed class registration for module '%s'",
|
|
modtype);
|
|
goto failed_setup;
|
|
}
|
|
|
|
desc->klass.id = id; /* enforce class/descriptor id */
|
|
desc->status = TC_DESCRIPTOR_DONE;
|
|
factory->descriptor_count++;
|
|
|
|
return id;
|
|
|
|
failed_setup:
|
|
desc->status = TC_DESCRIPTOR_FREE;
|
|
tc_free((void*)desc->type); /* avoid const warning */
|
|
failed_strdup:
|
|
dlclose(desc->so_handle);
|
|
failed_dlopen:
|
|
return -1;
|
|
}
|
|
|
|
#define CHECK_VALID_ID(id, where) do { \
|
|
if (id < 0 || id > TC_FACTORY_MAX_HANDLERS) { \
|
|
if (factory->verbose >= TC_DEBUG) { \
|
|
tc_log_error(__FILE__, "%s: invalid id (%i)", where, id); \
|
|
} \
|
|
return -1; \
|
|
} \
|
|
} while (0)
|
|
|
|
/*
|
|
* tc_unload_module:
|
|
* unload a given (by id) plugin from given factory.
|
|
* This means that module belonging to such plugin is no longer
|
|
* avalaible from given factory, unless, of course, reloading such
|
|
* plugin.
|
|
*
|
|
* Parameters:
|
|
* factory: a module factory
|
|
* id: id of plugin to unload
|
|
* Return Value:
|
|
* 0 plugin unloaded correctly
|
|
* != 0 error occcurred (and notified via tc_log*())
|
|
* Side effects:
|
|
* a plugin (.so) is UNloaded from process
|
|
* Preconditions:
|
|
* reference count for given plugin is zero.
|
|
* This means that no modules instances created by such plugin are
|
|
* still active.
|
|
* Postconditions:
|
|
* none
|
|
*/
|
|
static int tc_unload_module(TCFactory factory, int id)
|
|
{
|
|
int ret = 0;
|
|
TCModuleDescriptor *desc = NULL;
|
|
|
|
CHECK_VALID_ID(id, "tc_unload_module");
|
|
desc = &(factory->descriptors[id]);
|
|
|
|
if (desc->ref_count > 0) {
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "can't unload a module with active"
|
|
" ref_count (id=%i, ref_count=%i)",
|
|
desc->klass.id, desc->ref_count);
|
|
return 1;
|
|
}
|
|
|
|
ret = descriptor_fini(desc, NULL);
|
|
if (ret == 0) {
|
|
factory->descriptor_count--;
|
|
return 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* implementation of exported functions *
|
|
*************************************************************************/
|
|
|
|
TCFactory tc_new_module_factory(const char *modpath, int verbose)
|
|
{
|
|
TCFactory factory = NULL;
|
|
RETURN_IF_INVALID_STRING(modpath, "empty module path", NULL);
|
|
|
|
factory = tc_zalloc(sizeof(struct tcfactory_));
|
|
RETURN_IF_INVALID_QUIET(factory, NULL);
|
|
|
|
factory->mod_path = modpath;
|
|
factory->verbose = verbose;
|
|
factory->descriptor_count = 0;
|
|
factory->instance_count = 0;
|
|
|
|
tc_foreach_descriptor(factory, descriptor_init, NULL, NULL);
|
|
|
|
return factory;
|
|
}
|
|
|
|
int tc_del_module_factory(TCFactory factory)
|
|
{
|
|
RETURN_IF_INVALID_QUIET(factory, 1);
|
|
|
|
tc_foreach_descriptor(factory, descriptor_fini, NULL, NULL);
|
|
|
|
if (factory->descriptor_count > 0) {
|
|
/* should'nt happpen */
|
|
tc_log_warn(__FILE__, "left out %i module descriptors",
|
|
factory->descriptor_count);
|
|
return -1;
|
|
}
|
|
|
|
tc_free(factory);
|
|
return 0;
|
|
}
|
|
|
|
TCModule tc_new_module(TCFactory factory,
|
|
const char *modclass, const char *modname,
|
|
int media)
|
|
{
|
|
char modtype[MOD_TYPE_MAX_LEN];
|
|
uint32_t flags = translate_modclass(modclass);
|
|
int id = -1, ret;
|
|
TCModule module = NULL;
|
|
|
|
RETURN_IF_INVALID_QUIET(factory, NULL);
|
|
if (flags == TC_MODULE_FEATURE_NONE) {
|
|
TC_LOG_DEBUG(factory, TC_INFO, "unknown module class '%s'",
|
|
modclass);
|
|
return NULL;
|
|
}
|
|
|
|
make_modtype(modtype, MOD_TYPE_MAX_LEN, modclass, modname);
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "trying to load '%s'", modtype);
|
|
id = find_by_modtype(factory, modtype);
|
|
if (id == -1) {
|
|
/* module type not already known */
|
|
TC_LOG_DEBUG(factory, TC_STATS, "plugin not found for '%s',"
|
|
" loading...", modtype);
|
|
id = tc_load_module(factory, modclass, modname);
|
|
if (id == -1) {
|
|
/* load failed, give up */
|
|
return NULL;
|
|
}
|
|
}
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "module descriptor found: id %i", id);
|
|
|
|
module = tc_zalloc(sizeof(struct tcmodule_));
|
|
module->instance.type = factory->descriptors[id].type;
|
|
module->instance.id = factory->instance_count + 1;
|
|
module->klass = &(factory->descriptors[id].klass);
|
|
|
|
ret = tc_module_init(module, flags|translate_media(media));
|
|
if (ret != 0) {
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "initialization of '%s' failed"
|
|
" (code=%i)", modtype, ret);
|
|
tc_free(module);
|
|
return NULL;
|
|
}
|
|
|
|
factory->descriptors[id].ref_count++;
|
|
factory->instance_count++;
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "module created: type='%s'"
|
|
" instance id=(%i)", module->instance.type,
|
|
module->instance.id);
|
|
TC_LOG_DEBUG(factory, TC_STATS, "descriptor ref_count=(%i) instances"
|
|
" so far=(%i)", factory->descriptors[id].ref_count,
|
|
factory->instance_count);
|
|
|
|
return module;
|
|
}
|
|
|
|
int tc_del_module(TCFactory factory, TCModule module)
|
|
{
|
|
int ret = 0, id = -1;
|
|
|
|
RETURN_IF_INVALID_QUIET(factory, 1);
|
|
RETURN_IF_INVALID_QUIET(module, -1);
|
|
|
|
id = module->klass->id;
|
|
CHECK_VALID_ID(id, "tc_del_module");
|
|
|
|
ret = tc_module_fini(module);
|
|
if (ret != 0) {
|
|
TC_LOG_DEBUG(factory, TC_DEBUG, "finalization of '%s' failed"
|
|
" (code=%i)", module->instance.type, ret);
|
|
return ret;
|
|
}
|
|
tc_free(module);
|
|
|
|
factory->instance_count--;
|
|
factory->descriptors[id].ref_count--;
|
|
if (factory->descriptors[id].ref_count == 0) {
|
|
ret = tc_unload_module(factory, id);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*************************************************************************
|
|
* Debug helpers. *
|
|
*************************************************************************/
|
|
|
|
int tc_plugin_count(const TCFactory factory)
|
|
{
|
|
RETURN_IF_INVALID_QUIET(factory, -1);
|
|
return factory->descriptor_count;
|
|
}
|
|
|
|
int tc_instance_count(const TCFactory factory)
|
|
{
|
|
RETURN_IF_INVALID_QUIET(factory, -1);
|
|
return factory->instance_count;
|
|
}
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
int tc_compare_modules(const TCModule amod, const TCModule bmod)
|
|
{
|
|
assert(amod != NULL && bmod != NULL);
|
|
|
|
if ((amod == bmod) || (amod->instance.id == bmod->instance.id)) {
|
|
return 1;
|
|
}
|
|
|
|
if (strcmp(amod->instance.type, bmod->instance.type) == 0) {
|
|
/* some internal sanity checks.
|
|
* assert()s are used here because those conditions
|
|
* WILL NOT *NEVER* BE FALSE!
|
|
* otherwise something _*really*_ evil is going on
|
|
*/
|
|
assert(amod->klass != NULL && bmod->klass != NULL);
|
|
assert(amod->klass == bmod->klass);
|
|
assert(amod->klass->id == bmod->klass->id);
|
|
assert(amod->klass->info == bmod->klass->info);
|
|
/* we should check method pointers as well? */
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|