]> git.pond.sub.org Git - empserver/blob - src/server/main.c
Delay shutdown up to 3s to let player output buffers drain
[empserver] / src / server / main.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2009, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See files README, COPYING and CREDITS in the root of the source
23  *  tree for related information and legal notices.  It is expected
24  *  that future projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  main.c: Empire Server main, startup and shutdown
29  *
30  *  Known contributors to this file:
31  *     Dave Pare, 1994
32  *     Steve McClure, 1996, 1998
33  *     Doug Hay, 1998
34  *     Ron Koenderink, 2004-2009
35  *     Markus Armbruster, 2005-2008
36  */
37
38 #include <config.h>
39
40 #include <errno.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #ifndef _WIN32
44 #include <sys/wait.h>
45 #endif
46 #include <unistd.h>
47
48 #if defined(_WIN32)
49 #include <winsock2.h>
50 #undef NS_ALL
51 #include <process.h>
52 #include "service.h"
53 #endif
54
55 #include "empio.h"
56 #include "empthread.h"
57 #include "file.h"
58 #include "journal.h"
59 #include "land.h"
60 #include "match.h"
61 #include "misc.h"
62 #include "nat.h"
63 #include "nuke.h"
64 #include "optlist.h"
65 #include "plane.h"
66 #include "player.h"
67 #include "product.h"
68 #include "prototypes.h"
69 #include "sect.h"
70 #include "server.h"
71 #include "ship.h"
72 #include "version.h"
73
74 static void ignore(void);
75 static void crash_dump(void);
76 static void create_pidfile(char *, pid_t);
77
78 #if defined(_WIN32)
79 static void loc_NTInit(void);
80 static void loc_NTTerm(void);
81 #endif
82
83 /*
84  * Lock to synchronize player threads with update and shutdown.
85  * Update and shutdown takes it exclusive, commands take it shared.
86  */
87 empth_rwlock_t *play_lock;
88
89 static char pidfname[] = "server.pid";
90
91 /* Run as daemon?  If yes, detach from controlling terminal etc. */
92 static int daemonize = 1;
93
94 static void
95 help(char *program_name, char *complaint)
96 {
97     if (complaint)
98         fprintf(stderr, "%s: %s\n", program_name, complaint);
99     fprintf(stderr, "Try -h for help.\n");
100 }
101
102 static void
103 print_usage(char *program_name)
104 {
105     printf("Usage: %s [OPTION]...\n"
106            "  -d              debug mode, implies -E abort\n"
107            "  -e CONFIG-FILE  configuration file\n"
108            "                  (default %s)\n"
109            "  -E ACTION       what to do on oops: abort, crash-dump, nothing (default)\n"
110            "  -h              display this help and exit\n"
111 #ifdef _WIN32
112            "  -i              install service `%s'\n"
113            "  -I NAME         install service NAME\n"
114 #endif
115            "  -p              threading debug mode, implies -d\n"
116 #ifdef _WIN32
117            "  -u              uninstall service `%s'\n"
118            "  -U NAME         uninstall service NAME\n"
119 #endif
120            "  -s              enable stack checking\n"
121            "  -R RANDOM-SEED  random seed\n"
122            "  -v              display version information and exit\n",
123            program_name, dflt_econfig
124 #ifdef _WIN32
125            , DEFAULT_SERVICE_NAME, DEFAULT_SERVICE_NAME
126 #endif
127         );
128 }
129
130 int
131 main(int argc, char **argv)
132 {
133     static char *oops_key[] = { "abort", "crash-dump", "nothing", NULL };
134     static void (*oops_hndlr[])(void) = { abort, crash_dump, ignore };
135     int flags = 0;
136 #if defined(_WIN32)
137     int install_service_set = 0;
138     char *program_name = NULL;
139     char *service_name = NULL;
140     int remove_service_set = 0;
141 #endif
142     char *config_file = NULL;
143     int op, idx, sig;
144     unsigned seed = time(NULL);
145
146     oops_handler = ignore;
147
148 #ifdef _WIN32
149 # define XOPTS "iI:uU:"
150 #else
151 # define XOPTS
152 #endif
153     while ((op = getopt(argc, argv, "de:E:hpsR:v" XOPTS)) != EOF) {
154         switch (op) {
155         case 'p':
156             flags |= EMPTH_PRINT;
157             /* fall through */
158         case 'd':
159             oops_handler = abort;
160             daemonize = 0;
161             break;
162         case 'e':
163             config_file = optarg;
164             break;
165         case 'E':
166             idx = stmtch(optarg, oops_key, 0, sizeof(*oops_key));
167             if (idx < 0) {
168                 help(argv[0], "invalid argument for -E");
169                 return EXIT_FAILURE;
170             }
171             oops_handler = oops_hndlr[idx];
172             break;
173 #if defined(_WIN32)
174         case 'I':
175             service_name = optarg;
176             /* fall through */
177         case 'i':
178             install_service_set++;
179             break;
180         case 'U':
181             service_name = optarg;
182             /* fall through */
183         case 'u':
184             remove_service_set++;
185             break;
186 #endif  /* _WIN32 */
187         case 's':
188             flags |= EMPTH_STACKCHECK;
189             break;
190         case 'R':
191             seed = strtoul(optarg, NULL, 10);
192             break;
193         case 'v':
194             printf("%s\n\n%s", version, legal);
195             return EXIT_SUCCESS;
196         case 'h':
197             print_usage(argv[0]);
198             return EXIT_SUCCESS;
199         default:
200             help(argv[0], NULL);
201             return EXIT_FAILURE;
202         }
203     }
204
205 #if defined(_WIN32)
206     if ((!daemonize || flags || config_file != NULL) &&
207         remove_service_set) {
208         fprintf(stderr, "Can't use -p, -s, -d or -e with either "
209             "-u or -U options\n");
210         exit(EXIT_FAILURE);
211     }
212     if ((!daemonize || flags) && install_service_set) {
213         fprintf(stderr, "Can't use -d, -p or -s with either "
214             "-i or -I options\n");
215         exit(EXIT_FAILURE);
216     }
217     if (install_service_set && remove_service_set) {
218         fprintf(stderr, "Can't use both -u or -U and -i or -I "
219             "options\n");
220         exit(EXIT_FAILURE);
221     }
222
223     if (remove_service_set)
224         return remove_service(service_name);
225     if (install_service_set) {
226         program_name = _fullpath(NULL, argv[0], 0);
227         if (config_file != NULL)
228             config_file = _fullpath(NULL, config_file, 0);
229     }
230 #endif  /* _WIN32 */
231
232     empfile_init();
233     if (emp_config(config_file) < 0)
234         exit(EXIT_FAILURE);
235     empfile_fixup();
236     if (read_builtin_tables() < 0)
237         exit(EXIT_FAILURE);
238     if (read_custom_tables() < 0)
239         exit(EXIT_FAILURE);
240     if (chdir(gamedir)) {
241         fprintf(stderr, "Can't chdir to %s (%s)\n",
242                 gamedir, strerror(errno));
243         exit(EXIT_FAILURE);
244     }
245
246 #if defined(_WIN32)
247     if (install_service_set)
248         return install_service(program_name, service_name, config_file);
249 #endif  /* _WIN32 */
250
251     init_server(seed);
252
253 #if defined(_WIN32)
254     if (daemonize != 0) {
255         SERVICE_TABLE_ENTRY DispatchTable[]={
256             {"Empire Server", service_main},
257             {NULL, NULL}
258         };
259         if (StartServiceCtrlDispatcher(DispatchTable))
260             return 0;
261         else {
262             /*
263              * If it is service startup error then exit otherwise
264              * start server in the foreground
265              */
266             if (GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
267                 logerror("Failed to dispatch service (%lu)",
268                          GetLastError());
269                 finish_server();
270                 exit(EXIT_FAILURE);
271             }
272         }
273     }
274     daemonize = 0;
275 #else  /* !_WIN32 */
276     if (daemonize) {
277         if (disassoc() < 0) {
278             logerror("Can't become daemon (%s)", strerror(errno));
279             exit(1);
280         }
281     }
282 #endif /* !_WIN32 */
283     start_server(flags);
284     journal_prng(seed);
285
286     for (;;) {
287         sig = empth_wait_for_signal();
288 #ifdef SIGHUP
289         if (sig == SIGHUP) {
290             /* if you make changes here, also update relo() */
291             journal_reopen();
292             update_reschedule();
293             logreopen();
294             continue;
295         }
296 #endif
297         break;
298     }
299
300     shutdwn(sig);
301     CANT_REACH();
302     finish_server();
303     return EXIT_SUCCESS;
304 }
305
306 static void
307 ignore(void)
308 {
309 }
310
311 static void
312 crash_dump(void)
313 {
314 #ifdef _WIN32
315     logerror("Crash dump is not implemented");
316 #else
317     pid_t pid;
318     int status;
319
320     fflush(NULL);
321     pid = fork();
322     if (pid < 0) {
323         logerror("Can't fork for crash dump (%s)", strerror(errno));
324         return;
325     }
326     if (pid == 0)
327         raise(SIGABRT);         /* child */
328
329     /* parent */
330     while (waitpid(pid, &status, 0) < 0) {
331         if (errno != EINTR) {
332             logerror("Can't get crash dumping child's status (%s)",
333                      strerror(errno));
334             return;
335         }
336     }
337     run_hook(post_crash_dump_hook, "post-crash-dump");
338     logerror("Crash dump complete");
339 #endif
340 }
341
342 /*
343  * Initialize for serving, acquire resources.
344  */
345 void
346 init_server(unsigned seed)
347 {
348     srandom(seed);
349 #if defined(_WIN32)
350     loc_NTInit();
351 #endif
352     player_init();
353     ef_init_srv();
354     io_init();
355     init_nreport();
356
357     loginit("server");
358 }
359
360 /*
361  * Start serving.
362  */
363 void
364 start_server(int flags)
365 {
366     pid_t pid;
367
368     pid = getpid();
369     create_pidfile(pidfname, pid);
370     logerror("------------------------------------------------------");
371     logerror("Empire server (pid %d) started", (int)pid);
372
373     empth_init((void **)&player, flags);
374
375     if (journal_startup() < 0)
376         exit(1);
377
378     market_init();
379     update_init();
380     empth_create(player_accept, 50 * 1024, flags, "AcceptPlayers", NULL);
381 }
382
383 /*
384  * Finish serving, release resources.
385  */
386 void
387 finish_server(void)
388 {
389     ef_fin_srv();
390 #if defined(_WIN32)
391     loc_NTTerm();
392 #endif
393     journal_shutdown();
394     remove(pidfname);
395 }
396
397 static void
398 create_pidfile(char *fname, pid_t pid)
399 {
400     FILE *pidf = fopen(fname, "w");
401     if (!pidf
402         || fprintf(pidf, "%d\n", (int)pid) < 0
403         || fclose(pidf)) {
404         logerror("Can't write PID file (%s)", strerror(errno));
405         exit(1);
406     }
407 }
408
409 void
410 shutdwn(int sig)
411 {
412     struct player *p;
413     time_t now;
414     int i, queues_drained;
415
416     logerror("Shutdown commencing (cleaning up threads.)");
417
418     for (p = player_next(NULL); p; p = player_next(p)) {
419         if (p->state != PS_PLAYING)
420             continue;
421         pr_flash(p, "Server shutting down...\n");
422         p->state = PS_SHUTDOWN;
423         p->may_sleep = PLAYER_SLEEP_NEVER;
424         p->aborted++;
425         if (p->command) {
426             pr_flash(p, "Shutdown aborting command\n");
427         }
428         empth_wakeup(p->proc);
429     }
430     empth_rwlock_wrlock(play_lock);
431
432     now = time(NULL);
433     empth_yield();
434     for (i = 1; i <= 3; i++) {
435         queues_drained = 1;
436         for (p = player_next(NULL); p; p = player_next(p)) {
437             if (io_outputwaiting(p->iop))
438                 queues_drained = 0;
439         }
440         if (queues_drained)
441             break;
442         logerror("Waiting for player output to drain\n");
443         empth_sleep(now + i);
444     }
445
446     for (p = player_next(NULL); p; p = player_next(p)) {
447         if (io_outputwaiting(p->iop))
448             logerror("Output for player %d lost", p->cnum);
449     }
450
451     if (sig)
452         logerror("Server shutting down on signal %d", sig);
453     else
454         logerror("Server shutting down at deity's request");
455     finish_server();
456
457 #if defined(_WIN32)
458     if (daemonize)
459         stop_service();
460 #endif
461     exit(0);
462 }
463
464 #if defined(_WIN32)
465 static void
466 loc_NTInit(void)
467 {
468     int rc;
469     WORD wVersionRequested;
470     WSADATA wsaData;
471
472     wVersionRequested = MAKEWORD(2, 0);
473     rc = WSAStartup(wVersionRequested, &wsaData);
474     if (rc != 0) {
475         logerror("WSAStartup failed.  %d", rc);
476         exit(1);
477     }
478 }
479
480 static void
481 loc_NTTerm(void)
482 {
483     WSACleanup();
484 }
485 #endif