/* -*- c -*-
 *
 * Author:      James Brister <brister@vix.com> -- berkeley-unix --
 * Start Date:  Wed Nov 29 23:08:24 1995
 * Project:     INN (innfeed)
 * File:        endpoint.c
 * RCSId:       $Id: endpoint.c,v 1.14 1996/08/20 12:27:35 brister Exp $
 *
 * Copyright:   Copyright (c) 1996 by Internet Software Consortium
 *
 *              Permission to use, copy, modify, and distribute this
 *              software for any purpose with or without fee is hereby
 *              granted, provided that the above copyright notice and this
 *              permission notice appear in all copies.
 *
 *              THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE
 *              CONSORTIUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 *              SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 *              MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET
 *              SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 *              INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 *              WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 *              WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 *              TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
 *              USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Description: The implementation of the EndPoint object class.
 *
 *              The EndPoint class is what gives the illusion 9sort
 *              of, kind of) of threading. Basically it controls a
 *              select loop and a set of EndPoint objects. Each
 *              EndPoint has a file descriptor it is interested
 *              in. The users of the EndPoint tell the EndPoints to
 *              notify them when a read or write has been completed
 *              (or simple if the file descriptor is read or write
 *              ready).
 *
 */

#if ! defined (lint)
static char rcsid [] = "$Id: endpoint.c,v 1.14 1996/08/20 12:27:35 brister Exp $" ;
static void use_rcsid (const char *rId) {   /* Never called */
  use_rcsid (rcsid) ; use_rcsid (rId) ;
} 
#endif

#include "config.h"


#if defined (DO_HAVE_UNISTD)
#include <unistd.h>
#endif /* defined (DO_HAVE_UNISTD) */

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

#if defined (DO_NEED_TIME)
#include <time.h>
#endif /* defined (DO_NEED_TIME) */
#include <sys/time.h>

#include <syslog.h>
#include <sys/uio.h>
#include <sys/socket.h>

#if defined (DO_NEED_STREAM)
#include <sys/stream.h>
#endif



#include "endpoint.h"
#include "buffer.h"
#include "msgs.h"



#if defined (__bsdi__) && (defined (_ANSI_SOURCE) || defined (_POSIX_SOURCE))
  /* why do I have to do this???? */
struct timeval
{
    long tv_sec;                /* seconds */
    long tv_usec;               /* and microseconds */
};
#endif


#if ! defined (UIO_MAXIOV) && defined (MAX_WRITEV_VEC)
#define UIO_MAXIOV MAX_WRITEV_VEC
#elif ! defined (UIO_MAXIOV) && defined (DEF_MAX_IOV) /* svr4 */
#define UIO_MAXIOV DEF_MAX_IOV
#endif


#if ! defined (UIO_MAXIOV)
#error Neither UIO_MAXIOV nor MAX_WRITEV_VEC are defined -- see sysconfig.h
#endif


  /* This is the structure that is the EndPoint */
struct endpoint_s 
{
      /* fields for managing multiple reads into the inBuffer. */
    Buffer *inBuffer ;          /* list of buffers to read into */
    u_int inBufferIdx ;         /* where is list we're at. */
    size_t inIndex ;            /* where in current read we're at */
    size_t inMinLen ;           /* minimum amount to read */
    size_t inAmtRead ;          /* amount read so far */
    EndpRWCB inCbk ;            /* callback for when read complete */
    void *inClientData ;        /* callback data */
    
      /* fields for managing multiple writes from the outBuffer */
    Buffer *outBuffer ;         /* list of buffers to write */
    u_int outBufferIdx ;        /* index into buffer list to start write */
    size_t outIndex ;           /* where in current buffer we write from */
    size_t outSize ;            /* total of all the buffers */
    size_t outAmtWritten ;      /* amount written so far */
    EndpRWCB outCbk ;           /* callback when done */
    void *outClientData ;       /* callback data */

    EndpWorkCbk workCbk ;       /* callback to run if no I/O to do */
    void *workData ;            /* data for callback */

    int myFd ;                  /* the file descriptor we're handling */
    int myErrno ;               /* the errno when I/O fails */
    
    long timesSelected ;        /* incremented every time selected */
    long timesReady ;           /* incremented every time ready for I/O */
};



  /* A private structure. These hold the information on the timer callbacks. */
typedef struct timerqelem_s
{
    TimeoutId id ;              /* the id we gave out */
    time_t when ;               /* The time the timer should go off */
    EndpTCB func ;              /* the function to call */
    void *data ;                /* the client callback data */
    struct timerqelem_s *next ; /* next in the queue */
} *TimerElem, TimerElemStruct ;



  /* set to 1 elsewhere if you want stderr to get what's also being written
     in doWrite. */
int debugWrites ;
void (*gPrintInfo) (void) ;

static int sigFlag ;
static EndPoint mainEndPoint ;

time_t  PrivateTime;


  /* private functions */
