src/nexus.c
author Tero Marttila <terom@fixme.fi>
Thu, 02 Apr 2009 02:55:32 +0300
changeset 111 5a1ebffca81a
parent 110 43e9a7984955
child 120 576bab0a1c5a
permissions -rw-r--r--
fix refcount semantics for module_load and have module_unload call module_unloaded on module_desc::unload errors
#include "nexus.h"
#include "lua_config.h"
#include "log.h"

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>

/**
 * Command-line option codes
 */
enum option_code {
    OPT_HELP            = 'h',
    OPT_DEFAULTS        = 'D',
    OPT_NETWORK         = 'n',
    OPT_MODULE          = 'm',
    OPT_CONF            = 'C',
    OPT_DEBUG           = 'd',
    OPT_CONFIG          = 'c',
    
    /** Options without short names */
    _OPT_EXT_BEGIN      = 0x00ff,
    
    OPT_CONSOLE,
    OPT_CHANNEL,
};

/**
 * Command-line option definitions
 */
static struct option options[] = {
    {"help",            0,  NULL,   OPT_HELP        },
    {"defaults",        1,  NULL,   OPT_DEFAULTS    },
    {"network",         1,  NULL,   OPT_NETWORK     },
    {"channel",         1,  NULL,   OPT_CHANNEL     },
    {"module",          1,  NULL,   OPT_MODULE      },
    {"conf",            1,  NULL,   OPT_CONF        },
    {"debug",           0,  NULL,   OPT_DEBUG       },
    {"console",         0,  NULL,   OPT_CONSOLE     },
    {"config",          1,  NULL,   OPT_CONFIG      },
    {0,                 0,  0,      0               },
};

/**
 * Short command-line option defintion
 */
const char *short_options = "hn:c:m:C:d";

/**
 * Display --help output on stdout.
 *
 * If nexus is given, --config options for loaded modules are also listed
 */
static void usage (struct nexus *nexus, const char *exe)
{
    printf("Usage: %s [OPTIONS]\n", exe);
    printf("\n");
    printf(" --help / -h            display this message\n");
    printf(" --defaults / -D        set the IRC client default info using '<nickname>:<username>:<realname>'\n");
    printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl]]' format\n");
    printf(" --channel              add an IRC channel using '<network>:<channel>' format\n");
    printf(" --module / -m          add a module using '<name>:<path>' format\n");
    printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
    printf(" --debug / -d           set logging level to DEBUG\n");
    printf(" --config / -c          load a Lua config file from '<path>'\n");
    printf(" --console              use the interactive console\n");
    
    // dump loaded module configs
    if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
        struct module *module;

        printf("\n");
        printf("Module configuration options\n");

        TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
            printf("\n");
            printf("%s:\n", module->info.name);

            // XXX: shouldn't be accessing directly
            if (module->desc->config_options) {
                const struct config_option *opt;

                for (opt = module->desc->config_options; opt->name && opt->type; opt++) {
                    printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, opt->description, opt->help);
                }

            } else {
                printf("\t???\n");
            }
        }
    }
}

/**
 * Parse and apply a --defaults option, setting the resulting defaults to our irc_client
 */
