# HG changeset patch # User Tero Marttila # Date 1237152261 -7200 # Node ID 65bd90f94f4e97dccfeb04bc92506046ace0955f # Parent 12d806823775d757da5f490dc05f24fbbeae68a1# Parent ce1accba5fc7494746e4d1d1767a5ad0c8dfa212 merge modules -> default, this is a bit early as it breaks functionality, but who cares, need to replace the build system now :) diff -r 12d806823775 -r 65bd90f94f4e Makefile --- a/Makefile Fri Mar 13 16:10:48 2009 +0200 +++ b/Makefile Sun Mar 15 23:24:21 2009 +0200 @@ -34,28 +34,31 @@ # 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 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 +IRC_OBJS = obj/irc_line.o obj/irc_conn.o obj/irc_net.o obj/irc_chan.o obj/chain.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} + # computed -CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} ${EVSQL_CFLAGS} -LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} ${EVSQL_LDFLAGS} +CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} ${EVSQL_CFLAGS} -fpic +LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} ${EVSQL_LDFLAGS} -Wl,--export-dynamic # XXX: is this valid? CPPFLAGS = ${CFLAGS} @@ -94,6 +97,9 @@ bin/% : obj/%.o $(CC) $(LDFLAGS) $+ $(LOADLIBES) $(LDLIBS) -o $@ +modules/%.so : obj/%.o + $(CC) -shared -Wl,-soname,$(notdir $@) -o $@ $+ -lc + # documentation DOXYGEN_PATH = /usr/bin/doxygen DOXYGEN_CONF_PATH = doc/doxygen.conf diff -r 12d806823775 -r 65bd90f94f4e src/error.c --- a/src/error.c Fri Mar 13 16:10:48 2009 +0200 +++ b/src/error.c Sun Mar 15 23:24:21 2009 +0200 @@ -4,6 +4,7 @@ // for the error_desc tables #include "sock.h" #include "sock_gnutls.h" +#include "module.h" #include #include @@ -46,6 +47,12 @@ { 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_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 } }; /** @@ -56,6 +63,7 @@ _sock_error_desc, _sock_gnutls_error_desc, _irc_proto_error_desc, + _module_error_desc, NULL }; @@ -125,6 +133,11 @@ snprintf(msg, ERROR_MSG_MAXLEN, "%s: %s", desc->name, gnutls_strerror(err->extra)); break; + case ERR_EXTRA_STR: + // static error message string + snprintf(msg, ERROR_MSG_MAXLEN, "%s: %s", desc->name, err->extra_str); + break; + default: // ??? snprintf(msg, ERROR_MSG_MAXLEN, "%s: %#.8x", desc->name, err->extra); diff -r 12d806823775 -r 65bd90f94f4e src/error.h --- a/src/error.h Fri Mar 13 16:10:48 2009 +0200 +++ b/src/error.h Sun Mar 15 23:24:21 2009 +0200 @@ -29,6 +29,9 @@ /** GnuTLS, using gnutls_strerror() */ ERR_EXTRA_GNUTLS, + + /** Static error message string */ + ERR_EXTRA_STR, }; /** @@ -47,10 +50,10 @@ ERR_GETADDRINFO, ERR_GETADDRINFO_EMPTY, - /** @see enum sock_error_code*/ + /** @see sock_error_code*/ _ERR_SOCK = 0x000300, - /** @see enum sock_gnutls_error_code */ + /** @see sock_gnutls_error_code */ _ERR_GNUTLS = 0x000400, /** Libevent errors */ @@ -77,6 +80,9 @@ /** irc_net errors */ _ERR_IRC_NET = 0x000900, ERR_IRC_NET_QUIT_STATE, + + /** @see module_error_code */ + _ERR_MODULE = 0x000a00, }; /** @@ -99,9 +105,14 @@ struct error_info { /** The base error code */ err_t code; + + union { + /** Additional detail info, usually some third-party error code, as defined by the code's ERR_EXTRA_* */ + int extra; - /** Additional detail info, usually some third-party error code, as defined by the code's ERR_EXTRA_* */ - int extra; + /** Additional info, stored as a pointer to a static string (note how dangerous this is) */ + const char *extra_str; + }; }; /** @@ -150,6 +161,13 @@ #define _SET_ERROR_ERRNO(err_info_ptr, err_code) _SET_ERROR_EXTRA(err_info_ptr, err_code, errno); #define SET_ERROR_ERRNO(err_info_ptr, err_code) SET_ERROR_EXTRA(err_info_ptr, err_code, errno); +/** + * Set error_info.code to err_code, and .extra_str to str. The given string pointer should remain valid while the error + * is being handled down-stack. + */ +#define _SET_ERROR_STR(err_info_ptr, err_code, err_str) (err_info_ptr)->code = (err_code); (err_info_ptr)->extra_str = (err_str) +#define SET_ERROR_STR(err_info_ptr, err_code, err_str) do { _SET_ERROR_STR(err_info_ptr, err_code, err_str); } while(0) + /** Set error_info from another error_info. Evaluates to the new error_info */ #define SET_ERROR_INFO(err_info_ptr, from_ptr) (*err_info_ptr = *from_ptr) @@ -158,9 +176,11 @@ #define RETURN_SET_ERROR_EXTRA(err_info_ptr, err_code, err_extra) do { _SET_ERROR_EXTRA(err_info_ptr, err_code, err_extra); return (err_code); } while (0) #define RETURN_SET_ERROR_ERRNO(err_info_ptr, err_code) do { _SET_ERROR_ERRNO(err_info_ptr, err_code); return (err_code); } while (0) #define RETURN_SET_ERROR_INFO(err_info_ptr, from_ptr) do { SET_ERROR_INFO(err_info_ptr, from_ptr); return (from_ptr->code); } while (0) +#define RETURN_SET_ERROR_STR(err_info_ptr, err_code, err_str) do { _SET_ERROR_STR(err_info_ptr, err_code, err_str); return (err_code); } while (0) /** Same as above, but also do a 'goto error' */ #define JUMP_SET_ERROR(err_info_ptr, err_code) do { SET_ERROR(err_info_ptr, err_code); goto error; } while (0) #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) #endif diff -r 12d806823775 -r 65bd90f94f4e src/irc_client.c --- a/src/irc_client.c Fri Mar 13 16:10:48 2009 +0200 +++ b/src/irc_client.c Sun Mar 15 23:24:21 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; diff -r 12d806823775 -r 65bd90f94f4e src/irc_client.h --- a/src/irc_client.h Fri Mar 13 16:10:48 2009 +0200 +++ b/src/irc_client.h Sun Mar 15 23:24:21 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 diff -r 12d806823775 -r 65bd90f94f4e src/irc_log.c --- a/src/irc_log.c Fri Mar 13 16:10:48 2009 +0200 +++ b/src/irc_log.c Sun Mar 15 23:24:21 2009 +0200 @@ -1,18 +1,27 @@ -#include "irc_log.h" +#include "module.h" +#include "irc_chan.h" +#include "error.h" #include "log.h" +#include +#include + +#include // XXX: fix this err_t crap #define LIB_ERR_H #include /** - * The core irc_log state + * The irc_log module state */ -static struct irc_log_ctx { +struct irc_log_ctx { + /** The nexus this module is loaded for */ + struct nexus *nexus; + /** The database connection */ struct evsql *db; -} _ctx; +}; static void on_chan_msg (struct irc_chan *chan, const struct irc_nm *source, const char *message, void *arg) { @@ -28,33 +37,67 @@ .on_msg = on_chan_msg, }; -err_t irc_log_init (struct event_base *ev_base, const struct irc_log_info *info) +static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err) { - struct irc_log_ctx *ctx = &_ctx; - err_t err; - - // open the database connection - if (info->db_info) { - log_info("connect to database: %s", info->db_info); + struct irc_log_ctx *ctx; + + // allocate + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return SET_ERROR(err, ERR_CALLOC); - if ((ctx->db = evsql_new_pq(ev_base, info->db_info, NULL, NULL)) == NULL) + // initialize + memset(ctx, 0, sizeof(*ctx)); + + // store + ctx->nexus = nexus; + + // ok + *ctx_ptr = ctx; + + return SET_ERROR(err, SUCCESS); +} + +static err_t irc_log_conf (void *mod_ctx, const char *name, char *value, struct error_info *err) +{ + struct irc_log_ctx *ctx = mod_ctx; + + if (strcmp(name, "db_info") == 0) { + log_info("connect to database: %s", value); + + if ((ctx->db = evsql_new_pq(ctx->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) + RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid '/' value"); + + // 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"); + + // add channel callbacks + if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan, &_chan_callbacks, ctx))) + return ERROR_CODE(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; } +/** + * The module function table + */ +struct module_funcs irc_log_funcs = { + .init = &irc_log_init, + .conf = &irc_log_conf, +}; diff -r 12d806823775 -r 65bd90f94f4e src/irc_log.h --- a/src/irc_log.h Fri Mar 13 16:10:48 2009 +0200 +++ b/src/irc_log.h Sun Mar 15 23:24:21 2009 +0200 @@ -1,31 +1,1 @@ -#ifndef IRC_LOG_H -#define IRC_LOG_H - -/** - * @file - * - * Logging IRC events to an SQL database - */ -#include "error.h" -#include "irc_chan.h" -#include -/** - * Configuration state for irc_log - */ -struct irc_log_info { - /** Database connection string */ - const char *db_info; - - /** The channel to log */ - struct irc_chan *channel; -}; - -/** - * Initialize the global irc_log module to use the given configuration - * - * XXX: db_info is still unused if not specified - */ -err_t irc_log_init (struct event_base *ev_base, const struct irc_log_info *info); - -#endif diff -r 12d806823775 -r 65bd90f94f4e src/module.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/module.c Sun Mar 15 23:24:21 2009 +0200 @@ -0,0 +1,100 @@ +#include "module.h" +#include "log.h" + +#include +#include +#include +#include + +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; +} + +/** + * Load the symbol named "_". + */ +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; +} + +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); + + // alloc + if ((module = calloc(1, sizeof(*module))) == NULL) + return SET_ERROR(err, ERR_CALLOC); + + // store + module->info = *info; + + // clear dlerrors + (void) dlerror(); + + // load it + if ((module->handle = dlopen(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->funcs, "funcs"))) + JUMP_SET_ERROR_STR(err, ERROR_CODE(err), dlerror()); + + // call the init func + if ((module->funcs->init(modules->nexus, &module->ctx, 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, struct error_info *err) +{ + // call the conf func + return module->funcs->conf(module->ctx, name, value, err); +} diff -r 12d806823775 -r 65bd90f94f4e src/module.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/module.h Sun Mar 15 23:24:21 2009 +0200 @@ -0,0 +1,141 @@ +#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. Module initialization happens + * using a module_init_func_t named "_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" + +#include + +/** + * 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 module's behaviour is defined as a set of function pointers, which is dynamically resolved from the module DSO, + * using the _funcs symbol. + */ +struct module_funcs { + /** + * 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. + * + * @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); + + /** + * 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. + * + * XXX: make value a non-modifyable string + * + * @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); +}; + +/** + * A loaded module. + */ +struct module { + /** The identifying info for the module */ + struct module_info info; + + /** The dlopen handle */ + void *handle; + + /** The resolved function table */ + struct module_funcs *funcs; + + /** The module context object */ + void *ctx; + + /** 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; + + /** 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_OPEN, ///< dlopen() failed + ERR_MODULE_NAME, ///< invalid module_info.name + ERR_MODULE_SYM, ///< invalid symbol + ERR_MODULE_INIT_FUNC, ///< invalid module_init_func_t + ERR_MODULE_CONF, ///< value error in configuration data +}; + + +/** + * 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); + +/** + * Load a new module + */ +err_t module_load (struct modules *modules, struct module **module_ptr, 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, struct error_info *err); + +/** + * Unload a module + * + * XXX: not implemented + */ +err_t module_unload (struct module *module); + +#endif diff -r 12d806823775 -r 65bd90f94f4e src/nexus.c --- a/src/nexus.c Fri Mar 13 16:10:48 2009 +0200 +++ b/src/nexus.c Sun Mar 15 23:24:21 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,35 @@ 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) { + char conf_channel[] = "default/#test"; + + // create the channel + if ((irc_net_add_chan(net, &log_chan_info)) == NULL) + FATAL("irc_net_add_chan"); + + // configure it + // XXX: hardcoded + if (module_conf(mod_irc_log, "channel", conf_channel, &err)) + FATAL_ERROR(&err, "module_conf(irc_log, '%s', '%s)", "channel", conf_channel); + } + + // configure the databse info + if (log_db_info && module_conf(mod_irc_log, "db_info", log_db_info, &err)) + FATAL_ERROR(&err, "module_conf(irc_log, 'db_info', '%s')", log_db_info); } // run event loop diff -r 12d806823775 -r 65bd90f94f4e src/nexus.h --- a/src/nexus.h Fri Mar 13 16:10:48 2009 +0200 +++ b/src/nexus.h Sun Mar 15 23:24:21 2009 +0200 @@ -4,16 +4,23 @@ /** * A nexus is the central brain of the application; the place where the main() method is implemented */ + +struct nexus; + #include +#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; };