#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "config.h" #include "server.h" #include "cfgdir.h" #include "announce.h" #include "systemd.h" #include "igmp.h" #define DEFAULT_HOMEDIR_PATH "/home/david/intest" #define DEFAULT_MAIN_CONFIG_FILE_PATH "./mcproxy.conf" unsigned debug_mask = DBG_ERROR | DBG_INFO; bool exiting = false; struct cfg *cfghack = NULL; void __debug(enum debug_category category, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); } __attribute__((noreturn)) void __die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); exit(EXIT_FAILURE); }; static void cfg_free(struct uring_task *task) { struct cfg *cfg = container_of(task, struct cfg, task); debug(DBG_SIG, "called\n"); systemd_delete(cfg); xfree(cfg->igmp_iface); cfg->igmp_iface = NULL; xfree(cfg->cfg_path); cfg->cfg_path = NULL; exiting = true; /* The cfg struct is free:d in main() */ } enum mcfg_keys { MCFG_KEY_INVALID = 0, MCFG_KEY_IGMP, MCFG_KEY_IGMP_IFACE }; struct cfg_key_value_map mcfg_key_map[] = { { .key_name = "igmp", .key_value = MCFG_KEY_IGMP, .value_type = CFG_VAL_TYPE_BOOL, }, { .key_name = "igmp_iface", .key_value = MCFG_KEY_IGMP_IFACE, .value_type = CFG_VAL_TYPE_STRING, }, { .key_name = NULL, .key_value = MCFG_KEY_INVALID, .value_type = CFG_VAL_TYPE_INVALID, } }; static void cfg_read(struct cfg *cfg) { FILE *cfgfile; const char *path; char buf[4096]; char *pos = buf; size_t rd = 0; size_t r; if (cfg->cfg_path) path = cfg->cfg_path; else path = DEFAULT_MAIN_CONFIG_FILE_PATH; cfgfile = fopen(path, "re"); if (!cfgfile) { /* ENOENT is only an error with an explicitly set path */ if (errno == ENOENT && !cfg->cfg_path) return; else if (errno == ENOENT) die("main config file (%s) missing\n", path); else perrordie("fopen"); } debug(DBG_CFG, "opened main config file (%s)\n", path); while (rd < sizeof(buf)) { r = fread(pos, 1, sizeof(buf) - rd - 1, cfgfile); if (r == 0) break; rd += r; pos += r; } if (rd == 0) die("main config file (%s) invalid\n", path); if (rd >= sizeof(buf)) die("main config file (%s) too large\n", path); fclose(cfgfile); *pos = '\0'; pos = buf; if (!config_parse_header(cfg, path, "mcproxy", &pos)) die("main config file (%s) invalid\n", path); while (true) { int key; const char *keyname; union cfg_value value; if (!config_parse_line(cfg, path, &pos, mcfg_key_map, &key, &keyname, &value)) break; if (key == MCFG_KEY_INVALID) die("main config file (%s) invalid\n", path); debug(DBG_CFG, "main cfg: key %s\n", keyname); switch (key) { case MCFG_KEY_IGMP: cfg->do_igmp = value.boolean; break; case MCFG_KEY_IGMP_IFACE: cfg->igmp_iface = xstrdup(value.str); if (!cfg->igmp_iface) perrordie("xstrdup"); break; case MCFG_KEY_INVALID: default: die("main config file (%s) invalid\n", path); } } } const struct { const char *name; unsigned val; } debug_category_str[] = { { .name = "config", .val = DBG_CFG },{ .name = "refcount", .val = DBG_REF },{ .name = "malloc", .val = DBG_MALLOC },{ .name = "announce", .val = DBG_ANN },{ .name = "signals", .val = DBG_SIG },{ .name = "uring", .val = DBG_UR },{ .name = "server", .val = DBG_SRV },{ .name = "proxy", .val = DBG_PROXY },{ .name = "rcon", .val = DBG_RCON },{ .name = "idle", .val = DBG_IDLE },{ .name = "igmp", .val = DBG_IGMP },{ .name = NULL, .val = 0 } }; static struct cfg * cfg_init(int argc, char **argv) { struct cfg *cfg; int c; unsigned i; cfg = zmalloc(sizeof(*cfg)); if (!cfg) perrordie("malloc"); uring_task_init(&cfg->task, "cfg", NULL, cfg_free); list_init(&cfg->servers); while (true) { int option_index = 0; static struct option long_options[] = { { "homedir", required_argument, 0, 'h' }, { "debug", required_argument, 0, 'd' }, { "verbose", no_argument, 0, 'v' }, { 0, 0, 0, 0 } }; c = getopt_long(argc, argv, ":h:d:v", long_options, &option_index); if (c == -1) break; switch (c) { case 'h': cfg->homedir = optarg; break; case 'v': debug_mask |= DBG_VERBOSE; break; case 'd': if (!strcasecmp(optarg, "all")) { debug_mask = ~0; break; } else if (!strcasecmp(optarg, "list")) { error("Debug categories:\n"); error(" * all\n"); for (i = 0; debug_category_str[i].name; i++) error(" * %s\n", debug_category_str[i].name); exit(EXIT_FAILURE); } for (i = 0; debug_category_str[i].name; i++) { if (!strcasecmp(optarg, debug_category_str[i].name)) break; } if (!debug_category_str[i].name) die("invalid debug category"); debug_mask |= debug_category_str[i].val; break; default: die("invalid arguments"); } } if (optind < argc) die("invalid arguments"); if (!cfg->homedir) cfg->homedir = DEFAULT_HOMEDIR_PATH; verbose("Homedir is %s\n", cfg->homedir); if (chdir(cfg->homedir)) perrordie("chdir"); return cfg; } struct signalfd_ev { struct uring_task task; //struct signalfd_siginfo buf; struct cfg *cfg; uint64_t buf; }; static void signalfd_free(struct uring_task *task) { struct signalfd_ev *sev = container_of(task, struct signalfd_ev, task); debug(DBG_SIG, "called\n"); sev->cfg->sev = NULL; xfree(sev); } static void dump_tree(struct cfg *cfg) { struct server *server; if (!debug_enabled(DBG_REF)) return; debug(DBG_REF, "\n\n\n\n"); debug(DBG_REF, "Dumping Tree\n"); debug(DBG_REF, "============\n"); uring_task_refdump(&cfg->task); uring_refdump(cfg->uev); if (cfg->sev) uring_task_refdump(&cfg->sev->task); igmp_refdump(cfg->igmp); announce_refdump(cfg->aev); if (cfg->iev) cfgdir_refdump(cfg->iev); list_for_each_entry(server, &cfg->servers, list) server_refdump(server); debug(DBG_REF, "============\n"); debug(DBG_REF, "\n\n\n\n"); } static void signalfd_read(struct cfg *cfg, struct uring_task *task, int res) { struct signalfd_ev *sev = container_of(task, struct signalfd_ev, task); struct server *server, *stmp; static int count = 0; if (task->dead) { debug(DBG_SIG, "task dead\n"); return; } count++; if (count > 5) { error("Max signal count exceeded, force quitting\n"); exit(EXIT_FAILURE); } if (res != sizeof(sev->buf)) die("error in signalfd (%i)", res); if (sev->buf < 1000) { verbose("got a signal to quit\n"); sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); exit(EXIT_SUCCESS); } else { verbose("got a signal to dump tree\n"); sd_notifyf(0, "STOPPING=1\nSTATUS=Received signal, exiting"); dump_tree(cfg); uring_task_put(cfg, &sev->task); igmp_delete(cfg); announce_delete(cfg); cfgdir_delete(cfg); list_for_each_entry_safe(server, stmp, &cfg->servers, list) server_delete(cfg, server); uring_delete(cfg); return; } uring_read(cfg, &sev->task, &sev->buf, sizeof(sev->buf), signalfd_read); } static int hack_efd = -1; static void hack_handler(int signum) { uint64_t val; static int count = 0; switch (signum) { case SIGINT: debug(DBG_SIG, "Got a SIGINT\n"); val = 1000; if (count > 3) dump_tree(cfghack); break; case SIGHUP: debug(DBG_SIG, "Got a SIGHUP\n"); val = 1000; break; case SIGTERM: debug(DBG_SIG, "Got a SIGTERM\n"); val = 1; break; default: error("Got an unknown sig (%i)\n", signum); val = 1; break; } if (count > 5) { error("Max signal count exceeded, force quitting\n"); exit(EXIT_FAILURE); } write(hack_efd, &val, sizeof(val)); count++; } static void signalfd_init(struct cfg *cfg) { int sfd; //sigset_t mask; struct signalfd_ev *sev; sev = zmalloc(sizeof(*sev)); if (!sev) perrordie("malloc"); /* sigfillset(&mask); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) perrordie("sigprocmask"); sfd = signalfd(-1, &mask, SFD_CLOEXEC); if (sfd < 0) perrordie("signalfd"); */ struct sigaction action; sigfillset(&action.sa_mask); action.sa_handler = hack_handler; action.sa_flags = 0; sigaction(SIGINT, &action, NULL); sigaction(SIGHUP, &action, NULL); sigaction(SIGTERM, &action, NULL); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); sfd = eventfd(0, EFD_CLOEXEC); if (sfd < 0) perrordie("eventfd"); debug(DBG_SIG, "using fd %i\n", sfd); uring_task_init(&sev->task, "sev", uring_parent(cfg), signalfd_free); uring_task_set_fd(&sev->task, sfd); cfg->sev = sev; sev->cfg = cfg; hack_efd = sfd; uring_read(cfg, &sev->task, &sev->buf, sizeof(sev->buf), signalfd_read); } int main(int argc, char **argv) { struct cfg *cfg; struct server *server; unsigned server_count; cfg = cfg_init(argc, argv); cfghack = cfg; cfg_read(cfg); uring_init(cfg); signalfd_init(cfg); cfgdir_init(cfg); announce_init(cfg); announce_start(cfg->aev); igmp_init(cfg); uring_task_put(cfg, &cfg->task); server_count = 0; list_for_each_entry(server, &cfg->servers, list) server_count++; sd_notifyf(0, "READY=1\n" "STATUS=Running, %u server configurations loaded\n" "MAINPID=%lu", server_count, (unsigned long)getpid()); info("mcproxy started, %u server configurations loaded\n", server_count); uring_event_loop(cfg); verbose("Exiting\n"); xfree(cfg); if (debug_enabled(DBG_MALLOC)) debug_resource_usage(); fflush(stdout); fflush(stderr); exit(EXIT_SUCCESS); }