nexus.c compiles new-lib-errors
authorTero Marttila <terom@fixme.fi>
Wed, 27 May 2009 23:57:48 +0300
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 216 a10ba529ae39
child 218 5229a5d098b2
nexus.c compiles
src/CMakeLists.txt
src/config.c
src/config.h
src/console.c
src/console.h
src/irc_chan.c
src/irc_chan.h
src/irc_client.c
src/irc_client.h
src/irc_conn.c
src/irc_conn.h
src/irc_net.c
src/irc_net.h
src/irc_queue.c
src/irc_queue.h
src/lib/error.c
src/lib/error.h
src/lib/log.c
src/lib/log.h
src/lib/object.c
src/lib/object.h
src/line_proto.h
src/log.c
src/log.h
src/lua_config.c
src/lua_config.h
src/lua_console.c
src/lua_console.h
src/module.c
src/module.h
src/nexus.c
src/nexus.h
src/nexus_lua.c
src/nexus_lua.h
src/object.c
src/object.h
src/signals.c
src/signals.h
src/spbot/config.c
src/spbot/config.h
src/spbot/module.c
src/spbot/module.h
src/spbot/nexus.c
src/spbot/nexus.h
src/spbot/signals.c
src/spbot/signals.h
--- a/src/CMakeLists.txt	Wed May 27 23:07:00 2009 +0300
+++ b/src/CMakeLists.txt	Wed May 27 23:57:48 2009 +0300
@@ -9,15 +9,20 @@
 # add our include path
 include_directories (${LibEvent_INCLUDE_DIRS} ${GnuTLS_INCLUDE_DIRS} ${Evsql_INCLUDE_DIRS} ${Lua51_INCLUDE_DIRS} ${PCRE_INCLUDE_DIRS})
 
+# XXX: hack until we separate these
+include_directories (${CMAKE_CURRENT_SOURCE_DIR})
+
 # define our source code modules
-set (CORE_SOURCES error.c log.c str.c object.c)
+set (LIB_SOURCES lib/error.c lib/log.c lib/str.c lib/object.c)
 set (IO_SOURCES transport.c service.c transport_fd.c sock.c resolve.c tcp.c tcp_transport.c tcp_client.c tcp_server.c ssl.c ssl_client.c fifo.c)
 set (PROTO_SOURCES line_proto.c msg_proto.c)
 set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c irc_net_connect.c)
 set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c lua_thread.c)
 set (CONSOLE_SOURCES console.c lua_console.c)
 
-set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
+set (SPBOT_SOURCES spbot/nexus.c spbot/signals.c spbot/module.c spbot/config.c)
+
+set (NEXUS_SOURCES ${SPBOT_SOURCES} ${LIB_SOURCES} ${IO_SOURCES} ${PROTO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES})
 set (IRC_LOG_SOURCES modules/irc_log.c)
 set (LOGWATCH_SOURCES modules/logwatch.c modules/logwatch_source.c modules/logwatch_filter.c modules/logwatch_chan.c)
 
--- a/src/config.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,260 +0,0 @@
-#include "config.h"
-
-#include <string.h>
-#include <assert.h>
-
-const struct config_option* config_lookup (const struct config_option *options, const char *name, struct error_info *err)
-{
-    const struct config_option *option;
-
-    // find the matching config opt
-    for (option = options; option->name; option++) {
-        if (strcmp(option->name, name) == 0)
-            return option;
-    }
-
-    // not found
-    SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown configuration option");
-
-    return NULL;
-}
-
-int config_params_count (const struct config_option *option)
-{
-    const struct config_param *param;
-    int count = 0;
-
-    // handle each param
-    for (param = option->params; param->name && param->type; param++)
-        count++;
-
-    return count;
-}
-
-/**
- * Parse a raw value for a CONFIG_IRC_CHAN into an irc_chan
- */
-static struct irc_chan* config_parse_irc_chan (struct nexus *nexus, char *raw_value, struct error_info *err)
-{
-    const char *network, *channel;
-    struct irc_chan *chan;
-
-    // XXX: wrong error code
-
-    // parse required args
-    if ((network = strsep(&raw_value, "/")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "invalid <network> for CONFIG_IRC_CHAN value");
-
-    if ((channel = strsep(&raw_value, "/")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "invalid <channel> for CONFIG_IRC_CHAN value");
-    
-    // extraneous stuff?
-    if (raw_value)
-        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "trailing data for CONFIG_IRC_CHAN value");
-    
-    // get the channel?
-    if ((chan = irc_client_get_chan(nexus->client, network, channel)) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_CONFIG_VALUE, "unknown network/channel name for CONFIG_IRC_CHAN value");
-    
-    // ok
-    return chan;
-
-error:
-    return NULL;    
-}
-
-err_t config_parse_param (const struct config_param *param, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err)
-{
-    // parse the value 
-    switch (param->type) {
-        case CONFIG_INVALID:
-            RETURN_SET_ERROR_STR(err, ERR_CONFIG_TYPE, "invalid value for invalid type (too many values?)");
-
-        case CONFIG_STRING:
-            // simple!
-            value->string = raw_value;
-            break;
-
-        case CONFIG_IRC_CHAN:
-            // parse the value
-            if (!(value->irc_chan = config_parse_irc_chan(nexus, raw_value, err)))
-                return ERROR_CODE(err);
-
-            break;
-        
-        case CONFIG_USER:
-            // fail
-            RETURN_SET_ERROR_STR(err, ERR_CONFIG_TYPE, "user type can't be parsed");
-
-        default:
-            NOT_REACHED();
-    }
-
-    // copy the type
-    value->type = param->type;
-
-    // ok
-    return SUCCESS; 
-}
-
-err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err)
-{
-    // use the first param
-    const struct config_param *param = &option->params[0];
-
-    // must have exactly one param
-    if (!param->type || (param + 1)->type)
-        return SET_ERROR(err, ERR_CONFIG_PARAMS);
-    
-    // parse it
-    return config_parse_param(param, nexus, value, raw_value, err);
-}
-
-err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], struct error_info *err)
-{
-    const struct config_param *param;
-    const struct config_value *value;
-
-    // handle each param
-    for (param = option->params, value = values; param->name && param->type; param++) {
-        // no value given, or value given as NULL?
-        if (!value->type || value->type == CONFIG_NULL) {
-            if (param->optional) {
-                // optional, so just ignore the value 
-                
-            } else {
-                // required
-                JUMP_SET_ERROR_STR(err, ERR_CONFIG_REQUIRED, param->name);
-
-            }
-
-        } else if (param->type != value->type) {
-            // invalid type, XXX: also report correct type name?
-            JUMP_SET_ERROR(err, ERR_CONFIG_TYPE);
-
-        } else if (param->is_handler) {
-            // only applicable for non-optional args
-            err_t tmp;
-            
-            // invoke the handler
-            switch (param->type) {
-                case CONFIG_STRING:     tmp = param->func.string(ctx, value->string, err); break;
-                case CONFIG_IRC_CHAN:   tmp = param->func.irc_chan(ctx, value->irc_chan, err); break;
-                default:                NOT_REACHED();
-            }
-            
-            // abort on errors
-            if (tmp)
-                goto error;
-        }
-        
-        // the values list is NULL-terminated, and optional params can be left off
-        if (value->type)
-            value++;
-    }
-
-    // the option's handler?
-    if (option->func && option->func(ctx, option, values, err))
-        goto error;
-
-    // ok
-    return SUCCESS;
-
-error:
-    return ERROR_CODE(err);
-}
-
-err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], struct error_info *err)
-{
-    const struct config_option *option;
-
-    // no matching option found?
-    if (!(option = config_lookup(options, name, err)))
-        return ERROR_CODE(err);
-    
-    // apply it    
-    return config_apply_opt(option, ctx, values, err);
-}
-
-err_t config_apply_string (const struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err)
-{
-    struct config_value conf_value[] = {
-        CONFIG_VALUE_STRING(value),
-        CONFIG_VALUE_END,
-    };
-
-    return config_apply(options, ctx, name, conf_value, err);
-}
-
-err_t config_apply_irc_chan (const struct config_option *options, void *ctx, const char *name, struct irc_chan *value, struct error_info *err)
-{
-    struct config_value conf_value[] = {
-        CONFIG_VALUE_IRC_CHAN(value),
-        CONFIG_VALUE_END,
-    };
-
-    return config_apply(options, ctx, name, conf_value, err);
-}
-
-err_t config_apply_raw (const struct config_option options[], struct nexus *nexus, void *ctx, const char *name, char *raw_value, struct error_info *err)
-{
-    const struct config_option *option;
-    struct config_value value[2] = {
-        CONFIG_VALUE_END,
-        CONFIG_VALUE_END,
-    };
-
-    // no matching option found?
-    if (!(option = config_lookup(options, name, err)))
-        return ERROR_CODE(err);
-    
-    // parse it
-    if (config_parse(option, nexus, &value[0], raw_value, err))
-        return ERROR_CODE(err);
-    
-    // apply it
-    return config_apply_opt(option, ctx, value, err);
-}
-
-const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name)
-{
-    const struct config_param *param;
-    const struct config_value *value;
-    
-    // go through the (param, value) pairs
-    for (param = option->params, value = values; param->name && param->type && value->type; param++, value++) {
-        if (strcmp(param->name, name) == 0) {
-            // not NULL?
-            if (value->type != CONFIG_NULL)
-                return value;
-
-            else
-                return NULL;
-        }
-    }
-
-    // not found
-    return NULL;
-}
-
-const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name)
-{
-    const struct config_value *value;
-
-    return (value = config_get_value(option, values, name)) ? value->string : NULL;
-}
-
-struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name)
-{
-    const struct config_value *value;
-
-    return (value = config_get_value(option, values, name)) ? value->irc_chan : NULL;
-}
-
-void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type)
-{
-    const struct config_value *value;
-
-    return ((value = config_get_value(option, values, name)) && value->user.type == user_type) ? value->user.ptr : NULL;
-}
-
--- a/src/config.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-#ifndef CONFIG_H
-#define CONFIG_H
-
-/**
- * @file
- *
- * Support for module configuration parameters
- */
-#include "error.h"
-#include <stdbool.h>
-
-/** 
- * Different types of configuration parameters
- */
-enum config_type {
-    CONFIG_INVALID,
-
-    /** No value at all */
-    CONFIG_NULL,
-
-    /** A plain NUL-terminated string */
-    CONFIG_STRING,
-
-    /** A user-defined type, further identified by a string */
-    CONFIG_USER,
-
-    /** An IRC channel */
-    CONFIG_IRC_CHAN,
-};
-
-/**
- * A CONFIG_USER type info
- */
-struct config_user_type {
-    /** The name of the type */
-    const char *name;
-};
-
-/**
- * Structure to hold a value as defined by config_type
- */
-struct config_value {
-    /** The type of the value */
-    enum config_type type;
-    
-    /** The typed value */
-    union {
-        /** Value for CONFIG_STRING */
-        char *string;
-
-        /** Value for CONFIG_IRC_CHAN */
-        struct irc_chan *irc_chan;
-        
-        /** Value for CONFIG_USER */
-        struct {
-            /** The specific user type */
-            const struct config_user_type *type;
-
-            /** The pointer value */
-            void *ptr;
-        } user;
-    };
-};
-
-/**
- * Structure to define a single parameter for an option
- */
-struct config_param {
-    /** The name of the arg */
-    const char *name;
-
-    /** The type */
-    enum config_type type;
-
-    /** The specific type for CONFIG_USER */
-    const struct config_user_type *user_type;
-
-    /** Description */
-    const char *description;
-
-    /** Optional value handler function, by type */
-    union {
-        err_t (*string) (void *ctx, char *value, struct error_info *err);
-        err_t (*irc_chan) (void *ctx, struct irc_chan *chan, struct error_info *err);
-    } func;
-
-    /** Use handler function? */
-    bool is_handler;
-
-    /** Optional value? */
-    bool optional;
-};
-
-/**
- * The maximum number of parameters that a single option can have, although this includes the terminating NULL
- */
-#define CONFIG_PARAMS_MAX (15 + 1)
-
-/**
- * The maximum number of values that an option can access, including the terminating NULL
- */
-#define CONFIG_VALUES_MAX CONFIG_PARAMS_MAX
-
-/**
- * A more complicated configuration option that can have multiple parameters
- */
-struct config_option {
-    /** The name of the config option */
-    const char *name;
-
-    /** The list of parameters */
-    const struct config_param params[CONFIG_PARAMS_MAX];
-    
-    /** The handler function */
-    err_t (*func) (void *ctx, const struct config_option *option, const struct config_value values[], struct error_info *err);
-
-    /** Help text */
-    const char *help;
-};
-
-#define CONFIG_PARAM(name, type, desc, optional) \
-    {   name, type, NULL, desc, { NULL }, false, optional }
-
-#define CONFIG_PARAM_OPTIONAL(name, type, desc) \
-    CONFIG_PARAM(name, type, desc, true)
-
-#define CONFIG_PARAM_USER(name, user_type, desc, optional) \
-    {   name, CONFIG_USER, user_type, desc, { NULL }, false, optional }
-
-#define CONFIG_PARAM_USER_OPTIONAL(name, user_type, desc) \
-    CONFIG_PARAM_USER(name, user_type, desc, true) 
-
-#define CONFIG_PARAM_HANDLER(name, type, desc, _func_name_, func) \
-    {   name, type, NULL, desc, {._func_name_ = func }, true, false }
-
-#define CONFIG_PARAM_END \
-    {   NULL, 0, NULL, NULL, { NULL }, false, false }
-
-#define CONFIG_OPT(name, func, help, ...) \
-    {   name, { __VA_ARGS__, CONFIG_PARAM_END }, func, help }
-
-#define CONFIG_OPT_STRING(name, func, desc, help) \
-    CONFIG_OPT(name, NULL, help, CONFIG_PARAM_HANDLER(name, CONFIG_STRING, desc, string, func))
-
-#define CONFIG_OPT_IRC_CHAN(name, func, desc, help) \
-    CONFIG_OPT(name, NULL, help, CONFIG_PARAM_HANDLER(name, CONFIG_IRC_CHAN, desc, irc_chan, func))
-
-#define CONFIG_OPT_END \
-    {   NULL,   {}, NULL, NULL }
-
-#define CONFIG_OPTIONS(...) \
-    { __VA_ARGS__, CONFIG_OPT_END }
-
-#define CONFIG_VALUE(type, _name_, value) \
-    {   (type), { ._name_ = value } }
-
-#define CONFIG_VALUE_STRING(str_value) \
-    CONFIG_VALUE(CONFIG_STRING, string, (str_value))
-
-#define CONFIG_VALUE_IRC_CHAN(irc_chan_value) \
-    CONFIG_VALUE(CONFIG_IRC_CHAN, irc_chan, (irc_chan_value))
-
-#define CONFIG_VALUE_USER(user_type, user_value) \
-    CONFIG_VALUE(CONFIG_USER, user, { user_type, user_value })
-
-#define CONFIG_VALUE_END \
-    {   0, { NULL } }
-
-/**
- * Lookup a config option by name.
- *
- * @param options a CONFIG_OPT_END-terminated array of config_option's
- * @param name the config_option::name to look up
- * @param err returned error info if not found
- * @return a direct pointer to the config_option if found
- */
-const struct config_option* config_lookup (const struct config_option *options, const char *name, struct error_info *err);
-
-/**
- * Returns the number of params that an option has
- *
- * @param option the option to count
- * @return the number of params
- */
-int config_params_count (const struct config_option *option);
-
-// XXX: move this into nexus
-#include "nexus.h"
-/**
- * Parse a raw value into a suitable configuration value for the given param, based on its type.
- *
- * Since this needs to access the application state, you need to pass in the nexus as an argument.
- *
- * Formats supported:
- *  CONFIG_IRC_CHAN         - uses a '<network>/<channel>' format and irc_client_get_chan
- *
- */
-err_t config_parse_param (const struct config_param *param, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err);
-
-/**
- * Parse a raw value into a suitable configuration value for a single-param option, based on the config option type.
- *
- * @param option the option to parse the value for, use config_lookup to find it
- * @param nexus the application state
- * @param value the returned value, if succesfull
- * @param raw_value the raw value to parse based on the type
- * @param err returned error info
- */
-err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err);
-
-/**
- * Apply a list of parsed configuration values to the given config_option struct.
- *
- * The config option handlers take a context argument; the value of this depends on the implementor of the config_option.
- *
- * @param option the option to apply
- * @param ctx the context pointer for the option handler
- * @param values the NULL-terminated array of parsed values
- * @param err returned error info
- */
-err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], struct error_info *err);
-
-/**
- * Apply a list of parsed configuration values for the named config opt.
- *
- * @param options a CONFIG_OPT_END-terminated array of config_option's
- * @param ctx the context pointer for the option handler
- * @param name the config_option::name to look up
- * @param values the NULL-termianted array of parsed values
- * @param err returned error info
- */
-err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], struct error_info *err);
-
-/**
- * Apply a string value for the named config opt.
- *
- * @param options a CONFIG_OPT_END-terminated array of config_option's
- * @param ctx the context pointer for the option handler
- * @param name the config_option::name to look up
- * @param value the string value
- * @param err returned error info
- */
-err_t config_apply_string (const struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err);
-
-/**
- * Apply an irc_chan value for the named config opt.
- *
- * @param options a CONFIG_OPT_END-terminated array of config_option's
- * @param ctx the context pointer for the option handler
- * @param name the config_option::name to look up
- * @param value the irc_chan value
- * @param err returned error info
- */
-err_t config_apply_irc_chan (const struct config_option *options, void *ctx, const char *name, struct irc_chan *value, struct error_info *err);
-
-/**
- * Parse and apply a configuration value for the named config opt.
- *
- * See config_parse() for more info.
- *
- * @param options a CONFIG_OPT_END-terminated array of config_option's
- * @param nexus the application state
- * @param ctx the context pointer for the option handler
- * @param name the config_option::name to look up
- * @param raw_value the raw value to parse
- * @param err returned error info
- *
- * @see config_parse
- */
-err_t config_apply_raw (const struct config_option option[], struct nexus *nexus, void *ctx, const char *name, char *raw_value, struct error_info *err);
-
-/**
- * Lookup a config_value by name from the given list of config_values for the given config_option
- */
-const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name);
-
-const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name);
-struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name);
-void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type);
-
-#endif
--- a/src/console.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/console.c	Wed May 27 23:57:48 2009 +0300
@@ -138,7 +138,7 @@
 }
 
 err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
