
/*
 *  FIFO-HANDLER.C	V38.2
 *
 *  Provide an interface to the fifo.library
 *  Provide remote shell support, including "*", interactive and 2.0 support.
 *
 *  !!! MUST BE RUN, CANNOT USE MOUNT !!!
 *
 *  FIFO:fifo_name/flags
 *		   r	for reading
 *		   w	for writing
 *		   c	cooked (else raw)
 *		   e	EOF on close (if a writer)
 *		   k	allows writer to close before reader opens without
 *			any data lost.
 *		   K	a reader MUST exist or write(s) will fail
 *		   q	QUIT (debugging)
 *		   m	master
 *		   t	tee off the read stream (no interference with other
 *			readers)
 *		   s	shell
 *
 *		   d	debug mode (read by running 'RemCLI DBFifo')
 *		   x	exit debug mode
 */

#define CLI_START

#include "handler.h"

/*  These packets are strictly internal to the handler */
#define FACTION_SCREEN_MODE	ACTION_WRITE_RETURN
#define FACTION_WAIT_CHAR	ACTION_READ_RETURN
#define ACTION_WAIT_TIMEOUT	ACTION_TIMER

int myexit(int);
void Initialize(void);
void HandleRequestMsg(Message *);
void WaitMsg(Message *);
void SigHandles(char *, unsigned long);
void returnpacket(DosPacket *);
static inline char not_console(char *, unsigned char);
void xprintf(char *, ...);
FHan *OpenHandle(char *, char *, long, long);
void CloseHandle(FHan *);
MsgPort *SpecPort(FHan *);
static inline void *DosAllocMem(long);
static inline void DosFree(void *);
void MkDevice(char *);
void DelDevice(void);
WaitTreq *AllocTimerequest(unsigned long timeout);

struct Library  * FifoBase;
MsgPort	    *IoSink;
MsgPort	    *PktPort, *TimerPort;
long	    PortsMask;		/*  could be local ... */
#ifndef CLI_START
DeviceNode  *DevNode;
#endif
WaitTreq    *TimerIO;
List	    HanList;
#ifdef DEBUG
FifoHan	    DBFifo;
short	    DDebug;
#define D(x)  if (DDebug) x ;
#define ebug  xprintf
#else
#define D(x)  ;
#endif
short	    Done;	/*  could be local, but DICE2.06 uses up a register */

const char verstring[] = "\0$VER: fifo-handler 38.2 (8.5.96)\r\n";


#ifdef DEBUG

#if defined(__GNUC__)
/*  Do not use vsprintf from Libnix, it requires all of stdio.
 *  Builtin vsprintf has the advantage that htag.S can still be used.
 */
__asm(".even;_put1char:;"
      "movel a3@,a0; moveb d0,a0@+; movel a0,a3@; rts");

static inline
int
romvsprintf(char *buf, char *ctl, void *args)
{
  extern void put1char();
  char *bufptr = buf;
  RawDoFmt(ctl, args, put1char, &bufptr);
  return bufptr - buf;
}
#define vsprintf romvsprintf
#define romvsprintf romvsprintf
#endif

void
xprintf(char *ctl, ...)
{
    va_list va;
    static char buf[256];
    int n;

    if (DBFifo) {
	va_start(va, ctl);
	n = vsprintf(buf, ctl, va);
	if (n > 0)
	    WriteFifo(DBFifo, buf, n);
	va_end(va);
    }
}

#endif /*  DEBUG */

static inline char
not_console(char *bas, unsigned char len)
{
    short n = 7;
    if (len == 1)
	return ('*' - *bas);
    if (len <= (unsigned char) n)
	return n;
    {
	const char *console = "CONSOLE:";
	char r1, r2;

	while(!(r1=*console++ - (char)((r2=*bas++) >= 'a' ? r2 - ('a' - 'A') : r2))
	      /* && r2 */ && --n != -1) ;
	return r1;
    }
}

