#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "relay.c,v 1.1.1.1 1995/06/16 17:42:42 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * Multiple file descriptor relay code.
 *
 * Designed to be as fast and efficient
 * as possible for the relaying of multiple
 * streams of data.  Must not ever deadlock.
 *
 * The sos_allocate stuff is not tested.
 * (purpose: to allow listen() to add
 * additional file descriptors on the
 * fly.
 */

#include "sos.h"


/* Debug is noisy */
#ifdef DEBUG
#undef DEBUG
#endif /*DEBUG*/

#ifdef DEBUG
#define DEBUG_HALF_ADD
#endif /*DEBUG*/



static int setopts(int fd);
static char *getfreebuf(struct sos_bdr_freelist_t *free);



/* Relay bytes between these fds until all connections have closed */
int
sos_bdrelay( struct sos_bdr_fdpair **relayptr, 
	    int *countptr, 
	    struct sos_bdr_auxinfo *auxinfoptr)
{
  /* 
   * Comapatiblilty dereferences
   */
  struct sos_bdr_fdpair *relay = *relayptr; /* Vector of half-duplex pairs */
  int count = *countptr;	/*  Total number of half-duplex structs. */
  struct sos_bdr_auxinfo auxinfo=*auxinfoptr; /* Global relay info. */

  /* 
   * Real automatics
   */
  struct sos_bdr_freelist_t freelist, dupfree;
  int width = 0;
  int c;

  if (auxinfo.totbuf < 1 || auxinfo.veclen < 1 )
    {
      errno = EINVAL;
      return(-1);
    }

  /* Set NBIO && OOBINLINE on all fds */
  for(c=0;c<count;c++)
    {
      if (setopts(relay[c].rinfo.fd) || setopts(relay[c].winfo.fd))
	{
	  return -1;	  
	}
      width = MAX(width,relay[c].rinfo.fd);
      width = MAX(width,relay[c].winfo.fd);
    }
  width++;


  /* Allocate free list */
  if ((freelist.free = (caddr_t *)alloca(auxinfo.totbuf * sizeof(caddr_t))) == NULL)
    return(-1);
  if ((dupfree.free = (caddr_t *)alloca(auxinfo.totbuf * sizeof(caddr_t))) == NULL)
    return(-1);
	    
  /* Allocate buffers for free list */
  for(c=0;c<auxinfo.totbuf;c++)
    {
      if ( (freelist.free[c] = (char *)memalign(auxinfo.align,auxinfo.buflen+auxinfo.offset)) == (char *)NULL)
	return(-1);
      dupfree.free[c] = freelist.free[c];
      freelist.free[c] += auxinfo.offset;
    }
  freelist.numfree = auxinfo.totbuf;
  dupfree.numfree = auxinfo.totbuf;

  while(1)
    {
      fd_set rfds, wfds, xfds;
      int ready;

      FD_ZERO(&rfds);
      FD_ZERO(&wfds);
      FD_ZERO(&xfds);
      
      /* shutdown processing here */
      ready = count;		/* Number of possible relayers */
      for(c=0;c<count;c++)
	{
	  switch (relay[c].shutdown)
	    {
	      case 0: break;
	      case 1:
		if (relay[c].bd.cur_read_hand != -1)
		  break;
		/* In shutdown and no more data to write */
		shutdown(relay[c].rinfo.fd,0);
		shutdown(relay[c].winfo.fd,1);
		relay[c].shutdown = 2;
#ifdef DEBUG
		fprintf(stderr,"shutdown on fd %d\n",c);
#endif
		/* No break--fall through */
	      case 2:
		ready--;
		break;
	    }
	}

      /* Exit from loop */
      if (ready == 0 || (auxinfo.common_exit && ready != count))
	break;			/* All relays are shutdown */

      /* Set whether we care about each fd */
      for(c=0;c<count;c++)
	{
	  if ((freelist.numfree || relay[c].curRbuf.iov_base) &&
	       (relay[c].shutdown < 1) &&
	      relay[c].bd.cur_read_hand != relay[c].bd.cur_write_hand)
	      FD_SET(relay[c].rinfo.fd,&rfds);
	  if ((relay[c].shutdown < 2) &&
	      relay[c].bd.cur_read_hand != -1)
	    FD_SET(relay[c].winfo.fd,&wfds);
	}
      memcpy((char *)&xfds,(char *)&rfds,sizeof(rfds));

      if ((ready = select(width, &rfds, &wfds, &xfds, NULL)) < 0)
	{
	  int saveerrno = errno;

	  /* Free relay information */
	  for(c=0;c<auxinfo.totbuf;c++)
	    free(dupfree.free[c]);

	  errno = saveerrno;
	  return -1;
	}

#ifdef DEBUG2
      fprintf(stderr,"selected %d fds\n",ready);
#endif

      /* Check each FD for readyness */
      for(c=0;c<count;c++)
	{
	  /* We cannot handle exceptions (e.g. urgent data) because of
	   * encrypted telnet
	   */
	  if (FD_ISSET(relay[c].rinfo.fd,&xfds))
	    {
#ifdef DEBUG
	      fprintf(stderr,"Exception on fd %d\n",relay[c].rinfo.fd);
#endif
	    }

	  /***************************************************/
	  /*       Check for available read                  */
	  /***************************************************/
	  if ((freelist.numfree || relay[c].curRbuf.iov_base)&&
	      (relay[c].shutdown < 1) &&
	      relay[c].bd.cur_read_hand != relay[c].bd.cur_write_hand)
	    {
	      if (FD_ISSET(relay[c].rinfo.fd,&rfds))
		{
		  int bytes;

		  if (!relay[c].curRbuf.iov_base)
		    {
		      relay[c].curRbuf.iov_base = getfreebuf(&freelist);
		      relay[c].curRbuf.iov_len = 0;
		    }

#ifdef DEBUG2
		  fprintf(stderr,"reading (%x)(%d,%x,%d)\n",
			  relay[c].rinfo.iofun,relay[c].rinfo.fd,
			  relay[c].curRbuf.iov_base+relay[c].curRbuf.iov_len,
			  auxinfo.buflen-relay[c].curRbuf.iov_len);
		  fprintf(stderr,"reading on fd %d\n",c);
#endif

		  if ((bytes = (*relay[c].rinfo.iofun)
		       (relay[c].rinfo.fd,
			relay[c].curRbuf.iov_base+relay[c].curRbuf.iov_len,
			auxinfo.buflen-relay[c].curRbuf.iov_len)) < 1)
		    {		/* error or EOF */
		      if (!bytes || errno != EWOULDBLOCK)
			{	/* prepare to go away */
#ifdef DEBUG
			  if (bytes)
			    perror("read");
			  fprintf(stderr,"pending shutdown on fd %d\n",c);
#endif
			  relay[c].shutdown=1;

			  /* XXX
			   * Need some kind of escape mechinism
			   * so that non-full reads during -B mode
			   * will not get lost.
			   *
			   * Gotos are it, I guess (sigh)
			   */
			  if (auxinfo.fullread && relay[c].curRbuf.iov_len)
			    goto eoftarget;

			  /* Return the current read buf */
			  if (relay[c].curRbuf.iov_base)
			    {
			      freelist.free[freelist.numfree++] = relay[c].curRbuf.iov_base;
			      relay[c].curRbuf.iov_base = NULL;
			      relay[c].curRbuf.iov_len = 0;
			    }
			}
#ifdef DEBUG
		      else
			fprintf(stderr,"EWOULDBLOCK\n");
#endif
		    }
		  else
		    {		/* Normal read, probably */
		      relay[c].curRbuf.iov_len+=bytes;

#ifdef DEBUG
		      fprintf(stderr,"read %d byes on fd %d\n",bytes,relay[c].rinfo.fd);
#endif
		      if (!(auxinfo.fullread && relay[c].curRbuf.iov_len != auxinfo.buflen))
			{	/* Not in -B mode or full read */
			  /* XXX - doublecheck for queue full??? */

			  eoftarget: /* Probably a bad idea--jump here on fullread and eof */

			  
#ifdef DEBUG2
			  fprintf(stderr,"applying filter\n");
#endif
			  if  (relay[c].filter)
			    (relay[c].filter)(relayptr, 
					      countptr, 
					      auxinfoptr, 
					      &freelist,
					      &dupfree,
					      c, 
					      bytes);
#ifdef DEBUG2
			  else
			    fprintf(stderr,"No filter\n");
#endif
			    

#ifdef DEBUG2
			  fprintf(stderr,"appending to write buffer slot %d\n",relay[c].bd.cur_write_hand);
#endif

			  /* Put in current write buf at write hand */
			  relay[c].bd.curWbuf[relay[c].bd.cur_write_hand].iov_base =
			    relay[c].curRbuf.iov_base;

			  relay[c].bd.curWbuf[relay[c].bd.cur_write_hand].iov_len =
			    relay[c].curRbuf.iov_len;

			  /* Put in full write buf at write hand */
			  relay[c].bd.fullbuf[relay[c].bd.cur_write_hand].iov_base =
			    relay[c].curRbuf.iov_base;
			  relay[c].bd.fullbuf[relay[c].bd.cur_write_hand].iov_len =
			    relay[c].curRbuf.iov_len;

			  /* Null out current read buf */
			  relay[c].curRbuf.iov_base = NULL;
			  relay[c].curRbuf.iov_len = 0;

			  /* Check to see if there are no outstanding writes */
			  if (relay[c].bd.cur_read_hand == -1)
			    relay[c].bd.cur_read_hand = relay[c].bd.cur_write_hand;
			      

			  /* Increment write hand to next free slot */
			  relay[c].bd.cur_write_hand++;
			  relay[c].bd.cur_write_hand %= auxinfo.veclen;
#ifdef DEBUG
			  fprintf(stderr,"  writehand = %d, readhand = %d\n",relay[c].bd.cur_write_hand,relay[c].bd.cur_read_hand);
#endif
			}
		    }
		}
	    }

	  /***************************************************/
	  /*       Check for available write                 */
	  /***************************************************/
	  if ((relay[c].shutdown < 2) &&
	      relay[c].bd.cur_read_hand != -1)
	    {
	      if (FD_ISSET(relay[c].winfo.fd,&wfds))
		{
		  int bytes;

#ifdef DEBUG2
		  fprintf(stderr,"writing (%x)(%d,%x,%d)\n",
			  relay[c].winfo.iofun,relay[c].winfo.fd,
			  relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_base,
			  relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_len);
#endif

		  if ((bytes = (*relay[c].winfo.iofun)
		       (relay[c].winfo.fd,
			relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_base,
			relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_len)) < 1)
		    {		/* error or EOF */
		      if (!bytes || errno != EWOULDBLOCK)
			{	/* prepare to go away */
			  int x;

#ifdef DEBUG
			  if (bytes)
			    perror("write");
			  fprintf(stderr,"shutdown on fd %d\n",c);
#endif

			  /* Move queued data to free list */
			  for(x=0;x<auxinfo.veclen;x++)
			    if(relay[c].bd.fullbuf[x].iov_base)
			      {
				freelist.free[freelist.numfree++] =
				  relay[c].bd.fullbuf[x].iov_base;
				relay[c].bd.fullbuf[x].iov_base = NULL;
				relay[c].bd.fullbuf[x].iov_len = 0;
			      }
			  if (relay[c].curRbuf.iov_base)
			    {
			      freelist.free[freelist.numfree++] = relay[c].curRbuf.iov_base;
			      relay[c].curRbuf.iov_base = NULL;
			      relay[c].curRbuf.iov_len = 0;
			    }

			  /* send FINs */
			  shutdown(relay[c].rinfo.fd,0);
			  shutdown(relay[c].winfo.fd,1);

			  /* Mark as dead */
			  relay[c].shutdown=2;
			}
#ifdef DEBUG
		      else
			fprintf(stderr,"EWOULDBLOCK\n");
#endif
		    }
		  else
		    {		/* Normal write */
		      /* Update current-write information */
		      relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_len -= bytes;
		      relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_base += bytes;
		      relay[c].nbytes += bytes;

#ifdef DEBUG
		      fprintf(stderr,"wrote %d bytes on fd %d\n",bytes,relay[c].winfo.fd);
#endif

		      if (relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_len == 0)
			{
			  /* Move buffer to free list--we have written it all */
#ifdef DEBUG2
			  fprintf(stderr,"wrote buffer in slot %d\n",relay[c].bd.cur_read_hand);
#endif

			  freelist.free[freelist.numfree++] =
			    relay[c].bd.fullbuf[relay[c].bd.cur_read_hand].iov_base;

			  /* Zero out queue slot for later usage */
			  relay[c].bd.fullbuf[relay[c].bd.cur_read_hand].iov_base = NULL;
			  relay[c].bd.fullbuf[relay[c].bd.cur_read_hand].iov_len = 0;
			  relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_base = NULL;
			  relay[c].bd.curWbuf[relay[c].bd.cur_read_hand].iov_len = 0;

			  /* Update read pointer */
			  relay[c].bd.cur_read_hand++;
			  relay[c].bd.cur_read_hand %= auxinfo.veclen;
			  if (relay[c].bd.cur_read_hand == (relay[c].bd.cur_write_hand))
			    relay[c].bd.cur_read_hand = -1;
#ifdef DEBUG
			  fprintf(stderr,"  writehand = %d, readhand = %d\n",relay[c].bd.cur_write_hand,relay[c].bd.cur_read_hand);
#endif
			}
		    }
		} /* If we have stuff to read on this fd */
	    } /* If we can read on the fd */
	} /* for each relay pair */
    } /* while(1)*/

  /* Free relay information */
  for(c=0;c<auxinfo.totbuf;c++)
    free(dupfree.free[c]);

  return(0);
}



static int setopts(int fd)
{
  int one = 1;

#ifdef DEBUG
  fprintf(stderr,"Setting NBIO on fd %d\n",fd);
#endif

  if(setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, (char *)&one, sizeof(one)) < 0)
    {
#ifdef DEBUG2
      perror("setsockopt");
      return -1;
#endif
    }

  if (ioctl(fd,FIONBIO,&one) < 0)
    {
#ifdef DEBUG
      perror("FIONBIO");
      return -1;
#endif
    }
  return 0;
}



