move irc_log.c to modules/irc_log.c, and restructure sock_* to split the basic fd-level stuff out of sock_tcp and into sock_fd
authorTero Marttila <terom@fixme.fi>
Tue, 07 Apr 2009 18:09:16 +0300
changeset 117 9cb405164250
parent 116 92e71129074d
child 118 05b8d5150313
move irc_log.c to modules/irc_log.c, and restructure sock_* to split the basic fd-level stuff out of sock_tcp and into sock_fd
src/CMakeLists.txt
src/irc_log.c
src/modules/irc_log.c
src/sock.h
src/sock_fd.c
src/sock_fd.h
src/sock_gnutls.c
src/sock_gnutls.h
src/sock_tcp.c
src/sock_tcp.h
--- a/src/CMakeLists.txt	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/CMakeLists.txt	Tue Apr 07 18:09:16 2009 +0300
@@ -10,14 +10,14 @@
 
 # define our source code modules
 set (CORE_SOURCES error.c log.c)
-set (SOCK_SOURCES sock.c sock_tcp.c sock_gnutls.c sock_test.c line_proto.c)
+set (SOCK_SOURCES sock.c sock_fd.c sock_tcp.c sock_gnutls.c sock_test.c line_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)
 set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c)
 set (CONSOLE_SOURCES console.c lua_console.c)
 
 set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
 set (TEST_SOURCES test.c ${CORE_SOURCES} ${SOCK_SOURCES} ${IRC_SOURCES})
-set (IRC_LOG_SOURCES irc_log.c)
+set (IRC_LOG_SOURCES modules/irc_log.c)
 
 # define our libraries
 set (MODULE_LIBRARIES "dl")