static err_t apply_defaults (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct irc_client_defaults defaults = {
        .register_info      = { NULL, NULL, NULL },
        .service            = IRC_PORT,
        .service_ssl        = IRC_SSL_PORT,
    };

    // parse the required fields
    if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <nickname> field for --defaults");
    
    if ((defaults.register_info.username = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <username> field for --defaults");
    
    if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <realname> field for --defaults");

    // trailing garbage?
    if (opt)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
    
    // apply them
    log_info("using default nick/user/real-name: %s/%s/%s", 
            defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname);

    irc_client_set_defaults(nexus->client, &defaults);

    // ok
    return SET_ERROR(err, SUCCESS);
}

/**
 * Parse and apply a --network option, adding the given network to our irc_client.
 */
static err_t apply_network (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct irc_net_info info;

    // init to zero
    memset(&info, 0, sizeof(info));

    // parse the required fields
    if ((info.network = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --network");
    
    if ((info.hostname = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <hostname> field for --network");
    
    // parse the optional fields
    if (opt)
        info.service = strsep(&opt, ":");
    
    // parse any remaining flags
    while (opt) {
        char *flag = strsep(&opt, ":");

        if (strcmp(flag, "ssl"))
            info.use_ssl = true;

        else
            RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unrecognized flag for --network");
    }

    // create the net
    log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);

    if (irc_client_add_net(nexus->client, NULL, &info, err))
        return ERROR_CODE(err);

    // ok
    return SET_ERROR(err, SUCCESS);
}

/**
 * Parse and apply a --channel option, adding the given channel to the given irc_net.
 */
static err_t apply_channel (struct nexus *nexus, char *opt, struct error_info *err)
{
    const char *network = NULL;
    struct irc_net *net;
    struct irc_chan_info info = {
        .channel            = NULL,
    };

    // parse the required fields
    if ((network = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <network> field for --channel");
    
    if ((info.channel = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <channel> field for --channel");

    // trailing garbage?
    if (opt)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");

    // look up the net
    if ((net = irc_client_get_net(nexus->client, network)) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown network for --channel");
    
    // add the channel
    log_info("add channel '%s' on network '%s'", info.channel, net->info.network);

    if (irc_net_add_chan(net, NULL, &info, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Parse and apply a --module option, loading the given module.
 */
static err_t apply_module (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct module_info info = {
        .name       = NULL,
        .path       = NULL,
    };

    // parse the required fields
    if ((info.name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --module");

    if ((info.path = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <path> field for --module");

    // trailing garbage?
    if (opt)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "trailing values for --channel");
    
    // load it
    log_info("loading module '%s' from path '%s'", info.name, info.path);

    if (module_load(nexus->modules, NULL, &info, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Parse and apply a --conf option, calling the module's conf func.
 */
static err_t apply_conf (struct nexus *nexus, char *opt, struct error_info *err)
{
    struct module *module;
    const char *module_name, *conf_name;
    char *conf_value;

    // parse the required fields
    if ((module_name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <module> field for --config");

    if ((conf_name = strsep(&opt, ":")) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "missing <name> field for --config");

    // value is the rest of the data, might be NULL
    conf_value = opt;

    // lookup the module
    if ((module = module_get(nexus->modules, module_name)) == NULL)
        RETURN_SET_ERROR_STR(err, ERR_CMD_OPT, "unknown module for --config");
    
    // do the config
    log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);

    if (module_conf_raw(module, conf_name, conf_value, err))
        return ERROR_CODE(err);

    // ok
    return SUCCESS;
}

/**
 * Open the console
 */
static err_t apply_console (struct nexus *nexus, struct error_info *err)
{
    struct console_config config = {
        .prompt     = "> ",
    };
    struct console *console;

    log_info("initializing the console");
    
    // init the console
    if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err))
        return ERROR_CODE(err);

    // create the lua console on top of that
    if (lua_console_create(&nexus->lua_console, console, nexus->lua, err))
        goto error;

    // ok
    return SUCCESS;

error:
    console_destroy(console);

    return ERROR_CODE(err);
}

err_t nexus_load_config (struct nexus *nexus, const char *path, struct error_info *err)
{
    return lua_config_load(nexus->lua, path, err);
}

/**
 * Load the lua config file from \a path
 */
static err_t apply_config (struct nexus *nexus, char *path, struct error_info *err)
{
    log_info("loading lua config from %s", path);
    
    return nexus_load_config(nexus, path, err);
}

/**
 * Parse arguments and apply them to the given nexus
 */
static err_t parse_args (struct nexus *nexus, int argc, char **argv, struct error_info *err)
{
    int opt, option_index;
    
    // parse options
    while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) {
        switch (opt) {
            case OPT_HELP:
                usage(nexus, argv[0]);

                // XXX: return instead
                exit(EXIT_SUCCESS);
            
            case OPT_NETWORK:
                if (apply_network(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;
            
            case OPT_DEFAULTS:
                if (apply_defaults(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;

            case OPT_CHANNEL:
                if (apply_channel(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;
            
            case OPT_MODULE:
                if (apply_module(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;

            case OPT_CONF:
                if (apply_conf(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;
            
            case OPT_DEBUG:
                set_log_level(LOG_DEBUG);
                break;
            
            case OPT_CONSOLE:
                if (apply_console(nexus, err))
                    return ERROR_CODE(err);
                
                break;
            
            case OPT_CONFIG:
                if (apply_config(nexus, optarg, err))
                    return ERROR_CODE(err);

                break;

            case '?':
                usage(nexus, argv[0]);
                return SET_ERROR(err, ERR_CMD_OPT);
        }
    }
    
    // ok
    return SUCCESS;
}

void nexus_shutdown (struct nexus *nexus)
{
    // destroy the console
    if (nexus->lua_console)
        lua_console_destroy(nexus->lua_console);
    
    nexus->lua_console = NULL;

    // unload the modules
    if (nexus->modules)
       modules_unload(nexus->modules);

    // quit the irc client
    if (nexus->client)
       irc_client_quit(nexus->client, "Goodbye, cruel world ;(");

    // remove the signal handlers
    if (nexus->signals)
        signals_free(nexus->signals);

    nexus->signals = NULL;

    // now event_base_dispatch should return once everythings' shut down...
    nexus->shutdown = true;
}

void nexus_crash (struct nexus *nexus)
{
    // force-quit the event loop
    event_base_loopbreak(nexus->ev_base);
}

void nexus_destroy (struct nexus *nexus)
{
    // destroy the console
    if (nexus->lua_console)
        lua_console_destroy(nexus->lua_console);

    // destroy the lua state
    if (nexus->lua)
        nexus_lua_destroy(nexus->lua);

    // destroy the modules
    if (nexus->modules)
        modules_destroy(nexus->modules);

    // destroy the irc client
    if (nexus->client)
        irc_client_destroy(nexus->client);

    // remove the signal handlers
    if (nexus->signals)
        signals_free(nexus->signals);
    
    // finally, the libevent base
    if (nexus->ev_base)
        event_base_free(nexus->ev_base);

    // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :)
    memset(nexus, 0, sizeof(*nexus));
}

static void on_sigint (evutil_socket_t sig, short what, void *arg)
{
    struct nexus *nexus = arg;

    (void) sig;
    (void) what;
    
    log_info("Quitting...");
    
    // shutdown
    nexus_shutdown(nexus);
}

int main (int argc, char **argv) 
{
    struct nexus _nexus, *nexus = &_nexus;
    struct error_info err;

    // zero nexus
    memset(nexus, 0, sizeof(*nexus));

    // initialize libevent
    if ((nexus->ev_base = event_base_new()) == NULL)
        FATAL("event_base_new");
    

    // initialize signal handlers
    if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
        FATAL("signals_create");
 
    // add our signal handlers
    if (signal_ignore(SIGPIPE, &err))
        FATAL_ERROR(&err, "signals_ignore(SIGPIPE)");
    
    // XXX: add our SIGINT handler after console_init()?
    if ((ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, &on_sigint, nexus)))
        FATAL_ERROR(&err, "signals_add(SIGINT)");
 

    // initialize sock module
    if (sock_init(nexus->ev_base, &err))
        FATAL_ERROR(&err, "sock_init");

    // modules 
    if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus)))
        FATAL_ERROR(&err, "modules_create");
    
    // the IRC client
    if (irc_client_create(&nexus->client, &err))
        FATAL_ERROR(&err, "irc_client_create");
    
    // lua state
    if (nexus_lua_create(&nexus->lua, nexus, &err))
        FATAL_ERROR(&err, "nexus_lua_create");

    
    // parse args
    if (parse_args(nexus, argc, argv, &err))
        FATAL_ERROR(&err, "parse_args");

  
    // run event loop
    log_info("entering event loop");

    if (event_base_dispatch(nexus->ev_base)) {
        if (nexus->shutdown) {
            log_info("clean shutdown completed");

        } else {
            FATAL("event_base_dispatch returned without shutdown");

        }
    }

    // cleanup
    nexus_destroy(nexus);
    nexus = NULL;

    // ok
    return EXIT_SUCCESS;
}