1stMud4.5.3/
1stMud4.5.3/backup/
1stMud4.5.3/bin/
1stMud4.5.3/bin/extras/
1stMud4.5.3/data/i3/
1stMud4.5.3/doc/1stMud/
1stMud4.5.3/doc/Diku/
1stMud4.5.3/doc/MPDocs/
1stMud4.5.3/doc/Rom/
1stMud4.5.3/notes/
/**************************************************************************
*  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;
}