--- a/src/irc_log.c	Thu Apr 02 03:19:44 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,529 +0,0 @@
-#include "module.h"
-#include "irc_chan.h"
-#include "config.h"
-#include "error.h"
-#include "log.h"
-
-#include <stdlib.h>
-#include <string.h>
-
-#include <event2/event.h>
-#include <evsql.h>
-
-/**
- * The irc_log module state.
- */
-struct irc_log_ctx {
-    /** The nexus this module is loaded for */
-    struct nexus *nexus;
-
-    /** The database connection */
-    struct evsql *db;
-
-    /**
-     * Our logged channels
-     */
-    TAILQ_HEAD(irc_log_ctx_channels, irc_log_chan) channels;
-    
-    /** Are we currently unloading ourself (via irc_log_unload) ? */
-    bool unloading;
-
-    /** The `struct module` passed to us once we unload() */
-    struct module *module;
-};
-
-/**
- * State required to use irc_cmd_handler with irc_chan.handlers.
- */
-struct irc_log_chan {
-    /** The irc_log context */
-    struct irc_log_ctx *ctx;
-
-    /** The target channel */
-    struct irc_chan *chan;
-
-    /** Are we stopping (irc_log_chan_stop called)? */
-    bool stopping;
-
-    /** We are part of the irc_log_ctx.channels list */
-    TAILQ_ENTRY(irc_log_chan) ctx_channels;
-};
-
-/*
- * Forward-declare
- */
-static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx);
-
-/**
- * The irc_log_ctx has completed stopping all the channels, and should now destroy itself
- */
-static void irc_log_stopped (struct irc_log_ctx *ctx)
-{
-    log_debug("module unload() completed");
-    
-    // notify
-    module_unloaded(ctx->module);
-}
-
-/**
- * The irc_log_chan has completed shutdown
- */
-static void irc_log_chan_stopped (struct irc_log_chan *chan_ctx)
-{
-    struct irc_log_ctx *ctx = chan_ctx->ctx;
-
-    log_debug("destroying the irc_log_chan (chan=%s)", irc_chan_name(chan_ctx->chan));
-
-    // destroy the channel
-    irc_log_chan_destroy(chan_ctx);
-
-    // was it the final channel for unloading?
-    if (ctx->unloading && TAILQ_EMPTY(&ctx->channels))
-        irc_log_stopped(ctx);
-}
-
-/**
- * Our evsql result handler for irc_log_event INSERTs.
- */
-static void irc_log_on_sql_result (struct evsql_result *res, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-    err_t err;
-
-    // check errors
-    if ((err = evsql_result_check(res)))
-        log_error("irc_log_event: %s", evsql_result_error(res));
-
-    // ok, don't need the result anymore
-    evsql_result_free(res);
-
-    // if stopping, then handle that
-    if (chan_ctx->stopping)
-        irc_log_chan_stopped(chan_ctx);
-}
-
-/**
- * Log the event into our database.
- *
- * Any of chan_ctx, source, target, message can be NULL to insert NULLs
- */
-static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_log_chan *chan_ctx, const struct irc_nm *source,
-        const char *type, const char *target, const char *message)
-{
-    struct evsql_query *query;
-    
-    // unpack the nick/user/hostname args, as source can be NULL
-    const char *network = chan_ctx ? chan_ctx->chan->net->info.network : NULL;
-    const char *channel = chan_ctx ? irc_chan_name(chan_ctx->chan) : NULL;
-    const char *nickname = source ? source->nickname : NULL;
-    const char *username = source ? source->username : NULL;
-    const char *hostname = source ? source->hostname : NULL;
-
-    // our SQL query
-    static struct evsql_query_info sql = {
-        "INSERT INTO logs ("
-        "  network, channel, nickname, username, hostname, type, target, message"
-        " ) VALUES ("
-        "  $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, $6::varchar, $7::varchar, $8::varchar"
-        " )", { 
-            EVSQL_TYPE(STRING),     // network
-            EVSQL_TYPE(STRING),     // channel
-            EVSQL_TYPE(STRING),     // nickname
-            EVSQL_TYPE(STRING),     // username
-            EVSQL_TYPE(STRING),     // hostname
-            EVSQL_TYPE(STRING),     // type
-            EVSQL_TYPE(STRING),     // target
-            EVSQL_TYPE(STRING),     // message
-            EVSQL_TYPE_END
-        }
-    };
-    
-    // drop lines if not connected
-    if (ctx->db == NULL) {
-        log_warn("no database connected");
-
-        return ERR_MODULE_CONF;
-    }
-
-    // run the INSERT
-    if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, chan_ctx,
-        network, channel, nickname, username, hostname, type, target, message
-    )) == NULL) {
-        // XXX: get evsql_query error info somehow...
-        log_error("evsql_query_exec failed for %s:%s - %s!%s@%s - %s -> %s - %s", 
-            network, channel, nickname, username, hostname, type, target, message
-        );
-
-        return ERR_EVSQL_QUERY_EXEC;
-    }
-
-    // ok
-    log_debug("%s:%s - %s!%s@%s - %s -> %s - %s", 
-        network, channel, nickname, username, hostname, type, target, message
-    );
-    
-    return SUCCESS;
-}
-
-/**
- * Log a simple channel event of the form:
- *
- * :nm <type> <channel> [<msg>]
- */
-static void irc_log_on_chan_generic (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-    
-    const char *msg = line->args[1];
-
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, msg);
-}
-
-/**
- * Log a NICK event on a channel
- *
- * :nm NICK <nickname>
- *
- * This logs the new nickname as the target
- */
-static void irc_log_on_chan_NICK (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-
-    const char *nickname = line->args[0];
-
-    // log it
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, nickname, NULL);
-}
-
-/**
- * Log a QUIT event on a channel
- *
- * :nm QUIT [<message>]
- */
-static void irc_log_on_chan_QUIT (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-
-    const char *message = line->args[0];
-
-    // log it
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, message);
-}
-
-/**
- * Log a MODE event on a channel
- *
- * :nm MODE <channel> [<modearg> [...]]
- *
- * This conacts all the modeargs together, and logs that as the message
- */
-static void irc_log_on_chan_MODE (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-    char message[512], *ptr = message;
-    const char *cmdarg;
-    
-    // iterate over each arg
-    FOREACH_IRC_LINE_ARGS(line, cmdarg) {
-        // separate with spaces
-        if (cmdarg > line->args[0])
-            *ptr++ = ' ';
-        
-        // append
-        ptr += snprintf(ptr, (message + 512 - ptr), "%s", cmdarg);
-
-        // buffer full?
-        if (ptr > message + 512)
-            break;
-    }
-    
-    // log
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, message);
-}
-
-/**
- * Log a KICK event on a channel
- *
- * :nm KICK <channel> <target> [<comment>]
- */
-static void irc_log_on_chan_KICK (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-    
-    const char *target = line->args[1];    
-    const char *msg = line->args[2];
-
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, target, msg);
-}
-
-/**
- * Log a CTCP ACTION message on a channel
- *
- * :nm "CTCP ACTION" <channel> <action>
- */
-static void irc_log_on_chan_CTCP_ACTION (const struct irc_line *line, void *arg)
-{
-    struct irc_log_chan *chan_ctx = arg;
-
-    const char *action = line->args[1];
-
-    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, "ACTION", NULL, action);
-}
-
-/**
- * Our low-level channel-specific message handlers for logged channels.
- *
- * Note that these get a `struct irc_log_chan*` as an argument.
- */
-static struct irc_cmd_handler _chan_cmd_handlers[] = {
-    {   "NICK",         &irc_log_on_chan_NICK           },
-    {   "QUIT",         &irc_log_on_chan_QUIT           },
-    {   "JOIN",         &irc_log_on_chan_generic        },
-    {   "PART",         &irc_log_on_chan_generic        },
-    {   "MODE",         &irc_log_on_chan_MODE           },
-    {   "TOPIC",        &irc_log_on_chan_generic        },
-    {   "KICK",         &irc_log_on_chan_KICK           },
-    {   "PRIVMSG",      &irc_log_on_chan_generic        },
-    {   "NOTICE",       &irc_log_on_chan_generic        },
-    {   "CTCP ACTION",  &irc_log_on_chan_CTCP_ACTION    },
-    {   NULL,           NULL                            }
-};
-
-/**
- * Our high-level channel-specific callbacks for logged channels.
- *
- * Note that these get a `struct irc_log_chan*` as an argument.
- */
-static struct irc_chan_callbacks _chan_callbacks = {
-    .on_self_join       = NULL,
-    .on_msg             = NULL,
-};
-
-/**
- * Release resources associated with the given irc_log_chan without doing any clean shutdown stuff
- */
-static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx)
-{
-    // remove any handlers/callbacks
-    irc_cmd_remove(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx);
-    irc_chan_remove_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx);
-
-    // remove from channels list
-    TAILQ_REMOVE(&chan_ctx->ctx->channels, chan_ctx, ctx_channels);
-
-    // free ourselves
-    free(chan_ctx);
-}
-
-/**
- * Begin logging the given channel
- */
-static err_t irc_log_chan (struct irc_log_ctx *ctx, struct irc_chan *chan, struct error_info *err)
-{
-    struct irc_log_chan *chan_ctx;
-
-    // alloc
-    if ((chan_ctx = calloc(1, sizeof(*chan_ctx))) == NULL)
-        return SET_ERROR(err, ERR_CALLOC);
-
-    // store
-    chan_ctx->ctx = ctx;
-    chan_ctx->chan = chan;
-
-    // add to channels list
-    TAILQ_INSERT_TAIL(&ctx->channels, chan_ctx, ctx_channels);
-
-    // add low-level handlers
-    if ((ERROR_CODE(err) = irc_cmd_add(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx)))
-        goto error;
-
-    // add channel callbacks
-    if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx)))
-        goto error;
-    
-    // log an OPEN message
-    // XXX: move this to when we first JOIN the channel
-    if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx, NULL, "OPEN", NULL, NULL)))
-        goto error;
-
-    // ok
-    log_info("logging channel %s:%s", chan_ctx->chan->net->info.network, irc_chan_name(chan_ctx->chan));
-
-    return SUCCESS;
-
-error:
-    // cleanup
-    irc_log_chan_destroy(chan_ctx);
-    
-    return ERROR_CODE(err);
-}
-
-/**
- * Stop logging the given channel, shutting it down nicely and then releasing its resources.
- *
- * If shutting it down nicely fails, this just destroys it.
- */
-static err_t irc_log_chan_stop (struct irc_log_chan *chan_ctx)
-{
-    err_t err;
-
-    // mark it as stopping, so the result callback knows to destroy it
-    chan_ctx->stopping = true;
-
-    // log the CLOSE event
-    if ((err = irc_log_event(chan_ctx->ctx, chan_ctx, NULL, "CLOSE", NULL, NULL)))
-        goto error;
-
-    // ok
-    return SUCCESS;
-
-error:
-    // destroy it uncleanly
-    irc_log_chan_destroy(chan_ctx);
-
-    return err;
-}
-
-/**
- * Allocate and initialize an irc_log_ctx. This doesn't do very much, the real magic happens in irc_log_conf.
- */
-static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err)
-{
-    struct irc_log_ctx *ctx;
-    
-    // allocate
-    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
-        return SET_ERROR(err, ERR_CALLOC);
-
-    // store
-    ctx->nexus = nexus;
-
-    // initialize
-    TAILQ_INIT(&ctx->channels);
-
-    log_info("module initialized");
-
-    // ok
-    *ctx_ptr = ctx;
-
-    return SUCCESS;
-}
-
-/**
- * Process the irc_log.db_info config option.
- *
- * Creates a new evsql handle.
- *
- * Fails if ctx->db is already set.
- */
-static err_t irc_log_conf_db_info (void *_ctx, char *value, struct error_info *err)
-{
-    struct irc_log_ctx *ctx = _ctx;
-
-    log_info("connect to database: %s", value);
-
-    // already connected?
-    if (ctx->db)
-        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.db_info already set");
-    
-    // create a new evsql handle
-    if ((ctx->db = evsql_new_pq(ctx->nexus->ev_base, value, NULL, NULL)) == NULL)
-       return SET_ERROR(err, ERR_EVSQL_NEW_PQ);
-    
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Process the irc_log.channel config option.
- *
- * Creates a new irc_log_chan context, looks up the channel, adds our command handlers, and logs an OPEN event.
- *
- * 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, struct irc_chan *chan, struct error_info *err)
-{
-    struct irc_log_ctx *ctx = _ctx;
-    
-    // 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");
-
-    // begin logging it
-    if (irc_log_chan(ctx, chan, err))
-        return ERROR_CODE(err);
-    
-    // ok
-    return SUCCESS;
-}
-
-/**
- * Our configuration options
- */
-struct config_option irc_log_config_options[] = {
-    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"                         ),
-
-    CONFIG_OPT_END
-};
-
-/**
- * Deinitialize, logging CLOSE events for all channels, and removing any hooks we've added
- */
-static err_t irc_log_unload (void *_ctx, struct module *module)
-{
-    struct irc_log_ctx *ctx = _ctx;
-    struct irc_log_chan *chan_ctx;
-
-    // update our state to mark ourselves as unloading
-    ctx->unloading = true;
-    ctx->module = module;
-    
-    if (TAILQ_EMPTY(&ctx->channels)) {
-        // nothing to do, unloaded now
-        module_unloaded(module);
-
-    } else {
-        // stop logging each channel
-        TAILQ_FOREACH(chan_ctx, &ctx->channels, ctx_channels) {
-            irc_log_chan_stop(chan_ctx);
-        }
-
-        // once they have all stopped, we will be unloaded
-    }
-
-    // wait for all the channels to be stopped, which will call irc_log_stopped
-    return SUCCESS;
-}
-
-/**
- * We can safely destroy the evsql instance from here, since we're outside of any callbacks from this module.
- *
- * Note that we might not have had any of the config options called...
- */
-static void irc_log_destroy (void *_ctx)
-{
-    struct irc_log_ctx *ctx = _ctx;
-
-    log_debug("destroying the irc_log_ctx");
-
-    if (ctx->db)
-        // destroy the evsql instance
-        evsql_destroy(ctx->db);
-
-    // ...no more
-    free(ctx);
-}
-
-/**
- * The module function table
- */
-struct module_desc irc_log_module = {
-    .init               = &irc_log_init,
-    .config_options     = irc_log_config_options,
-    .unload             = &irc_log_unload,
-    .destroy            = &irc_log_destroy
-};
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/modules/irc_log.c	Tue Apr 07 18:09:16 2009 +0300
@@ -0,0 +1,529 @@
+#include "../module.h"
+#include "../irc_chan.h"
+#include "../config.h"
+#include "../error.h"
+#include "../log.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <event2/event.h>
+#include <evsql.h>
+
+/**
+ * The irc_log module state.
+ */
+struct irc_log_ctx {
+    /** The nexus this module is loaded for */
+    struct nexus *nexus;
+
+    /** The database connection */
+    struct evsql *db;
+
+    /**
+     * Our logged channels
+     */
+    TAILQ_HEAD(irc_log_ctx_channels, irc_log_chan) channels;
+    
+    /** Are we currently unloading ourself (via irc_log_unload) ? */
+    bool unloading;
+
+    /** The `struct module` passed to us once we unload() */
+    struct module *module;
+};
+
+/**
+ * State required to use irc_cmd_handler with irc_chan.handlers.
+ */
+struct irc_log_chan {
+    /** The irc_log context */
+    struct irc_log_ctx *ctx;
+
+    /** The target channel */
+    struct irc_chan *chan;
+
+    /** Are we stopping (irc_log_chan_stop called)? */
+    bool stopping;
+
+    /** We are part of the irc_log_ctx.channels list */
+    TAILQ_ENTRY(irc_log_chan) ctx_channels;
+};
+
+/*
+ * Forward-declare
+ */
+static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx);
+
+/**
+ * The irc_log_ctx has completed stopping all the channels, and should now destroy itself
+ */
+static void irc_log_stopped (struct irc_log_ctx *ctx)
+{
+    log_debug("module unload() completed");
+    
+    // notify
+    module_unloaded(ctx->module);
+}
+
+/**
+ * The irc_log_chan has completed shutdown
+ */
+static void irc_log_chan_stopped (struct irc_log_chan *chan_ctx)
+{
+    struct irc_log_ctx *ctx = chan_ctx->ctx;
+
+    log_debug("destroying the irc_log_chan (chan=%s)", irc_chan_name(chan_ctx->chan));
+
+    // destroy the channel
+    irc_log_chan_destroy(chan_ctx);
+
+    // was it the final channel for unloading?
+    if (ctx->unloading && TAILQ_EMPTY(&ctx->channels))
+        irc_log_stopped(ctx);
+}
+
+/**
+ * Our evsql result handler for irc_log_event INSERTs.
+ */
+static void irc_log_on_sql_result (struct evsql_result *res, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+    err_t err;
+
+    // check errors
+    if ((err = evsql_result_check(res)))
+        log_error("irc_log_event: %s", evsql_result_error(res));
+
+    // ok, don't need the result anymore
+    evsql_result_free(res);
+
+    // if stopping, then handle that
+    if (chan_ctx->stopping)
+        irc_log_chan_stopped(chan_ctx);
+}
+
+/**
+ * Log the event into our database.
+ *
+ * Any of chan_ctx, source, target, message can be NULL to insert NULLs
+ */
+static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_log_chan *chan_ctx, const struct irc_nm *source,
+        const char *type, const char *target, const char *message)
+{
+    struct evsql_query *query;
+    
+    // unpack the nick/user/hostname args, as source can be NULL
+    const char *network = chan_ctx ? chan_ctx->chan->net->info.network : NULL;
+    const char *channel = chan_ctx ? irc_chan_name(chan_ctx->chan) : NULL;
+    const char *nickname = source ? source->nickname : NULL;
+    const char *username = source ? source->username : NULL;
+    const char *hostname = source ? source->hostname : NULL;
+
+    // our SQL query
+    static struct evsql_query_info sql = {
+        "INSERT INTO logs ("
+        "  network, channel, nickname, username, hostname, type, target, message"
+        " ) VALUES ("
+        "  $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, $6::varchar, $7::varchar, $8::varchar"
+        " )", { 
+            EVSQL_TYPE(STRING),     // network
+            EVSQL_TYPE(STRING),     // channel
+            EVSQL_TYPE(STRING),     // nickname
+            EVSQL_TYPE(STRING),     // username
+            EVSQL_TYPE(STRING),     // hostname
+            EVSQL_TYPE(STRING),     // type
+            EVSQL_TYPE(STRING),     // target
+            EVSQL_TYPE(STRING),     // message
+            EVSQL_TYPE_END
+        }
+    };
+    
+    // drop lines if not connected
+    if (ctx->db == NULL) {
+        log_warn("no database connected");
+
+        return ERR_MODULE_CONF;
+    }
+
+    // run the INSERT
+    if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, chan_ctx,
+        network, channel, nickname, username, hostname, type, target, message
+    )) == NULL) {
+        // XXX: get evsql_query error info somehow...
+        log_error("evsql_query_exec failed for %s:%s - %s!%s@%s - %s -> %s - %s", 
+            network, channel, nickname, username, hostname, type, target, message
+        );
+
+        return ERR_EVSQL_QUERY_EXEC;
+    }
+
+    // ok
+    log_debug("%s:%s - %s!%s@%s - %s -> %s - %s", 
+        network, channel, nickname, username, hostname, type, target, message
+    );
+    
+    return SUCCESS;
+}
+
+/**
+ * Log a simple channel event of the form:
+ *
+ * :nm <type> <channel> [<msg>]
+ */
+static void irc_log_on_chan_generic (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+    
+    const char *msg = line->args[1];
+
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, msg);
+}
+
+/**
+ * Log a NICK event on a channel
+ *
+ * :nm NICK <nickname>
+ *
+ * This logs the new nickname as the target
+ */
+static void irc_log_on_chan_NICK (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+
+    const char *nickname = line->args[0];
+
+    // log it
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, nickname, NULL);
+}
+
+/**
+ * Log a QUIT event on a channel
+ *
+ * :nm QUIT [<message>]
+ */
+static void irc_log_on_chan_QUIT (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+
+    const char *message = line->args[0];
+
+    // log it
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, message);
+}
+
+/**
+ * Log a MODE event on a channel
+ *
+ * :nm MODE <channel> [<modearg> [...]]
+ *
+ * This conacts all the modeargs together, and logs that as the message
+ */
+static void irc_log_on_chan_MODE (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+    char message[512], *ptr = message;
+    const char *cmdarg;
+    
+    // iterate over each arg
+    FOREACH_IRC_LINE_ARGS(line, cmdarg) {
+        // separate with spaces
+        if (cmdarg > line->args[0])
+            *ptr++ = ' ';
+        
+        // append
+        ptr += snprintf(ptr, (message + 512 - ptr), "%s", cmdarg);
+
+        // buffer full?
+        if (ptr > message + 512)
+            break;
+    }
+    
+    // log
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, NULL, message);
+}
+
+/**
+ * Log a KICK event on a channel
+ *
+ * :nm KICK <channel> <target> [<comment>]
+ */
+static void irc_log_on_chan_KICK (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+    
+    const char *target = line->args[1];    
+    const char *msg = line->args[2];
+
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, line->command, target, msg);
+}
+
+/**
+ * Log a CTCP ACTION message on a channel
+ *
+ * :nm "CTCP ACTION" <channel> <action>
+ */
+static void irc_log_on_chan_CTCP_ACTION (const struct irc_line *line, void *arg)
+{
+    struct irc_log_chan *chan_ctx = arg;
+
+    const char *action = line->args[1];
+
+    irc_log_event(chan_ctx->ctx, chan_ctx, line->source, "ACTION", NULL, action);
+}
+
+/**
+ * Our low-level channel-specific message handlers for logged channels.
+ *
+ * Note that these get a `struct irc_log_chan*` as an argument.
+ */
+static struct irc_cmd_handler _chan_cmd_handlers[] = {
+    {   "NICK",         &irc_log_on_chan_NICK           },
+    {   "QUIT",         &irc_log_on_chan_QUIT           },
+    {   "JOIN",         &irc_log_on_chan_generic        },
+    {   "PART",         &irc_log_on_chan_generic        },
+    {   "MODE",         &irc_log_on_chan_MODE           },
+    {   "TOPIC",        &irc_log_on_chan_generic        },
+    {   "KICK",         &irc_log_on_chan_KICK           },
+    {   "PRIVMSG",      &irc_log_on_chan_generic        },
+    {   "NOTICE",       &irc_log_on_chan_generic        },
+    {   "CTCP ACTION",  &irc_log_on_chan_CTCP_ACTION    },
+    {   NULL,           NULL                            }
+};
+
+/**
+ * Our high-level channel-specific callbacks for logged channels.
+ *
+ * Note that these get a `struct irc_log_chan*` as an argument.
+ */
+static struct irc_chan_callbacks _chan_callbacks = {
+    .on_self_join       = NULL,
+    .on_msg             = NULL,
+};
+
+/**
+ * Release resources associated with the given irc_log_chan without doing any clean shutdown stuff
+ */
+static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx)
+{
+    // remove any handlers/callbacks
+    irc_cmd_remove(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx);
+    irc_chan_remove_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx);
+
+    // remove from channels list
+    TAILQ_REMOVE(&chan_ctx->ctx->channels, chan_ctx, ctx_channels);
+
+    // free ourselves
+    free(chan_ctx);
+}
+
+/**
+ * Begin logging the given channel
+ */
+static err_t irc_log_chan (struct irc_log_ctx *ctx, struct irc_chan *chan, struct error_info *err)
+{
+    struct irc_log_chan *chan_ctx;
+
+    // alloc
+    if ((chan_ctx = calloc(1, sizeof(*chan_ctx))) == NULL)
+        return SET_ERROR(err, ERR_CALLOC);
+
+    // store
+    chan_ctx->ctx = ctx;
+    chan_ctx->chan = chan;
+
+    // add to channels list
+    TAILQ_INSERT_TAIL(&ctx->channels, chan_ctx, ctx_channels);
+
+    // add low-level handlers
+    if ((ERROR_CODE(err) = irc_cmd_add(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx)))
+        goto error;
+
+    // add channel callbacks
+    if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx)))
+        goto error;
+    
+    // log an OPEN message
+    // XXX: move this to when we first JOIN the channel
+    if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx, NULL, "OPEN", NULL, NULL)))
+        goto error;
+
+    // ok
+    log_info("logging channel %s:%s", chan_ctx->chan->net->info.network, irc_chan_name(chan_ctx->chan));
+
+    return SUCCESS;
+
+error:
+    // cleanup
+    irc_log_chan_destroy(chan_ctx);
+    
+    return ERROR_CODE(err);
+}
+
+/**
+ * Stop logging the given channel, shutting it down nicely and then releasing its resources.
+ *
+ * If shutting it down nicely fails, this just destroys it.
+ */
+static err_t irc_log_chan_stop (struct irc_log_chan *chan_ctx)
+{
+    err_t err;
+
+    // mark it as stopping, so the result callback knows to destroy it
+    chan_ctx->stopping = true;
+
+    // log the CLOSE event
+    if ((err = irc_log_event(chan_ctx->ctx, chan_ctx, NULL, "CLOSE", NULL, NULL)))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    // destroy it uncleanly
+    irc_log_chan_destroy(chan_ctx);
+
+    return err;
+}
+
+/**
+ * Allocate and initialize an irc_log_ctx. This doesn't do very much, the real magic happens in irc_log_conf.
+ */
+static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err)
+{
+    struct irc_log_ctx *ctx;
+    
+    // allocate
+    if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
+        return SET_ERROR(err, ERR_CALLOC);
+
+    // store
+    ctx->nexus = nexus;
+
+    // initialize
+    TAILQ_INIT(&ctx->channels);
+
+    log_info("module initialized");
+
+    // ok
+    *ctx_ptr = ctx;
+
+    return SUCCESS;
+}
+
+/**
+ * Process the irc_log.db_info config option.
+ *
+ * Creates a new evsql handle.
+ *
+ * Fails if ctx->db is already set.
+ */
+static err_t irc_log_conf_db_info (void *_ctx, char *value, struct error_info *err)
+{
+    struct irc_log_ctx *ctx = _ctx;
+
+    log_info("connect to database: %s", value);
+
+    // already connected?
+    if (ctx->db)
+        RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.db_info already set");
+    
+    // create a new evsql handle
+    if ((ctx->db = evsql_new_pq(ctx->nexus->ev_base, value, NULL, NULL)) == NULL)
+       return SET_ERROR(err, ERR_EVSQL_NEW_PQ);
+    
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Process the irc_log.channel config option.
+ *
+ * Creates a new irc_log_chan context, looks up the channel, adds our command handlers, and logs an OPEN event.
+ *
+ * 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, struct irc_chan *chan, struct error_info *err)
+{
+    struct irc_log_ctx *ctx = _ctx;
+    
+    // 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");
+
+    // begin logging it
+    if (irc_log_chan(ctx, chan, err))
+        return ERROR_CODE(err);
+    
+    // ok
+    return SUCCESS;
+}
+
+/**
+ * Our configuration options
+ */
+struct config_option irc_log_config_options[] = {
+    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"                         ),
+
+    CONFIG_OPT_END
+};
+
+/**
+ * Deinitialize, logging CLOSE events for all channels, and removing any hooks we've added
+ */
+static err_t irc_log_unload (void *_ctx, struct module *module)
+{
+    struct irc_log_ctx *ctx = _ctx;
+    struct irc_log_chan *chan_ctx;
+
+    // update our state to mark ourselves as unloading
+    ctx->unloading = true;
+    ctx->module = module;
+    
+    if (TAILQ_EMPTY(&ctx->channels)) {
+        // nothing to do, unloaded now
+        module_unloaded(module);
+
+    } else {
+        // stop logging each channel
+        TAILQ_FOREACH(chan_ctx, &ctx->channels, ctx_channels) {
+            irc_log_chan_stop(chan_ctx);
+        }
+
+        // once they have all stopped, we will be unloaded
+    }
+
+    // wait for all the channels to be stopped, which will call irc_log_stopped
+    return SUCCESS;
+}
+
+/**
+ * We can safely destroy the evsql instance from here, since we're outside of any callbacks from this module.
+ *
+ * Note that we might not have had any of the config options called...
+ */
+static void irc_log_destroy (void *_ctx)
+{
+    struct irc_log_ctx *ctx = _ctx;
+
+    log_debug("destroying the irc_log_ctx");
+
+    if (ctx->db)
+        // destroy the evsql instance
+        evsql_destroy(ctx->db);
+
+    // ...no more
+    free(ctx);
+}
+
+/**
+ * The module function table
+ */
+struct module_desc irc_log_module = {
+    .init               = &irc_log_init,
+    .config_options     = irc_log_config_options,
+    .unload             = &irc_log_unload,
+    .destroy            = &irc_log_destroy
+};
+
--- a/src/sock.h	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/sock.h	Tue Apr 07 18:09:16 2009 +0300
@@ -5,6 +5,8 @@
  * @file
  *
  * Low-level socket-related functions
