diff -r a10ba529ae39 -r 7728d6ec3abf src/spbot/module.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spbot/module.c Wed May 27 23:57:48 2009 +0300 @@ -0,0 +1,407 @@ +#include "module.h" +#include "log.h" + +#include +#include +#include +#include +#include + +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 "_". + */ +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); +} + +