/* $Id: ps_io.c,v 1.1.1.1 90/11/28 16:45:06 altenhof Exp $ */

/*
 * Copyright (C) 1990 by Digital Equipment Corporation.
 * 
 * Author: Michael P. Altenhofen, CEC Karlsruhe e-mail:
 * Altenhofen@kampus.enet.dec.com
 * 
 * This file ist part of Shared X
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation without fee is hereby granted, but only for non-profit  use
 * and distribution,  and provided  that the copyright notice and this notice
 * is preserved on all copies.
 * 
 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * DIGITAL BE LIABLE FOR ANY SPECIAL, 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.
 */

/*****************************************************************
 * i/o functions
 *
 *   WriteToClient, ReadRequestFromClient
 *
 *****************************************************************/

#define PUBLIC extern

#define _USE_STRUCTS_
#define _USE_MASKS_
#define _USE_PROTO_
#define _XEVENTS_

#include "glob.h"

#include "utils.h"

#undef PUBLIC

#include <errno.h>
#include <sys/types.h>
#include <sys/uio.h>

static Bool CriticalOutputPending;
static int timesThisConnection = 0;

extern int errno;

#define request_length(req, cli) ((cli->swapped ? \
	lswaps((req)->length) : (req)->length) << 2)
#define MAX_TIMES_PER         10

/*****************************************************************
 * ReadRequestFromClient
 *    Returns one request from client.  If the client misbehaves,
 *    returns NULL.  The dispatcher closes down all misbehaving clients.
 *
 *        client:  index into bit array returned from WaitForSomething()
 *
 *        status: status is set to
 *            > 0 the number of bytes in the request if the read is sucessful
 *            = 0 if action would block (entire request not ready)
 *            < 0 indicates an error (probably client died)
 *
 *        oldbuf:
 *            To facilitate buffer management (e.g. on multi-processor
 *            systems), the diX layer must tell the OS layer when it is
 *            done with a request, so the parameter oldbuf is a pointer
 *            to a request that diX is finished with.  In the
 *            sample implementation, which is single threaded,
 *            oldbuf is ignored.  We assume that when diX calls
 *            ReadRequestFromClient(), the previous buffer is finished with.
 *
 *    The returned string returned must be contiguous so that it can be
 *    cast in the dispatcher to the correct request type.  Because requests
 *    are variable length, ReadRequestFromClient() must look at the first 4
 *    bytes of a request to determine the length (the request length is
 *    always the 3rd byte in the request).
 *
 *    Note: in order to make the server scheduler (WaitForSomething())
 *    "fair", the ClientsWithInput mask is used.  This mask tells which
 *    clients have FULL requests left in their buffers.  Clients with
 *    partial requests require a read.  Basically, client buffers
 *    are drained before select() is called again.  But, we can't keep
 *    reading from a client that is sending buckets of data (or has
 *    a partial request) because others clients need to be scheduled.
 *****************************************************************/