static IoStatus doRead (EndPoint endp) ;
static IoStatus doWrite (EndPoint endp) ;
static IoStatus doExcept (EndPoint endp) ;
static void pipeHandler (int s) ;
static void intHandler (int s) ;
static int hitCompare (const void *v1, const void *v2) ;
static void reorderPriorityList (void) ;
static TimerElem newTimerElem (TimeoutId i, time_t w, EndpTCB f, void *d) ;
static TimeoutId timerElemAdd (time_t when, EndpTCB func, void *data) ;
static struct timeval *getTimeout (struct timeval *tout) ;
static void doTimeout (void) ;


  /* Private data */
static size_t maxEndPoints ;

static EndPoint *endPoints ;    /* endpoints indexed on fd */
static EndPoint *priorityList ; /* endpoints indexed on priority */

static int highestFd = -1 ;
static u_int endPointCount = 0 ;
static u_int priorityCount = 0 ;

static fd_set rdSet ;
static fd_set wrSet ;
static fd_set exSet ;

static int keepSelecting ;

static TimerElem timeoutQueue ;
static TimeoutId nextId ;



  /* Create a new EndPoint and hook it to the give file descriptor. All
     fields are initialized to appropriate values.  On the first time this
     function is called the global data structs that manages lists of
     endpoints are intialized. */
EndPoint newEndPoint (int fd) 
{
  EndPoint ep ;

  if (fd < 0)
    return NULL ;
  
  ep = CALLOC (struct endpoint_s, 1) ;
  ASSERT (ep != NULL) ;

  ep->inBuffer = NULL ;
  ep->inBufferIdx = 0 ;
  ep->inIndex = 0 ;
  ep->inMinLen = 0 ;
  ep->inAmtRead = 0 ;
  ep->inCbk = NULL ;
  ep->inClientData = NULL ;

  ep->outBuffer = 0 ;
  ep->outBufferIdx = 0 ;
  ep->outIndex = 0 ;
  ep->outSize = 0 ;
  ep->outCbk = NULL ;
  ep->outClientData = NULL ;
  ep->outAmtWritten = 0 ;

  ep->workCbk = NULL ;
  ep->workData = NULL ;
  
  ep->myFd = fd ;
  ep->myErrno = 0 ;

  ep->timesSelected = 1 ;
  ep->timesReady = 0 ;

  if (endPoints == NULL)        /* happens only the first time through */
    {
      u_int i ;
      
      maxEndPoints = maxFds () ;
      endPoints = CALLOC (EndPoint,maxEndPoints) ;
      ASSERT (endPoints != NULL) ;

      priorityList = CALLOC (EndPoint,maxEndPoints) ;
      ASSERT (priorityList != NULL) ;


      for (i = 0 ; i < maxEndPoints ; i++)
        endPoints [i] = priorityList [i] = NULL ;
    }

  ASSERT ((u_int) fd < maxEndPoints) ;
  ASSERT (endPoints [fd] == NULL) ;
  ASSERT (priorityCount < maxEndPoints) ;
  
  endPoints [fd] = ep ;
  priorityList [priorityCount++] = ep ;
  endPointCount++ ;

  highestFd = (fd > highestFd ? fd : highestFd) ;

  return ep ;
}



/* Delete the given endpoint. The files descriptor is closed and the two
   Buffer arrays are released. */

void delEndPoint (EndPoint ep) 
{
  u_int idx ;

  if (ep == NULL)
    return ;

  ASSERT (endPoints [ep->myFd] == ep) ;

  if (ep->inBuffer != NULL)
    freeBufferArray (ep->inBuffer) ;
  
  if (ep->outBuffer != NULL)
    freeBufferArray (ep->outBuffer) ;

  close (ep->myFd) ;

  /* remove from selectable bits */
  FD_CLR (ep->myFd,&rdSet) ;
  FD_CLR (ep->myFd,&wrSet) ;
  FD_CLR (ep->myFd,&exSet) ;

  /* Adjust the global arrays to account for deleted endpoint. */
  endPoints [ep->myFd] = NULL ;
  if (ep->myFd == highestFd)
    while (endPoints [highestFd] == NULL && highestFd >= 0)
      highestFd-- ;

  for (idx = 0 ; idx < priorityCount ; idx++)
    if (priorityList [idx] == ep)
      break ;

  ASSERT (idx < priorityCount) ; /* i.e. was found */
  ASSERT (priorityList [idx] == ep) ; /* redundant */

  /* this hole will removed in the reorder routine */
  priorityList [idx] = NULL ;

  endPointCount-- ;

  FREE (ep) ;
}

int endPointFd (EndPoint endp)
{
  ASSERT (endp != NULL) ;

  return endp->myFd ;
}




/* Request a read to be done next time there's data. The endpoint
 * ENDP is what will do the read. BUFF is the Buffer the data should
 * go into. FUNC is the callback function to call when the read is
 * complete. CLIENTDATA is the client data to pass back into the
 * callback function. MINLEN is the minimum amount of data to
 * read. If MINLEN is 0 then then BUFF must be filled, otherwise at
 * least MINLEN bytes must be read.
 *
 * BUFF can be null, in which case no read is actually done, but the
 * callback function will be called still. This is useful for
 * listening sockets.
 *
 * Returns 0 if the read couln't be prepared (for example if a read
 * is already outstanding).
 */

