/* $Header: iomgr.c,v 1.12 88/12/28 22:00:07 kazar Exp $ */
/* $Source: /afs/andrew.cmu.edu/usr7/kazar/afs/lwp/RCS/iomgr.c,v $ */

#ifndef lint
static char *rcsid = "$Header: iomgr.c,v 1.12 88/12/28 22:00:07 kazar Exp $";
#endif

/*
 * P_R_P_Q_# (C) COPYRIGHT IBM CORPORATION 1987
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */

/*******************************************************************\
* 								    *
* 	Information Technology Center				    *
* 	Carnegie-Mellon University				    *
* 								    *
* 								    *
* 								    *
\*******************************************************************/


/*
	IO Manager routines & server process for VICE server.
*/

#include <afs/param.h>
#include <stdio.h>
#include "lwp.h"
#include <sys/time.h>
#include "timer.h"
#include <signal.h>
#include <errno.h>
#include <sys/file.h>

typedef unsigned char bool;
#define FALSE	0
#define TRUE	1

#define NIL	0

/* Stack size for IOMGR process and processes instantiated to handle signals */
#define STACK_SIZE	8000


/********************************\
* 				 *
*  Stuff for managing IoRequests *
* 				 *
\********************************/

struct IoRequest {

    /* Pid of process making request (for use in IOMGR_Cancel */
    PROCESS		pid;

    /* Descriptor masks for requests */
    int			readfds;
    int			writefds;
    int			exceptfds;

    struct TM_Elem	timeout;

    /* Result of select call */
    int			result;

};

/********************************\
* 				 *
*  Stuff for managing signals    *
* 				 *
\********************************/

#define badsig(signo)		(((signo) <= 0) || ((signo) >= NSIG))
#define mysigmask(signo)		(1 << ((signo)-1))

extern int errno;

static long openMask;		/* mask of open files on an IOMGR abort */
static long sigsHandled;		/* sigmask(signo) is on if we handle signo */
static int anySigsDelivered;		/* true if any have been delivered. */
static struct sigvec oldVecs[NSIG];	/* the old signal vectors */
static char *sigEvents[NSIG];		/* the event to do an LWP signal on */
static int sigDelivered[NSIG];		/* True for signals delivered so far.
					   This is an int array to make sure there
					   are no conflicts when trying to write it */
/* software 'signals' */
#define NSOFTSIG		4
static int (*sigProc[NSOFTSIG])();
static char *sigRock[NSOFTSIG];


static struct IoRequest *iorFreeList = 0;

static struct TM_Elem *Requests;	/* List of requests */
static struct timeval iomgr_timeout;	/* global so signal handler can zap it */

/* stuff for debugging */
static int iomgr_errno;
static struct timeval iomgr_badtv;
static PROCESS iomgr_badpid;

#define FreeRequest(x) ((x)->result = (int) iorFreeList, iorFreeList = (x))

static struct IoRequest *NewRequest()
{
    register struct IoRequest *request;

    if (request=iorFreeList) iorFreeList = (struct IoRequest *) (request->result);
    else request = (struct IoRequest *) malloc(sizeof(struct IoRequest));
    return request;
}

#define Purge(list) FOR_ALL_ELTS(req, list, { free(req->BackPointer); })

#define MAX_FDS 32

/* The IOMGR process */

/*
 * Important invariant: process->iomgrRequest is null iff request not in timer queue
 * also, request->pid is valid while request is in queue,
 * also, don't signal selector while request in queue, since selector free's request.
 */

