/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "@(#)$Header: microtask.c 1.8 90/03/12 $"

/*
 * microtask.c
 *	Simple microtasking support for shared-memory C and Fortran programs.
 *
 * TODO:
 *	Profile execution and optimize.
 *	deal with SIGSTOP, SIGTTIN, etc in a nice way
 *	do a binary tree fork, for speed
 *
 * Assumes shared-memory environment is already set up.
 */

#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <sys/resource.h>

#include "parallel.h"
#include "microtask.h"
#include "parc.h"

/*
 * Use this variable to tell ppinit to set up a shared parent stack
 */
private int _do_shared_stack = 1;

/*
 * Internal variables.
 */

shared	int	_procid[MAXPROCS];	/* pids */
private		int	m_myid;		/* procid[] idx for each proc */

static void child_handler();
static void sig_handler();

static private struct sigaction DFL_vec = {SIG_DFL, 0, 0};
static  private struct sigaction CHLD_vec = {child_handler, 0, SA_ALTSTACK};
/*
 * Want to make sure that cleanup procedures for a signal don't cause
 * SIGCHLD to be invoked.
 */
static  private struct sigaction HAND_vec;
static private void (*sig_array[NSIG + 1])();	/* bss inits this as  SIG_DFL */
static private int first_dump;

/*
 * process state information
 */

static private int mystate;


/*
 * Initialization routine called from m_fork(), when there are not
 * process waiting to run in parallel.
 */

/*
 * THESE MUST be a private, non-automatic variable
 */
static private int mfork_pid;
static private int mfork_i;

/*
 *	THIS ROUTINE MUST BE CALLED WHILE RUNNING ON A PRIVATE STACK!!!!
 */
_m_fork_init()
{
	int	err = errno;
	extern int *_priv_sp;

	if (mystate == S_SINGLE) {

		if (m_numprocs == -1) {
			m_numprocs = m_get_default();
		}

		/*
		 * Check nprocs <= MAXPROCS?  Check not reentered?
		 */

		(void) sigaction(SIGCHLD, &CHLD_vec, (struct sigaction *)NULL);
		_m_init_worker_stacks();

		/*
		 * flush all the user's buffers to keep from
		 * getting duplicate infor.
		 */
		_p__ffork();

		for (mfork_i = 1; mfork_i < m_numprocs; mfork_i++) {
			mfork_pid = fork();
			if (mfork_pid < 0) {
				(void) sigaction(SIGCHLD, &DFL_vec,
						(struct sigaction *)NULL);
				(void) sigaction(SIGCHLD, &DFL_vec,
						(struct sigaction *)NULL);
				while (--mfork_i > 1) {
					(void) kill(_procid[mfork_i], SIGKILL);
					/* could wait for processes */
				}
				errno = err;
				return(-1);
			}

			if (mfork_pid == 0) {
				/*
				 * Child.
				 */
				m_myid = mfork_i;
				mystate = S_WAIT;
#ifdef i386
			;asm("	movl	$0,%ebp");
			;asm("	call	m_worker");
#endif /* i386 */
				/*NOTREACHED*/
			}

			/*
			 * Parent.  Record kid's pid.
			 */
			_procid[mfork_i] = mfork_pid;
			/* maybe SIGSTOP child? */
		}

		/*
		 * Only parent gets here.
		 */

		init_sigaction();
		m_myid = MASTER;
		_procid[0] = getpid();
		mystate = S_WAIT;

	}

	/*
	 * Handle reentrancy -- if already active, have nested m_fork() call.
	 * Note that if !active, caller must be master.
	 */

	/*
	 * If they're parked, let'em go.
	 */

	if (mystate == S_PARKED)
		(void)_m_rele_procs();

	if (mystate != S_WAIT) {
		errno = EINVAL;
		return(-1);
	}
	return(0);
}
_m_park_procs()
{
	register int i;

	/*
	 * Only allow park if not currently active.
	 * Note that if !active, only master thread is calling.
	 */

	if (mystate == S_SINGLE)
		return(0);

	if (mystate != S_WAIT) {
		errno = EINVAL;
		return(-1);
	}

	/*
	 * Stop worker processes to get them off processors.
	 */

	for (i = 1; i < m_numprocs; i++) {
		(void) kill(_procid[i], SIGSTOP);
	}
	mystate = S_PARKED;
	return(0);
}