int prepareRead (EndPoint endp,
                 Buffer *buffers,
                 EndpRWCB func,
                 void *clientData,
                 int minlen) 
{
  int bufferSizeTotal = 0 ;
  int idx ;
  
  ASSERT (endp != NULL) ;
  
  if (endp->inBuffer != NULL || FD_ISSET (endp->myFd,&rdSet)) 
    return 0 ;                  /* something already there */

  for (idx = 0 ; buffers != NULL && buffers [idx] != NULL ; idx++)
    {
      size_t bs = bufferSize (buffers [idx]) ;
      size_t bds = bufferDataSize (buffers [idx]) ;
      
      bufferSizeTotal += (bs - bds) ;
    }
  
  endp->inBuffer = buffers ;
  endp->inBufferIdx = 0 ;
  endp->inIndex = 0 ;
  endp->inMinLen = (minlen > 0 ? minlen : bufferSizeTotal) ;
  endp->inCbk = func ;
  endp->inAmtRead = 0 ;
  endp->inClientData = clientData ;

  FD_SET (endp->myFd, &rdSet) ;
  FD_SET (endp->myFd, &exSet) ;

  return 1 ;
}



/* Request a write to be done at a later point. ENDP is the EndPoint
 * to do the write. BUFF is the Buffer to write from. FUNC is the
 * function to call when the write is complete. CLIENTDATA is some
 * data to hand back to the callback function.
 *
 * If BUFF is null, then no write will actually by done, but the
 * callback function will still be called. This is useful for
 * connecting sockets.
 *
 * Returns 0 if the write couldn't be prepared (like if a write is
 * still in process.
 */
int prepareWrite (EndPoint endp,
                  Buffer *buffers,
                  EndpRWCB func,
                  void *clientData) 
{
  int bufferSizeTotal = 0 ;
  int idx ;

  ASSERT (endp != NULL) ;
  
  if (endp->outBuffer != NULL || FD_ISSET (endp->myFd,&wrSet))
    return 0 ;                  /* something already there */

  for (idx = 0 ; buffers != NULL && buffers [idx] != NULL ; idx++)
    bufferSizeTotal += bufferDataSize (buffers [idx]) ;
  
  endp->outBuffer = buffers ;
  endp->outBufferIdx = 0 ;
  endp->outIndex = 0 ;
  endp->outCbk = func ;
  endp->outClientData = clientData ;
  endp->outSize = bufferSizeTotal ;
  endp->outAmtWritten = 0 ;

  FD_SET (endp->myFd, &wrSet) ;
  FD_SET (endp->myFd, &exSet) ;

  return 1 ;
}


/* queue up a new timeout request. to go off at a specific time. */
TimeoutId prepareWake (EndpTCB func, time_t timeToWake, void *clientData) 
{
  TimeoutId id ;
  time_t now = theTime() ;
  int timeToSleep ;

  if (now > timeToWake)
    return 0 ;
  
  timeToSleep = timeToWake - now ;
  
  id = timerElemAdd (timeToWake,func,clientData) ;

#if 0
  dprintf (1,"Preparing wake %d at date %ld for %d seconds\n",
           (int) id, (long) now, timeToSleep) ;
#endif

  return id ;
}


/* queue up a new timeout request to off TIMETOSLEEP seconds from now */
TimeoutId prepareSleep (EndpTCB func, int timeToSleep, void *clientData) 
{
  time_t now = theTime() ;
  TimeoutId id ;
  
  id = timerElemAdd (now + timeToSleep,func,clientData) ;

#if 0
  dprintf (1,"Preparing sleep %d at date %ld for %d seconds\n",
           (int) id, (long) now, timeToSleep) ;
#endif

  return id ;
}


/* Remove a timeout that was previously prepared. 0 is a legal value that
   is just ignored. */
bool removeTimeout (TimeoutId tid)
{
  TimerElem n = timeoutQueue ;
  TimerElem p = NULL ;

  if (tid == 0)
    return true ;
  
  while (n != NULL && n->id != tid)
    {
      p = n ;
      n = n->next ;
    }

  if (n == NULL)
    return false ;

  if (p == NULL)                /* at the head. */
    timeoutQueue = n->next ;
  else
    p->next = n->next ;

  FREE (n) ;
        
  return true ;
}


/* The main routine. This is a near-infinite loop that drives the whole
   program. */