+ *
+ * XXX: not just sockets anymore
  */
 #include "error.h"
 #include <sys/types.h>
@@ -37,23 +39,16 @@
  */
 enum sock_error_code {
     _ERR_SOCK_BEGIN = _ERR_SOCK,
-    ERR_SOCKET,
-
-    /** connect() error, either direct or async */
-    ERR_CONNECT,
-    ERR_READ,
-
-    /** EOF on read() */
-    ERR_READ_EOF,
-    ERR_WRITE,
-
-    /** EOF on write()  */
-    ERR_WRITE_EOF,
-    ERR_FCNTL,
-
-    /** Lingering error on close() */
-    ERR_CLOSE,
-    ERR_GETSOCKOPT,
+    ERR_SOCKET,         ///< socket(2) failed
+    ERR_CONNECT,        ///< connect(2) error - either direct or async
+    ERR_READ,           ///< read(2) error - will probably show up as an ERR_WRITE as well
+    ERR_READ_EOF,       ///< EOF on read(2)
+    ERR_WRITE,          ///< write(2) error - data was unsent, will probably show up as an ERR_READ as well
+    ERR_WRITE_EOF,      ///< write(2) gave EOF - zero bytes written
+    ERR_FCNTL,          ///< fcntl(2) failed
+    ERR_CLOSE,          ///< close(2) failed, some written data was probably not sent
+    ERR_GETSOCKOPT,     ///< getsockopt(2) failed
+    ERR_OPEN,           ///< open(2) failed
 };
 
 /**
@@ -110,6 +105,11 @@
 err_t sock_ssl_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err);
 
 /**
+ * A read-only "socket" based on a FIFO, this provides nonblocking read operations by re-opening the FIFO on EOF.
+ */
+err_t fifo_read_open (struct sock_stream **stream_ptr, const char *path, struct error_info *err);
+
+/**
  * Read a series of bytes from the socket into the given \a buf (up to \a len bytes). If succesfull, this returns
  * the number of bytes read (which will be less than or equal to \a len). If the socket is nonblocking (i.e.
  * sock_stream_event_init() was set), and there is no data available, this returns zero, and one should use
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sock_fd.c	Tue Apr 07 18:09:16 2009 +0300
@@ -0,0 +1,185 @@
+#include "sock_fd.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+void sock_fd_event_handler (evutil_socket_t fd, short what, void *arg) 
+{
+    struct sock_fd *sock = arg;
+
+    (void) fd;
+
+    // invoke appropriate callback
+    sock_stream_invoke_callbacks(SOCK_FD_BASE(sock), what);
+}
+
+err_t sock_fd_read (struct sock_stream *base_sock, void *buf, size_t *len)
+{
+    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
+    int ret;
+    
+    // read(), and detect non-EAGAIN or EOF
+    if ((ret = read(sock->fd, buf, *len)) < 0 && errno != EAGAIN)
+        // unexpected error
+        RETURN_SET_ERROR_ERRNO(SOCK_FD_ERR(sock), ERR_READ);
+    
+    else if (ret == 0)
+        // EOF
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_READ_EOF);
+
+
+    if (ret < 0) {
+        // EAGAIN -> zero bytes
+        *len = 0;
+
+    } else {
+        // normal -> bytes read
+        *len = ret;
+    }
+
+    // ok
+    return SUCCESS;
+}
+
+err_t sock_fd_write (struct sock_stream *base_sock, const void *buf, size_t *len)
+{
+    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
+    int ret;
+    
+    // write(), and detect non-EAGAIN or EOF
+    if ((ret = write(sock->fd, buf, *len)) < 0 && errno != EAGAIN)
+        // unexpected error
+        RETURN_SET_ERROR_ERRNO(SOCK_FD_ERR(sock), ERR_WRITE);
+    
+    else if (ret == 0)
+        // EOF
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_WRITE_EOF);
+
+
+    if (ret < 0) {
+        // EAGAIN -> zero bytes
+        *len = 0;
+
+    } else {
+        // normal -> bytes read
+        *len = ret;
+    }
+
+    return SUCCESS;
+}
+
+err_t sock_fd_event_init (struct sock_stream *base_sock)
+{
+    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
+    err_t err;
+
+    // set nonblocking
+    if ((err = sock_fd_set_nonblock(sock, 1)))
+        return err;
+
+    // add ourselves as the event handler
+    if ((err = sock_fd_init_ev(sock, &sock_fd_event_handler, sock)))
+        return err;
+    
+    // done
+    return SUCCESS;
+}
+
+err_t sock_fd_event_enable (struct sock_stream *base_sock, short mask)
+{
+    struct sock_fd *sock = SOCK_FROM_BASE(base_sock, struct sock_fd);
+    
+    // implemented in sock_fd_add_event
+    return sock_fd_enable_events(sock, mask);
+}
+
+void sock_fd_init (struct sock_fd *sock, int fd)
+{
+    assert(!sock->ev_read && !sock->ev_write);
+
+    // initialize
+    sock->fd = fd;
+}
+
+err_t sock_fd_set_nonblock (struct sock_fd *sock, bool nonblock)
+{
+    // fcntl it
+    // XXX: maintain old flags?
+    if (fcntl(sock->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
+        RETURN_SET_ERROR_ERRNO(SOCK_FD_ERR(sock), ERR_FCNTL);
+
+    // ok
+    return SUCCESS;
+}
+
+err_t sock_fd_init_ev (struct sock_fd *sock, void (*ev_cb)(evutil_socket_t, short, void *), void *cb_arg)
+{
+    // require valid fd
+    assert(sock->fd >= 0);
+
+    // this is initialization
+    assert(sock->ev_read == NULL && sock->ev_write == NULL);
+    
+    // create new event
+    if ((sock->ev_read = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_READ, ev_cb, cb_arg)) == NULL)
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_NEW);
+
+    if ((sock->ev_write = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_WRITE, ev_cb, cb_arg)) == NULL)
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_NEW);
+
+    // ok
+    return SUCCESS;
+}
+
+err_t sock_fd_enable_events (struct sock_fd *sock, short mask)
+{
+    // just add the appropraite events
+    if (mask & EV_READ && event_add(sock->ev_read, NULL))
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_ADD);
+ 
+    if (mask & EV_WRITE && event_add(sock->ev_write, NULL))
+        return SET_ERROR(SOCK_FD_ERR(sock), ERR_EVENT_ADD);
+    
+    // done
+    return SUCCESS;
+}
+
+void sock_fd_deinit_ev (struct sock_fd *sock)
+{
+    if (sock->ev_read) {
+        event_free(sock->ev_read);
+
+        sock->ev_read = NULL;
+    }
+
+    if (sock->ev_write) {
+        event_free(sock->ev_write);
+        
+        sock->ev_write = NULL;
+    }
+}
+
+err_t sock_fd_close (struct sock_fd *sock)
+{
+    struct error_info *err = SOCK_FD_ERR(sock);
+    
+    // no errors yet
+    RESET_ERROR(err);
+
+    // must be connected
+    assert(sock->fd >= 0);
+
+    // kill any events
+    sock_fd_deinit_ev(sock);
+
+    // close the socket itself
+    if (close(sock->fd))
+        SET_ERROR_ERRNO(err, ERR_CLOSE);
+
+    // invalidate
+    sock->fd = -1;
+
+    return ERROR_CODE(err);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/sock_fd.h	Tue Apr 07 18:09:16 2009 +0300
@@ -0,0 +1,101 @@
+#ifndef SOCK_FD_H
+#define SOCK_FD_H
+/**
+ * @file
+ *
+ * A generic sock_stream implementation for normal POSIX file descriptor based byte streams.
+ */
+#include "sock_internal.h"
+#include <event2/event.h>
+#include <stdbool.h>
+
+/**
+ * The fd-based sock_stream base implementation
+ */
+struct sock_fd {
+    /** The base struct for sock_stream_* functions */
+    struct sock_stream base;
+    
+    /** The OS file descriptor */
+    int fd;
+
+    /** The IO events */
+    struct event *ev_read, *ev_write;
+
+};
+
+/**
+ * Get a sock_stream pointer from a sock_fd
+ */
+#define SOCK_FD_BASE(sock_ptr) (&(sock_ptr)->base)
+
+/**
+ * Get the sock_stream.err pointer from a sock_fd
+ */
+#define SOCK_FD_ERR(sock_ptr) SOCK_ERR(SOCK_FD_BASE(sock_ptr))
+
+
+
+/**
+ * Callback suitable for use with sock_fd_init_ev, which just invoke's the sock_stream's callbacks as appropriate.
+ */
+void sock_fd_event_handler (evutil_socket_t fd, short what, void *arg);
+
+/**
+ * sock_stream_methods::read implementation.
+ */
+err_t sock_fd_read (struct sock_stream *base_sock, void *buf, size_t *len);
+
+/**
+ * sock_stream_methods::write implementation.
+ */
+err_t sock_fd_write (struct sock_stream *base_sock, const void *buf, size_t *len);
+
+/**
+ * sock_stream_methods::event_init implementation.
+ */
+err_t sock_fd_event_init (struct sock_stream *base_sock);
+
+/**
+ * sock_stream_methods::event_enable implementation.
+ */
+err_t sock_fd_event_enable (struct sock_stream *base_sock, short mask);
+
+
+
+/**
+ * Initialize the sock_fd with the given fd, or -1, if no valid fd yet.
+ */
+void sock_fd_init (struct sock_fd *sock, int fd);
+
+/**
+ * Set the socket's nonblock mode. This should not do anything (apart from an extraneous syscall) if non-blocking
+ * mode is already set.
+ */
+err_t sock_fd_set_nonblock (struct sock_fd *sock, bool nonblock);
+
+/**
+ * Initialize sock_fd.ev_* to use the socket's fd with the given callback. The ev's are not activated yet.
+ *
+ * The sock_fd must *not* have any ev's set.
+ */
+err_t sock_fd_init_ev (struct sock_fd *sock, void (*ev_cb) (evutil_socket_t, short, void *), void *arg);
+
+/**
+ * event_add the specified ev_* events, so they are enabled and the callback will be executed.
+ */
+err_t sock_fd_enable_events (struct sock_fd *sock, short mask);
+
+/**
+ * The opposite of init_ev, this clears any set events, so that they can be re-initialized with init_ev.
+ */
+void sock_fd_deinit_ev (struct sock_fd *sock);
+
+/**
+ * Close an opened sock_fd, restoring it to a state suitable for sock_fd_init
+ */
+err_t sock_fd_close (struct sock_fd *sock);
+
+
+
+#endif
--- a/src/sock_gnutls.c	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/sock_gnutls.c	Tue Apr 07 18:09:16 2009 +0300
@@ -82,11 +82,11 @@
     err_t err;
 
     // set nonblocking