/*
 * m_rele_procs()
 *	Relese processes from "parked" state -- ready to compute again.
 */

_m_rele_procs()
{
	register int i;

	if (mystate == S_SINGLE)
		return(0);

	if (mystate != S_PARKED) {
		errno = EINVAL;
		return(-1);
	}
	for (i = 1; i < m_numprocs; i++)
		(void) kill(_procid[i], SIGCONT);
	mystate = S_WAIT;
	return(0);
}

/*
 * m_kill_procs()
 *	Release slave processes to system, no longer needed.
 *
 * Also, wait for them to terminate (note this could get other non-worker
 * children).
 */

_m_kill_procs()
{
	register int togo = m_numprocs-1;
	register int i;
	register int pid;

	/*
	 * If slaves are "parked", let'em wakeup so they can die.
	 */

	if (mystate == S_SINGLE)
		return(0);

	if (mystate == S_PARKED)
		(void)_m_rele_procs();

	/*
	 * Only allow kill if not currently active.
	 * Note that if !active, only master thread is calling.
	 */

	if (mystate != S_WAIT) {
		errno = EINVAL;
		return(-1);
	}

	/*
	 * Sneaky -- fool them into calling exit!
	 */

	restore_sigaction();
	(void) sigaction(SIGCHLD, &DFL_vec, (struct sigaction *)NULL);
	m_shutdown();

	/*
	 * Wait for the processes to exit, to collect their run-time
	 * statistics.
	 */

	mystate = S_SINGLE;
	while (togo != 0) {
		for (i = 1; i < m_numprocs; i++) if (_procid[i]) break;
		if (i >= m_numprocs) break;
		pid = wait((int *)0);
		for (i = 1; i < m_numprocs; i++) {
			if (pid == _procid[i]) {
				--togo;
				_procid[i] = EMPTY;
				break;
			}
		}
	}
	return(0);
}


/*
 * Internal procedures.
 */


/*
 * called only by MASTER from m_fork()
 * Make sure that signals declared SIG_DFL point to the microtasker's handler.
 */
static
init_sigaction()
{
	int sig;
	sigset_t newmask, oldmask;
	struct sigaction oldvec;

	HAND_vec.sa_handler = sig_handler;
	sigfillset(&HAND_vec.sa_mask);
	sigaddset(&HAND_vec.sa_mask, SIGCHLD);
	HAND_vec.sa_flags = SA_ALTSTACK;

	sigfillset(&newmask);
	sigprocmask(SIG_BLOCK, &newmask, &oldmask);
	for (sig = 1; sig <= NSIG; sig++) {
		switch (sig) {
		case	SIGSTOP:
		case	SIGTSTP:
		case	SIGTTIN:
		case	SIGTTOU:
		case	SIGCONT:
		case	SIGCHLD:
		case	SIGWINCH:
			continue;

		default:
			if (sig_array[sig] != (void (*)()) SIG_DFL)
				continue;
			(void) _sigaction(sig, &HAND_vec, &oldvec);
			if (oldvec.sa_handler == (void (*)()) SIG_IGN)
				sig_array[sig] = (void (*)()) SIG_IGN;
		}
	}
	(void)sigprocmask(SIG_SETMASK, &oldmask, (sigset_t *)NULL);
}

/*
 * Called only by MASTER from m_kill_procs.
 * Return unused signals to the SIG_DFL state.
 */
static restore_sigaction()
{
	int sig;

	for (sig = 1; sig <= NSIG; sig++) {
		switch (sig) {
		case	SIGSTOP:
		case	SIGTSTP:
		case	SIGTTIN:
		case	SIGTTOU:
		case	SIGCONT:
		case	SIGCHLD:
		case	SIGWINCH:
			continue;

		default:
			if (sig_array[sig] != (void (*)()) SIG_DFL)
				continue;
			(void) _sigaction(sig, &DFL_vec, (struct sigaction *)NULL);
		}
	}
}


/*
 * for MASTER only, make sure that all SIG_DFL's point at microtasker's
 * handler.
 */

static void sig_handler();

int
_lib_sigaction(sig, vec, ovec)
int sig;
struct sigaction *vec, *ovec;
{
	register int ret_val;