void Run () 
{
  fd_set rSet ;
  fd_set wSet ;
  fd_set eSet ;

  keepSelecting = 1 ;
  signal (SIGPIPE, pipeHandler) ;
  signal (SIGINT, intHandler) ;
  
  while (keepSelecting)
    {
      struct timeval timeout ;
      struct timeval *twait ;
      int sval ;
      u_int idx ;
      bool modifiedTime = false ;
      
      twait = getTimeout (&timeout) ;

      memcpy (&rSet,&rdSet,sizeof (rdSet)) ;
      memcpy (&wSet,&wrSet,sizeof (wrSet)) ;
      memcpy (&eSet,&exSet,sizeof (exSet)) ;

      if (highestFd < 0 && twait == NULL) /* no fds and no timeout */
        break ;
      else if (twait != NULL && (twait->tv_sec != 0 || twait->tv_usec != 0))
        {
            /* if we have any workprocs registered we poll rather than
               block on the fds */
          for (idx = 0 ; idx < priorityCount ; idx++)
            if (priorityList [idx] != NULL &&
                priorityList [idx]->workCbk != NULL)
              {
                modifiedTime = true ;
                twait->tv_sec = 0 ;
                twait->tv_usec = 0 ;

                break ;
              }
        }
        
      sval = select (highestFd + 1, &rSet, &wSet, &eSet, twait) ;

      timePasses () ;
      
      if (sval == 0 && twait == NULL)
        die ("No fd's ready and no timeouts") ;
      else if (sval < 0 && errno == EINTR)
        {
          if (sigFlag && gPrintInfo != NULL)
            {
              gPrintInfo () ;
              sigFlag = 0 ;
            }
        }
      else if (sval < 0) 
        {
          syslog (LOG_ERR,BAD_SELECT,sval) ;
          stopRun () ;
        }
      else if (sval > 0)
        {
          IoStatus rval ;
          int readyCount = sval ;
          
          if (sigFlag && gPrintInfo != NULL)
            {
              gPrintInfo () ;
              sigFlag = 0 ;
            }
          
          for (idx = 0 ; idx < priorityCount ; idx++)
            {
              EndPoint ep = priorityList [idx] ;

              if (readyCount > 0 && ep != NULL) 
                {
                  int fd = ep->myFd ;

                  ep->timesSelected += (FD_ISSET (fd,&wrSet) ? 1 : 0) ;
                  ep->timesSelected += (FD_ISSET (fd,&rdSet) ? 1 : 0) ;
  
                  FD_CLR (fd, &exSet) ;

                  if (FD_ISSET (fd,&rSet))
                    {
                      FD_CLR (fd, &rdSet) ;

                      ep->timesReady++ ;
                      readyCount-- ;
                      
                      if ((rval = doRead (ep)) != IoIncomplete)
                        {
                          Buffer *buff = ep->inBuffer ;

                          /* incase callback wants to issue read */
                          ep->inBuffer = NULL ; 
                          
                          if (ep->inCbk != NULL)
                            (*ep->inCbk) (ep,rval,buff,ep->inClientData) ;
                          else
                            freeBufferArray (buff) ;
                        }
                      else
                        {
                          FD_SET (ep->myFd, &rdSet) ;
                          FD_SET (ep->myFd, &exSet) ;
                        }
                    }

                  /* get it again as the read callback may have deleted the */
                  /* endpoint */
                  ep = priorityList [idx] ;
                  
                  if (readyCount > 0 && ep != NULL && FD_ISSET (fd,&wSet))
                    {
                      FD_CLR (fd, &wrSet) ;

                      ep->timesReady++ ;
                      readyCount-- ;
                      
                      if ((rval = doWrite (ep)) != IoIncomplete)
                        {
                          Buffer *buff = ep->outBuffer ;

                          /* incase callback wants to issue a write */
                          ep->outBuffer = NULL ;        
                          
                          if (ep->outCbk != NULL)
                            (*ep->outCbk) (ep,rval,buff,ep->outClientData) ;
                          else
                            freeBufferArray (buff) ;
                        }
                      else
                        {
                          FD_SET (ep->myFd, &wrSet) ;
                          FD_SET (ep->myFd, &exSet) ;
                        }
                    }
                  
                  /* get it again as the write callback may have deleted the */
                  /* endpoint */
                  ep = priorityList [idx] ;

                  if (readyCount > 0 && ep != NULL && FD_ISSET (fd,&eSet))
                    doExcept (ep) ;
                }
            }
          
          reorderPriorityList () ;
        }
      else if (sval == 0 && !modifiedTime)
        doTimeout () ;

        /* now we're done processing all read fds and/or the
           timeout(s). Next we do the work callbacks for all the endpoints
           whose fds weren't ready. */
      for (idx = 0 ; idx < priorityCount ; idx++)
        {
          EndPoint ep = priorityList [idx] ;

          if (ep != NULL)
            {
              int fd = ep->myFd ;
              
              if ( !FD_ISSET (fd,&rSet) && !FD_ISSET (fd,&wSet) )
                if (ep->workCbk != NULL)
                  {
                    EndpWorkCbk func = ep->workCbk ;
                    void *data = ep->workData ;

                    ep->workCbk = NULL ;
                    ep->workData = NULL ;
                    func (ep,data) ;
                  }
              
            }
        }
    }
}

