/************************************************************************** * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. * * * * Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * * * In order to use any part of this Merc Diku Mud, you must comply with * * both the original Diku license in 'license.doc' as well the Merc * * license in 'license.txt'. In particular, you may not remove either of * * these copyright notices. * * * * Much time and thought has gone into this software and you are * * benefiting. We hope that you share your changes too. What goes * * around, comes around. * *************************************************************************** * ROM 2.4 is copyright 1993-1998 Russ Taylor * * ROM has been brought to you by the ROM consortium * * Russ Taylor (rtaylor@hypercube.org) * * Gabrielle Taylor (gtaylor@hypercube.org) * * Brian Moore (zump@rom.org) * * By using this code, you have agreed to follow the terms of the * * ROM license, in the file Rom24/doc/rom.license * *************************************************************************** * 1stMud ROM Derivative (c) 2001-2004 by Markanth * * http://www.firstmud.com/ <markanth@firstmud.com> * * By using this code you have agreed to follow the term of * * the 1stMud license in ../doc/1stMud/LICENSE * ***************************************************************************/ #include "merc.h" #include "interp.h" #include "data_table.h" #include "recycle.h" #include "tables.h" #ifdef HAVE_SETITIMER struct itimerval vtimer; void set_vtimer (long sec) { struct itimerval otimer; if (IsSet (mud_info.disabled_signals, MakeBit (SIGVTALRM))) return; vtimer.it_value.tv_sec = sec <= 0 ? (MINUTE * 3) : sec; vtimer.it_value.tv_usec = 0; #ifdef __CYGWIN__ if (setitimer (ITIMER_REAL, &vtimer, &otimer) == -1) #else if (setitimer (ITIMER_VIRTUAL, &vtimer, &otimer) == -1) #endif { log_error ("Failed to set vtimer."); exit (1); } vtimer.it_interval = otimer.it_value; } RETSIGTYPE sigalarm (int sig) { static int safe_check = 0; char crash_message_a[] = "The mud has been looping for the past 60 seconds."; char crash_message_b[] = "Initiating reboot..."; char crash_message_c[] = "The mud failed to inform the players of the above."; if (crash_info.status == CRASH_LOOPING) return; switch (safe_check) { case 0: safe_check = 1; bug (crash_message_a); bug (crash_message_b); break; case 1: safe_check = 2; log_string (crash_message_a); log_string (crash_message_b); log_string (crash_message_c); break; case 2: break; } set_vtimer (-1); halt_mud (sig); exit (0); } #elif defined HAVE_ALARM RETSIGTYPE sigalarm (int sig) { static int attempt = 0; static bool boredom = false; time_t tm; if (boredom) log_string ("TOCK!"); else log_string ("TICK!"); boredom = !boredom; if (crash_info.status == CRASH_LOOPING) return; time (&tm); if ((tm - current_time) > 120 || crash_info.crashed) { if (attempt != 1) { attempt = 1; halt_mud (sig); } raise (SIGSEGV); exit (0); } alarm (MINUTE * 3); } #endif char * crash_cmd_info (void) { switch (crash_info.status) { case CRASH_UNLIKELY: return "It is very UNlikely that this command caused the crash."; case CRASH_LIKELY: return "It is VERY likely that this command caused the crash."; case CRASH_PRE_PROCESSING: return "This crash occured during the command preprocessing."; case CRASH_POST_PROCESSING: return "This crash occured during the command postprocessing."; case CRASH_UPDATING: return "This crash occured while updating the above."; case CRASH_UNKNOWN: return "This crash occured after all updates were complete. Unknown cause."; case CRASH_BOOT: return "This crash occured during boot. Check log for cause."; default: return "Unknown cause."; } } void send_crash_info (void) { if (crash_info.shrt_cmd[0] && crash_info.desc != NULL && crash_info.status == CRASH_LIKELY) { d_write (crash_info.desc, NEWLINE "The last command you typed, '", 0); d_write (crash_info.desc, crash_info.shrt_cmd, 0); d_write (crash_info.desc, "', might have caused this crash." NEWLINE "Please note any unusual circumstances to IMP and avoid using that command." NEWLINE, 0); } } void crash_log (const char *msg) { FILE *fp; #ifdef HAVE_GDB char gdb[MPL]; char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH]; struct stat fst; #endif if (crash_info.crashed > 1) return; #ifdef HAVE_GDB gdb[0] = NUL; sprintf (buf, "%s/core", CWDIR); if (stat (buf, &fst) != -1) { sprintf (buf2, "gdb -batch %s %s", EXE_FILE, buf); if ((fp = popen (buf2, "r")) != NULL) { fread (gdb, MPL - 1000, 1, fp); pclose (fp); } sprintf (buf2, "mv -f %s %s/core.%d", buf, BIN_DIR, getpid ()); system (buf2); } #endif if ((fp = fopen (LOG_DIR "crash.log", "w")) != NULL) { fprintf (fp, "Crash on %s.\n", str_time (-1, -1, NULL)); fprintf (fp, "%s\n", fix_string (msg)); #ifdef HAVE_GDB if (gdb != NULL) fprintf (fp, "%s\n", gdb); #endif } fclose (fp); #ifdef HAVE_SENDMAIL if ((fp = popen ("sendmail -t", "w")) != NULL) { fprintf (fp, "To: %s Administrator <%s@%s>" LF, mud_info.name, UNAME, HOSTNAME); fprintf (fp, "From: %s <%s@%s>" LF, mud_info.name, UNAME, HOSTNAME); fprintf (fp, "Reply-to: %s <%s@%s>" LF, mud_info.name, UNAME, HOSTNAME); fprintf (fp, "X-Mailer: %s" LF, mud_info.name); fprintf (fp, "Subject: Crash: %s" LF, str_time (-1, -1, NULL)); fprintf (fp, LF); fprintf (fp, "%s" LF, fix_string (msg)); #ifdef HAVE_GDB if (gdb != NULL) { fprintf (fp, "---GDB OUTPUT---" LF); fprintf (fp, "%s" LF, gdb); } #endif pclose (fp); } #endif return; } RETSIGTYPE halt_mud (int sig) { Descriptor *d; CharData *ch; char message[MSL]; #ifdef HAVE_WORKING_FORK struct sigaction default_action; pid_t forkpid; int i; int status; waitpid (-1, &status, WNOHANG); switch (crash_info.crashed) { case 0: #endif crash_info.crashed++; #ifdef HAVE_STRSIGNAL logf ("GAME CRASHED: %s", strsignal (sig)); #elif defined HAVE_PSIGNAL psignal (sig, "GAME CRASHED"); #endif send_crash_info (); sprintf (message, NEWLINE "---CRASH INFORMATION---" NEWLINE "Signal %d" #ifdef HAVE_STRSIGNAL " (%s)" #endif NEWLINE "Last recorded function: %s" NEWLINE "Details: %s" NEWLINE "%s" NEWLINE, sig, #ifdef HAVE_STRSIGNAL strsignal (sig), #endif crash_info.shrt_cmd, crash_info.long_cmd, crash_cmd_info ()); for (d = descriptor_first; d != NULL; d = d_next) { d_next = d->next; ch = CH (d); if (!ch) { close_socket (d); continue; } save_char_obj (ch); d_write (d, NEWLINE "\007", 3); d_write (d, mud_info.name, 0); d_write (d, " has CRASHED.\007" NEWLINE, 0); if (IsImmortal (ch)) d_write (d, message, 0); } #ifdef HAVE_WORKING_FORK if ((forkpid = fork ()) > 0) { waitpid (forkpid, &status, WNOHANG); crs_info.status = CRS_COPYOVER; copyover (); exit (0); } else if (forkpid < 0) { exit (1); } for (i = 255; i >= 0; i--) close (i); open (NULL_FILE, O_RDWR); dup (0); dup (0); default_action.sa_handler = SIG_DFL; sigaction (sig, &default_action, NULL); if (!fork ()) { crash_log (message); exit (1); } else return; raise (sig); break; case 1: crash_info.crashed++; for (d = descriptor_first; d != NULL; d = d_next) { d_next = d->next; ch = d->original ? d->original : d->character; if (ch == NULL) { close_socket (d); continue; } d_write (d, "** Error saving character files; conducting full reboot. **\007" NEWLINE, 0); close_socket (d); continue; } log_string ("CHARACTERS NOT SAVED."); default_action.sa_handler = SIG_DFL; sigaction (sig, &default_action, NULL); if (!fork ()) { kill (getppid (), sig); exit (1); } else return; raise (sig); break; case 2: crash_info.crashed++; log_string ("TOTAL GAME CRASH."); default_action.sa_handler = SIG_DFL; sigaction (sig, &default_action, NULL); if (!fork ()) { kill (getppid (), sig); exit (1); } else return; raise (sig); break; case 3: default_action.sa_handler = SIG_DFL; sigaction (sig, &default_action, NULL); if (!fork ()) { kill (getppid (), sig); exit (1); } else return; raise (sig); break; } #endif } void cleanup_mud (void) { EXTERN FileData *fpArea; #ifdef HAVE_SETITIMER set_vtimer (-1); #endif while (auction_first != NULL) reset_auc (auction_first, true); rw_gquest_data (act_write); rw_war_data (act_write); rw_mud_data (act_write); rw_time_data (act_write); rw_note_data (act_write); do_function (NULL, &do_asave, "changed"); save_room_objs (); #ifndef DISABLE_WEBSRV shutdown_web_server (); #endif #ifndef DISABLE_I3 I3_shutdown (0); #endif close_network (); fflush (NULL); if (fpArea) f_close (fpArea); if (fpReserve != NULL) fclose (fpReserve); log_string ("Mud cleanup successfull."); logf ("%s ran for %s.", mud_info.name, timestr (getcurrenttime () - boot_time, false)); } void exit_mud (void) { Descriptor *d, *d_next; logf ("Normal program termination..."); for (d = descriptor_first; d != NULL; d = d_next) { d_next = d->next; d_write (d, NEWLINE "Normal program termination..." NEWLINE, 0); if (CH (d) != NULL) { save_char_obj (CH (d)); d_write (d, NEWLINE "Saving, and disconnecting..." NEWLINE, 0); } d->outtop = 0; close_socket (d); } cleanup_mud (); } RETSIGTYPE terminate_mud (int sig) { Descriptor *d; CharData *ch; char message[MSL]; crash_info.crashed++; log_string ("GAME TERMINATED"); sprintf (message, NEWLINE "Last recorded function: %s" NEWLINE "Details: %s" NEWLINE "%s" NEWLINE, crash_info.shrt_cmd, crash_info.long_cmd, crash_cmd_info ()); for (d = descriptor_first; d != NULL; d = d_next) { d_next = d->next; ch = CH (d); if (!ch) { close_socket (d); continue; } save_char_obj (ch); d_write (d, NEWLINE "\007", 3); d_write (d, mud_info.name, 0); d_write (d, " has been TERMINATED.\007" NEWLINE, 0); if (IsImmortal (ch)) d_write (d, message, 0); } exit (1); } const struct sig_type sig_table[] = { #ifndef WIN32 {"SIGPIPE", SIGPIPE, SIG_IGN, 0}, {"SIGCHLD", SIGCHLD, SIG_IGN, 0}, {"SIGHUP", SIGHUP, SIG_IGN, 0}, {"SIGQUIT", SIGQUIT, halt_mud, SA_NODEFER}, {"SIGBUS", SIGBUS, halt_mud, SA_NODEFER}, {"SIGUSR1", SIGUSR1, halt_mud, SA_NODEFER}, {"SIGUSR2", SIGUSR2, halt_mud, SA_NODEFER}, #else #define SA_NODEFER 0 #endif {"SIGINT", SIGINT, halt_mud, SA_NODEFER}, {"SIGILL", SIGILL, halt_mud, SA_NODEFER}, {"SIGFPE", SIGFPE, halt_mud, SA_NODEFER}, {"SIGSEGV", SIGSEGV, halt_mud, SA_NODEFER}, {"SIGTERM", SIGTERM, terminate_mud, SA_NODEFER}, {"SIGABRT", SIGABRT, halt_mud, SA_NODEFER}, #ifdef HAVE_SETITIMER {"SIGVTALRM", SIGVTALRM, sigalarm, SA_NODEFER}, #elif defined HAVE_ALARM {"SIGALRM", SIGALRM, sigalarm, SA_NODEFER}, #endif {NULL, -1, NULL, -1} }; bool init_sig (const struct sig_type *tabl) { if (IsSet (mud_info.disabled_signals, MakeBit (tabl->sig))) return false; #ifdef WIN32 signal (tabl->sig, tabl->sigfun); #else { struct sigaction sigact; sigact.sa_flags = tabl->flags; sigact.sa_handler = (RETSIGTYPE (*)(int)) tabl->sigfun; sigemptyset (&sigact.sa_mask); sigaction (tabl->sig, &sigact, NULL); } #endif #ifdef HAVE_SETITIMER if (tabl->sig == SIGVTALRM) { vtimer.it_interval.tv_sec = MINUTE * 3; vtimer.it_interval.tv_usec = 0; set_vtimer (-1); } #elif defined HAVE_ALARM if (tabl->sig == SIGALRM) alarm (MINUTE * 3); #endif return true; } void set_signals (void) { int i; crash_info.desc = NULL; crash_info.shrt_cmd[0] = '\0'; crash_info.long_cmd[0] = '\0'; crash_info.status = CRASH_BOOT; crash_info.crashed = 0; for (i = 0; sig_table[i].name != NULL; i++) init_sig (&sig_table[i]); #if defined HAVE_ATEXIT || defined(WIN32) atexit (exit_mud); #endif log_string ("Signals Initialized."); crs_info.who = &str_empty[0]; crs_info.reason = &str_empty[0]; } Do_Fun (do_crash) { char arg[MIL]; int i; argument = one_argument (argument, arg); if (get_trust (ch) < MAX_LEVEL) { chprintln (ch, "You don't have enough security to use this command."); return; } if (NullStr (arg)) { Column c; set_cols (&c, ch, 4, COLS_CHAR, ch); cmd_syntax (ch, NULL, n_fun, "<sig>", NULL); chprint (ch, "Available signals:"); for (i = 0; sig_table[i].name != NULL; i++) print_cols (&c, " %s", sig_table[i].name); cols_nl (&c); return; } for (i = 0; sig_table[i].name != NULL; i++) { if (!str_prefix (arg, sig_table[i].name)) { chprintlnf (ch, "Sending %s signal to %s...", sig_table[i].name, mud_info.name); raise (sig_table[i].sig); break; } } do_crash (n_fun, ch, ""); return; }