]> git.pond.sub.org Git - empserver/blobdiff - src/server/main.c
Simplify checks whether player thread may sleep
[empserver] / src / server / main.c
index f9006c101bc2b03c35ae2543effef1adf9e336f4..336eb6a65d1e4c408b910b9271f4481bbdce08f8 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Empire - A multi-player, client/server Internet based war game.
- *  Copyright (C) 1986-2004, Dave Pare, Jeff Bailey, Thomas Ruschak,
+ *  Copyright (C) 1986-2009, Dave Pare, Jeff Bailey, Thomas Ruschak,
  *                           Ken Stevens, Steve McClure
  *
  *  This program is free software; you can redistribute it and/or modify
  *
  *  ---
  *
- *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
- *  related information and legal notices. It is expected that any future
- *  projects/authors will amend these files as needed.
+ *  See files README, COPYING and CREDITS in the root of the source
+ *  tree for related information and legal notices.  It is expected
+ *  that future projects/authors will amend these files as needed.
  *
  *  ---
  *
- *  main.c: Thread and signal initialization for Empire Server
- * 
+ *  main.c: Empire Server main, startup and shutdown
+ *
  *  Known contributors to this file:
  *     Dave Pare, 1994
  *     Steve McClure, 1996, 1998
  *     Doug Hay, 1998
+ *     Ron Koenderink, 2004-2009
+ *     Markus Armbruster, 2005-2008
  */
 
-#include <signal.h>
-#if !defined(_WIN32)
-#include <sys/ioctl.h>
-#endif
+#include <config.h>
+
 #include <errno.h>
-#include <fcntl.h>
+#include <signal.h>
 #include <stdio.h>
-#include <string.h>
+#ifndef _WIN32
+#include <sys/wait.h>
+#endif
+#include <unistd.h>
 
 #if defined(_WIN32)
 #include <winsock2.h>
 #undef NS_ALL
 #include <process.h>
-#include "../lib/gen/getopt.h"
 #include "service.h"
-#include "direct.h"
 #endif
 
+#include "empio.h"
+#include "empthread.h"
+#include "file.h"
+#include "journal.h"
+#include "land.h"
+#include "match.h"
 #include "misc.h"
 #include "nat.h"
-#include "file.h"
-#include "player.h"
-#include "empthread.h"
-#include "plane.h"
 #include "nuke.h"
-#include "land.h"
-#include "ship.h"
-#include "sect.h"
-#include "product.h"
 #include "optlist.h"
-#include "server.h"
+#include "plane.h"
+#include "player.h"
+#include "product.h"
 #include "prototypes.h"
+#include "sect.h"
+#include "server.h"
+#include "ship.h"
+#include "version.h"
 
-static void nullify_objects(void);
-static void init_files(void);
-static void close_files(void);
+static void ignore(void);
+static void crash_dump(void);
 static void create_pidfile(char *, pid_t);
 
 #if defined(_WIN32)
@@ -76,139 +80,182 @@ static void loc_NTInit(void);
 static void loc_NTTerm(void);
 #endif
 
+/*
+ * Lock to synchronize player threads with update and shutdown.
+ * Update and shutdown takes it exclusive, commands take it shared.
+ */
+empth_rwlock_t *play_lock;
+
 static char pidfname[] = "server.pid";
 
-/* Debugging?  If yes call abort() on internal error.  */
-int debug = 0;
 /* Run as daemon?  If yes, detach from controlling terminal etc. */
-int daemonize = 1;
+static int daemonize = 1;
 
 static void
-print_usage(char *program_name)
+help(char *program_name, char *complaint)
 {
-#if defined(_WIN32)
-    printf("Usage: %s -i -I service_name -r -R service_name -D datadir -e config_file -d -p\n",
-       program_name);
-    printf("-i install service with the default name %s\n", DEFAULT_SERVICE_NAME);
-    printf("-r remove service with the default name %s\n", DEFAULT_SERVICE_NAME);
+    if (complaint)
+       fprintf(stderr, "%s: %s\n", program_name, complaint);
+    fprintf(stderr, "Try -h for help.\n");
+}
 