#if !defined(__GNUC__)
__stkargs int			/*  changed from void (jch) */
_main(char *arg, long len)
#else
int _main(void)			/*  my special startup since libnix is >=2.0 */
#endif
{
    DosPacket  *packet;

    NewList((MaxList *)&HanList);

    Initialize();
    if (FifoBase == NULL)
	return myexit(RETURN_FAIL);

    /*
     *	Main Loop
     */

    while (!Done || HanList.mlh_TailPred != (Node *)&HanList) {
	{
	    Message *msg;

	    while ((msg = GetMsg(IoSink)))
		HandleRequestMsg(msg);
	    if ((msg = GetMsg(PktPort)) == NULL) {
		/*  handler depends on TimerPort being used last */
		if ((msg = GetMsg(TimerPort)) == NULL) {
		    if (Wait(SIGS | PortsMask) & SIGBREAKF_CTRL_C)
			Done = 1;
		    continue;
		} else {
		    /*  Got a timeout from timer.device */
		    packet = ((WaitTreq *)msg)->wt_packet;
		    ((WaitTreq *)msg)->wt_packet = NULL; /*  means timeout */
		}
	    } else
		packet = (DosPacket *)msg->mn_Node.ln_Name;
	}
	switch(packet->dp_Type) {
	case ACTION_WRITE:
	case FACTION_SCREEN_MODE:
	case FACTION_WAIT_CHAR:
	case ACTION_WAIT_TIMEOUT:
	    break;		/*  use dp_Res1 for temporary storage */
	default:
	    packet->dp_Res1 = DOS_TRUE;
	}
	packet->dp_Res2 = DOS_FALSE;

	D(ebug("packet %ld\n", packet->dp_Type));

	switch(packet->dp_Type) {
	case ACTION_DIE:
	    Done = 1;
	    break;
	case ACTION_FINDUPDATE:	    /*	FileHandle,Lock,Name	    Bool	*/
	case ACTION_FINDINPUT:	    /*	FileHandle,Lock,Name	    Bool	*/
	case ACTION_FINDOUTPUT:	    /*	FileHandle,Lock,Name	    Bool	*/
	    {
		FileHandle *fh = BTOC(packet->dp_Arg1);
		char fifo_name_m[128];
		char fifo_name_s[128];
		long han_flags = 0;
		long opn_flags = FIFOF_NORMAL | FIFOF_NBIO;
		short error = 0;

		{
		    char *bas = BTOC(packet->dp_Arg3);
		    char *ptr;
		    unsigned char len = *(unsigned char *)bas++;

#if defined(DEBUG) && defined(romvsprintf)
		    /*  RawDoFmt() does not understand the * flag */
		    if (DDebug) {
			char format[18];
			long args[2];
			args[1] = args[0] = len;
			romvsprintf(format, "open: %%%ld.%lds\n", args);
			xprintf(format, bas);
		    }
#else
		    D(ebug("open: %*.*s\n", len, len, bas));
#endif
		    if (!not_console(bas, len)) { /*  either "*" or "Console:" */
			if (fh->fh_Port && (long)fh->fh_Port != -1) {
			    FHan *han;
			    /*  assume IntCode() filled fh_Port with port */
			    /*  ln_name was filled with FHan by SpecPort() */
			    han = (FHan *)fh->fh_Port->mp_Node.ln_Name;
			    ++han->ff_Refs;
			    fh->fh_Arg1 = (LONG)han;
			    fh->fh_Port = (MsgPort *)DOS_TRUE; /*  means interactive */
			    break;
			}
			/* fh->fh_Port = (MsgPort *)DOS_FALSE; */
			packet->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
			break;
		    }

		    /*  skip "fifo:" part */
		    for (ptr = bas; len && *ptr != ':'; ++ptr, --len) ;
		    if (len && *ptr == ':') {
			bas = ptr + 1;
			--len;
		    } else {
			len = ptr - bas; /*  restore len */
		    }
		    for (ptr = bas; len && *ptr != '/'; ++ptr, --len) ;
		    {
			short i = ptr - bas;
			if (/* i && */ i < 126) {
			    strncpy(fifo_name_m, bas, i);
			    strncpy(fifo_name_s, bas, i);
			    /*  GCC can optimize these into MOVEB sequences */
			    strcpy(fifo_name_m + i, "_m");
			    strcpy(fifo_name_s + i, "_s");
			} else
			    error = 1; /*  nevertheless get flags */
		    }
		    /*  get flags */
		    if (len && *ptr == '/') {
			unsigned long sigs = 0;
			for (++ptr, --len; len; ++ptr, --len) {
			    switch(*ptr) {
			    case 'q':
				Done = 1;
				break;
			    case 'e':
				han_flags |= FHF_CLOSEEOF;
				break;
			    case 'c':
				han_flags |= FHF_COOKED|FHF_STARTCOOK;
				break;
			    case 's':
				han_flags |= FHF_SHELL;
				break;
			    case 'r':
				han_flags |= FHF_READ;
				break;
			    case 'w':
				han_flags |= FHF_WRITE;
				break;
			    case 'k':
				opn_flags |= FIFOF_KEEPIFD;
				break;
			    case 'K':
				opn_flags |= FIFOF_RREQUIRED;
				break;
			    case 'm':
				han_flags |= FHF_MASTER;
				break;
			    case 't':
				han_flags |= FHF_TEE;
				break;
			    case 'd':
#ifdef DEBUG
				if (DDebug == 0) {
				    DDebug = 1;
				    DBFifo = OpenFifo("DBFifo_s", 1024, FIFOF_WRITE | FIFOF_NORMAL);
				}
#endif
				break;
			    case 'x':
#ifdef DEBUG
				if (DDebug) {
				    DDebug = 0;
				    if (DBFifo)
					CloseFifo(DBFifo, FIFOF_EOF);
				    DBFifo = NULL;
				}
#endif
				break;
			    case 'C':
				sigs |= SIGBREAKF_CTRL_C;
				break;
			    case 'D':
				sigs |= SIGBREAKF_CTRL_D;
				break;
			    case 'E':
				sigs |= SIGBREAKF_CTRL_E;
				break;
			    case 'F':
				sigs |= SIGBREAKF_CTRL_F;
				break;
			    default:
				error = 1;
				break;
			    }
			}
			if (sigs)
			    SigHandles((han_flags & FHF_MASTER)
				       ? fifo_name_m : fifo_name_s, sigs);
		    }
		}
		if (((han_flags & FHF_COOKED) &&
		     ((han_flags & FHF_MASTER) ||	/*  as documented */
		      !(han_flags & FHF_READ)))		/*  necessary     */
		    || !(han_flags & (FHF_READ | FHF_WRITE)))
		    error = 1;
		/*
		 *  To handle the stderr channel, "*", an interactive
		 *  shell must be opened with the 's' flag, which causes
		 *  a special message port to be created for the interactive
		 *  shell (and thus the pr_ConsoleTask field)
		 *
		 *  To handle COOKED data mode, FIFO: itself must processed
		 *  received data before the slave device, echoing it back
		 *  on the master channel and doing other cooked processing.
		 */

		if (!error) {
		    FHan *han;
		    if (NULL != (han = OpenHandle(fifo_name_s, fifo_name_m, han_flags, opn_flags))) {
			if (han_flags & FHF_SHELL) {
			    fh->fh_Type = han->ff_Port;	/*  special port */
			}
			fh->fh_Arg1 = (LONG)han;
			fh->fh_Port = (MsgPort *)DOS_TRUE; /*  means interactive */
			han->ff_SigPort = packet->dp_Port; /*  for SIGBREAKF_CTRL_* */
			break;
		    } else {
			packet->dp_Res2 = ERROR_NO_FREE_STORE;
			break;
		    }
		}
		packet->dp_Res2 = ERROR_BAD_STREAM_NAME;
	    }
	    break;
	case ACTION_READ:	    /*	FHArg1,CPTRBuffer,Length    ActLength	*/
	    /*
	     *	read from fifo.	 If we are in cooked mode this action is
	     *	only able to read from the cooked buffer, and then only
	     *	if a line is complete.
	     */

	    {
		FHan *han = (FHan *)packet->dp_Arg1;
		long n;

		if (!(han->ff_Flags & FHF_READ)) {
		    packet->dp_Res1 = -1;
		    packet->dp_Res2 = ERROR_READ_PROTECTED;
		    returnpacket(packet); /*  or loose dp_Res1 */
		    packet = NULL;
		    break;
		}
		if (han->ff_Flags & FHF_REOF) {
		    han->ff_Flags &= ~FHF_REOF;
		    packet->dp_Res1 = 0;
		    break;
		}

		han->ff_Flags &= ~FHF_STARTCOOK; /*  restarted below */
		if (han->ff_CookBuf && han->ff_CookIdx) {
		    n = strlen(han->ff_CookBuf);
		    /*  read processed cooked input even in raw mode */
		    if (!(han->ff_Flags & FHF_COOKED)
		      || n != han->ff_CookIdx /* || han->ff_LRet superfluous */) {
			short add1 = (han->ff_Flags & FHF_COOKED || n != han->ff_CookIdx)
			  ? n++	: -1;	/*  add the newline character */

			D(ebug("Can read %ld chars\n", n));
			if (n > packet->dp_Arg3)
			    n = packet->dp_Arg3;
			else if (add1 >= 0)
			    han->ff_CookBuf[add1] = '\n';

			memmove((void *)packet->dp_Arg2, han->ff_CookBuf, n);
			han->ff_CookIdx -= n;
			/*  overlapping memory! */
			memmove(han->ff_CookBuf, han->ff_CookBuf + n, han->ff_CookIdx + 1);
			if (han->ff_CookIdx == 0)
			    han->ff_LRet = 0;
			packet->dp_Res1 = n;

			/*
			 *  if we blocked on reading the fifo to cook more
			 *  data, we unblock it here.
			 */

			han->ff_Flags &= ~FHF_COOKBFUL;

			if (han->ff_Flags & FHF_COOKED &&
			    !(han->ff_Flags & FHF_RPEND)) {
			    RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
			    han->ff_Flags |= FHF_RPEND;
			}
			break;
		    }
		}
		if (!(han->ff_Flags & FHF_COOKED)) {
		    char *ptr;

		    n = ReadFifo(han->ff_FifoR, &ptr, 0);
		    D(ebug("%ld chars available\n", n));
		    if (n < 0 /* || (n == 0 && (han->ff_Flags & FHF_REOF)--impossible)*/) {
			han->ff_Flags &= ~FHF_REOF;
			packet->dp_Res1 = 0;
			break;
		    }
		    if (n > 0) {
			/* Hans Verkuil introduced a patch to only read upto \n,
			 * however RAW: does not exhibit this behaviour.
			 */
			if (n > packet->dp_Arg3)
			    n = packet->dp_Arg3;
			CopyMem(ptr, (void *)packet->dp_Arg2, n);
			if (ReadFifo(han->ff_FifoR, &ptr, n) < 0)
			    han->ff_Flags |= FHF_REOF;
			/*
			 *  Concatenating data when last result is > 0 gives
			 *  not much, as with the way packets are sequenced
			 *  here, it is usually only possible once when
			 *  unblocking from a full buffer.
			 */
			packet->dp_Res1 = n;
			break;
		    }
		}

		/*
		 *  blocked
		 */

		AddTail((MaxList *)&han->ff_RdWait, &packet->dp_Link->mn_Node);
		if ((han->ff_Flags & FHF_RPEND) == 0) {
		    RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
		    han->ff_Flags |= FHF_RPEND;
		}
		packet = NULL;
	    }
	    break;
	case ACTION_WRITE:	    /*	FHArg1,CPTRBuffer,Length    ActLength	*/
	    {
		FHan *han = (FHan *)packet->dp_Arg1;
		long n;
		long i;

		if (!(han->ff_Flags & FHF_WRITE)) {
		    packet->dp_Res1 = -1;
		    packet->dp_Res2 = ERROR_WRITE_PROTECTED;
		    returnpacket(packet); /*  or loose dp_Res1 */
		    packet = NULL;
		    break;
		}

		i = packet->dp_Arg3;
		if (i < 0) {	/*  re-scan	*/
		    i = -i;
		    packet->dp_Arg3 = i;
		} else {	/*  initial pkt */
		    packet->dp_Res1 = 0;
		}


		/*
		 *  check for output stopped due to pending input line
		 *
		 *  dp_Arg3 < 0 indicates a re-scan (so we do not clear
		 *  our dp_Res1 field that is tracking the amnt written)
		 */

		if ((han->ff_Flags & FHF_COOKED) && han->ff_CookIdx && han->ff_LRet == 0) {
		    packet->dp_Arg3 = -packet->dp_Arg3;
		    AddTail((MaxList *)&han->ff_WrWait, &packet->dp_Link->mn_Node);
		    han->ff_Flags |= FHF_WIHOLD;
		    packet = NULL;
		    break;
		}


		/*
		 *  limit size of writes to fifo to something the fifo can
		 *  handle.  If cooked mode writer, prepend CR to LF's.
		 */

		if (i > han->ff_FHBufSiz)
		    i = han->ff_FHBufSiz;

		if (han->ff_Flags & FHF_COOKED) {
		    char *ptr = (char *)packet->dp_Arg2;
		    long j;

		    /*  n is always initialized because i >= 0 */
		    for (j = 0; j < i; ++j) {
			if (ptr[j] == '\n') {
			    if (j == 0) {
				n = WriteFifo(han->ff_FifoW, "\r\n", 2);
				if (n == 2)
				    n = 1;	/*  skip LF */
				break;
			    }
			    n = WriteFifo(han->ff_FifoW, ptr, j);
			    break;
			}
		    }
		    if (i == j)
			n = WriteFifo(han->ff_FifoW, ptr, i);
		} else
		    n = WriteFifo(han->ff_FifoW, (char *)packet->dp_Arg2, i);

		/*
		 *  object too large or broken pipe
		 */

		if (n < 0) {	/*  most probably for FIFO_RREQUIRED */
		    packet->dp_Res1 = -1;
		    packet->dp_Res2 = (n == -2)
		      ? ERROR_OBJECT_TOO_LARGE /*  can this really happen? */
		      : ERROR_WRITE_PROTECTED;
		    returnpacket(packet); /*  or loose dp_Res1 */
		    packet = NULL;
		    break;
		}

		packet->dp_Res1 += n;
		if (n == packet->dp_Arg3)
		    break;

		packet->dp_Arg3 = -(packet->dp_Arg3 - n);
		packet->dp_Arg2 += n;

		/*
		 *  blocked (n == 0)
		 *  or splitted
		 */

		AddTail((MaxList *)&han->ff_WrWait, &packet->dp_Link->mn_Node);
		if ((han->ff_Flags & FHF_WAVAIL) == 0) {
		    RequestFifo(han->ff_FifoW, &han->ff_WrMsg, FREQ_WAVAIL);
		    han->ff_Flags |= FHF_WAVAIL;
		}
		packet = NULL;
	    }
	    break;
	case ACTION_REQUEST:	    /*	FHArg1, msg, how	    Bool    */
	    {
		FHan *han = (FHan *)packet->dp_Arg1;

		if ((unsigned short)packet->dp_Arg3 == FREQ_RPEND) {
		    if (han->ff_FifoR) {
			RequestFifo(han->ff_FifoR, (void *)packet->dp_Arg2, packet->dp_Arg3);
			break;
		    }
		} else if ((unsigned short)packet->dp_Arg3 == FREQ_WAVAIL) {
		    if (han->ff_FifoW) {
			RequestFifo(han->ff_FifoW, (void *)packet->dp_Arg2, packet->dp_Arg3);
			break;
		    }
		} else if ((unsigned short)packet->dp_Arg3 == FREQ_ABORT) {
		    if (han->ff_FifoR)
			RequestFifo(han->ff_FifoR, (void *)packet->dp_Arg2, packet->dp_Arg3);
		    if (han->ff_FifoW)
			RequestFifo(han->ff_FifoW, (void *)packet->dp_Arg2, packet->dp_Arg3);
		    break;
		}
		packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
	    }
	    break;
	case ACTION_END:	    /*	FHArg1			    Bool:TRUE	*/
	    {
		FHan *han = (FHan *)packet->dp_Arg1;

		if (--han->ff_Refs == 0) {
		    if (han->ff_Flags & FHF_RPEND) {
			RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_ABORT);
			WaitMsg(&han->ff_RdMsg);
		    }
		    if (han->ff_Flags & FHF_WAVAIL) {
			RequestFifo(han->ff_FifoW, &han->ff_WrMsg, FREQ_ABORT);
			WaitMsg(&han->ff_WrMsg);
		    }
		    /*  no need to do bookkeeping for ACTION_WAIT_CHAR */
		    returnpacket(packet);   /*  before ff_Port goes away */
		    packet = NULL;
		    CloseHandle(han);
		}
	    }
	    break;
	/*  Added for 37.5 and 38.0 */
	case FACTION_WAIT_CHAR:	    /*	timeout			    Bool	*/
	    /*
	     *  as ACTION_WAIT_CHAR does not give a filehandle, we get it
	     *  indirectly through the special port. Fold packet type for safety.
	     */
	    packet->dp_Type = ACTION_WAIT_CHAR;
	    {
		FHan *han = (FHan *) (((MsgPort *) packet->dp_Res1)->mp_Node.ln_Name);
		unsigned long timeout = packet->dp_Arg1;
		WaitTreq *treq;

		if (!(han->ff_Flags & FHF_READ)) {
		    packet->dp_Res2 = ERROR_READ_PROTECTED;
		    break;
		}
		if (han->ff_Flags & FHF_REOF) {
		    packet->dp_Res1 = DOS_TRUE;
		    break;
		}
		if (han->ff_Flags & FHF_COOKED) {
		    if (han->ff_LRet || strlen(han->ff_CookBuf) != han->ff_CookIdx) {
			packet->dp_Res1 = DOS_TRUE;
			break;
		    }
		} else {
		    char *ptr;
		    if ((han->ff_CookBuf && han->ff_CookIdx)
			/*  cooked input is available */
		      || ReadFifo(han->ff_FifoR, &ptr, 0)) {
			packet->dp_Res1 = DOS_TRUE;
			break;
		    }
		}
		/*  always queue if cooked processing must be started */
		if (timeout <= 50 /* microseconds */ && !(han->ff_Flags & FHF_STARTCOOK)) {
		    packet->dp_Res1 = DOS_FALSE;
		    break;
		}

		/*
		 *  queue packet
		 */

		if (NULL != (treq = AllocTimerequest(timeout))) {
		    treq->wt_packet = packet;
		    packet->dp_Type = ACTION_WAIT_TIMEOUT;
		    packet->dp_Res1 = (LONG)treq;
		    han->ff_Flags &= ~FHF_STARTCOOK;
		    AddTail((MaxList *)&han->ff_RdWait, &packet->dp_Link->mn_Node);
		    if (!(han->ff_Flags & FHF_RPEND)) {
			RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
			han->ff_Flags |= FHF_RPEND;
		    }
		    SendIO(&treq->wt_timereq.tr_node);
		    packet = NULL;
		    break;
		} else
		    packet->dp_Res2 = ERROR_NO_FREE_STORE;
	    }
	    break;
	case ACTION_WAIT_TIMEOUT:
	    packet->dp_Type = ACTION_WAIT_CHAR;
	    if (((WaitTreq *)packet->dp_Res1)->wt_packet) {
	        /*  input is available */
		struct IORequest *ior = (struct IORequest *)packet->dp_Res1;

		if (!CheckIO(ior))
		    AbortIO(ior);
		WaitIO(ior);
		FreeMem((void *)ior, sizeof(WaitTreq));
		packet->dp_Res1 = DOS_TRUE;
	    } else {
		/*  timer.device timeout */

		/*
		 *  we know that node is still in han->ff_RdWait
		 *  because TimerPort is checked last
		 */
		Remove(&packet->dp_Link->mn_Node);
		/*  no need to try to abort the read request */
		FreeMem((void *)packet->dp_Res1, sizeof(WaitTreq));
		packet->dp_Res1 = DOS_FALSE;
	    }
	    break;
	case FACTION_SCREEN_MODE:   /*	Mode			    Bool:TRUE */
	    /* As ACTION_SCREEN_MODE does not give a filehandle, we get it
	     * indirectly through the special port. Fold packet type for safety.
	     */
	    packet->dp_Type = ACTION_SCREEN_MODE;
	    D(ebug("SCREEN_MODE %ld\n", packet->dp_Arg1));
	    {
		/*  special port passed in dp->Res1 */
		FHan *han = (FHan *)(((MsgPort *)packet->dp_Res1)->mp_Node.ln_Name);

		if (han->ff_CookBuf == NULL) {	/*  also NULL when !FHF_READ */
		    /* Normally	 a shell uses cooked mode and programs might
		     * also set cooked mode at exit. Thus if the
		     * application did not open FIFO: in cooked mode, it
		     * had good reasons to do so and we refuse any change.
		     * At least this is good for GNUEmacs (uses "rwesK").
		     */
		    packet->dp_Res2 = ERROR_OBJECT_WRONG_TYPE;
		    break;
		}

		packet->dp_Res1 = DOS_TRUE;
		if (DOSFALSE != packet->dp_Arg1) {	/*  RAW */
		    if (han->ff_Flags & FHF_COOKED) {
			han->ff_Flags &= ~(FHF_COOKED|FHF_STARTCOOK|FHF_WIHOLD|FHF_COOKECHOBLK /*|FHF_COOKBFUL --still full */);

			/*  cooked input is available, unblock readers and timers */
			if (han->ff_CookIdx && (han->ff_Flags & FHF_RPEND)) {
			    D(ebug("Restarting readers\n"));
			    RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_ABORT);
			    /* han->ff_Flags |= FHF_RPEND; --already set */
			}
			/*  unblock writers probably in FHF_WIHOLD */
			if ((han->ff_Flags & FHF_WRITE) && !(han->ff_Flags & FHF_WAVAIL)) {
			    RequestFifo(han->ff_FifoW, &han->ff_WrMsg, FREQ_WAVAIL);
			    han->ff_Flags |= FHF_WAVAIL;
			}
		    }
		} else {	/*	COOKED	*/
		    if (!(han->ff_Flags & FHF_COOKED)) {
			/*  but ff->CookBuf is NULL if not opened with 'c' flag! */
			han->ff_Flags |= FHF_COOKED|FHF_STARTCOOK;
			/*  cooked processing depends on a read request being active. */
			/*  Since 38.2, cooked processing is only started when
			 *  ACTION_READ and ACTION_WAIT_CHAR actually occur, thus
			 *  pretyped input is not mangled should the application
			 *  switch to raw mode before reading
			 *  (like GDB when controlled by dejagnu+expect)
			 *
			if ((han->ff_Flags & FHF_READ) && !(han->ff_Flags & FHF_RPEND)) {
			    RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
			    han->ff_Flags |= FHF_RPEND;
			}
			 */
		    }
		}
	    }
	    break;
	/*  Added for 38.1 */
	case ACTION_CHANGE_SIGNAL:	/*  Filehandle,port|NULL    Bool,port */
	    {
		FHan *han = (FHan *)packet->dp_Arg1;
		packet->dp_Res2 = (LONG)han->ff_SigPort;
		D(ebug("Signal Port change %08lx\n", packet->dp_Arg2));
		if (packet->dp_Arg2)
		    han->ff_SigPort = (MsgPort *)packet->dp_Arg2;
		returnpacket(packet);	/*  or loose dp_Res1 */
		packet = NULL;
	    }
	    break;
	/*  Added for 38.1 */
	case ACTION_IS_FILESYSTEM:  /*				    Bool */
	    packet->dp_Res1 = DOS_FALSE;
	    break;
	/*  Added for 38.1 */
	case ACTION_SEEK:	    /*	FHArg1,Position,Mode	    OldPosition */
	    packet->dp_Res1 = -1;   /*  do not return 0 */
	    packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
	    returnpacket(packet);	/*  or loose dp_Res1 */
	    packet = NULL;
	    break;
	default:
	    D(ebug("UNKNOWN packet\nArg1 %08lx Arg2 %08lx\n",
		   packet->dp_Arg1, packet->dp_Arg2));
	    packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
	    break;
	}
	if (packet) {
	    if (packet->dp_Res2)
		packet->dp_Res1 = DOS_FALSE;
	    returnpacket(packet);
	}
    }
    return myexit(RETURN_OK);
}

