/* $Id: io.c,v 1.1.1.1 90/11/28 17:02:13 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.
 */
/*
 * $Log:	io.c,v $
 * Revision 1.1.1.1  90/11/28  17:02:13  altenhof
 * First public release
 * 
 * Revision 1.1  90/11/28  17:02:11  altenhof
 * Initial revision
 * 
 * Revision 1.1.1.1  90/11/28  16:25:26  spanachi
 * First public version of shX
 * 
 * Revision 1.1  90/11/28  16:25:24  spanachi
 * Initial revision
 * 
 *
 * Revision 1.2  90/05/14
 * 08:57:28  altenhof Fixed two BUGS: [1] Overwriting of event masks in
 * XChWAttrs.c (doesn't make any sense). [2] Removal of resources that are
 * still used; e.g. a pixmap in a gc.
 * 
 * Revision 1.1.1.1  90/04/23  10:12:28  spanachi First version of 'shared X'
 * library (X11R3).
 * 
 * Revision 1.1  90/04/23  10:12:25  spanachi Initial revision
 * 
 * Revision 1.2  90/04/04  12:44:38  altenhof Mod: more "flexible' select
 * 
 * Revision 1.1  90/04/04  09:39:34  altenhof Initial revision
 * 
 * Revision 1.3  89/11/28  13:52:49  neideck Trying to fix up bugs with select
 * 
 * Revision 1.2  89/11/24  09:02:10  neideck *** empty log message ***
 * 
 * Revision 1.1  89/11/03  14:45:18  neideck Original send to X consortium
 * 
 * Revision 1.8  89/09/01  15:31:24  michael FIXED BUG : Don't continue, if
 * select returns with an error. You've heard about xfig ? Well, it decides
 * to set an alarm during select with timeout = 0 to make it's text cursor
 * blink !!
 * 
 * Revision 1.7  89/08/23  11:48:19  michael *** empty log message ***
 * 
 * Revision 1.6  89/08/15  10:43:50  michael XmuXselect can be customized to run
 * in two different versions under ChalkPassing mode ( defensive or
 * optimistic). FIXED BUG : Cleaning thee masks got caught in a loop if you
 * specified nfds as 0 !
 * 
 * Revision 1.5  89/08/10  18:44:18  michael Changes : XmuX's version of ioctl
 * system call added ( you guess why ? Well read the code of xterm !)
 * XuXselect has been changed to work correctly under ChalkPassing mode (we
 * may change it again)
 * 
 * Revision 1.4  89/07/20  20:27:08  michael Have you seen this wonderful
 * "multiplex" version of select ?
 * 
 * Revision 1.3  89/07/07  08:44:39  michael *** empty log message ***
 * 
 * Revision 1.2  89/06/26  15:20:16  michael Add two new functions :
 * XmuXWaitForReadables -> XmuX's extension to Xlib's XWaitForReadable
 * XmuXselect           -> XmuX's version of the select system call
 * 
 * Revision 1.1  89/06/07  17:25:12  michael Initial revision
 */

/*****************************************************************
 * i/o functions
 * 
 * ReadRequest (macro in io.h),
 * XmuXWriteToServer,
 * XmuXWaitForReadables,
 * XmuXSelect
 *****************************************************************/

#define NEED_EVENTS
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <X11/Xos.h>
#include "Xlibint.h"
#include "XmuXlibint.h"
#include "multiplex.h"
#ifndef MAXSELFD

/*
 * This defines the maximum number of file descriptors allowed in a select(2)
 * call.  On some systems (e.g. Ultrix) it is defined in
 * /usr/include/sys/types.h
 */
#define MAXSELFD NOFILE
#endif

#ifndef MY_OWN_SELECT
#define sElEcT(a,b,c,d,e) select(a,b,c,d,e)
#endif

#define OtherServers( fd ) \
	     ConnectionTranslation[ fd ]->other_servers

