/************************************************************************** * 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-2003 by Ryan Jennings * * http://1stmud.dlmud.com/ <r-jenn@shaw.ca> * ***************************************************************************/ #include "merc.h" #include "signals.h" #include "interp.h" #include "tablesave.h" #include "webserver.h" struct crash_type crash_info; #if !defined(NO_VTIMER) struct itimerval vtimer; void set_vtimer(long sec) { vtimer.it_value.tv_sec = sec < 0 ? (60 * 3) : sec; vtimer.it_value.tv_usec = 0; if (setitimer(ITIMER_VIRTUAL, &vtimer, NULL) < 0) { log_string("Failed to set vtimer."); exit(1); } } 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.looping) { set_vtimer(-1); 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; } /* Reboot the MUD */ set_vtimer(-1); halt_mud(sig); // Shouldn't return exit(1); // Last resort } #elif defined(HAVE_ALARM) RETSIGTYPE sigalarm(int sig) { static int attempt = 0; static bool boredom = FALSE; time_t ptm; if (boredom) log_string("TOCK!"); else log_string("TICK!"); boredom = !boredom; if (crash_info.looping) { alarm(60); return; } time(&ptm); if ((ptm - current_time) > 120 || crash_info.crashed) { if (attempt != 1) { attempt = 1; halt_mud(sig); // Should NOT return } raise(SIGSEGV); // Something's wrong, cause a crash exit(0); } alarm(60); } #endif char *crash_cmd_info(void) { switch (crash_info.cmd_status) { case 0: return "It is very UNlikely that this command caused the crash."; case 1: return "It is VERY likely that this command caused the crash."; case 2: return "This crash occured during the command preprocessing."; case 3: return "This crash occured during the command postprocessing."; case 4: return "This crash occured while updating the above."; case 5: return "This crash occured after all updates were complete. Unknown cause."; case 6: return "This crash occured during boot. Check log for cause."; default: return "Unknown cause."; } } void send_crash_info(void) { // Inform last command typer that he caused the crash if (crash_info.shrt_cmd[0] && crash_info.desc != NULL && crash_info.cmd_status == 1) { d_write(crash_info.desc, "\n\rThe 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.\n\r" "Please note any unusual circumstances to IMP and avoid using that command.\n\r", 0); } } void crash_log(const char *msg) { FILE *fp; #if defined(unix) const char *gdb; char buf[MAX_STRING_LENGTH], CWDIR[1024]; #endif if (crash_info.crashed > 1) return; #if defined(unix) gdb = NULL; sprintf(buf, "gdb -batch %s %s/core", EXE_FILE, CWDIR); if ((fp = popen(buf, "r")) != NULL) { gdb = fread_file(fp); pclose(fp); } #endif /* Would be good to make a "note" of this on boot up */ if ((fp = fopen(LOG_DIR "crash.log", "w")) != NULL) { fprintf(fp, "Crash on %.24s.\n", ctime(¤t_time)); fprintf(fp, fix_string(msg)); #if defined(unix) if (gdb != NULL) fprintf(fp, "\n%s", gdb); #endif } fclose(fp); #if defined(unix) /* send an email as well */ if ((fp = popen("sendmail -t", "w")) != NULL) { fprintf(fp, "To: %s@%s\n", UNAME, HOSTNAME); fprintf(fp, "From: %s <%s@%s>\n", MUD_NAME, UNAME, HOSTNAME); fprintf(fp, "Reply-to: %s <%s@%s>\n", MUD_NAME, UNAME, HOSTNAME); fprintf(fp, "X-Mailer: %s\n", MUD_NAME); fprintf(fp, "Subject: Crash: %s\n\n", str_time(-1, -1, NULL)); fprintf(fp, fix_string(msg)); if (gdb != NULL) { fprintf(fp, "---GDB OUTPUT---\n"); fprintf(fp, "%s\n", gdb); } pclose(fp); } #endif return; } RETSIGTYPE halt_mud(int sig) { DESCRIPTOR_DATA *d; CHAR_DATA *ch; char message[MSL]; #if !defined(NO_COPYOVER) struct sigaction default_action; pid_t forkpid; int i; int status; waitpid(-1, &status, WNOHANG); if (crash_info.crashed == 0) { #endif crash_info.crashed++; #if defined(HAVE_STRSIGNAL) logf("GAME CRASHED: %s", strsignal(sig)); #elif defined(HAVE_PSIGNAL) psignal(sig, "GAME CRASHED"); #endif send_crash_info(); sprintf(message, "\n\r---CRASH INFORMATION---\n\rSignal %d" #if defined(HAVE_STRSIGNAL) " (%s)" #endif "\n\rLast recorded function: %s\n\rDetails: %s\n\r%s\n\r", sig, #if defined(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, "\n\r\007" MUD_NAME " has CRASHED.\007\n\r", 0); if (IS_IMMORTAL(ch)) d_write(d, message, 0); } #if !defined(NO_COPYOVER) // success - proceed with fork/copyover plan. Otherwise will go to // next section and crash with a full reboot to recover if ((forkpid = fork()) > 0) { // Parent process copyover and exit waitpid(forkpid, &status, WNOHANG); crs_info.status = CRS_COPYOVER; copyover(); exit(0); } else if (forkpid < 0) { exit(1); } // Child process proceed to dump // Close all files! for (i = 255; i >= 0; i--) close(i); // Dup /dev/null to STD{IN,OUT,ERR} open(NULL_FILE, O_RDWR); dup(0); dup(0); default_action.sa_handler = SIG_DFL; sigaction(sig, &default_action, NULL); // Run gdb, crash_log() now does this. if (!fork()) { crash_log(message); exit(0); } else return; raise(sig); } else if (crash_info.crashed == 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\n\r", 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); } else if (crash_info.crashed == 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); } else if (crash_info.crashed == 3) { default_action.sa_handler = SIG_DFL; sigaction(sig, &default_action, NULL); if (!fork()) { kill(getppid(), sig); exit(1); } else return; raise(sig); } #endif } void cleanup_mud(void) { EXTERN READ_DATA *fpArea; #if !defined(NO_VTIMER) set_vtimer(-1); #endif while (auction_first != NULL) reset_auc(auction_first, TRUE); save_donation_pit(); rw_gquest_data(action_write); rw_war_data(action_write); do_asave(NULL, "changed"); #if !defined(NO_WEB) if (WebUP) shutdown_web_server(); #endif #if defined(WIN32) WSACleanup(); #endif fflush(NULL); // flush all open outputs if (fpArea) close_read(fpArea); // close any open areas if (fpReserve != NULL) fclose(fpReserve); // close the reserve file log_string("Mud cleanup successfull."); logf(MUD_NAME " ran for %s.", timestr(current_time - boot_time, FALSE)); } void exit_mud(void) { DESCRIPTOR_DATA *d, *d_next; logf("Normal program termination..."); for (d = descriptor_first; d != NULL; d = d_next) { d_next = d->next; d_write(d, "\n\rNormal program termination...\n\r", 0); if (CH(d) != NULL) { save_char_obj(CH(d)); d_write(d, "\n\rSaving, and disconnecting....\n\r", 0); } d->outtop = 0; close_socket(d); } cleanup_mud(); } void set_signals(void) { #if defined(WIN32) signal(SIGINT, halt_mud); signal(SIGILL, halt_mud); signal(SIGFPE, halt_mud); signal(SIGSEGV, halt_mud); signal(SIGTERM, halt_mud); signal(SIGABRT, halt_mud); #else struct sigaction sigact; sigact.sa_handler = SIG_IGN; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigaction(SIGPIPE, &sigact, NULL); sigaction(SIGCHLD, &sigact, NULL); sigaction(SIGHUP, &sigact, NULL); sigact.sa_handler = halt_mud; sigact.sa_flags = SA_NODEFER; sigaction(SIGINT, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGILL, &sigact, NULL); sigaction(SIGFPE, &sigact, NULL); sigaction(SIGSEGV, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); sigaction(SIGBUS, &sigact, NULL); sigaction(SIGUSR1, &sigact, NULL); sigaction(SIGUSR2, &sigact, NULL); sigaction(SIGABRT, &sigact, NULL); sigact.sa_handler = sigalarm; #ifndef NO_VTIMER sigaction(SIGVTALRM, &sigact, NULL); /* Virtual Timer */ vtimer.it_interval.tv_sec = 60; vtimer.it_interval.tv_usec = 0; set_vtimer(-1); #else sigaction(SIGALRM, &sigact, NULL); alarm(300); #endif #endif atexit(exit_mud); crash_info.desc = NULL; crash_info.shrt_cmd[0] = '\0'; crash_info.long_cmd[0] = '\0'; crash_info.cmd_status = 6; crash_info.crashed = 0; crash_info.looping = FALSE; log_string("Signals Initialized."); } CH_CMD(do_crash) { char arg[MIL]; argument = one_argument(argument, arg); if (ch->level < MAX_LEVEL) { chprintln(ch, "You don't have enough security to use this command."); return; } if (IS_NULLSTR(arg)) { chprintln(ch, "Syntax: crash confirm - send a SIGSEGV to the mud."); chprintln(ch, " : crash loop - start an infinite loop."); chprintln(ch, " : crash abort - do an abnormal program termination."); chprintln(ch, " : crash exit - do a normal program termination."); return; } if (!str_cmp(arg, "loop")) { for (;;); return; } else if (!str_cmp(arg, "confirm")) { raise(SIGSEGV); return; } else if (!str_cmp(arg, "abort")) { abort(); return; } else if (!str_cmp(arg, "exit")) { exit(atoi(argument)); return; } else { do_crash(ch, ""); return; } }