FHan *
OpenHandle(r_name, w_name, han_flags, opn_flags)
char *r_name;	/*  slave name	*/
char *w_name;	/*  master name */
long han_flags;
long opn_flags;
{
    FHan *han;

    if (NULL == (han = (FHan *)AllocMem(sizeof(FHan) + strlen(r_name) +1,
					MEMF_CLEAR | MEMF_PUBLIC)))
	return NULL;
    if (!(han_flags & FHF_MASTER)) {
	char *swap = r_name;
	r_name = w_name;
	w_name = swap;
    }
    han->ff_Node.ln_Name = (char *)(han + 1);
    strcpy(han->ff_Node.ln_Name, w_name);

    if ((han_flags & FHF_TEE) == 0 && (han_flags & FHF_READ)) {
	FHan *h2;
	for (h2 = (FHan *)HanList.mlh_Head; h2->ff_Node.ln_Succ; h2 = (FHan *)h2->ff_Node.ln_Succ) {
	    if (strcmp(h2->ff_Node.ln_Name, han->ff_Node.ln_Name) == 0) {
		if ((h2->ff_Flags & FHF_TEE) == 0 && h2->ff_SRead) {
		    han->ff_SRead = h2->ff_SRead;
		    ++han->ff_SRead->sr_Refs;
		    break;
		}
	    }
	}
    }
    AddTail((MaxList *)&HanList, &han->ff_Node);

    if ((han_flags & FHF_READ) && han->ff_SRead == NULL) {
	if (NULL == (han->ff_SRead = AllocMem(sizeof(SharRead), MEMF_CLEAR | MEMF_PUBLIC)))
	    goto fail;
	han->ff_SRead->sr_Refs	= 1;
	if (NULL == (han->ff_SRead->sr_FifoR = OpenFifo(r_name, FIFO_SIZE, opn_flags | FIFOF_READ)))
	    goto fail;
    }
    if (han_flags & FHF_WRITE) {
	if (NULL == (han->ff_FifoW = OpenFifo(w_name, FIFO_SIZE, opn_flags | FIFOF_WRITE)))
	    goto fail;
	han->ff_FHBufSiz = (BufSizeFifo(han->ff_FifoW) >> 1) -1;
    }
    if (han_flags & FHF_SHELL) {
	if (NULL == (han->ff_Port = SpecPort(han)))
	    goto fail;
    }

    han->ff_Flags = han_flags;
    han->ff_Refs = 1;
    han->ff_RdMsg.mn_ReplyPort = IoSink;
    han->ff_RdMsg.mn_Node.ln_Name = (char *)han;
    han->ff_WrMsg.mn_ReplyPort = IoSink;
    han->ff_WrMsg.mn_Node.ln_Name = (char *)han;
    NewList((MaxList *)&han->ff_RdWait);
    NewList((MaxList *)&han->ff_WrWait);

    if (han_flags & FHF_COOKED) {
	/*  allocation need not be public, fifo-handler use only */
	if (NULL == (han->ff_CookBuf = AllocMem(CB_SIZE, MEMF_CLEAR)))
	    goto fail;
	/*  lookahead for efficiency */
	/*  Don't start cooked processing, application may switch to raw mode
	 *
	RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
	han->ff_Flags |= FHF_RPEND;
	 */
    }
    return han;

    fail:
    CloseHandle(han);
    return NULL;
}

