/* $Id: Signal.c,v 1.2 1997/05/01 12:05:57 archer Exp $
 *	SIGNAL.C - Sub-routines that handle signals all around
 *
 * Due to asynchronous nature and non re-entrency of pre-X11R6 Xlibs, signals
 * are deferred using a pipe, and presented to the system as bytes from the
 * 'signals' stream.
 * Handled: SIGCHLD (children exits), SIGALRM (timers)
 */
#include <Widget/WidgetsP.h>
#include <Widget/SignalP.h>
#include <Widget/Events.h>
#include <Widget/Perror.h>
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <signal.h>
#include <fcntl.h>


/**
 ** Derive methods used to block signals (got too many #ifdef/#else/#endif...)
 **/
#ifdef HAVE_SIGPROCMASK
#define USE_SIGSET_T
#define USE_SIGPROCMASK
#else
#ifdef HAVE_SIGSETMASK
#define USE_SIGSET_T
#define USE_SIGSETMASK
#else
#ifdef HAVE_SIGACTION
#define USE_SIGACTION
#else
#define USE_SIGNAL_SIGDFL
#endif
#endif
#endif


/**
 ** Private variables
 **/
static x11__sigchld	*list_of_children;
static x11__sigchld	*exited_children;
static X11_timer	main_timer;
static struct timeval	now;
static int		fd_signals[2];
static boolean		enable_sigalrm;


/**
 ** Macro might be missing
 **/
#ifndef WIFEXITED
#define WIFEXITED(s) (((s) & 255) == 0)
#endif


/**
 ** Internal: ON_SIGALRM
 **	Handle a SIGALRM (Timer expire) signal
 **/
static RETSIGTYPE on_sigalrm(int dummy)
{
char	c;
#ifndef HAVE_SIGACTION
/*
 * Reactivate SYSV signals
 */
	signal(SIGALRM, on_sigalrm);
#endif /*HAVE_SIGACTION*/
/*
 * Send a piped notify
 */
	c = SIGALRM;
	write(fd_signals[1], &c, 1);
}


/**
 ** Internal: BEFORE_TIMEVAL
 **	Is this timeval before or after this other?
 **/
static boolean before_timeval(struct timeval *a, struct timeval *b)
{
	if (a->tv_sec < b->tv_sec)
		return True;
	if (a->tv_sec > b->tv_sec)
		return False;
	if (a->tv_usec < b->tv_usec)
		return True;
	return False;
}


/**
 ** Internal: ADD_REARM
 **	Add the re-arm timer to current value
 **/
static void add_rearm(X11_timer *t)
{
	t->at.tv_sec  += t->every.tv_sec;
	t->at.tv_usec += t->every.tv_usec;
	while (t->at.tv_usec >= 1000000) {
		t->at.tv_sec++;
		t->at.tv_usec -= 1000000;
	}
}


/** 
 ** Internal: INSERT_TIMER
 **	Insert a timer on the list
 **/
static void insert_timer(X11_timer *t)
{
X11_timer	*p, *n;
/*
 * Sorted insertion
 */
	p = &main_timer;
	for (n = p->next; n; n = p->next) {
		if (before_timeval(&t->at, &n->at))
			break;
		p = n;
	}
/*
 * Insert now
 */
	t->next = n;
	p->next = t;
}


/**
 ** Internal: RUN_TIMER
 **	The timer must run now
 **/
static void run_timer(void)
{
X11_timer		*n;
struct itimerval	t;
/*
 * Stop timers?
 */
	if ((n = main_timer.next) == 0) {
		memset(&main_timer.every, 0, sizeof(main_timer.every));
		memset(&t, 0, sizeof(t));
		setitimer(ITIMER_REAL, &t, (struct itimerval *)0);
		return;
	}
/*
 * When to start?
 */
	t.it_value.tv_sec  = n->at.tv_sec  - now.tv_sec;
	t.it_value.tv_usec = n->at.tv_usec - now.tv_usec;
	while (t.it_value.tv_usec < 0) {
		t.it_value.tv_sec--;
		t.it_value.tv_usec += 1000000;
	}
/*
 * Timer already expired!
 */
	if (t.it_value.tv_sec < 0) {
		on_sigalrm(SIGALRM);
		return;
	}
/*
 * Launch timer!
 */
	main_timer.at    = n->at;
	main_timer.every = n->every;
	t.it_interval    = n->every;
	if (enable_sigalrm)
		setitimer(ITIMER_REAL, &t, (struct itimerval *)0);
}


