improve the config module futher, now the module_desc interface uses structured config_value's
authorTero Marttila <terom@fixme.fi>
Tue, 31 Mar 2009 23:36:05 +0300
changeset 100 cfb7776bd6f0
parent 99 155a6c7d3886
child 101 bb8ed1382103
improve the config module futher, now the module_desc interface uses structured config_value's
src/config.c
src/config.h
src/error.c
src/error.h
src/irc_client.c
src/irc_log.c
src/irc_proto.h
src/module.c
src/module.h
src/nexus.c
--- a/src/config.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/config.c	Tue Mar 31 23:36:05 2009 +0300
@@ -1,21 +1,140 @@
 #include "config.h"
 
 #include <string.h>
+#include <assert.h>
 
-err_t config_apply (struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err)
+const struct config_option* config_lookup (const struct config_option *options, const char *name, struct error_info *err)
 {
-    struct config_option *option;
+    const struct config_option *option;
 
     // find the matching config opt
     for (option = options; option->name && option->type; option++) {
         if (strcmp(option->name, name) == 0)
+            return option;
+    }
+
+    // not found
+    SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown configuration option");
+
+    return NULL;
+}
+
+/**
+ * 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;
+
+    // parse required args
+    if ((network = strsep(&raw_value, "/")) == NULL)
+        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <network> for CONFIG_IRC_CHAN value");
+
+    if ((channel = strsep(&raw_value, "/")) == NULL)
+        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <channel> for CONFIG_IRC_CHAN value");
+    
+    // extraneous stuff?
+    if (raw_value)
+        JUMP_SET_ERROR_STR(err, ERR_MODULE_CONF, "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_MODULE_CONF, "unknown network/channel name for CONFIG_IRC_CHAN value");
+    
+    // ok
+    return chan;
+
+error:
+    return NULL;    
+}
+
+err_t config_parse (const struct config_option *option, struct nexus *nexus, struct config_value *value, char *raw_value, struct error_info *err)
+{
+    // parse the value 
+    switch (option->type) {
+        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;
+        
+        default:
+            NOT_REACHED();
     }
 
+    // copy the type
+    value->type = option->type;
+
+    // ok
+    return SUCCESS; 
+}
+
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value *value, struct error_info *err)
+{
+    // wrong type?
+    if (option->type != value->type)
+        // XXX: info about type names
+        return SET_ERROR(err, ERR_CONFIG_TYPE);
+
+    // null?
+    if (!value)
+        RETURN_SET_ERROR_STR(err, ERR_CONFIG_REQUIRED, option->name);
+    
+    // call the handler
+    switch (option->type) {
+        case CONFIG_STRING:     return option->func.string(ctx, value->string, err);
+        case CONFIG_IRC_CHAN:   return option->func.irc_chan(ctx, value->irc_chan, err);
+        default:                NOT_REACHED();
+    }
+}
+
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value *value, struct error_info *err)
+{
+    const struct config_option *option;
+
     // no matching option found?
-    if (!option)
-        RETURN_SET_ERROR_STR(err, ERR_CONFIG_NAME, name);
+    if (!(option = config_lookup(options, name, err)))
+        return ERROR_CODE(err);
+    
+    // apply it    
+    return config_apply_opt(option, ctx, value, err);
+}
 
-    // call the handler
-    return option->func(ctx, value, 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_STRING, { .string = value } };
+
+    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_STRING, { .irc_chan = value } };
+
+    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;
+
+    // no matching option found?
+    if (!(option = config_lookup(options, name, err)))
+        return ERROR_CODE(err);
+    
+    // parse it
+    if (config_parse(option, nexus, &value, raw_value, err))
+        return ERROR_CODE(err);
+    
+    // apply it
+    return config_apply_opt(option, ctx, &value, err);
+}
+
--- a/src/config.h	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/config.h	Tue Mar 31 23:36:05 2009 +0300
@@ -16,6 +16,26 @@
 
     /** A plain NUL-terminated string */
     CONFIG_STRING,
+
+    /** An IRC channel */
+    CONFIG_IRC_CHAN,
+};
+
+/**
+ * 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;
+    };
 };
 
 /**
@@ -25,11 +45,14 @@
     /** The name of the config option */
     const char *name;
 
-    /** The type of the value, XXX: unused */
+    /** The type of the value expected */
     enum config_type type;
     
