// monitor/main.cpp - Old daemon to restart servers when they crashed. // // Copyright © ???? Bartosz Waszak // Copyright © 2011-2014 Ben Longbons // // This file is part of The Mana World (Athena server) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include #include #include #include #include "../strings/mstring.hpp" #include "../strings/astring.hpp" #include "../strings/zstring.hpp" #include "../strings/xstring.hpp" #include "../strings/literal.hpp" #include "../io/cxxstdio.hpp" #include "../io/fd.hpp" #include "../io/line.hpp" #include "../mmo/config_parse.hpp" #include "../net/timestamp-utils.hpp" #include "../poison.hpp" #define LOGIN_SERVER "./login-server"_s #define MAP_SERVER "./map-server"_s #define CHAR_SERVER "./char-server"_s #define CONFIG "conf/eathena-monitor.conf"_s namespace tmwa { // initialiized to $HOME/tmwserver static AString workdir; //the rest are relative to workdir static AString login_server = LOGIN_SERVER; static AString map_server = MAP_SERVER; static AString char_server = CHAR_SERVER; static pid_t pid_login, pid_map, pid_char; static AString make_path(XString base, XString path) { MString m; m += base; m += '/'; m += path; return AString(m); } static bool parse_option(io::Spanned name, io::Spanned value) { if (name.data == "login_server"_s) login_server = value.data; else if (name.data == "map_server"_s) map_server = value.data; else if (name.data == "char_server"_s) char_server = value.data; else if (name.data == "workdir"_s) workdir = value.data; else { name.span.error("invalid option key"_s); return false; } return true; } static bool read_config(ZString filename) { bool rv = true; io::LineReader in(filename); if (!in.is_open()) { FPRINTF(stderr, "Monitor config file not found: %s\n"_fmt, filename); exit(1); } io::Line line_; while (in.read_line(line_)) { io::Spanned line = respan(line_.to_span(), ZString(line_.text)); if (is_comment(line.data)) continue; io::Spanned name; io::Spanned value; if (!config_split(line, &name, &value)) { line.span.error("Unable to split config key: value"_s); rv = false; continue; } if (!parse_option(name, value)) { rv = false; continue; } } return rv; } static pid_t start_process(ZString exec) { const char *args[2] = {exec.c_str(), nullptr}; pid_t pid = fork(); if (pid == -1) { FPRINTF(stderr, "Failed to fork"_fmt); return 0; } if (pid == 0) { DIAG_PUSH(); DIAG_I(cast_qual); execv(exec.c_str(), const_cast(args)); DIAG_POP(); perror("Failed to exec"); kill(getppid(), SIGABRT); exit(1); } return pid; } // Kill all children with the same signal we got, then ourself. static void stop_process(int sig) { if (pid_map) kill(pid_map, sig); if (pid_login) kill(pid_login, sig); if (pid_char) kill(pid_char, sig); DIAG_PUSH(); DIAG_I(old_style_cast); DIAG_I(zero_as_null_pointer_constant); signal(sig, SIG_DFL); DIAG_POP(); raise(sig); } } // namespace tmwa int main(int argc, char *argv[]) { using namespace tmwa; // These are all the signals we are likely to get // The shell handles stop/cont signal(SIGTERM, stop_process); signal(SIGINT, stop_process); signal(SIGQUIT, stop_process); signal(SIGABRT, stop_process); workdir = make_path(ZString(strings::really_construct_from_a_pointer, getenv("HOME"), nullptr), "tmwserver"_s); ZString config = CONFIG; if (argc > 1) config = ZString(strings::really_construct_from_a_pointer, argv[1], nullptr); if (!read_config(config)) { exit(1); } if (chdir(workdir.c_str()) < 0) { perror("Failed to change directory"); exit(1); } PRINTF("Starting:\n"_fmt); PRINTF("* workdir: %s\n"_fmt, workdir); PRINTF("* login_server: %s\n"_fmt, login_server); PRINTF("* char_server: %s\n"_fmt, char_server); PRINTF("* map_server: %s\n"_fmt, map_server); { //make sure all possible file descriptors are free for use by the servers //if there are file descriptors higher than the max open from before the limit dropped, that's not our problem io::FD fd = io::FD::sysconf_SC_OPEN_MAX(); while ((fd = fd.prev()) > io::FD::stderr()) { if (fd.close() == 0) FPRINTF(stderr, "close fd %d\n"_fmt, fd.uncast_dammit()); } fd = io::FD::open("/dev/null"_s, O_RDWR); if (fd == io::FD()) { perror("open /dev/null"); exit(1); } fd.dup2(io::FD::stdin()); fd.dup2(io::FD::stdout()); fd.close(); } while (1) { // write stuff to stderr timestamp_seconds_buffer timestamp; stamp_time(timestamp); if (!pid_login) { pid_login = start_process(login_server); FPRINTF(stderr, "[%s] forked login server: %lu\n"_fmt, timestamp, static_cast(pid_login)); } if (!pid_char) { pid_char = start_process(char_server); FPRINTF(stderr, "[%s] forked char server: %lu\n"_fmt, timestamp, static_cast(pid_char)); } if (!pid_map) { pid_map = start_process(map_server); FPRINTF(stderr, "[%s] forked map server: %lu\n"_fmt, timestamp, static_cast(pid_map)); } pid_t dead = wait(nullptr); if (dead == -1) { perror("Failed to wait for child"); exit(1); } if (pid_login == dead) pid_login = 0; if (pid_char == dead) pid_char = 0; if (pid_map == dead) pid_map = 0; } }