void
CloseHandle(han)
FHan *han;
{
    /*  zero fields to protect against buggy programs */
    Remove(&han->ff_Node);
    if (han->ff_SRead) {
	if (--han->ff_SRead->sr_Refs == 0) {
	    if (han->ff_FifoR) {
		CloseFifo(han->ff_FifoR, 0);
		/* han->ff_FifoR = NULL; */
	    }
	    FreeMem(han->ff_SRead, sizeof(SharRead));
	}
	han->ff_SRead = NULL;
    }
    if (han->ff_FifoW) {
	CloseFifo(han->ff_FifoW, (han->ff_Flags & FHF_CLOSEEOF) ? FIFOF_EOF : 0);
	han->ff_FifoW = NULL;
    }
    if (han->ff_Port) {
	FreeMem(han->ff_Port, sizeof(MsgPort) + sizeof(Interrupt));
	han->ff_Port = NULL;
    }
    if (han->ff_CookBuf) {
	FreeMem(han->ff_CookBuf, CB_SIZE);
	han->ff_CookBuf = NULL;
    }
    FreeMem(han, sizeof(FHan) + strlen(han->ff_Node.ln_Name) + 1);
}


/*
 *  handle cooked data by actually reading it from the fifo, echoing it
 *  to the return channel (if it exists), and processing it.  If a <CR>
 *  is processed, handle any
 */