-    /** The value handler func */
-    err_t (*func) (void *ctx, char *value, struct error_info *err);
+    /** The value handler func, 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;
 
     /** The value description */
     const char *description;
@@ -38,11 +61,102 @@
     const char *help;
 };
 
+#define CONFIG_OPT_STRING(name, func, desc, help) \
+    {   name, CONFIG_STRING, { .string = func }, desc, help }
+
+#define CONFIG_OPT_IRC_CHAN(name, func, desc, help) \
+    {   name, CONFIG_IRC_CHAN, { .irc_chan = func }, desc, help }
+
+#define CONFIG_OPT_END \
+    {   NULL,   CONFIG_INVALID, { NULL }, NULL, NULL }
+
 /**
- * Apply a configuration name/value to an array of config_option's.
+ * Lookup a config option by name.
  *
- * This finds the appropriate config_option and calls its func.
+ * @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
  */
-err_t config_apply (struct config_option *options, void *ctx, const char *name, char *value, struct error_info *err);
+const struct config_option* config_lookup (const struct config_option *options, const char *name, struct error_info *err);
+
+// XXX: move this into nexus
+#include "nexus.h"
+/**
+ * Parse a raw value into a suitable configuration value, based on the config option 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
+ *
+ * @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 parsed configuration value to the given option.
+ *
+ * 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 value the parsed value
+ * @param err returned error info
+ */
+err_t config_apply_opt (const struct config_option *option, void *ctx, const struct config_value *value, struct error_info *err);
+
+/**
+ * Apply a parsed configuration 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 parsed value
+ * @param err returned error info
+ */
+err_t config_apply (const struct config_option *options, void *ctx, const char *name, const struct config_value *value, 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 *options, struct nexus *nexus, void *ctx, const char *name, char *raw_value, struct error_info *err);
 
 #endif
--- a/src/error.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/error.c	Tue Mar 31 23:36:05 2009 +0300
@@ -19,7 +19,6 @@
     {   ERR_EVENT_ADD,                      "event_add",                                ERR_EXTRA_NONE      },
     {   ERR_EVSQL_NEW_PQ,                   "evsql_new_pq",                             ERR_EXTRA_NONE      },
     {   ERR_EVSQL_QUERY_EXEC,               "evsql_query_exec",                         ERR_EXTRA_NONE      },
-    {   ERR_CONFIG_NAME,                    "no matching config name",                  ERR_EXTRA_STR       },
     {   ERR_CMD_OPT,                        "argv",                                     ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
@@ -45,18 +44,44 @@
     {   ERR_GNUTLS_RECORD_GET_DIRECTION,    "gnutls_record_get_direction",              ERR_EXTRA_GNUTLS    },
     {   _ERR_INVALID,                       NULL,                                       0                   }
 
-}, _irc_proto_error_desc[] = {
+}, _irc_error_desc[] = {
     {   ERR_LINE_TOO_LONG,                  "IRC line is too long",                     ERR_EXTRA_NONE      },
     {   ERR_LINE_INVALID_TOKEN,             "Illegal token value for IRC line",         ERR_EXTRA_NONE      },
     {   ERR_INVALID_NM,                     "Invalid nickmask",                         ERR_EXTRA_NONE      },
     {   ERR_INVALID_NICK_LENGTH,            "Nickname is too long",                     ERR_EXTRA_NONE      },
+    
+    // extra: the name of the invalid field
+    {   ERR_IRC_NET_INFO,                   "invalid irc_net_info",                     ERR_EXTRA_STR       },
+    {   ERR_IRC_NET_STATE,                  "invalid irc_net state for operation",      ERR_EXTRA_NONE      },
+    {   ERR_IRC_CHAN_STATE,                 "invalid irc_chan state for operation",     ERR_EXTRA_NONE      },
+
     {   _ERR_INVALID,                       NULL,                                       0                   }
+
+}, _config_error_desc[] = {
+    // extra: the (unknown) config field name
+    {   ERR_CONFIG_NAME,                    "no matching config name",                  ERR_EXTRA_STR       },
+
+    // XXX: extra: the mismatching types
+    {   ERR_CONFIG_TYPE,                    "wrong config type",                        ERR_EXTRA_NONE      },
+    
+    // extra: the name of the field with missing value
+    {   ERR_CONFIG_REQUIRED,                "missing required value",                   ERR_EXTRA_STR       },
+
+    {   _ERR_INVALID,                       NULL,                                       0                   }
+
 }, _module_error_desc[] = {
     {   ERR_MODULE_OPEN,                    "module dlopen() failed",                   ERR_EXTRA_STR       },
     {   ERR_MODULE_NAME,                    "invalid module name",                      ERR_EXTRA_NONE      },
     {   ERR_MODULE_INIT_FUNC,               "invalid module init func",                 ERR_EXTRA_STR       },
     {   ERR_MODULE_CONF,                    "module_conf",                              ERR_EXTRA_STR       },
     {   _ERR_INVALID,                       NULL,                                       0                   }
+
+}, _lua_error_desc[] = {
+    {   ERR_LUA_MEM,                        "lua: out of memory",                       ERR_EXTRA_NONE      },
+    {   ERR_LUA_SYNTAX,                     "lua: syntax error",                        ERR_EXTRA_NONE      },
+    {   ERR_LUA_RUN,                        "lua: runtime error",                       ERR_EXTRA_NONE      },
+    {   ERR_LUA_ERR,                        "lua: error handling error",                ERR_EXTRA_NONE      },
+    {   _ERR_INVALID,                       NULL,                                       0                   }
 };
 
 /**
@@ -66,8 +91,9 @@
     _core_error_desc,
     _sock_error_desc,
     _sock_gnutls_error_desc,
-    _irc_proto_error_desc,
+    _irc_error_desc,
     _module_error_desc,
+    _lua_error_desc,
     NULL
 };
 
--- a/src/error.h	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/error.h	Tue Mar 31 23:36:05 2009 +0300
@@ -90,6 +90,8 @@
     /** config errors */
     _ERR_CONFIG     = 0x000b00,
     ERR_CONFIG_NAME,