/**
 ** Internal: CALL_SIGALRM
 **	Signal SIGALRM handling
 **/
static void call_sigalrm(void)
{
struct timeval	trash;
X11_timer	*t;
/*
 * When are we?
 */
	gettimeofday(&now, &trash);
	if (before_timeval(&now, &main_timer.at)) {
		run_timer();
		return;
	}
/*
 * Call time-outs
 */
	while ((t = main_timer.next) != 0) {
		if (before_timeval(&now, &t->at))
			break;
		main_timer.next = t->next;
		if (t->every.tv_sec || t->every.tv_usec) {
			add_rearm(t);
			insert_timer(t);
		}
		(*t->on_timer)(t->wdg, t->data);
	}
/*
 * Need to change running timer?
 */
	add_rearm(&main_timer);
	if ((t = main_timer.next) == 0 ||
	    (t->at.tv_sec == main_timer.at.tv_sec &&
	     t->at.tv_usec == main_timer.at.tv_usec) )
		run_timer();
}


/**
 ** Internal: ON_SIGCHLD
 **	Handle a SIGCHLD (Child status) signal
 **/
static RETSIGTYPE on_sigchld(int dummy)
{
x11__sigchld	*c, *p;
pid_t		id;
int		status;
char		b;
/*
 * Solaris forces us to catch the signal in the handler
 */
	b = SIGCHLD;
	while (1) {
		id = waitpid(-1, &status, WNOHANG);
		if (id < 0)
			break;
		if (!WIFEXITED(status))
			break;
/*
 * Find out which child we meant
 */
		p = 0;
		for (c = list_of_children; c; c = c->next)
			if (c->pid == id)
				break;
			else
				p = c;
		if (!c)
			continue;
/*
 * Unlink (don't find it twice)
 */
		if (p)
			p->next = c->next;
		else
			list_of_children = c->next;
/*
 * Link back into exited children list
 */
		c->next = exited_children;
		exited_children = c;
	}
/*
 * If any children exited, notify the main loop
 */
	if (exited_children)
		write(fd_signals[1], &b, 1);
#ifndef HAVE_SIGACTION
/*
 * Reactivate SYSV signals
 */
	signal(SIGCHLD, on_sigchld);
#endif /*HAVE_SIGACTION*/
}


/**
 ** Internal: CALL_SIGCHLD
 **	Signal SIGCHLD handling
 **/
static void call_sigchld(void)
{
x11__sigchld		*c;
#ifdef USE_SIGSET_T
sigset_t		mask, old;
#endif
#ifdef USE_SIGACTION
struct sigaction	act, oact;
#endif
/*
 * Mask SIGCHLD here.
 */
#ifdef USE_SIGSET_T
	sigemptyset(&mask);
	sigaddset(&mask, SIGCHLD);
#endif
#ifdef USE_SIGPROCMASK
/*
 * Sigprocmask adds the mask
 */
	sigprocmask(SIG_BLOCK, &mask, &old);
#endif
#ifdef USE_SIGSETMASK
/*
 * Sigblock implies that mask is an integer
 */
	old = sigblock(mask);
#endif
#ifdef USE_SIGACTION
/*
 * Disable signal handler
 */
	act.sa_handler = SIG_DFL;
	sigemptyset(&act.sa_mask);
	act.sa_flags   = 0;
	sigaction(SIGCHLD, &act, &oact);
#endif
#ifdef USE_SIGNAL_SIGDFL
/*
 * Reset signal handler to default action
 */
	signal(SIGCHLD, SIG_DFL);
#endif
/*
 * Loop on all exited children. We're safe from signals
 */
	while ((c = exited_children) != 0) {
		exited_children = c->next;
		if (c->on_exit)
			(*c->on_exit)(c->pid, c->user_data);
		free(c);
	}
/*
 * Exit critical section, restore signal
 */
#ifdef USE_SIGPROCMASK
/*
 * Sigprocmask reloads the mask
 */
	sigprocmask(SIG_SETMASK, &old, (sigset_t *)0);
#endif
#ifdef USE_SIGSETMASK
/*
 * sigsetmask forces the mask back to old value
 */
	sigsetmask(old);
#endif
#ifdef USE_SIGACTION
/*
 * Reinstall handler...
 */
	sigaction(SIGCHLD, &oact, 0);
#endif
#ifdef USE_SIGNAL_SIGDFL
/*
 * Put back function for signal
 */
	signal(SIGCHLD, on_sigchld);
#endif
}