#define ValidConnection( fd ) \
             ( ConnectionTranslation[ fd ] != NullServer )

extern int errno;
extern void _XWaitForWritable ();
extern (*_XIOErrorFunction) ();

/*
 * Here we are, making our ugly select faster!  Won't work if we still
 * use application contexts (assuming applications may use more than one
 * display)
 */

static int num_of_servers = 0;
static xReq _dummy_request = {0, 0, 0};

/*********************************************************
 * XmuXWriteToServer : write data to the server
 *
 * That's the place where we do the actual "writes" on a socket.
 * This is normally done by Xlib in _XFlush and _XSend.
 * We have modified these two routines to allow MULTIPLEXING.
 * Here you find the code that was replaced.
 *********************************************************/

void
XmuXWriteToServer (dpy, buffer, size)
  Display *dpy;
  char *buffer;
  int size;
{
  register int todo, write_stat;
  register char *bufindex;

  todo = size;
  bufindex = buffer;

  /*
   * While write has not written the entire buffer, keep looping until
   * the entire buffer is written. bufindex will be incremented and size
   * decremented as data is written out. WriteToServer is defined in
   * Xlibos.h which is included via Xlibint.h
   */
  while (size) {
    errno = 0;
    write_stat = WriteToServer (dpy->fd, bufindex, todo);
    if (write_stat >= 0) {
      size -= write_stat;
      todo = size;
      bufindex += write_stat;
#ifdef EWOULDBLOCK
    }
    else if (errno == EWOULDBLOCK) {
      _XWaitForWritable (dpy);
#endif
#ifdef SUNSYSV
    }
    else if (errno == 0) {
      _XWaitForWritable (dpy);
#endif
#ifdef EMSGSIZE
    }
    else if (errno == EMSGSIZE) {
      todo >>= 1;
#endif
    }
    else {
      /* write failed! errno set by write system call */
      (*_XIOErrorFunction) (dpy);
    }
  }
}

/*********************************************************
 * XmuXFlushConnections: write data to all MULTIPLEX'ed  servers
 *
 * Loop through the list of dpy's "other servers" and call
 * XmuXWriteToServer for this displays.
 *********************************************************/

void
XmuXFlushConnections (dpy)
  Display *dpy;
{
  register int i;
  register Display *next_dpy;

  for (i = 1; (next_dpy = XmuXGetDisplay (dpy, i)) != NULL; i++) {
    /* flush the output queue */
    XmuXWriteToServer (next_dpy, next_dpy->buffer,
		       next_dpy->bufptr - next_dpy->buffer);
    next_dpy->bufptr = next_dpy->buffer;
    next_dpy->last_req = (char *) &_dummy_request;
  }
}

/********************
 * XmuXWaitForReadables -- XmuX's version of Xlib's XWaitForReadable, with two
 * additional arguments:
 *  size    -> we want to read at least size (e.g. sizeof( xEvent ) ) bytes
 *  pending -> array containing the number of pending bytes for each
 *             investigated socket.
********************/
void
XmuXWaitForReadables (dpy, size, pending)
  Display *dpy;
  int size;			/* we want to read al least size bytes from
				 * any socket */
  long pending[];		/* returns the number of pending bytes on
				 * each individual socket */
{
  unsigned long r_mask[MSKCNT], readables[MSKCNT];
  int i, fd, pend, result;
  Bool got_one = False;

  CLEARBITS (readables);

  /*
   * Check ALL associated sockets !
   */
  COPYBITS (OtherServers (dpy->fd), readables);
  BITSET (readables, dpy->fd);
  /* loop until there's at least one socket with #size bytes pending */
  do {
    do {
      COPYBITS (readables, r_mask);
      result = sElEcT (MAXSOCKS, r_mask, NULL, NULL, NULL);
      if (result == -1 && errno != EINTR)
	(*_XIOErrorFunction) (dpy);
    }
    while (result <= 0);

    for (i = 0; i < MSKCNT; i++)
      while (r_mask[i]) {
	fd = ffs (r_mask[i]) - 1 + (i << 5);
	BITCLEAR (r_mask, fd);
	pending[fd] = 0;

	/* how much can we read from this socket */
	if (BytesReadable (fd, (char *) &pend) < 0)
	  (*_XIOErrorFunction) (MuxDisplay (fd));
	else
	  pending[fd] = pend;
	if (pend >= size)
	  got_one = True;
      }
  }
  while (!got_one);
}