+    ERR_CONFIG_TYPE,
+    ERR_CONFIG_REQUIRED,
     
     /** lua errors */
     _ERR_LUA        = 0x000c00,
@@ -209,4 +211,9 @@
 #define JUMP_SET_ERROR_INFO(err_info_ptr, from_ptr) do { SET_ERROR_INFO(err_info_ptr, from_ptr); goto error; } while (0)
 #define JUMP_SET_ERROR_STR(err_info_ptr, err_code, err_str) do { _SET_ERROR_STR(err_info_ptr, err_code, err_str); goto error; } while (0)
 
+/**
+ * Macro used to mark code segments that should never be executed (e.g. switch-default), kind of like assert
+ */
+#define NOT_REACHED() assert(false)
+
 #endif
--- a/src/irc_client.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/irc_client.c	Tue Mar 31 23:36:05 2009 +0300
@@ -58,18 +58,28 @@
     };
 
     // combine _net_info and defaults to get net_info
-    if (
-            _net_info->raw_sock
-        ||  (net_info.network = _net_info->network) == NULL
-        ||  (net_info.hostname = _net_info->hostname) == NULL
-        ||  (net_info.service = (_net_info->service ? _net_info->service : (
+    if (_net_info->raw_sock)
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "raw_sock");
+
+    if ((net_info.network = _net_info->network) == NULL)
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "network");
+
+    if ((net_info.hostname = _net_info->hostname) == NULL)
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "hostname");
+    
+    if ((net_info.service = (_net_info->service ? _net_info->service : (
                     _net_info->use_ssl ? client->defaults.service_ssl : client->defaults.service
-            ))) == NULL
-        ||  APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, nickname)
-        ||  APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, username)
-        ||  APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, realname)
-    )
-        return SET_ERROR(err, ERR_IRC_NET_INFO);
+            ))) == NULL)
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "service");
+
+    if (APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, nickname))
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "nickname");
+
+    if (APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, username))
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "username");
+
+    if (APPLY_REGISTER_INFO_DEFAULT(&net_info, _net_info, &client->defaults, realname))
+        RETURN_SET_ERROR_STR(err, ERR_IRC_NET_INFO, "realname");
 
     // create the new irc_chan struct
     if (irc_net_create(&net, &net_info, err))