/* ARGSUSED */
char *
ReadRequestFromClient (who, status, oldbuf)
  ClientPtr who;
  int *status;			/* read at least n from client */
  char *oldbuf;
{
#define YieldControl()				\
        { isItTimeToYield = TRUE;		\
	  timesThisConnection = 0; }
#define YieldControlNoInput()			\
        { YieldControl();			\
	  BITCLEAR(BITS(ClientsWithInput), client); }
#define YieldControlAndReturnNull()		\
        { YieldControlNoInput();		\
	  return((char *) NULL ); }

  OsCommPtr oc = (OsCommPtr) who->osPrivate;
  int client = oc->fd;
  int result, gotnow, needed;
  register ConnectionInput *pBuff;
  register xReq *request;

  /* ignore oldbuf, just assume we're done with prev. buffer */

  if (client == -1) {
    ErrorF ("OH NO, %d translates to -1\n", who);
    return ((char *) NULL);
  }

  pBuff = &inputBuffers[client];
  pBuff->bufptr += pBuff->lenLastReq;
  pBuff->lenLastReq = 0;

  /* handle buffer empty or full case first */

  if ((pBuff->bufptr - pBuff->buffer) >= pBuff->bufcnt) {
    result = read (client, pBuff->buffer, pBuff->size);
    if (result < 0) {
      if (errno == EWOULDBLOCK)
	*status = 0;
      else
	*status = -1;
      YieldControlAndReturnNull ();
    }
    else if (result == 0) {
      *status = -1;
      YieldControlAndReturnNull ();
    }
    else {
      pBuff->bufcnt = result;
      /* free up some space after huge requests */
      if ((pBuff->size > BUFWATERMARK) && (result < BUFSIZE)) {
	pBuff->size = BUFSIZE;
	pBuff->buffer = (char *) Xrealloc (pBuff->buffer, pBuff->size);
	if (pBuff->buffer == NULL) {
	  *status = -1;
	  YieldControlAndReturnNull ();
	}
      }
      pBuff->bufptr = pBuff->buffer;
    }
  }
  /* now look if there is enough in the buffer */

  request = (xReq *) pBuff->bufptr;
  gotnow = pBuff->bufcnt + pBuff->buffer - pBuff->bufptr;

  if (gotnow < sizeof (xReq))
    needed = sizeof (xReq) - gotnow;
  else {
    needed = request_length (request, who);
    if (needed > MAXBUFSIZE) {
      *status = -1;
      YieldControlAndReturnNull ();
    }
    if (needed <= 0)
      needed = sizeof (xReq);
  }

  /*
   * If the needed amount won't fit in what's remaining, move everything to
   * the front of the buffer.  If the entire header isn't available, move
   * what's there too
   */
  if ((pBuff->bufptr + needed - pBuff->buffer > pBuff->size) ||
      (gotnow < sizeof (xReq))) {
    bcopy (pBuff->bufptr, pBuff->buffer, gotnow);
    pBuff->bufcnt = gotnow;
    if (needed > pBuff->size) {
      pBuff->size = needed;
      pBuff->buffer = (char *) Xrealloc (pBuff->buffer, needed);
      if (pBuff->buffer == NULL) {
	*status = -1;
	YieldControlAndReturnNull ();
      }
    }
    pBuff->bufptr = pBuff->buffer;
  }
  /* don't have a full header */
  if (gotnow < sizeof (xReq)) {
    while (pBuff->bufcnt + pBuff->buffer - pBuff->bufptr < sizeof (xReq)) {
      result = read (client, pBuff->buffer + pBuff->bufcnt,
		     pBuff->size - pBuff->bufcnt);
      if (result < 0) {
	if (errno == EWOULDBLOCK)
	  *status = 0;
	else
	  *status = -1;
	YieldControlAndReturnNull ();
      }
      if (result == 0) {
	*status = -1;
	YieldControlAndReturnNull ();
      }
      pBuff->bufcnt += result;
    }
    request = (xReq *) pBuff->bufptr;
    gotnow = pBuff->bufcnt + pBuff->buffer - pBuff->bufptr;
    needed = request_length (request, who);
    if (needed <= 0)
      needed = sizeof (xReq);
    if (needed > pBuff->size) {
      pBuff->size = needed;
      pBuff->buffer = (char *) Xrealloc (pBuff->buffer, needed);

      if (pBuff->buffer == NULL) {
	*status = -1;
	YieldControlAndReturnNull ();
      }
    }
    pBuff->bufptr = pBuff->buffer;
  }

  if (gotnow < needed) {
    int i, wanted;

    wanted = needed - gotnow;
    i = 0;
    while (i < wanted) {
      result = read (client, pBuff->buffer + pBuff->bufcnt,
		     pBuff->size - pBuff->bufcnt);
      if (result < 0) {
	if (errno == EWOULDBLOCK)
	  *status = 0;
	else
	  *status = -1;
	YieldControlAndReturnNull ();
      }
      else if (result == 0) {
	*status = -1;
	YieldControlAndReturnNull ();
      }
      i += result;
      pBuff->bufcnt += result;
    }
  }
  *status = needed;
  pBuff->lenLastReq = needed;

  /*
   * Check to see if client has at least one whole request in the buffer.  If
   * there is only a partial request, treat like buffer is empty so that
   * select() will be called again and other clients can get into the queue.
   */

  timesThisConnection++;
  if (pBuff->bufcnt + pBuff->buffer >= pBuff->bufptr + needed + sizeof (xReq)) {
    request = (xReq *) (pBuff->bufptr + needed);
    if ((pBuff->bufcnt + pBuff->buffer) >=
	((char *) request + request_length (request, who)))
      BITSET (BITS (ClientsWithInput), client);
    else
      YieldControlNoInput ();
  }
  else
    YieldControlNoInput ();
  if (timesThisConnection == MAX_TIMES_PER)
    YieldControl ();

  return ((char *) pBuff->bufptr);

#undef YieldControlAndReturnNull
#undef YieldControlNoInput
#undef YieldControl
}