static IOMGR(dummy)
    char *dummy;
{
    for (;;) {
 /* commented out to match use - kazar	static struct timeval Poll = { 0, 0 }; */
	int fds, readfds, writefds, exceptfds;
	struct TM_Elem *earliest;
	struct timeval timeout, junk;
	bool woke_someone;

	/* Wake up anyone who has expired or who has received a
	   Unix signal between executions.  Keep going until we
	   run out. */
	do {
	    woke_someone = FALSE;
	    /* Wake up anyone waiting on signals. */
	    /* Note: SignalSignals() may yield! */
	    if (anySigsDelivered && SignalSignals ())
		woke_someone = TRUE;
	    TM_Rescan(Requests);
	    for (;;) {
		register struct IoRequest *req;
		struct TM_Elem *expired;
		expired = TM_GetExpired(Requests);
		if (expired == NIL) break;
		woke_someone = TRUE;
		req = (struct IoRequest *) expired -> BackPointer;
#ifdef DEBUG
		if (lwp_debug != 0) puts("[Polling SELECT]");
#endif DEBUG
		req->readfds = req->writefds = req->exceptfds = 0;	/* no data ready */
		req->result = 0;				/* no fds ready */
		TM_Remove(Requests, &req->timeout);
#ifdef DEBUG
		req -> timeout.Next = (struct TM_Elem *) 2;
		req -> timeout.Prev = (struct TM_Elem *) 2;
#endif DEBUG
		LWP_QSignal(req->pid);
		req->pid->iomgrRequest = 0;
	    }
	    if (woke_someone) LWP_DispatchProcess();
	} while (woke_someone);


	/* Collect requests & update times */
	readfds = 0;
	writefds = 0;
	exceptfds = 0;
	FOR_ALL_ELTS(r, Requests, {
	    register struct IoRequest *req;
	    req = (struct IoRequest *) r -> BackPointer;
	    readfds |= req -> readfds;
	    writefds |= req -> writefds;
	    exceptfds |= req -> exceptfds;
	})
	earliest = TM_GetEarliest(Requests);
	if (earliest != NIL) {
	    timeout = earliest -> TimeLeft;

	    /* Do select */
#ifdef DEBUG
	    if (lwp_debug != 0) {
		printf("[select(%d, 0x%x, 0x%x, 0x%x, ", MAX_FDS, readfds, writefds, exceptfds);
		if (timeout.tv_sec == -1 && timeout.tv_usec == -1)
		    puts("INFINITE)]");
		else
		    printf("<%d, %d>)]\n", timeout.tv_sec, timeout.tv_usec);
	    }
#endif DEBUG
	    iomgr_timeout = timeout;
	    if (timeout.tv_sec == -1 && timeout.tv_usec == -1) {
		/* infinite, sort of */
		iomgr_timeout.tv_sec = 100000000;
		iomgr_timeout.tv_usec = 0;
	    }
	    /* Check one last time for a signal delivery.  If one comes after
	       this, the signal handler will set iomgr_timeout to zero, causing
	       the select to return immediately.  The timer package won't return
	       a zero timeval because all of those guys were handled above.

	       I'm assuming that the kernel masks signals while it's picking up
	       the parameters to select.  This may a bad assumption.  -DN */
	    if (anySigsDelivered)
		continue;	/* go to the top and handle them. */

	    /* select runs much faster if 0's are passed instead of &0s */
	    fds = select(MAX_FDS, (readfds? &readfds : 0), (writefds? &writefds : 0), (exceptfds? &exceptfds : 0), &iomgr_timeout);

	    if (fds < 0 && errno != EINTR) {
		iomgr_errno = errno;
		for(fds=0;fds<32;fds++) {
		    if (fcntl(fds, F_GETFD, 0) < 0 && errno == EBADF) openMask |= (1<<fds);
		}
		abort();
	    }

	    /* force a new gettimeofday call so FT_AGetTimeOfDay calls work */
	    FT_GetTimeOfDay(&junk, 0);

	    /* See what happened */
	    if (fds > 0)
		/* Action -- wake up everyone involved */
		SignalIO(fds, readfds, writefds, exceptfds);
	    else if (fds == 0
		&& (iomgr_timeout.tv_sec != 0 || iomgr_timeout.tv_usec != 0))
		/* Real timeout only if signal handler hasn't set
		   iomgr_timeout to zero. */
		SignalTimeout(fds, &timeout);

	}
	LWP_DispatchProcess();
    }
}

