/* 
 *   Creation Date: <2002/06/27 22:47:46 samuel>
 *   Time-stamp: <2004/01/14 21:40:37 samuel>
 *   
 *	<main.c>
 *	
 *	
 *   
 *   Copyright (C) 1997, 1999-2004 Samuel Rydh (samuel@ibrium.se)
 *   
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation
 *   
 */

#include "mol_config.h"

#include <unistd.h>
#include <signal.h>
#ifdef __linux__
#include <ucontext.h>
#endif
#include "memory.h"
#include "wrapper.h"
#include "promif.h"
#include "io.h"
#include "res_manager.h" 
#include "debugger.h"
#include "thread.h"
#include "timer.h"
#include "version.h"
#include "os_interface.h"
#include "booter.h"
#include "async.h"
#include "session.h"
#include "keycodes.h"
#include "molcpu.h"
#include "rvec.h"
#include "syscall.h"
#include "../drivers/include/console.h"

static void	exit_hook( void );

/* GLOBALS */
int 		in_security_mode;
ulong		g_session_magic;
mac_regs_t	*mregs = NULL;


/************************************************************************/
/*	lockfile creation						*/
/************************************************************************/

static int
remove_lockfile( void )
{
	char buf[64], *name = get_lockfile();
	int fd, del=0;
	pid_t pid;
	FILE *f;
	
	if( (f=fopen(name, "r")) ) {
		del = (fscanf(f, "%d", &pid) != 1 || pid == getpid());
		fclose(f);
		if( !del ) {
			sprintf( buf, "/proc/%d", pid );
			if( (fd=open(buf, O_RDONLY)) >= 0 ) {
				close( fd );
				return 1;
			}
			if( errno != ENOENT )
				return 1;
			printm("Removing stale lockfile %s\n", name );
		}
		unlink( name );
	}
	return 0;
}

static int
create_lockfile( void )
{
	char *name = get_lockfile();
	FILE *f;

	/* printm("Session %d. Lockfile: %s\n", g_session_id, name ); */
	
 	if( (f=fopen( name, "r")) != NULL ) {
		fclose(f);
		if( remove_lockfile() ) {
			printm("MOL lockfile detected! Make sure MOL isn't running\n"
			       "before deleting the lockfile %s\n", get_lockfile() );
			return 1;
		}
	}
	if( (f=fopen( name, "w+") ) == NULL ) {
		printm("Warning: could not create the lockfile %s\n", name);
	} else {
		fprintf(f,"%d\n", getpid() );
		fclose( f );
	}
	return 0;
}


/************************************************************************/
/*	backtrace							*/
/************************************************************************/

#ifdef __linux__
static void
print_btrace_sym( ulong addr )
{
	char *s, ch, buf[80];
	ulong v, v2=0;
	int found=0;
	FILE *f=NULL;

	if( (s=get_filename_res("molsyms")) )
		f = fopen(s, "ro");

	buf[0]=0;
	while( f && fscanf( f, "%lx %c ", &v, &ch ) == 2 ) {
		if( v <= addr ) {
			v2 = v;
			fscanf( f, "%79s\n", buf );
			continue;
		}
		if( buf[0] ) {
			printm("%s + 0x%lx\n", buf, addr - v2 );
			found=1;
		}
		break;
	}
	if( !found )
		printm( "0x%08lx\n", addr );
	if( f )
		fclose(f);
}

static void
backtrace( void )
{
	ulong oldsp, sp, base;
	asm volatile("mr %0,1" : "=r" (sp) : );
	base = sp;
	printm("***** Backtrace *****\n");
	do {
		printm( "   %08lx: ", sp );
		print_btrace_sym( *((ulong*)sp+1) );
		oldsp = sp;
		sp = *(ulong*)sp;
	} while( sp > oldsp && sp < base + 0x10000 );
}
#endif

/************************************************************************/
/*	signal handlers							*/
/************************************************************************/

static void 
#ifdef __linux__
signal_handler( int sig_num, siginfo_t *sinfo, struct ucontext *puc, ulong rt_sf )
#else
signal_handler( int sig_num )
#endif
{
	static time_t warntime = 0;
	static int once = 0;
	time_t cur;
	
	if( sig_num != SIGINT && sig_num != SIGTRAP ) {
		aprint("***** SIGNAL %d [%s] in thread %s *****\n", 
		       sig_num, (sig_num < NSIG)? sys_siglist[sig_num]:"?", get_thread_name() );
	}
	switch( sig_num ) {
	case SIGINT:
		if( !is_main_thread() )
			return;

		/* backtrace(); */
		time( &cur );
		if( !warntime || cur-warntime>1 ) {
			aprint("Signal INT\nOne more to kill emulator\n");
			warntime = cur;
			/* break emulation */
			stop_emulation();
			return;
		}
		if( !once++ ) {
			quit_emulation();
			return;
		}
		break;
	case SIGPIPE:
		/* Usually the debugger... */
		return;
		break;
#ifdef __linux__
	case SIGHUP:
		aprint("HUP: _pid %d, _uid %d\n", sinfo->si_pid, sinfo->si_uid );
		// if( !is_main_thread() )
		//	return;
		break;
	case SIGTRAP: 
	case SIGSEGV:
	case SIGILL:
	case SIGBUS: {
		struct pt_regs *regs;
		aprint("   si_signo = %d, si_errno %d, si_code %08X, si_addr %p\n", 
		       sinfo->si_signo, sinfo->si_errno, sinfo->si_code, sinfo->si_addr );
		aprint("   Last RVEC: 0x%lx (%ld), last OSI: %ld, mac_nip %08lX\n", 
		       mregs->dbg_last_rvec, (mregs->dbg_last_rvec & RVEC_MASK), mregs->dbg_last_osi,
		       mregs->nip );
#ifdef __ppc__
#ifdef UCCONTEXT_HAS_GREGS
		regs = (struct pt_regs *)puc->uc_mcontext.gregs;
#else
		regs = puc->uc_mcontext.regs;
#endif
		aprint("   NIP ");
		print_btrace_sym( regs->nip );
#endif /* __ppc__ */
		backtrace();
		if( sig_num == SIGTRAP )
			return;
		break;
	}
#endif /* __linux__ */
	default:
		break;
	}
	exit(1);
}

