'working' modules code, and convert irc_log to use said interface, but we've hit the limits on our Makefile, and the compiled module doesn't really work modules
authorTero Marttila <terom@fixme.fi>
Sun, 15 Mar 2009 01:17:22 +0200
branchmodules
changeset 55 6f7f6ae729d0
parent 54 9f74e924b01a
child 56 942370000450
'working' modules code, and convert irc_log to use said interface, but we've hit the limits on our Makefile, and the compiled module doesn't really work
Makefile
src/error.c
src/error.h
src/irc_client.c
src/irc_client.h
src/irc_log.c
src/irc_log.h
src/module.c
src/module.h
src/nexus.c
src/nexus.h
--- a/Makefile	Fri Mar 13 17:38:23 2009 +0200
+++ b/Makefile	Sun Mar 15 01:17:22 2009 +0200
@@ -34,25 +34,28 @@
 # modules
 module_objs = $(patsubst src/%.c,obj/%.o,$(wildcard src/$(1)/*.c))
 
-CORE_OBJS = obj/error.o obj/log.o obj/chain.o obj/signals.o
+CORE_OBJS = obj/error.o obj/log.o obj/chain.o
 SOCK_OBJS = obj/sock.o obj/sock_tcp.o
 SOCK_TEST_OBJS = obj/sock_test.o
 SOCK_GNUTLS_OBJS = obj/sock_gnutls.o
 LINEPROTO_OBJS = obj/line_proto.o
 IRC_OBJS = obj/irc_line.o obj/irc_conn.o obj/irc_net.o obj/irc_chan.o obj/irc_cmd.o obj/irc_proto.o obj/irc_client.o
+NEXUS_OBJS = obj/signals.o obj/module.o
 IRC_LOG_OBJS = obj/irc_log.o
 
 # XXX: not yet there
 #CORE_OBJS = obj/lib/log.o obj/lib/signals.o
 
 # first target
-all: ${BIN_PATHS}
+all: ${BIN_PATHS} modules/irc_log.so
 
 # binaries
-bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} ${IRC_LOG_OBJS}
+bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} ${IRC_LOG_OBJS} ${NEXUS_OBJS}
 
 bin/test: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${SOCK_TEST_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS}
 
+modules/irc_log.so: ${CORE_OBJS} ${IRC_OBJS}
+
 # computed
 CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} ${EVSQL_CFLAGS}
 LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} ${EVSQL_LDFLAGS}
@@ -89,11 +92,14 @@
 
 # XXX: removed $(CPPFLAGS) 
 obj/%.o : src/%.c
-	$(CC) -c $(CFLAGS) $< -o $@
+	$(CC) -fpic -c $(CFLAGS) $< -o $@
 
 bin/% : obj/%.o
 	$(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@
 
+modules/%.so : obj/%.o
+	$(CC) -shared -Wl,-soname,$(basename %@) -o $@ $+ -lc
+
 # documentation
 DOXYGEN_PATH = /usr/bin/doxygen
 DOXYGEN_CONF_PATH = doc/doxygen.conf
--- a/src/error.c	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/error.c	Sun Mar 15 01:17:22 2009 +0200
@@ -4,6 +4,7 @@
 // for the error_desc tables
 #include "sock.h"
 #include "sock_gnutls.h"
+#include "module.h"
 
 #include <string.h>
 #include <stdio.h>
@@ -46,6 +47,11 @@
     {   ERR_INVALID_NM,                     "Invalid nickmask",                         ERR_EXTRA_NONE      },
     {   ERR_INVALID_NICK_LENGTH,            "Nickname is too long",                     ERR_EXTRA_NONE      },
     {   _ERR_INVALID,                       NULL,                                       0                   }
+}, _module_error_desc[] = {
+    {   ERR_MODULE_OPEN,                    "module dlopen() failed",                   ERR_EXTRA_NONE      },
+    {   ERR_MODULE_NAME,                    "invalid module name",                      ERR_EXTRA_NONE      },
+    {   ERR_MODULE_INIT_FUNC,               "invalid module init func",                 ERR_EXTRA_NONE      },
+    {   _ERR_INVALID,                       NULL,                                       0                   }
 };
 
 /**
@@ -56,6 +62,7 @@
     _sock_error_desc,
     _sock_gnutls_error_desc,
     _irc_proto_error_desc,
+    _module_error_desc,
     NULL
 };
 
--- a/src/error.h	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/error.h	Sun Mar 15 01:17:22 2009 +0200
@@ -77,6 +77,9 @@
     /** irc_net errors */
     _ERR_IRC_NET    = 0x000900,
     ERR_IRC_NET_QUIT_STATE,
+
+    /** modul errors */
+    _ERR_MODULE     = 0x000a00,
 };
 
 /**
--- a/src/irc_client.c	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/irc_client.c	Sun Mar 15 01:17:22 2009 +0200
@@ -70,6 +70,18 @@
     return NULL;
 }
 
+struct irc_chan* irc_client_get_chan (struct irc_client *client, const char *network, const char *channel)
+{
+    struct irc_net *net;
+    
+    // lookup network
+    if ((net = irc_client_get_net(client, network)) == NULL)
+        return NULL;
+    
+    // and then return channel lookup
+    return irc_net_get_chan(net, channel);
+}
+
 err_t irc_client_quit (struct irc_client *client, const char *message)
 {
     struct irc_net *net;
--- a/src/irc_client.h	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/irc_client.h	Sun Mar 15 01:17:22 2009 +0200
@@ -44,6 +44,11 @@
 struct irc_net* irc_client_get_net (struct irc_client *client, const char *network);
 
 /**
+ * Get an irc_chan by network/channel name
+ */
+struct irc_chan* irc_client_get_chan (struct irc_client *client, const char *network, const char *channel);
+
+/**
  * Quit cleanly from all our IRC networks.
  *
  * XXX: currently no way to indicate once we've quit all of them
--- a/src/irc_log.c	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/irc_log.c	Sun Mar 15 01:17:22 2009 +0200
@@ -1,6 +1,8 @@
 #include "irc_log.h"
 #include "log.h"
 
+#include <string.h>
+
 // XXX: fix this err_t crap
 #define LIB_ERR_H
 #include <evsql.h>
@@ -28,33 +30,57 @@
     .on_msg         = on_chan_msg,
 };
 
-err_t irc_log_init (struct event_base *ev_base, const struct irc_log_info *info)
+void* irc_log_init (struct modules *modules, struct error_info *err)
 {
-    struct irc_log_ctx *ctx = &_ctx;
+    struct irc_log_ctx *ctx;
+
+    (void) modules;
+    (void) err;
+        
+    // XXX: static pointer
+    ctx = &_ctx;
+
+    // ok
+    return ctx;
+}
+
+err_t irc_log_conf (struct module *module, const char *name, char *value)
+{
+    struct irc_log_ctx *ctx = module->ctx;
+    struct nexus *nexus = module->modules->nexus;
     err_t err;
 
-    // open the database connection
-    if (info->db_info) {
-        log_info("connect to database: %s", info->db_info);
+    if (strcmp(name, "db_info") == 0) {
+        log_info("connect to database: %s", value);
 
-        if ((ctx->db = evsql_new_pq(ev_base, info->db_info, NULL, NULL)) == NULL)
+        if ((ctx->db = evsql_new_pq(nexus->ev_base, value, NULL, NULL)) == NULL)
            return ERR_EVSQL_NEW_PQ;
+
+    } else if (strcmp(name, "channel") == 0) {
+        const char *network = strsep(&value, ":");
+        const char *channel = value;
+
+        struct irc_chan *chan;
+        
+        // kill missing tokens
+        if (!network || !channel) 
+            // XXX: need to fix the error crap
+            return -1;
+
+        // get the channel?
+        if ((chan = irc_client_get_chan(nexus->client, network, channel)) == NULL)
+            return -1;
+
+        // add channel callbacks
+        if ((err = irc_chan_add_callbacks(chan, &_chan_callbacks, ctx)))
+            return err;
+
+    } else {
+        return -1;
+
     }
-    
-    if (info->channel) {
-        log_info("log channel: %s", irc_chan_name(info->channel));
-    }
-
-    // add channel callbacks
-    if ((err = irc_chan_add_callbacks(info->channel, &_chan_callbacks, ctx)))
-        goto error;
 
     // ok
     return SUCCESS;
-
-error:
-    // XXX: cleanup
-    
-    return err;
 }
 
--- a/src/irc_log.h	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/irc_log.h	Sun Mar 15 01:17:22 2009 +0200
@@ -4,28 +4,24 @@
 /**
  * @file
  *
- * Logging IRC events to an SQL database
+ * Module for logging IRC events to an SQL database
  */
+#include "module.h"
 #include "error.h"
 #include "irc_chan.h"
 #include <event2/event.h>
 
 /**
- * Configuration state for irc_log
+ * Initialize the irc_log module to use the given configuration
  */
-struct irc_log_info {
-    /** Database connection string */
-    const char *db_info;
-
-    /** The channel to log */
-    struct irc_chan *channel;
-};
+void* irc_log_init (struct modules *modules, struct error_info *err);
 
 /**
- * Initialize the global irc_log module to use the given configuration
+ * Set one of the config options:
  *
- * XXX: db_info is still unused if not specified
+ *  db_info     - the database connection string
+ *  channel     - the network:channel to log
  */
-err_t irc_log_init (struct event_base *ev_base, const struct irc_log_info *info);
+err_t irc_log_conf (struct module *module, const char *name, char *value);
 
 #endif
--- a/src/module.c	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/module.c	Sun Mar 15 01:17:22 2009 +0200
@@ -1,26 +1,118 @@
 #include "module.h"
+#include "log.h"
 
 #include <stdlib.h>
 #include <dlfcn.h>
+#include <string.h>
+#include <assert.h>
 
 err_t modules_create (struct modules **modules_ptr, struct nexus *nexus)
 {
-    struct moduels *modules;
+    struct modules *modules;
 
     // alloc
     if ((modules = calloc(1, sizeof(*modules))) == NULL)
         return ERR_CALLOC;
     
     // init
-    TAILQ_INIT
+    TAILQ_INIT(&modules->list);
 
     // store
     modules->nexus = nexus;
+
+    // ok
+    *modules_ptr = modules;
+
+    return SUCCESS;
 }
 
-err_t module_load (struct module **module_ptr, struct nexus *nexus, const struct module_info *info, struct error_info *err)
+/**
+ * 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(suffix) <= MODULE_SUFFIX_MAX);
+
+    // format
+    sprintf(sym_name, "%s_%s", module->info.name, suffix);
+
+    // load
+    if ((*sym = dlsym(module->handle, sym_name)) == NULL) {
+        log_error("dlsym(%s, %s) failed: %s", module->info.name, sym_name, dlerror());
+
+        return ERR_MODULE_SYM;
+    }
+
+    // ok
+    return SUCCESS;
+}
+
+err_t module_load (struct modules *modules, struct module **module_ptr, const struct module_info *info, struct error_info *err)
 {
     struct module *module;
+    module_init_func_t init_func;
 
+    // validate the module name
+    if (strlen(info->name) > MODULE_NAME_MAX)
+        return SET_ERROR(err, ERR_MODULE_NAME);
+    
+    // alloc
+    if ((module = calloc(1, sizeof(*module))) == NULL)
+        return SET_ERROR(err, ERR_CALLOC);
+    
+    // store
+    module->info = *info;
+    module->modules = modules;
 
+    // clear dlerrors
+    (void) dlerror();
+
+    // load it
+    if ((module->handle = dlopen(info->path, RTLD_NOW)) == NULL) {
+        log_error("dlopen(%s) failed: %s", info->path, dlerror());
+
+        JUMP_SET_ERROR(err, ERR_MODULE_OPEN);
+    }
+    
+    // load the init symbol    
+    if ((ERROR_CODE(err) = module_symbol(module, (void *) &init_func, "init")))
+        JUMP_SET_ERROR(err, ERR_MODULE_INIT_FUNC);
+
+    // call it
+    if ((module->ctx = init_func(modules, err)))
+        goto error;
+
+    // add to modules list
+    TAILQ_INSERT_TAIL(&modules->list, module, modules_list);
+
+    // ok
+    *module_ptr = module;
+
+    return SUCCESS;
+
+error:
+    // XXX: cleanup
+    free(module);
+
+    return ERROR_CODE(err);    
 }
+
+err_t module_conf (struct module *module, const char *name, char *value)
+{
+    module_conf_func_t conf_func;
+    err_t err;
+
+    // load the conf symbol
+    if ((err = module_symbol(module, (void *) &conf_func, "conf")))
+        return err;
+
+    // call it
+    if ((err = conf_func(module, name, value)))
+        return err;
+
+    // ok
+    return SUCCESS;
+}
--- a/src/module.h	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/module.h	Sun Mar 15 01:17:22 2009 +0200
@@ -4,7 +4,11 @@
 /**
  * @file
  *
- * Dynamically loadable modules for use with nexus
+ * Dynamically loadable modules for use with nexus.
+ *
+ * The modules are loaded using dlopen(), and hence should be standard dynamic libraries. Module initialization happens
+ * using a module_init_func_t named "<name>_init", which should return some kind of context pointer, which can later be
+ * used to perform other operations on the module.
  */
 #include "nexus.h"
 #include "error.h"
@@ -29,11 +33,17 @@
     /** The identifying info for the module */
     struct module_info info;
 
+    /** The dlopen handle */
+    void *handle;
+
     /** The module context object */
     void *ctx;
 
+    /** Pointer back to the modules struct used to load this */
+    struct modules *modules;
+
     /** Our entry in the list of modules */
-    TAILQ_ENTRY(module) ctx_modules;
+    TAILQ_ENTRY(module) modules_list;
 };
 
 /**
@@ -44,13 +54,54 @@
     struct nexus *nexus;
 
     /** List of loaded modules */