#ifdef DONT_READ_X_SOCKETS
Bool
HaveReadEvents (fd)
  int fd;
{
  register Display *dpy, *dflt_dpy;
  register long pend;
  long pend_not_register;
  register int old_qlen;
  char buf[BUFSIZE];
  register xEvent *ev;

#ifndef DONT_MASKALL
  return (1);
#else
  dpy = MuxDisplay (fd);
  dflt_dpy = XmuXPrimaryDisplayFromConnection (fd);
  old_qlen = dflt_dpy->qlen;
  /* find out how much data can be read */
  if (BytesReadable (dpy->fd, (char *) &pend_not_register) < 0)
    (*_XIOErrorFunction) (dpy);
  pend = pend_not_register;

  /*
   * Must read at least one xEvent; if none is pending, then we'll just block
   * waiting for it
   */
  if (pend < SIZEOF (xEvent))
    pend = SIZEOF (xEvent);

  /* but we won't read more than the max buffer size */
  if (pend > BUFSIZE)
    pend = BUFSIZE;

  /* round down to an integral number of XReps */
  pend = (pend / SIZEOF (xEvent)) * SIZEOF (xEvent);

  _XRead (dpy, buf, pend);

  /* no space between comma and type or else macro will die */
  STARTITERATE (ev, xEvent, buf, (pend > 0), (pend -= SIZEOF (xEvent))) {
    if (ev->u.u.type == X_Error)
      _XError (dpy, (xError *) ev);
    else
      /* it's an event packet; enqueue it in default queue */
      _XEnq (dpy, ev);
  }
  ENDITERATE
    return (old_qlen != dflt_dpy->qlen);

#endif
}

#endif

#define BIT_ISSET( i , mask )  \
         ( ( mask[ MASKIDX( i ) ] ) & ( BITMASK( i ) ) )
#define SEL_MASK( i ) \
          ConnectionTranslation[ i ]->other_servers

#ifdef DONT_READ_X_SOCKETS
#define WaitUntilDoomsday( timeout )  ( timeout == ( struct timeval * ) NULL )
#endif

#define HAS_CHALK( i ) \
    ( XmuXHasChalk( MuxDisplay( i ) ) )

#define FD_ZERO_INDEXED( fd_set , i ) \
          (fd_set)->fds_bits[ i ] = 0L
#define SIZEOFFDMASK  ( sizeof( fd_mask ) << 3 )

/********************
 * XmuXselect : XmuX's version of the select system call (aha !)
 * Xt Intrinsics use the select system call to block if no events are
 * pending.
 * Since we do not tell the application that it is multiplexed, it will call
 * select without the multiplex'ed sockets in its masks. This implies, that
 * select would not return if something happened on these "transparent"
 * sockets.
 * We may  use a chalkpass mode, and there are two ways to
 * handle this case :
 * DEFENSIVE way :
 *  -->only the ChalkHolder connection may appear in the new mask;
 *     otherwise XmuXselect may return, butthe corresponding events
 *     are thrown away (what a nice deadlock!)
 *  Disadvantage : "Tabula rasa" on multiplex'ed displays, i.e. application
 *    blocks in XmuXselect and the necessary Expose events on the mux'ed
 *    displays have not yet been delivered. You have do "wake up" the
 *    application by doing something on the Chalk display.
 * OPTIMISTIC way :
 *  --> allow all associated connections to appear in the READ mask.
 *      If something has happened on non-chalk sockets try to enque these
 *      events. Return depends on the result of this enqueing and the
 *      selection timer : If there were only throw-away events we clear
 *      the bit in the read mask and decrement the nfound variable.
 *      If the application says "I can wait" by passing NULL as the timeout
 *      argument and nfound has decrement to zero, we're just looping.
 *  "Disadvantage" (May turn to an error): We assume that applications
 *     do not try to do their own "event reading" by calling read, but rely
 *     on the Xlib routines.
 ********************/