static char *getfreebuf(struct sos_bdr_freelist_t *freelist)
{
#ifdef DEBUG
  fprintf(stderr,"Getting free buffer (%d @ %x)\n",freelist->numfree-1,freelist->free[freelist->numfree-1]);
#endif

  if (!freelist->numfree)
    return(NULL);

  return(freelist->free[--freelist->numfree]);
}



/*
 * Allocate an auxinfo structure and set all parameters to valid parameters
 * user should free.
 */
struct sos_bdr_auxinfo *
sos_allocate_bdr_auxinfo()
{
  struct sos_bdr_auxinfo *new; 

  if ( (new = (struct sos_bdr_auxinfo *) malloc (sizeof (struct sos_bdr_auxinfo))) != (struct sos_bdr_auxinfo *)NULL )
    {
      new->veclen=0;
      new->totbuf=0;
      new->align=0;
      new->offset=0;
      new->common_exit=0;
      new->fullread=0;
    }
  return (new);
}



int
sos_allocate_relay_half_duplex( struct sos_bdr_fdpair **relayp,
			       int *relaycountp,
			       struct sos_bdr_auxinfo *auxinfop,
			       struct sos_bdr_freelist_t *freelistptr,
			       struct sos_bdr_freelist_t *dupfreeptr,
			       int fdin,
			       int (*readFunc)(int, void *, __SIZE_TYPE__),
			       int fdout,
			       int (*writeFunc)(int, void *, __SIZE_TYPE__))
{
  struct sos_bdr_fdpair *relay = *relayp;
  int relaycount = *relaycountp; /* Deref */
  int i;			/* Dumb counter variable */
  int slotlock=-1;		/* I claim this fd slot */
  int fdcheck = -1;		/* Used to check for slotlock's relay mate */
  int shouldclose=0;		/* True if we should close old descriptor */

  /* 
   * Search for an unused fdpair structure. 
   * If you find a *pair* just use the first and take no further action
   * If you find a singleton, close the fd's and reuse.
   */
  
  for ( i = relaycount; i > 0; i-- )
    {
      if ( relay[i].shutdown == 2 )
	{
	  if ( slotlock == -1 )
	    {
#if defined (DEBUG) || defined (DEBUG_HALF_ADD)
	      printf("allocate_relay(): Grabbing slot: %d\n", i);
#endif /* DEBUG */	      
	      slotlock=i;
	      fdcheck = relay[i].rinfo.fd;
	      shouldclose=1;
	    }
	  
	  else if (relay[i].winfo.fd == fdcheck )
	    /*
	     * Put off closing descriptors until next check.
	     */
	    shouldclose=0;
	}
    }
  
  if ( shouldclose ) 
    {
#if defined (DEBUG) || defined (DEBUG_HALF_ADD)
      printf("allocate_relay(): freeing : %d\n", i);
#endif /* DEBUG */	      
      close (relay[slotlock].rinfo.fd);
      close (relay[slotlock].winfo.fd);
    }


  if ( slotlock == -1 )
    {
      /*
       * There were no available fdpairs.  Must realloc.
       */
#if defined (DEBUG) || defined (DEBUG_HALF_ADD)
      printf("allocate_relay(): Reallocing relay structure\n");
#endif /* DEBUG */	      
      if ( (relay = ( struct sos_bdr_fdpair *) realloc ( relay, (relaycount+1) * sizeof ( struct sos_bdr_fdpair))) == (struct sos_bdr_fdpair *)NULL)
	{
	  return -1;
	}

      *relayp = relay;		/* Pass out new relay vector */
      *relaycountp = ++relaycount;	/* Increment relay count. Side effect!! */
      slotlock = relaycount;	/* Immediately claim this new slot.*/

      /*
       * Check size of buffer free pool. Is it big enough to gaurentee
       * no deadlock will occur? If not realloc both the freelist and
       * the dupfree.
       */
      if ( auxinfop->veclen * (relaycount-1) >= auxinfop->totbuf )
	{
	  /* 
	   * Deadlock potential. Here lies danger. Realoc pool.
	   * XXX For now just make total pool == (current totbuf) + veclen.
	   * necessary, but easy both to code and to understand.
	   * Seth will harrumph of course, :-)
	   */
#if defined (DEBUG) || defined (DEBUG_HALF_ADD)
	  printf("allocate_relay: Realloc()'ing freelist: \n\ttotbuf now: %d\n\tbuffer stress: %d\n", auxinfop->totbuf, (auxinfop->veclen*relaycount));
#endif /* DEBUG */

	  if ( (freelistptr->free=(caddr_t *)realloc(freelistptr->free,(auxinfop->veclen+auxinfop->totbuf) * sizeof(void *))) == (void *)NULL)
	    return -1;
	  if ( (dupfreeptr->free=(caddr_t *)realloc(dupfreeptr->free,(auxinfop->veclen+auxinfop->totbuf) * sizeof(void *))) == (void *)NULL)
	    return -1;
	  
	  /*
	   * Actually allocate new buffers from heap
	   */
	  for (i=auxinfop->totbuf; 
	       i < auxinfop->totbuf + auxinfop->veclen;
	       i++)
	    {
	      if ( (freelistptr->free[i] = (char *)memalign(auxinfop->align,auxinfop->buflen+auxinfop->offset)) == (char *)NULL)
		return -1;
	      dupfreeptr->free[i] = freelistptr->free[i];
	      freelistptr->free[i] += auxinfop->offset;
	    }
	  
	  /* 
	   * Update the number of free buffers in the pool.
	   */
	  auxinfop->totbuf += auxinfop->veclen;
	  freelistptr->numfree += auxinfop->veclen;
	  dupfreeptr->numfree += auxinfop->veclen; 
	}

      
    } /* End allocating new fdpair and incrementing freelist pool */

  relay[slotlock].rinfo.fd = fdin;
  relay[slotlock].rinfo.iofun = readFunc;
  relay[slotlock].winfo.fd = fdout;
  relay[slotlock].winfo.iofun =
#if defined(__sun__) && !defined(__svr4__)
    (int (*)(int, void *, __SIZE_TYPE__))
#endif /* SUNOS */
      writeFunc;


  /* Add filter too ofcourse :-) needs to me a parameter. */
  
  relay[slotlock].curRbuf.iov_base = NULL;
  relay[slotlock].curRbuf.iov_len = 0;
  relay[slotlock].shutdown = 0;
  relay[slotlock].nbytes = 0;
  relay[slotlock].bd.cur_write_hand = 0;
  relay[slotlock].bd.cur_read_hand = -1;
  if (!(relay[slotlock].bd.curWbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfop->veclen)))
    return -1;
  if (!(relay[slotlock].bd.fullbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfop->veclen)))
    return -1;

  return 0;
}


