asgard/
asgard/.settings/
asgard/area/
asgard/data/clans/
asgard/data/clans/history/
asgard/data/rosters/
asgard/src/notice/
/*
 * copyover.c
 *
 *  Created on: 9 Feb 2011
 *      Author: Nico
 *  Cleaner copyover/crash code.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

#include "merc.h"
#include "recycle.h"
#include "newclan.h"

// Externals.
extern const char compress_will[];
int init_socket( int port );
bool process_output(DESCRIPTOR_DATA *d, bool fPrompt);
bool write_to_descriptor( DESCRIPTOR_DATA *d, char *txt, int length );
bool write_to_descriptor_2(int desc, char *txt, int length);
DECLARE_DO_FUN(do_look);
void shutdown_system(void);

// File to store hotboot data.
#define HOTBOOT_FILE		"hotboot.dat"

int port		= 4444;
bool hotboot	= FALSE;
int control		= 0;

// Counters for last crash time, as well as number of crashes.
static time_t last_crash = 0;
static int crash_count = 0;
static bool in_copyover = FALSE;
// If there are more than 10 crashes in quick succession, exit.
#define CRASH_INTERVAL		2
#define MAX_CRASHES			5

// Resets signal handlers to default system handlers.
static void unlink_signal_handlers() {
	struct sigaction default_action;

	// Default system action
    default_action.sa_handler = SIG_DFL;
    sigemptyset(&default_action.sa_mask);
    default_action.sa_flags = 0;

    sigaction(SIGQUIT, &default_action, NULL );
    sigaction(SIGILL, &default_action, NULL );
    sigaction(SIGFPE, &default_action, NULL );
    sigaction(SIGSEGV, &default_action, NULL );
    sigaction(SIGTERM, &default_action, NULL );
    sigaction(SIGBUS, &default_action, NULL );
    sigaction(SIGABRT, &default_action, NULL );
    sigaction(SIGPIPE, &default_action, NULL );
    sigaction(SIGHUP, &default_action, NULL );
    sigaction(SIGINT, &default_action, NULL );
}

// Perform a hotboot, indicating whether its a result of a crash or not.
void perform_hotboot(int state) {
	FILE *fp;
	bool save_stuff = FALSE;

	// state = 0, NORMAL
	// state = 1, CRASHED
	// state = 2, VALGRIND 

	// Only attempt to save stuff if this isn't the second time.
	if (!in_copyover) {
		save_stuff = TRUE;
		in_copyover = TRUE;
	} else {
		// Unlink signal handlers if this is take 2.
		unlink_signal_handlers();
	}

	if ((state == CRASHED) && (fp = fopen(LAST_COMMAND, "a")) != NULL) {
		in_copyover = TRUE;
		fprintf(fp, "Last Command: %s\n", last_command);
		fclose(fp);
	}

	if ((fp = fopen(HOTBOOT_FILE, "w")) == NULL) {
		printf_system("hotboot: Could not open " HOTBOOT_FILE " to dump game state.");
		perror("hotboot file " HOTBOOT_FILE);
		return;
	}

	// Reset auctions.
	AUCTION_DATA *auc;
	for (auc = auction_list; auc != NULL; auc = auc->next) {
		if (auc->item != NULL)
			obj_to_char(auc->item, auc->owner);

		if (auc->high_bidder != NULL)
			add_cost(auc->high_bidder, auc->bid_amount, auc->bid_type);
	}

	// Close arena.
	arena = FIGHT_LOCK;

	CHAR_DATA *ch;
	char buf[MAX_STRING_LENGTH];

	if (state == CRASHED)
		strcpy(buf, "\r\n{Y***{x " GAME_COL_NAME " {Ris crashing! Attemping recovery! {Y***{x\r\n");
	else
		strcpy(buf, "\r\n{Y***{x " GAME_COL_NAME " {Wis hotbooting {Y***{x\r\n");

	// Save and tell players what is happening, drop crappy sockets.
	for (ch = char_list; ch != NULL; ch = ch->next) {
		// Separate descriptor check from NPC check in case of switched characters.
		if (ch->desc != NULL) {
			ch->desc->wasCompressed = ch->desc->out_compress != NULL;
			if (compressEnd(ch->desc)) {
				send_to_char(buf, ch);
				if (!process_output(ch->desc, FALSE))
					close_socket(ch->desc);
			} else
				close_socket(ch->desc);
		}
		if (save_stuff && !IS_NPC(ch))
			save_char_obj(ch);
	}

	if (save_stuff) {
		// Save clan/roster data.
		save_all_clans();
		save_all_rosters();
	}

	// Save descriptor data.
	DESCRIPTOR_DATA *d, *d_next;
	for (d = descriptor_list; d != NULL; d = d_next) {
		d_next = d->next;
		if ((ch = d->character) != NULL && !IS_NPC(ch)) {
			fprintf(fp, "%d %d %.511s %.511s %.511s\n",
				d->descriptor, d->wasCompressed,
				ch->pload && ch->pload[0] != '\0' ? ch->pload : ch->name,
				d->host, d->hostip);
			d->wasCompressed = 0; // Ensure the flag is reset to 0.
		} else {
			write_to_descriptor(d, "\r\nSorry, we are rebooting, please reconnect in a few seconds.\r\n", 0);
			close_socket(d);
		}
	}

	// End marker.
	fprintf(fp, "-1\n");
	fclose(fp);

	// Do an execute.
	char port_arg[512], fd_arg[512], cc_arg[512] = "", ct_arg[512] = "";
	sprintf(port_arg, "%d", port);
	sprintf(fd_arg, "%d", control);
	if (state == CRASHED) {
		sprintf(cc_arg, "%d", (current_time-last_crash > CRASH_INTERVAL*2 ? 0 : crash_count)+1);
		sprintf(ct_arg, "%ld", current_time);
	}
	if ((state == NORMAL) || (state == CRASHED))
		execl(GAME_EXE, GAME_EXE, port_arg, fd_arg, cc_arg, ct_arg, (char *) NULL);
	else
		execl("./", VALGRIND1, GAME_EXE, port_arg, fd_arg, cc_arg, ct_arg, (char *) NULL);

	// Failed to execute.
	printf_system("hotboot: execl failed.");
	perror("hotboot: execl failed");
	if (state == CRASHED)	// Exit out if it was a crash so things don't break further.
		exit(EXIT_FAILURE);
}

// Prints the usage of program.
void usage() {
	printf_system("Usage: " GAME_EXE " <port>\r\n");
	exit(1);
}

// Check the program arguments to whether we've booted up from a hotboot.
void setup_from_args(int argc, char **argv) {
	switch (argc) {
	default:
		// Try to continue as normal.
	case 5:
		if (is_number(argv[3]))
			crash_count = atoi(argv[3]);
		if (is_number(argv[4]))
			last_crash = atol(argv[4]);
		// Prevent repeating crashes.
		if (current_time - last_crash < CRASH_INTERVAL) {
			if (crash_count >= MAX_CRASHES) {
				printf_system("Game crashed too many times, exiting.\r\n");
				exit(1);
			} else if (crash_count >= (MAX_CRASHES-1)) {
				printf_system("Game crashed many times. Attempting clean startup.\r\n");
				int i;
				for (i = 3; i < 1024; i++)
					close(i);
				goto clean_start;
			}
		}
	case 3:
		if (!is_number(argv[2]) || (control = atoi(argv[2])) < 0)
			printf_system("Invalid recovery FD: (%s). Attempting clean startup.\r\n", argv[2]);
		else {
			printf_system("Recovering from hotboot.\r\n");
			hotboot = TRUE;
		}
	case 2:
clean_start:
		if (!is_number(argv[1]) || (port = atoi(argv[1])) < 1024 || port >= 65536)
			usage();
		break;
	case 0:
		printf_system("argc == 0? Isn't that impossible? Trying to continue anyway.");
	case 1:
		break;	// Do nothing, use default settings.
	}
	if (!hotboot)
		control = init_socket(port);
}

// Load old connected descriptors.
void recover_hotboot_state() {
	if (!hotboot)
		return;

	FILE *fp;

	if ((fp = fopen(HOTBOOT_FILE, "r")) == NULL) {
		printf_system("hotboot: Could not open " HOTBOOT_FILE " to restore game state.");
		perror("hotboot file " HOTBOOT_FILE);
		return;
	}
	// Unlink the file so others can't find it just in case.
	unlink(HOTBOOT_FILE);

	char name[512], host[512], hostip[512];
	DESCRIPTOR_DATA *d;
	int desc, wasCompressed;

	do {
		fscanf(fp, "%d %d %s %s %s\n", &desc, &wasCompressed, name, host, hostip);

		// End marker.
		if (desc < 0)
			break;

		// Check a write is error-free, close if not.
		if (!write_to_descriptor_2(desc, "\n\rRestoring from Hot-Boot...\n\r", 0)) {
			close(desc);
			continue;
		}

		// Create descriptor and populate.
		d = new_descriptor();
		init_descriptor(d, desc);
		d->host = str_dup(host);
		d->hostip = str_dup(hostip);
		d->connected = CON_COPYOVER_RECOVER;
		// Add to descriptor list.
		d->next = descriptor_list;
		descriptor_list = d;

		// Load up the character.
		if (!load_char_obj(d, name)) {
			// No character found.
			write_to_descriptor(
				d, "\n\rSomehow, your character was lost in the copyover.Sorry.\n\r", 0);
			close_socket(d);
			continue;
		}

		// Insert character into character list.
		d->character->next = char_list;
		char_list = d->character;
		d->character->pcdata->socket = d->host;

		// Make sure the room the character is in exists before putting them in it.
		if (!d->character->in_room)
			d->character->in_room = get_room_index(ROOM_VNUM_TEMPLE);
		char_to_room(d->character, d->character->in_room);

		// We're now playing.
		d->connected = CON_PLAYING;

		// Don't forget about pets.
		if (d->character->pet != NULL)
			char_to_room(d->character->pet, d->character->in_room);

		write_to_descriptor(d, "\n\rHot-Boot recovery complete.\n\r", 0);

		do_look(d->character, "auto");
		act("$n steps out of limbo!", d->character, NULL, NULL, TO_ROOM);

		// Try to enable compression again.
//		write_to_buffer(d, compress_will, 0);
		if (wasCompressed)
			compressStart(d);
	} while (1);

	fclose(fp);
}

// Do a copyover.
void do_copyover(CHAR_DATA *ch, char *argument) {
	send_to_char("Performing copyover.\r\n", ch);

	// Check the game exe exists.
	FILE *fp;
	if ((fp = fopen(GAME_EXE, "r")) == NULL) {
		send_to_char("Game executable does not exist! Aborting.\r\n", ch);
		return;
	}
	fclose(fp);

	perform_hotboot(NORMAL);
	send_to_char("Copyover {RFAILED{x.\r\n", ch);
}

void crash_handler(int signal) {
	printf_system("*** CRASH *** Signal: %d, Last command: %s.", signal, last_command);

	wait(NULL);

	pid_t forkpid;

	// Fork into child process and wait for it to die before hotbooting.
	if ((forkpid = fork()) > 0) {
		waitpid(forkpid, NULL, WNOHANG | WUNTRACED );
		perform_hotboot(CRASHED);
		exit(0);
	} else if (forkpid < 0)
		printf_system("crash_handler: could not fork.");

	// Closes all of its file descriptors.
	int i;
	for (i = 1024; i >= 0; i--)
		close(i);

	// And calls the default crash handler.
	struct sigaction default_action;
	default_action.sa_handler = SIG_DFL;
	sigaction(signal, &default_action, NULL );
	raise(signal);
	exit(EXIT_FAILURE);	// Exit just in case.
}

void shutdown_handler(int signal) {
	printf_system("*** Received signal %d - terminating gracefully.", signal);

	// Save players.
	CHAR_DATA *ch;
	for (ch = char_list; ch != NULL; ch = ch->next)
		if (!IS_NPC(ch))
			save_char_obj(ch);

	// Save clan/roster data.
	save_all_clans();
	save_all_rosters();

	// Shutdown. Note: this will still cause the mud to be resurrected by the
	// startup script.
	shutdown_system();
}

// Set up signal handling.
void setup_signal_handlers() {
	struct sigaction crash_action, ignore_action, shutdown_action;

	// Set up the crash handler.
	crash_action.sa_handler = crash_handler;
	sigemptyset(&crash_action.sa_mask);
	crash_action.sa_flags = SA_NOMASK;
	sigaction(SIGQUIT, &crash_action, NULL );
	sigaction(SIGILL, &crash_action, NULL );
	sigaction(SIGFPE, &crash_action, NULL );
	sigaction(SIGSEGV, &crash_action, NULL );
	sigaction(SIGBUS, &crash_action, NULL );
	sigaction(SIGABRT, &crash_action, NULL );

	// Set up the ignore handler.
	ignore_action.sa_handler = SIG_IGN;
	sigemptyset(&ignore_action.sa_mask);
	ignore_action.sa_flags = 0;
	sigaction(SIGPIPE, &ignore_action, NULL );
	sigaction(SIGHUP, &ignore_action, NULL );
	sigaction(SIGINT, &ignore_action, NULL );

	// Graceful shutdown on SIGTERM.
	shutdown_action.sa_handler = shutdown_handler;
	sigemptyset(&crash_action.sa_mask);
	crash_action.sa_flags = SA_NOMASK;
	sigaction(SIGTERM, &shutdown_action, NULL );
}

void do_copyove(CHAR_DATA *ch, char *argument) {
	if (IS_IMMORTAL(ch))
		send_to_char("To \"copyover\", type the whole thing.\r\n", ch);
}