-        const struct console_callbacks *callbacks, void *cb_arg, struct error_info *err)
+        const struct console_callbacks *callbacks, void *cb_arg, error_t *err)
 {
     struct console *console = &_console;
 
--- a/src/console.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/console.h	Wed May 27 23:57:48 2009 +0300
@@ -81,7 +81,7 @@
  * @param err returned error info
  */
 err_t console_init (struct console **console_ptr, struct event_base *ev_base, const struct console_config *config,
-        const struct console_callbacks *callbacks, void *cb_arg, struct error_info *err);
+        const struct console_callbacks *callbacks, void *cb_arg, error_t *err);
 
 /**
  * Replace the current callbacks with the given new ones.
--- a/src/irc_chan.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_chan.c	Wed May 27 23:57:48 2009 +0300
@@ -275,7 +275,7 @@
     {   NULL,       NULL                                }
 };
 
-err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, struct error_info *err)
+err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, error_t *err)
 {
     struct irc_chan *chan;
 
--- a/src/irc_chan.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_chan.h	Wed May 27 23:57:48 2009 +0300
@@ -156,7 +156,7 @@
  * @param info the channel's identifying information
  * @param err error codes are returned via this
  */
-err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, struct error_info *err);
+err_t irc_chan_create (struct irc_chan **chan_ptr, struct irc_net *net, const struct irc_chan_info *info, error_t *err);
 
 /**
  * Destroy irc_chan state, without doing anything to the network
--- a/src/irc_client.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_client.c	Wed May 27 23:57:48 2009 +0300
@@ -4,7 +4,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-err_t irc_client_create (struct irc_client **client_ptr, struct error_info *err)
+err_t irc_client_create (struct irc_client **client_ptr, error_t *err)
 {
     struct irc_client *client;
 
@@ -50,7 +50,7 @@
         )) == NULL   \
     )
 
-err_t irc_client_add_net (struct irc_client *client, struct irc_net **net_ptr, const struct irc_net_info *_net_info, struct error_info *err)
+err_t irc_client_add_net (struct irc_client *client, struct irc_net **net_ptr, const struct irc_net_info *_net_info, error_t *err)
 {
     struct irc_net *net;
     struct irc_net_info net_info = {
--- a/src/irc_client.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_client.h	Wed May 27 23:57:48 2009 +0300
@@ -4,7 +4,9 @@
 /**
  * @file
  * 
- * Defines the high-level, full-featured IRC 'client' state, which essentially manipulates a set of irc_net's
+ * Defines the high-level, full-featured IRC 'client' state, which essentially manipulates a set of irc_net's.
+ *
+ * XXX: rename to something else
  */
 #include "irc_net.h"
 #include <sys/queue.h>
@@ -43,7 +45,7 @@
 /**
  * Construct a new irc_client
  */
-err_t irc_client_create (struct irc_client **client_ptr, struct error_info *err);
+err_t irc_client_create (struct irc_client **client_ptr, error_t *err);
 
 /**
  * Destroy the irc_client, also destroying all networks
@@ -71,7 +73,7 @@
  * @param net_info info required to identify and connect to the network
  * @param err returned error info
  */
-err_t irc_client_add_net (struct irc_client *client, struct irc_net **net_ptr, const struct irc_net_info *net_info, struct error_info *err);
+err_t irc_client_add_net (struct irc_client *client, struct irc_net **net_ptr, const struct irc_net_info *net_info, error_t *err);
 
 /**
  * Get a pre-existing IRC network by name
--- a/src/irc_conn.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_conn.c	Wed May 27 23:57:48 2009 +0300
@@ -13,7 +13,7 @@
  *
  * For conveniance, this returns the ERROR_CODE
  */
-static err_t irc_conn_set_error (struct irc_conn *conn, struct error_info *err)
+static err_t irc_conn_set_error (struct irc_conn *conn, error_t *err)
 {
     // notify user callback
     conn->callbacks.on_error(conn, err, conn->cb_arg);
@@ -27,7 +27,7 @@
  */
 static err_t irc_conn_set_nickname (struct irc_conn *conn, const char *nickname)
 {
-    struct error_info err;
+    error_t err;
 
     // drop old nickname
     free(conn->nickname);
@@ -213,7 +213,7 @@
 /**
  * Transport failed
  */
-static void irc_conn_on_error (struct error_info *err, void *arg)
+static void irc_conn_on_error (error_t *err, void *arg)
 {
     struct irc_conn *conn = arg;
 
--- a/src/irc_conn.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_conn.h	Wed May 27 23:57:48 2009 +0300
@@ -7,6 +7,8 @@
  * Support for connections to IRC servers, a rather fundamental thing. This holds the line_proto to handle the network
  * communications, and then takes care of sending/receiving commands, as well as some core functionality like
  * responding to PING requests, and tracking our current nickname.
+ *
+ * XXX: split into irc_proto and irc_client
  */
 
 struct irc_conn;
@@ -48,7 +50,7 @@
      *
      * NOTE: Implementing this callback is mandatory
      */
-    void (*on_error) (struct irc_conn *conn, struct error_info *err, void *arg);
+    void (*on_error) (struct irc_conn *conn, error_t *err, void *arg);
 
     /**
      * The connection was closed cleanly after irc_conn_QUIT.
--- a/src/irc_net.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_net.c	Wed May 27 23:57:48 2009 +0300
@@ -39,7 +39,7 @@
 {
     struct irc_net *net = arg;
     struct irc_chan *chan = NULL;
-    struct error_info err;
+    error_t err;
 
     (void) conn;
 
@@ -55,7 +55,7 @@
 /**
  * Our irc_conn has spontaneously failed, destroy it
  */
-static void irc_net_conn_error (struct irc_conn *conn, struct error_info *err, void *arg)
+static void irc_net_conn_error (struct irc_conn *conn, error_t *err, void *arg)
 {
     struct irc_net *net = arg;
 
@@ -250,7 +250,7 @@
     {   NULL,               NULL                    }
 };
 
-err_t irc_net_create (struct irc_net **net_ptr, const struct irc_net_info *info, struct error_info *err)
+err_t irc_net_create (struct irc_net **net_ptr, const struct irc_net_info *info, error_t *err)
 {
     struct irc_net *net;
     
@@ -319,7 +319,7 @@
     free(net);
 }
 
-err_t irc_net_add_chan (struct irc_net *net, struct irc_chan **chan_ptr, const struct irc_chan_info *info, struct error_info *err)
+err_t irc_net_add_chan (struct irc_net *net, struct irc_chan **chan_ptr, const struct irc_chan_info *info, error_t *err)
 {
     struct irc_chan *chan;
 
--- a/src/irc_net.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_net.h	Wed May 27 23:57:48 2009 +0300
@@ -95,7 +95,7 @@
  * @param info network information, used to connect and register
  * @param err return error info
  */
-err_t irc_net_create (struct irc_net **net, const struct irc_net_info *info, struct error_info *err);
+err_t irc_net_create (struct irc_net **net, const struct irc_net_info *info, error_t *err);
 
 /**
  * Destroy an irc_net state without closing anything cleanly. This destroys the irc_conn, if any, and any irc_chans as
@@ -113,7 +113,7 @@
  * @param info the info required to identify and join the channel
  * @param err return error info
  */
-err_t irc_net_add_chan (struct irc_net *net, struct irc_chan **chan_ptr, const struct irc_chan_info *info, struct error_info *err);
+err_t irc_net_add_chan (struct irc_net *net, struct irc_chan **chan_ptr, const struct irc_chan_info *info, error_t *err);
 
 /**
  * Look up an existing irc_chan by name, returning NULL if not found.
--- a/src/irc_queue.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_queue.c	Wed May 27 23:57:48 2009 +0300
@@ -125,7 +125,7 @@
     }
 }
 
-err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, struct error_info *err)
+err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, error_t *err)
 {
     struct irc_queue *queue;
 
--- a/src/irc_queue.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/irc_queue.h	Wed May 27 23:57:48 2009 +0300
@@ -69,7 +69,7 @@
 /**
  * Create a new irc_queue for use with the given line_proto
  */
-err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, struct error_info *err);
+err_t irc_queue_create (struct irc_queue **queue_ptr, struct event_base *ev_base, struct line_proto *lp, error_t *err);
 
 /**
  * Process a line, either sending it directly, or enqueueing it, based on the timer state.
--- a/src/lib/error.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/lib/error.c	Wed May 27 23:57:48 2009 +0300
@@ -1,6 +1,14 @@
 #include "error.h"
 #include "log.h"
 
+const struct error_list general_errors = ERROR_LIST("general",
+    ERROR_TYPE(         ERR_MEM,                "memory allocation error"           ),
+    ERROR_TYPE_STRING(  ERR_NOT_IMPLEMENTED,    "function not implmented"           ),
+    ERROR_TYPE_STRING(  ERR_MISC,               "miscellaneous error"               ),
+    ERROR_TYPE_STRING(  ERR_CMD_OPT,            "invalid command line option"       ),
+    ERROR_TYPE(         ERR_UNKNOWN,            "unknown error"                     )
+);
+
 const struct error_type* error_lookup_code (const struct error_list *list, err_t code)
 {
     const struct error_type *type;
@@ -43,12 +51,12 @@
     return type;
 }
 
-const char* error_name (const error_t *err)
+const char* error_name (const struct error_list *list, err_t code);
 {
     struct error_type *type;
     
     // just do a lookup
-    if ((type = error_lookup(err)))
+    if ((type = error_lookup_code(list, code)))
         return "[unknown]";
 
     else
@@ -119,6 +127,11 @@
     return true;
 }
 
+void error_reset (error_t *err)
+{
+    memset(err, 0, sizeof(*err));
+}
+
 /**
  * Advance error stack to next position, and return pointer.
  *
--- a/src/lib/error.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/lib/error.h	Wed May 27 23:57:48 2009 +0300
@@ -11,6 +11,7 @@
  * tree has it's own set of error codes, and then error info contexts contain a stack of these error codes, which can
  * then be used to trace the error down.
  */
+#include <stdbool.h>
 
 /**
  * The type used to represent a scalar error code, there are only unique per level.
@@ -133,7 +134,7 @@
     } stack[ERROR_DEPTH_MAX];
 
     /** Current top of stack for accumulating errors, NULL means empty stack */
-    struct error_info_level *cur;
+    struct error_item *cur;
 
     /** Type info for external error info */
     const struct error_extra_type *extra_type;
@@ -154,9 +155,9 @@
 const struct error_type* error_lookup (const error_t *err);
 
 /**
- * Return the error name for the lowest level error in the given state's stack.
+ * Return the error name for the given code.
  */
-const char* error_name (const error_t *err);
+const char* error_name (const struct error_list *list, err_t code);
 
 /**
  * Maximum length of messages returned by error_msg
@@ -175,17 +176,16 @@
 
 
 /**
- * Reset the given error state to an empty state
+ * Reset the given error state to an empty state.
+ *
+ * This is just the same as memset() with zero.
  */
-static inline void error_reset (error_t *err)
-{
-    memset(err, 0, sizeof(*err));
-}
+void error_reset (error_t *err);
 
 /**
  * Evaluates to the current top of the error stack, 
  */
-static struct error_item* error_top (const error_t *err)
+static struct error_item* error_top (error_t *err)
 {
     return err->cur ? err->cur : err->stack;
 }
@@ -275,4 +275,18 @@
  */
 #define NOT_REACHED(val) error_abort("%s = %#x", #val, (int) (val));
 
+/**
+ * General-purpose errors that may be useful and don't belong in any more specific namespace.
+ */
+enum general_error_code {
+    ERR_GENERAL_NONE,
+    ERR_MEM,                ///< memory allocation error
+    ERR_NOT_IMPLEMENTED,    ///< function not implmented: <func>
+    ERR_MISC,               ///< miscellaneous error: <error>
+    ERR_CMD_OPT,            ///< invalid command line option: <error> - XXX: replace with something getopt
+    ERR_UNKNOWN,            ///< unknown error
+};
+
+const struct error_list general_errors;
+
 #endif /* LIBQMSK_ERROR_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/log.c	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,165 @@
+
+#include "log.h"
+#include "str.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+/**
+ * The default log output func
+ */
+void log_default_func (const char *line, void *arg)
+{
+    (void) arg;
+
+    // display
+    printf("%s\n", line);
+}
+
+/**
+ * The global log level
+ */
+static enum log_level _log_level = LOG_LEVEL_DEFAULT;
+
+/**
+ * The global log output func
+ */
+static struct log_output_ctx {
+    /** The function itself */
+    log_output_func func;
+
+    /** The arg */
+    void *arg;
+} _log_output_ctx = { log_default_func, NULL };
+
+/**
+ * List of log level names
+ */
+const char *log_level_names[] = {
+    "DEBUG",
+    "INFO",
+    "WARN",
+    "ERROR",
+    "FATAL",
+    NULL
+};
+
+#define _LOG_LEVEL_NAME(ll) case LOG_ ## ll: return #ll;
+const char *log_level_name (enum log_level level)
+{
+    switch (level) {
+        _LOG_LEVEL_NAME(DEBUG)
+        _LOG_LEVEL_NAME(INFO)
+        _LOG_LEVEL_NAME(WARN)
+        _LOG_LEVEL_NAME(ERROR)
+        _LOG_LEVEL_NAME(FATAL)
+        default: return "???";
+    }
+}
+#undef _LOG_LEVEL_NAME
+
+void set_log_level (enum log_level level)
+{
+    // meep meep
+    _log_level = level;
+}
+
+void log_set_func (log_output_func func, void *arg)
+{
+    // replace the current one
+    _log_output_ctx.func = func ? func : log_default_func;
+    _log_output_ctx.arg = arg;
+}
+
+void log_output_tag_va (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, va_list log_fmtargs)
+{
+    char buf[LOG_MSG_MAX], *buf_ptr = buf;
+    size_t buf_size = sizeof(buf);
+
+    // filter out?
+    if (level < _log_level)
+        return;
+
+    // output the header
+    buf_ptr += str_advance(NULL, &buf_size, str_append_fmt(buf_ptr, buf_size, "[%5s] %20s : ", tag, func));
+    
+    // output the user data
+    if (user_fmt)
+        buf_ptr += str_advance(NULL, &buf_size, str_append_fmt_va(buf_ptr, buf_size, user_fmt, user_fmtargs));
+    
+    // output the suffix
+    if (log_fmt)
+        buf_ptr += str_advance(NULL, &buf_size, str_append_fmt_va(buf_ptr, buf_size, log_fmt, log_fmtargs));
+
+    // send it to the output func
+    _log_output_ctx.func(buf, _log_output_ctx.arg);
+
+}
+
+void log_output_tag (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, ...)
+{
+    va_list vargs;
+
+    va_start(vargs, log_fmt);
+    log_output_tag_va(level, tag, func, user_fmt, user_fmtargs, log_fmt, vargs);
+    va_end(vargs);
+}
+
+void _log_msg (enum log_level level, const char *func, const char *format, ...)
+{
+    va_list vargs;
+    
+    // formatted output: no suffix
+    va_start(vargs, format);
+    log_output_tag(level, log_level_name(level), func, format, vargs, NULL);
+    va_end(vargs);
+}
+
+void _log_msg_va2 (enum log_level level, const char *func, const char *fmt1, va_list fmtargs1, const char *fmt2, va_list fmtargs2)
+{
+    log_output_tag_va(level, log_level_name(level), func, fmt1, fmtargs1, fmt2, fmtargs2);
+}
+
+void _log_err (enum log_level level, const struct error_list *list, err_t code, const char *func, const char *format, ...)
+{
+    va_list vargs;
+
+    // formatted output: suffix error_name()
+    va_start(vargs, format);
+    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", error_name(list, code));
+    va_end(vargs);
+}
+
+void _log_error (enum log_level level, const error_t *err, const char *func, const char *format, ...)
+{
+    va_list vargs;
+
+    // formatted output: suffix error_msg()
+    va_start(vargs, format);
+    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", error_msg(err));
+    va_end(vargs);
+}
+
+void _log_perr (enum log_level level, const char *func, const char *format, ...)
+{
+    va_list vargs;
+
+    // formatted output: suffix strerror()
+    va_start(vargs, format);
+    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", strerror(errno));
+    va_end(vargs);
+}
+
+void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
+{
+    va_list vargs;
+
+    // formatted output without any suffix
+    va_start(vargs, format);
+    log_output_tag(level, "EXIT", func, format, vargs, NULL);
+    va_end(vargs);
+
+    // exit
+    exit(exit_code);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/log.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,153 @@
+#ifndef LOG_H
+#define LOG_H
+
+/** @file log.h
+ *
+ * Local logging functions
+ */
+#include "error.h"
+#include <stdarg.h>
+
+/**
+ * Log level definitions
+ *
+ * XXX: these names conflict with <syslog.h>
+ */
+enum log_level {
+    LOG_DEBUG,
+    LOG_INFO,
+    LOG_WARN,
+    LOG_ERROR,
+    LOG_FATAL,
+};
+
+/**
+ * List of log_level values as strings, NULL-terminated
+ */
+extern const char *log_level_names[];
+
+/**
+ * The default log level
+ */
+#define LOG_LEVEL_DEFAULT LOG_INFO
+
+/**
+ * Maximum length of a log message, including terminating NUL
+ */
+#define LOG_MSG_MAX 1024
+
+/**
+ * Log output function, for displaying a line of text, without any trailing newline.
+ */
+typedef void (*log_output_func) (const char *line, void *arg);
+
+/**
+ * Set the current log level to filter out messages below the given level
+ */
+void set_log_level (enum log_level level);
+
+/**
+ * Set the current log output function, replacing any default or previous value.
+ *
+ * Pass in func as NULL to revert to the default handler.
+ */
+void log_set_func (log_output_func func, void *arg);
+
+/**
+ * Internal logging func, meant for using custom level tags. This performs the filtering of log levels.
+ *
+ * Format the full output line, and pass it to the log_output_func. The line will be of the form:
+ *  "[<tag>] <func> : <user_fmt> [ <log_fmt> ]"
+ */
+void log_output_tag (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, ...)
+    __attribute__ ((format (printf, 6, 7)));
+
+/**
+ * va_list version of log_output_tag
+ */
+void log_output_tag_va (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, va_list log_fmtargs);
+
+/**
+ * Log a message with the given level
+ */
+#define log_msg(level, ...) _log_msg(level, __func__, __VA_ARGS__)
+void _log_msg (enum log_level level, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 3, 4)));
+
+/**
+ * Log a message with the given level with the given format and varargs
+ */
+#define log_msg_va2(level, fmt1, vargs1, fmt2, vargs2) _log_msg_va(level, __func__, fmt1, vargs1, fmt2, vargs2)
+void _log_msg_va2 (enum log_level level, const char *func, const char *fmt1, va_list fmtargs1, const char *fmt2, va_list fmtargs2);
+
+
+/**
+ * Shorthand for log_msg
+ */
+#define log_debug(...) log_msg(LOG_DEBUG, __VA_ARGS__)
+#define log_info(...)  log_msg(LOG_INFO,  __VA_ARGS__)
+#define log_warn(...)  log_msg(LOG_WARN,  __VA_ARGS__)
+/* #define log_error(...) log_msg(LOG_ERROR, __func__, __VA_ARGS__) */
+#define log_fatal(...) log_msg(LOG_FATAL, __VA_ARGS__)
+
+/**
+ * Log a message with the given level, appending the formatted error code name
+ */
+#define log_err(err_list, err_code, ...) _log_err(LOG_ERROR, err_list, err_code, __func__, __VA_ARGS__)
+#define log_warn_err(err_list, err_code, ...) _log_err(LOG_WARN, err_list, err_code, __func__, __VA_ARGS__)
+void _log_err (enum log_level level, const struct error_list *list, err_t code, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 5, 6)));
+
+/**
+ * Log a message with the given level, appending the formatted error message
+ */
+#define log_error(err_info, ...) _log_error(LOG_ERROR, err_info, __func__, __VA_ARGS__)
+#define log_warn_error(err_info, ...) _log_error(LOG_WARN, err_info, __func__, __VA_ARGS__)
+void _log_error (enum log_level level, const error_t *err, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 4, 5)));
+
+/**
+ * Log using errno.
+ */
+#define log_perr(...) _log_perr(LOG_ERROR, __func__, __VA_ARGS__)
+void _log_perr (enum log_level level, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 3, 4)));
+
+/**
+ * Log with an [EXIT] tag at given level, and then exit with given exit code
+ */
+#define log_exit(level, exit_code, ...) _log_exit(level, exit_code, __func__, __VA_ARGS__)
+void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
+    __attribute__ ((format (printf, 4, 5)))
+    __attribute__ ((noreturn));
+
+
+/**
+ * log_fatal + exit failure
+ */
+#define FATAL(...) do { log_fatal(__VA_ARGS__); abort(); } while (0)
+
+/**
+ * log_err + exit failure
+ */
+#define FATAL_ERR(err_code, ...) do { _log_err(LOG_FATAL, err_code, __func__, __VA_ARGS__); abort(); } while (0)
+
+/**
+ * log_err_info + exit failure
+ */
+#define FATAL_ERROR(err_info, ...) do { _log_error(LOG_FATAL, err_info, __func__, __VA_ARGS__); abort(); } while (0)
+
+/**
+ * log_perr + exit failure
+ */
+#define FATAL_PERROR(...) do { _log_perr(LOG_FATAL, __func__, __VA_ARGS__); abort(); } while (0)
+
+/**
+ * Exit with given code, also logging a message at LOG_INFO with an [EXIT] tag
+ */
+#define EXIT_INFO(exit_code, ...)  log_exit(LOG_INFO,  exit_code, __VA_ARGS__)
+#define EXIT_WARN(exit_code, ...)  log_exit(LOG_WARN,  exit_code, __VA_ARGS__)
+#define EXIT_ERROR(exit_code, ...) log_exit(LOG_ERROR, exit_code, __VA_ARGS__)
+#define EXIT_FATAL(exit_code, ...) log_exit(LOG_FATAL, exit_code, __VA_ARGS__)
+
+#endif /* LOG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/object.c	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,53 @@
+#include "object.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+void object_init (struct object *obj, const struct object_type *type)
+{
+    // trip on bugs
+    assert(!obj->type);
+
+    // set type
+    obj->type = type;
+}
+
+bool object_type_check (const struct object_type *obj_type, const struct object_type *type)
+{
+    const struct object_type *t;
+
+    // sanity-check
+    assert(obj_type && type);
+
+    // look for a matching type in the type's inheritance tree
+    for (t = obj_type; t; t = t->parent)
+        if (t == type)
+            break;
+    
+    // t will be (parent == NULL) if we didn't find any matching type
+    return (t != NULL);    
+}
+
+bool object_check (struct object *obj, const struct object_type *type)
+{
+    // sanity check
+    assert(obj && type);
+
+    return object_type_check(obj->type, type);
+}
+
+void* object_cast (struct object *obj, const struct object_type *type)
+{
+    assert(object_type_check(obj->type, type));
+
+    // ok, return as void*
+    return obj;
+}
+
+const void* object_type (struct object *obj, const struct object_type *type)
+{
+    assert(object_type_check(obj->type, type));
+
+    // ok, return as void*
+    return obj->type;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/object.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,56 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+/**
+ * @file
+ *
+ * The thing feared by every C-programmer - calling C structs "objects".
+ */
+#include <stdbool.h>
+
+/**
+ * Basic object type info
+ */
+struct object_type {
+    /** Parent type for nested structs, NULL if the parent is object_type */
+    const struct object_type *parent;
+};
+
+/**
+ * Basic object state
+ */
+struct object {
+    /** Object's type */
+    const struct object_type *type;
+};
+
+/**
+ * Initialize an object to the given type.
+ */
+void object_init (struct object *obj, const struct object_type *type);
+
+/**
+ * Check that the given object type is compatible with the given type.
+ */
+bool object_type_check (const struct object_type *obj_type, const struct object_type *type);
+
+/**
+ * Check that the given object "implements" the given type.
+ */
+bool object_check (struct object *obj, const struct object_type *type);
+
+/**
+ * Return the given object as a pointer suitable for casting to the struct for the given type.
+ *
+ * It is a bug to call this with an object that's not compatible with the given type.
+ */
+void* object_cast (struct object *obj, const struct object_type *type);
+
+/**
+ * Return a pointer to the object's type that is compatible with the given type.
+ *
+ * It is a bug to call this with a object that's not compatible with the given type.
+ */
+const void* object_type (struct object *obj, const struct object_type *type);
+
+#endif /* OBJECT_H */
--- a/src/line_proto.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/line_proto.h	Wed May 27 23:57:48 2009 +0300
@@ -22,7 +22,7 @@
     void (*on_line) (char *line, void *arg);
     
     /** Transport failed, the line_proto is corrupt, you should call line_proto_release next. */
-    void (*on_error) (struct error_info *err, void *arg);
+    void (*on_error) (const error_t *err, void *arg);
 };
 
 /**
@@ -72,7 +72,7 @@
 /**
  * Get current error_info*
  */
