/** \file signal.c

The library for various signal related issues

*/

#include "config.h"


#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>

#include "common.h"
#include "fallback.h"
#include "util.h"

#include "wutil.h"
#include "signal.h"
#include "event.h"
#include "reader.h"
#include "proc.h"
#include "translate.h"

/**
   Struct describing an entry for the lookup table used to convert
   between signal names and signal ids, etc.
*/
struct lookup_entry
{
	/**
	   Signal id
	*/
	int signal;
	/**
	   Signal name
	*/
	const wchar_t *name;
	/**
	   Signal description
	*/
	const wchar_t *desc;	
};

/**
   The number of signal blocks in place. Increased by signal_block, decreased by signal_unblock.
*/
static int block_count=0;


/**
   Lookup table used to convert between signal names and signal ids,
   etc.
*/
const static struct lookup_entry lookup[] =
{
#ifdef SIGHUP
	{
		SIGHUP, 
		L"SIGHUP",
		N_( L"Terminal hung up" )
	}
	,
#endif
#ifdef SIGINT
	{
		SIGINT,
		L"SIGINT",
		N_( L"Quit request from job control (^C)" )
	}
	,
#endif
#ifdef SIGQUIT
	{
		SIGQUIT,
		L"SIGQUIT",
		N_( L"Quit request from job control with core dump (^\\)" )
	}
	,
#endif
#ifdef SIGILL
	{
		SIGILL,
		L"SIGILL",
		N_( L"Illegal instruction" )
	}
	,
#endif
#ifdef SIGTRAP
	{
		SIGTRAP, 
		L"SIGTRAP",
		N_( L"Trace or breakpoint trap" )
	}
	,
#endif
#ifdef SIGABRT
	{
		SIGABRT,
		L"SIGABRT",
		N_( L"Abort" )
	}
	,
#endif
#ifdef SIGBUS
	{
		SIGBUS, 
		L"SIGBUS",
		N_( L"Misaligned address error" )
	}
	,
#endif
#ifdef SIGFPE
	{
		SIGFPE, 
		L"SIGFPE",
		N_( L"Floating point exception" )
	}
	,
#endif
#ifdef SIGKILL
	{
		SIGKILL, 
		L"SIGKILL",
		N_( L"Forced quit" )
	}
	,
#endif
#ifdef SIGUSR1
	{
		SIGUSR1,
		L"SIGUSR1",
		N_( L"User defined signal 1" )
	}
	,
#endif
#ifdef SIGUSR2
	{
		SIGUSR2, L"SIGUSR2",
		N_( L"User defined signal 2" )
	}
	,
#endif
#ifdef SIGSEGV
	{
		SIGSEGV, 
		L"SIGSEGV",
		N_( L"Address boundary error" )
	}
	,
#endif
#ifdef SIGPIPE
	{
		SIGPIPE,
		L"SIGPIPE",
		N_( L"Broken pipe" )
	}
	,
#endif
#ifdef SIGALRM
	{
		SIGALRM, 
		L"SIGALRM",
		N_( L"Timer expired" )
	}
	,
#endif
#ifdef SIGTERM
	{
		SIGTERM,
		L"SIGTERM",
		N_( L"Polite quit request" )
	}
	,
#endif
#ifdef SIGCHLD
	{
		SIGCHLD,
		L"SIGCHLD",
		N_( L"Child process status changed" )
	}
	,
#endif
#ifdef SIGCONT
	{
		SIGCONT,
		L"SIGCONT",
		N_( L"Continue previously stopped process" )
	}
	,
#endif
#ifdef SIGSTOP
	{
		SIGSTOP,
		L"SIGSTOP",
		N_( L"Forced stop" )
	}
	,
#endif
#ifdef SIGTSTP
	{
		SIGTSTP,
		L"SIGTSTP",
		N_( L"Stop request from job control (^Z)" )
	}
	,
#endif
#ifdef SIGTTIN
	{
		SIGTTIN, 
		L"SIGTTIN",
		N_( L"Stop from terminal input" )
	}
	,
#endif
#ifdef SIGTTOU
	{
		SIGTTOU,
		L"SIGTTOU",
		N_( L"Stop from terminal output" )
	}
	,
#endif
#ifdef SIGURG
	{
		SIGURG, 
		L"SIGURG",
		N_( L"Urgent socket condition" )
	}
	,
#endif
#ifdef SIGXCPY
	{
		SIGXCPU,
		L"SIGXCPU",
		N_( L"CPU time limit exceeded" )
	}
	,
#endif
#ifdef SIGXFSZ
	{
		SIGXFSZ, 
		L"SIGXFSZ",
		N_( L"File size limit exceeded" )
	}
	,
#endif
#ifdef SIGVTALRM
	{
		SIGVTALRM,
		L"SIGVTALRM",
		N_( L"Virtual timer expired" )
	}
	,
#endif
#ifdef SIGPROF
	{
		SIGPROF,
		L"SIGPROF",
		N_( L"Profiling timer expired" )
	}
	,
#endif
#ifdef SIGWINCH
	{
		SIGWINCH,
		L"SIGWINCH",
		N_( L"Window size change" )
	}
	,
#endif
#ifdef SIGWIND
	{
		SIGWIND,
		L"SIGWIND",
		N_( L"Window size change" )
	}
	,
#endif
#ifdef SIGWIO
	{
		SIGIO,
		L"SIGIO",
		N_( L"I/O on asynchronous file descriptor is possible" )
	}
	,
#endif
#ifdef SIGPWR
	{
		SIGPWR, 
		L"SIGPWR",
		N_( L"Power failure" )
	}
	,
#endif
#ifdef SIGSYS
	{
		SIGSYS, 
		L"SIGSYS",
		N_( L"Bad system call" )
	}
	,
#endif
#ifdef SIGINFO
	{
		SIGINFO,
		L"SIGINFO",
		N_( L"Information request" )
	}
	,
#endif
#ifdef SIGSTKFLT
	{
		SIGSTKFLT,
		L"SISTKFLT",
		N_( L"Stack fault" )
	}
	,
#endif
#ifdef SIGEMT
	{
		SIGEMT,
		L"SIGEMT",
		N_( L"Emulator trap" )
	}
	,
#endif
#ifdef SIGIOT
	{
		SIGIOT,
		L"SIGIOT",
		N_( L"Abort (Alias for SIGABRT)" )
	}
	,
#endif
#ifdef SIGUNUSED
	{
		SIGUNUSED,
		L"SIGUNUSED",
		N_( L"Unused signal" )
	}
	,
#endif
	{
		0,
		0,
		0
	}
}
	;