void
HandleRequestMsg(Message *msg)
{
    FHan *han = (FHan *)msg->mn_Node.ln_Name;

    if (msg == &han->ff_WrMsg) {
	han->ff_Flags &= ~FHF_WAVAIL;
	while ((msg = (Message *)RemHead((MaxList *)&han->ff_WrWait))) /*  retry operation */
	    PutMsg(PktPort, msg);

	/*
	 *  if we were blocked trying to echo, then read data pending,
	 *  make sure read-request is queued and will be retried.
	 */

	if (han->ff_Flags & FHF_COOKECHOBLK) {
	    if ((han->ff_Flags & FHF_RPEND) == 0) {
		RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
		han->ff_Flags |= FHF_RPEND;
	    }
	    han->ff_Flags &= ~FHF_COOKECHOBLK;
	}
    } else if (msg == &han->ff_RdMsg) {
	han->ff_Flags &= ~FHF_RPEND;
	if (han->ff_Flags & FHF_COOKED) {
	    long n;
	    long i;
	    short rwakeup = 0;
	    char *ptr;

	    n = ReadFifo(han->ff_FifoR, &ptr, 0);

	    if (n < 0) {
		han->ff_Flags |= FHF_REOF;
		rwakeup = 1;
	    }

	    for (i = 0; i < n; ++i) {
		switch(ptr[i]) {
		case 13:
		case 10:
		    if (han->ff_CookIdx >= CB_SIZE - 2) {
			han->ff_Flags |= FHF_COOKBFUL;
			n = --i;
			break;
		    }
		    if (han->ff_FifoW) {
			if (WriteFifo(han->ff_FifoW, "\r\n", 2) != 2) {
			    han->ff_Flags |= FHF_COOKECHOBLK;
			    n = --i;
			    break;
			}
		    }
		    /*  split buffer into \0 terminated strings */
		    han->ff_CookBuf[han->ff_CookIdx++] = 0;
		    han->ff_CookBuf[han->ff_CookIdx] = 0;
		    han->ff_LRet = 1;
		    rwakeup = 1;
		    break;
		case 8:
		    if (han->ff_CookIdx && han->ff_CookBuf[han->ff_CookIdx-1] != 0) {
			if (han->ff_FifoW) {
			    if (WriteFifo(han->ff_FifoW, "\010 \010", 3) != 3) {
				han->ff_Flags |= FHF_COOKECHOBLK;
				n = --i;
				break;
			    }
			}
			han->ff_CookBuf[--han->ff_CookIdx] = 0;
			if (han->ff_CookIdx && han->ff_CookBuf[han->ff_CookIdx-1] == 0)
			    han->ff_LRet = 1;
		    }
		    break;
		default:
		    if (han->ff_CookIdx >= CB_SIZE - 2) {
			han->ff_Flags |= FHF_COOKBFUL;
			n = --i;
			break;
		    }
		    if (han->ff_FifoW) {
			if (WriteFifo(han->ff_FifoW, ptr + i, 1) != 1) {
			    han->ff_Flags |= FHF_COOKECHOBLK;
			    n = --i;
			    break;
			}
		    }
		    han->ff_CookBuf[han->ff_CookIdx++] = ptr[i];
		    han->ff_CookBuf[han->ff_CookIdx] = 0;
		    han->ff_LRet = 0;
		    break;
		}
	    }

	    /*
	     *	if output was held due to cooked input pending, and the
	     *	case is no longer true, then restart output
	     */

	    if ((han->ff_Flags & FHF_WIHOLD) && (han->ff_LRet || han->ff_CookIdx == 0)) {
		han->ff_Flags &= ~FHF_WIHOLD;
		while ((msg = (Message *)RemHead((MaxList *)&han->ff_WrWait)))
		    PutMsg(PktPort, msg);
	    }

	    if (i > 0) {
		if (ReadFifo(han->ff_FifoR, &ptr, i) < 0) {
		    han->ff_Flags |= FHF_REOF;
		    rwakeup = 1;
		}
	    }
	    if (n >= 0 && !(han->ff_Flags & (FHF_COOKECHOBLK|FHF_COOKBFUL|FHF_REOF))) {
		RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
		han->ff_Flags |= FHF_RPEND;
	    }
	    if (!rwakeup)
		return;
	}
	while ((msg = (Message *)RemHead((MaxList *)&han->ff_RdWait))) /*  retry operation */
	    PutMsg(PktPort, msg);
    }
}