#ifdef MY_OWN_SELECT
int
select (nfds, readfds, writefds, exceptfds, timeout)
#else
int
sElEcT (nfds, readfds, writefds, exceptfds, timeout)
#endif
  int nfds;
  fd_set *readfds, *writefds, *exceptfds;
  struct timeval *timeout;
{
  fd_set XmuXreadfds, XmuXwritefds, XmuXexceptfds;

  /*
   * If we "wait until doomsday", we need a save set for subsequent
   * select calls AND we need it to decrement nfound correctly!
   */
  fd_set saved_rmask, saved_wmask, saved_emask;
  register int i, j, sel_fd, XmuXnfds, nfound;

#ifndef MULTIPLE_DISPLAYS
  if (num_of_servers <= 1 || !nfds)
    return (sElEcT (nfds, readfds, writefds, exceptfds, timeout));
#endif

  /*
   * Sometimes C reminds me of assembler...
   */
  FD_ZERO (&XmuXreadfds);
  FD_ZERO (&XmuXwritefds);
  FD_ZERO (&XmuXexceptfds);
  FD_ZERO (&saved_rmask);
  FD_ZERO (&saved_wmask);
  FD_ZERO (&saved_emask);

  XmuXnfds = nfds;

  /*
   * Augment the fd masks (and increment XmuXnfds !) if necessary
   */
  for (i = 0; i < nfds; i++) {
    if (readfds) {
      if (FD_ISSET (i, readfds)) {
	FD_SET (i, &XmuXreadfds);
	/* are there other interesting sockets? */
	if (ValidConnection (i)) {
#ifndef DONT_READ_X_SOCKETS
	  /* defensive way clears non-chalk bits */
	  if (!HAS_CHALK (i))
	    FD_CLR (i, &XmuXreadfds);
#endif
	  for (j = 0; j < MAXSELFD; j++)
#ifdef DONT_READ_X_SOCKETS
	    if (BIT_ISSET (j, SEL_MASK (i)))
#else
	    if (BIT_ISSET (j, SEL_MASK (i)) && HAS_CHALK (j))
#endif
	    {
	      FD_SET (j, &XmuXreadfds);
	      XmuXnfds = (j >= XmuXnfds) ? j + 1 : XmuXnfds;
	    }
	}
      }
    }

    if (writefds)
      if (FD_ISSET (i, writefds))
	FD_SET (i, &XmuXwritefds);
    if (exceptfds)
      if (FD_ISSET (i, exceptfds))
	FD_SET (i, &XmuXexceptfds);
  }

  /*
   * clear the original masks!!!  WATCH OUT: There are sneaky devils
   * like xterm that pass integers as masks (a 32-bit mask), but assume
   * that it still works!
   */
  for (i = 0; i <= MASKIDX (nfds); i++) {
    if (readfds)
      FD_ZERO_INDEXED (readfds, i);	/* ha ha ! */
    if (writefds)
      FD_ZERO_INDEXED (writefds, i);	/* ha ha ! */
    if (exceptfds)
      FD_ZERO_INDEXED (exceptfds, i);	/* ha ha ! */
  }