void *addWorkCallback (EndPoint endp, EndpWorkCbk cbk, void *data)
{
  void *oldBk = endp->workData ;
  
  endp->workCbk = cbk ;
  endp->workData = data ;

  return oldBk ;
}

/* Tell the Run routine to stop next time around. */
void stopRun () 
{
  keepSelecting = 0 ;
}


int endPointErrno (EndPoint endp)
{
  return endp->myErrno ;
}

bool readIsPending (EndPoint endp) 
{
  return (endp->inBuffer != NULL ? true : false) ;
}

bool writeIsPending (EndPoint endp)
{
  return (endp->outBuffer != NULL ? true : false) ;
}

void setMainEndPoint (EndPoint endp)
{
  mainEndPoint = endp ;
}



/***********************************************************************/
/*                      STATIC FUNCTIONS BELOW HERE                    */
/***********************************************************************/


/*
 * called when the file descriptor on this endpoint is read ready.
 */
static IoStatus doRead (EndPoint endp) 
{
  int i = 0 ;
  u_int idx ;
  u_int bCount = 0 ;
  struct iovec *vp = NULL ;
  Buffer *buffers = endp->inBuffer ;
  u_int currIdx = endp->inBufferIdx ;
  size_t amt = 0 ;
  IoStatus rval = IoIncomplete ;

  for (i = currIdx ; buffers && buffers [i] != NULL ; i++)
    bCount++ ;

  /* if UIO_MAXIOV is undefined then set MAX_WRITEV_VEC in config.h */
  bCount = (bCount > UIO_MAXIOV ? UIO_MAXIOV : bCount) ;

  i = 0 ;

  /* now set up the iovecs for the readv */
  if (bCount > 0)
    {
      char *bbase ;
      size_t bds, bs ;

      vp = CALLOC (struct iovec, bCount) ;
      ASSERT (vp != NULL) ;

      bbase = bufferBase (buffers[currIdx]) ;
      bds = bufferDataSize (buffers[currIdx]) ;
      bs = bufferSize (buffers [currIdx]) ;

      /* inIndex is an index in the virtual array of the read, not directly
         into the buffer. */
      vp[0].iov_base = bbase + bds + endp->inIndex ;
      vp[0].iov_len = bs - bds - endp->inIndex ;

      amt = vp[0].iov_len ;
      
      for (idx = currIdx + 1 ; idx < bCount ; idx++)
        {
          bbase = bufferBase (buffers[idx]) ;
          bds = bufferDataSize (buffers[idx]) ;
          bs = bufferSize (buffers [idx]) ;
      
          vp [idx].iov_base = bbase ;
          vp [idx].iov_len = bs - bds ;
          amt += (bs - bds) ;
        }

      i = readv (endp->myFd,vp,(int) bCount) ;

      if (i > 0)
        {
          size_t readAmt = (size_t) i ;
            
          endp->inAmtRead += readAmt ;
          
          /* check if we filled the first buffer */
          if (readAmt >= vp[0].iov_len)
            {                   /* we did */
              bufferIncrDataSize (buffers[currIdx], vp[0].iov_len) ;
              readAmt -= vp [0].iov_len ;
              endp->inBufferIdx++ ;
            }
          else
            {
              endp->inIndex += readAmt ;
              bufferIncrDataSize (buffers[currIdx], readAmt) ;
              readAmt = 0 ;
            }
          
          /* now check the rest of the buffers */
          for (idx = 1 ; readAmt > 0 ; idx++)
            {
              ASSERT (idx < bCount) ;

              bs = bufferSize (buffers [currIdx + idx]) ;
              bbase = bufferBase (buffers [currIdx + idx]) ;
              bds = bufferDataSize (buffers [currIdx + idx]) ;
              
              if (readAmt >= (bs - bds))
                {
                  bufferSetDataSize (buffers [currIdx + idx],bs) ;
                  readAmt -= bs ;
                  endp->inBufferIdx++ ;
                }
              else
                {
                  endp->inIndex = readAmt ;
                  bufferIncrDataSize (buffers [currIdx + idx], readAmt) ;
                  memset (bbase + bds + readAmt, 0, bs - bds - readAmt) ;
                  readAmt = 0 ;
                }
            }

          if (endp->inAmtRead >= endp->inMinLen)
            {
              endp->inIndex = 0 ;
              rval = IoDone ;
            }
        }
      else if (i < 0 && errno != EINTR)
        {
          endp->myErrno = errno ;
          rval = IoFailed ;
        }
      else if (i == 0)
        rval = IoEOF ;
      else                      /* i < 0 && errno == EINTR */
        rval = IoIncomplete ;

      FREE (vp) ;
    }
  else
    rval = IoDone ;
  
  return rval ;
}