-const struct error_info* line_proto_error (struct line_proto *lp);
+const error_t* line_proto_error (struct line_proto *lp);
 
 /**
  * Destroy any buffers and the underlying transport.
--- a/src/log.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-
-#include "log.h"
-#include "str.h"
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-
-/**
- * The default log output func
- */
-void log_default_func (const char *line, void *arg)
-{
-    (void) arg;
-
-    // display
-    printf("%s\n", line);
-}
-
-/**
- * The global log level
- */
-static enum log_level _log_level = LOG_LEVEL_DEFAULT;
-
-/**
- * The global log output func
- */
-static struct log_output_ctx {
-    /** The function itself */
-    log_output_func func;
-
-    /** The arg */
-    void *arg;
-} _log_output_ctx = { log_default_func, NULL };
-
-/**
- * List of log level names
- */
-const char *log_level_names[] = {
-    "DEBUG",
-    "INFO",
-    "WARN",
-    "ERROR",
-    "FATAL",
-    NULL
-};
-
-#define _LOG_LEVEL_NAME(ll) case LOG_ ## ll: return #ll;
-const char *log_level_name (enum log_level level)
-{
-    switch (level) {
-        _LOG_LEVEL_NAME(DEBUG)
-        _LOG_LEVEL_NAME(INFO)
-        _LOG_LEVEL_NAME(WARN)
-        _LOG_LEVEL_NAME(ERROR)
-        _LOG_LEVEL_NAME(FATAL)
-        default: return "???";
-    }
-}
-#undef _LOG_LEVEL_NAME
-
-void set_log_level (enum log_level level)
-{
-    // meep meep
-    _log_level = level;
-}
-
-void log_set_func (log_output_func func, void *arg)
-{
-    // replace the current one
-    _log_output_ctx.func = func ? func : log_default_func;
-    _log_output_ctx.arg = arg;
-}
-
-void log_output_tag_va (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, va_list log_fmtargs)
-{
-    char buf[LOG_MSG_MAX], *buf_ptr = buf;
-    size_t buf_size = sizeof(buf);
-
-    // filter out?
-    if (level < _log_level)
-        return;
-
-    // output the header
-    buf_ptr += str_advance(NULL, &buf_size, str_append_fmt(buf_ptr, buf_size, "[%5s] %20s : ", tag, func));
-    
-    // output the user data
-    if (user_fmt)
-        buf_ptr += str_advance(NULL, &buf_size, str_append_fmt_va(buf_ptr, buf_size, user_fmt, user_fmtargs));
-    
-    // output the suffix
-    if (log_fmt)
-        buf_ptr += str_advance(NULL, &buf_size, str_append_fmt_va(buf_ptr, buf_size, log_fmt, log_fmtargs));
-
-    // send it to the output func
-    _log_output_ctx.func(buf, _log_output_ctx.arg);
-
-}
-
-void log_output_tag (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, ...)
-{
-    va_list vargs;
-
-    va_start(vargs, log_fmt);
-    log_output_tag_va(level, tag, func, user_fmt, user_fmtargs, log_fmt, vargs);
-    va_end(vargs);
-}
-
-void _log_msg (enum log_level level, const char *func, const char *format, ...)
-{
-    va_list vargs;
-    
-    // formatted output: no suffix
-    va_start(vargs, format);
-    log_output_tag(level, log_level_name(level), func, format, vargs, NULL);
-    va_end(vargs);
-}
-
-void _log_msg_va2 (enum log_level level, const char *func, const char *fmt1, va_list fmtargs1, const char *fmt2, va_list fmtargs2)
-{
-    log_output_tag_va(level, log_level_name(level), func, fmt1, fmtargs1, fmt2, fmtargs2);
-}
-
-void _log_err (enum log_level level, err_t err, const char *func, const char *format, ...)
-{
-    va_list vargs;
-
-    // formatted output: suffix error_name()
-    va_start(vargs, format);
-    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", error_name(err));
-    va_end(vargs);
-}
-
-void _log_error (enum log_level level, const error_t *err, const char *func, const char *format, ...)
-{
-    va_list vargs;
-
-    // formatted output: suffix error_msg()
-    va_start(vargs, format);
-    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", error_msg(err));
-    va_end(vargs);
-}
-
-void _log_perr (enum log_level level, const char *func, const char *format, ...)
-{
-    va_list vargs;
-
-    // formatted output: suffix strerror()
-    va_start(vargs, format);
-    log_output_tag(level, log_level_name(level), func, format, vargs, ": %s", strerror(errno));
-    va_end(vargs);
-}
-
-void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
-{
-    va_list vargs;
-
-    // formatted output without any suffix
-    va_start(vargs, format);
-    log_output_tag(level, "EXIT", func, format, vargs, NULL);
-    va_end(vargs);
-
-    // exit
-    exit(exit_code);
-}
--- a/src/log.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-#ifndef LOG_H
-#define LOG_H
-
-/** @file log.h
- *
- * Local logging functions
- */
-#include "error.h"
-#include <stdarg.h>
-
-/**
- * Log level definitions
- *
- * XXX: these names conflict with <syslog.h>
- */
-enum log_level {
-    LOG_DEBUG,
-    LOG_INFO,
-    LOG_WARN,
-    LOG_ERROR,
-    LOG_FATAL,
-};
-
-/**
- * List of log_level values as strings, NULL-terminated
- */
-extern const char *log_level_names[];
-
-/**
- * The default log level
- */
-#define LOG_LEVEL_DEFAULT LOG_INFO
-
-/**
- * Maximum length of a log message, including terminating NUL
- */
-#define LOG_MSG_MAX 1024
-
-/**
- * Log output function, for displaying a line of text, without any trailing newline.
- */
-typedef void (*log_output_func) (const char *line, void *arg);
-
-/**
- * Set the current log level to filter out messages below the given level
- */
-void set_log_level (enum log_level level);
-
-/**
- * Set the current log output function, replacing any default or previous value.
- *
- * Pass in func as NULL to revert to the default handler.
- */
-void log_set_func (log_output_func func, void *arg);
-
-/**
- * Internal logging func, meant for using custom level tags. This performs the filtering of log levels.
- *
- * Format the full output line, and pass it to the log_output_func. The line will be of the form:
- *  "[<tag>] <func> : <user_fmt> [ <log_fmt> ]"
- */
-void log_output_tag (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, ...)
-    __attribute__ ((format (printf, 6, 7)));
-
-/**
- * va_list version of log_output_tag
- */
-void log_output_tag_va (enum log_level level, const char *tag, const char *func, const char *user_fmt, va_list user_fmtargs, const char *log_fmt, va_list log_fmtargs);
-
-/**
- * Log a message with the given level
- */
-#define log_msg(level, ...) _log_msg(level, __func__, __VA_ARGS__)
-void _log_msg (enum log_level level, const char *func, const char *format, ...)
-    __attribute__ ((format (printf, 3, 4)));
-
-/**
- * Log a message with the given level with the given format and varargs
- */
-#define log_msg_va2(level, fmt1, vargs1, fmt2, vargs2) _log_msg_va(level, __func__, fmt1, vargs1, fmt2, vargs2)
-void _log_msg_va2 (enum log_level level, const char *func, const char *fmt1, va_list fmtargs1, const char *fmt2, va_list fmtargs2);
-
-
-/**
- * Shorthand for log_msg
- */
-#define log_debug(...) log_msg(LOG_DEBUG, __VA_ARGS__)
-#define log_info(...)  log_msg(LOG_INFO,  __VA_ARGS__)
-#define log_warn(...)  log_msg(LOG_WARN,  __VA_ARGS__)
-/* #define log_error(...) log_msg(LOG_ERROR, __func__, __VA_ARGS__) */
-#define log_fatal(...) log_msg(LOG_FATAL, __VA_ARGS__)
-
-/**
- * Log a message with the given level, appending the formatted error code name
- */
-#define log_err(err_code, ...) _log_err(LOG_ERROR, err_code, __func__, __VA_ARGS__)
-#define log_warn_err(err_code, ...) _log_err(LOG_WARN, err_code, __func__, __VA_ARGS__)
-void _log_err (enum log_level level, err_t err, const char *func, const char *format, ...)
-    __attribute__ ((format (printf, 4, 5)));
-
-/**
- * Log a message with the given level, appending the formatted error message
- */
-#define log_error(err_info, ...) _log_error(LOG_ERROR, err_info, __func__, __VA_ARGS__)
-#define log_warn_error(err_info, ...) _log_error(LOG_WARN, err_info, __func__, __VA_ARGS__)
-void _log_error (enum log_level level, const error_t *err, const char *func, const char *format, ...)
-    __attribute__ ((format (printf, 4, 5)));
-
-/**
- * Log using errno.
- */
-#define log_perr(...) _log_perr(LOG_ERROR, __func__, __VA_ARGS__)
-void _log_perr (enum log_level level, const char *func, const char *format, ...)
-    __attribute__ ((format (printf, 3, 4)));
-
-/**
- * Log with an [EXIT] tag at given level, and then exit with given exit code
- */
-#define log_exit(level, exit_code, ...) _log_exit(level, exit_code, __func__, __VA_ARGS__)
-void _log_exit (enum log_level level, int exit_code, const char *func, const char *format, ...)
-    __attribute__ ((format (printf, 4, 5)))
-    __attribute__ ((noreturn));
-
-
-/**
- * log_fatal + exit failure
- */
-#define FATAL(...) do { log_fatal(__VA_ARGS__); abort(); } while (0)
-
-/**
- * log_err + exit failure
- */
-#define FATAL_ERR(err_code, ...) do { _log_err(LOG_FATAL, err_code, __func__, __VA_ARGS__); abort(); } while (0)
-
-/**
- * log_err_info + exit failure
- */
-#define FATAL_ERROR(err_info, ...) do { _log_error(LOG_FATAL, err_info, __func__, __VA_ARGS__); abort(); } while (0)
-
-/**
- * log_perr + exit failure
- */
-#define FATAL_PERROR(...) do { _log_perr(LOG_FATAL, __func__, __VA_ARGS__); abort(); } while (0)
-
-/**
- * Exit with given code, also logging a message at LOG_INFO with an [EXIT] tag
- */
-#define EXIT_INFO(exit_code, ...)  log_exit(LOG_INFO,  exit_code, __VA_ARGS__)
-#define EXIT_WARN(exit_code, ...)  log_exit(LOG_WARN,  exit_code, __VA_ARGS__)
-#define EXIT_ERROR(exit_code, ...) log_exit(LOG_ERROR, exit_code, __VA_ARGS__)
-#define EXIT_FATAL(exit_code, ...) log_exit(LOG_FATAL, exit_code, __VA_ARGS__)
-
-#endif /* LOG_H */
--- a/src/lua_config.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/lua_config.c	Wed May 27 23:57:48 2009 +0300
@@ -2,7 +2,7 @@
 
 #include <lua5.1/lauxlib.h>
 
