src/spbot/module.c
author Tero Marttila <terom@fixme.fi>
Wed, 27 May 2009 23:57:48 +0300
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 180 src/module.c@22967b165692
child 218 5229a5d098b2
permissions -rw-r--r--
nexus.c compiles
#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);
}