/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/

/* This is a keyboard server for JOVE.  It wraps the keyboard input in
   packets so that JOVE can identify its source.  By the time we get here,
   our standard output goes to JOVE's process input.  It is suspended when
   no interactive subprocesses processes are active.

   JOVE communicates with this server through signals:
    - KBD_TOGGLE_SUSPEND_SIG	(JOVE -> kbd) to suspend/wakeup the server;
    - KBD_TIMEOUT_SIG		(JOVE -> kbd) requests a timed-out read;
    - KBD_FATAL_ERROR_SIG	(kbd -> JOVE) to report an unrecoverable error.
   These signals are defined in "process.h".

   Packet layout:

 - header.pid		- process id. of sender (the kbd process)

 - header.nbytes	- if >= 0, the number of data bytes that follow;
			  if < 0, reports a read error, its value is the
			  negated `errno' at the time the read error occurred.
			  EINTR errors are filtered out, unless a timeout was
			  requested.  A special case of an "error" is the value
			  KBD_SUSPENDED which is actually the acknowledge of
			  a suspend request.

 - data[header.nbytes]	- the characters read by the server from stdin.

   Each packet is sent in a single atomic write, so other processes writing
   on the same pipe cannot interfere.
 */

#define NOT_JOVE

#include "tune.h"

#ifdef PIPEPROCS

RCS("$Id: kbd.c,v 14.31 1993/02/15 02:01:48 tom Exp tom $")

#include "jove.h"
#include "io.h"
#include "process.h"

/* {{There must be better places to handle this...}} */
#   ifndef _NSIG
#	ifdef NSIGS
#	    define _NSIG NSIGS
#	endif
#   endif
#   ifndef _NSIG
#	define _NSIG	"(warning) _NSIG is undefined for this system."
#	define _NSIG	32	/* should be enough for most systems. */
#   endif

#ifdef BSD_SIGS
#   define pause()	sigpause(0L)
#endif

#ifdef DEBUG
void
DIAGNOSE(str)
char	*str;
{
	write(2, str, strlen(str));
	write(2, "\r\n", 2);
}
#else
#   define DIAGNOSE(str)
#endif

/* The keyboard server sends KBD_FATAL_ERROR_SIG to JOVE whenever it
   runs into an unrecoverable error, and then exits.  It is up to JOVE
   to do something sensible with it. */

#define FATAL_ERROR()	kill(getppid(), KBD_FATAL_ERROR_SIG)


/* JOVE sends KBD_TOGGLE_SUSPEND_SIG whenever it wants the kbd process
   (this program) to stop competing for input from the keyboard.  JOVE
   does this when it realizes that there are no more interactive processes
   running.  The reason we go through all this trouble is that JOVE slows
   down a lot when it's getting its keyboard input via a pipe. */

private sig_tp	suspend_or_wakeup __(( int _(sig) )),
		timeout __(( int _(sig) )),
		shoot __(( int _(sig) )),
		spurious __(( int _(sig) ));

private int	suspended ZERO;

private sig_tp
suspend_or_wakeup(sig)
{
	suspended ^= 1;
	signal(sig, suspend_or_wakeup);
	DIAGNOSE(suspended ? "{suspend}" : "{wakeup}");
}

/* JOVE sends KBD_TIMEOUT_SIG if it wants the kbd process to setup a
   timed-out read.  This is done because it is not reliable to have JOVE
   setup a timeout itself when I-processes are active. (since the
   insertion of I-process output can take a considerable amount of time.)
 */

private int	alarmed ZERO;

private sig_tp
shoot(sig)
{
	alarmed = 1;
	/* this is a one-shot */
	DIAGNOSE("{shoot}");
}

private sig_tp
timeout(sig)
{
	alarmed = -1;			/* < 0 means timeout request. */
	signal(sig, timeout);
	DIAGNOSE("{timeout}");
}

/* This catches spurious interrupts. */

private sig_tp
spurious(sig)
{
	DIAGNOSE("{spurious interrupt}");
	FATAL_ERROR();			/* signal JOVE that we're rotten */
	kill(getpid(), sig);		/* ...and die as we would have. */
}

#endif /* PIPEPROCS */

void
main()
{
#ifdef PIPEPROCS
	struct {
		struct header header;
		char buf[10];
	} packet;
	register int	sig;
	register int	n = 0;

	for (sig = 1; sig < _NSIG; sig++) {	/* setup signal handlers */
		switch (sig) {
		case KBD_TOGGLE_SUSPEND_SIG:
			signal(sig, suspend_or_wakeup);
			break;
		case KBD_TIMEOUT_SIG:
			signal(sig, timeout);
			break;
		default:
			signal(sig, spurious);
		}
	}
	packet.header.pid = getpid();

	DIAGNOSE("{kbd}");

	for (;;) {

		/* Wait here while we're suspended.  Both the read (from a
		   tty) and the write (to a pipe) are interruptable so we'll
		   always end up here when a suspend signal comes in.  The
		   original kbd process paused inside a signal handler, which
		   seemed to cause problems in Minix.  Also it didn't cope
		   with mixed timeout and suspend signals the way it was.
		   This should be safer. */

		if (suspended) {

			/* send an acknowledgment so JOVE knows when to stop
			   draining the process input pipe; retry on timeout
			   interrupts. */

			DIAGNOSE("{acknowledge}");
			packet.header.nbytes = KBD_SUSPENDED;
			while (write(1, &packet, sizeof packet.header) < 0 &&
			       alarmed) {
				alarm(0);
				alarmed = 0;
			}
			DIAGNOSE("{suspended}");
			while (suspended)
				pause();

			alarm(0);	/* start with clean slate */
			alarmed = 0;
		}

		if (alarmed < 0) {	/* setup timeout */
			DIAGNOSE("{alarm}");
			signal(SIGALRM, shoot);
			alarm(ALARM_1_SEC);
		}

		if ((packet.header.nbytes = n =
		     read(0, packet.buf, sizeof packet.buf)) <= 0) {
			if (n == 0)
				continue;

			if (errno != EINTR) {	/* now bail out on read error */
				DIAGNOSE("{read failed}");
				FATAL_ERROR();
				_exit(-errno);
			}

			if (alarmed <= 0) {
				DIAGNOSE("{interrupted}");
				continue;
			}
			DIAGNOSE("{alarmed}");
			n = 0;
			packet.header.nbytes = -errno;	/* report error */
		}
		if (alarmed < 0)	/* timeout set but not yet expired; */
			alarm(0);	/* so turn it off now. */
		alarmed = 0;

		/* Make sure the packet is sent; retry interrupted writes. */

		while (write(1, &packet, sizeof packet.header + n) < 0) {
			if (errno == EINTR)
				continue;
			DIAGNOSE("{write failed}");
			FATAL_ERROR();
			_exit(-errno);
		}
	}
	DIAGNOSE("{?kbd past loop?}");
	FATAL_ERROR();

#endif /* PIPEPROCS */

	_exit(EXIT_SUCCESS);
}

/*======================================================================
 * $Log: kbd.c,v $
 * Revision 14.31  1993/02/15  02:01:48  tom
 * remove (void) casts.
 *
 * Revision 14.30  1993/01/26  18:43:17  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.26  1992/08/26  23:57:05  tom
 * add RCS directives.
 *
 */
