PennMUSH Community

root/1.8.3/trunk/src/game.c

Revision 1228, 69.6 kB (checked in by shawnw, 6 months ago)

Panic db file handle was never closed.

Line 
1 /**
2  * \file game.c
3  *
4  * \brief The main game driver.
5  *
6  *
7  */
8
9 #include "copyrite.h"
10 #include "config.h"
11
12 #include <ctype.h>
13 #include <fcntl.h>
14 #include <string.h>
15 #include <signal.h>
16 #ifdef I_SYS_TIME
17 #include <sys/time.h>
18 #ifdef TIME_WITH_SYS_TIME
19 #include <time.h>
20 #endif
21 #else
22 #include <time.h>
23 #endif
24 #ifdef WIN32
25 #include <process.h>
26 #include <windows.h>
27 #undef OPAQUE                   /* Clashes with flags.h */
28 void Win32MUSH_setup(void);
29 #endif
30 #ifdef I_SYS_TYPES
31 #include <sys/types.h>
32 #endif
33 #ifdef HAVE_SYS_RESOURCE_H
34 #include <sys/resource.h>
35 #endif
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #ifdef I_UNISTD
39 #include <unistd.h>
40 #endif
41 #include <errno.h>
42
43 #include "conf.h"
44 #include "externs.h"
45 #include "mushdb.h"
46 #include "game.h"
47 #include "attrib.h"
48 #include "match.h"
49 #include "case.h"
50 #include "extmail.h"
51 #include "extchat.h"
52 #ifdef HAS_OPENSSL
53 #include "myssl.h"
54 #endif
55 #include "getpgsiz.h"
56 #include "parse.h"
57 #include "access.h"
58 #include "version.h"
59 #include "strtree.h"
60 #include "command.h"
61 #include "htab.h"
62 #include "ptab.h"
63 #include "log.h"
64 #include "lock.h"
65 #include "dbdefs.h"
66 #include "flags.h"
67 #include "function.h"
68 #include "help.h"
69 #include "dbio.h"
70 #include "mypcre.h"
71 #ifndef WIN32
72 #include "wait.h"
73 #endif
74 #include "ansi.h"
75
76 #ifdef hpux
77 #include <sys/syscall.h>
78 #define getrusage(x,p)   syscall(SYS_GETRUSAGE,x,p)
79 #endif                          /* fix to HP-UX getrusage() braindamage */
80
81 #include "confmagic.h"
82
83 /* declarations */
84 GLOBALTAB globals = { 0, "", 0, 0, 0, 0, 0, 0, 0, 0 };
85 static int epoch = 0;
86 static int reserved;                    /**< Reserved file descriptor */
87 static dbref *errdblist = NULL; /**< List of dbrefs to return errors from */
88 static dbref *errdbtail = NULL;        /**< Pointer to end of errdblist */
89 #define ERRDB_INITIAL_SIZE 5
90 static int errdbsize = ERRDB_INITIAL_SIZE; /**< Current size of errdblist array */
91 static void errdb_grow(void);
92
93
94 extern void initialize_mt(void);
95 extern const unsigned char *tables;
96 extern void conf_default_set(void);
97 static bool dump_database_internal(void);
98 static FILE *db_open(const char *filename);
99 static FILE *db_open_write(const char *filename);
100 static void db_close(FILE * f);
101 static int fail_commands(dbref player);
102 void do_readcache(dbref player);
103 int check_alias(const char *command, const char *list);
104 static int list_check(dbref thing, dbref player, char type,
105                       char end, char *str, int just_match);
106 int alias_list_check(dbref thing, const char *command, const char *type);
107 int loc_alias_check(dbref loc, const char *command, const char *type);
108 void do_poor(dbref player, char *arg1);
109 void do_writelog(dbref player, char *str, int ltype);
110 void bind_and_queue(dbref player, dbref cause, char *action, const char *arg,
111                     const char *placestr);
112 void do_scan(dbref player, char *command, int flag);
113 void do_list(dbref player, char *arg, int lc);
114 void do_dolist(dbref player, char *list, char *command,
115                dbref cause, unsigned int flags);
116 void do_uptime(dbref player, int mortal);
117 static char *make_new_epoch_file(const char *basename, int the_epoch);
118 #ifdef HAS_GETRUSAGE
119 void rusage_stats(void);
120 #endif
121
122 void do_list_memstats(dbref player);
123 void do_list_allocations(dbref player);
124 void st_stats_header(dbref player);
125 void st_stats(dbref player, StrTree *root, const char *name);
126 void do_timestring(char *buff, char **bp, const char *format,
127                    unsigned long secs);
128
129 extern void create_minimal_db(void);    /* From db.c */
130
131 dbref orator = NOTHING;  /**< Last dbref to issue a speech command */
132
133 #ifdef COMP_STATS
134 extern void compress_stats(long *entries,
135                            long *mem_used,
136                            long *total_uncompressed, long *total_compressed);
137 #endif
138
139
140 pid_t forked_dump_pid = -1;
141
142 /** Open /dev/null to reserve a file descriptor that can be reused later. */
143 void
144 reserve_fd(void)
145 {
146 #ifndef WIN32
147   reserved = open("/dev/null", O_RDWR);
148 #endif
149 }
150
151 /** Release the reserved file descriptor for other use. */
152 void
153 release_fd(void)
154 {
155 #ifndef WIN32
156   close(reserved);
157 #endif
158 }
159
160 /** User command to dump the database.
161  * \verbatim
162  * This implements the @dump command.
163  * \endverbatim
164  * \param player the enactor, for permission checking.
165  * \param num checkpoint interval, as a string.
166  * \param flag type of dump.
167  */
168 void
169 do_dump(dbref player, char *num, enum dump_type flag)
170 {
171   if (Wizard(player)) {
172 #ifdef ALWAYS_PARANOID
173     if (1) {
174 #else
175     if (flag != DUMP_NORMAL) {
176 #endif
177       /* want to do a scan before dumping each object */
178       globals.paranoid_dump = 1;
179       if (num && *num) {
180         /* checkpoint interval given */
181         globals.paranoid_checkpt = atoi(num);
182         if ((globals.paranoid_checkpt < 1)
183             || (globals.paranoid_checkpt >= db_top)) {
184           notify(player, T("Permission denied. Invalid checkpoint interval."));
185           globals.paranoid_dump = 0;
186           return;
187         }
188       } else {
189         /* use a default interval */
190         globals.paranoid_checkpt = db_top / 5;
191         if (globals.paranoid_checkpt < 1)
192           globals.paranoid_checkpt = 1;
193       }
194       if (flag == DUMP_PARANOID) {
195         notify_format(player, T("Paranoid dumping, checkpoint interval %d."),
196                       globals.paranoid_checkpt);
197         do_rawlog(LT_CHECK,
198                   "*** PARANOID DUMP *** done by %s(#%d),\n",
199                   Name(player), player);
200       } else {
201         notify_format(player, T("Debug dumping, checkpoint interval %d."),
202                       globals.paranoid_checkpt);
203         do_rawlog(LT_CHECK,
204                   "*** DEBUG DUMP *** done by %s(#%d),\n",
205                   Name(player), player);
206       }
207       do_rawlog(LT_CHECK, T("\tcheckpoint interval %d, at %s"),
208                 globals.paranoid_checkpt, show_time(mudtime, 0));
209     } else {
210       /* normal dump */
211       globals.paranoid_dump = 0;        /* just to be safe */
212       notify(player, "Dumping...");
213       do_rawlog(LT_CHECK, "** DUMP ** done by %s(#%d) at %s",
214                 Name(player), player, show_time(mudtime, 0));
215     }
216     fork_and_dump(1);
217     globals.paranoid_dump = 0;
218   } else {
219     notify(player, T("Sorry, you are in a no dumping zone."));
220   }
221 }
222
223
224 /** Print global variables to the trace log.
225  * This function is used for error reporting.
226  */
227 void
228 report(void)
229 {
230   if (GoodObject(global_eval_context.cplr))
231     do_rawlog(LT_TRACE, T("TRACE: Cmd:%s\tby #%d at #%d"),
232               global_eval_context.ccom, global_eval_context.cplr,
233               Location(global_eval_context.cplr));
234   else
235     do_rawlog(LT_TRACE, "TRACE: Cmd:%s\tby #%d", global_eval_context.ccom,
236               global_eval_context.cplr);
237   notify_activity(NOTHING, 0, 1);
238 }
239
240 #ifdef HAS_GETRUSAGE
241 /** Log process statistics to the error log.
242  */
243 void
244 rusage_stats(void)
245 {
246   struct rusage usage;
247   int pid;
248   int psize;
249
250   pid = getpid();
251   psize = getpagesize();
252   getrusage(RUSAGE_SELF, &usage);
253
254   do_rawlog(LT_ERR, T("Process statistics:"));
255   do_rawlog(LT_ERR, T("Time used:   %10ld user   %10ld sys"),
256             (long) usage.ru_utime.tv_sec, (long) usage.ru_stime.tv_sec);
257   do_rawlog(LT_ERR, "Max res mem: %10ld pages  %10ld bytes",
258             usage.ru_maxrss, (usage.ru_maxrss * psize));
259   do_rawlog(LT_ERR, "Integral mem:%10ld shared %10ld private %10ld stack",
260             usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss);
261   do_rawlog(LT_ERR,
262             T("Page faults: %10ld hard   %10ld soft    %10ld swapouts"),
263             usage.ru_majflt, usage.ru_minflt, usage.ru_nswap);
264   do_rawlog(LT_ERR, T("Disk I/O:    %10ld reads  %10ld writes"),
265             usage.ru_inblock, usage.ru_oublock);
266   do_rawlog(LT_ERR, T("Network I/O: %10ld in     %10ld out"), usage.ru_msgrcv,
267             usage.ru_msgsnd);
268   do_rawlog(LT_ERR, T("Context swi: %10ld vol    %10ld forced"),
269             usage.ru_nvcsw, usage.ru_nivcsw);
270   do_rawlog(LT_ERR, "Signals:     %10ld", usage.ru_nsignals);
271 }
272
273 #endif                          /* HAS_GETRUSAGE */
274
275 /** User interface to shut down the MUSH.
276  * \verbatim
277  * This implements the @shutdown command.
278  * \endverbatim
279  * \param player the enactor, for permission checking.
280  * \param flag type of shutdown to perform.
281  */
282 void
283 do_shutdown(dbref player, enum shutdown_type flag)
284 {
285   if (flag == SHUT_PANIC && !God(player)) {
286     notify(player, T("It takes a God to make me panic."));
287     return;
288   }
289   if (Wizard(player)) {
290     flag_broadcast(0, 0, T("GAME: Shutdown by %s"), Name(player));
291     do_log(LT_ERR, player, NOTHING, T("SHUTDOWN by %s(%s)\n"),
292            Name(player), unparse_dbref(player));
293
294     if (flag == SHUT_PANIC) {
295       mush_panic("@shutdown/panic");
296     } else {
297       if (flag == SHUT_PARANOID) {
298         globals.paranoid_checkpt = db_top / 5;
299         if (globals.paranoid_checkpt < 1)
300           globals.paranoid_checkpt = 1;
301         globals.paranoid_dump = 1;
302       }
303       shutdown_flag = 1;
304     }
305   } else {
306     notify(player, T("Your delusions of grandeur have been duly noted."));
307   }
308 }
309
310 jmp_buf db_err;
311
312 static bool
313 dump_database_internal(void)
314 {
315   char realdumpfile[2048];
316   char realtmpfl[2048];
317   char tmpfl[2048];
318   FILE *f = NULL;
319
320 #ifndef PROFILING
321 #ifndef WIN32
322   ignore_signal(SIGPROF);
323 #endif
324 #endif
325
326   if (setjmp(db_err)) {
327     /* The dump failed. Disk might be full or something went bad with the
328        compression slave. Boo! */
329     do_rawlog(LT_ERR, T("ERROR! Database save failed."));
330     flag_broadcast("WIZARD ROYALTY", 0,
331                    T("GAME: ERROR! Database save failed!"));
332     if (f)
333       db_close(f);
334 #ifndef PROFILING
335 #ifdef HAS_ITIMER
336     install_sig_handler(SIGPROF, signal_cpu_limit);
337 #endif
338 #endif
339     return false;
340   } else {
341     local_dump_database();
342
343 #ifdef ALWAYS_PARANOID
344     globals.paranoid_checkpt = db_top / 5;
345     if (globals.paranoid_checkpt < 1)
346       globals.paranoid_checkpt = 1;
347 #endif
348
349     sprintf(realdumpfile, "%s%s", globals.dumpfile, options.compresssuff);
350     strcpy(tmpfl, make_new_epoch_file(globals.dumpfile, epoch));
351     sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
352
353     if ((f = db_open_write(tmpfl)) != NULL) {
354       switch (globals.paranoid_dump) {
355       case 0:
356 #ifdef ALWAYS_PARANOID
357         db_paranoid_write(f, 0);
358 #else
359         db_write(f, 0);
360 #endif
361         break;
362       case 1:
363         db_paranoid_write(f, 0);
364         break;
365       case 2:
366         db_paranoid_write(f, 1);
367         break;
368       }
369       db_close(f);
370       if (rename_file(realtmpfl, realdumpfile) < 0) {
371         penn_perror(realtmpfl);
372         longjmp(db_err, 1);
373       }
374     } else {
375       penn_perror(realtmpfl);
376       longjmp(db_err, 1);
377     }
378     sprintf(realdumpfile, "%s%s", options.mail_db, options.compresssuff);
379     strcpy(tmpfl, make_new_epoch_file(options.mail_db, epoch));
380     sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
381     if (mdb_top >= 0) {
382       if ((f = db_open_write(tmpfl)) != NULL) {
383         dump_mail(f);
384         db_close(f);
385         if (rename_file(realtmpfl, realdumpfile) < 0) {
386           penn_perror(realtmpfl);
387           longjmp(db_err, 1);
388         }
389       } else {
390         penn_perror(realtmpfl);
391         longjmp(db_err, 1);
392       }
393     }
394     sprintf(realdumpfile, "%s%s", options.chatdb, options.compresssuff);
395     strcpy(tmpfl, make_new_epoch_file(options.chatdb, epoch));
396     sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
397     if ((f = db_open_write(tmpfl)) != NULL) {
398       save_chatdb(f);
399       db_close(f);
400       if (rename_file(realtmpfl, realdumpfile) < 0) {
401         penn_perror(realtmpfl);
402         longjmp(db_err, 1);
403       }
404     } else {
405       penn_perror(realtmpfl);
406       longjmp(db_err, 1);
407     }
408     time(&globals.last_dump_time);
409   }
410
411 #ifndef PROFILING
412 #ifdef HAS_ITIMER
413   install_sig_handler(SIGPROF, signal_cpu_limit);
414 #endif
415 #endif
416
417   return true;
418 }
419
420 /** Crash gracefully.
421  * This function is called when something disastrous happens - typically
422  * a failure to malloc memory or a signal like segfault.
423  * It logs the fault, does its best to dump a panic database, and
424  * exits abruptly. This function does not return.
425  * \param message message to log to the error log.
426  */
427 void
428 mush_panic(const char *message)
429 {
430   const char *panicfile = options.crash_db;
431   FILE *f = NULL;
432   static int already_panicking = 0;
433
434   if (already_panicking) {
435     do_rawlog(LT_ERR,
436               T
437               ("PANIC: Attempted to panic because of '%s' while already panicking. Run in circles, scream and shout!"),
438               message);
439     _exit(133);
440   }
441
442   already_panicking = 1;
443   do_rawlog(LT_ERR, "PANIC: %s", message);
444   report();
445   flag_broadcast(0, 0, "EMERGENCY SHUTDOWN: %s", message);
446
447   /* turn off signals */
448   block_signals();
449
450   /* shut down interface */
451   emergency_shutdown();
452
453   /* dump panic file if we have a database read. */
454   if (globals.database_loaded) {
455     if (setjmp(db_err)) {
456       /* Dump failed. We're in deep doo-doo */
457       do_rawlog(LT_ERR, T("CANNOT DUMP PANIC DB. OOPS."));
458       _exit(134);
459     } else {
460       if ((f = fopen(panicfile, FOPEN_WRITE)) == NULL) {
461         do_rawlog(LT_ERR, T("CANNOT OPEN PANIC FILE, YOU LOSE"));
462         _exit(135);
463       } else {
464         do_rawlog(LT_ERR, T("DUMPING: %s"), panicfile);
465         db_write(f, DBF_PANIC);
466         dump_mail(f);
467         save_chatdb(f);
468         fclose(f);
469         do_rawlog(LT_ERR, T("DUMPING: %s (done)"), panicfile);
470       }
471     }
472   } else {
473     do_rawlog(LT_ERR, T("Skipping panic dump because database isn't loaded."));
474   }
475   _exit(136);
476 }
477
478 /** Crash gracefully.
479  * Calls mush_panic() with its arguments formatted.
480  * \param msg printf()-style format string.
481  */
482 void
483 mush_panicf(const char *msg, ...)
484 {
485 #ifdef HAS_VSNPRINTF
486   char c[BUFFER_LEN];
487 #else
488   char c[BUFFER_LEN * 3];
489 #endif
490   va_list args;
491
492   va_start(args, msg);
493
494 #ifdef HAS_VSNPRINTF
495   vsnprintf(c, sizeof c, msg, args);
496 #else
497   vsprintf(c, msg, args);
498 #endif
499   c[BUFFER_LEN - 1] = '\0';
500   va_end(args);
501
502   mush_panic(c);
503   _exit(136);                   /* Not reached but kills warnings */
504 }
505
506 /** Dump the database.
507  * This function is a wrapper for dump_database_internal() that does
508  * a little logging before and after the dump.
509  */
510 void
511 dump_database(void)
512 {
513   epoch++;
514
515   do_rawlog(LT_ERR, "DUMPING: %s.#%d#", globals.dumpfile, epoch);
516   if (dump_database_internal())
517     do_rawlog(LT_ERR, "DUMPING: %s.#%d# (done)", globals.dumpfile, epoch);
518 }
519
520 /** Dump a database, possibly by forking the process.
521  * This function calls dump_database_internal() to dump the MUSH
522  * databases. If we're configured to do so, it forks first, so that
523  * the child process can perform the dump while the parent continues
524  * to run the MUSH for the players. If we can't fork, this function
525  * warns players online that a dump is taking place and the game
526  * may pause.
527  * \param forking if 1, attempt a forking dump.
528  */
529 void
530 fork_and_dump(int forking)
531 {
532   pid_t child;
533   bool nofork, status, split;
534   epoch++;
535
536 #ifdef LOG_CHUNK_STATS
537   chunk_stats(NOTHING, 0);
538   chunk_stats(NOTHING, 1);
539 #endif
540   do_rawlog(LT_CHECK, "CHECKPOINTING: %s.#%d#", globals.dumpfile, epoch);
541   if (NO_FORK)
542     nofork = 1;
543   else
544     nofork = !forking || (globals.paranoid_dump == 2);  /* Don't fork for dump/debug */
545 #if defined(WIN32) || !defined(HAVE_FORK)
546   nofork = 1;
547 #endif
548   split = 0;
549   if (!nofork && chunk_num_swapped()) {
550 #ifndef WIN32
551     /* Try to clone the chunk swapfile. */
552     if (chunk_fork_file()) {
553       split = 1;
554     } else {
555       /* Ack, can't fork, 'cause we have stuff on disk... */
556       do_log(LT_ERR, 0, 0,
557              "fork_and_dump: Data are swapped to disk, so nonforking dumps will be used.");
558       flag_broadcast("WIZARD", 0,
559                      "DUMP: Data are swapped to disk, so nonforking dumps will be used.");
560       nofork = 1;
561     }
562 #endif
563   }
564   if (!nofork) {