src/spbot/nexus.c
branchnew-lib-errors
changeset 217 7728d6ec3abf
parent 200 c414343101df
child 218 5229a5d098b2
equal deleted inserted replaced
216:a10ba529ae39 217:7728d6ec3abf
       
     1 #include "nexus.h"
       
     2 #include "lua_config.h"
       
     3 #include "sock.h"
       
     4 #include <lib/log.h>
       
     5 
       
     6 #include <stdlib.h>
       
     7 #include <stdbool.h>
       
     8 #include <stdio.h>
       
     9 #include <getopt.h>
       
    10 #include <signal.h>
       
    11 #include <string.h>
       
    12 
       
    13 /**
       
    14  * Command-line option codes
       
    15  */
       
    16 enum option_code {
       
    17     OPT_HELP            = 'h',
       
    18     OPT_DEFAULTS        = 'D',
       
    19     OPT_NETWORK         = 'n',
       
    20     OPT_MODULE          = 'm',
       
    21     OPT_CONF            = 'C',
       
    22     OPT_DEBUG           = 'd',
       
    23     OPT_CONFIG          = 'c',
       
    24     
       
    25     /** Options without short names */
       
    26     _OPT_EXT_BEGIN      = 0x00ff,
       
    27     
       
    28     OPT_CONSOLE,
       
    29     OPT_CHANNEL,
       
    30 };
       
    31 
       
    32 /**
       
    33  * Command-line option definitions
       
    34  */
       
    35 static struct option options[] = {
       
    36     {"help",            0,  NULL,   OPT_HELP        },
       
    37     {"defaults",        1,  NULL,   OPT_DEFAULTS    },
       
    38     {"network",         1,  NULL,   OPT_NETWORK     },
       
    39     {"channel",         1,  NULL,   OPT_CHANNEL     },
       
    40     {"module",          1,  NULL,   OPT_MODULE      },
       
    41     {"conf",            1,  NULL,   OPT_CONF        },
       
    42     {"debug",           0,  NULL,   OPT_DEBUG       },
       
    43     {"console",         0,  NULL,   OPT_CONSOLE     },
       
    44     {"config",          1,  NULL,   OPT_CONFIG      },
       
    45     {0,                 0,  0,      0               },
       
    46 };
       
    47 
       
    48 /**
       
    49  * Short command-line option defintion
       
    50  */
       
    51 const char *short_options = "hn:c:m:C:d";
       
    52 
       
    53 /**
       
    54  * Display --help output on stdout.
       
    55  *
       
    56  * If nexus is given, --config options for loaded modules are also listed
       
    57  */
       
    58 static void usage (struct nexus *nexus, const char *exe)
       
    59 {
       
    60     printf("Usage: %s [OPTIONS]\n", exe);
       
    61     printf("\n");
       
    62     printf(" --help / -h            display this message\n");
       
    63     printf(" --defaults / -D        set the IRC client default info using '<nickname>:<username>:<realname>'\n");
       
    64     printf(" --network / -n         add an IRC network using '<name>:<hostname>[:<port>[:ssl][:ssl_cafile=<path>][:ssl_verify][:ssl_cert=<path>][:ssl_pkey=<path>]]' format\n");
       
    65     printf(" --channel              add an IRC channel using '<network>:<channel>' format\n");
       
    66     printf(" --module / -m          add a module using '<name>:<path>' format\n");
       
    67     printf(" --config / -C          add a module configuration option using '<mod_name>:<name>[:<value>]' format\n");
       
    68     printf(" --debug / -d           set logging level to DEBUG\n");
       
    69     printf(" --config / -c          load a Lua config file from '<path>'\n");
       
    70     printf(" --console              use the interactive console\n");
       
    71     
       
    72     // dump loaded module configs
       
    73     if (nexus && !TAILQ_EMPTY(&nexus->modules->list)) {
       
    74         struct module *module;
       
    75 
       
    76         printf("\n");
       
    77         printf("Module configuration options\n");
       
    78 
       
    79         TAILQ_FOREACH(module, &nexus->modules->list, modules_list) {
       
    80             printf("\n");
       
    81             printf("%s:\n", module->info.name);
       
    82 
       
    83             // XXX: shouldn't be accessing directly
       
    84             if (module->desc->config_options) {
       
    85                 const struct config_option *opt;
       
    86 
       
    87                 for (opt = module->desc->config_options; opt->name; opt++) {
       
    88                     printf(" --config %s:%s:%s\t\t%s\n", module->info.name, opt->name, "xxx", opt->help);
       
    89                 }
       
    90 
       
    91             } else {
       
    92                 printf("\t???\n");
       
    93             }
       
    94         }
       
    95     }
       
    96 }
       
    97 
       
    98 /**
       
    99  * Parse and apply a --defaults option, setting the resulting defaults to our irc_client
       
   100  */
       
   101 static err_t apply_defaults (struct nexus *nexus, char *opt, error_t *err)
       
   102 {
       
   103     struct irc_client_defaults defaults = {
       
   104         .register_info      = { NULL, NULL, NULL },
       
   105         .service            = IRC_PORT,
       
   106         .service_ssl        = IRC_SSL_PORT,
       
   107     };
       
   108 
       
   109     // parse the required fields
       
   110     if ((defaults.register_info.nickname = strsep(&opt, ":")) == NULL)
       
   111         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <nickname> field for --defaults");
       
   112     
       
   113     if ((defaults.register_info.username = strsep(&opt, ":")) == NULL)
       
   114         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <username> field for --defaults");
       
   115     
       
   116     if ((defaults.register_info.realname = strsep(&opt, ":")) == NULL)
       
   117         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <realname> field for --defaults");
       
   118 
       
   119     // trailing garbage?
       
   120     if (opt)
       
   121         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
       
   122     
       
   123     // apply them
       
   124     log_info("using default nick/user/real-name: %s/%s/%s", 
       
   125             defaults.register_info.nickname, defaults.register_info.username, defaults.register_info.realname);
       
   126 
       
   127     irc_client_set_defaults(nexus->client, &defaults);
       
   128 
       
   129     // ok
       
   130     return SUCCESS;
       
   131 }
       
   132 
       
   133 /**
       
   134  * Parse and apply a --network option, adding the given network to our irc_client.
       
   135  */
       
   136 static err_t apply_network (struct nexus *nexus, char *opt, error_t *err)
       
   137 {
       
   138     struct irc_net_info info;
       
   139     const char *ssl_cafile = NULL, *ssl_cert = NULL, *ssl_pkey = NULL;
       
   140     bool use_ssl = false, ssl_verify = false;
       
   141 
       
   142     // init to zero
       
   143     memset(&info, 0, sizeof(info));
       
   144 
       
   145     // parse the required fields
       
   146     if ((info.network = strsep(&opt, ":")) == NULL)
       
   147         JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --network");
       
   148     
       
   149     if ((info.hostname = strsep(&opt, ":")) == NULL)
       
   150         JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <hostname> field for --network");
       
   151     
       
   152     // parse the optional fields
       
   153     if (opt)
       
   154         info.service = strsep(&opt, ":");
       
   155     
       
   156     // parse any remaining flags
       
   157     while (opt) {
       
   158         char *flag_token = strsep(&opt, ":");
       
   159         char *flag = strsep(&flag_token, "=");
       
   160         char *value = flag_token;
       
   161 
       
   162         if (strcmp(flag, "ssl") == 0) {
       
   163             use_ssl = true;
       
   164 
       
   165         } else if (strcmp(flag, "ssl_cafile") == 0) {
       
   166             if (!value)
       
   167                 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cafile");
       
   168 
       
   169             ssl_cafile = value;
       
   170         
       
   171         } else if (strcmp(flag, "ssl_verify") == 0) {
       
   172             ssl_verify = true;
       
   173 
       
   174         } else if (strcmp(flag, "ssl_cert") == 0) {
       
   175             if (!value)
       
   176                 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_cert");
       
   177             
       
   178             ssl_cert = value;
       
   179 
       
   180         } else if (strcmp(flag, "ssl_pkey") == 0) {
       
   181             if (!value)
       
   182                 JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing value for :ssl_pkey");
       
   183 
       
   184             ssl_pkey = value;
       
   185 
       
   186         } else
       
   187             JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unrecognized flag for --network");
       
   188     }
       
   189 
       
   190     // SSL?
       
   191     if (use_ssl || ssl_cafile || ssl_verify || ssl_cert || ssl_pkey) {
       
   192         // verify
       
   193         if ((ssl_cert || ssl_pkey) && !(ssl_cert && ssl_pkey))
       
   194             JUMP_SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "must give both :ssl_cert/:ssl_pkey");
       
   195         
       
   196         // create
       
   197         if (ssl_client_cred_create(&info.ssl_cred, ssl_cafile, ssl_verify, ssl_cert, ssl_pkey, err))
       
   198             goto error;
       
   199     }
       
   200 
       
   201     // create the net
       
   202     log_info("add network '%s' at '%s:%s'", info.network, info.hostname, info.service);
       
   203 
       
   204     if (irc_client_add_net(nexus->client, NULL, &info, err))
       
   205         goto error;
       
   206 
       
   207     // ok
       
   208     return SUCCESS;
       
   209 
       
   210 error:
       
   211     // cleanup
       
   212     if (info.ssl_cred)
       
   213         ssl_client_cred_put(info.ssl_cred);
       
   214 
       
   215     return ERROR_CODE(err);
       
   216 }
       
   217 
       
   218 /**
       
   219  * Parse and apply a --channel option, adding the given channel to the given irc_net.
       
   220  */
       
   221 static err_t apply_channel (struct nexus *nexus, char *opt, error_t *err)
       
   222 {
       
   223     const char *network = NULL;
       
   224     struct irc_net *net;
       
   225     struct irc_chan_info info = {
       
   226         .channel            = NULL,
       
   227     };
       
   228 
       
   229     // parse the required fields
       
   230     if ((network = strsep(&opt, ":")) == NULL)
       
   231         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <network> field for --channel");
       
   232     
       
   233     if ((info.channel = strsep(&opt, ":")) == NULL)
       
   234         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <channel> field for --channel");
       
   235 
       
   236     // trailing garbage?
       
   237     if (opt)
       
   238         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
       
   239 
       
   240     // look up the net
       
   241     if ((net = irc_client_get_net(nexus->client, network)) == NULL)
       
   242         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown network for --channel");
       
   243     
       
   244     // add the channel
       
   245     log_info("add channel '%s' on network '%s'", info.channel, net->info.network);
       
   246 
       
   247     if (irc_net_add_chan(net, NULL, &info, err))
       
   248         return ERROR_CODE(err);
       
   249 
       
   250     // ok
       
   251     return SUCCESS;
       
   252 }
       
   253 
       
   254 /**
       
   255  * Parse and apply a --module option, loading the given module.
       
   256  */
       
   257 static err_t apply_module (struct nexus *nexus, char *opt, error_t *err)
       
   258 {
       
   259     struct module_info info = {
       
   260         .name       = NULL,
       
   261         .path       = NULL,
       
   262     };
       
   263 
       
   264     // parse the required fields
       
   265     if ((info.name = strsep(&opt, ":")) == NULL)
       
   266         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --module");
       
   267 
       
   268     if ((info.path = strsep(&opt, ":")) == NULL)
       
   269         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <path> field for --module");
       
   270 
       
   271     // trailing garbage?
       
   272     if (opt)
       
   273         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "trailing values for --channel");
       
   274     
       
   275     // load it
       
   276     log_info("loading module '%s' from path '%s'", info.name, info.path);
       
   277 
       
   278     if (module_load(nexus->modules, NULL, &info, err))
       
   279         return ERROR_CODE(err);
       
   280 
       
   281     // ok
       
   282     return SUCCESS;
       
   283 }
       
   284 
       
   285 /**
       
   286  * Parse and apply a --conf option, calling the module's conf func.
       
   287  */
       
   288 static err_t apply_conf (struct nexus *nexus, char *opt, error_t *err)
       
   289 {
       
   290     struct module *module;
       
   291     const char *module_name, *conf_name;
       
   292     char *conf_value;
       
   293 
       
   294     // parse the required fields
       
   295     if ((module_name = strsep(&opt, ":")) == NULL)
       
   296         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <module> field for --config");
       
   297 
       
   298     if ((conf_name = strsep(&opt, ":")) == NULL)
       
   299         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "missing <name> field for --config");
       
   300 
       
   301     // value is the rest of the data, might be NULL
       
   302     conf_value = opt;
       
   303 
       
   304     // lookup the module
       
   305     if ((module = modules_get(nexus->modules, module_name)) == NULL)
       
   306         return SET_ERROR_STR(err, &general_errors, ERR_CMD_OPT, "unknown module for --config");
       
   307     
       
   308     // do the config
       
   309     log_info("applying module '%s' config name '%s' with value: %s", module->info.name, conf_name, conf_value);
       
   310 
       
   311     if (module_conf_raw(module, conf_name, conf_value, err))
       
   312         return ERROR_CODE(err);
       
   313 
       
   314     // ok
       
   315     return SUCCESS;
       
   316 }
       
   317 
       
   318 /**
       
   319  * Open the console
       
   320  */
       
   321 static err_t apply_console (struct nexus *nexus, error_t *err)
       
   322 {
       
   323     struct console_config config = {
       
   324         .prompt     = "> ",
       
   325     };
       
   326     struct console *console;
       
   327 
       
   328     log_info("initializing the console");
       
   329     
       
   330     // init the console
       
   331     if (console_init(&console, nexus->ev_base, &config, NULL, NULL, err))
       
   332         return ERROR_CODE(err);
       
   333     
       
   334     // set it as the log output handler
       
   335     console_set_log_output(console);
       
   336 
       
   337     // create the lua console on top of that
       
   338     if (lua_console_create(&nexus->lua_console, console, nexus->lua, err))
       
   339         goto error;
       
   340 
       
   341     // ok
       
   342     return SUCCESS;
       
   343 
       
   344 error:
       
   345     console_destroy(console);
       
   346 
       
   347     return ERROR_CODE(err);
       
   348 }
       
   349 
       
   350 err_t nexus_load_config (struct nexus *nexus, const char *path, error_t *err)
       
   351 {
       
   352     return lua_config_load(nexus->lua, path, err);
       
   353 }
       
   354 
       
   355 /**
       
   356  * Load the lua config file from \a path
       
   357  */
       
   358 static err_t apply_config (struct nexus *nexus, char *path, error_t *err)
       
   359 {
       
   360     log_info("loading lua config from %s", path);
       
   361     
       
   362     return nexus_load_config(nexus, path, err);
       
   363 }
       
   364 
       
   365 /**
       
   366  * Parse arguments and apply them to the given nexus
       
   367  */
       
   368 static err_t parse_args (struct nexus *nexus, int argc, char **argv, error_t *err)
       
   369 {
       
   370     int opt, option_index;
       
   371     
       
   372     // parse options
       
   373     while ((opt = getopt_long(argc, argv, short_options, options, &option_index)) != -1) {
       
   374         switch (opt) {
       
   375             case OPT_HELP:
       
   376                 usage(nexus, argv[0]);
       
   377 
       
   378                 // XXX: return instead
       
   379                 exit(EXIT_SUCCESS);
       
   380             
       
   381             // XXX: come up with something nicer for parsing these command-line options            
       
   382             case OPT_NETWORK:
       
   383                 if (apply_network(nexus, optarg, err))
       
   384                     goto error;
       
   385 
       
   386                 break;
       
   387             
       
   388             case OPT_DEFAULTS:
       
   389                 if (apply_defaults(nexus, optarg, err))
       
   390                     goto error;
       
   391 
       
   392                 break;
       
   393 
       
   394             case OPT_CHANNEL:
       
   395                 if (apply_channel(nexus, optarg, err))
       
   396                     goto error;
       
   397 
       
   398                 break;
       
   399             
       
   400             case OPT_MODULE:
       
   401                 if (apply_module(nexus, optarg, err))
       
   402                     goto error;
       
   403 
       
   404                 break;
       
   405 
       
   406             case OPT_CONF:
       
   407                 if (apply_conf(nexus, optarg, err))
       
   408                     goto error;
       
   409 
       
   410                 break;
       
   411             
       
   412             case OPT_DEBUG:
       
   413                 set_log_level(LOG_DEBUG);
       
   414                 break;
       
   415             
       
   416             case OPT_CONSOLE:
       
   417                 if (apply_console(nexus, err))
       
   418                     goto error;
       
   419                 
       
   420                 break;
       
   421             
       
   422             case OPT_CONFIG:
       
   423                 if (apply_config(nexus, optarg, err))
       
   424                     goto error;
       
   425 
       
   426                 break;
       
   427 
       
   428             case '?':
       
   429                 usage(nexus, argv[0]);
       
   430                 return SET_ERROR(err, &general_errors, ERR_CMD_OPT);
       
   431         }
       
   432     }
       
   433     
       
   434     // ok
       
   435     return SUCCESS;
       
   436 
       
   437 error:
       
   438     return ERROR_CODE(err);    
       
   439 }
       
   440 
       
   441 void nexus_shutdown (struct nexus *nexus)
       
   442 {
       
   443     // destroy the console
       
   444     if (nexus->lua_console)
       
   445         lua_console_destroy(nexus->lua_console);
       
   446     
       
   447     nexus->lua_console = NULL;
       
   448 
       
   449     // unload the modules
       
   450     if (nexus->modules)
       
   451        modules_unload(nexus->modules);
       
   452 
       
   453     // quit the irc client
       
   454     if (nexus->client)
       
   455        irc_client_quit(nexus->client, "Goodbye, cruel world ;(");
       
   456 
       
   457     // remove the signal handlers
       
   458     if (nexus->signals)
       
   459         signals_free(nexus->signals);
       
   460 
       
   461     nexus->signals = NULL;
       
   462 
       
   463     // now event_base_dispatch should return once everythings' shut down...
       
   464     nexus->shutdown = true;
       
   465 }
       
   466 
       
   467 void nexus_crash (struct nexus *nexus)
       
   468 {
       
   469     // force-quit the event loop
       
   470     event_base_loopbreak(nexus->ev_base);
       
   471 }
       
   472 
       
   473 void nexus_destroy (struct nexus *nexus)
       
   474 {
       
   475     // destroy the console
       
   476     if (nexus->lua_console)
       
   477         lua_console_destroy(nexus->lua_console);
       
   478 
       
   479     // destroy the lua state
       
   480     if (nexus->lua)
       
   481         nexus_lua_destroy(nexus->lua);
       
   482 
       
   483     // destroy the modules
       
   484     if (nexus->modules)
       
   485         modules_destroy(nexus->modules);
       
   486 
       
   487     // destroy the irc client
       
   488     if (nexus->client)
       
   489         irc_client_destroy(nexus->client);
       
   490 
       
   491     // remove the signal handlers
       
   492     if (nexus->signals)
       
   493         signals_free(nexus->signals);
       
   494     
       
   495     // finally, the libevent base
       
   496     if (nexus->ev_base)
       
   497         event_base_free(nexus->ev_base);
       
   498 
       
   499     // ok, nexus is now dead, so drop all pointers to make valgrind show things as leaked :)
       
   500     memset(nexus, 0, sizeof(*nexus));
       
   501 }
       
   502 
       
   503 static void on_sig_shutdown (evutil_socket_t sig, short what, void *arg)
       
   504 {
       
   505     struct nexus *nexus = arg;
       
   506 
       
   507     (void) sig;
       
   508     (void) what;
       
   509     
       
   510     if (!nexus->shutdown) {
       
   511         // shutdown
       
   512         log_info("Terminating...");
       
   513 
       
   514         nexus_shutdown(nexus);
       
   515 
       
   516     } else {
       
   517         // already tried to shutdown
       
   518         log_info("Crashing!");
       
   519 
       
   520         nexus_crash(nexus);
       
   521     }
       
   522 }
       
   523 
       
   524 int main (int argc, char **argv) 
       
   525 {
       
   526     struct nexus _nexus, *nexus = &_nexus;
       
   527     error_t err;
       
   528 
       
   529     // zero nexus
       
   530     memset(nexus, 0, sizeof(*nexus));
       
   531 
       
   532     // initialize libevent
       
   533     if ((nexus->ev_base = event_base_new()) == NULL)
       
   534         FATAL("event_base_new");
       
   535     
       
   536 
       
   537     // initialize signal handlers
       
   538     if ((ERROR_CODE(&err) = signals_create(&nexus->signals, nexus->ev_base)))
       
   539         FATAL("signals_create");
       
   540  
       
   541     // add our signal handlers
       
   542     if (signal_ignore(SIGPIPE, &err))
       
   543         FATAL_ERROR(&err, "signals_ignore");
       
   544     
       
   545     // add our SIGTERM handler before console_init()?
       
   546     if (
       
   547             (ERROR_CODE(&err) = signals_add(nexus->signals, SIGTERM, on_sig_shutdown, nexus))
       
   548         ||  (ERROR_CODE(&err) = signals_add(nexus->signals, SIGINT, on_sig_shutdown, nexus))
       
   549     )
       
   550         FATAL_ERROR(&err, "signals_add");
       
   551 
       
   552     // initialize sock module
       
   553     if (sock_init(nexus->ev_base, &err))
       
   554         FATAL_ERROR(&err, "sock_init");
       
   555 
       
   556     // modules 
       
   557     if ((ERROR_CODE(&err) = modules_create(&nexus->modules, nexus)))
       
   558         FATAL_ERROR(&err, "modules_create");
       
   559     
       
   560     // the IRC client
       
   561     if (irc_client_create(&nexus->client, &err))
       
   562         FATAL_ERROR(&err, "irc_client_create");
       
   563     
       
   564     // lua state
       
   565     if (nexus_lua_create(&nexus->lua, nexus, &err))
       
   566         FATAL_ERROR(&err, "nexus_lua_create");
       
   567 
       
   568     
       
   569     // parse args
       
   570     if (parse_args(nexus, argc, argv, &err))
       
   571         FATAL_ERROR(&err, "parse_args");
       
   572 
       
   573   
       
   574     // run event loop
       
   575     log_info("entering event loop");
       
   576 
       
   577     if (event_base_dispatch(nexus->ev_base)) {
       
   578         if (nexus->shutdown) {
       
   579             log_info("clean shutdown completed");
       
   580 
       
   581         } else {
       
   582             FATAL("event_base_dispatch returned without shutdown");
       
   583 
       
   584         }
       
   585     } else {
       
   586         log_warn("zero return from event_base_dispatch");
       
   587     }
       
   588 
       
   589     // cleanup
       
   590     nexus_destroy(nexus);
       
   591     nexus = NULL;
       
   592 
       
   593     // ok
       
   594     return EXIT_SUCCESS;
       
   595 }
       
   596