--- a/src/irc_log.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/irc_log.c	Tue Mar 31 23:36:05 2009 +0300
@@ -440,31 +440,14 @@
  * Fails if the value is invalid, we don't have a database connected, the channel doesn't exist, adding our command
  * handlers/callbacks fails, or sending the initial INSERT-OPEN query fails.
  */
-static err_t irc_log_conf_channel (void *_ctx, char *value, struct error_info *err)
+static err_t irc_log_conf_channel (void *_ctx, struct irc_chan *chan, struct error_info *err)
 {
     struct irc_log_ctx *ctx = _ctx;
-    const char *network, *channel;
-    struct irc_chan *chan;
     
-    // parse required args
-    if ((network = strsep(&value, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <network> for irc_log.channel");
-        
-    if ((channel = strsep(&value, ":")) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid <channel> for irc_log.channel");
-
-    // extraneous stuff?
-    if (value)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "trailing data for irc_log.channel");
-
     // have a db configured?
     if (!ctx->db)
         RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.channel used without any irc_log.db_info");
 
-    // get the channel?
-    if ((chan = irc_client_get_chan(ctx->nexus->client, network, channel)) == NULL)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown channel name");
-
     // begin logging it
     if (irc_log_chan(ctx, chan, err))
         return ERROR_CODE(err);
@@ -477,25 +460,11 @@
  * Our configuration options
  */
 struct config_option irc_log_config_options[] = {
-    {   "db_info",  CONFIG_STRING,  &irc_log_conf_db_info,  "[<key>=<value> [...]]",    "set database connection info, see libpq docs"  },
-    {   "channel",  CONFIG_STRING,  &irc_log_conf_channel,  "<network>:<channel>",      "log the given channel"                         },
-    {   NULL,       0,              NULL,                   NULL,                       NULL                                            }
-};
+    CONFIG_OPT_STRING(      "db_info",  &irc_log_conf_db_info,  "[<key>=<value> [...]]",    "set database connection info, see libpq docs"  ),
+    CONFIG_OPT_IRC_CHAN(    "channel",  &irc_log_conf_channel,  "<channel>",                "log the given channel"                         ),
 
-/**
- * Set a config option
- */
-static err_t irc_log_conf (void *_ctx, const char *name, char *value, struct error_info *err)
-{
-    struct irc_log_ctx *ctx = _ctx;
-
-    // wrong state
-    if (ctx->unloading)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "module is being unloaded");
-    
-    // apply it
-    return config_apply(irc_log_config_options, ctx, name, value, err);
-}
+    CONFIG_OPT_END
+};
 
 /**
  * Deinitialize, logging CLOSE events for all channels, and removing any hooks we've added
@@ -520,9 +489,8 @@
 /**
  * The module function table
  */
-struct module_funcs irc_log_funcs = {
+struct module_desc irc_log_module = {
     .init               = &irc_log_init,
-    .conf               = &irc_log_conf,
     .config_options     = irc_log_config_options,
     .unload             = &irc_log_unload,
 };
--- a/src/irc_proto.h	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/irc_proto.h	Tue Mar 31 23:36:05 2009 +0300
@@ -10,6 +10,16 @@
 #include <stddef.h>
 
 /**
+ * Default TCP port for normal connections
+ */
+#define IRC_PORT "6667"
+
+/**
+ * Default TCP port for SSL connections
+ */
+#define IRC_SSL_PORT "6697"
+
+/**
  * Maximum length of an IRC nickname including terminating NUL.
  */
 #define IRC_NICK_MAX (30 + 1)
--- a/src/module.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/module.c	Tue Mar 31 23:36:05 2009 +0300
@@ -72,11 +72,11 @@
         JUMP_SET_ERROR_STR(err, ERR_MODULE_OPEN, dlerror());
     
     // load the funcs symbol
-    if ((ERROR_CODE(err) = module_symbol(module, (void **) &module->funcs, "funcs")))
+    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->funcs->init(modules->nexus, &module->ctx, err)))
+    if ((module->desc->init(modules->nexus, &module->ctx, err)))
         goto error;
 
     // add to modules list
@@ -110,19 +110,52 @@
     return NULL;
 }
 