/************************\
* 			 *
*  Signalling routines 	 *
* 			 *
\************************/

static SignalIO(fds, readfds, writefds, exceptfds)
    int fds;
    register int readfds, writefds, exceptfds;
{
    /* Look at everyone who's bit mask was affected */
    FOR_ALL_ELTS(r, Requests, {
	register struct IoRequest *req;
	register PROCESS pid;
	req = (struct IoRequest *) r -> BackPointer;
	if ((req->readfds & readfds) ||
	    (req->writefds & writefds) ||
	    (req->exceptfds & exceptfds)) {
	    /* */
	    req -> readfds &= readfds;
	    req -> writefds &= writefds;
	    req -> exceptfds &= exceptfds;
	    req -> result = fds;
	    TM_Remove(Requests, &req->timeout);
	    LWP_QSignal(pid=req->pid);
	    pid->iomgrRequest = 0;
	}
    })
}

static SignalTimeout(fds, timeout)
    int fds;
    register struct timeval *timeout;
{
    /* Find everyone who has specified timeout */
    FOR_ALL_ELTS(r, Requests, {
	register struct IoRequest *req;
	register PROCESS pid;
	req = (struct IoRequest *) r -> BackPointer;
	if (TM_eql(&r->TimeLeft, timeout)) {
	    req -> result = fds;
	    TM_Remove(Requests, &req->timeout);
	    LWP_QSignal(pid=req->pid);
	    pid->iomgrRequest = 0;
	} else
	    return;
    })
}

/*****************************************************\
*						      *
*  Signal handling routine (not to be confused with   *
*  signalling routines, above).			      *
*						      *
\*****************************************************/
static SigHandler (signo)
    int signo;
{
    if (badsig(signo) || (sigsHandled & mysigmask(signo)) == 0)
	return;		/* can't happen. */
    sigDelivered[signo] = TRUE;
    anySigsDelivered = TRUE;
    /* Make sure that the IOMGR process doesn't pause on the select. */
    iomgr_timeout.tv_sec = 0;
    iomgr_timeout.tv_usec = 0;
}

/* Alright, this is the signal signalling routine.  It delivers LWP signals
   to LWPs waiting on Unix signals. NOW ALSO CAN YIELD!! */
static SignalSignals ()
{
    bool gotone = FALSE;
    register int i;
    register int (*p)();

    anySigsDelivered = FALSE;

    /* handle software signals */
    for (i=0; i < NSOFTSIG; i++) {
	PROCESS *pid;
	if (p=sigProc[i]) /* This yields!!! */
	    LWP_CreateProcess(p, STACK_SIZE, LWP_NORMAL_PRIORITY, sigRock[i],
		"SignalHandler", &pid);
	sigProc[i] = 0;
    }

    for (i = 1; i <= NSIG; ++i)  /* forall !badsig(i) */
	if ((sigsHandled & mysigmask(i)) && sigDelivered[i] == TRUE) {
	    sigDelivered[i] = FALSE;
	    LWP_NoYieldSignal (sigEvents[i]);
	    gotone = TRUE;
	}
    return gotone;
}


/***************************\
* 			    *
*  User-callable routines   *
* 			    *
\***************************/


/* Keep IOMGR process id */
static PROCESS *IOMGR_Id = NIL;

int IOMGR_SoftSig(aproc, arock)
int (*aproc)();
char *arock; {
    register int i;
    for (i=0;i<NSOFTSIG;i++) {
	if (sigProc[i] == 0) {
	    /* a free entry */
	    sigProc[i] = aproc;
	    sigRock[i] = arock;
	    anySigsDelivered = TRUE;
	    iomgr_timeout.tv_sec = 0;
	    iomgr_timeout.tv_usec = 0;
	    return 0;
	}
    }
    return -1;
}