-    if ((err = sock_tcp_set_nonblock(SOCK_GNUTLS_TCP(sock), 1)))
+    if ((err = sock_fd_set_nonblock(SOCK_GNUTLS_FD(sock), true)))
         return err;
 
     // add ourselves as the event handler
-    if ((err = sock_tcp_init_ev(SOCK_GNUTLS_TCP(sock), &sock_gnutls_event_handler, sock)))
+    if ((err = sock_fd_init_ev(SOCK_GNUTLS_FD(sock), &sock_gnutls_event_handler, sock)))
         return err;
 
     // ok
@@ -107,12 +107,12 @@
     switch ((ret = gnutls_record_get_direction(sock->session))) {
         case 0: 
             // read more data
-            sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_READ); 
+            sock_fd_enable_events(SOCK_GNUTLS_FD(sock), EV_READ); 
             break;
         
         case 1:
             // write buffer full
-            sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_WRITE);
+            sock_fd_enable_events(SOCK_GNUTLS_FD(sock), EV_WRITE);
             break;
         
         default:
@@ -216,7 +216,7 @@
         goto error;
 
     // bind default transport functions (recv/send) to use the TCP fd
-    gnutls_transport_set_ptr(sock->session, (gnutls_transport_ptr_t) (long int) sock->base_tcp.fd);
+    gnutls_transport_set_ptr(sock->session, (gnutls_transport_ptr_t) (long int) SOCK_GNUTLS_FD(sock)->fd);
 
     // perform the handshake
     if ((ERROR_EXTRA(err) = gnutls_handshake(sock->session)) < 0)