/* called when the file descriptor on the endpoint is write ready. */
static IoStatus doWrite (EndPoint endp)
{
  u_int idx ;
  int i = 0 ;
  size_t amt = 0 ;
  u_int bCount = 0 ;
  struct iovec *vp = NULL ;
  Buffer *buffers = endp->outBuffer ;
  u_int currIdx = endp->outBufferIdx ;
  IoStatus rval = IoIncomplete ;
  
  for (i = currIdx ; buffers && buffers [i] != NULL ; i++)
    bCount++ ;

  /* if UIO_MAXIOV is undefined then set MAX_WRITEV_VEC in config.h */
  bCount = (bCount > UIO_MAXIOV ? UIO_MAXIOV : bCount) ;

  i = 0 ;
  
  if (bCount > 0)
    {
      vp = CALLOC (struct iovec, bCount) ;
      ASSERT (vp != NULL) ;


      vp[0].iov_base = bufferBase (buffers [currIdx]) ;
      vp[0].iov_base = (char *) vp[0].iov_base + endp->outIndex ;
      vp[0].iov_len = bufferDataSize (buffers [currIdx]) - endp->outIndex ;

      amt = vp[0].iov_len ;
      
      for (idx = 1 ; idx < bCount ; idx++)
        {
          vp [idx].iov_base = bufferBase (buffers [idx + currIdx]) ;
          vp [idx].iov_len = bufferDataSize (buffers [idx + currIdx]) ;
          amt += vp[idx].iov_len ;
        }

#if 1
      if (debugWrites) 
        {
          /* nasty mixing, but stderr is unbuffered usually. It's debugging only */
          dprintf (5,"About to write this:================================\n") ;
          writev (2,vp,bCount) ;
          dprintf (5,"end=================================================\n") ;
        }
      
#endif

      ASSERT (endp->myFd >= 0) ;
      ASSERT (vp != 0) ;
      ASSERT (bCount > 0) ;
      
      i = writev (endp->myFd,vp,(int) bCount) ;

      if (i > 0)
        {
          size_t writeAmt = (size_t) i ;
          
          endp->outAmtWritten += writeAmt ;

          /* now figure out which buffers got completely written */
          for (idx = 0 ; writeAmt > 0 ; idx++)
            {
              if (writeAmt >= vp [idx].iov_len)
                {
                  endp->outBufferIdx++ ;
                  endp->outIndex = 0 ;
                  writeAmt -= vp [idx].iov_len ;
                }
              else
                {
                  /* this buffer was not completly written */
                  endp->outIndex += writeAmt ;
                  writeAmt = 0 ;
                }
            }

          if (endp->outAmtWritten == endp->outSize)
            rval = IoDone ;
        }
      else if (i < 0)
        {
          endp->myErrno = errno ;
          rval = IoFailed ;
        }
      else
        dprintf (1,"Wrote 0 bytes in doWrite()?\n") ;

      FREE (vp) ;
    }
  else
    rval = IoDone ;

  return rval ;
}


static IoStatus doExcept (EndPoint endp)
{
  int optval, size ;
  int fd = endPointFd (endp) ;

  if (getsockopt (fd, SOL_SOCKET, SO_ERROR,
                  (GETSOCKOPT_ARG) &optval, &size) != 0)
    syslog (LOG_ERR,GETSOCKOPT_FAILURE,fd) ;
  else if (optval != 0)
    {
      errno = optval ;
      syslog (LOG_ERR,EXCEPTION_NOTICE,fd) ;
    }
  else
    syslog (LOG_ERR,UNKNOWN_EXCEPTION,fd) ;

  sleep (5) ;
  abort () ;

  /* Not reached */
  return IoFailed ;
}

#if 0
static void endPointPrint (EndPoint ep, FILE *fp)
{
  fprintf (fp,"EndPoint [%p]: fd [%d]\n",(void *) ep, ep->myFd) ;
}
#endif

static void intHandler (int s)
{
  sigFlag = 1 ;
  signal (s, intHandler) ;
}


static void pipeHandler (int s)
{
  signal (s, pipeHandler) ;
}


/* compare the hit ratio of two endpoint for qsort. We're sorting the
   endpoints on their relative activity */
static int hitCompare (const void *v1, const void *v2)
{
  const EndPoint e1 = *((const EndPoint *) v1) ;
  const EndPoint e2 = *((const EndPoint *) v2) ;
  double e1Hit = 0.0 ;
  double e2Hit = 0.0 ;

  if (e1 != NULL && e1->timesSelected != 0)
    e1Hit = ((double) e1->timesReady) / ((double) e1->timesSelected) ;
  if (e2 != NULL && e2->timesSelected != 0)
    e2Hit = ((double) e2->timesReady) / ((double) e2->timesSelected) ;

  if (e1 == NULL && e2 == NULL)
    return 0 ;
  else if (e1 == NULL)
    return 1 ;
  else if (e2 == NULL)
    return -1 ;
  else if (e1 == mainEndPoint)
    return -1 ;
  else if (e2 == mainEndPoint)
    return 1 ;
  else if (e1Hit < e2Hit)
    return 1 ;
  else if (e1Hit > e2Hit)
    return -1 ;

  return 0 ;
}



