/* butlerwait.c
This package takes care of waiting for child processes to die.
David Nichols
November 1985 */

#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <r/xdr.h>
#include <r/r.h>
#include <lwp.h>
#include "butler.h"
#include "butlerlog.h"

#define NULL	0

extern char *malloc ();
extern char *realloc ();

#define STACKSIZE	8192	/* Stack size for new LWPs */

/* This struct is used solely to pass several parameters to a forked lwp so it can call Butler_ExecuteRequest. */
struct WaitBlock {
    struct WaitBlock *next;	/* for the free list */
    struct r_connection *conn;
    struct r_packet *pb;
};
/* List of available wait blocks.  Locked by being current LWP. */
static struct WaitBlock *waitBlocks = NULL;

/* This struct describes a Unix process that is a child of the butler. */
struct ProcessBlock {
    int pid;		/* The Unix process id */
    int status;		/* The status after it dies */
    int alive;		/* true until wait3 says no */
    int waiting;	/* true if someone is waiting on this */
    char *event;	/* event to signal */
};
/* List of current children of the Butler.  Locked by being current LWP.  A process is in this list if it is alive or if it is dead and the waiting LWP hasn't awakened yet. */
static struct ProcessBlock *processes;	/* array of active processes */
static int nProcesses;			/* count of them */
static int nProcessesAlloced;		/* current size of the array */

/* Init the package */
BW_Init ()
{
    int BWDaemon ();
    PROCESS pid;

    nProcesses = 0;
    nProcessesAlloced = 10;
    processes = (struct ProcessBlock *) malloc ((u_int) nProcessesAlloced * sizeof (*processes));
    LWP_CreateProcess (BWDaemon, STACKSIZE, LWP_NORMAL_PRIORITY, (char *) NULL, "BW daemon", &pid);
}

/* This proc tries to move a Butler_Wait request into an LWP.  It returns the special value 1 if successful to tell the execute request procedure to not send a response.  If it fails, it returns 0, telling to execute request preocedure to send an error reply. */
struct r_packet *BW_ExecuteWait (conn, pb)
    struct r_connection *conn;
    struct r_packet *pb;
{
    int ExecuteWait ();
    PROCESS pid;
    struct WaitBlock   *wb;

    if (waitBlocks != NULL) {
	wb = waitBlocks;
	waitBlocks = waitBlocks->next;
    }
    else
	wb = (struct WaitBlock *) malloc (sizeof (struct WaitBlock));
    wb->conn = conn;
    wb->pb = pb;
    if (LWP_CreateProcess (ExecuteWait, STACKSIZE, LWP_NORMAL_PRIORITY, (char *) wb, "Waiter", &pid) == LWP_SUCCESS)
	return (struct r_packet *) 1;
    wb->next = waitBlocks;
    waitBlocks = wb;
    return (struct r_packet *) 0;
}

/* This is the root proc of an lwp forked to do a Butler_Wait request. */
static ExecuteWait (param)
    char *param;
{
    struct WaitBlock *wb = (struct WaitBlock *) param;
    struct r_packet *pb;
    extern struct r_packet * _Butler_Wait();

    /* We have to do the work that Butler_Execute request would normally do, including sending the reply packet back. */
    pb = _Butler_Wait (wb->conn, wb->pb);
    if (pb != NULL)
	r_SendResponse(wb->conn, pb);
    else
	r_NoResponse(wb->conn);
    wb->next = waitBlocks;
    waitBlocks = wb;
}

/* Call this to tell the package of a new child. */
BW_NewProcess (pid)
    int pid;
{
    if (nProcesses >= nProcessesAlloced) {
	nProcessesAlloced *= 2;
	processes = (struct ProcessBlock *) realloc ((char *) processes, (u_int) nProcessesAlloced * sizeof (*processes));
    }
    processes[nProcesses].pid = pid;
    processes[nProcesses].alive = TRUE;
    processes[nProcesses].waiting = FALSE;
    ++nProcesses;
}

/* Tell how many live ones we have. */
BW_ProcessCount ()
{
    register int i, n;

    for (i = n = 0; i < nProcesses; ++i)
	if (processes[i].alive)
	    ++n;
    return n;
}

/* Knock off running processes. */
BW_KillProcs(reallyKill)
    int reallyKill;
{
    int i;
    int sig = reallyKill ? SIGKILL : SIGTERM;

    for (i = 0; i < nProcesses; ++i)
	if (killpg(processes[i].pid, sig) >= 0)
	    BL_Log(BL_ToUser, "%s process group %d.\n", reallyKill ? "Killed" : "Terminating", processes[i].pid);
}

/* Tell if this pid is registered. */
BW_PidOk (pid)
    int pid;
{
    int i;

    for (i = 0; i < nProcesses; ++i)
	if (processes[i].pid == pid)
	    return TRUE;
    return FALSE;
}

/* Wait for a Unix process to die. */
BW_DoWait (pid, status)
    int pid;
    int *status;
{
    int i;
    char event;

start: 
    for (i = 0; i < nProcesses; ++i)
	if (processes[i].pid == pid)
	    if (processes[i].alive) {
		/* We'll need to wait on it. */
		processes[i].waiting = TRUE;
		processes[i].event = &event;
		LWP_WaitProcess (&event);
		/* When we get here, the process is dead, but our index may be wrong.  Go back to the start and the process dead code should fire. */
		goto start;
	    }
	    else {
		/* Process is dead, remove it from the list. */
		if (status != NULL)
		    *status = processes[i].status;
		processes[i] = processes[nProcesses-1];
		--nProcesses;
		return butler_Success;
	    }
    /* Couldn't find the process.  Must be bogus pid. */
    return butler_Failure;
}

/* This is the LWP that waits for SIGCHLD events and marks them in the
   process array. */
static BWDaemon (param)
    char *param;
{
    char event;
    int pid;
    int i, found;
    union wait status;

    sigsetmask(sigblock(0) & ~(1 << (SIGCHLD-1)));	/* just in case autobutler is confused */
    IOMGR_Signal (SIGCHLD, &event);
    for (;;) {
	for (;;) {
	    pid = wait3 (&status, WNOHANG, (struct rusage *) 0);
	    if (pid < 0)
		break;
	    /* Temporary logging. */
	    BL_Log(BL_LogDebug, "BWDaemon: pid = %d, stopval = %d.\n", pid, status.w_stopval);
	    if (status.w_stopval != WSTOPPED) {
		found = FALSE;
		for (i = 0; i < nProcesses; ++i)
		    if (processes[i].pid == pid) {
			found = TRUE;
			if (processes[i].waiting) {
			    processes[i].alive = FALSE;
			    processes[i].status = status.w_status;
			    LWP_NoYieldSignal(processes[i].event);
			    /* Sleeper will delete it. */
			}
			else {
			    processes[i] = processes[nProcesses-1];
			    --nProcesses;
			}
			break;
		    }
		if (!found)
		    BL_Log(BL_LogError, "BWDaemon: got process death for unknown pid: %d.\n", pid);
	    }
	}
	LWP_WaitProcess (&event);
    }
}