void
WaitMsg(Message *msg)
{
    while (msg->mn_Node.ln_Type == NT_MESSAGE)
	Wait(1 << msg->mn_ReplyPort->mp_SigBit);
    Forbid();
    Remove(&msg->mn_Node);
    Permit();
}

void
SigHandles(char *name, unsigned long sigs)
{
    FHan *han;

    for (han = (FHan *)HanList.mlh_Head; han->ff_Node.ln_Succ; han = (FHan *)han->ff_Node.ln_Succ) {
	if (strcmp(han->ff_Node.ln_Name, name) == 0) {
	    MsgPort *mp = han->ff_SigPort;
	    D(ebug("Maybe signal port %08lx %s\n", mp, name));
	    if ((mp->mp_Flags & PF_ACTION) == PA_SIGNAL)
		Signal(mp->mp_SigTask, sigs);
	}
    }
}


/*
 *  PACKET ROUTINES.	Dos Packets are in a rather strange format as you
 *  can see by this and how the PACKET structure is extracted in the
 *  GetMsg() of the main routine.
 */

void
returnpacket(DosPacket *packet)
{
    MsgPort *replyPort	     = packet->dp_Port;
    Message *mess	     = packet->dp_Link;
    packet->dp_Port	     = PktPort;	/*  not possibly special port? */
    mess->mn_Node.ln_Name    = (char *)packet;
    D(ebug("Return type=%ld, res1=%ld, res2=%ld\n", packet->dp_Type, packet->dp_Res1, packet->dp_Res2));
    PutMsg(replyPort, mess);
}