static void 
set_signals( void )
{
	struct sigaction act;
	sigset_t set;
	
	sigemptyset( &set );
	sigaddset( &set, SIGCHLD );
	pthread_sigmask( SIG_BLOCK, &set, NULL );

	memset( &act, 0, sizeof(act) );
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_RESTART;
#ifdef __linux__
	act.sa_sigaction = (void*)signal_handler;
	act.sa_flags |= SA_SIGINFO;
	act.sa_restorer = NULL;
#else
	act.sa_handler = (void*)signal_handler;
#endif      
	sigaction( SIGINT, &act, NULL );
	sigaction( SIGHUP, &act, NULL );
	sigaction( SIGILL, &act, NULL );
	sigaction( SIGBUS, &act, NULL );
	sigaction( SIGSEGV, &act, NULL );
	sigaction( SIGTERM, &act, NULL );
	sigaction( SIGTRAP, &act, NULL );
#ifdef __linux__
	sigaction( SIGSTKFLT, &act, NULL );
#endif
	sigaction( SIGTRAP, &act, NULL );
	sigaction( SIGPIPE, &act, NULL );
	/* NOTE: SIGUSR1 and SIGUSR2 are reserved by pthread (still true?). */

	sigaction( SIGCHLD, &act, NULL );
}


/************************************************************************/
/*	init / cleanup							*/
/************************************************************************/

static void
authenticate( void )
{
	char *s1, *s, *user = getlogin();
	int i, j, deny_all=0;

	s1 = NULL;
	for( i=0; get_str_res_ind( "allow", i, 0 ) ; i++ )
		for( j=0; (s1=get_str_res_ind( "allow", i, j )) && strcmp(s1,user) ; j++ )
			;
	s = NULL;
	for( i=0; get_str_res_ind( "deny", i, 0 ) ; i++ )
		for( j=0; (s=get_str_res_ind( "deny", i, j )) && strcmp(s,user) ; j++ )
			deny_all |= !strcasecmp( s, "all" );

	if( s || (!s1 && deny_all) ) {
		printm("User %s is not allowed to run MOL.\n", user );
		exit(1);
	}
}

static void
load_mods( void )
{
#ifdef __linux__
	char *s, buf[80], script[256];
	int i, sheep, tun, tap, allow_mismatch;

	allow_mismatch = (get_bool_res("allow_kver_mismatch") == 1);

	sheep = tun = tap = 0;
	for( i=0 ; get_str_res_ind("netdev",i,0) ; i++ ) {
		if( !(s=get_str_res_ind("netdev", i, 1)) )
			continue;
		sheep |= !!strstr( s, "sheep" );
		tap |= !!strstr( s, "tap" );
		tun |= !!strstr( s, "tun" );
	}
	sprintf( buf, "%d %s %s %s", allow_mismatch,
		 sheep? "sheep" : "", tun? "tun" : "", tap? "tap" : "" );
#ifndef __MPC107__
	strncpy0( script, get_libdir(), sizeof(script) );
	strncat0( script, "/bin/modload", sizeof(script) );
	if( script_exec(script, get_libdir(), buf) )
		exit(1);	
	if( get_bool_res("load_only") == 1 )
		exit(0);
#endif
#endif
}

static volatile int tty_signo;

static void
tty_sighandler( int signo )
{
	switch( signo ) {
	case SIGCHLD:
		exit(0);
		break;
	}
	tty_signo = signo;
}

static void
tty_loop( void )
{
	sigset_t set;
	pid_t p;
	char *s;

	if( !(s=get_lockfile()) )
		exit(1);
	s = strdup( s );
	res_manager_cleanup();

	signal( SIGCHLD, tty_sighandler );
	signal( SIGINT, tty_sighandler );
	signal( SIGTERM, tty_sighandler );
	signal( SIGHUP, tty_sighandler );

	sigemptyset( &set );
	sigaddset( &set, SIGCHLD );
	sigaddset( &set, SIGINT );
	sigaddset( &set, SIGTERM );
	sigaddset( &set, SIGHUP );
	sigprocmask( SIG_UNBLOCK, &set, NULL );

	for( ;; ) {
		FILE *f;

		while( !tty_signo )
			sleep(120);
		if( (f=fopen(s, "r")) ) {
			if( fscanf(f, "%d", &p) == 1 )
				kill( p, tty_signo );
			fclose( f );
			tty_signo = 0;
			continue;
		}
		exit(1);
	}
}

