/**
* Name: eAthena processes monitor
* Original Author: Bartosz Waszak <waszi@evil.org.pl>
* Rewrite Author: Ben Longbons <b.r.longbons@gmail.com>
* License: GPL
* Compilation:
* gcc -o eathena-monitor eathena-monitor.c
*/
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <csignal>
#include "../strings/mstring.hpp"
#include "../strings/astring.hpp"
#include "../strings/zstring.hpp"
#include "../strings/xstring.hpp"
#include "../io/cxxstdio.hpp"
#include "../io/fd.hpp"
#include "../io/read.hpp"
#include "../mmo/config_parse.hpp"
#include "../mmo/utils.hpp"
#include "../poison.hpp"
#define LOGIN_SERVER "./login-server"
#define MAP_SERVER "./map-server"
#define CHAR_SERVER "./char-server"
#define CONFIG "conf/eathena-monitor.conf"
// 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(XString name, ZString value)
{
if (name == "login_server")
login_server = value;
else if (name == "map_server")
map_server = value;
else if (name == "char_server")
char_server = value;
else if (name == "workdir")
workdir = value;
else
{
FPRINTF(stderr, "WARNING: ingnoring invalid option '%s' : '%s'\n",
AString(name), value);
return false;
}
return true;
}
static
bool read_config(ZString filename)
{
bool rv = true;
io::ReadFile in(filename);
if (!in.is_open())
{
FPRINTF(stderr, "Monitor config file not found: %s\n", filename);
exit(1);
}
AString line;
while (in.getline(line))
{
if (is_comment(line))
continue;
XString name;
ZString value;
if (!config_split(line, &name, &value))
{
PRINTF("Bad line: %s\n", line);
rv = false;
continue;
}
if (!parse_option(name, value))
{
PRINTF("Bad key/value: %s\n", line);
rv = false;
continue;
}
}
return rv;
}
static
pid_t start_process(ZString exec)
{
const char *args[2] = {exec.c_str(), NULL};
pid_t pid = fork();
if (pid == -1)
{
FPRINTF(stderr, "Failed to fork");
return 0;
}
if (pid == 0)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
execv(exec.c_str(), const_cast<char **>(args));
#pragma GCC diagnostic 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);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
signal(sig, SIG_DFL);
#pragma GCC diagnostic pop
raise(sig);
}
int main(int argc, char *argv[])
{
// 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");
ZString config = CONFIG;
if (argc > 1)
config = ZString(strings::really_construct_from_a_pointer, argv[1], nullptr);
read_config(config);
if (chdir(workdir.c_str()) < 0)
{
perror("Failed to change directory");
exit(1);
}
PRINTF("Starting:\n");
PRINTF("* workdir: %s\n", workdir);
PRINTF("* login_server: %s\n", login_server);
PRINTF("* char_server: %s\n", char_server);
PRINTF("* map_server: %s\n", 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", fd.uncast_dammit());
}
fd = io::FD::open("/dev/null", 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",
timestamp, static_cast<unsigned long>(pid_login));
}
if (!pid_char)
{
pid_char = start_process(char_server);
FPRINTF(stderr, "[%s] forked char server: %lu\n",
timestamp, static_cast<unsigned long>(pid_char));
}
if (!pid_map)
{
pid_map = start_process(map_server);
FPRINTF(stderr, "[%s] forked map server: %lu\n",
timestamp, static_cast<unsigned long>(pid_map));
}
pid_t dead = wait(NULL);
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;
}
}