  /* set the save set */
  bcopy ((char *) &XmuXreadfds,
	 (char *) &saved_rmask, sizeof (fd_set));
  bcopy ((char *) &XmuXwritefds,
	 (char *) &saved_wmask, sizeof (fd_set));
  bcopy ((char *) &XmuXexceptfds,
	 (char *) &saved_emask, sizeof (fd_set));

#ifdef DONT_READ_X_SOCKETS
  while (True) {
    /* restore the masks */
    bcopy ((char *) &saved_rmask,
	   (char *) &XmuXreadfds, sizeof (fd_set));
    bcopy ((char *) &saved_wmask,
	   (char *) &XmuXwritefds, sizeof (fd_set));
    bcopy ((char *) &saved_emask,
	   (char *) &XmuXexceptfds, sizeof (fd_set));
#endif

    /* call select with augmented masks and incremented nfds */

    nfound = sElEcT (XmuXnfds, &XmuXreadfds, &XmuXwritefds,
		     &XmuXexceptfds, timeout);

    if (nfound < 0) {
      XmuXdebug (debug_events, "select failed , errr = %d\n", errno);
      return (nfound);
    }

    /*
     * translate the result back to fds that are known by the caller
     */
    XmuXdebug (debug_events, "XmuXselect ");
    for (i = 0; i < XmuXnfds; i++) {
      if (FD_ISSET (i, &XmuXreadfds)) {
	if (!readfds) {
	  readfds = (fd_set *) Xmalloc (sizeof (fd_set));
	  FD_ZERO (readfds);
	}
	XmuXdebug (debug_events, "r%d ", i);
#ifdef DONT_READ_X_SOCKETS
	if (ValidConnection (i)) {
	  if (HaveReadEvents (i))
	    FD_SET (ConnectionTranslation[i]->prim_server,
		    readfds);
	}
	else
	  FD_SET (i, readfds);
#else
	if (ValidConnection (i))
	  sel_fd =
	    ConnectionTranslation[i]->prim_server;
	else
	  sel_fd = i;
	FD_SET (sel_fd, readfds);
#endif
      }

      if (FD_ISSET (i, &XmuXwritefds)) {
	XmuXdebug (debug_events, "w%d ", i);
	if (!writefds) {
	  writefds = (fd_set *) Xmalloc (sizeof (fd_set));
	  FD_ZERO (writefds);
	}
	FD_SET (i, writefds);
      }

      if (FD_ISSET (i, &XmuXexceptfds)) {
	XmuXdebug (debug_events, "e%d ", i);
	if (!exceptfds) {
	  exceptfds = (fd_set *) Xmalloc (sizeof (fd_set));
	  FD_ZERO (exceptfds);
	}
	FD_SET (i, exceptfds);
      }
    }

    /*
     * recalculate nfound
     */
    nfound = 0;
    for (i = 0; i < nfds; i++)
      if ((readfds && FD_ISSET (i, readfds)) ||
	  (writefds && FD_ISSET (i, writefds)) ||
	  (exceptfds && FD_ISSET (i, exceptfds)))
	nfound++;

    XmuXdebug (debug_events, " found %d \n", nfound);

#ifdef DONT_READ_X_SOCKETS
    if ((nfound != 0) || !WaitUntilDoomsday (timeout)) {
      return (nfound);
    }
  }				/* while( True ) */
#else
    return (nfound);
#endif

}

#undef BIT_ISSET

  /* xterm drives me crazy !!! */

int
XmuXioctl (d, request, argp)
  int d, request;
  char *argp;
{
  register Display *chalk_dpy;
  register int result;
  
  if (ValidConnection (d)) {
    chalk_dpy = XmuXChalkDisplay (MuxDisplay (d));
    d = chalk_dpy->fd;
  }
  XmuXdebug (debug_events, "XmuXioctl on socket %d, ", d);
  
  result = ioctl (d, request, argp);
  
  XmuXdebug (debug_events, "argp = %d\n", argp ? (int) *argp : 0);
  
  return (result);
}

void
XmuXIncServers (n)
  int n;
{
  num_of_servers += n;
}

void
XmuXDecServers (n)
  int n;
{
  num_of_servers -= n;
  if (num_of_servers < 0)
    num_of_servers = 0;
}