/*
 * lookup table for adding padding bytes to data that is read from or written
 * to the X socket.
 */
static int padlength[4] =
{0, 3, 2, 1};

/********************
* FlushClient()
*    If the client isn't keeping up with us, then we try to continue
*    buffering the data and set the apropriate bit in ClientsWritable
*    (which is used by WaitFor in the select).  If the connection yields
*    a permanent error, or we can't allocate any more space, we then
*    close the connection.
*
**********************/

int
FlushClient (who, oc, extraBuf, extraCount)
  ClientPtr who;
  OsCommPtr oc;
  char *extraBuf;
  int extraCount;		/* do not modify... returned below */
{
  int connection = oc->fd, total, n, i, notWritten, written, iovCnt = 0;
  struct iovec iov[3];
  char padBuffer[3];

  debug (_debug_io, "flush client %d...", connection);
  total = 0;
  iov[0].iov_len = 0;
  iov[1].iov_len = 0;
  iov[2].iov_len = 0;
  if (oc->count) {
    total += iov[iovCnt].iov_len = oc->count;
    iov[iovCnt++].iov_base = (caddr_t) oc->buf;

    /*
     * _Notice that padding isn't needed for oc->buf since it is alreay
     * padded by WriteToClient
     */
  }
  if (extraCount) {
    total += iov[iovCnt].iov_len = extraCount;
    iov[iovCnt++].iov_base = extraBuf;
    if (extraCount & 3) {
      total += iov[iovCnt].iov_len = padlength[extraCount & 3];
      iov[iovCnt++].iov_base = padBuffer;
    }
  }

  notWritten = total;
  while ((n = writev (connection, iov, iovCnt)) != notWritten) {
#ifdef hpux
    if (n == -1 && errno == EMSGSIZE)
      n = swWritev (connection, iov, 2);
#endif
    if (n > 0) {
      notWritten -= n;
      for (i = 0; i < iovCnt; i++) {
	if (n > iov[i].iov_len) {
	  n -= iov[i].iov_len;
	  iov[i].iov_len = 0;
	}
	else {
	  iov[i].iov_len -= n;
	  iov[i].iov_base += n;
	  break;
	}
      }
      continue;
    }
    else if (errno != EWOULDBLOCK) {
      if (errno != EBADF)
	ErrorF ("Closing connection %d because write failed (errno=%d)\n",
		connection, errno);

      /*
       * This close will cause the select in WaitForSomething to return that
       * the connection is dead, so we can actually clean up after the
       * client.  We can't clean up here, because the we're in the middle of
       * doing something and will probably screw up some data strucutres
       */
      close (connection);
      MarkClientException (who);
      return (-1);
    }

    /*
     * If we've arrived here, then the client is stuffed to the gills and not
     * ready to accept more.  Make a note of it and buffer the rest.
     */
    BITSET (BITS (ClientsWriteBlocked), connection);
    AnyClientsWriteBlocked = TRUE;

    written = total - notWritten;
    if (written < oc->count) {
      if (written > 0) {
	oc->count -= written;
	bcopy ((char *) oc->buf + written, (char *) oc->buf, oc->count);
	written = 0;
      }
    }
    else {
      written -= oc->count;
      oc->count = 0;
    }

    if (notWritten > oc->bufsize) {

      /*
       * allocate at least enough to contain it plus one OutputBufferSize
       */
      oc->bufsize = notWritten + OutputBufferSize;
      oc->buf = (unsigned char *) Xrealloc (oc->buf, oc->bufsize);
      if (oc->buf == NULL) {
      outOfMem:
	ErrorF ("Closing connection %d because out of memory\n",
		connection);

	/*
	 * This close will cause the select in WaitForSomething to return
	 * that the connection is dead, so we can actually clean up after the
	 * client.  We can't clean up here, because the we're in the middle
	 * of doing something and will probably screw up some data strucutres
	 */
	close (connection);
	MarkClientException (who);
	oc->count = 0;
	oc->bufsize = 0;
	return (-1);
      }
    }

    /*
     * If the amount written extended into the padBuffer, then the difference
     * "extraCount - written" may be less than 0
     */
    if ((n = extraCount - written) > 0)
      bcopy (extraBuf + written, (char *) oc->buf + oc->count, n);

    oc->count = notWritten;	/* this will include the pad */

    return extraCount;		/* return only the amount explicitly
				 * requested */
  }

  /* everything was flushed out */
  oc->count = 0;
  if (oc->bufsize > OutputBufferSize) {
    oc->bufsize = OutputBufferSize;
    oc->buf = (unsigned char *) Xrealloc (oc->buf, OutputBufferSize);
    if (oc->buf == NULL)	/* nearly impossible */
      goto outOfMem;
  }
  debug (_debug_io, " flushed\n");
  return extraCount;		/* return only the amount explicitly
				 * requested */
}

