diff -r a10ba529ae39 -r 7728d6ec3abf src/spbot/nexus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spbot/nexus.c Wed May 27 23:57:48 2009 +0300 @@ -0,0 +1,596 @@ +#include "nexus.h" +#include "lua_config.h" +#include "sock.h" +#include + +#include +#include +#include +#include +#include +#include + +/** + * 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 '::'\n"); + printf(" --network / -n add an IRC network using ':[:[:ssl][:ssl_cafile=][:ssl_verify][:ssl_cert=][:ssl_pkey=]]' format\n"); + printf(" --channel add an IRC channel using ':' format\n"); + printf(" --module / -m add a module using ':' format\n"); + printf(" --config / -C add a module configuration option using ':[:]' format\n"); + printf(" --debug / -d set logging level to DEBUG\n"); + printf(" --config / -c load a Lua config file from ''\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++) { + printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", 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, error_t *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, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); + + if ((defaults.register_info.username = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); + + if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --defaults"); + + // trailing garbage? + if (opt) + return SET_ERROR_STR(err, &general_errors, 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 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, error_t *err) +{ + struct irc_net_info info; + const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL; + bool use_ssl = false, ssl_verify = false; + + // init to zero + memset(&info, 0, sizeof(info)); + + // parse the required fields + if ((info.network = strsep(&opt, ":")) == NULL) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --network"); + + if ((info.hostname = strsep(&opt, ":")) == NULL) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --network"); + + // parse the optional fields + if (opt) + info.service = strsep(&opt, ":"); + + // parse any remaining flags + while (opt) { + char *flag_token = strsep(&opt, ":"); + char *flag = strsep(&flag_token, "="); + char *value = flag_token; + + if (strcmp(flag, "ssl") == 0) { + use_ssl = true; + + } else if (strcmp(flag, "ssl_cafile") == 0) { + if (!value) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cafile"); + + ssl_cafile = value; + + } else if (strcmp(flag, "ssl_verify") == 0) { + ssl_verify = true; + + } else if (strcmp(flag, "ssl_cert") == 0) { + if (!value) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cert"); + + ssl_cert = value; + + } else if (strcmp(flag, "ssl_pkey") == 0) { + if (!value) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_pkey"); + + ssl_pkey = value; + + } else + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unrecognized flag for --network"); + } + + // SSL? + if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) { + // verify + if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey)) + JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey"); + + // create + if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err)) + goto error; + } + + // 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)) + goto error; + + // ok + return SUCCESS; + +error: + // cleanup + if (info.ssl_cred) + ssl_client_cred_put(info.ssl_cred); + + return ERROR_CODE(err); +} + +/** + * 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, error_t *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, &general_errors, ERR_CMD_OPT, "missing field for --channel"); + + if ((info.channel = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --channel"); + + // trailing garbage? + if (opt) + return SET_ERROR_STR(err, &general_errors, 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, &general_errors, 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, error_t *err) +{ + struct module_info info = { + .name = NULL, + .path = NULL, + }; + + // parse the required fields + if ((info.name = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --module"); + + if ((info.path = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --module"); + + // trailing garbage? + if (opt) + return SET_ERROR_STR(err, &general_errors, 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, error_t *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, &general_errors, ERR_CMD_OPT, "missing field for --config"); + + if ((conf_name = strsep(&opt, ":")) == NULL) + return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing field for --config"); + + // value is the rest of the data, might be NULL + conf_value = opt; + + // lookup the module + if ((module = modules_get(nexus->modules, module_name)) == NULL) + return SET_ERROR_STR(err, &general_errors, 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, error_t *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); + + // set it as the log output handler + console_set_log_output(console); + + // 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, error_t *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, error_t *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, error_t *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); + + // XXX: come up with something nicer for parsing these command-line options + case OPT_NETWORK: + if (apply_network(nexus, optarg, err)) + goto error; + + break; + + case OPT_DEFAULTS: + if (apply_defaults(nexus, optarg, err)) + goto error; + + break; + + case OPT_CHANNEL: + if (apply_channel(nexus, optarg, err)) + goto error; + + break; + + case OPT_MODULE: + if (apply_module(nexus, optarg, err)) + goto error; + + break; + + case OPT_CONF: + if (apply_conf(nexus, optarg, err)) + goto error; + + break; + + case OPT_DEBUG: + set_log_level(LOG_DEBUG); + break; + + case OPT_CONSOLE: + if (apply_console(nexus, err)) + goto error; + + break; + + case OPT_CONFIG: + if (apply_config(nexus, optarg, err)) + goto error; + + break; + + case '?': + usage(nexus, argv[0]); + return SET_ERROR(err, &general_errors, ERR_CMD_OPT); + } + } + + // ok + return SUCCESS; + +error: + return ERROR_CODE(err); +} + +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_sig_shutdown (evutil_socket_t sig, short what, void *arg) +{ + struct nexus *nexus = arg; + + (void) sig; + (void) what; + + if (!nexus->shutdown) { + // shutdown + log_info("Terminating..."); + + nexus_shutdown(nexus); + + } else { + // already tried to shutdown + log_info("Crashing!"); + + nexus_crash(nexus); + } +} + +int main (int argc, char **argv) +{ + struct nexus _nexus, *nexus = &_nexus; + error_t 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"); + + // add our SIGTERM handler before console_init()? + if ( + (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus)) + || (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus)) + ) + FATAL_ERROR(&err, "signals_add"); + + // 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"); + + } + } else { + log_warn("zero return from event_base_dispatch"); + } + + // cleanup + nexus_destroy(nexus); + nexus = NULL; + + // ok + return EXIT_SUCCESS; +} +