@@ -237,7 +237,7 @@
 void sock_gnutls_destroy (struct sock_gnutls *sock)
 {
     // terminate the TCP transport
-    sock_tcp_close(SOCK_GNUTLS_TCP(sock));
+    sock_fd_close(SOCK_GNUTLS_FD(sock));
 
     // close the session rudely
     // XXX: does this actually do everything we need it to? Don't want to call gnutls_bye here, since we're void...
@@ -247,4 +247,3 @@
     free(sock);
 }
 
-
--- a/src/sock_gnutls.h	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/sock_gnutls.h	Tue Apr 07 18:09:16 2009 +0300
@@ -57,16 +57,21 @@
 };
 
 /**
- * Cast a sock_gnutls to a sock_stream.
- */
-#define SOCK_GNUTLS_BASE(sock_ptr) (&(sock_ptr)->base_tcp.base)
-
-/**
  * Cast a sock_gnutls to a sock_tcp.
  */
 #define SOCK_GNUTLS_TCP(sock_ptr) (&(sock_ptr)->base_tcp)
 
 /**
+ * Cast a sock_gnutls to a sock_fd.
+ */
+#define SOCK_GNUTLS_FD(sock_ptr) SOCK_TCP_FD(SOCK_GNUTLS_TCP(sock_ptr))
+
+/**
+ * Cast a sock_gnutls to a sock_stream.
+ */
+#define SOCK_GNUTLS_BASE(sock_ptr) SOCK_TCP_BASE(SOCK_GNUTLS_TCP(sock_ptr))
+
+/**
  * Get a pointer to the sock_gnutls's error_info.
  */
 #define SOCK_GNUTLS_ERR(sock_ptr) SOCK_ERR(SOCK_GNUTLS_BASE(sock_ptr))