/**
 ** Internal: ON_SIGNAL_BYTE
 **	Handle a signal received on the signal stream
 **/
static void on_signal_byte(int fd, void *dummy)
{
int	i;
char	c;
/*
 * Read signal byte, if any
 */
	i = read(fd, &c, 1);
	if (i == 0)
		return;
/*
 * Error on PIPE! Panic!
 */
	if (i != 1) {
/*** HACK ***/
		abort();
	}
/*
 * Dispatch piped signal
 */
	switch (c) {
	    case SIGALRM:
		call_sigalrm();
		break;
	    case SIGCHLD:
		call_sigchld();
		break;
	}
/*
 * Next signal will be received after a time...
 */
}


/**
 ** X11_SIGNAL_CANCEL
 **	Cancel a timer after expiry?
 **/
void X11_SignalCancel(X11_timer *t)
{
X11_timer	*l, *n;
/*
 * Inside list
 */
	l = &main_timer;
	n = 0;
	while (l) {
		n = l->next;
		if (n == t)
			break;
		l = n;
	}
	if (l)
		l->next = n->next;
}


/**
 ** X11_SIGNAL_ARM
 **	Set up a timer
 **/
void X11_SignalArm(X11_timer *t)
{
X11_timer	*l;
struct timeval	trash;
/*
 * Make sure we're not running...
 */
	for (l = &main_timer; l; l = l->next)
		if (l == t)
			return;
/*
 * When are we?
 */
	gettimeofday(&now, &trash);
	if (t->every.tv_sec || t->every.tv_usec) {
		t->at = now;
		add_rearm(t);
	}
/*
 * Insert when?
 */
	insert_timer(t);
	if (main_timer.next == t)
		run_timer();
}


/**
 ** X11_SIGNAL_CHILD
 **	Set up a child watching
 **/
void X11_SignalChild(pid_t id, X11_exit call, void *data)
{
x11__sigchld	*c;
/*
 * Cancel child catching?
 */
	if (call == 0) {
		for (c = list_of_children; c; c = c->next)
			if (c->pid == id)
				c->on_exit = 0;
		return;
	}
/*
 * Normal catching
 */
	c = allocator(x11__sigchld);
	c->user_data = data;
	c->on_exit   = call;
	c->pid       = id;
	c->next      = list_of_children;
	list_of_children = c;
}


/**
 ** X11_SIGNAL_INIT
 **	Set up the signal watcher...
 **/
void x11__signal_init(boolean do_alarms)
{
#ifdef HAVE_SIGACTION
struct sigaction	act;
#endif
/*
 * Create the PIPE to receive signals. Both end of the pipe vanish when
 * fork/exec is called.
 */
	if (pipe(fd_signals))
		X11_Perror("pipe()");
	fcntl(fd_signals[0], F_SETFD, 1);
	fcntl(fd_signals[1], F_SETFD, 1);
	X11_InputDescriptor(fd_signals[0], on_signal_byte, (void *)0);
#ifdef HAVE_SIGACTION
/*
 * Arm the POSIX signals
 */
	act.sa_handler = on_sigalrm;
	sigemptyset(&act.sa_mask);
	act.sa_flags   = 0;
	sigaction(SIGALRM, &act, 0);
	act.sa_handler = on_sigchld;
#ifdef SA_NOCLDSTOP
	act.sa_flags   = SA_NOCLDSTOP;
#endif
	sigaction(SIGCHLD, &act, 0);
#else
/*
 * Arm the signals
 */
	signal(SIGALRM, on_sigalrm);
	signal(SIGCHLD, on_sigchld);
#endif
/*
 * Keep track of sigalrm
 */
	enable_sigalrm = do_alarms;
}