/**
   Test if \c name is a string describing the signal named \c canonical. 
*/
static int match_signal_name( const wchar_t *canonical, 
							  const wchar_t *name )
{
	if( wcsncasecmp( name, L"sig", 3 )==0)
		name +=3;

	return wcscasecmp( canonical+3,name ) == 0;	
}


int wcs2sig( const wchar_t *str )
{
	int i, res;
	wchar_t *end=0;
	
	for( i=0; lookup[i].desc ; i++ )
	{
		if( match_signal_name( lookup[i].name, str) )
		{
			return lookup[i].signal;
		}
	}
	errno=0;
	res = wcstol( str, &end, 10 );
	if( !errno && end && !*end )
		return res;
	
	return -1;	
}


const wchar_t *sig2wcs( int sig )
{
	int i;

	for( i=0; lookup[i].desc ; i++ )
	{
		if( lookup[i].signal == sig )
		{
			return lookup[i].name;
		}
	}

	return _(L"Unknown");
}

const wchar_t *signal_get_desc( int sig )
{
	int i;

	for( i=0; lookup[i].desc ; i++ )
	{
		if( lookup[i].signal == sig )
		{
			return _(lookup[i].desc);
		}
	}

	return _(L"Unknown");
}

/**
   Standard signal handler
*/
static void default_handler(int signal, siginfo_t *info, void *context)
{
	event_t e;

	e.type=EVENT_SIGNAL;
	e.param1.signal = signal;
	e.function_name=0;

	if( event_get( &e, 0 ) )
	{
		
		event_fire( &e );
	}
}

/**
   Respond to a winch signal by checking the terminal size
*/
static void handle_winch( int sig, siginfo_t *info, void *context )
{
	common_handle_winch( sig );	
	default_handler( sig, 0, 0 );	
}

/**
   Respond to a winch signal by checking the terminal size
*/
static void handle_hup( int sig, siginfo_t *info, void *context )
{
	event_t e;

	e.type=EVENT_SIGNAL;
	e.param1.signal = SIGHUP;
	e.function_name=0;

	if( event_get( &e, 0 ) )
	{
		default_handler( sig, 0, 0 );	
	}
	else
	{
		reader_exit( 1, 1 );
	}
	
}

/**
   Interactive mode ^C handler. Respond to int signal by setting
   interrupted-flag and stopping all loops and conditionals.
*/
static void handle_int( int sig, siginfo_t *info, void *context )
{
	reader_handle_int( sig );
	default_handler( sig, info, context);	
}

