/*
 * Signal handling for Plan 9 programs. 
 * We stubbornly use the strings from Plan 9 instead 
 * of the enumerated Unix constants.  
 * There are some weird translations.  In particular,
 * a "kill" note is the same as SIGTERM in Unix.
 * There is no equivalent note to Unix's SIGKILL, since
 * it's not a deliverable signal anyway.
 *
 * We do not handle SIGABRT or SIGSEGV, mainly because
 * the thread library queues its notes for later, and we want
 * to dump core with the state at time of delivery.
 *
 * We have to add some extra entry points to provide the
 * ability to tweak which signals are deliverable and which
 * are acted upon.  Notifydisable and notifyenable play with
 * the process signal mask.  Notifyignore enables the signal
 * but will not call notifyf when it comes in.  This is occasionally
 * useful.
 */

#include <u.h>
#include <signal.h>
#define NOPLAN9DEFINES
#include <libc.h>

extern char *_p9sigstr(int, char*);
extern int _p9strsig(char*);

typedef struct Sig Sig;
struct Sig
{
	int sig;			/* signal number */
	int restart;			/* do we restart the system call after this signal is handled? */
	int enabled;		/* is this signal enabled (not masked)? */
	int notified;		/* do we call the notify function for this signal? */
};

/* initial settings; for current status, ask the kernel */
static Sig sigs[] = {
	SIGHUP, 0, 1, 1,
	SIGINT, 0, 1, 1,
	SIGQUIT, 0, 1, 1,
	SIGILL, 0, 1, 1,
	SIGTRAP, 0, 1, 1,
/*	SIGABRT, 0, 1, 1,	*/
#ifdef SIGEMT
	SIGEMT, 0, 1, 1,
#endif
	SIGFPE, 0, 1, 1,
	SIGBUS, 0, 1, 1,
/*	SIGSEGV, 0, 1, 1,	*/
	SIGCHLD, 1, 0, 1,
	SIGSYS, 0, 1, 1,
	SIGPIPE, 0, 0, 1,
	SIGALRM, 0, 1, 1,
	SIGTERM, 0, 1, 1,
	SIGTSTP, 1, 0, 1,
	SIGTTIN, 1, 0, 1,
	SIGTTOU, 1, 0, 1,
	SIGXCPU, 0, 1, 1,
	SIGXFSZ, 0, 1, 1,
	SIGVTALRM, 0, 1, 1,
	SIGUSR1, 0, 1, 1,
	SIGUSR2, 0, 1, 1,
	SIGWINCH, 1, 0, 1,
#ifdef SIGINFO
	SIGINFO, 1, 1, 1,
#endif
};

static Sig*
findsig(int s)
{
	int i;

	for(i=0; i<nelem(sigs); i++)
		if(sigs[i].sig == s)
			return &sigs[i];
	return nil;
}

/*
 * The thread library initializes _notejmpbuf to its own
 * routine which provides a per-pthread jump buffer.
 * If we're not using the thread library, we assume we are
 * single-threaded.
 */
typedef struct Jmp Jmp;
struct Jmp
{
	p9jmp_buf b;
};

static Jmp onejmp;

static Jmp*
getonejmp(void)
{
	return &onejmp;
}

Jmp *(*_notejmpbuf)(void) = getonejmp;
static void noteinit(void);

/*
 * Actual signal handler. 
 */

static void (*notifyf)(void*, char*);	/* Plan 9 handler */

static void
signotify(int sig)
{
	char tmp[64];
	Jmp *j;

	j = (*_notejmpbuf)();
	switch(p9setjmp(j->b)){
	case 0:
		if(notifyf)
			(*notifyf)(nil, _p9sigstr(sig, tmp));
		/* fall through */
	case 1:	/* noted(NDFLT) */
		if(0)print("DEFAULT %d\n", sig);
		signal(sig, SIG_DFL);
		raise(sig);
		_exit(1);
	case 2:	/* noted(NCONT) */
		if(0)print("HANDLED %d\n", sig);
		return;
	}
}

static void
signonotify(int sig)
{
	USED(sig);
}

int
noted(int v)
{
	p9longjmp((*_notejmpbuf)()->b, v==NCONT ? 2 : 1);
	abort();
	return 0;
}

int
notify(void (*f)(void*, char*))
{
	static int init;

	notifyf = f;
	if(!init){
		init = 1;
		noteinit();
	}
	return 0;
}

/*
 * Nonsense about enabling and disabling signals.
 */
typedef void Sighandler(int);
static Sighandler*
handler(int s)
{
	struct sigaction sa;

	sigaction(s, nil, &sa);
	return sa.sa_handler;
}

static void
notesetenable(int sig, int enabled)
{
	sigset_t mask;

	if(sig == 0)
		return;

	sigemptyset(&mask);
	sigaddset(&mask, sig);
	sigprocmask(enabled ? SIG_UNBLOCK : SIG_BLOCK, &mask, nil);
}

void
noteenable(char *msg)
{
	notesetenable(_p9strsig(msg), 1);
}

void
notedisable(char *msg)
{
	notesetenable(_p9strsig(msg), 0);
}

static void
notifyseton(int s, int on)
{
	Sig *sig;
	struct sigaction sa;

	sig = findsig(s);
	if(sig == nil)
		return;
	memset(&sa, 0, sizeof sa);
	sa.sa_handler = on ? signotify : signonotify;
	if(sig->restart)
		sa.sa_flags |= SA_RESTART;

	/*
	 * We can't allow signals within signals because there's
	 * only one jump buffer.
	 */
	sigfillset(&sa.sa_mask);

	/*
	 * Install handler.
	 */
	sigaction(sig->sig, &sa, nil);
}

void
notifyon(char *msg)
{
	notifyseton(_p9strsig(msg), 1);
}

void
notifyoff(char *msg)
{
	notifyseton(_p9strsig(msg), 0);
}

/*
 * Initialization follows sigs table.
 */
static void
noteinit(void)
{
	int i;
	Sig *sig;

	for(i=0; i<nelem(sigs); i++){
		sig = &sigs[i];
		/*
		 * If someone has already installed a handler,
		 * It's probably some ld preload nonsense,
		 * like pct (a SIGVTALRM-based profiler).
		 * Or maybe someone has already called notifyon/notifyoff.
		 * Leave it alone.
		 */
		if(handler(sig->sig) != SIG_DFL)
			continue;
		/*
		 * Should we only disable and not enable signals?
		 * (I.e. if parent has disabled for us, should we still enable?)
		 * Right now we always initialize to the state we want.
		 */
		notesetenable(sig->sig, sig->enabled);
		notifyseton(sig->sig, sig->notified);
	}
}