/**************************************************************************
* 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;
}
}