void
Initialize(void)
{
#ifndef CLI_START
    Process *proc = FindTask(NULL);
    DosPacket *packet;
#endif

    /*
     *	Initialize port
     */
    {
	IoSink = CreatePort("FIFO-PORT", -10);
	FreeSignal(IoSink->mp_SigBit);
	IoSink->mp_SigBit = SIGBREAKB_CTRL_F; /*  why? (jch) */
    }
    PktPort = CreatePort(NULL, 0);
    TimerPort = CreatePort(NULL, 0);
    TimerIO = (WaitTreq *) CreateExtIO(TimerPort, sizeof(WaitTreq));
    OpenDevice(TIMERNAME, UNIT_MICROHZ, (struct IORequest *)TimerIO, 0);
    TimerIO->wt_timereq.tr_node.io_Command = TR_ADDREQUEST;
    PortsMask = (1 << PktPort->mp_SigBit) | (1 << TimerPort->mp_SigBit);

#ifdef CLI_START

    /*
     *	create DOS node
     */

    MkDevice("FIFO");

    FifoBase = OpenLibrary(FIFONAME, 0);

#else

    /*
     *	Handle initial message.	 We pull the message off the port before
     *	calling OpenLibrary() so if OpenLibrary() makes a DOS call it
     *	doesn't crash the machine (due to an unexpected packet).  This
     *	will happen if the library needs to be loaded.	There is no other
     *	safe time to do this.
     */

    {
	Message *msg;

	WaitPort(&proc->pr_MsgPort);
	msg = GetMsg(&proc->pr_MsgPort);
	packet = (DosPacket *)msg->mn_Node.ln_Name;
    }

    /*
     *	Fifo Library
     */

    FifoBase = OpenLibrary(FIFONAME, 0);

    {
	DeviceNode *dn;
	DevNode = dn = BTOC(packet->dp_Arg3);

	dn->dn_Task = PktPort;
	packet->dp_Res1 = (FifoBase) ? DOS_TRUE : DOS_FALSE;
	packet->dp_Res2 = 0;
	returnpacket(packet);
    }

#endif
}


