1stMUD4.0/bin/
1stMUD4.0/doc/MPDocs/
1stMUD4.0/player/
1stMUD4.0/win32/
1stMUD4.0/win32/rom/
/**************************************************************************
*  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(&current_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;
	}
}