add irc_net_quit and signal handling
authorTero Marttila <terom@fixme.fi>
Thu, 12 Mar 2009 23:54:03 +0200
changeset 48 4841f4398fd2
parent 47 7d4094eb3117
child 49 96e0f703a58c
add irc_net_quit and signal handling
Makefile
src/error.h
src/irc_conn.c
src/irc_conn.h
src/irc_net.c
src/irc_net.h
src/nexus.c
src/signals.c
src/signals.h
--- a/Makefile	Thu Mar 12 23:15:57 2009 +0200
+++ b/Makefile	Thu Mar 12 23:54:03 2009 +0200
@@ -34,7 +34,7 @@
 # modules
 module_objs = $(patsubst src/%.c,obj/%.o,$(wildcard src/$(1)/*.c))
 
-CORE_OBJS = obj/error.o obj/log.o obj/chain.o
+CORE_OBJS = obj/error.o obj/log.o obj/chain.o obj/signals.o
 SOCK_OBJS = obj/sock.o obj/sock_tcp.o
 SOCK_TEST_OBJS = obj/sock_test.o
 SOCK_GNUTLS_OBJS = obj/sock_gnutls.o
--- a/src/error.h	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/error.h	Thu Mar 12 23:54:03 2009 +0200
@@ -72,6 +72,11 @@
     /** irc_conn errors */
     _ERR_IRC_CONN   = 0x000800,
     ERR_IRC_CONN_REGISTER_STATE,
+    ERR_IRC_CONN_QUIT_STATE,
+
+    /** irc_net errors */
+    _ERR_IRC_NET    = 0x000900,
+    ERR_IRC_NET_QUIT_STATE,
 };
 
 /**
--- a/src/irc_conn.c	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/irc_conn.c	Thu Mar 12 23:54:03 2009 +0200
@@ -136,11 +136,19 @@
 {
     struct irc_conn *conn = arg;
 
-    // log
-    log_err_info(err, "line_proto error");
-    
-    // trash ourselves
-    irc_conn_set_error(conn, err);
+    // EOF after quit?
+    if (ERROR_CODE(err) == ERR_READ_EOF && conn->quitting) {
+        // callback
+        if (conn->callbacks.on_quit)
+            conn->callbacks.on_quit(conn, conn->cb_arg);
+
+    } else {
+        // log
+        log_err_info(err, "line_proto error");
+        
+        // trash ourselves
+        irc_conn_set_error(conn, err);
+    }
 }
 
 static struct line_proto_callbacks _lp_callbacks = {
@@ -210,9 +218,7 @@
 {
     err_t err;
 
-    // assert state
     if (conn->registering || conn->registered)
-        // XXX: stupid error code
         return ERR_IRC_CONN_REGISTER_STATE;
 
     // send the initial messages
@@ -291,3 +297,26 @@
     return irc_conn_send(conn, &line);
 }
 
+err_t irc_conn_QUIT (struct irc_conn *conn, const char *message)
+{
+    err_t err;
+
+    struct irc_line line = {
+        NULL, "QUIT", { message, NULL }
+    };
+    
+    // state check
+    if (conn->quitting)
+        return ERR_IRC_CONN_QUIT_STATE;
+    
+    // try and send
+    if ((err = irc_conn_send(conn, &line)))
+        return err;
+
+    // mark as quitting
+    conn->quitting = true;
+    
+    // ok
+    return SUCCESS;
+}
+
--- a/src/irc_conn.h	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/irc_conn.h	Thu Mar 12 23:54:03 2009 +0200
@@ -44,8 +44,17 @@
     /**
      * The connection has failed in some way, and can not be considered useable anymore. Sending messages won't work,
      * and no more messages will be received. The connection should be destroyed, probably from this callback.
+     *
+     * NOTE: Implementing this callback is mandatory
      */
     void (*on_error) (struct irc_conn *conn, struct error_info *err, void *arg);
+
+    /**
+     * The connection was closed cleanly after irc_conn_QUIT.
+     *
+     * You probably want to destroy the irc_conn now to clean up
+     */
+    void (*on_quit) (struct irc_conn *conn, void *arg);
 };
 
 /*
@@ -67,6 +76,9 @@
 
     /** Registered (as in, we have a working nickname)? */
     bool registered;
+
+    /** Quit message sent, waiting for server to close connection */
+    bool quitting;
     
     // @}
     
@@ -144,7 +156,7 @@
  */
 err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname);
 
-/*
+/**
  * Send a USER message
  *
  * @param username the username to register with, may be replaced with ident reply
@@ -152,14 +164,14 @@
  */
 err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname);
 
-/*
+/**
  * Send a PONG message to the given target
  *
  * @param target the PING source, aka. the target to send the PONG reply to
  */
 err_t irc_conn_PONG (struct irc_conn *conn, const char *target);
 
-/*
+/**
  * Send a JOIN message for the given channel
  *
  * XXX: this doesn't implement the full command options
@@ -168,6 +180,13 @@
  */
 err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel);
 