--- a/src/sock_tcp.c	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/sock_tcp.c	Tue Apr 07 18:09:16 2009 +0300
@@ -5,116 +5,14 @@
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <unistd.h>
-#include <fcntl.h>
 #include <string.h>
-#include <assert.h>
-
-/*
- * Our basic socket event handler for driving our callbacks
- */
-static void sock_tcp_event_handler (evutil_socket_t fd, short what, void *arg) 
-{
-    struct sock_tcp *sock = arg;
-
-    (void) fd;
-
-    // invoke appropriate callback
-    sock_stream_invoke_callbacks(SOCK_TCP_BASE(sock), what);
-}
-
-/*
- * Our sock_stream_methods.read method
- */
-static err_t sock_tcp_read (struct sock_stream *base_sock, void *buf, size_t *len)
-{
-    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
-    int ret;
-    
-    // read(), and detect non-EAGAIN or EOF
-    if ((ret = read(sock->fd, buf, *len)) < 0 && errno != EAGAIN)
-        // unexpected error
-        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_READ);
-    
-    else if (ret == 0)
-        // EOF
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_READ_EOF);
-
-
-    if (ret < 0) {
-        // EAGAIN -> zero bytes
-        *len = 0;
-
-    } else {
-        // normal -> bytes read
-        *len = ret;
-    }
-
-    // ok
-    return SUCCESS;
-}
-
-/*
- * Our sock_stream_methods.write method
- */
-static err_t sock_tcp_write (struct sock_stream *base_sock, const void *buf, size_t *len)
-{
-    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
-    int ret;
-    
-    // write(), and detect non-EAGAIN or EOF
-    if ((ret = write(sock->fd, buf, *len)) < 0 && errno != EAGAIN)
-        // unexpected error
-        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_WRITE);
-    
-    else if (ret == 0)
-        // EOF
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_WRITE_EOF);
-
-
-    if (ret < 0) {
-        // EAGAIN -> zero bytes
-        *len = 0;
-
-    } else {
-        // normal -> bytes read
-        *len = ret;
-    }
-
-    return SUCCESS;
-}
-
-static err_t sock_tcp_event_init (struct sock_stream *base_sock)
-{
-    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
-    err_t err;
-
-    // set nonblocking
-    if ((err = sock_tcp_set_nonblock(sock, 1)))
-        return err;
-
-    // add ourselves as the event handler
-    if ((err = sock_tcp_init_ev(sock, &sock_tcp_event_handler, sock)))
-        return err;
-    
-    // done
-    return SUCCESS;
-}
-
-static err_t sock_tcp_event_enable (struct sock_stream *base_sock, short mask)
-{
-    struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
-    
-    // implemented in sock_tcp_add_event
-    return sock_tcp_add_event(sock, mask);
-}
 
 static void sock_tcp_release (struct sock_stream *base_sock)
 {
     struct sock_tcp *sock = SOCK_FROM_BASE(base_sock, struct sock_tcp);
     
     // close and free
-    sock_tcp_close(sock);
+    sock_fd_close(SOCK_TCP_FD(sock));
     sock_tcp_free(sock);
 }
 