int
myexit(int code)
{
#ifdef CLI_START
    DelDevice();
#else

    /*
     *	Device Node
     */

    {
	DeviceNode *dn = DevNode;

	dn->dn_Task = NULL;
	dn->dn_SegList = NULL;
    }
#endif

    /*
     *	delete ports
     */

    CloseDevice((struct IORequest *) TimerIO);
    DeleteExtIO((struct IORequest *) TimerIO);
    DeletePort(TimerPort);

    if (PktPort)
	DeletePort(PktPort);

    if (IoSink) {
	IoSink->mp_SigBit = AllocSignal(-1);
	DeletePort(IoSink);
    }

#ifdef DEBUG
    if (DBFifo) {
	CloseFifo(DBFifo, FIFOF_EOF);
    }
#endif
    if (FifoBase) {
	CloseLibrary(FifoBase);
	/* FifoBase = NULL; */
    }
    _exit(code);		/*  some startups always return 0 from main */
    return code;
}


MsgPort *
SpecPort(FHan *han)
{
    MsgPort *port = AllocMem(sizeof(MsgPort) + sizeof(Interrupt), MEMF_CLEAR | MEMF_PUBLIC);
    Interrupt *xint = (Interrupt *)(port + 1);
    extern void AIntCode();
    if (NULL == port) return NULL;

    NewList(&port->mp_MsgList);
    port->mp_Node.ln_Name = (char *)han; /*  ln_name holds local data */
    port->mp_Node.ln_Type = NT_MSGPORT;
    port->mp_Flags = PA_SOFTINT;
    port->mp_SigTask = (void *)xint;
    xint->is_Node.ln_Type = NT_INTERRUPT;
    xint->is_Node.ln_Pri  = -32;
    xint->is_Data = (APTR)port;
    xint->is_Code = AIntCode;
    return(port);
}

#if !defined(__GNUC__)
__geta4 __stkargs
#endif
void
IntCode(port)
MsgPort *port;
{
    Message *msg;
    DosPacket *packet;
    FileHandle *fh;

    while ((msg = GetMsg(port))) {
	packet = (DosPacket *)msg->mn_Node.ln_Name;
	D(ebug("Port %08lx, type %ld\n", port, packet->dp_Type));

	switch(packet->dp_Type) {
	case ACTION_FINDUPDATE:
	case ACTION_FINDINPUT:
	case ACTION_FINDOUTPUT:
	    fh = BTOC(packet->dp_Arg1);
	    fh->fh_Port = port;	/*  temporary storage */
	    break;
	case ACTION_SCREEN_MODE:
	    packet->dp_Type = FACTION_SCREEN_MODE;
	    packet->dp_Res1 = (LONG)port; /*  we are not allowed to change dp->Arg2 */
	    break;
	case ACTION_WAIT_CHAR:
	    packet->dp_Type = FACTION_WAIT_CHAR;
	    packet->dp_Res1 = (LONG)port; /*  we are not allowed to change dp->Arg2 */
	    break;
	}
	PutMsg(PktPort, msg);
    }
}


#ifdef CLI_START

/*
 *  DEVICE CREATION AND DELETION
 */

static inline
void *
DosAllocMem(bytes)
long bytes;
{
    long *ptr;

    bytes += 4;

    if ((ptr = AllocMem(bytes, MEMF_PUBLIC | MEMF_CLEAR))) {
	*ptr++ = bytes;
	return((void *)ptr);
    }
    Alert(AG_NoMemory|AT_DeadEnd);
    return NULL;		/*  NOTREACHED */
}

static inline
void
DosFree(void *vptr)
{
    long *ptr = vptr;
    --ptr;
    FreeMem(ptr, *ptr);
}

DosList *Dl;

void
MkDevice(char *devName)
{
    DosList *dl;
    RootNode *root;
    DosInfo *info;

    Dl = dl = (struct DosList *)DosAllocMem(sizeof(struct DosList)+strlen(devName)+2);
    strcpy((char *)(dl+1) + 1, devName);
    *(char *)(dl + 1) = strlen(devName);
    dl->dol_Type = DLT_DEVICE;
    dl->dol_Task = PktPort;
    dl->dol_Name = MKBADDR((char *)(dl+1));

    Forbid();
    root  = (struct RootNode *)DOSBase->dl_Root;
    info  = (struct DosInfo  *)BADDR(root->rn_Info);
    dl->dol_Next = info->di_DevInfo;
    info->di_DevInfo = MKBADDR(dl);
    Permit();
}

void
DelDevice(void)
{
    DosList *dl;
    DosInfo *info;
    RootNode *root;
    DosList *dls;
    BPTR    *bpp;

    if ((dl = Dl)) {
	Forbid();
	root  = (struct RootNode *)DOSBase->dl_Root;
	info  = (struct DosInfo	 *)BADDR(root->rn_Info);

	for (bpp = &info->di_DevInfo; (dls = BADDR(*bpp)); bpp = &dls->dol_Next) {
	    if (dls == dl)
		break;
	}
	if (dls == dl) {
	    *bpp = dls->dol_Next;
	} else {
	    Alert(0x07AAAAAA|AT_Recovery);
	}
	Permit();
	DosFree(dl);
	Dl = NULL;
    }
}

#endif

WaitTreq *
AllocTimerequest(unsigned long timeout)
{
  struct WaitTreq *tr = (struct WaitTreq *)AllocMem(sizeof(WaitTreq), MEMF_PUBLIC);

  if (NULL != tr) {
      memmove(tr, TimerIO, sizeof(tr->wt_timereq));
      tr->wt_timereq.tr_time.tv_secs = 0;
      if (timeout < 10 && SysBase->LibNode.lib_Version < 36)
	  timeout = 10; /*  work around bug in <2.0 timer.device */
      while (timeout >= 1000000) {
	  tr->wt_timereq.tr_time.tv_secs++;
	  timeout -= 1000000;
      }
      tr->wt_timereq.tr_time.tv_micro = timeout;
  }
  return tr;
}