-#else
-    printf("Usage: %s -D datadir -e config_file -d -p -s\n", program_name);
-    printf("-s stack check flag (include print flag)\n");
+static void
+print_usage(char *program_name)
+{
+    printf("Usage: %s [OPTION]...\n"
+          "  -d              debug mode, implies -E abort\n"
+          "  -e CONFIG-FILE  configuration file\n"
+          "                  (default %s)\n"
+          "  -E ACTION       what to do on oops: abort, crash-dump, nothing (default)\n"
+          "  -h              display this help and exit\n"
+#ifdef _WIN32
+          "  -i              install service `%s'\n"
+          "  -I NAME         install service NAME\n"
+#endif
+          "  -p              threading debug mode, implies -d\n"
+#ifdef _WIN32
+          "  -u              uninstall service `%s'\n"
+          "  -U NAME         uninstall service NAME\n"
 #endif
-    printf("-p print flag\n");
-    printf("-d debug mode\n");
+          "  -s              enable stack checking\n"
+          "  -R RANDOM-SEED  random seed\n"
+          "  -v              display version information and exit\n",
+          program_name, dflt_econfig
+#ifdef _WIN32
+          , DEFAULT_SERVICE_NAME, DEFAULT_SERVICE_NAME
+#endif
+       );
 }
 
 int
 main(int argc, char **argv)
 {
+    static char *oops_key[] = { "abort", "crash-dump", "nothing", NULL };
+    static void (*oops_hndlr[])(void) = { abort, crash_dump, ignore };
     int flags = 0;
 #if defined(_WIN32)
     int install_service_set = 0;
+    char *program_name = NULL;
     char *service_name = NULL;
     int remove_service_set = 0;
-    int datadir_set = 0;
 #endif
     char *config_file = NULL;
-    int op;
+    int op, idx, sig;
+    unsigned seed = time(NULL);
 
-#if defined(_WIN32)
-    while ((op = getopt(argc, argv, "D:de:iI:rR:hp")) != EOF) {
+    oops_handler = ignore;
+
+#ifdef _WIN32
+# define XOPTS "iI:uU:"
 #else
-    while ((op = getopt(argc, argv, "D:de:psh")) != EOF) {
+# define XOPTS
 #endif
+    while ((op = getopt(argc, argv, "de:E:hpsR:v" XOPTS)) != EOF) {
        switch (op) {
-       case 'D':
-           datadir = optarg;
-#if defined(_WIN32)
-           datadir_set++;
-#endif
-           break;
+       case 'p':
+           flags |= EMPTH_PRINT;
+           /* fall through */
        case 'd':
-           debug++;
+           oops_handler = abort;
            daemonize = 0;
            break;
        case 'e':
            config_file = optarg;
            break;
-       case 'p':
-           flags |= EMPTH_PRINT;
-           daemonize = 0;
+       case 'E':
+           idx = stmtch(optarg, oops_key, 0, sizeof(*oops_key));
+           if (idx < 0) {
+               help(argv[0], "invalid argument for -E");
+               return EXIT_FAILURE;
+           }
+           oops_handler = oops_hndlr[idx];
            break;
 #if defined(_WIN32)
        case 'I':
            service_name = optarg;
-           /*
-            * fall out
-            */
+           /* fall through */
        case 'i':
            install_service_set++;
            break;
-           break;
-       case 'R':
+       case 'U':
            service_name = optarg;
-           /*
-            * fall out
-            */
-       case 'r':
+           /* fall through */
+       case 'u':
            remove_service_set++;
            break;
-#else
+#endif /* _WIN32 */
        case 's':
-           flags |= EMPTH_PRINT | EMPTH_STACKCHECK;
-           daemonize = 0;
+           flags |= EMPTH_STACKCHECK;
            break;
-#endif
+       case 'R':
+           seed = strtoul(optarg, NULL, 10);
+           break;
+       case 'v':
+           printf("%s\n\n%s", version, legal);
+           return EXIT_SUCCESS;
        case 'h':
-       default:
            print_usage(argv[0]);
+           return EXIT_SUCCESS;
+       default:
+           help(argv[0], NULL);
            return EXIT_FAILURE;
        }
     }
 
 #if defined(_WIN32)
-    if ((debug || datadir_set || config_file != NULL) &&
+    if ((!daemonize || flags || config_file != NULL) &&
        remove_service_set) {
-       fprintf(stderr, "Can't use -d, -D or -e with either "
-           "-r or -R options when starting the server\n");
+       fprintf(stderr, "Can't use -p, -s, -d or -e with either "
+           "-u or -U options\n");
        exit(EXIT_FAILURE);
     }
-    if (debug && install_service_set) {
-       fprintf(stderr, "Can't use -d with either "
-           "-i or -I options when starting the server\n");
+    if ((!daemonize || flags) && install_service_set) {
+       fprintf(stderr, "Can't use -d, -p or -s with either "
+           "-i or -I options\n");
        exit(EXIT_FAILURE);
     }
     if (install_service_set && remove_service_set) {
-       fprintf(stderr, "Can't use both -r or -R and -i or -I options when starting "
-           "the server\n");
+       fprintf(stderr, "Can't use both -u or -U and -i or -I "
+           "options\n");
        exit(EXIT_FAILURE);
     }
-#endif /* _WIN32 */
-
 
-#if defined(_WIN32)
     if (remove_service_set)
         return remove_service(service_name);
+    if (install_service_set) {
+       program_name = _fullpath(NULL, argv[0], 0);
+       if (config_file != NULL)
+           config_file = _fullpath(NULL, config_file, 0);
+    }
 #endif /* _WIN32 */
 
+    empfile_init();
     if (emp_config(config_file) < 0)
        exit(EXIT_FAILURE);
-    if (chdir(datadir)) {
-       fprintf(stderr, "Can't chdir to %s (%s)\n", datadir, strerror(errno));
+    empfile_fixup();
+    if (read_builtin_tables() < 0)
+       exit(EXIT_FAILURE);
+    if (read_custom_tables() < 0)
+       exit(EXIT_FAILURE);
+    if (chdir(gamedir)) {
+       fprintf(stderr, "Can't chdir to %s (%s)\n",
+               gamedir, strerror(errno));
        exit(EXIT_FAILURE);
     }
 
 #if defined(_WIN32)
     if (install_service_set)
-       return install_service(argv[0], service_name, datadir_set, config_file);
+       return install_service(program_name, service_name, config_file);
 #endif /* _WIN32 */
 
-    init_server();
+    init_server(seed);
 
 #if defined(_WIN32)
     if (daemonize != 0) {
-       SERVICE_TABLE_ENTRY DispatchTable[]={{"Empire Server", service_main},{NULL, NULL}};
+       SERVICE_TABLE_ENTRY DispatchTable[]={
+           {"Empire Server", service_main},
+           {NULL, NULL}
+       };
        if (StartServiceCtrlDispatcher(DispatchTable))
            return 0;
        else {
@@ -217,7 +264,8 @@ main(int argc, char **argv)
             * start server in the foreground
             */
            if (GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
-               logerror("Failed to dispatch service (%d)", GetLastError());
+               logerror("Failed to dispatch service (%lu)",
+                        GetLastError());
                finish_server();
                exit(EXIT_FAILURE);
            }
@@ -225,44 +273,87 @@ main(int argc, char **argv)
     }
     daemonize = 0;
 #else  /* !_WIN32 */
-    if (daemonize)
-       disassoc();
+    if (daemonize) {
+       if (disassoc() < 0) {
+           logerror("Can't become daemon (%s)", strerror(errno));
+           exit(1);
+       }
+    }
 #endif /* !_WIN32 */
     start_server(flags);
+    journal_prng(seed);
+
+    for (;;) {
+       sig = empth_wait_for_signal();
+#ifdef SIGHUP
+       if (sig == SIGHUP) {
+           /* if you make changes here, also update relo() */
+           journal_reopen();
+           update_reschedule();
+           logreopen();
+           continue;
+       }
+#endif
+       break;
+    }
 
-    empth_exit();
-
-    CANT_HAPPEN("main thread terminated");
+    shutdwn(sig);
+    CANT_REACH();
     finish_server();
     return EXIT_SUCCESS;
 }
 
+static void
+ignore(void)
+{
+}
+
+static void
+crash_dump(void)
+{
+#ifdef _WIN32
+    logerror("Crash dump is not implemented");
+#else
+    pid_t pid;
+    int status;
+
+    fflush(NULL);
+    pid = fork();
+    if (pid < 0) {
+       logerror("Can't fork for crash dump (%s)", strerror(errno));
+       return;
+    }
+    if (pid == 0)
+       raise(SIGABRT);         /* child */
+
+    /* parent */
+    while (waitpid(pid, &status, 0) < 0) {
+       if (errno != EINTR) {
+           logerror("Can't get crash dumping child's status (%s)",
+                    strerror(errno));
+           return;
+       }
+    }
+    run_hook(post_crash_dump_hook, "post-crash-dump");
+    logerror("Crash dump complete");
+#endif
+}
 
 /*
  * Initialize for serving, acquire resources.
  */
 void
-init_server(void)
+init_server(unsigned seed)
 {
-    srandom(time(NULL));
+    srandom(seed);
 #if defined(_WIN32)
     loc_NTInit();
 #endif
-    update_policy_check();
-    nullify_objects();
-    global_init();
-    shutdown_init();
     player_init();
-    ef_init();
-    init_files();
+    ef_init_srv();
     io_init();
     init_nreport();
 
-    if (opt_MOB_ACCESS) {
-       /* This fixes up mobility upon restart */
-       mobility_init();
-    }
-
     loginit("server");
 }
 
@@ -273,51 +364,21 @@ void
 start_server(int flags)
 {
     pid_t pid;
-#if !defined(_WIN32)
-    struct sigaction act;
-#endif
 
     pid = getpid();
     create_pidfile(pidfname, pid);
     logerror("------------------------------------------------------");
     logerror("Empire server (pid %d) started", (int)pid);
 
-#if !defined(_WIN32)
-    /* signal() should not be used with mit pthreads. Anyway if u
-       have a posix threads u definitly have posix signals -- Sasha */
-    sigemptyset(&act.sa_mask);
-    act.sa_handler = shutdwn;
-    sigaction(SIGTERM, &act, NULL);
-    sigaction(SIGINT, &act, NULL);
-    act.sa_handler = panic;
-    sigaction(SIGBUS, &act, NULL);
-    sigaction(SIGSEGV, &act, NULL);
-    sigaction(SIGILL, &act, NULL);
-    sigaction(SIGFPE, &act, NULL);
-    act.sa_handler = SIG_IGN;
-    sigaction(SIGPIPE, &act, NULL);
-#endif /* !_WIN32 */
+    empth_init((void **)&player, flags);
 
-    empth_init((char **)&player, flags);
-
-    empth_create(PP_ACCEPT, player_accept, (50 * 1024), flags,
-                "AcceptPlayers", "Accept network connections", 0);
-    empth_create(PP_KILLIDLE, player_kill_idle, (50 * 1024), flags,
-                "KillIdle", "Kills idle player connections", 0);
-    empth_create(PP_SCHED, update_sched, (50 * 1024), flags, "UpdateSched",
-                "Schedules updates to occur", 0);
-    empth_create(PP_TIMESTAMP, delete_lostitems, (50 * 1024), flags,
-                "DeleteItems", "Deletes old lost items", 0);
-    if (opt_MOB_ACCESS) {
-       /* Start the mobility access check thread */
-       empth_create(PP_TIMESTAMP, mobility_check, (50 * 1024), flags,
-                    "MobilityCheck", "Writes the timestamp file", 0);
-    }
+    if (journal_startup() < 0)
+       exit(1);
 
-    if (opt_MARKET) {
-       empth_create(PP_TIMESTAMP, market_update, (50 * 1024), flags,
-                    "MarketUpdate", "Updates the market", 0);
-    }
+    empth_create(player_accept, 50 * 1024, flags, "AcceptPlayers", NULL);
+
+    market_init();
+    update_init();
 }
 
 /*
@@ -326,10 +387,11 @@ start_server(int flags)
 void
 finish_server(void)
 {
-    close_files();
+    ef_fin_srv();
 #if defined(_WIN32)
     loc_NTTerm();
 #endif
+    journal_shutdown();
     remove(pidfname);
 }
 
@@ -338,118 +400,33 @@ create_pidfile(char *fname, pid_t pid)
 {
     FILE *pidf = fopen(fname, "w");
     if (!pidf
-       || fprintf(pidf, "%d\n", pid) < 0
+       || fprintf(pidf, "%d\n", (int)pid) < 0
        || fclose(pidf)) {
        logerror("Can't write PID file (%s)", strerror(errno));
        exit(1);
     }
 }
 
-static void
-init_files(void)
-{
-    int failed = 0;
-    failed |= !ef_open(EF_NATION, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_SECTOR, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_SHIP, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_PLANE, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_LAND, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_NEWS, O_RDWR, 0);
-    failed |= !ef_open(EF_LOAN, O_RDWR, 0);
-    failed |= !ef_open(EF_TREATY, O_RDWR, 0);
-    failed |= !ef_open(EF_NUKE, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_POWER, O_RDWR, 0);
-    failed |= !ef_open(EF_TRADE, O_RDWR, 0);
-    failed |= !ef_open(EF_MAP, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_BMAP, O_RDWR, EFF_MEM);
-    failed |= !ef_open(EF_COMM, O_RDWR, 0);
-    failed |= !ef_open(EF_LOST, O_RDWR, 0);
-    if (failed) {
-       logerror("Missing files, giving up");
-       exit(EXIT_FAILURE);
-    }
-}
-
-static void
-close_files(void)
-{
-    ef_close(EF_NATION);
-    ef_close(EF_SECTOR);
-    ef_close(EF_SHIP);
-    ef_close(EF_PLANE);
-    ef_close(EF_LAND);
-    ef_close(EF_NEWS);
-    ef_close(EF_LOAN);
-    ef_close(EF_TREATY);
-    ef_close(EF_NUKE);
-    ef_close(EF_POWER);
-    ef_close(EF_TRADE);
-    ef_close(EF_MAP);
-    ef_close(EF_COMM);
-    ef_close(EF_BMAP);
-    ef_close(EF_LOST);
-}
-
-/* we're going down.  try to close the files at least */
-#if !defined(_WIN32)
-void
-panic(int sig)
-{
-    struct sigaction act;
-
-    act.sa_flags = 0;
-    sigemptyset(&act.sa_mask);
-    act.sa_handler = SIG_DFL;
-    sigaction(SIGBUS, &act, NULL);
-    sigaction(SIGSEGV, &act, NULL);
-    sigaction(SIGILL, &act, NULL);
-    sigaction(SIGFPE, &act, NULL);
-    logerror("server received fatal signal %d", sig);
-    log_last_commands();
-    close_files();
-    if (CANT_HAPPEN(sig != SIGBUS && sig != SIGSEGV
-                   && sig != SIGILL && sig != SIGFPE))
-       _exit(1);
-    if (raise(sig))
-       _exit(1);
-}
-#endif /* _WIN32 */
-
 void
 shutdwn(int sig)
 {
     struct player *p;
-    time_t now;
 
     logerror("Shutdown commencing (cleaning up threads.)");
 
-    for (p = player_next(0); p != 0; p = player_next(p)) {
+    for (p = player_next(NULL); p; p = player_next(p)) {
        if (p->state != PS_PLAYING)
            continue;
        pr_flash(p, "Server shutting down...\n");
        p->state = PS_SHUTDOWN;
+       p->may_sleep = PLAYER_SLEEP_NEVER;
        p->aborted++;
        if (p->command) {
            pr_flash(p, "Shutdown aborting command\n");
        }
        empth_wakeup(p->proc);
     }
-
-    if (!sig) {
-       /* Sleep and let some cleanup happen - note this doesn't work
-          when called from a signal handler, since we may or may not
-          be in the right thread.  So we just pass by and kill 'em
-          all. */
-       time(&now);
-       empth_sleep(now + 1);
-    }
-
-    for (p = player_next(0); p != 0; p = player_next(p)) {
-       p->state = PS_KILL;
-       p->aborted++;
-       empth_terminate(p->proc);
-       p = player_delete(p);
-    }
+    empth_rwlock_wrlock(play_lock);
     if (sig)
        logerror("Server shutting down on signal %d", sig);
     else
@@ -457,78 +434,10 @@ shutdwn(int sig)
     finish_server();
 
 #if defined(_WIN32)
-    if (daemonize) {
+    if (daemonize)
         stop_service();
-       return;
-    }
 #endif
-    _exit(0);
-}
-
-
-static void
-nullify_objects(void)
-{
-    int i, j;
-
-    if (opt_BIG_CITY)
-       dchr[SCT_CAPIT] = bigcity_dchr;
-    if (opt_NO_LCMS)
-       dchr[SCT_LIGHT].d_cost = -1;
-    if (opt_NO_HCMS)
-       dchr[SCT_HEAVY].d_cost = -1;
-    if (opt_NO_OIL) {
-       dchr[SCT_OIL].d_cost = -1;
-       dchr[SCT_REFINE].d_cost = -1;
-    }
-    for (i = 0; i < pln_maxno; i++) {
-       if (opt_NO_HCMS)
-           plchr[i].pl_hcm = 0;
-       if (opt_NO_LCMS)
-           plchr[i].pl_lcm = 0;
-       if (opt_NO_OIL)
-           plchr[i].pl_fuel = 0;
-    }
-    for (i = 0; i < lnd_maxno; i++) {
-       if (opt_NO_HCMS)
-           lchr[i].l_hcm = 0;
-       if (opt_NO_LCMS)
-           lchr[i].l_lcm = 0;
-       /* Fix up the military values */
-       lchr[i].l_mil = lchr[i].l_item[I_MILIT];
-    }
-    for (i = 0; i < shp_maxno; i++) {
-       if (opt_NO_HCMS)
-           mchr[i].m_hcm = 0;
-       if (opt_NO_LCMS)
-           mchr[i].m_lcm = 0;
-       if (opt_NO_OIL) {
-           if (mchr[i].m_flags & M_OIL)
-               mchr[i].m_name = 0;
-       }
-    }
-    for (i = 0; i < nuk_maxno; i++) {
-       if (opt_NO_HCMS)
-           nchr[i].n_hcm = 0;
-       if (opt_NO_LCMS)
-           nchr[i].n_lcm = 0;
-    }
-    for (i = 0; i <= SCT_MAXDEF; i++) {
-       if (opt_NO_HCMS)
-           dchr[i].d_hcms = 0;
-       if (opt_NO_LCMS)
-           dchr[i].d_lcms = 0;
-    }
-    for (i = 0; i < prd_maxno; i++) {
-       for (j = 0; j < MAXPRCON; j++) {
-           if (opt_NO_HCMS && pchr[i].p_ctype[j] == I_HCM)
-               pchr[i].p_camt[j] = 0;
-           if (opt_NO_LCMS && pchr[i].p_ctype[j] == I_LCM)
-               pchr[i].p_camt[j] = 0;
-           if (opt_NO_OIL && pchr[i].p_ctype[j] == I_OIL)
-               pchr[i].p_camt[j] = 0;
-       }
-    }
+    exit(0);
 }
 
 #if defined(_WIN32)
@@ -543,7 +452,7 @@ loc_NTInit(void)
     rc = WSAStartup(wVersionRequested, &wsaData);
     if (rc != 0) {
        logerror("WSAStartup failed.  %d", rc);
-       _exit(1);
+       exit(1);
     }
 }