#include "module.h"
#include "log.h"
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <string.h>
#include <assert.h>
const struct error_list module_errors = ERROR_LIST("module",
ERROR_TYPE_STRING( ERR_MODULE_NAME, "invalid module name" ),
ERROR_TYPE( ERR_MODULE_DUP, "module already loaded" ),
ERROR_TYPE_STRING( ERR_MODULE_PATH, "invalid module path" ),
ERROR_TYPE_STRING( ERR_MODULE_OPEN, "module dlopen() failed" ),
ERROR_TYPE_STRING( ERR_MODULE_SYM, "module dlsym() failed" ),
ERROR_TYPE_STRING( ERR_MODULE_INIT_FUNC, "invalid module init func" ),
ERROR_TYPE_STRING( ERR_MODULE_CONF, "module_conf" )
);
err_t modules_create (struct modules **modules_ptr, struct nexus *nexus)
{
struct modules *modules;
// alloc
if ((modules = calloc(1, sizeof(*modules))) == NULL)
return ERR_CALLOC;
// init
TAILQ_INIT(&modules->list);
// store
modules->nexus = nexus;
// ok
*modules_ptr = modules;
return SUCCESS;
}
const char* modules_path (struct modules *modules, const char *path)
{
const char *old_path = modules->path;
if (path)
// replace
modules->path = path;
return old_path;
}
/**
* module_get without the refcount stuff
*/
static struct module* modules_lookup (struct modules *modules, const char *name)
{
struct module *module = NULL;
// look for it...
TAILQ_FOREACH(module, &modules->list, modules_list) {
if (strcasecmp(module->info.name, name) == 0) {
// found it
return module;
}
}
// no such module
return NULL;
}
struct module* modules_get (struct modules *modules, const char *name)
{
struct module *module;
// get it
if (!(module = modules_lookup(modules, name)))
return NULL;
// up the refcount
module->refcount++;
// ok
return module;
}
err_t modules_unload (struct modules *modules)
{
struct module *module;
err_t err;
// unload each module in turn
while ((module = TAILQ_FIRST(&modules->list))) {
if ((err = module_unload(module)))
log_warn("module_unload(%s) failed: %s", module->info.name, error_name(err));
}
// ok
return SUCCESS;
}
void modules_destroy (struct modules *modules)
{
struct module *module;
// destroy each module
while ((module = TAILQ_FIRST(&modules->list)))
module_destroy(module);
// ourselves
free(modules);
}
const char* module_name (struct module *module)
{
return module ? module->info.name : NULL;
}
/**
* Load the symbol named "<module>_<suffix>".
*/
static err_t module_symbol (struct module *module, void **sym, const char *suffix, error_t *err)
{
char sym_name[MODULE_SYMBOL_MAX];
// validate the length of the suffix
assert(strlen(module->info.name) <= MODULE_NAME_MAX);
assert(strlen(suffix) <= MODULE_SUFFIX_MAX);
// format
sprintf(sym_name, "%s_%s", module->info.name, suffix);
// load
if ((*sym = dlsym(module->handle, sym_name)) == NULL)
return SET_ERROR_STR(err, &module_errors, ERR_MODULE_SYM, sym_name);
// ok
return SUCCESS;
}
/**
* XXX: ugly function to format to a dynamically allocated string
*/
char *strfmt (const char *fmt, ...)
{
va_list vargs;
size_t len;
char *buf;
// figure out the length of the resulting string
va_start(vargs, fmt);
len = vsnprintf(NULL, 0, fmt, vargs) + 1;
va_end(vargs);
// malloc
if ((buf = malloc(len)) == NULL)
return NULL;
// format
va_start(vargs, fmt);
vsnprintf(buf, len, fmt, vargs);
va_end(vargs);
// ok
return buf;
}
/**
* Looks up a module's path by name, returning a dynamically allocated string with the path on success
*/
static char* module_find (struct modules *modules, struct module_info *info, error_t *err)
{
char *path = NULL;
// build the path...
if ((path = strfmt("%s/mod_%s.so", modules->path, info->name)) == NULL)
JUMP_SET_ERROR(err, &general_errors, ERR_MEM);
// exists and readable?
if (access(path, R_OK))
// XXX: this doesn't contain the path...
JUMP_SET_ERROR_STR(err, &module_errors, ERR_MODULE_PATH, "module not found in search path");
// ok
return path;
error:
// release the dynamically allocated path
free(path);
return NULL;
}
err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *_info, error_t *err)
{
struct module *module;
// validate the module name
if (strlen(_info->name) > MODULE_NAME_MAX)
return SET_ERROR(err, &module_errors, ERR_MODULE_NAME);
// already open with the same name?
if (modules_lookup(modules, _info->name))
return SET_ERROR(err, &module_errors, ERR_MODULE_DUP);
// alloc
if ((module = calloc(1, sizeof(*module))) == NULL)
return SET_ERROR(err, &module_errors, ERR_CALLOC);
// initialize
module->info = *_info;
module->modules = modules;
module->refcount = 1;
// add to modules list
TAILQ_INSERT_TAIL(&modules->list, module, modules_list);
// lookup path?
if (!module->info.path) {
// have a search path?
if (!modules->path)
JUMP_SET_ERROR_STR(err, &module_errors, ERR_MODULE_PATH, "no module search path defined");
// try and resolve the path automatically
if ((module->path_buf = module_find(modules, &module->info, err)) == NULL)
goto error;
// use that path
module->info.path = module->path_buf;
}
// clear dlerrors
(void) dlerror();
// load it
if ((module->handle = dlopen(module->info.path, RTLD_NOW)) == NULL)
JUMP_SET_ERROR_STR(err, &module_errors, ERR_MODULE_OPEN, dlerror());
// load the funcs symbol
if ((ERROR_CODE(err) = module_symbol(module, (void **) &module->desc, "module")))
JUMP_SET_ERROR_STR(err, &module_errors, ERROR_CODE(err), dlerror());
// call the init func
if ((module->desc->init(modules->nexus, &module->ctx, err)))
goto error;
// ok
if (module_ptr) {
module->refcount++;
*module_ptr = module;
}
return SUCCESS;
error:
// cleanup
module->refcount = 0;
module_destroy(module);
return ERROR_CODE(err);
}
void module_put (struct module *module)
{
assert(module->refcount > 0);
// decrement, just return if still alive
if (--module->refcount > 0)
return;
// refcount zero, destroy
module_destroy(module);
}
err_t module_conf_raw (struct module *module, const char *name, char *value, error_t *err)
{
// sanity-check
if (!module->desc->config_options)
RETURN_SET_ERROR_STR(err, &module_errors, ERR_MODULE_CONF, "module does not have any config options");
// wrong state
if (module->unloading)
RETURN_SET_ERROR_STR(err, &module_errors, ERR_MODULE_CONF, "module is being unloaded");
// parse/apply using the module's stuff
// XXX: error namespaces?
return config_apply_raw(module->desc->config_options, module->modules->nexus, module->ctx, name, value, err);
}
const struct config_option* module_conf_lookup (struct module *module, const char *name, error_t *err)
{
// sanity-check
if (!module->desc->config_options)
JUMP_SET_ERROR_STR(err, &module_errors, ERR_MODULE_CONF, "module does not have any config options");
// direct lookup
// XXX: error namespaces?
return config_lookup(module->desc->config_options, name, err);
error:
return NULL;
}
err_t module_conf (struct module *module, const struct config_option *opt, const struct config_value *value, error_t *err)
{
// wrong state
if (module->unloading)
RETURN_SET_ERROR_STR(err, &module_errors, ERR_MODULE_CONF, "module is being unloaded");
// apply with the module's ctx
// XXX: error namespaces?
return config_apply_opt(opt, module->ctx, value, err);
}
err_t module_unload (struct module *module)
{
err_t err;
// wrong state
if (module->unloading)
return ERR_MODULE_STATE;
// remove from modules list
TAILQ_REMOVE(&module->modules->list, module, modules_list);
// update status
module->unloading = true;
if (module->desc->unload) {
// invoke the unload func, passing it our reference
// note that the module might not exist anymore after calling this
if ((err = module->desc->unload(module->ctx, module))) {
// mark it as "unloaded"
module_unloaded(module);
return err;
}
} else {
// no unload procedure, just destroy it as soon as needed
module_unloaded(module);
}
// ok
return SUCCESS;
}
/**
* A wrapper for module_destroy suitable for use as a libevent callback
*/
static void _module_destroy (int fd, short what, void *arg)
{
struct module *module = arg;
(void) fd;
(void) what;
// execute
module_destroy(module);
}
void module_unloaded (struct module *module)
{
struct timeval tv = { 0, 0 };
assert(module->refcount > 0);
// decrement, just return if still alive
if (--module->refcount > 0)
return;
// schedule a deferred module_destroy
if (event_base_once(module->modules->nexus->ev_base, -1, EV_TIMEOUT, &_module_destroy, module, &tv))
// XXX: how to reach?
log_fatal("event_base_once failed, unable to schedule deferred module_destroy");
}
void module_destroy (struct module *module)
{
// XXX: warn about destroy with significant refcount...
if (module->refcount)
log_warn("destroying module %s with refcount>0: %zu", module_name(module), module->refcount);
else
log_debug("destroying module %s", module_name(module));
// still need to remove from modules_list?
if (!module->unloading)
TAILQ_REMOVE(&module->modules->list, module, modules_list);
// run the destroy hook
if (module->desc && module->desc->destroy && module->ctx)
module->desc->destroy(module->ctx);
// unload the dl handle
if (module->handle && dlclose(module->handle))
log_warn("dlclose(%s): %s", module->info.name, dlerror());
// release the path buf, if any
free(module->path_buf);
// free the module info
free(module);
}