--- /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 <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);
+}
+
+