-    TAILQ_HEAD(module_ctx_modules, module) modules;
+    TAILQ_HEAD(module_ctx_modules, module) list;
+};
+
+/**
+ * Possible error codes
+ */
+enum module_error_code {
+    _ERR_MODULE_BEGIN = _ERR_MODULE,
+    
+    ERR_MODULE_OPEN,        ///< dlopen() failed
+    ERR_MODULE_NAME,        ///< invalid module_info.name
+    ERR_MODULE_SYM,         ///< invalid symbol
+    ERR_MODULE_INIT_FUNC,   ///< invalid module_init_func_t
 };
 
 /**
  * Module initialization function type
+ *
+ * @param modules the module-loading context, containing the nexus
+ * @param err returned error info
+ * @return context pointer, or NULL on errors
  */
-typedef void* (*module_init_func_t) (struct nexus *nexus, struct error_info *err);
+typedef void* (*module_init_func_t) (struct modules *modules, struct error_info *err);
+
+/**
+ * Module configuration function type
+ *
+ * @param ctx the module's context pointer
+ * @param name the name of the configuration setting
+ * @param value the value of the configuration setting
+ * @return error code
+ */
+typedef err_t (*module_conf_func_t) (struct module *module, const char *name, char *value);
+
+/**
+ * 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
@@ -58,12 +109,19 @@
 err_t modules_create (struct modules **modules_ptr, struct nexus *nexus);
 
 /**
- * Load a new module.
+ * Load a new module
  */
