/************************************************************************** * 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" Proto(void log_string_flush, (void)); #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_status_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_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.logline[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.logline, 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); } } time_t last_crash; void crash_log(const char *msg) { FILE *fp; char buf[MAX_STRING_LENGTH]; char buf2[MAX_STRING_LENGTH]; struct stat fst; #ifdef HAVE_GDB char gdb[MPL]; #endif if (crash_info.crashed > 1) return; #ifdef HAVE_GDB gdb[0] = NUL; #endif sprintf(buf, "%s/core", CWDIR); if (stat(buf, &fst) != -1) { #ifdef HAVE_GDB sprintf(buf2, "gdb -batch %s %s", EXE_FILE, buf); if ((fp = popen(buf2, "r")) != NULL) { fread(gdb, MPL - 1000, 1, fp); pclose(fp); } #endif sprintf(buf2, "mv -f %s %s/core.%d", buf, BIN_DIR, getpid()); system(buf2); } if (last_crash > 0 && (getcurrenttime() - last_crash) < (MINUTE * 2)) return; if ((fp = fopen(CRASH_FILE, "w")) != NULL) { fprintf(fp, TIME_T_FMT "\n", current_time); 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 "Log: %s" NEWLINE "Details: %s" NEWLINE, sig, #ifdef HAVE_STRSIGNAL strsignal(sig), #endif crash_info.logline, crash_status_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; EXTERN FILE *current_logfile_descriptor; #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 #ifndef DISABLE_MYSQL db_stop(); #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)); log_string_flush(); if (current_logfile_descriptor) fclose(current_logfile_descriptor); } 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 "Log: %s" NEWLINE "Details: %s" NEWLINE, crash_info.logline, crash_status_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.logline[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; }