	switch (sig) {
	case	SIGSTOP:
	case	SIGTSTP:
	case	SIGTTIN:
	case	SIGTTOU:
	case	SIGCONT:
	case	SIGCHLD:
	case	SIGWINCH:
		ret_val = _sigaction(sig, vec, ovec);
		break;

	default:
		ret_val = _sigaction(sig, vec, ovec);
		if ((m_myid == MASTER) && (ret_val == 0)) {
			sig_array[sig] = vec->sa_handler;
			if (sig_array[sig] == (void (*)()) SIG_DFL)
				(void) _sigaction(sig, &HAND_vec,
						(struct sigaction *)NULL);
		}
	}
	return(ret_val);
}

/*
 * All SIG_DFL handlers for MASTER(only) reach here
 */
/*ARGSUSED*/
static void
sig_handler(sig)
int sig;
{

	if (sig_array[sig] == (void (*)()) SIG_IGN)
		return;
	(void) _sigaction(SIGCHLD, &DFL_vec, (struct sigaction *)NULL);
	_clean_house(sig);

	/*
	 * all the kids are dead and gone.
	 * Time to blow myself away.
	 */

	(void) _sigaction(sig, &DFL_vec, (struct sigaction *)NULL);
	(void) kill(getpid(), sig);
	/* Not reached */
	return;
}

_clean_house(sig)
int sig;
{
	register int i;
	int pid, tpid;
	int status;

	/* assume we're in trouble--kill 'em the rough way */

	for (i = 1; i < m_numprocs; i++) {
		if (_procid[i] == EMPTY)
			continue;

		/*
		 * gotta make sure he's dead.
		 *
		 * For example, if this is a SIGSEGV, and there
		 * just happened to be an unmapped part of the shared
		 * data region, the shsbrk() code will eat the first signal.
		 * In fact, it'll keep eating them until a signal gets
		 * in and there's no growth to do.  Conceivable, this race
		 * could go on forever.  But, that's unlikely.
		 */

		while (kill(_procid[i], sig) == 0) sleep(1);

		/*
		 * clean up any dead kids.
		 *
		 * Try to rename core files if possible.  Also deal with
		 * the possibility that other kids may have died spontaneously.
		 * This, of course makes the core file renaming a little
		 * unreliable.
		 */

		pid = _procid[i];
		do {
			tpid = wait(&status);
			(void) clean_kid(i, tpid, WIFCORESIG(status));
		} while (tpid != pid);
	}
}


static
clean_kid(i, tpid, coredump)
register int i, tpid;
unsigned short coredump;
{
	register int j;
	char buf[16];

	for (j = i; j < m_numprocs; j++) {
		if (_procid[j] != tpid)
			continue;
		_procid[j] = EMPTY;
		if (coredump) {
			if (first_dump)
				(void) sprintf(buf, "core.%d.First", j);
			 else
				(void) sprintf(buf, "core.%d", j);
			/*
			 * I really don't care if this fails.
			 */
			(void) rename("core", buf);
		}
		first_dump = 0;
		return(0);
	}
	return(-1);
}

static void
child_handler()
{
	register int tpid, sig;
	int status;


	if (m_myid != MASTER)
		return;


	tpid = waitpid(0, &status, WNOHANG|WUNTRACED);
	if (tpid <= 0)
		return;
	if (WIFSTOPPED(status)) {
		return;
	}

	first_dump = 1;
	if (clean_kid(1, tpid, WIFCORESIG(status)))
		return;

	sig = WTERMSIG(status);
	if (sig == 0) {
		_clean_house(9);
		exit((int)WEXITSTATUS(status));
	} else {
		_clean_house(sig);
		(void) _sigaction(SIGCHLD, &DFL_vec, (struct sigaction *)NULL);
		(void) _sigaction(sig, &DFL_vec, (struct sigaction *)NULL);
		(void) kill(getpid(), sig);
	}
}

private static nice_exit = 0;
_lib_exit(status)
int status;
{

	if ((m_myid != MASTER) || (mystate == S_SINGLE))
		__exit(status);

	(void) _sigaction(SIGCHLD, &DFL_vec, (struct sigaction *)NULL);
	if (nice_exit++) _clean_house(SIGKILL);
	else {
		(void)_m_kill_procs();
	}
	__exit(status);
}