@@ -123,15 +21,15 @@
  */
 static struct sock_stream_type sock_tcp_type = {
     .methods                = {
-        .read               = &sock_tcp_read,
-        .write              = &sock_tcp_write,
-        .event_init         = &sock_tcp_event_init,
-        .event_enable       = &sock_tcp_event_enable,
+        .read               = &sock_fd_read,
+        .write              = &sock_fd_write,
+        .event_init         = &sock_fd_event_init,
+        .event_enable       = &sock_fd_event_enable,
         .release            = &sock_tcp_release,
     },
 };
 
-err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
+static err_t sock_tcp_alloc (struct sock_tcp **sock_ptr)
 {
     // alloc
     if ((*sock_ptr = calloc(1, sizeof(**sock_ptr))) == NULL)
@@ -140,82 +38,24 @@
     // initialize base with sock_tcp_type
     sock_stream_init(SOCK_TCP_BASE(*sock_ptr), &sock_tcp_type);
 
-    // invalid fds are <0
-    (*sock_ptr)->fd = -1;
-
-    // done
-    return SUCCESS;
-}
-
-err_t sock_tcp_init_fd (struct sock_tcp *sock, int fd)
-{
-    // valid fd
-    assert(fd >= 0);
-
-    // initialize
-    sock->fd = fd;
+    // init without any fd
+    sock_fd_init(SOCK_TCP_FD(*sock_ptr), -1);
 
     // done
     return SUCCESS;
 }
 
-err_t sock_tcp_init_ev (struct sock_tcp *sock, void (*ev_cb)(evutil_socket_t, short, void *), void *cb_arg)
-{
-    // require valid fd
-    assert(sock->fd >= 0);
-
-    // this is initialization
-    assert(sock->ev_read == NULL && sock->ev_write == NULL);
-    
-    // create new event
-    if ((sock->ev_read = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_READ, ev_cb, cb_arg)) == NULL)
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_NEW);
-
-    if ((sock->ev_write = event_new(_sock_stream_ctx.ev_base, sock->fd, EV_WRITE, ev_cb, cb_arg)) == NULL)
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_NEW);
-
-    // ok
-    return SUCCESS;
-}
-
-void sock_tcp_deinit_ev (struct sock_tcp *sock)
-{
-    if (sock->ev_read) {
-        event_free(sock->ev_read);
-
-        sock->ev_read = NULL;
-    }
-
-    if (sock->ev_write) {
-        event_free(sock->ev_write);
-        
-        sock->ev_write = NULL;
-    }
-}
-
 err_t sock_tcp_init_socket (struct sock_tcp *sock, struct addrinfo *addr, struct error_info *err)
 {
-    // must not be set already
-    assert(sock->fd < 0);
+    int fd;
 
     // call socket
-    if ((sock->fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0)
+    if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) < 0)
         RETURN_SET_ERROR_ERRNO(err, ERR_SOCKET);
 
     // ok
-    return SUCCESS;
-}
+    sock_fd_init(SOCK_TCP_FD(sock), fd);
 
-err_t sock_tcp_add_event (struct sock_tcp *sock, short mask)
-{
-    // just add the appropraite events
-    if (mask & EV_READ && event_add(sock->ev_read, NULL))
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD);
- 
-    if (mask & EV_WRITE && event_add(sock->ev_write, NULL))
-        return SET_ERROR(SOCK_TCP_ERR(sock), ERR_EVENT_ADD);
-    
-    // done
     return SUCCESS;
 }
 