+/**
+ * Send a QUIT message to the server.
+ *
+ * This updates our state as disconnecting, and waits for the server to close the connection cleanly.
+ */
+err_t irc_conn_QUIT (struct irc_conn *conn, const char *message);
+
 // @}
 
 #endif /* IRC_CONN_H */
--- a/src/irc_net.c	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/irc_net.c	Thu Mar 12 23:54:03 2009 +0200
@@ -58,11 +58,26 @@
 }
 
 /**
+ * Our irc_conn has quit succesfully
+ */
+static void irc_net_conn_quit (struct irc_conn *conn, void *arg)
+{
+    struct irc_net *net = arg;
+
+    // clean up the conn
+    irc_conn_destroy(conn);
+    net->conn = NULL;
+
+    // XXX: notify user
+}
+
+/**
  * Our irc_conn_callbacks list
  */
 struct irc_conn_callbacks _conn_callbacks = {
     .on_registered  = &irc_net_conn_registered,
     .on_error       = &irc_net_conn_error,
+    .on_quit        = &irc_net_conn_quit,
 };
 
 /**
@@ -244,3 +259,11 @@
     return NULL;
 }
 
+err_t irc_net_quit (struct irc_net *net, const char *message)
+{
+   if (!net->conn)
+       return ERR_IRC_NET_QUIT_STATE;
+    
+    // send the QUIT message, and then we can wait for the reply
+    return irc_conn_QUIT(net->conn, message);
+}
--- a/src/irc_net.h	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/irc_net.h	Thu Mar 12 23:54:03 2009 +0200
@@ -78,4 +78,9 @@
  */
 struct irc_chan* irc_net_get_chan (struct irc_net *net, const char *channel);
 
+/**
+ * Quit from the IRC network, this sends a QUIT message to the server, and waits for the connection to be closed cleanly.
+ */
+err_t irc_net_quit (struct irc_net *net, const char *message);
+
 #endif
--- a/src/nexus.c	Thu Mar 12 23:15:57 2009 +0200
+++ b/src/nexus.c	Thu Mar 12 23:54:03 2009 +0200
@@ -1,12 +1,14 @@
 
-#include "log.h"
 #include "irc_net.h"
 #include "irc_log.h"
+#include "signals.h"
+#include "log.h"
 
 #include <stdlib.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <getopt.h>
+#include <signal.h>
 
 #include <event2/event.h>
 
@@ -44,11 +46,49 @@
     printf(" --log-channel          channel to log\n");
 }
 