int IOMGR_Initialize()
{
    extern int TM_Init();
    PROCESS pid;

    /* If lready initialized, just return */
    if (IOMGR_Id != NIL) return LWP_SUCCESS;

    /* Init LWP if someone hasn't yet. */
    if (LWP_InitializeProcessSupport (LWP_NORMAL_PRIORITY, &pid) != LWP_SUCCESS)
	return -1;

    /* Initialize request lists */
    if (TM_Init(&Requests) < 0) return -1;

    /* Initialize signal handling stuff. */
    sigsHandled = 0;
    anySigsDelivered = TRUE; /* A soft signal may have happened before
	IOMGR_Initialize:  so force a check for signals regardless */

    return LWP_CreateProcess(IOMGR, STACK_SIZE, 0, 0, "IO MANAGER", &IOMGR_Id);
}

int IOMGR_Finalize()
{
    int status;

    Purge(Requests)
    TM_Final(&Requests);
    status = LWP_DestroyProcess(IOMGR_Id);
    IOMGR_Id = NIL;
    return status;
}

/* signal I/O for anyone who is waiting for a FD or a timeout; not too cheap, since forces select and timeofday check */
long IOMGR_Poll() {
    long readfds, writefds, exceptfds;
    register long code;
    struct timeval tv;
    static long lastPollSleep = 0;

    FT_GetTimeOfDay(&tv, 0);    /* force accurate time check */
    TM_Rescan(Requests);
    for (;;) {
	register struct IoRequest *req;
	struct TM_Elem *expired;
	expired = TM_GetExpired(Requests);
	if (expired == NIL) break;
	req = (struct IoRequest *) expired -> BackPointer;
#ifdef DEBUG
	if (lwp_debug != 0) puts("[Polling SELECT]");
#endif DEBUG
	req->readfds = req->writefds = req->exceptfds = 0;	/* no data ready */
	req->result = 0;				/* no fds ready */
	TM_Remove(Requests, &req->timeout);
#ifdef DEBUG
	req -> timeout.Next = (struct TM_Elem *) 2;
	req -> timeout.Prev = (struct TM_Elem *) 2;
#endif DEBUG
	LWP_QSignal(req->pid);
	req->pid->iomgrRequest = 0;
    }

    /* Collect requests & update times */
    readfds = 0;
    writefds = 0;
    exceptfds = 0;
    FOR_ALL_ELTS(r, Requests, {
	register struct IoRequest *req;
	req = (struct IoRequest *) r -> BackPointer;
	readfds |= req -> readfds;
	writefds |= req -> writefds;
	exceptfds |= req -> exceptfds;
    })
    
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    code = select(32, &readfds, &writefds, &exceptfds, &tv);
    if (code > 0) {
	SignalIO(code, readfds, writefds, exceptfds);
	/* now, sleep for a little while every other second to give RPC a real chance */
	code = time(0);
	if (code >= lastPollSleep+2) {
	    lastPollSleep = code;
	    tv.tv_sec = 0;
	    tv.tv_usec = 150000;
	    IOMGR_Select(0, 0, 0, 0, &tv);
	}
    }
    LWP_DispatchProcess();  /* make sure others run */
    LWP_DispatchProcess();
    return 0;
}

int IOMGR_Select(fds, readfds, writefds, exceptfds, timeout)
    register int *readfds, *writefds, *exceptfds;
    struct timeval *timeout;
{
    register struct IoRequest *request;
    int result;

    /* See if polling request. If so, handle right here */
    if (timeout != NIL) {
	if (timeout->tv_sec == 0 && timeout->tv_usec == 0) {
	    int nfds;
#ifdef DEBUG
	    if (lwp_debug != 0) puts("[Polling SELECT]");
#endif DEBUG
	    nfds = select(MAX_FDS, readfds, writefds, exceptfds, timeout);
	    return (nfds > 1 ? 1 : nfds);
	}
    }