/**
   sigchld handler. Does notification and calls the handler in proc.c
*/
static void handle_chld( int sig, siginfo_t *info, void *context )
{
	job_handle_signal( sig, info, context );
	default_handler( sig, info, context);	
}

void signal_reset_handlers()
{
	int i;
	
	struct sigaction act;
	sigemptyset( & act.sa_mask );
	act.sa_flags=0;
	act.sa_handler=SIG_DFL;
	
	for( i=0; lookup[i].desc ; i++ )
	{
		sigaction( lookup[i].signal, &act, 0);
	}	
}


/**
   Sets appropriate signal handlers.
*/
void signal_set_handlers()
{
	struct sigaction act;

	if( is_interactive == -1 )
		return;
	
	sigemptyset( & act.sa_mask );
	act.sa_flags=SA_SIGINFO;
	act.sa_sigaction = &default_handler;
	
	/*
	  First reset everything to a use default_handler, a function
	  whose sole action is to fire of an event
	*/
	sigaction( SIGINT, &act, 0);
	sigaction( SIGQUIT, &act, 0);
	sigaction( SIGTSTP, &act, 0);
	sigaction( SIGTTIN, &act, 0);
	sigaction( SIGTTOU, &act, 0);
	sigaction( SIGCHLD, &act, 0);
	
	/*
	  Ignore sigpipe, it is generated if fishd dies, but we can
	  recover.
	*/
	sigaction( SIGPIPE, &act, 0);

	if( is_interactive )
	{
		/*
		   Interactive mode. Ignore interactive signals.  We are a
		   shell, we know whats best for the user. ;-)
		*/

		act.sa_handler=SIG_IGN;
		
		sigaction( SIGINT, &act, 0);
		sigaction( SIGQUIT, &act, 0);
		sigaction( SIGTSTP, &act, 0);
		sigaction( SIGTTIN, &act, 0);
		sigaction( SIGTTOU, &act, 0);

		act.sa_sigaction = &handle_int;
		act.sa_flags = SA_SIGINFO;
		if( sigaction( SIGINT, &act, 0) )
		{
			wperror( L"sigaction" );
			exit(1);
		}

		act.sa_sigaction = &handle_chld;
		act.sa_flags = SA_SIGINFO;
		if( sigaction( SIGCHLD, &act, 0) )
		{
			wperror( L"sigaction" );
			exit(1);
		}
		
		act.sa_flags = SA_SIGINFO;
		act.sa_sigaction= &handle_winch;
		if( sigaction( SIGWINCH, &act, 0 ) )
		{
			wperror( L"sigaction" );
			exit(1);
		}

		act.sa_flags = SA_SIGINFO;
		act.sa_sigaction= &handle_hup;
		if( sigaction( SIGHUP, &act, 0 ) )
		{
			wperror( L"sigaction" );
			exit(1);
		}

	}
	else
	{
		/*
		  Non-interactive. Ignore interrupt, check exit status of
		  processes to determine result instead.
		*/
		act.sa_handler=SIG_IGN;

		sigaction( SIGINT, &act, 0);
		sigaction( SIGQUIT, &act, 0);

		act.sa_handler=SIG_DFL;

		act.sa_sigaction = &handle_chld;
		act.sa_flags = SA_SIGINFO;
		if( sigaction( SIGCHLD, &act, 0) )
		{
			wperror( L"sigaction" );
			exit(1);
		}
	}
	
}

void signal_handle( int sig, int do_handle )
{
	struct sigaction act;
	
	/*
	  These should always be handled
	*/
	if( (sig == SIGINT) ||
		(sig == SIGQUIT) ||
		(sig == SIGTSTP) ||
		(sig == SIGTTIN) ||
		(sig == SIGTTOU) ||
		(sig == SIGCHLD) )
		return;
	
	sigemptyset( &act.sa_mask );
	if( do_handle )
	{
		act.sa_flags = SA_SIGINFO;
		act.sa_sigaction = &default_handler;
	}
	else
	{
		act.sa_flags = 0;
		act.sa_handler = SIG_DFL;
	}
	
	sigaction( sig, &act, 0);
}

void signal_block()
{
	sigset_t chldset; 
	
	if( !block_count )
	{
		sigfillset( &chldset );
		sigprocmask(SIG_BLOCK, &chldset, 0);	
	}
	
	block_count++;
}

void signal_unblock()
{
	sigset_t chldset; 

	block_count--;

	if( !block_count )
	{
		sigfillset( &chldset );
		sigprocmask(SIG_UNBLOCK, &chldset, 0);	
	}

}