@@ -257,15 +97,17 @@
  */
 static void sock_tcp_connect_async_done (struct sock_tcp *sock, struct error_info *err)
 {
+    struct sock_stream *sock_base = SOCK_TCP_BASE(sock);
+
     // free the addrinfo
     freeaddrinfo(sock->async_res); 
     sock->async_res = sock->async_cur = NULL;
 
     // remove our event handler so the user can install their own
-    sock_tcp_deinit_ev(sock);
+    sock_fd_deinit_ev(SOCK_TCP_FD(sock));
 
     // ok, run callback
-    SOCK_TCP_BASE(sock)->conn_cb_func(SOCK_TCP_BASE(sock), err, SOCK_TCP_BASE(sock)->conn_cb_arg);
+    sock_base->conn_cb_func(sock_base, err, sock_base->conn_cb_arg);
 }
 
 /**
@@ -303,7 +145,7 @@
 
 error:
     // close the socket
-    if ((tmp = sock_tcp_close(sock)))
+    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
         log_warn("error closing socket after connect error: %s", error_name(tmp));
 
     // log a warning
@@ -324,20 +166,20 @@
         return ERROR_CODE(err);
 
     // then, set it up as nonblocking
-    if ((ERROR_CODE(err) = sock_tcp_set_nonblock(sock, true)))
+    if ((ERROR_CODE(err) = sock_fd_set_nonblock(SOCK_TCP_FD(sock), true)))
         goto error;
 
     // then, initiate the connect
-    if ((ret = connect(sock->fd, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) 
+    if ((ret = connect(SOCK_TCP_FD(sock)->fd, addr->ai_addr, addr->ai_addrlen)) < 0 && errno != EINPROGRESS) 
         JUMP_SET_ERROR_ERRNO(err, ERR_CONNECT);
     
     if (ret < 0) {
         // ok, connect started, setup our completion callback
-        if ((ERROR_CODE(err) = sock_tcp_init_ev(sock, &sock_tcp_connect_cb, sock)))
+        if ((ERROR_CODE(err) = sock_fd_init_ev(SOCK_TCP_FD(sock), &sock_tcp_connect_cb, sock)))
             goto error;
     
         // enable for write
-        if ((ERROR_CODE(err) = sock_tcp_add_event(sock, EV_WRITE)))
+        if ((ERROR_CODE(err) = sock_fd_enable_events(SOCK_TCP_FD(sock), EV_WRITE)))
             goto error;
 
         // set the "current" address in case it fails and we need to try the next one
@@ -354,7 +196,7 @@
 
 error:
     // close the stuff we did open
-    if ((tmp = sock_tcp_close(sock)))
+    if ((tmp = sock_fd_close(SOCK_TCP_FD(sock))))
         log_warn("error closing socket after connect error: %s", error_name(tmp));
 
     return ERROR_CODE(err);
@@ -375,7 +217,20 @@
         RETURN_SET_ERROR_EXTRA(err, ERR_GETADDRINFO, ret);
     
     // start connecting
-    return sock_tcp_connect_async_continue(sock, sock->async_res, err);
+    if (sock_tcp_connect_async_continue(sock, sock->async_res, err))
+        goto error;
+
+    // ok
+    return SUCCESS;
+
+error:
+    // cleanup
+    if (sock->async_res) {
+        freeaddrinfo(sock->async_res);
+        sock->async_res = NULL;
+    }
+
+    return ERROR_CODE(err);
 }
 
 err_t sock_tcp_connect_blocking (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err)
@@ -398,7 +253,7 @@
     // try each result in turn
     for (r = res; r; r = r->ai_next) {
         // create the socket
-        if ((sock->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
+        if ((SOCK_TCP_FD(sock)->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) < 0) {
             // remember error
             SET_ERROR_ERRNO(err, ERR_SOCKET);
 
@@ -407,13 +262,12 @@
         }
         
         // connect to remote address
-        if (connect(sock->fd, r->ai_addr, r->ai_addrlen)) {
+        if (connect(SOCK_TCP_FD(sock)->fd, r->ai_addr, r->ai_addrlen)) {
             // remember error
             SET_ERROR_ERRNO(err, ERR_CONNECT);
             
             // close/invalidate socket
-            close(sock->fd);
-            sock->fd = -1;
+            sock_fd_close(SOCK_TCP_FD(sock));
 
             // skip to next one
             continue;
@@ -424,7 +278,7 @@
     }
     
     // ensure we got some valid socket, else return last error code
-    if (sock->fd < 0) {
+    if (SOCK_TCP_FD(sock)->fd < 0) {
         // did we hit some error?
         if (IS_ERROR(err))
             // return last error
@@ -434,50 +288,13 @@
             // no results
             return SET_ERROR(err, ERR_GETADDRINFO_EMPTY);
     }
-    
+
     // ok, done
     return 0;    
 }
 
-err_t sock_tcp_set_nonblock (struct sock_tcp *sock, bool nonblock)
-{
-    // fcntl it
-    // XXX: maintain old flags?
-    if (fcntl(sock->fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0)
-        RETURN_SET_ERROR_ERRNO(SOCK_TCP_ERR(sock), ERR_FCNTL);
-
-    // ok
-    return SUCCESS;
-}
-
-err_t sock_tcp_close (struct sock_tcp *sock)
-{
-    struct error_info *err = SOCK_TCP_ERR(sock);
-    
-    // no errors yet
-    RESET_ERROR(err);
-
-    // must be connected
-    assert(sock->fd >= 0);
-
-    // kill any events
-    sock_tcp_deinit_ev(sock);
-
-    // close the socket itself
-    if (close(sock->fd))
-        SET_ERROR_ERRNO(err, ERR_CLOSE);
-
-    // invalidate
-    sock->fd = -1;
-
-    return ERROR_CODE(err);
-}
-
 void sock_tcp_free (struct sock_tcp *sock)
 {
-    // must not be connected
-    assert(sock->fd < 0);
-
     // free
     free(sock);
 }
--- a/src/sock_tcp.h	Thu Apr 02 03:19:44 2009 +0300
+++ b/src/sock_tcp.h	Tue Apr 07 18:09:16 2009 +0300
@@ -7,30 +7,29 @@
  * TCP implementation of sock_stream interface.
  */
 #include "sock_internal.h"
+#include "sock_fd.h"
 #include <netdb.h>
-#include <stdbool.h>
 
 /**
  * Contains the base sock_stream struct, and the file descriptor
  */
 struct sock_tcp {
     /** The base struct for sock_stream_* functions */
-    struct sock_stream base;
+    struct sock_fd base_fd;
     
-    /** The OS file descriptor */
-    int fd;
-
-    /** The IO events */
-    struct event *ev_read, *ev_write;
-
     /** The current connect_async resolved address */
     struct addrinfo *async_res, *async_cur;
 };
 
 /**
- * Get a sock_stream pointer from a sock_tcp pointer
+ * Get a sock_fd pointer from a sock_tcp pointer
  */
-#define SOCK_TCP_BASE(sock_ptr) (&(sock_ptr)->base)
+#define SOCK_TCP_FD(sock_ptr) (&(sock_ptr)->base_fd)
+
+/**
+ * Get a sock_base pointer from a sock_tcp pointer
+ */
+#define SOCK_TCP_BASE(sock_ptr) SOCK_FD_BASE(SOCK_TCP_FD(sock_ptr))
 
 /**
  * Get the sock_stream.err pointer from a sock_tcp pointer
@@ -38,26 +37,6 @@
 #define SOCK_TCP_ERR(sock_ptr) SOCK_ERR(SOCK_TCP_BASE(sock_ptr))
 
 /**
- * Allocate a new blank sock_tcp with a correctly initialized base
- */
-err_t sock_tcp_alloc (struct sock_tcp **sock_ptr);
-
-/**
- * Initialize a blank sock_tcp with a given already-existing fd
- */
-err_t sock_tcp_init_fd (struct sock_tcp *sock, int fd);
-
-/**
- * Initialize sock_tcp.ev_* to use the socket's fd with the given callback. The ev's are not activated yet.
- */
-err_t sock_tcp_init_ev (struct sock_tcp *sock, void (*ev_cb) (evutil_socket_t, short, void *), void *arg);
-
-/**
- * The opposite of init_ev, this clears any set events, so that they can be re-initialized with init_ev.
- */
-void sock_tcp_deinit_ev (struct sock_tcp *sock);
-
-/**
  * Initialize a blank sock_tcp by creating a new socket (using the socket() syscall), but doesn't do anything further.
  *
  * This uses the ai_family, ai_socktype and ai_protocol fields from the given addrinfo.
@@ -96,21 +75,6 @@
 err_t sock_tcp_connect_blocking (struct sock_tcp *sock, const char *hostname, const char *service, struct error_info *err);
 
 /**
- * Set the socket's nonblock mode
- */
-err_t sock_tcp_set_nonblock (struct sock_tcp *sock, bool nonblock);
-
-/**
- * event_add the specified ev_* events.
- */
-err_t sock_tcp_add_event (struct sock_tcp *sock, short mask);
-
-/**
- * Close a connected sock_tcp
- */
-err_t sock_tcp_close (struct sock_tcp *sock);
-
-/**
  * Free a non-connected sock_tcp
  */
 void sock_tcp_free (struct sock_tcp *sock);