    /* Construct request block & insert */
    request = NewRequest();
    request -> readfds = (readfds != NIL ? *readfds : 0);
    request -> writefds = (writefds != NIL ? *writefds : 0);
    request -> exceptfds = (exceptfds != NIL ? *exceptfds : 0);
    if (timeout == NIL) {
	request -> timeout.TotalTime.tv_sec = -1;
	request -> timeout.TotalTime.tv_usec = -1;
    } else {
	request -> timeout.TotalTime = *timeout;
	/* check for bad request */
	if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec > 999999) {
	    /* invalid arg */
	    iomgr_badtv = *timeout;
	    iomgr_badpid = LWP_ActiveProcess;
	    /* now fixup request */
	    if(request->timeout.TotalTime.tv_sec < 0)
		request->timeout.TotalTime.tv_sec = 1;
	    request->timeout.TotalTime.tv_usec = 100000;
	}
    }

    request -> timeout.BackPointer = (char *) request;

    /* Insert my PID in case of IOMGR_Cancel */
    request -> pid = LWP_ActiveProcess;
    LWP_ActiveProcess -> iomgrRequest = request;

#ifdef DEBUG
    request -> timeout.Next = (struct TM_Elem *) 1;
    request -> timeout.Prev = (struct TM_Elem *) 1;
#endif DEBUG
    TM_Insert(Requests, &request->timeout);

    /* Wait for action */
    LWP_QWait();

    /* Update parameters & return */
    if (readfds != NIL) *readfds = request -> readfds;
    if (writefds != NIL) *writefds = request -> writefds;
    if (exceptfds != NIL) *exceptfds = request -> exceptfds;
    result = request -> result;
    FreeRequest(request);
    return (result > 1 ? 1 : result);
}

int IOMGR_Cancel(pid)
    register PROCESS pid;
{
    register struct IoRequest *request;

    if ((request = pid->iomgrRequest) == 0) return -1;	/* Pid not found */

    request -> readfds = 0;
    request -> writefds = 0;
    request -> exceptfds = 0;
    request -> result = -2;
    TM_Remove(Requests, &request->timeout);
#ifdef DEBUG
    request -> timeout.Next = (struct TM_Elem *) 5;
    request -> timeout.Prev = (struct TM_Elem *) 5;
#endif DEBUG
    LWP_QSignal(request->pid);
    pid->iomgrRequest = 0;

    return 0;
}

/* Cause delivery of signal signo to result in a LWP_SignalProcess of
   event. */
IOMGR_Signal (signo, event)
    int signo;
    char *event;
{
    struct sigvec sv;

    if (badsig(signo))
	return LWP_EBADSIG;
    if (event == NIL)
	return LWP_EBADEVENT;
    sv.sv_handler = SigHandler;
    sv.sv_mask = ~0;	/* mask all signals */
    sv.sv_onstack = 0;
    sigsHandled |= mysigmask(signo);
    sigEvents[signo] = event;
    sigDelivered[signo] = FALSE;
    if (sigvec (signo, &sv, &oldVecs[signo]) == -1)
	return LWP_ESYSTEM;
    return LWP_SUCCESS;
}

/* Stop handling occurances of signo. */
IOMGR_CancelSignal (signo)
    int signo;
{
    if (badsig(signo) || (sigsHandled & mysigmask(signo)) == 0)
	return LWP_EBADSIG;
    sigvec (signo, &oldVecs[signo], (struct sigvec *)0);
    sigsHandled &= ~mysigmask(signo);
    return LWP_SUCCESS;
}

/* This routine calls select is a fashion that simulates the standard sleep routine */
void IOMGR_Sleep (seconds)
  unsigned seconds;
{   struct timeval timeout;

    timeout.tv_sec = seconds;
    timeout.tv_usec = 0;
    IOMGR_Select(0, 0, 0, 0, &timeout);
}