/********************
* FlushAllOutput()
*    Flush all clients with output.  However, if some client still
*    has input in the queue (more requests), then don't flush.  This
*    will prevent the output queue from being flushed every time around
*    the round robin queue.  Now, some say that it SHOULD be flushed
*    every time around, but...
*
**********************/

void
FlushAllOutput ()
{
  register int index, base, mask;
  OsCommPtr oc;
  register ClientPtr client;

  if (!NewOutputPending)
    return;

  /*
   * It may be that some client still has critical output pending, but he is
   * not yet ready to receive it anyway, so we will simply wait for the
   * select to tell us when he's ready to receive.
   */
  CriticalOutputPending = FALSE;
  NewOutputPending = FALSE;

  for (base = 0; base < mskcnt; base++) {
    mask = BITS (OutputPending)[base];
    BITS (OutputPending)[base] = 0;
    while (mask) {
      index = ffs (mask) - 1;
      mask &= ~lowbit (mask);
      if ((client = Connections[(32 * base) + index]) == NULL)
	continue;
      if (client->clientGone)
	continue;
      oc = (OsCommPtr) client->osPrivate;
      if (GETBIT (BITS (ClientsWithInput), client->index)) {
	BITSET (BITS (OutputPending), oc->fd);	/* set the bit again */
	NewOutputPending = TRUE;
      }
      else
	FlushClient (client, oc, (char *) NULL, 0);
    }
  }

}

void
FlushIfCriticalOutputPending ()
{
  if (CriticalOutputPending)
    FlushAllOutput ();
}

void
SetCriticalOutputPending ()
{
  CriticalOutputPending = TRUE;
}

/*****************
 * WriteToClient
 *    Copies buf into ClientPtr.buf if it fits (with padding), else
 *    flushes ClientPtr.buf and buf to client.  As of this writing,
 *    every use of WriteToClient is cast to void, and the result
 *    is ignored.  Potentially, this could be used by requests
 *    that are sending several chunks of data and want to break
 *    out of a loop on error.  Thus, we will leave the type of
 *    this routine as int.
 *****************/

int
WriteToClient (who, count, buf)
  ClientPtr who;
  char *buf;
  int count;
{
  OsCommPtr oc = (OsCommPtr) who->osPrivate;
  int padBytes;

  if (oc->fd == -1) {
    ErrorF ("OH NO, %d translates to -1\n", oc->fd);
    return (-1);
  }

  if (oc->fd == -2) {
#ifdef notdef
    ErrorF ("CONNECTION %d ON ITS WAY OUT\n", oc->fd);
#endif
    return (-1);
  }

  padBytes = padlength[count & 3];

  if (oc->count + count + padBytes > oc->bufsize) {
    BITCLEAR (BITS (OutputPending), oc->fd);
    CriticalOutputPending = FALSE;
    NewOutputPending = FALSE;
    return FlushClient (who, oc, buf, count);
  }

  NewOutputPending = TRUE;
  BITSET (BITS (OutputPending), oc->fd);
  bcopy (buf, (char *) oc->buf + oc->count, count);
  oc->count += count + padBytes;

  return (count);
}
