/* (C) Copyright International Business Machines Corporation 23 January */
/* 1990.  All Rights Reserved. */
/*  */
/* See the file USERAGREEMENT distributed with this software for full */
/* terms and conditions of use. */
#define _BSD 43

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>

#include "li.h"

extern int errno;

/* Used by other chermes processes to mark an fd for closing */

int fd_close[FD_SETSIZE];

/* Keep track of file descriptors and whether we're looking for read */
/* or write events.  Each entry also records the (single) process that */
/* should be revived when I/O activity appears on the file descriptor */
typedef struct fdwait_struct {
  int fd;
  int read;
  int write;
  pcb *waiter;
} FDWAIT;
FDWAIT fd_waitlist[FD_SETSIZE];
int fd_waitcount;

/* Flags for signal processing */
int sigpipe_flag,sigio_flag;

/* Signal handlers */
void sigpipe_handler(sig, code, scp, addr)
     int sig, code;
     struct sigcontext *scp;
     char *addr;
{
#ifdef SIGNALIO_DEBUG
  printf(")Sigpipe detected.\n"); 
#endif
  (void) fflush(stdout);
  sigpipe_flag = 1;
}

void sigchild_handler(sig, code, scp, addr)
     int sig, code;
     struct sigcontext *scp;
     char *addr;
{
  wait(0);
#ifdef SIGNALIO_DEBUG
  printf(")Child reaped.\n");
#endif
  (void) fflush(stdout);
}

void sigio_handler(sig, code, scp, addr)
     int sig, code;
     struct sigcontext *scp;
     char *addr;
{
#ifdef SIGNALIO_DEBUG
  printf(")Sigio detected.\n");
  (void) fflush(stdout);
#endif
  sigio_flag = 1;
}

/* Support functions */
void init_signalio()
{
  /* Initialize globals */
  sigpipe_flag = 0;
  sigio_flag = 1;
  fd_waitcount = 0;
  /* Catch signals */
  (void)signal(SIGPIPE,sigpipe_handler);
  (void)signal(SIGCHLD,sigchild_handler);
  (void)signal(SIGIO,sigio_handler);
}

/* Generate the set representations of the blocked proc's. */
void fd_waitset(readset,writeset,width)
     fd_set *readset,*writeset;
     int *width;
{
  int i,m;

  m = 0;
  FD_ZERO(readset);
  FD_ZERO(writeset);

  for (i=0;i<fd_waitcount;i++)
    {
      if (fd_waitlist[i].read)
	FD_SET(fd_waitlist[i].fd,readset);
      if (fd_waitlist[i].write)
	FD_SET(fd_waitlist[i].fd,writeset);
      if (fd_waitlist[i].fd>m)
	m = fd_waitlist[i].fd;
    }
  *width = m+1;
  assert(*width > 0);
}

/* Block on waiting fd's */
void fd_block()
{
  fd_set readset,writeset;
  int width,result;
  int i;

  assert(fd_waitcount);
  sigio_flag = 1;
  for (i=0;i<fd_waitcount;i++)
    if (fd_close[fd_waitlist[i].fd])
      return; /* Wake up all processes waiting on fd's to be closed */
  fd_waitset(&readset,&writeset,&width);
  result = select(width,&readset,&writeset,NULL,NULL);
  if (result==-1)
    assert (errno == EINTR);
}

/* Poll, wake up all ready processes and remove them from the wait list */
void fd_poll(sched)
     schedblock *sched;
{
  fd_set readset,writeset;
  int width,result;
  struct timeval zero;
  int i, fd;
  status closed;
  pcb *waiter;

  sigio_flag = 0;

  i = 0;
  closed = FALSE;
  while (i<fd_waitcount)
    if (fd_close[fd_waitlist[i].fd])
      {
	/* Remove this entry from the wait list */
	waiter = fd_waitlist[i].waiter;
	fd_waitcount--;
	memcpy(&fd_waitlist[i],&fd_waitlist[fd_waitcount],sizeof(FDWAIT));
	/* Wake up the process */
	if (waiter->state is BLOCKED)
	  sched->wakeup_proc(sched, waiter);
	closed = TRUE;
      }
    else
      i++;

  if (not closed)
    if (fd_waitcount) {
      fd_waitset(&readset,&writeset,&width);
      zero.tv_sec = 0;
      zero.tv_usec = 0;
      result = select(width,&readset,&writeset,NULL,&zero);
      if (result==-1) {
	/* Error */
	if (errno == EINTR)
	  sigio_flag = 1;
	else {
	  fprintf(stderr,"Error on select() in signalio.c.\n");
	  exit(1);
	}
      }
      else if (result) {
	/* Some selectors were awoken */
	i = 0;
	while (i<fd_waitcount) {
	  fd = fd_waitlist[i].fd;
	  if (FD_ISSET(fd,&readset)||FD_ISSET(fd,&writeset)) {
	    /* Remove this entry from the wait list */
	    waiter = fd_waitlist[i].waiter;
	    fd_waitcount--;
	    memcpy(&fd_waitlist[i],&fd_waitlist[fd_waitcount],sizeof(FDWAIT));
	    /* Wake up the process */
	    if (waiter->state is BLOCKED)
	      sched->wakeup_proc(sched, waiter);
	  }
	  else
	    i++;
	}
      }
    }
  /* Clear any processes that are no longer blocked from our table */
  /* (they were problably waiting on multiple fd's, one of which fired) */
  i = 0;
  while (i<fd_waitcount)
    if (fd_waitlist[i].waiter->state isnt BLOCKED) {
      fd_waitcount--;
      memcpy(&fd_waitlist[i],&fd_waitlist[fd_waitcount],sizeof(FDWAIT));
    }
    else
      i++;
}

/* Block a process waiting for I/O activity on a given file descriptor */
void fd_wait(process,fd,read,write,sched)
     pcb *process;
     int fd;
     int read,write;
     schedblock *sched;
{
  int i;

  /* Check that the options are legal */
  assert((fd>=0)&&(fd<FD_SETSIZE));
  assert(read||write);
  /* Add it to the list */
  assert(fd_waitcount+1<FD_SETSIZE);
  fd_waitlist[fd_waitcount].waiter = process;
  fd_waitlist[fd_waitcount].fd = fd;
  fd_waitlist[fd_waitcount].read = read;
  fd_waitlist[fd_waitcount].write = write;
  fd_close[fd] = FALSE;
  fd_waitcount++;
  sched->suspend(sched, process, nil);
}