+/**
+ * Context for async nexus operation
+ */
+struct nexus_ctx {
+    /** The libevent base */
+    struct event_base *ev_base;
+
+    /** The one IRC network */
+    struct irc_net *net;
+};
+
+void on_sigint (evutil_socket_t sig, short what, void *arg)
+{
+    struct nexus_ctx *ctx = arg;
+
+    (void) sig;
+    (void) what;
+    
+    if (ctx->net && ctx->net->conn && !ctx->net->conn->quitting) {
+        log_info("Quitting...");
+
+        // quit it
+        irc_net_quit(ctx->net, "Goodbye, cruel world ;(");
+
+    } else {
+        log_error("Aborting");
+        
+        // die
+        if (ctx->net) {
+            irc_net_destroy(ctx->net);
+            ctx->net = NULL;
+        }
+
+        // exit
+        event_base_loopexit(ctx->ev_base, NULL);
+    }
+}
+
 int main (int argc, char **argv) 
 {
     int opt, option_index;
-    struct event_base *ev_base;
-    struct irc_net *net;
+    struct nexus_ctx ctx;
+    struct signals *signals;
     struct error_info err;
 
     struct irc_net_info net_info = {
@@ -113,30 +153,41 @@
     }
 
     // initialize libevent
-    if ((ev_base = event_base_new()) == NULL)
+    if ((ctx.ev_base = event_base_new()) == NULL)
         FATAL("event_base_new");
+    
+    // initialize signal handlers
+    if ((ERROR_CODE(&err) = signals_create(&signals, ctx.ev_base)))
+        FATAL("signals_create");
 
     // initialize sock module
-    if (sock_init(ev_base, &err))
+    if (sock_init(ctx.ev_base, &err))
         FATAL_ERROR(&err, "sock_init");
     
     // the IRC network
-    if (irc_net_create(&net, &net_info, &err))
+    if (irc_net_create(&ctx.net, &net_info, &err))
         FATAL_ERROR(&err, "irc_net_create");
+
+    // add our signal handlers
+    if (
+            (ERROR_CODE(&err) = signals_add(signals, SIGPIPE, &signals_ignore, signals))
+        ||  (ERROR_CODE(&err) = signals_add(signals, SIGINT, &on_sigint, &ctx))
+    )
+        FATAL_ERROR(&err, "signals_add");
     
     // logging?
     if (log_info.db_info || log_chan_info.channel) {
         // get the channel
-        if (log_chan_info.channel && (log_info.channel = irc_net_add_chan(net, &log_chan_info)) == NULL)
+        if (log_chan_info.channel && (log_info.channel = irc_net_add_chan(ctx.net, &log_chan_info)) == NULL)
             FATAL("irc_net_add_chan");
         
         // init the irc_log module
-        if ((ERROR_CODE(&err) = irc_log_init(ev_base, &log_info)))
+        if ((ERROR_CODE(&err) = irc_log_init(ctx.ev_base, &log_info)))
             FATAL_ERROR(&err, "irc_log_init");
     }
 
     // run event loop
-    if (event_base_dispatch(ev_base))
+    if (event_base_dispatch(ctx.ev_base))
         FATAL("event_base_dispatch");
     
     // ok, no cleanup
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/signals.c	Thu Mar 12 23:54:03 2009 +0200
@@ -0,0 +1,122 @@
+#define _GNU_SOURCE
+
+#include "signals.h"
+#include "log.h"
+
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <assert.h>
+
+struct signals {
+    /** The libevent base to use */
+    struct event_base *ev_base;
+    
+    /** Information about up to MAX_SIGNALS signals */
+    struct signal {
+        /** The event we use to handle the signal */
+        struct event *ev;
+
+    } sig_list[MAX_SIGNALS];
+    
+    /** Number of used sig_list slots */
+    int sig_count;
+};
+
+void signals_loopexit (int signal, short event, void *arg) 
+{
+    struct signals *signals = arg;
+
+    (void) event;
+
+    log_info("caught %s: exiting", strsignal(signal));
+    
+    if (event_base_loopexit(signals->ev_base, NULL))
+        FATAL("event_base_loopexit");
+}
+
+void signals_ignore (int signal, short event, void *arg)
+{
+    struct signals *signals = arg;
+    
+    (void) signal;
+    (void) event;
+    (void) arg;
+    (void) signals;
+    
+    /* ignore */
+}
+
+err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base)
+{
+    struct signals *signals;
+
+    if ((signals = calloc(1, sizeof(*signals))) == NULL)
+        return ERR_CALLOC;
+    
+    // simple attributes
+    signals->ev_base = ev_base;
+
+    // done
+    *signals_ptr = signals;
+
+    return SUCCESS;
+}
+
+err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg) 
+{
+    struct signal *sig_info;
+    
+    // find our sig_info
+    assert(signals->sig_count < MAX_SIGNALS);
+    sig_info = signals->sig_list + (signals->sig_count++);
+    
+    // set up the libevent signal events
+    if ((sig_info->ev = evsignal_new(signals->ev_base, sigval, sig_handler, arg)) == NULL)
+        return ERR_EVENT_NEW;
+
+    if (evsignal_add(sig_info->ev, NULL))
+        return ERR_EVENT_ADD;
+
+    // ok
+    return SUCCESS;
+}
+
+struct signals *signals_default (struct event_base *ev_base) 
+{
+    struct signals *signals = NULL;
+    
+    // alloc signals
+    if (signals_create(&signals, ev_base))
+        return NULL;
+    
+    // add the set of default signals
+    if (    signals_add(signals,    SIGPIPE,    &signals_ignore,        signals)
+        ||  signals_add(signals,    SIGINT,     &signals_loopexit,      signals)
+    ) 
+        goto error;
+    
+    // ok
+    return signals;
+
+error:
+    if (signals)
+        signals_free(signals);
+
+    return NULL;
+}   
+
+void signals_free (struct signals *signals) 
+{
+    int i;
+    
+    // free all events
+    for (i = 0; i < signals->sig_count; i++) {
+        if (evsignal_del(signals->sig_list[i].ev))
+            log_warn("evsignal_del failed");
+    }
+    
+    // free the info itself
+    free(signals);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/signals.h	Thu Mar 12 23:54:03 2009 +0200
@@ -0,0 +1,56 @@
+#ifndef SIGNALS_H
+#define SIGNALS_H
+
+/**
+ * @file 
+ *
+ * Handle signals in a libevent way
+ */
+#include "error.h"
+#include <event2/event.h>
+
+/**
+ * How many signals we can define actions for
+ */
+#define MAX_SIGNALS 8
+
+/**
+ * State for a set of signals
+ */
+struct signals;
+
+/**
+ * Used as a handler for signals that should cause a loopexit.
+ */
+void signals_loopexit (int signal, short event, void *arg);
+
+/**
+ * Used to receive signals, but discard them, and continue what we were doing.
+ */
+void signals_ignore (int signal, short event, void *arg);
+
+/**
+ * Allocate a signals struct, acting on the given ev_base.
+ *
+ * Returns NULL on failure
+ */
+err_t signals_create (struct signals **signals_ptr, struct event_base *ev_base);
+
+/*
+ * Add a signal to be handled by the given signals struct with the given handler.
+ */
+err_t signals_add (struct signals *signals, int sigval, void (*sig_handler)(evutil_socket_t, short, void *), void *arg);
+
+/*
+ * Add a set of default signals
+ *      SIGPIPE     signals_ignore
+ *      SIGINT      signals_loopexit
+ */
+struct signals *signals_default (struct event_base *ev_base);
+
+/*
+ * Free the resources/handlers associated with the given signal handler
+ */
+void signals_free (struct signals *signals);
+
+#endif /* LIB_SIGNAL_H */