-err_t module_conf (struct module *module, const char *name, char *value, struct error_info *err)
+err_t module_conf_raw (struct module *module, const char *name, char *value, struct error_info *err)
 {
-    // call the conf func
-    return module->funcs->conf(module->ctx, name, value, err);
+    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)
+{
+    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;
-    
+
     // invoke the unload func
-    if ((err = module->funcs->unload(module->ctx)))
+    if ((err = module->desc->unload(module->ctx)))
         return err;
+        
+    // update status
+    // XXX: before or after?
+    module->unloading = true;
 
     // ok
     return SUCCESS;
--- a/src/module.h	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/module.h	Tue Mar 31 23:36:05 2009 +0300
@@ -28,12 +28,10 @@
 };
 
 /**
- * A table of (function) pointers defining a module's behaviour. This is dynamically resolved from the module DSO
- * using the "<mod_name>_funcs" symbol.
- *
- * XXX: this also includes non-functions now...
+ * 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_funcs {
+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.
@@ -45,23 +43,10 @@
     err_t (*init) (struct nexus *nexus, void **ctx_ptr, struct error_info *err);
 
     /**
-     * Set a configuration option with the given name and value, given by the user. Configuration settings are not
-     * regarded as unique-per-name, but rather, several invocations of 'conf' with the same name and different values
-     * could set up a multitude of things.
-     *
-     * The given value is either a NUL-terminated string value (which may be mutated, using e.g. strsep), or NULL if
-     * no value was given (flag option).
+     * 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.
      *
-     * @param ctx the module's context pointer as returned by init
-     * @param name the name of the configuration setting
-     * @param value the value of the configuration setting
-     * @param err returned error info
-     */
-    err_t (*conf) (void *ctx, const char *name, char *value, struct error_info *err);
-
-    /**
-     * Optionally, a module may also provide some kind of list of available configuration parameters, by setting this
-     * to a pointer to an array of those.
+     * The handler functions will recieve the module's context pointer as their ctx argument.
      */
     const struct config_option *config_options;
 
@@ -86,8 +71,8 @@
     /** The dlopen handle */
     void *handle;
 
-    /** The resolved function table */
-    struct module_funcs *funcs;
+    /** The module entry point */
+    struct module_desc *desc;
 
     /** The module context object */
     void *ctx;
@@ -95,6 +80,11 @@
     /** Reference back to modules struct used to load this module */
     struct modules *modules;
 
+    /**
+     * Is the module currently being unloaded?
+     */
+    bool unloading;
+
     /** Our entry in the list of modules */
     TAILQ_ENTRY(module) modules_list;
 };
@@ -164,9 +154,19 @@
 struct module* module_get (struct modules *modules, const char *name);
 
 /**
- * Set a module configuration option
+ * Look up a module configuration option by name
  */
-err_t module_conf (struct module *module, const char *name, char *value, struct error_info *err);
+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 means calling its 'unload' func, which will then go about shutting down the module. This might
--- a/src/nexus.c	Tue Mar 31 23:35:24 2009 +0300
+++ b/src/nexus.c	Tue Mar 31 23:36:05 2009 +0300
@@ -9,10 +9,6 @@
 #include <signal.h>
 #include <string.h>
 
-#define DEFAULT_HOST "irc.fixme.fi"
-#define DEFAULT_PORT "6667"
-#define DEFAULT_PORT_SSL "6697"
-
 /**
  * Command-line option codes
  */
@@ -75,10 +71,11 @@
             printf("\n");
             printf("%s:\n", module->info.name);
 
-            if (module->funcs->config_options) {
+            // XXX: shouldn't be accessing directly
+            if (module->desc->config_options) {
                 const struct config_option *opt;
 
-                for (opt = module->funcs->config_options; opt->name && opt->type; opt++) {
+                for (opt = module->desc->config_options; opt->name && opt->type; opt++) {
                     printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, opt->description, opt->help);
                 }
 
@@ -96,8 +93,8 @@
 {
     struct irc_client_defaults defaults = {
         .register_info      = { NULL, NULL, NULL },
-        .service            = DEFAULT_PORT,
-        .service_ssl        = DEFAULT_PORT_SSL,
+        .service            = IRC_PORT,
+        .service_ssl        = IRC_SSL_PORT,
     };
 
     // parse the required fields
@@ -260,7 +257,7 @@
     // do the config
     log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);
 
-    if (module_conf(module, conf_name, conf_value, err))
+    if (module_conf_raw(module, conf_name, conf_value, err))
         return ERROR_CODE(err);
 
     // ok