static void
do_clearenv( void )
{
	/* both DISPLAY and HOME are needed in order to get X11 working over ssh */
	const char *preserve[] = { "DISPLAY", "HOME" };
	char *s, *saved[ sizeof(preserve)/sizeof(preserve[0]) ];
	int i;

	for( i=0; i<sizeof(preserve)/sizeof(preserve[0]); i++ ) {
		if( (s=getenv(preserve[i])) )
			s = strdup( s );
		saved[i] = s;
	}
	clearenv();
	for( i=0; i<sizeof(preserve)/sizeof(preserve[0]); i++ ) {
		if( saved[i] ) {
			setenv( preserve[i], saved[i], 1 );
			free( saved[i] );
		}
	}
}


int
main( int argc, char **argv )
{
	printm("Mac-on-Linux %s [%s]\n", MOL_RELEASE, MOL_BUILD_DATE );
	printm("Copyright (C) 1997-2004 Samuel Rydh\n");

	/* If we are not root, disable privileged features. */
	in_security_mode = (getuid() != 0);

	/* clear most environmental variables (for extra security) */
	do_clearenv();

	res_manager_init( 1, argc, argv );	/* sets g_session_id */
	printm("Starting MOL session %d\n", g_session_id );
	authenticate();

	if( seteuid(0) ) {
		fprintf( stderr,"Mac-on-Linux must be setuid root!\n");
		exit(1);
	}
	load_mods();

	/* 
	 * It is necessary to run in a new session (for the console/FB stuff).
	 * The fork is necessary since setsid() usually fails otherwise.
	 */
	if( fork() ) {
		if( get_bool_res("detach_tty") != 1 && (isatty(0) || isatty(1) || isatty(2)) )
			tty_loop();
		return 0;
	}
	/* There is a race if we exit immediately (and if the lockfile is present);
	 * the SIGCHLD signal might be lost. It is not a serious problem but this
	 * removes it for all practical purposes.
	 */
	usleep(1000);

	if( setsid() < 0 )
		perrorm("setsid failed!\n");

	wrapper_init();

	/* initialize session */
	if( create_lockfile() )
		exit(1);
	if( open_session() ) {
		remove_lockfile();
		exit(1);
	}
	atexit( exit_hook );

	open_logfile( get_filename_res("logfile") );
	determine_boot_method();

	/* we must be careful with dependencies here. */
	set_signals();
	threadpool_init();		/* Provides threads */
	async_init();			/* Provides async IO capabilities */
	debugger_init();		/* Provides logging and debugger */
	mainloop_init();		/* Provides set_rvector */
	os_interface_init();		/* Provides register_osi_call */

	session_init();
	booter_init();			/* Provides gPE.xxx */

	promif_init();			/* Most drivers depend on this */
	timer_init();			/* Provides timer services */

	mem_init();			/* Memory subsystem */
	molcpu_init();			/* CPU / emulation */
	ioports_init();			/* All drivers */
	booter_startup();		/* Boot loader */

	session_is_loaded();

	if( !is_main_thread() )
		printm("*** is_main_thread not working? ***\n");

	mainloop_start();

	/* cleanup through exit_hook */
	return 0;
}

static void 
do_cleanup( void ) 
{
	printm("cleaning up...\n");
#ifdef __linux__
	console_make_safe();
#endif
	ioports_cleanup();
	timer_cleanup();
	molcpu_cleanup();
	booter_cleanup();
	mem_cleanup();
	promif_cleanup();

	debugger_cleanup();
	async_cleanup();
	threadpool_cleanup();
	session_cleanup();

	mainloop_cleanup();
	os_interface_cleanup();

	close_session();
	wrapper_cleanup();

	close_logfile();
	remove_lockfile();
	res_manager_cleanup();
}

static void 
exit_hook( void )
{
	if( !is_main_thread() ) {
		/* Umm... one of the sub-threads has probably segfaulted.
		 * We better kill -9 the main thread and then tries cleanup
		 * things from this thread.
		 */ 
		printm("Exiting due to a crashed thread\n");
#ifdef __linux__
		pthread_kill( get_main_th(), 9 );
#endif
	}

	/* reset signal handlers */
	signal( SIGILL, SIG_DFL );
	signal( SIGSEGV, SIG_DFL );
	signal( SIGINT, SIG_DFL );
	signal( SIGTERM, SIG_DFL );
#ifdef __linux__
	signal( SIGSTKFLT, SIG_DFL );
#endif	
	do_cleanup();

	printm( "DONE\n" );
	/* execlp("/etc/mol.sh", "mol.sh", "stop", NULL ); */
}