-err_t module_load (struct moduels *modules, struct module **module, const struct module_info *info, struct error_info *err);
+err_t module_load (struct modules *modules, struct module **module, const struct module_info *info, struct error_info *err);
+
+/**
+ * Set a module configuration option
+ */
+err_t module_conf (struct module *module, const char *name, char *value);
 
 /**
  * Unload a module
+ *
+ * XXX: not implemented
  */
 err_t module_unload (struct module *module);
 
--- a/src/nexus.c	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/nexus.c	Sun Mar 15 01:17:22 2009 +0200
@@ -44,7 +44,7 @@
 
 void on_sigint (evutil_socket_t sig, short what, void *arg)
 {
-    struct nexus_ctx *ctx = arg;
+    struct nexus *ctx = arg;
 
     (void) sig;
     (void) what;
@@ -73,12 +73,12 @@
 {
     int opt, option_index;
     struct signals *signals;
-    struct nexus_ctx ctx;
+    struct nexus ctx;
     struct irc_net *net;
     struct error_info err;
 
     struct irc_net_info net_info = {
-        .network                    = NULL,
+        .network                    = "default",
         .hostname                   = DEFAULT_HOST,
         .service                    = DEFAULT_PORT,
         .use_ssl                    = false,
@@ -89,15 +89,12 @@
         }
     };
 
+    // XXX: hardcode irc_log config
+    char *log_db_info = NULL;
     struct irc_chan_info log_chan_info = {
         .channel                    = NULL, 
     };
 
-    struct irc_log_info log_info = {
-        .db_info                    = NULL,
-        .channel                    = NULL,
-    };
-
     bool port_default = true;
     
     // parse options
@@ -125,7 +122,7 @@
                 break;
             
             case OPT_LOG_DATABASE:
-                log_info.db_info = optarg;
+                log_db_info = optarg;
                 break;
 
             case OPT_LOG_CHANNEL:
@@ -149,6 +146,10 @@
     // initialize sock module
     if (sock_init(ctx.ev_base, &err))
         FATAL_ERROR(&err, "sock_init");
+
+    // modules 
+    if ((ERROR_CODE(&err) = modules_create(&ctx.modules, &ctx)))
+        FATAL_ERROR(&err, "modules_create");
     
     // the IRC client
     if (irc_client_create(&ctx.client, &err))
@@ -166,14 +167,33 @@
         FATAL_ERROR(&err, "signals_add");
     
     // logging?
-    if (log_info.db_info || log_chan_info.channel) {
+    if (log_db_info || log_chan_info.channel) {
+        struct module *mod_irc_log;
+
+        struct module_info mod_irc_log_info = {
+            .name = "irc_log",
+            .path = "modules/irc_log.so"
+        };
+
+        // load the module
+        if (module_load(ctx.modules, &mod_irc_log, &mod_irc_log_info, &err))
+            FATAL_ERROR(&err, "module_load");
+
         // get the channel
-        if (log_chan_info.channel && (log_info.channel = irc_net_add_chan(net, &log_chan_info)) == NULL)
-            FATAL("irc_net_add_chan");
-        
-        // init the irc_log module
-        if ((ERROR_CODE(&err) = irc_log_init(ctx.ev_base, &log_info)))
-            FATAL_ERROR(&err, "irc_log_init");
+        if (log_chan_info.channel) {
+            // create the channel
+            if ((irc_net_add_chan(net, &log_chan_info)) == NULL)
+                FATAL("irc_net_add_chan");
+
+            // configure it
+            // XXX: hardcoded
+            if ((ERROR_CODE(&err) = module_conf(mod_irc_log, "channel", "default/#test")))
+                FATAL_ERROR(&err, "module_conf(irc_log, '%s', '%s)", "channel", "default/#test");
+        }
+
+        // configure the databse info
+        if (log_db_info && (ERROR_CODE(&err) = module_conf(mod_irc_log, "db_info", log_db_info)))
+            FATAL_ERROR(&err, "module_conf(irc_log, 'db_info', '%s')", log_db_info);
     }
 
     // run event loop
--- a/src/nexus.h	Fri Mar 13 17:38:23 2009 +0200
+++ b/src/nexus.h	Sun Mar 15 01:17:22 2009 +0200
@@ -5,15 +5,19 @@
  * A nexus is the central brain of the application; the place where the main() method is implemented
  */
 #include <event2/event.h>
+#include "module.h"
 #include "irc_client.h"
 
 /**
  * Context for async nexus operation
  */
-struct nexus_ctx {
+struct nexus {
     /** The libevent base */
     struct event_base *ev_base;
 
+    /** Our loaded modules */
+    struct modules *modules;
+
     /** The IRC client state */
     struct irc_client *client;
 };