/* We maintain the endpoints in order of the percent times they're ready
   for read/write when they've been selected. This helps us favour the more
   active endpoints. */
static void reorderPriorityList (void)
{
  qsort (priorityList, (size_t)priorityCount, sizeof (EndPoint), &hitCompare);

  while (priorityCount > 0 && priorityList [priorityCount - 1] == NULL)
    priorityCount-- ;
}




/* create a new timeout data structure properly initialized. */
static TimerElem newTimerElem (TimeoutId i, time_t w, EndpTCB f, void *d)
{
  TimerElem p = CALLOC (TimerElemStruct,1) ;
  ASSERT (p != NULL) ;


  ASSERT (p != NULL) ;
  
  p->id = i ;
  p->when = w ;
  p->func = f ;
  p->data = d ;
  p->next = NULL ;

  return p ;
}



/* add a new timeout structure to the global list. */
static TimeoutId timerElemAdd (time_t when, EndpTCB func, void *data)
{
  TimerElem p = newTimerElem (++nextId,when,func,data) ;
  TimerElem n = timeoutQueue ;
  TimerElem q = NULL ;
  
  while (n != NULL && n->when <= when)
    {
      q = n ;
      n = n->next ;
    }

  if (n == NULL && q == NULL)   /* empty list so put at head */
    timeoutQueue = p ;
  else if (q == NULL)           /* put at head of list */
    {
      p->next = timeoutQueue ;
      timeoutQueue = p ;
    }
  else if (n == NULL)           /* put at end of list */
    q->next = p ;
  else                          /* in middle of list */
    {
      p->next = q->next ;
      q->next = p ;
    }

  return p->id ;
}


/* Fills in TOUT with the timeout to use on the next call to
 * select. Returns TOUT. If there is no timeout, then returns NULL.  If the
 * timeout has already passed, then it calls the timeout handling routine
 * first.
 */
static struct timeval *getTimeout (struct timeval *tout)
{
  struct timeval *rval = NULL ;
  
  if (timeoutQueue != NULL)
    {
      time_t now = theTime() ;

      while (timeoutQueue && now > timeoutQueue->when)
        doTimeout () ;
          
      if (timeoutQueue != NULL && now == timeoutQueue->when)
        {
          tout->tv_sec = 0 ;
          tout->tv_usec = 0 ;
          rval = tout ;
        }
      else if (timeoutQueue != NULL)
        {
          tout->tv_sec = timeoutQueue->when - now ;
          tout->tv_usec = 0 ;
          rval = tout ;
        }
    }

  return rval ;
}

      
  


  
static void doTimeout (void)
{
  EndpTCB cbk = timeoutQueue->func ;
  void *data = timeoutQueue->data ;
  TimerElem p = timeoutQueue ;
  TimeoutId tid = timeoutQueue->id ;

  timeoutQueue = timeoutQueue->next ;

  FREE (p) ;
  
  if (cbk)
    (*cbk) (tid, data) ;        /* call the callback function */
}





#if defined (WANT_MAIN)


#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>

#define BUFF_SIZE 100


void timerCallback (void *cd) ;
void timerCallback (void *cd)
{
  dprintf (1,"Callback \n") ;
}

  
void lineIsWritten (EndPoint ep, IoStatus status, Buffer *buffer, void *data);
void lineIsWritten (EndPoint ep, IoStatus status, Buffer *buffer, void *data)
{
  int i ;
  
  if (status == IoDone)
    dprintf (1,"LINE was written\n") ;
  else
    {
      int oldErrno = errno ;
      
      errno = endPointErrno (ep) ;
      perror ("write failed") ;
      errno = oldErrno ;
    }

  for (i = 0 ; buffer && buffer [i] ; i++)
    delBuffer (buffer [i]) ;
}

void lineIsRead (EndPoint myEp, IoStatus status, Buffer *buffer, void *data);
void lineIsRead (EndPoint myEp, IoStatus status, Buffer *buffer, void *d)
{
  Buffer *writeBuffers, *readBuffers ;
  Buffer newBuff1, newBuff2 ;
  Buffer newInputBuffer ;
  char *data, *p ;
  size_t len ;

  if (status == IoFailed)
    {
      int oldErrno = errno ;

      errno = endPointErrno (myEp) ;
      perror ("read failed") ;
      errno = oldErrno ;

      return ;
    }
  else if (status == IoEOF)
    {
      dprintf (1,"EOF on endpoint.\n") ;
      delEndPoint (myEp) ;

      return ;
    }
  
  
  data = bufferBase (buffer[0]) ;
  len = bufferDataSize (buffer[0]) ;
  
  if (data [len - 1] == '\r' || data [len - 1] == '\n')
    bufferDecrDataSize (buffer [0],1) ;
  if (data [len - 1] == '\r' || data [len - 1] == '\n')
    bufferDecrDataSize (buffer [0],1) ;

  data [len] = '\0' ;
  
  dprintf (1,"Got a line: %s\n", data) ;

  newBuff1 = newBuffer (len + 50) ;
  newBuff2 = newBuffer (len + 50) ;
  newInputBuffer = newBuffer (BUFF_SIZE) ;
  
  p = bufferBase (newBuff1) ; 
  strcpy (p, "Thanks for that \"") ;
  bufferSetDataSize (newBuff1,strlen (p)) ;
  
  p = bufferBase (newBuff2) ;
  strcpy (p,"\" very tasty\n") ;
  bufferSetDataSize (newBuff2,strlen (p)) ;

  writeBuffers = makeBufferArray (newBuff1,buffer[0],newBuff2,NULL) ;
  readBuffers = makeBufferArray (newInputBuffer,NULL) ;
  
  prepareWrite (myEp,writeBuffers,lineIsWritten,NULL) ;
  prepareRead (myEp,readBuffers,lineIsRead,NULL,1) ;

#if 0
  myEp->registerWake (&timerCallback,theTime() + 7,0) ;
#endif
}