-err_t lua_config_load (struct nexus_lua *lua, const char *path, struct error_info *err)
+err_t lua_config_load (struct nexus_lua *lua, const char *path, error_t *err)
 {
     // just use luaL_loadfile and translate the error code
     if (nexus_lua_error(lua->st, luaL_loadfile(lua->st, path), err))
--- a/src/lua_config.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/lua_config.h	Wed May 27 23:57:48 2009 +0300
@@ -14,6 +14,6 @@
  *
  * Path can also be given as NULL to load from stdin.
  */
-err_t lua_config_load (struct nexus_lua *lua, const char *path, struct error_info *err);
+err_t lua_config_load (struct nexus_lua *lua, const char *path, error_t *err);
 
 #endif /* LUA_CONFIG_H */
--- a/src/lua_console.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/lua_console.c	Wed May 27 23:57:48 2009 +0300
@@ -91,7 +91,7 @@
     .on_interrupt   = lua_console_on_interrupt,
 };
 
-err_t lua_console_create (struct lua_console **lc_ptr, struct console *console, struct nexus_lua *lua, struct error_info *err)
+err_t lua_console_create (struct lua_console **lc_ptr, struct console *console, struct nexus_lua *lua, error_t *err)
 {
     struct lua_console *lc;
 
--- a/src/lua_console.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/lua_console.h	Wed May 27 23:57:48 2009 +0300
@@ -21,7 +21,7 @@
  *
  * This overrides the console callbacks.
  */
-err_t lua_console_create (struct lua_console **lc_ptr, struct console *console, struct nexus_lua *lua, struct error_info *err);
+err_t lua_console_create (struct lua_console **lc_ptr, struct console *console, struct nexus_lua *lua, error_t *err);
 
 /**
  * Destroy the lua console state
--- a/src/module.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,394 +0,0 @@
-#include "module.h"
-#include "log.h"
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <dlfcn.h>
-#include <string.h>
-#include <assert.h>
-
-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)
-{
-    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 ERR_MODULE_SYM;
-
-    // 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, struct error_info *err)
-{
-    char *path = NULL;
-
-    // build the path...
-    if ((path = strfmt("%s/mod_%s.so", modules->path, info->name)) == NULL)
-        JUMP_SET_ERROR(err, ERR_STRDUP);
-
-    // exists and readable?
-    if (access(path, R_OK))
-        // XXX: this doesn't contain the path...
-        JUMP_SET_ERROR_STR(err, 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, struct error_info *err)
-{
-    struct module *module;
-
-    // validate the module name
-    if (strlen(_info->name) > MODULE_NAME_MAX)
-        return SET_ERROR(err, ERR_MODULE_NAME);
-    
-    // already open with the same name?
-    if (modules_lookup(modules, _info->name))
-        return SET_ERROR(err, ERR_MODULE_DUP);
-
-    // alloc
-    if ((module = calloc(1, sizeof(*module))) == NULL)
-        return SET_ERROR(err, 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, 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, ERR_MODULE_OPEN, dlerror());
-    
-    // load the funcs symbol
-    if ((ERROR_CODE(err) = module_symbol(module, (void **) &module->desc, "module")))
-        JUMP_SET_ERROR_STR(err, 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, struct error_info *err)
-{
-    // sanity-check
-    if (!module->desc->config_options)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module does not have any config options");
-
-    // wrong state
-    if (module->unloading)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module is being unloaded");
-    
-    // parse/apply using the module's stuff    
-    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, struct error_info *err)
-{
-    // sanity-check
-    if (!module->desc->config_options)
-        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "module does not have any config options");
-    
-    // direct lookup
-    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, struct error_info *err)
-{
-    // wrong state
-    if (module->unloading)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module is being unloaded");
-    
-    // apply with the module's ctx
-    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);
-}
-
-
--- a/src/module.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,297 +0,0 @@
-#ifndef MODULE_H
-#define MODULE_H
-
-/**
- * @file
- *
- * Dynamically loadable modules for use with nexus.
- *
- * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Modules are then "loaded" by
- * resolving a `struct module_funcs` symbol called "<modname>_funcs", and then using the init func to construct a
- * "context", which is then further manipulated by various other module functions.
- *
- * Modules are also reference counted, mainly for implementing module_unload(). When a module is first loaded, it has
- * a reference count of one - the entry in struct modules. Later, modules can be referenced using module_get(), and
- * returned with module_put() once done. The module should never be destroyed during its lifetime, until a
- * module_unload() occurs. At this point, the module is removed from the modules_list, and the one reference is
- * 'passed on' to the module itself. Once it has finished unloading, it can call module_unloaded() on the reference it was
- * given by module_desc::unload, which may then result in a deferred module_destroy().
- *
- * Module destroying itself is an interesting issue, since modules effectively need to be able to destroy themselves
- * (as they must be able to perform cleanup, and then notify completion from inside an event loop callback). This means
- * that they cannot directly execute a module_destroy() on themselves - if we call dlclose() with dlopen-mapped code
- * pages on the stack, a segfault ensues. Hence, they must call module_unloaded() on themselves, which then executes a
- * deferred module_destroy() if there are no references left. Otherwise, the module should be safe from external code,
- * as module_put() should never cause a module to be destroyed before module_unloaded() is executed, due to the primary
- * reference.
- *
- * Ugh, it's compliated, I need to write a clearer explenation once it's implemented :)
- */
-
-struct module;
-
-#include "nexus.h"
-#include "config.h"
-#include "error.h"
-
-#include <sys/queue.h>
-#include <stdbool.h>
-
-/**
- * Information required to load/identify a module.
- */
-struct module_info {
-    /** Human-readable name */
-    const char *name;
-    
-    /** Filesystem path to the .so */
-    const char *path;
-};
-
-/**
- * A table of (function/other) pointers defining a module's behaviour. This is dynamically resolved from the module DSO
- * using the "<mod_name>_module" symbol.
- */
-struct module_desc {
-    /**
-     * Initialize the module, returning an opaque context pointer that is stored in the module state, and supplied for
-     * subsequent calls. The supplied nexus arg can be used to access the global state.
-     *
-     * Implementing this is mandatory.
-     *
-     * @param nexus a pointer to the nexus struct containing the global state
-     * @param ctx_ptr the context pointer should be returned via this
-     * @param err returned error info
-     */
-    err_t (*init) (struct nexus *nexus, void **ctx_ptr, struct error_info *err);
-
-    /**
-     * A module may define a set of available configuration parameters for use by module_conf, by setting this to an
-     * array of config_option's.
-     *
-     * The handler functions will recieve the module's context pointer as their ctx argument.
-     *
-     * Implementing this is optional, but recommended.
-     */
-    const struct config_option *config_options;
-
-    /**
-     * Unload the module, removing all handlers/callbacks added to the nexus' irc_client. This does not have to act
-     * immediately, and will still have access to all irc_* resources it had earlier, and may perform I/O to unload
-     * cleanly. But the unloading should complete reasonably quickly, after which all event handlers added by the
-     * module to the nexus' ev_base should have been removed, and resources released.
-     *
-     * The module given as an argument is the module itself - which has been removed from the modules_list - the
-     * primary reference is passed on to the module. Once the module has finished unloading, it may call module_put(),
-     * which may then call module_destroy(), if no other references were left.
-     *
-     * If the unload operation fails (returns an error code), then the module is considered as unloaded (as if
-     * module_unloaded() was called - don't call this if you return an error).
-     *
-     * Implementing this is optional, if all of this can be implemented in destroy.
-     *
-     * @param ctx the module's context pointer as returned by init
-     * @param module the hanging module reference, that must be passed to module_unloaded()
-     * @return error code
-     */
-    err_t (*unload) (void *ctx, struct module *module);
-
-    /**
-     * Destroy the module now. No later chances, the module's code will be unloaded directly after this, which means
-     * that attempts to execute the module's code (even on the stack...) after this will segfault.
-     *
-     * The module code /should/ garuntee that this is never called from *inside* the module code - calls to
-     * module_destroy() will be deferred via the event loop if needed.
-     *
-     * Implementing this is optional.
-     */
-    void (*destroy) (void *ctx);
-};
-
-/**
- * A loaded module.
- */
-struct module {
-    /** The identifying info for the module */
-    struct module_info info;
-
-    /** Possible dynamically allocated path */
-    char *path_buf;
-
-    /** The dlopen handle */
-    void *handle;
-
-    /** The module entry point */
-    struct module_desc *desc;
-
-    /** The module context object */
-    void *ctx;
-
-    /** Reference back to modules struct used to load this module */
-    struct modules *modules;
-
-    /** Reference count for destroy() */
-    size_t refcount;
-    
-    /** Is the module currently being unloaded? */
-    bool unloading;
-
-    /** Our entry in the list of modules */
-    TAILQ_ENTRY(module) modules_list;
-};
-
-/**
- * A set of loaded modules, and functionality to load more
- */
-struct modules {
-    /** The nexus in use */
-    struct nexus *nexus;
-
-    /** Module search path */
-    const char *path;
-
-    /** List of loaded modules */
-    TAILQ_HEAD(module_ctx_modules, module) list;
-};
-
-/**
- * Possible error codes
- */
-enum module_error_code {
-    _ERR_MODULE_BEGIN = _ERR_MODULE,
-    
-    ERR_MODULE_NAME,        ///< invalid module_info.name
-    ERR_MODULE_DUP,         ///< module already opened
-    ERR_MODULE_PATH,        ///< resolving the path failed
-    ERR_MODULE_OPEN,        ///< dlopen() failed
-    ERR_MODULE_SYM,         ///< invalid symbol
-    ERR_MODULE_INIT_FUNC,   ///< invalid module_init_func_t
-    ERR_MODULE_CONF,        ///< value error in configuration data
-    ERR_MODULE_STATE,       ///< module in wrong state for operation
-};
-
-
-/**
- * Maximum length of a module name
- */
-#define MODULE_NAME_MAX 24
-
-/**
- * Maximum length of module symbol suffix
- */
-#define MODULE_SUFFIX_MAX 16
-
-/**
- * Maximum length of symbol name name, including terminating NUL
- */
-#define MODULE_SYMBOL_MAX (MODULE_NAME_MAX + 1 + MODULE_SUFFIX_MAX + 1)
-
-/**
- * Create a new modules state
- */
-err_t modules_create (struct modules **modules_ptr, struct nexus *nexus);
-
-/**
- * Set a search path for finding modules by name. The given string won't be copied.
- *
- * A module called "<name>" will be searched for at "<path>/mod_<name>.so"
- *
- * If path is NULL, this doesn't change anything. This returns the old path, which may be NULL.
- *
- * @param modules the modules state
- * @param path the new search path, or NULL to just get the old one
- * @return the old search path
- */
-const char* modules_path (struct modules *modules, const char *path);
-
-/**
- * Get a reference to the module, which must be returned using module_put
- *
- * @param modules the modules state
- * @param name the module name to get
- * @return the module struct, or NULL if not found
- */
-struct module* modules_get (struct modules *modules, const char *name);
-
-/**
- * Unload all modules, this just calls module_unload for each module, logging errors as warnings.
- */
-err_t modules_unload (struct modules *modules);
-
-/**
- * Destroy all modules immediately.
- */
-void modules_destroy (struct modules *modules);
-
-
-
-/*******************************************/
-
-
-/**
- * Return a module's name
- */
-const char* module_name (struct module *module);
-
-/**
- * Load a new module, as named by info.
- *
- * If info->path is not given, the module will be searched for using the path set by modules_path().
- *
- * If module_ptr is given, a reference (that must be module_put'd) is returned, that must be returned using 
- * module_put().
- *
- * @param modules the module-loading context
- * @param module_ptr retuturned new module struct, as a new reference, if not NULL.
- * @param info the info required to identify and load the module
- * @param err returned error info
- */
-err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *info, struct error_info *err);
-
-/**
- * Return a module retrieved using module_get
- */
-void module_put (struct module *module);
-
-/**
- * Look up a module configuration option by name
- */
-const struct config_option* module_conf_lookup (struct module *module, const char *name, struct error_info *err);
-
-/**
- * Apply a module configuration option using a structured value
- */
-err_t module_conf (struct module *module, const struct config_option *opt, const struct config_value *value, struct error_info *err);
-
-/**
- * Set a module configuration option using a raw value
- */
-err_t module_conf_raw (struct module *module, const char *name, char *value, struct error_info *err);
-
-/**
- * Unload a module. This removes the module from the modules_list, marks it as unloading, and then calls the module's
- * \p unload function, passing it the primary reference. The module's unload code will then go about shutting down the
- * module, and once that is done, it may module_put() the primary reference, which may then lead to module_destroy().
- *
- * This returns ERR_MODULE_STATE if the module is already being unloaded, or other errors from the module's own unload
- * functionality.
- */
-err_t module_unload (struct module *module);
-
-/**
- * Used by a module itself to indicate that an module_desc::unload() operation has completed. This will execute a
- * deferred module_destroy() if there are no more references left on the module.
- *
- * Note: this is not intended to be called from outside the given module itself...
- */
-void module_unloaded (struct module *module);
-
-/**
- * Destroy a module, releasing as many resources as possible, but not stopping for errors.
- *
- * This does not enforce the correct refcount - 'tis the caller's responsibility. Prints out a warning if
- * refcount > 0.
- */
-void module_destroy (struct module *module);
-
-#endif
--- a/src/nexus.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,592 +0,0 @@
-#include "nexus.h"
-#include "lua_config.h"
-#include "sock.h"
-#include "log.h"
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <getopt.h>
-#include <signal.h>
-#include <string.h>
-
-/**
- * Command-line option codes
- */
-enum option_code {
-    OPT_HELP            = 'h',
-    OPT_DEFAULTS        = 'D',
-    OPT_NETWORK         = 'n',
-    OPT_MODULE          = 'm',
-    OPT_CONF            = 'C',
-    OPT_DEBUG           = 'd',
-    OPT_CONFIG          = 'c',
-    
-    /** Options without short names */
-    _OPT_EXT_BEGIN      = 0x00ff,
-    
-    OPT_CONSOLE,
-    OPT_CHANNEL,
-};
-
-/**
- * Command-line option definitions
- */
-static struct option options[] = {
-    {"help",            0,  NULL,   OPT_HELP        },
-    {"defaults",        1,  NULL,   OPT_DEFAULTS    },
-    {"network",         1,  NULL,   OPT_NETWORK     },
-    {"channel",         1,  NULL,   OPT_CHANNEL     },
-    {"module",          1,  NULL,   OPT_MODULE      },
-    {"conf",            1,  NULL,   OPT_CONF        },
-    {"debug",           0,  NULL,   OPT_DEBUG       },
-    {"console",         0,  NULL,   OPT_CONSOLE     },
-    {"config",          1,  NULL,   OPT_CONFIG      },
-    {0,                 0,  0,      0               },
-};
-
-/**
- * Short command-line option defintion
- */
-const char *short_options = "hn:c:m:C:d";
-
-/**
- * Display --help output on stdout.
- *
- * If nexus is given, --config options for loaded modules are also listed
- */
-static void usage (struct nexus *nexus, const char *exe)
-{
-    printf("Usage: %s [OPTIONS]\n", exe);
-    printf("\n");
-    printf(" --help / -h            display this message\n");
-    printf(" --defaults / -D        set the IRC client default info using '<nickname>:<username>:<realname>'\n");
-    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl][:ssl_cafile=<path>][:ssl_verify][:ssl_cert=<path>][:ssl_pkey=<path>]]' format\n");
-    printf(" --channel              add an IRC channel using '<network>:<channel>' format\n");
-    printf(" --module / -m          add a module using '<name>:<path>' format\n");
-    printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
-    printf(" --debug / -d           set logging level to DEBUG\n");
-    printf(" --config / -c          load a Lua config file from '<path>'\n");
-    printf(" --console              use the interactive console\n");
-    
-    // dump loaded module configs
-    if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
-        struct module *module;
-
-        printf("\n");
-        printf("Module configuration options\n");
-
-        TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
-            printf("\n");
-            printf("%s:\n", module->info.name);
-
-            // XXX: shouldn't be accessing directly
-            if (module->desc->config_options) {
-                const struct config_option *opt;
-
-                for (opt = module->desc->config_options; opt->name; opt++) {
-                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help);
-                }
-
-            } else {
-                printf("\t???\n");
-            }
-        }
-    }
-}
-
-/**
- * Parse and apply a --defaults option, setting the resulting defaults to our irc_client
- */
-static err_t apply_defaults (struct nexus *nexus, char *opt, struct error_info *err)
-{
-    struct irc_client_defaults defaults = {
-        .register_info      = { NULL, NULL, NULL },
-        .service            = IRC_PORT,
-        .service_ssl        = IRC_SSL_PORT,
-    };
-
-    // parse the required fields
-    if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <nickname> field for --defaults");
-    
-    if ((defaults.register_info.username = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <username> field for --defaults");
-    
-    if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <realname> field for --defaults");
-
-    // trailing garbage?
-    if (opt)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
-    
-    // apply them
-    log_info("using default nick/user/real-name: %s/%s/%s", 
-            defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname);
-
-    irc_client_set_defaults(nexus->client, &defaults);
-
-    // ok
-    return SET_ERROR(err, SUCCESS);
-}
-
-/**
- * Parse and apply a --network option, adding the given network to our irc_client.
- */
-static err_t apply_network (struct nexus *nexus, char *opt, struct error_info *err)
-{
-    struct irc_net_info info;
-    const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL;
-    bool use_ssl = false, ssl_verify = false;
-
-    // init to zero
-    memset(&info, 0, sizeof(info));
-
-    // parse the required fields
-    if ((info.network = strsep(&opt, ":")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
-    
-    if ((info.hostname = strsep(&opt, ":")) == NULL)
-        JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <hostname> field for --network");
-    
-    // parse the optional fields
-    if (opt)
-        info.service = strsep(&opt, ":");
-    
-    // parse any remaining flags
-    while (opt) {
-        char *flag_token = strsep(&opt, ":");
-        char *flag = strsep(&flag_token, "=");
-        char *value = flag_token;
-
-        if (strcmp(flag, "ssl") == 0) {
-            use_ssl = true;
-
-        } else if (strcmp(flag, "ssl_cafile") == 0) {
-            if (!value)
-                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_cafile");
-
-            ssl_cafile = value;
-        
-        } else if (strcmp(flag, "ssl_verify") == 0) {
-            ssl_verify = true;
-
-        } else if (strcmp(flag, "ssl_cert") == 0) {
-            if (!value)
-                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_cert");
-            
-            ssl_cert = value;
-
-        } else if (strcmp(flag, "ssl_pkey") == 0) {
-            if (!value)
-                JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "missing value for :ssl_pkey");
-
-            ssl_pkey = value;
-
-        } else
-            JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
-    }
-
-    // SSL?
-    if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) {
-        // verify
-        if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey))
-            JUMP_SET_ERROR_STR(err, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey");
-        
-        // create
-        if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err))
-            goto error;
-    }
-
-    // create the net
-    log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);
-
-    if (irc_client_add_net(nexus->client, NULL, &info, err))
-        goto error;
-
-    // ok
-    return SUCCESS;
-
-error:
-    // cleanup
-    if (info.ssl_cred)
-        ssl_client_cred_put(info.ssl_cred);
-
-    return ERROR_CODE(err);
-}
-
-/**
- * Parse and apply a --channel option, adding the given channel to the given irc_net.
- */
-static err_t apply_channel (struct nexus *nexus, char *opt, struct error_info *err)
-{
-    const char *network = NULL;
-    struct irc_net *net;
-    struct irc_chan_info info = {
-        .channel            = NULL,
-    };
-
-    // parse the required fields
-    if ((network = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <network> field for --channel");
-    
-    if ((info.channel = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <channel> field for --channel");
-
-    // trailing garbage?
-    if (opt)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
-
-    // look up the net
-    if ((net = irc_client_get_net(nexus->client, network)) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown network for --channel");
-    
-    // add the channel
-    log_info("add channel '%s' on network '%s'", info.channel, net->info.network);
-
-    if (irc_net_add_chan(net, NULL, &info, err))
-        return ERROR_CODE(err);
-
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Parse and apply a --module option, loading the given module.
- */
-static err_t apply_module (struct nexus *nexus, char *opt, struct error_info *err)
-{
-    struct module_info info = {
-        .name       = NULL,
-        .path       = NULL,
-    };
-
-    // parse the required fields
-    if ((info.name = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --module");
-
-    if ((info.path = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <path> field for --module");
-
-    // trailing garbage?
-    if (opt)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
-    
-    // load it
-    log_info("loading module '%s' from path '%s'", info.name, info.path);
-
-    if (module_load(nexus->modules, NULL, &info, err))
-        return ERROR_CODE(err);
-
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Parse and apply a --conf option, calling the module's conf func.
- */
-static err_t apply_conf (struct nexus *nexus, char *opt, struct error_info *err)
-{
-    struct module *module;
-    const char *module_name, *conf_name;
-    char *conf_value;
-
-    // parse the required fields
-    if ((module_name = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <module> field for --config");
-
-    if ((conf_name = strsep(&opt, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --config");
-
-    // value is the rest of the data, might be NULL
-    conf_value = opt;
-
-    // lookup the module
-    if ((module = modules_get(nexus->modules, module_name)) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown module for --config");
-    
-    // do the config
-    log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);
-
-    if (module_conf_raw(module, conf_name, conf_value, err))
-        return ERROR_CODE(err);
-
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Open the console
- */
-static err_t apply_console (struct nexus *nexus, struct error_info *err)
-{
-    struct console_config config = {
-        .prompt     = "> ",
-    };
-    struct console *console;
-
-    log_info("initializing the console");
-    
-    // init the console
-    if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err))
-        return ERROR_CODE(err);
-    
-    // set it as the log output handler
-    console_set_log_output(console);
-
-    // create the lua console on top of that
-    if (lua_console_create(&nexus->lua_console, console, nexus->lua, err))
-        goto error;
-
-    // ok
-    return SUCCESS;
-
-error:
-    console_destroy(console);
-
-    return ERROR_CODE(err);
-}
-
-err_t nexus_load_config (struct nexus *nexus, const char *path, struct error_info *err)
-{
-    return lua_config_load(nexus->lua, path, err);
-}
-
-/**
- * Load the lua config file from \a path
- */
-static err_t apply_config (struct nexus *nexus, char *path, struct error_info *err)
-{
-    log_info("loading lua config from %s", path);
-    
-    return nexus_load_config(nexus, path, err);
-}
-
-/**
- * Parse arguments and apply them to the given nexus
- */
-static err_t parse_args (struct nexus *nexus, int argc, char **argv, struct error_info *err)
-{
-    int opt, option_index;
-    
-    // parse options
-    while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) {
-        switch (opt) {
-            case OPT_HELP:
-                usage(nexus, argv[0]);
-
-                // XXX: return instead
-                exit(EXIT_SUCCESS);
-            
-            case OPT_NETWORK:
-                if (apply_network(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-            
-            case OPT_DEFAULTS:
-                if (apply_defaults(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-
-            case OPT_CHANNEL:
-                if (apply_channel(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-            
-            case OPT_MODULE:
-                if (apply_module(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-
-            case OPT_CONF:
-                if (apply_conf(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-            
-            case OPT_DEBUG:
-                set_log_level(LOG_DEBUG);
-                break;
-            
-            case OPT_CONSOLE:
-                if (apply_console(nexus, err))
-                    return ERROR_CODE(err);
-                
-                break;
-            
-            case OPT_CONFIG:
-                if (apply_config(nexus, optarg, err))
-                    return ERROR_CODE(err);
-
-                break;
-
-            case '?':
-                usage(nexus, argv[0]);
-                return SET_ERROR(err, ERR_CMD_OPT);
-        }
-    }
-    
-    // ok
-    return SUCCESS;
-}
-
-void nexus_shutdown (struct nexus *nexus)
-{
-    // destroy the console
-    if (nexus->lua_console)
-        lua_console_destroy(nexus->lua_console);
-    
-    nexus->lua_console = NULL;
-
-    // unload the modules
-    if (nexus->modules)
-       modules_unload(nexus->modules);
-
-    // quit the irc client
-    if (nexus->client)
-       irc_client_quit(nexus->client, "Goodbye, cruel world ;(");
-
-    // remove the signal handlers
-    if (nexus->signals)
-        signals_free(nexus->signals);
-
-    nexus->signals = NULL;
-
-    // now event_base_dispatch should return once everythings' shut down...
-    nexus->shutdown = true;
-}
-
-void nexus_crash (struct nexus *nexus)
-{
-    // force-quit the event loop
-    event_base_loopbreak(nexus->ev_base);
-}
-
-void nexus_destroy (struct nexus *nexus)
-{
-    // destroy the console
-    if (nexus->lua_console)
-        lua_console_destroy(nexus->lua_console);
-
-    // destroy the lua state
-    if (nexus->lua)
-        nexus_lua_destroy(nexus->lua);
-
-    // destroy the modules
-    if (nexus->modules)
-        modules_destroy(nexus->modules);
-
-    // destroy the irc client
-    if (nexus->client)
-        irc_client_destroy(nexus->client);
-
-    // remove the signal handlers
-    if (nexus->signals)
-        signals_free(nexus->signals);
-    
-    // finally, the libevent base
-    if (nexus->ev_base)
-        event_base_free(nexus->ev_base);
-
-    // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :)
-    memset(nexus, 0, sizeof(*nexus));
-}
-
-static void on_sig_shutdown (evutil_socket_t sig, short what, void *arg)
-{
-    struct nexus *nexus = arg;
-
-    (void) sig;
-    (void) what;
-    
-    if (!nexus->shutdown) {
-        // shutdown
-        log_info("Terminating...");
-
-        nexus_shutdown(nexus);
-
-    } else {
-        // already tried to shutdown
-        log_info("Crashing!");
-
-        nexus_crash(nexus);
-    }
-}
-
-int main (int argc, char **argv) 
-{
-    struct nexus _nexus, *nexus = &_nexus;
-    struct error_info err;
-
-    // zero nexus
-    memset(nexus, 0, sizeof(*nexus));
-
-    // initialize libevent
-    if ((nexus->ev_base = event_base_new()) == NULL)
-        FATAL("event_base_new");
-    
-
-    // initialize signal handlers
-    if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
-        FATAL("signals_create");
- 
-    // add our signal handlers
-    if (signal_ignore(SIGPIPE, &err))
-        FATAL_ERROR(&err, "signals_ignore");
-    
-    // add our SIGTERM handler before console_init()?
-    if (
-            (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus))
-        ||  (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus))
-    )
-        FATAL_ERROR(&err, "signals_add");
-
-    // initialize sock module
-    if (sock_init(nexus->ev_base, &err))
-        FATAL_ERROR(&err, "sock_init");
-
-    // modules 
-    if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus)))
-        FATAL_ERROR(&err, "modules_create");
-    
-    // the IRC client
-    if (irc_client_create(&nexus->client, &err))
-        FATAL_ERROR(&err, "irc_client_create");
-    
-    // lua state
-    if (nexus_lua_create(&nexus->lua, nexus, &err))
-        FATAL_ERROR(&err, "nexus_lua_create");
-
-    
-    // parse args
-    if (parse_args(nexus, argc, argv, &err))
-        FATAL_ERROR(&err, "parse_args");
-
-  
-    // run event loop
-    log_info("entering event loop");
-
-    if (event_base_dispatch(nexus->ev_base)) {
-        if (nexus->shutdown) {
-            log_info("clean shutdown completed");
-
-        } else {
-            FATAL("event_base_dispatch returned without shutdown");
-
-        }
-    } else {
-        log_warn("zero return from event_base_dispatch");
-    }
-
-    // cleanup
-    nexus_destroy(nexus);
-    nexus = NULL;
-
-    // ok
-    return EXIT_SUCCESS;
-}
-
--- a/src/nexus.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-#ifndef NEXUS_H
-#define NEXUS_H
-
-/**
- * @file
- *
- * A nexus is the central brain of the application; the place where the main() method is implemented
- */
-struct nexus;
-
-#include <event2/event.h>
-#include "signals.h"
-#include "module.h"
-#include "irc_client.h"
-#include "nexus_lua.h"
-#include "lua_console.h"
-
-/**
- * The central brain, as created in the main() function.
- */
-struct nexus {
-    /** The libevent base */
-    struct event_base *ev_base;
-
-    /** Our signal handlers */
-    struct signals *signals;
-
-    /** Our loaded modules */
-    struct modules *modules;
-
-    /** The IRC client state */
-    struct irc_client *client;
-
-    /** Our lua state */
-    struct nexus_lua *lua;
-
-    /** Our lua console */
-    struct lua_console *lua_console;
-
-    /** Shutting down? */
-    bool shutdown;
-};
-
-/**
- * Load a config file into the nexus from the given path
- */
-err_t nexus_load_config (struct nexus *nexus, const char *path, struct error_info *err);
-
-/**
- * Shut down the nexus cleanly. It /should/ be safe to call this several times, but it won't do anything.
- *
- * Once everything has shut down nicely (which it should, unless there's something buggy), the event loop should exit,
- * and the main() function return.
- */
-void nexus_shutdown (struct nexus *nexus);
-
-/**
- * Shut down the nexus fast. This will stop the event loop right away, which should lead to nexus_destroy being called.
- */
-void nexus_crash (struct nexus *nexus);
-
-/**
- * Destroy the nexus directly. This is intended to be used once the event loop as exited.
- */
-void nexus_destroy (struct nexus *nexus);
-
-/**
- * The nexus main function, application entry point, etc.
- */
-int main (int argc, char **argv);
-
-#endif /* NEXUS_H */
--- a/src/nexus_lua.c	Wed May 27 23:07:00 2009 +0300
+++ b/src/nexus_lua.c	Wed May 27 23:57:48 2009 +0300
@@ -31,7 +31,7 @@
     return 0;
 }
 
-err_t nexus_lua_create (struct nexus_lua **lua_ptr, struct nexus *nexus, struct error_info *err)
+err_t nexus_lua_create (struct nexus_lua **lua_ptr, struct nexus *nexus, error_t *err)
 {
     struct nexus_lua *lua;
 
@@ -69,7 +69,7 @@
     free(lua);
 }
 
-err_t nexus_lua_eval (struct nexus_lua *lua, const char *chunk, struct error_info *err)
+err_t nexus_lua_eval (struct nexus_lua *lua, const char *chunk, error_t *err)
 {
     int ret;
     bool loaded = false;
@@ -99,7 +99,7 @@
     return ERROR_CODE(err);
 }
 
-err_t nexus_lua_error (lua_State *L, int ret, struct error_info *err)
+err_t nexus_lua_error (lua_State *L, int ret, error_t *err)
 {
     // XXX: this can raise an erorr itself
     const char *error = lua_tostring(L, -1);
--- a/src/nexus_lua.h	Wed May 27 23:07:00 2009 +0300
+++ b/src/nexus_lua.h	Wed May 27 23:57:48 2009 +0300
@@ -26,7 +26,7 @@
 /**
  * Create a new lua state for nexus
  */
-err_t nexus_lua_create (struct nexus_lua **lua_ptr, struct nexus *nexus, struct error_info *err);
+err_t nexus_lua_create (struct nexus_lua **lua_ptr, struct nexus *nexus, error_t *err);
 
 /**
  * Destroy the lua state
--- a/src/object.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-#include "object.h"
-
-#include <stdlib.h>
-#include <assert.h>
-
-void object_init (struct object *obj, const struct object_type *type)
-{
-    // trip on bugs
-    assert(!obj->type);
-
-    // set type
-    obj->type = type;
-}
-
-bool object_type_check (const struct object_type *obj_type, const struct object_type *type)
-{
-    const struct object_type *t;
-
-    // sanity-check
-    assert(obj_type && type);
-
-    // look for a matching type in the type's inheritance tree
-    for (t = obj_type; t; t = t->parent)
-        if (t == type)
-            break;
-    
-    // t will be (parent == NULL) if we didn't find any matching type
-    return (t != NULL);    
-}
-
-bool object_check (struct object *obj, const struct object_type *type)
-{
-    // sanity check
-    assert(obj && type);
-
-    return object_type_check(obj->type, type);
-}
-
-void* object_cast (struct object *obj, const struct object_type *type)
-{
-    assert(object_type_check(obj->type, type));
-
-    // ok, return as void*
-    return obj;
-}
-
-const void* object_type (struct object *obj, const struct object_type *type)
-{
-    assert(object_type_check(obj->type, type));
-
-    // ok, return as void*
-    return obj->type;
-}
--- a/src/object.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-#ifndef OBJECT_H
-#define OBJECT_H
-
-/**
- * @file
- *
- * The thing feared by every C-programmer - calling C structs "objects".
- */
-#include <stdbool.h>
-
-/**
- * Basic object type info
- */
-struct object_type {
-    /** Parent type for nested structs, NULL if the parent is object_type */
-    const struct object_type *parent;
-};
-
-/**
- * Basic object state
- */
-struct object {
-    /** Object's type */
-    const struct object_type *type;
-};
-
-/**
- * Initialize an object to the given type.
- */
-void object_init (struct object *obj, const struct object_type *type);
-
-/**
- * Check that the given object type is compatible with the given type.
- */
-bool object_type_check (const struct object_type *obj_type, const struct object_type *type);
-
-/**
- * Check that the given object "implements" the given type.
- */
-bool object_check (struct object *obj, const struct object_type *type);
-
-/**
- * Return the given object as a pointer suitable for casting to the struct for the given type.
- *
- * It is a bug to call this with an object that's not compatible with the given type.
- */
-void* object_cast (struct object *obj, const struct object_type *type);
-
-/**
- * Return a pointer to the object's type that is compatible with the given type.
- *
- * It is a bug to call this with a object that's not compatible with the given type.
- */
-const void* object_type (struct object *obj, const struct object_type *type);
-
-#endif /* OBJECT_H */
--- a/src/signals.c	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +0,0 @@
-
-#include "signals.h"
-#include "log.h"
-
-#define _GNU_SOURCE
-
-#include <string.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <assert.h>
-
-struct signals {
-    /** The libevent base to use */
-    struct event_base *ev_base;
-    
-    /** Information about up to MAX_SIGNALS signals */
-    struct signal {
-        /** The event we use to handle the signal */
-        struct event *ev;
-
-    } sig_list[MAX_SIGNALS];
-    
-    /** Number of used sig_list slots */
-    int sig_count;
-};
-
-void signals_loopexit (int signal, short event, void *arg) 
-{
-    struct signals *signals = arg;
-
-    (void) event;
-
-    log_info("caught %s: exiting", /* strsignal(signal) */ "xxx");
-    
-    if (event_base_loopexit(signals->ev_base, NULL))
-        FATAL("event_base_loopexit");
-}
-
-void signals_ignore (int signal, short event, void *arg)
-{
-    struct signals *signals = arg;
-    
-    (void) signal;
-    (void) event;
-    (void) arg;
-    (void) signals;
-    
-    /* ignore */
-}
-
-err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base)
-{
-    struct signals *signals;
-
-    if ((signals = calloc(1, sizeof(*signals))) == NULL)
-        return ERR_CALLOC;
-    
-    // simple attributes
-    signals->ev_base = ev_base;
-
-    // done
-    *signals_ptr = signals;
-
-    return SUCCESS;
-}
-
-err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg) 
-{
-    struct signal *sig_info;
-    
-    // find our sig_info
-    assert(signals->sig_count < MAX_SIGNALS);
-    sig_info = signals->sig_list + (signals->sig_count++);
-    
-    // set up the libevent signal events
-    if ((sig_info->ev = evsignal_new(signals->ev_base, sigval, sig_handler, arg)) == NULL)
-        return ERR_EVENT_NEW;
-
-    if (evsignal_add(sig_info->ev, NULL))
-        return ERR_EVENT_ADD;
-
-    // ok
-    return SUCCESS;
-}
-
-err_t signal_ignore (int signum, struct error_info *err)
-{
-    struct sigaction act = {
-        .sa_handler     = SIG_IGN,
-    };
-    
-    // signall the handler
-    if (sigaction(signum, &act, NULL))
-        RETURN_SET_ERROR_ERRNO(err, ERR_SIGACTION);
-
-    // ok
-    return SUCCESS;
-}
-
-struct signals *signals_default (struct event_base *ev_base) 
-{
-    struct signals *signals = NULL;
-    struct error_info err;
-    
-    // alloc signals
-    if (signals_create(&signals, ev_base))
-        return NULL;
-    
-    // add the set of default signals
-    if (signal_ignore(SIGPIPE, &err))
-        log_error(&err, "signal_ignore(SIGPIPE)");
-
-    if ((ERROR_CODE(&err) = signals_add(signals, SIGINT, &signals_loopexit, signals))) {
-        log_error(&err, "signals_add(SIGINT)");
-        goto error;
-    }
-
-    // ok
-    return signals;
-
-error:
-    signals_free(signals);
-
-    return NULL;
-}   
-
-void signals_free (struct signals *signals) 
-{
-    int i;
-    
-    // free all events
-    for (i = 0; i < signals->sig_count; i++) {
-        event_free(signals->sig_list[i].ev);
-    }
-    
-    // free the info itself
-    free(signals);
-}
-
--- a/src/signals.h	Wed May 27 23:07:00 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#ifndef SIGNALS_H
-#define SIGNALS_H
-
-/**
- * @file 
- *
- * Handle signals in a libevent way
- */
-#include "error.h"
-#include <event2/event.h>
-
-/**
- * How many signals we can define actions for
- */
-#define MAX_SIGNALS 8
-
-/**
- * State for a set of signals
- */
-struct signals;
-
-/**
- * Used as a handler for signals that should cause a loopexit.
- */
-void signals_loopexit (int signal, short event, void *arg);
-
-/**
- * Used to receive signals, but discard them, and continue what we were doing.
- */
-void signals_ignore (int signal, short event, void *arg);
-
-/**
- * Allocate a signals struct, acting on the given ev_base.
- *
- * Returns NULL on failure
- */
-err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base);
-
-/**
- * Add a signal to be handled by the given signals struct with the given handler.
- */
-err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg);
-
-/**
- * Ignore the given sigval.
- */
-err_t signal_ignore (int signum, struct error_info *err);
-
-/**
- * Add a set of default signals
- *      SIGPIPE     signals_ignore
- *      SIGINT      signals_loopexit
- */
-struct signals *signals_default (struct event_base *ev_base);
-
-/**
- * Free the resources/handlers associated with the given signal handler set
- */
-void signals_free (struct signals *signals);
-
-#endif /* SIGNALS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/config.c	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,268 @@
+#include "config.h"
+
+#include <string.h>
+#include <assert.h>
+
+const struct error_list config_errors = ERROR_LIST("config",
+    ERROR_TYPE_STRING(  ERR_CONFIG_NAME,        "unknown config option"         ),
+    ERROR_TYPE(         ERR_CONFIG_TYPE,        "invalid config type"           ),
+    ERROR_TYPE_STRING(  ERR_CONFIG_REQUIRED,    "missing required value"        ),
+    ERROR_TYPE_STRING(  ERR_CONFIG_VALUE,       "invalid value"                 ),
+    ERROR_TYPE(         ERR_CONFIG_PARAMS,      "invalid number of paramters"   )
+);
+
+const struct config_option* config_lookup (const struct config_option *options, const char *name, error_t *err)
+{
+    const struct config_option *option;
+
+    // find the matching config opt
+    for (option = options; option->name; option++) {
+        if (strcmp(option->name, name) == 0)
+            return option;
+    }
+
+    // not found
+    SET_ERROR_STR(err, &config_errors, ERR_CONFIG_NAME, name);
+
+    return NULL;
+}
+
+int config_params_count (const struct config_option *option)
+{
+    const struct config_param *param;
+    int count = 0;
+
+    // handle each param
+    for (param = option->params; param->name && param->type; param++)
+        count++;
+
+    return count;
+}
+
+/**
+ * Parse a raw value for a CONFIG_IRC_CHAN into an irc_chan
+ */
+static struct irc_chan* config_parse_irc_chan (struct nexus *nexus, char *raw_value, error_t *err)
+{
+    const char *network, *channel;
+    struct irc_chan *chan;
+
+    // XXX: wrong error code
+
+    // parse required args
+    if ((network = strsep(&raw_value, "/")) == NULL)
+        JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_VALUE, "invalid <network> for CONFIG_IRC_CHAN value");
+
+    if ((channel = strsep(&raw_value, "/")) == NULL)
+        JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_VALUE, "invalid <channel> for CONFIG_IRC_CHAN value");
+    
+    // extraneous stuff?
+    if (raw_value)
+        JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_VALUE, "trailing data for CONFIG_IRC_CHAN value");
+    
+    // get the channel?
+    if ((chan = irc_client_get_chan(nexus->client, network, channel)) == NULL)
+        JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_VALUE, "unknown network/channel name for CONFIG_IRC_CHAN value");
+    
+    // ok
+    return chan;
+
+error:
+    return NULL;    
+}
+
+err_t config_parse_param (const struct config_param *param, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *err)
+{
+    // parse the value 
+    switch (param->type) {
+        case CONFIG_INVALID:
+            return SET_ERROR_STR(err, &config_errors, ERR_CONFIG_TYPE, "invalid value for invalid type (too many values?)");
+
+        case CONFIG_STRING:
+            // simple!
+            value->string = raw_value;
+            break;
+
+        case CONFIG_IRC_CHAN:
+            // parse the value
+            if (!(value->irc_chan = config_parse_irc_chan(nexus, raw_value, err)))
+                return error_code(err);
+
+            break;
+        
+        case CONFIG_USER:
+            // fail
+            return SET_ERROR_STR(err, &config_errors, ERR_CONFIG_TYPE, "user type can't be parsed");
+
+        default:
+            NOT_REACHED(param->type);
+    }
+
+    // copy the type
+    value->type = param->type;
+
+    // ok
+    return SUCCESS; 
+}
+
+err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *err)
+{
+    // use the first param
+    const struct config_param *param = &option->params[0];
+
+    // must have exactly one param
+    if (!param->type || (param + 1)->type)
+        return SET_ERROR(err, &config_errors, ERR_CONFIG_PARAMS);
+    
+    // parse it
+    return config_parse_param(param, nexus, value, raw_value, err);
+}
+
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], error_t *err)
+{
+    const struct config_param *param;
+    const struct config_value *value;
+
+    // handle each param
+    for (param = option->params, value = values; param->name && param->type; param++) {
+        // no value given, or value given as NULL?
+        if (!value->type || value->type == CONFIG_NULL) {
+            if (param->optional) {
+                // optional, so just ignore the value 
+                
+            } else {
+                // required
+                JUMP_SET_ERROR_STR(err, &config_errors, ERR_CONFIG_REQUIRED, param->name);
+
+            }
+
+        } else if (param->type != value->type) {
+            // invalid type, XXX: also report correct type name?
+            JUMP_SET_ERROR(err, &config_errors, ERR_CONFIG_TYPE);
+
+        } else if (param->is_handler) {
+            // only applicable for non-optional args
+            err_t tmp;
+            
+            // invoke the handler
+            switch (param->type) {
+                case CONFIG_STRING:     tmp = param->func.string(ctx, value->string, err); break;
+                case CONFIG_IRC_CHAN:   tmp = param->func.irc_chan(ctx, value->irc_chan, err); break;
+                default:                NOT_REACHED(param->type);
+            }
+            
+            // abort on errors
+            if (tmp)
+                goto error;
+        }
+        
+        // the values list is NULL-terminated, and optional params can be left off
+        if (value->type)
+            value++;
+    }
+
+    // the option's handler?
+    if (option->func && option->func(ctx, option, values, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    return error_code(err);
+}
+
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], error_t *err)
+{
+    const struct config_option *option;
+
+    // no matching option found?
+    if (!(option = config_lookup(options, name, err)))
+        return error_code(err);
+    
+    // apply it    
+    return config_apply_opt(option, ctx, values, err);
+}
+
+err_t config_apply_string (const struct config_option *options, void *ctx, const char *name, char *value, error_t *err)
+{
+    struct config_value conf_value[] = {
+        CONFIG_VALUE_STRING(value),
+        CONFIG_VALUE_END,
+    };
+
+    return config_apply(options, ctx, name, conf_value, err);
+}
+
+err_t config_apply_irc_chan (const struct config_option *options, void *ctx, const char *name, struct irc_chan *value, error_t *err)
+{
+    struct config_value conf_value[] = {
+        CONFIG_VALUE_IRC_CHAN(value),
+        CONFIG_VALUE_END,
+    };
+
+    return config_apply(options, ctx, name, conf_value, err);
+}
+
+err_t config_apply_raw (const struct config_option options[], struct nexus *nexus, void *ctx, const char *name, char *raw_value, error_t *err)
+{
+    const struct config_option *option;
+    struct config_value value[2] = {
+        CONFIG_VALUE_END,
+        CONFIG_VALUE_END,
+    };
+
+    // no matching option found?
+    if (!(option = config_lookup(options, name, err)))
+        return error_code(err);
+    
+    // parse it
+    if (config_parse(option, nexus, &value[0], raw_value, err))
+        return error_code(err);
+    
+    // apply it
+    return config_apply_opt(option, ctx, value, err);
+}
+
+const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name)
+{
+    const struct config_param *param;
+    const struct config_value *value;
+    
+    // go through the (param, value) pairs
+    for (param = option->params, value = values; param->name && param->type && value->type; param++, value++) {
+        if (strcmp(param->name, name) == 0) {
+            // not NULL?
+            if (value->type != CONFIG_NULL)
+                return value;
+
+            else
+                return NULL;
+        }
+    }
+
+    // not found
+    return NULL;
+}
+
+const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name)
+{
+    const struct config_value *value;
+
+    return (value = config_get_value(option, values, name)) ? value->string : NULL;
+}
+
+struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name)
+{
+    const struct config_value *value;
+
+    return (value = config_get_value(option, values, name)) ? value->irc_chan : NULL;
+}
+
+void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type)
+{
+    const struct config_value *value;
+
+    return ((value = config_get_value(option, values, name)) && value->user.type == user_type) ? value->user.ptr : NULL;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/config.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,292 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/**
+ * @file
+ *
+ * Support for module configuration parameters
+ */
+#include <lib/error.h>
+#include <stdbool.h>
+
+enum config_error_code {
+    ERR_CONFIG_NONE,
+    ERR_CONFIG_NAME,        ///< unknown option name
+    ERR_CONFIG_TYPE,        ///< invalid value type for parameter
+    ERR_CONFIG_REQUIRED,    ///< missing value for required parameter
+    ERR_CONFIG_VALUE,       ///< invalid value
+    ERR_CONFIG_PARAMS,      ///< invalid number of parameters
+};
+
+const struct error_list config_errors;
+
+/** 
+ * Different types of configuration parameters
+ */
+enum config_type {
+    CONFIG_INVALID,
+
+    /** No value at all */
+    CONFIG_NULL,
+
+    /** A plain NUL-terminated string */
+    CONFIG_STRING,
+
+    /** A user-defined type, further identified by a string */
+    CONFIG_USER,
+
+    /** An IRC channel */
+    CONFIG_IRC_CHAN,
+};
+
+/**
+ * A CONFIG_USER type info
+ */
+struct config_user_type {
+    /** The name of the type */
+    const char *name;
+};
+
+/**
+ * Structure to hold a value as defined by config_type
+ */
+struct config_value {
+    /** The type of the value */
+    enum config_type type;
+    
+    /** The typed value */
+    union {
+        /** Value for CONFIG_STRING */
+        char *string;
+
+        /** Value for CONFIG_IRC_CHAN */
+        struct irc_chan *irc_chan;
+        
+        /** Value for CONFIG_USER */
+        struct {
+            /** The specific user type */
+            const struct config_user_type *type;
+
+            /** The pointer value */
+            void *ptr;
+        } user;
+    };
+};
+
+/**
+ * Structure to define a single parameter for an option
+ */
+struct config_param {
+    /** The name of the arg */
+    const char *name;
+
+    /** The type */
+    enum config_type type;
+
+    /** The specific type for CONFIG_USER */
+    const struct config_user_type *user_type;
+
+    /** Description */
+    const char *description;
+
+    /** Optional value handler function, by type */
+    union {
+        err_t (*string) (void *ctx, char *value, error_t *err);
+        err_t (*irc_chan) (void *ctx, struct irc_chan *chan, error_t *err);
+    } func;
+
+    /** Use handler function? */
+    bool is_handler;
+
+    /** Optional value? */
+    bool optional;
+};
+
+/**
+ * The maximum number of parameters that a single option can have, although this includes the terminating NULL
+ */
+#define CONFIG_PARAMS_MAX (15 + 1)
+
+/**
+ * The maximum number of values that an option can access, including the terminating NULL
+ */
+#define CONFIG_VALUES_MAX CONFIG_PARAMS_MAX
+
+/**
+ * A more complicated configuration option that can have multiple parameters
+ */
+struct config_option {
+    /** The name of the config option */
+    const char *name;
+
+    /** The list of parameters */
+    const struct config_param params[CONFIG_PARAMS_MAX];
+    
+    /** The handler function */
+    err_t (*func) (void *ctx, const struct config_option *option, const struct config_value values[], error_t *err);
+
+    /** Help text */
+    const char *help;
+};
+
+#define CONFIG_PARAM(name, type, desc, optional) \
+    {   name, type, NULL, desc, { NULL }, false, optional }
+
+#define CONFIG_PARAM_OPTIONAL(name, type, desc) \
+    CONFIG_PARAM(name, type, desc, true)
+
+#define CONFIG_PARAM_USER(name, user_type, desc, optional) \
+    {   name, CONFIG_USER, user_type, desc, { NULL }, false, optional }
+
+#define CONFIG_PARAM_USER_OPTIONAL(name, user_type, desc) \
+    CONFIG_PARAM_USER(name, user_type, desc, true) 
+
+#define CONFIG_PARAM_HANDLER(name, type, desc, _func_name_, func) \
+    {   name, type, NULL, desc, {._func_name_ = func }, true, false }
+
+#define CONFIG_PARAM_END \
+    {   NULL, 0, NULL, NULL, { NULL }, false, false }
+
+#define CONFIG_OPT(name, func, help, ...) \
+    {   name, { __VA_ARGS__, CONFIG_PARAM_END }, func, help }
+
+#define CONFIG_OPT_STRING(name, func, desc, help) \
+    CONFIG_OPT(name, NULL, help, CONFIG_PARAM_HANDLER(name, CONFIG_STRING, desc, string, func))
+
+#define CONFIG_OPT_IRC_CHAN(name, func, desc, help) \
+    CONFIG_OPT(name, NULL, help, CONFIG_PARAM_HANDLER(name, CONFIG_IRC_CHAN, desc, irc_chan, func))
+
+#define CONFIG_OPT_END \
+    {   NULL,   {}, NULL, NULL }
+
+#define CONFIG_OPTIONS(...) \
+    { __VA_ARGS__, CONFIG_OPT_END }
+
+#define CONFIG_VALUE(type, _name_, value) \
+    {   (type), { ._name_ = value } }
+
+#define CONFIG_VALUE_STRING(str_value) \
+    CONFIG_VALUE(CONFIG_STRING, string, (str_value))
+
+#define CONFIG_VALUE_IRC_CHAN(irc_chan_value) \
+    CONFIG_VALUE(CONFIG_IRC_CHAN, irc_chan, (irc_chan_value))
+
+#define CONFIG_VALUE_USER(user_type, user_value) \
+    CONFIG_VALUE(CONFIG_USER, user, { user_type, user_value })
+
+#define CONFIG_VALUE_END \
+    {   0, { NULL } }
+
+/**
+ * Lookup a config option by name.
+ *
+ * @param options a CONFIG_OPT_END-terminated array of config_option's
+ * @param name the config_option::name to look up
+ * @param err returned error info if not found
+ * @return a direct pointer to the config_option if found
+ */
+const struct config_option* config_lookup (const struct config_option *options, const char *name, error_t *err);
+
+/**
+ * Returns the number of params that an option has
+ *
+ * @param option the option to count
+ * @return the number of params
+ */
+int config_params_count (const struct config_option *option);
+
+// XXX: move this into nexus
+#include "nexus.h"
+/**
+ * Parse a raw value into a suitable configuration value for the given param, based on its type.
+ *
+ * Since this needs to access the application state, you need to pass in the nexus as an argument.
+ *
+ * Formats supported:
+ *  CONFIG_IRC_CHAN         - uses a '<network>/<channel>' format and irc_client_get_chan
+ *
+ */
+err_t config_parse_param (const struct config_param *param, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *err);
+
+/**
+ * Parse a raw value into a suitable configuration value for a single-param option, based on the config option type.
+ *
+ * @param option the option to parse the value for, use config_lookup to find it
+ * @param nexus the application state
+ * @param value the returned value, if succesfull
+ * @param raw_value the raw value to parse based on the type
+ * @param err returned error info
+ */
+err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, error_t *err);
+
+/**
+ * Apply a list of parsed configuration values to the given config_option struct.
+ *
+ * The config option handlers take a context argument; the value of this depends on the implementor of the config_option.
+ *
+ * @param option the option to apply
+ * @param ctx the context pointer for the option handler
+ * @param values the NULL-terminated array of parsed values
+ * @param err returned error info
+ */
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value values[], error_t *err);
+
+/**
+ * Apply a list of parsed configuration values for the named config opt.
+ *
+ * @param options a CONFIG_OPT_END-terminated array of config_option's
+ * @param ctx the context pointer for the option handler
+ * @param name the config_option::name to look up
+ * @param values the NULL-termianted array of parsed values
+ * @param err returned error info
+ */
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value values[], error_t *err);
+
+/**
+ * Apply a string value for the named config opt.
+ *
+ * @param options a CONFIG_OPT_END-terminated array of config_option's
+ * @param ctx the context pointer for the option handler
+ * @param name the config_option::name to look up
+ * @param value the string value
+ * @param err returned error info
+ */
+err_t config_apply_string (const struct config_option *options, void *ctx, const char *name, char *value, error_t *err);
+
+/**
+ * Apply an irc_chan value for the named config opt.
+ *
+ * @param options a CONFIG_OPT_END-terminated array of config_option's
+ * @param ctx the context pointer for the option handler
+ * @param name the config_option::name to look up
+ * @param value the irc_chan value
+ * @param err returned error info
+ */
+err_t config_apply_irc_chan (const struct config_option *options, void *ctx, const char *name, struct irc_chan *value, error_t *err);
+
+/**
+ * Parse and apply a configuration value for the named config opt.
+ *
+ * See config_parse() for more info.
+ *
+ * @param options a CONFIG_OPT_END-terminated array of config_option's
+ * @param nexus the application state
+ * @param ctx the context pointer for the option handler
+ * @param name the config_option::name to look up
+ * @param raw_value the raw value to parse
+ * @param err returned error info
+ *
+ * @see config_parse
+ */
+err_t config_apply_raw (const struct config_option option[], struct nexus *nexus, void *ctx, const char *name, char *raw_value, error_t *err);
+
+/**
+ * Lookup a config_value by name from the given list of config_values for the given config_option
+ */
+const struct config_value* config_get_value (const struct config_option *option, const struct config_value values[], const char *name);
+
+const char* config_get_string (const struct config_option *option, const struct config_value values[], const char *name);
+struct irc_chan* config_get_irc_chan (const struct config_option *option, const struct config_value values[], const char *name);
+void* config_get_user (const struct config_option *option, const struct config_value values[], const char *name, const struct config_user_type *user_type);
+
+#endif
--- /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);
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/module.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,295 @@
+#ifndef MODULE_H
+#define MODULE_H
+
+/**
+ * @file
+ *
+ * Dynamically loadable modules for use with nexus.
+ *
+ * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Modules are then "loaded" by
+ * resolving a `struct module_funcs` symbol called "<modname>_funcs", and then using the init func to construct a
+ * "context", which is then further manipulated by various other module functions.
+ *
+ * Modules are also reference counted, mainly for implementing module_unload(). When a module is first loaded, it has
+ * a reference count of one - the entry in struct modules. Later, modules can be referenced using module_get(), and
+ * returned with module_put() once done. The module should never be destroyed during its lifetime, until a
+ * module_unload() occurs. At this point, the module is removed from the modules_list, and the one reference is
+ * 'passed on' to the module itself. Once it has finished unloading, it can call module_unloaded() on the reference it was
+ * given by module_desc::unload, which may then result in a deferred module_destroy().
+ *
+ * Module destroying itself is an interesting issue, since modules effectively need to be able to destroy themselves
+ * (as they must be able to perform cleanup, and then notify completion from inside an event loop callback). This means
+ * that they cannot directly execute a module_destroy() on themselves - if we call dlclose() with dlopen-mapped code
+ * pages on the stack, a segfault ensues. Hence, they must call module_unloaded() on themselves, which then executes a
+ * deferred module_destroy() if there are no references left. Otherwise, the module should be safe from external code,
+ * as module_put() should never cause a module to be destroyed before module_unloaded() is executed, due to the primary
+ * reference.
+ *
+ * Ugh, it's compliated, I need to write a clearer explenation once it's implemented :)
+ */
+
+struct module;
+
+#include "nexus.h"
+#include "config.h"
+#include <lib/error.h>
+
+#include <sys/queue.h>
+#include <stdbool.h>
+
+enum module_error_code {
+    ERR_MODULE_NONE, 
+    ERR_MODULE_NAME,        ///< invalid module_info.name
+    ERR_MODULE_DUP,         ///< module already opened
+    ERR_MODULE_PATH,        ///< resolving the path failed
+    ERR_MODULE_OPEN,        ///< dlopen() failed
+    ERR_MODULE_SYM,         ///< invalid symbol
+    ERR_MODULE_INIT_FUNC,   ///< invalid module_init_func_t
+    ERR_MODULE_CONF,        ///< value error in configuration data
+    ERR_MODULE_STATE,       ///< module in wrong state for operation
+};
+
+const struct error_list module_errors;
+
+/**
+ * Information required to load/identify a module.
+ */
+struct module_info {
+    /** Human-readable name */
+    const char *name;
+    
+    /** Filesystem path to the .so */
+    const char *path;
+};
+
+/**
+ * A table of (function/other) pointers defining a module's behaviour. This is dynamically resolved from the module DSO
+ * using the "<mod_name>_module" symbol.
+ */
+struct module_desc {
+    /**
+     * Initialize the module, returning an opaque context pointer that is stored in the module state, and supplied for
+     * subsequent calls. The supplied nexus arg can be used to access the global state.
+     *
+     * Implementing this is mandatory.
+     *
+     * @param nexus a pointer to the nexus struct containing the global state
+     * @param ctx_ptr the context pointer should be returned via this
+     * @param err returned error info
+     */
+    err_t (*init) (struct nexus *nexus, void **ctx_ptr, error_t *err);
+
+    /**
+     * A module may define a set of available configuration parameters for use by module_conf, by setting this to an
+     * array of config_option's.
+     *
+     * The handler functions will recieve the module's context pointer as their ctx argument.
+     *
+     * Implementing this is optional, but recommended.
+     */
+    const struct config_option *config_options;
+
+    /**
+     * Unload the module, removing all handlers/callbacks added to the nexus' irc_client. This does not have to act
+     * immediately, and will still have access to all irc_* resources it had earlier, and may perform I/O to unload
+     * cleanly. But the unloading should complete reasonably quickly, after which all event handlers added by the
+     * module to the nexus' ev_base should have been removed, and resources released.
+     *
+     * The module given as an argument is the module itself - which has been removed from the modules_list - the
+     * primary reference is passed on to the module. Once the module has finished unloading, it may call module_put(),
+     * which may then call module_destroy(), if no other references were left.
+     *
+     * If the unload operation fails (returns an error code), then the module is considered as unloaded (as if
+     * module_unloaded() was called - don't call this if you return an error).
+     *
+     * Implementing this is optional, if all of this can be implemented in destroy.
+     *
+     * @param ctx the module's context pointer as returned by init
+     * @param module the hanging module reference, that must be passed to module_unloaded()
+     * @return error code
+     */
+    err_t (*unload) (void *ctx, struct module *module);
+
+    /**
+     * Destroy the module now. No later chances, the module's code will be unloaded directly after this, which means
+     * that attempts to execute the module's code (even on the stack...) after this will segfault.
+     *
+     * The module code /should/ garuntee that this is never called from *inside* the module code - calls to
+     * module_destroy() will be deferred via the event loop if needed.
+     *
+     * Implementing this is optional.
+     */
+    void (*destroy) (void *ctx);
+};
+
+/**
+ * A loaded module.
+ */
+struct module {
+    /** The identifying info for the module */
+    struct module_info info;
+
+    /** Possible dynamically allocated path */
+    char *path_buf;
+
+    /** The dlopen handle */
+    void *handle;
+
+    /** The module entry point */
+    struct module_desc *desc;
+
+    /** The module context object */
+    void *ctx;
+
+    /** Reference back to modules struct used to load this module */
+    struct modules *modules;
+
+    /** Reference count for destroy() */
+    size_t refcount;
+    
+    /** Is the module currently being unloaded? */
+    bool unloading;
+
+    /** Our entry in the list of modules */
+    TAILQ_ENTRY(module) modules_list;
+};
+
+/**
+ * A set of loaded modules, and functionality to load more
+ */
+struct modules {
+    /** The nexus in use */
+    struct nexus *nexus;
+
+    /** Module search path */
+    const char *path;
+
+    /** List of loaded modules */
+    TAILQ_HEAD(module_ctx_modules, module) list;
+};
+
+
+/**
+ * Maximum length of a module name
+ */
+#define MODULE_NAME_MAX 24
+
+/**
+ * Maximum length of module symbol suffix
+ */
+#define MODULE_SUFFIX_MAX 16
+
+/**
+ * Maximum length of symbol name name, including terminating NUL
+ */
+#define MODULE_SYMBOL_MAX (MODULE_NAME_MAX + 1 + MODULE_SUFFIX_MAX + 1)
+
+/**
+ * Create a new modules state
+ */
+err_t modules_create (struct modules **modules_ptr, struct nexus *nexus);
+
+/**
+ * Set a search path for finding modules by name. The given string won't be copied.
+ *
+ * A module called "<name>" will be searched for at "<path>/mod_<name>.so"
+ *
+ * If path is NULL, this doesn't change anything. This returns the old path, which may be NULL.
+ *
+ * @param modules the modules state
+ * @param path the new search path, or NULL to just get the old one
+ * @return the old search path
+ */
+const char* modules_path (struct modules *modules, const char *path);
+
+/**
+ * Get a reference to the module, which must be returned using module_put
+ *
+ * @param modules the modules state
+ * @param name the module name to get
+ * @return the module struct, or NULL if not found
+ */
+struct module* modules_get (struct modules *modules, const char *name);
+
+/**
+ * Unload all modules, this just calls module_unload for each module, logging errors as warnings.
+ */
+err_t modules_unload (struct modules *modules);
+
+/**
+ * Destroy all modules immediately.
+ */
+void modules_destroy (struct modules *modules);
+
+
+
+/*******************************************/
+
+
+/**
+ * Return a module's name
+ */
+const char* module_name (struct module *module);
+
+/**
+ * Load a new module, as named by info.
+ *
+ * If info->path is not given, the module will be searched for using the path set by modules_path().
+ *
+ * If module_ptr is given, a reference (that must be module_put'd) is returned, that must be returned using 
+ * module_put().
+ *
+ * @param modules the module-loading context
+ * @param module_ptr retuturned new module struct, as a new reference, if not NULL.
+ * @param info the info required to identify and load the module
+ * @param err returned error info
+ */
+err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *info, error_t *err);
+
+/**
+ * Return a module retrieved using module_get
+ */
+void module_put (struct module *module);
+
+/**
+ * Look up a module configuration option by name
+ */
+const struct config_option* module_conf_lookup (struct module *module, const char *name, error_t *err);
+
+/**
+ * Apply a module configuration option using a structured value
+ */
+err_t module_conf (struct module *module, const struct config_option *opt, const struct config_value *value, error_t *err);
+
+/**
+ * Set a module configuration option using a raw value
+ */
+err_t module_conf_raw (struct module *module, const char *name, char *value, error_t *err);
+
+/**
+ * Unload a module. This removes the module from the modules_list, marks it as unloading, and then calls the module's
+ * \p unload function, passing it the primary reference. The module's unload code will then go about shutting down the
+ * module, and once that is done, it may module_put() the primary reference, which may then lead to module_destroy().
+ *
+ * This returns ERR_MODULE_STATE if the module is already being unloaded, or other errors from the module's own unload
+ * functionality.
+ */
+err_t module_unload (struct module *module);
+
+/**
+ * Used by a module itself to indicate that an module_desc::unload() operation has completed. This will execute a
+ * deferred module_destroy() if there are no more references left on the module.
+ *
+ * Note: this is not intended to be called from outside the given module itself...
+ */
+void module_unloaded (struct module *module);
+
+/**
+ * Destroy a module, releasing as many resources as possible, but not stopping for errors.
+ *
+ * This does not enforce the correct refcount - 'tis the caller's responsibility. Prints out a warning if
+ * refcount > 0.
+ */
+void module_destroy (struct module *module);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/nexus.c	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,596 @@
+#include "nexus.h"
+#include "lua_config.h"
+#include "sock.h"
+#include <lib/log.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <signal.h>
+#include <string.h>
+
+/**
+ * Command-line option codes
+ */
+enum option_code {
+    OPT_HELP            = 'h',
+    OPT_DEFAULTS        = 'D',
+    OPT_NETWORK         = 'n',
+    OPT_MODULE          = 'm',
+    OPT_CONF            = 'C',
+    OPT_DEBUG           = 'd',
+    OPT_CONFIG          = 'c',
+    
+    /** Options without short names */
+    _OPT_EXT_BEGIN      = 0x00ff,
+    
+    OPT_CONSOLE,
+    OPT_CHANNEL,
+};
+
+/**
+ * Command-line option definitions
+ */
+static struct option options[] = {
+    {"help",            0,  NULL,   OPT_HELP        },
+    {"defaults",        1,  NULL,   OPT_DEFAULTS    },
+    {"network",         1,  NULL,   OPT_NETWORK     },
+    {"channel",         1,  NULL,   OPT_CHANNEL     },
+    {"module",          1,  NULL,   OPT_MODULE      },
+    {"conf",            1,  NULL,   OPT_CONF        },
+    {"debug",           0,  NULL,   OPT_DEBUG       },
+    {"console",         0,  NULL,   OPT_CONSOLE     },
+    {"config",          1,  NULL,   OPT_CONFIG      },
+    {0,                 0,  0,      0               },
+};
+
+/**
+ * Short command-line option defintion
+ */
+const char *short_options = "hn:c:m:C:d";
+
+/**
+ * Display --help output on stdout.
+ *
+ * If nexus is given, --config options for loaded modules are also listed
+ */
+static void usage (struct nexus *nexus, const char *exe)
+{
+    printf("Usage: %s [OPTIONS]\n", exe);
+    printf("\n");
+    printf(" --help / -h            display this message\n");
+    printf(" --defaults / -D        set the IRC client default info using '<nickname>:<username>:<realname>'\n");
+    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl][:ssl_cafile=<path>][:ssl_verify][:ssl_cert=<path>][:ssl_pkey=<path>]]' format\n");
+    printf(" --channel              add an IRC channel using '<network>:<channel>' format\n");
+    printf(" --module / -m          add a module using '<name>:<path>' format\n");
+    printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
+    printf(" --debug / -d           set logging level to DEBUG\n");
+    printf(" --config / -c          load a Lua config file from '<path>'\n");
+    printf(" --console              use the interactive console\n");
+    
+    // dump loaded module configs
+    if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
+        struct module *module;
+
+        printf("\n");
+        printf("Module configuration options\n");
+
+        TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
+            printf("\n");
+            printf("%s:\n", module->info.name);
+
+            // XXX: shouldn't be accessing directly
+            if (module->desc->config_options) {
+                const struct config_option *opt;
+
+                for (opt = module->desc->config_options; opt->name; opt++) {
+                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help);
+                }
+
+            } else {
+                printf("\t???\n");
+            }
+        }
+    }
+}
+
+/**
+ * Parse and apply a --defaults option, setting the resulting defaults to our irc_client
+ */
+static err_t apply_defaults (struct nexus *nexus, char *opt, error_t *err)
+{
+    struct irc_client_defaults defaults = {
+        .register_info      = { NULL, NULL, NULL },
+        .service            = IRC_PORT,
+        .service_ssl        = IRC_SSL_PORT,
+    };
+
+    // parse the required fields
+    if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <nickname> field for --defaults");
+    
+    if ((defaults.register_info.username = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <username> field for --defaults");
+    
+    if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <realname> field for --defaults");
+
+    // trailing garbage?
+    if (opt)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
+    
+    // apply them
+    log_info("using default nick/user/real-name: %s/%s/%s", 
+            defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname);
+
+    irc_client_set_defaults(nexus->client, &defaults);
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Parse and apply a --network option, adding the given network to our irc_client.
+ */
+static err_t apply_network (struct nexus *nexus, char *opt, error_t *err)
+{
+    struct irc_net_info info;
+    const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL;
+    bool use_ssl = false, ssl_verify = false;
+
+    // init to zero
+    memset(&info, 0, sizeof(info));
+
+    // parse the required fields
+    if ((info.network = strsep(&opt, ":")) == NULL)
+        JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --network");
+    
+    if ((info.hostname = strsep(&opt, ":")) == NULL)
+        JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <hostname> field for --network");
+    
+    // parse the optional fields
+    if (opt)
+        info.service = strsep(&opt, ":");
+    
+    // parse any remaining flags
+    while (opt) {
+        char *flag_token = strsep(&opt, ":");
+        char *flag = strsep(&flag_token, "=");
+        char *value = flag_token;
+
+        if (strcmp(flag, "ssl") == 0) {
+            use_ssl = true;
+
+        } else if (strcmp(flag, "ssl_cafile") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cafile");
+
+            ssl_cafile = value;
+        
+        } else if (strcmp(flag, "ssl_verify") == 0) {
+            ssl_verify = true;
+
+        } else if (strcmp(flag, "ssl_cert") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cert");
+            
+            ssl_cert = value;
+
+        } else if (strcmp(flag, "ssl_pkey") == 0) {
+            if (!value)
+                JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_pkey");
+
+            ssl_pkey = value;
+
+        } else
+            JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unrecognized flag for --network");
+    }
+
+    // SSL?
+    if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) {
+        // verify
+        if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey))
+            JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey");
+        
+        // create
+        if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err))
+            goto error;
+    }
+
+    // create the net
+    log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);
+
+    if (irc_client_add_net(nexus->client, NULL, &info, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    // cleanup
+    if (info.ssl_cred)
+        ssl_client_cred_put(info.ssl_cred);
+
+    return ERROR_CODE(err);
+}
+
+/**
+ * Parse and apply a --channel option, adding the given channel to the given irc_net.
+ */
+static err_t apply_channel (struct nexus *nexus, char *opt, error_t *err)
+{
+    const char *network = NULL;
+    struct irc_net *net;
+    struct irc_chan_info info = {
+        .channel            = NULL,
+    };
+
+    // parse the required fields
+    if ((network = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <network> field for --channel");
+    
+    if ((info.channel = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <channel> field for --channel");
+
+    // trailing garbage?
+    if (opt)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
+
+    // look up the net
+    if ((net = irc_client_get_net(nexus->client, network)) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown network for --channel");
+    
+    // add the channel
+    log_info("add channel '%s' on network '%s'", info.channel, net->info.network);
+
+    if (irc_net_add_chan(net, NULL, &info, err))
+        return ERROR_CODE(err);
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Parse and apply a --module option, loading the given module.
+ */
+static err_t apply_module (struct nexus *nexus, char *opt, error_t *err)
+{
+    struct module_info info = {
+        .name       = NULL,
+        .path       = NULL,
+    };
+
+    // parse the required fields
+    if ((info.name = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --module");
+
+    if ((info.path = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <path> field for --module");
+
+    // trailing garbage?
+    if (opt)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
+    
+    // load it
+    log_info("loading module '%s' from path '%s'", info.name, info.path);
+
+    if (module_load(nexus->modules, NULL, &info, err))
+        return ERROR_CODE(err);
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Parse and apply a --conf option, calling the module's conf func.
+ */
+static err_t apply_conf (struct nexus *nexus, char *opt, error_t *err)
+{
+    struct module *module;
+    const char *module_name, *conf_name;
+    char *conf_value;
+
+    // parse the required fields
+    if ((module_name = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <module> field for --config");
+
+    if ((conf_name = strsep(&opt, ":")) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --config");
+
+    // value is the rest of the data, might be NULL
+    conf_value = opt;
+
+    // lookup the module
+    if ((module = modules_get(nexus->modules, module_name)) == NULL)
+        return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown module for --config");
+    
+    // do the config
+    log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);
+
+    if (module_conf_raw(module, conf_name, conf_value, err))
+        return ERROR_CODE(err);
+
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Open the console
+ */
+static err_t apply_console (struct nexus *nexus, error_t *err)
+{
+    struct console_config config = {
+        .prompt     = "> ",
+    };
+    struct console *console;
+
+    log_info("initializing the console");
+    
+    // init the console
+    if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err))
+        return ERROR_CODE(err);
+    
+    // set it as the log output handler
+    console_set_log_output(console);
+
+    // create the lua console on top of that
+    if (lua_console_create(&nexus->lua_console, console, nexus->lua, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    console_destroy(console);
+
+    return ERROR_CODE(err);
+}
+
+err_t nexus_load_config (struct nexus *nexus, const char *path, error_t *err)
+{
+    return lua_config_load(nexus->lua, path, err);
+}
+
+/**
+ * Load the lua config file from \a path
+ */
+static err_t apply_config (struct nexus *nexus, char *path, error_t *err)
+{
+    log_info("loading lua config from %s", path);
+    
+    return nexus_load_config(nexus, path, err);
+}
+
+/**
+ * Parse arguments and apply them to the given nexus
+ */
+static err_t parse_args (struct nexus *nexus, int argc, char **argv, error_t *err)
+{
+    int opt, option_index;
+    
+    // parse options
+    while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) {
+        switch (opt) {
+            case OPT_HELP:
+                usage(nexus, argv[0]);
+
+                // XXX: return instead
+                exit(EXIT_SUCCESS);
+            
+            // XXX: come up with something nicer for parsing these command-line options            
+            case OPT_NETWORK:
+                if (apply_network(nexus, optarg, err))
+                    goto error;
+
+                break;
+            
+            case OPT_DEFAULTS:
+                if (apply_defaults(nexus, optarg, err))
+                    goto error;
+
+                break;
+
+            case OPT_CHANNEL:
+                if (apply_channel(nexus, optarg, err))
+                    goto error;
+
+                break;
+            
+            case OPT_MODULE:
+                if (apply_module(nexus, optarg, err))
+                    goto error;
+
+                break;
+
+            case OPT_CONF:
+                if (apply_conf(nexus, optarg, err))
+                    goto error;
+
+                break;
+            
+            case OPT_DEBUG:
+                set_log_level(LOG_DEBUG);
+                break;
+            
+            case OPT_CONSOLE:
+                if (apply_console(nexus, err))
+                    goto error;
+                
+                break;
+            
+            case OPT_CONFIG:
+                if (apply_config(nexus, optarg, err))
+                    goto error;
+
+                break;
+
+            case '?':
+                usage(nexus, argv[0]);
+                return SET_ERROR(err, &general_errors, ERR_CMD_OPT);
+        }
+    }
+    
+    // ok
+    return SUCCESS;
+
+error:
+    return ERROR_CODE(err);    
+}
+
+void nexus_shutdown (struct nexus *nexus)
+{
+    // destroy the console
+    if (nexus->lua_console)
+        lua_console_destroy(nexus->lua_console);
+    
+    nexus->lua_console = NULL;
+
+    // unload the modules
+    if (nexus->modules)
+       modules_unload(nexus->modules);
+
+    // quit the irc client
+    if (nexus->client)
+       irc_client_quit(nexus->client, "Goodbye, cruel world ;(");
+
+    // remove the signal handlers
+    if (nexus->signals)
+        signals_free(nexus->signals);
+
+    nexus->signals = NULL;
+
+    // now event_base_dispatch should return once everythings' shut down...
+    nexus->shutdown = true;
+}
+
+void nexus_crash (struct nexus *nexus)
+{
+    // force-quit the event loop
+    event_base_loopbreak(nexus->ev_base);
+}
+
+void nexus_destroy (struct nexus *nexus)
+{
+    // destroy the console
+    if (nexus->lua_console)
+        lua_console_destroy(nexus->lua_console);
+
+    // destroy the lua state
+    if (nexus->lua)
+        nexus_lua_destroy(nexus->lua);
+
+    // destroy the modules
+    if (nexus->modules)
+        modules_destroy(nexus->modules);
+
+    // destroy the irc client
+    if (nexus->client)
+        irc_client_destroy(nexus->client);
+
+    // remove the signal handlers
+    if (nexus->signals)
+        signals_free(nexus->signals);
+    
+    // finally, the libevent base
+    if (nexus->ev_base)
+        event_base_free(nexus->ev_base);
+
+    // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :)
+    memset(nexus, 0, sizeof(*nexus));
+}
+
+static void on_sig_shutdown (evutil_socket_t sig, short what, void *arg)
+{
+    struct nexus *nexus = arg;
+
+    (void) sig;
+    (void) what;
+    
+    if (!nexus->shutdown) {
+        // shutdown
+        log_info("Terminating...");
+
+        nexus_shutdown(nexus);
+
+    } else {
+        // already tried to shutdown
+        log_info("Crashing!");
+
+        nexus_crash(nexus);
+    }
+}
+
+int main (int argc, char **argv) 
+{
+    struct nexus _nexus, *nexus = &_nexus;
+    error_t err;
+
+    // zero nexus
+    memset(nexus, 0, sizeof(*nexus));
+
+    // initialize libevent
+    if ((nexus->ev_base = event_base_new()) == NULL)
+        FATAL("event_base_new");
+    
+
+    // initialize signal handlers
+    if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
+        FATAL("signals_create");
+ 
+    // add our signal handlers
+    if (signal_ignore(SIGPIPE, &err))
+        FATAL_ERROR(&err, "signals_ignore");
+    
+    // add our SIGTERM handler before console_init()?
+    if (
+            (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus))
+        ||  (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus))
+    )
+        FATAL_ERROR(&err, "signals_add");
+
+    // initialize sock module
+    if (sock_init(nexus->ev_base, &err))
+        FATAL_ERROR(&err, "sock_init");
+
+    // modules 
+    if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus)))
+        FATAL_ERROR(&err, "modules_create");
+    
+    // the IRC client
+    if (irc_client_create(&nexus->client, &err))
+        FATAL_ERROR(&err, "irc_client_create");
+    
+    // lua state
+    if (nexus_lua_create(&nexus->lua, nexus, &err))
+        FATAL_ERROR(&err, "nexus_lua_create");
+
+    
+    // parse args
+    if (parse_args(nexus, argc, argv, &err))
+        FATAL_ERROR(&err, "parse_args");
+
+  
+    // run event loop
+    log_info("entering event loop");
+
+    if (event_base_dispatch(nexus->ev_base)) {
+        if (nexus->shutdown) {
+            log_info("clean shutdown completed");
+
+        } else {
+            FATAL("event_base_dispatch returned without shutdown");
+
+        }
+    } else {
+        log_warn("zero return from event_base_dispatch");
+    }
+
+    // cleanup
+    nexus_destroy(nexus);
+    nexus = NULL;
+
+    // ok
+    return EXIT_SUCCESS;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/nexus.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,72 @@
+#ifndef NEXUS_H
+#define NEXUS_H
+
+/**
+ * @file
+ *
+ * A nexus is the central brain of the application; the place where the main() method is implemented
+ */
+struct nexus;
+
+#include <event2/event.h>
+#include "signals.h"
+#include "module.h"
+#include "irc_client.h"
+#include "nexus_lua.h"
+#include "lua_console.h"
+
+/**
+ * The central brain, as created in the main() function.
+ */
+struct nexus {
+    /** The libevent base */
+    struct event_base *ev_base;
+
+    /** Our signal handlers */
+    struct signals *signals;
+
+    /** Our loaded modules */
+    struct modules *modules;
+
+    /** The IRC client state */
+    struct irc_client *client;
+
+    /** Our lua state */
+    struct nexus_lua *lua;
+
+    /** Our lua console */
+    struct lua_console *lua_console;
+
+    /** Shutting down? */
+    bool shutdown;
+};
+
+/**
+ * Load a config file into the nexus from the given path
+ */
+err_t nexus_load_config (struct nexus *nexus, const char *path, error_t *err);
+
+/**
+ * Shut down the nexus cleanly. It /should/ be safe to call this several times, but it won't do anything.
+ *
+ * Once everything has shut down nicely (which it should, unless there's something buggy), the event loop should exit,
+ * and the main() function return.
+ */
+void nexus_shutdown (struct nexus *nexus);
+
+/**
+ * Shut down the nexus fast. This will stop the event loop right away, which should lead to nexus_destroy being called.
+ */
+void nexus_crash (struct nexus *nexus);
+
+/**
+ * Destroy the nexus directly. This is intended to be used once the event loop as exited.
+ */
+void nexus_destroy (struct nexus *nexus);
+
+/**
+ * The nexus main function, application entry point, etc.
+ */
+int main (int argc, char **argv);
+
+#endif /* NEXUS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/signals.c	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,139 @@
+
+#include "signals.h"
+#include "log.h"
+
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <assert.h>
+
+struct signals {
+    /** The libevent base to use */
+    struct event_base *ev_base;
+    
+    /** Information about up to MAX_SIGNALS signals */
+    struct signal {
+        /** The event we use to handle the signal */
+        struct event *ev;
+
+    } sig_list[MAX_SIGNALS];
+    
+    /** Number of used sig_list slots */
+    int sig_count;
+};
+
+void signals_loopexit (int signal, short event, void *arg) 
+{
+    struct signals *signals = arg;
+
+    (void) event;
+
+    log_info("caught %s: exiting", /* strsignal(signal) */ "xxx");
+    
+    if (event_base_loopexit(signals->ev_base, NULL))
+        FATAL("event_base_loopexit");
+}
+
+void signals_ignore (int signal, short event, void *arg)
+{
+    struct signals *signals = arg;
+    
+    (void) signal;
+    (void) event;
+    (void) arg;
+    (void) signals;
+    
+    /* ignore */
+}
+
+err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base)
+{
+    struct signals *signals;
+
+    if ((signals = calloc(1, sizeof(*signals))) == NULL)
+        return ERR_CALLOC;
+    
+    // simple attributes
+    signals->ev_base = ev_base;
+
+    // done
+    *signals_ptr = signals;
+
+    return SUCCESS;
+}
+
+err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg) 
+{
+    struct signal *sig_info;
+    
+    // find our sig_info
+    assert(signals->sig_count < MAX_SIGNALS);
+    sig_info = signals->sig_list + (signals->sig_count++);
+    
+    // set up the libevent signal events
+    if ((sig_info->ev = evsignal_new(signals->ev_base, sigval, sig_handler, arg)) == NULL)
+        return ERR_EVENT_NEW;
+
+    if (evsignal_add(sig_info->ev, NULL))
+        return ERR_EVENT_ADD;
+
+    // ok
+    return SUCCESS;
+}
+
+err_t signal_ignore (int signum, struct error_info *err)
+{
+    struct sigaction act = {
+        .sa_handler     = SIG_IGN,
+    };
+    
+    // signall the handler
+    if (sigaction(signum, &act, NULL))
+        RETURN_SET_ERROR_ERRNO(err, ERR_SIGACTION);
+
+    // ok
+    return SUCCESS;
+}
+
+struct signals *signals_default (struct event_base *ev_base) 
+{
+    struct signals *signals = NULL;
+    struct error_info err;
+    
+    // alloc signals
+    if (signals_create(&signals, ev_base))
+        return NULL;
+    
+    // add the set of default signals
+    if (signal_ignore(SIGPIPE, &err))
+        log_error(&err, "signal_ignore(SIGPIPE)");
+
+    if ((ERROR_CODE(&err) = signals_add(signals, SIGINT, &signals_loopexit, signals))) {
+        log_error(&err, "signals_add(SIGINT)");
+        goto error;
+    }
+
+    // ok
+    return signals;
+
+error:
+    signals_free(signals);
+
+    return NULL;
+}   
+
+void signals_free (struct signals *signals) 
+{
+    int i;
+    
+    // free all events
+    for (i = 0; i < signals->sig_count; i++) {
+        event_free(signals->sig_list[i].ev);
+    }
+    
+    // free the info itself
+    free(signals);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/spbot/signals.h	Wed May 27 23:57:48 2009 +0300
@@ -0,0 +1,61 @@
+#ifndef SPBOT_SIGNALS_H
+#define SPBOT_SIGNALS_H
+
+/**
+ * @file 
+ *
+ * Some kind of convenience stuff to handle signals, but actually pretty crap.
+ */
+#include <lib/error.h>
+#include <event2/event.h>
+
+/**
+ * How many signals we can define actions for
+ */
+#define MAX_SIGNALS 8
+
+/**
+ * State for a set of signals
+ */
+struct signals;
+
+/**
+ * Used as a handler for signals that should cause a loopexit.
+ */
+void signals_loopexit (int signal, short event, void *arg);
+
+/**
+ * Used to receive signals, but discard them, and continue what we were doing.
+ */
+void signals_ignore (int signal, short event, void *arg);
+
+/**
+ * Allocate a signals struct, acting on the given ev_base.
+ *
+ * Returns NULL on failure
+ */
+err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base);
+
+/**
+ * Add a signal to be handled by the given signals struct with the given handler.
+ */
+err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg);
+
+/**
+ * Ignore the given sigval.
+ */
+err_t signal_ignore (int signum, error_t *err);
+
+/**
+ * Add a set of default signals
+ *      SIGPIPE     signals_ignore
+ *      SIGINT      signals_loopexit
+ */
+struct signals *signals_default (struct event_base *ev_base);
+
+/**
+ * Free the resources/handlers associated with the given signal handler set
+ */
+void signals_free (struct signals *signals);
+
+#endif /* SPBOT_SIGNALS_H */