static void printDate (TimeoutId tid, void *data) ;
static void printDate (TimeoutId tid, void *data)
{
  time_t t ;

  t = theTime() ;
  
  dprintf (1,"Timeout (%d) time now is %ld %s",
           (int) tid,(long) t,ctime(&t)) ;

  if (timeoutQueue == NULL) 
    {
      int ti = (rand () % 10) + 1 ;

      prepareSleep (printDate,ti,data) ;
    }
}

TimeoutId rm ;

static void Timeout (TimeoutId tid, void *data) ;
static void Timeout (TimeoutId tid, void *data)
{
  static int seeded ;
  static int howMany ;
  static int i ;
  time_t t = theTime() ;

  if ( !seeded )
    {
      srand (t) ;
      seeded = 1 ;
    }

  dprintf (1,"Timeout (%d) time now is %ld %s",
           (int) tid, (long) t,ctime(&t)) ;
  
  if (timeoutQueue != NULL && timeoutQueue->next != NULL)
    dprintf (1,"%s timeout id %d\n",
             (removeTimeout (rm) ? "REMOVED" : "FAILED TO REMOVE"), rm) ;
  rm = 0 ;
  
  howMany = (rand() % 10) + (timeoutQueue == NULL ? 1 : 0) ;

  for (i = 0 ; i < howMany ; i++ )
    {
      TimeoutId id ;
      int count = (rand () % 30) + 1 ;

      id = (i % 2 == 0 ? prepareSleep (Timeout,count,data)
            : prepareWake (Timeout,t + count,data)) ;

      if (rm == 0)
        rm = id ;
    }
}


void newConn (EndPoint ep, IoStatus status, Buffer *buffer, void *d) ;
void newConn (EndPoint ep, IoStatus status, Buffer *buffer, void *d)
{
  EndPoint newEp ;
  struct sockaddr_in in ;
  Buffer *readBuffers ;
  Buffer newBuff = newBuffer (BUFF_SIZE) ;
  int len = sizeof (in) ;
  int fd ;

  memset (&in, 0, sizeof (in)) ;
  
  fd = accept (ep->myFd, (struct sockaddr *) &in, &len) ;

  if (fd < 0)
    {
      perror ("::accept") ;
      return ;
    }
  
  newEp = newEndPoint (fd) ;

  prepareRead (ep, NULL, newConn,NULL,0) ;

  readBuffers = makeBufferArray (newBuff,NULL) ;

  prepareRead (newEp, readBuffers, lineIsRead, NULL, 1) ;

  dprintf (1,"Set up a new connection\n");
}


int main (int argc, char **argv)
{
  EndPoint accConn ;
  struct sockaddr_in accNet ;
  int accFd = socket (AF_INET,SOCK_STREAM,0) ;
  u_short port = atoi (argc > 1 ? argv[1] : "10000") ;
  time_t t = theTime() ;


  program = strrchr (argv[0],'/') ;

  if (!program)
    program = argv [0] ;
  else
    program++ ;

  ASSERT (accFd >= 0) ;

  memset (&accNet,0,sizeof (accNet)) ;
  accNet.sin_family = AF_INET ;
  accNet.sin_addr.s_addr = htonl (INADDR_ANY) ;
  accNet.sin_port = htons (port) ;

  openlog (program, LOG_PERROR | LOG_PID, LOG_NEWS) ;
  
  if (bind (accFd, (struct sockaddr *) &accNet, sizeof (accNet)) < 0)
    {
      perror ("bind: %m") ;
      exit (1) ;
    }

  listen (accFd,5) ;
  
  accConn = newEndPoint (accFd) ;

  prepareRead (accConn,NULL,newConn,NULL,0) ;

  prepareSleep (Timeout,5,(void *) 0x10) ;

  t = theTime() ;
  dprintf (1,"Time now is %s",ctime(&t)) ;
  
  prepareWake (printDate,t + 16,NULL) ;

  Run () ;

  return 0;
}
#endif
