
/*
 *  FIFO-HANDLER.C	V37.4
 *
 *  Provide an interface to the fifo.library
 *  Provide remote shell support, including "*" support and interactive
 *  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')
 */

#define CLI_START

#include "handler.h"
#include "fifo.h"
#include <lists.h>
#include <string.h>

#define FIFO_SIZE   2048

#define SIGS	(SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D|SIGBREAKF_CTRL_E|SIGBREAKF_CTRL_F)

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

short	    DDebug;
short	    NotDone = 1;
MsgPort     *IoSink;
MsgPort     *PktPort;
#ifndef CLI_START
DeviceNode  *DevNode;
#endif
void	    *FifoBase;
long	    PktPortMask;
#ifdef DEBUG
long	    DBFifo;
#endif

List	HanList;

extern struct DosLibrary *DOSBase;

__stkargs void
_main(arg, len)
char *arg;
long len;
{
    DosPacket  *packet;

    NewList((MaxList *)&HanList);

    Initialize();

    /*
     *	Main Loop
     */

    while (NotDone || GetHead(&HanList)) {
	{
	    Message *msg;

	    while (msg = GetMsg(IoSink))
		HandleRequestMsg(msg);
	    if ((msg = GetMsg(PktPort)) == NULL) {
		if (Wait(SIGS | PktPortMask) & SIGBREAKF_CTRL_C)
		    NotDone = 0;
		continue;
	    }
	    packet = (DosPacket *)msg->mn_Node.ln_Name;
	}
	if (packet->dp_Type != ACTION_WRITE)
	    packet->dp_Res1 = DOS_TRUE;
	packet->dp_Res2 = 0;

#ifdef DEBUG
	if (DDebug)
	    xprintf("packet %08lx (%d)\n", packet->dp_Type, packet->dp_Type);
#endif

	switch(packet->dp_Type) {
	case ACTION_DIE:
	    NotDone = 0;
	    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);
		FHan *han;
		char fifo_name_m[128];
		char fifo_name_s[128];
		short error = 0;
		long han_flags = 0;
		long opn_flags = FIFOF_NORMAL | FIFOF_NBIO;
		long opn_size  = FIFO_SIZE;

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

#ifdef DEBUG
		    if (DDebug)
			xprintf("open: %*.*s\n", len, len, bas + 1);
#endif
		    for (ptr = ++bas; *ptr != ':' && len; ++ptr, --len);
		    if (len && *ptr == ':') {
			++ptr;
			--len;
		    } else {
			len = ptr - bas;
			ptr = bas;
		    }
		    bas = ptr;
		    for (ptr = bas; *ptr != '/' && len; ++ptr, --len);
		    {
			int i = ptr - bas;
			strncpy(fifo_name_m, bas, i);
			strncpy(fifo_name_s, bas, i);
			strcpy(fifo_name_m + i, "_m");
			strcpy(fifo_name_s + i, "_s");
		    }
		    if (len && *ptr == '/') {
			for (++ptr, --len; len; ++ptr, --len) {
			    switch(*ptr) {
			    case 'q':
				NotDone = 0;
				break;
			    case 'e':
				han_flags |= FHF_CLOSEEOF;
				break;
			    case 'c':
				han_flags |= FHF_COOKED;
				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 's':
				han_flags |= FHF_SHELL;
				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) {
			if (han_flags & FHF_MASTER)
			    SigHandles(fifo_name_m, sigs);
			else
			    SigHandles(fifo_name_s, sigs);
		    }
		}

		/*
		 *  To handle the stderr channel, "*", an interactive
		 *  shell must be openned 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 == 0) {
		    if (strcmp(fifo_name_m, "*_m") == 0) {
			if (fh->fh_Port && (long)fh->fh_Port != -1) {
			    han = (FHan *)fh->fh_Port->mp_Node.ln_Name;
			    ++han->ff_Refs;
			    fh->fh_Arg1 = han;
			    fh->fh_Port = (MsgPort *)DOS_TRUE;
			    break;
			}
			fh->fh_Port = (MsgPort *)DOS_FALSE;
			packet->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
			break;
		    }

		    if (han = AllocMem(sizeof(FHan) + strlen(fifo_name_s) + 1, MEMF_CLEAR | MEMF_PUBLIC)) {
			han->ff_Node.ln_Name = (char *)(han + 1);
			if (OpenHandle(han, fifo_name_s, fifo_name_m, han_flags, opn_size, opn_flags) >= 0) {
			    if (han_flags & FHF_SHELL) {
				han->ff_Port = SpecPort(han);
				fh->fh_Type = han->ff_Port;
			    }
			    fh->fh_Arg1 = han;
			    fh->fh_Port = (MsgPort *)DOS_TRUE;
			    AddTail((MaxList *)&HanList, &han->ff_Node);
			    ++han->ff_Refs;
			    han->ff_Flags = han_flags;
			    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;
			    han->ff_Task = packet->dp_Port->mp_SigTask;
			    /*
			    han->ff_Task = ((Message *)packet->dp_Link)->mn_ReplyPort->mp_SigTask;
			    */
			    if (han_flags & FHF_WRITE)
				han->ff_FBufSiz = BufSizeFifo(han->ff_FifoW);
			    NewList((MaxList *)&han->ff_RdWait);
			    NewList((MaxList *)&han->ff_WrWait);
			    if (han_flags & FHF_COOKED) {
				RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
				han->ff_Flags |= FHF_RPEND;
				han->ff_CookBuf = AllocMem(CB_SIZE, MEMF_PUBLIC | MEMF_CLEAR);
			    }
			    break;
			}
			AddTail((MaxList *)&HanList, &han->ff_Node);
			CloseHandle(han);
		    }
		}
		fh->fh_Port = (MsgPort *)DOS_FALSE;
		packet->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
#ifdef DEBUG
		if (DDebug)
		    xprintf("open failed\n");
#endif
	    }
	    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;
		char *ptr;
		long n;

		if (!(han->ff_Flags & FHF_READ)) {
		    packet->dp_Res1 = -1;
		    break;
		}
		if (han->ff_Flags & FHF_REOF) {
		    han->ff_Flags &= ~FHF_REOF;
		    packet->dp_Res1 = 0;
		    break;
		}
		if (han->ff_Flags & FHF_COOKED) {
		    long n;
		    if ((n = strlen(han->ff_CookBuf)) != han->ff_CookIdx || han->ff_LRet) {
			++n;
			if (n > packet->dp_Arg3)
			    n = packet->dp_Arg3;
			else
			    han->ff_CookBuf[n-1] = '\n';
			movmem(han->ff_CookBuf, (void *)packet->dp_Arg2, n);
			movmem(han->ff_CookBuf + n, han->ff_CookBuf, han->ff_CookIdx - n + 1);
			han->ff_CookIdx -= n;
			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_RPEND) == 0) {
			    RequestFifo(han->ff_FifoR, &han->ff_RdMsg, FREQ_RPEND);
			    han->ff_Flags |= FHF_RPEND;
			}
			break;
		    } else {
			if (han->ff_Flags & FHF_REOF) {
			    han->ff_Flags &= ~FHF_REOF;
			    packet->dp_Res1 = 0;
			    break;
			}
		    }
		} else {
		    n = ReadFifo(han->ff_FifoR, &ptr, 0);
		    if (n < 0 || (n == 0 && (han->ff_Flags & FHF_REOF))) {
			han->ff_Flags &= ~FHF_REOF;
			packet->dp_Res1 = 0;
			break;
		    }
		    if (n > 0) {
			if (n > packet->dp_Arg3)
			    n = packet->dp_Arg3;
			movmem(ptr, (void *)packet->dp_Arg2, n);
			if (ReadFifo(han->ff_FifoR, &ptr, n) < 0)
			    han->ff_Flags |= FHF_REOF;
			packet->dp_Res1 = n;
			break;
		    }
		}

		/*
		 *  blocked
		 */

		AddTail((MaxList *)&han->ff_RdWait, &((Message *)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;
		    break;
		}

		i = packet->dp_Arg3;
#ifdef DEBUG
		if (DDebug)
		    xprintf("i = %d\n", i);
#endif
		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, &((Message *)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_FBufSiz >> 1) - 1)
		    i = (han->ff_FBufSiz >> 1) - 1;

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

		    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, (char *)packet->dp_Arg2, i);
		} else {
		    n = WriteFifo(han->ff_FifoW, (char *)packet->dp_Arg2, i);
		}

		/*
		 *  object too large or broken pipe
		 */

		if (n < 0) {
		    packet->dp_Res1 = -1;
		    packet->dp_Res2 = ERROR_OBJECT_TOO_LARGE;
		    break;
		}
#ifdef DEBUG
		if (DDebug)
		    xprintf("n = %d, res1 = %d,  i=%d\n", n, packet->dp_Res1 + n, i);
#endif
		packet->dp_Res1 += n;
		if (n == packet->dp_Arg3)
		    break;

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

		/*
		 *  blocked
		 *  n == 0
		 */

#ifdef DEBUG
		if (DDebug)
		    xprintf("AddTail:res1 = %d\n", packet->dp_Res1);
#endif

		AddTail((MaxList *)&han->ff_WrWait, &((Message *)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);
		    else
			packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
		    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);
		    else
			packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
		    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);
		} else {
		    packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
		}
	    }
	    break;
	case ACTION_END:	    /*	FHArg1			    Bool:TRUE	*/
	    {
		FHan *han = (FHan *)packet->dp_Arg1;
		Message *msg;

		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);
		    }
		    returnpacket(packet);   /* before ff_Port goes away */
		    CloseHandle(han);
		    packet = NULL;
		}
	    }
	    break;
	case ACTION_SCREEN_MODE:
	    if (packet->dp_Arg2 && packet->dp_Arg2 != -1) {
		FHan *han = (FHan *)(((MsgPort *)packet->dp_Arg2)->mp_Node.ln_Name);

#ifdef DEBUG
		if (DDebug)
		    xprintf("handle %s arg1 %d\n", han->ff_Node.ln_Name, packet->dp_Arg1);
#endif

		switch (packet->dp_Arg1) {
		case DOS_TRUE:	    /*	RAW	*/
		    if (han->ff_Flags & FHF_COOKED) {
			han->ff_Flags &= ~(FHF_COOKED|FHF_WIHOLD|FHF_COOKECHOBLK|FHF_COOKBFUL);

			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;
			}
			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;
			}
		    }
		    break;
		case DOS_FALSE:     /*	COOKED	*/
		    if (!(han->ff_Flags & FHF_COOKED)) {
			han->ff_Flags |= FHF_COOKED;

		    }
		    break;
		}
	    }
	    break;
	case ACTION_SEEK:	    /*	FHArg1,Position,Mode	    OldPosition */
	default:
	    packet->dp_Res2 = ERROR_ACTION_NOT_KNOWN;
	    break;
	}
	if (packet) {
	    if (packet->dp_Res2)
		packet->dp_Res1 = DOS_FALSE;
	    returnpacket(packet);
	}
    }
    myexit(0);
}

int
OpenHandle(han, r_name, w_name, han_flags, opn_size, opn_flags)
FHan *han;
char *r_name;	/*  slave name	*/
char *w_name;	/*  master name */
long han_flags;
long opn_size;
long opn_flags;
{
    if (han_flags & FHF_MASTER) {
	strcpy(han->ff_Node.ln_Name, w_name);
    } else {
	char *swap;
	strcpy(han->ff_Node.ln_Name, r_name);
	swap = r_name;
	r_name = w_name;
	w_name = swap;
    }

    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;
		}
	    }
	}
    }

    if ((han_flags & FHF_READ) && han->ff_SRead == NULL) {
	han->ff_SRead = AllocMem(sizeof(SharRead), MEMF_CLEAR | MEMF_PUBLIC);
	han->ff_SRead->sr_FifoR = OpenFifo(r_name, opn_size, opn_flags | FIFOF_READ);
	han->ff_SRead->sr_Refs	= 1;
	if (han->ff_FifoR == NULL)
	    return(-1);
    }
    if (han_flags & FHF_WRITE) {
	han->ff_FifoW = OpenFifo(w_name, opn_size, opn_flags | FIFOF_WRITE);
	if (han->ff_FifoW == NULL)
	    return(-1);
    }
    return(0);
}

void
CloseHandle(han)
FHan *han;
{
    Remove(&han->ff_Node);
    if (han->ff_SRead) {
	if (--han->ff_SRead->sr_Refs == 0) {
	    if (han->ff_FifoR)
		CloseFifo(han->ff_FifoR, 0);
	    FreeMem(han->ff_SRead, sizeof(SharRead));
	}
	han->ff_SRead = NULL;
    }
    if (han->ff_FifoW) {
	if (han->ff_Flags & FHF_CLOSEEOF)
	    CloseFifo(han->ff_FifoW, FIFOF_EOF);
	else
	    CloseFifo(han->ff_FifoW, 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);
    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(msg)
Message *msg;
{
    FHan *han = (FHan *)msg->mn_Node.ln_Name;

    if (msg == &han->ff_WrMsg) {
	han->ff_Flags &= ~FHF_WAVAIL;
	while (msg = 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;
			}
		    }
		    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 = 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 = RemHead((MaxList *)&han->ff_RdWait))  /*  retry operation */
	    PutMsg(PktPort, msg);
    }
}

void
WaitMsg(msg)
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(name, sigs)
char *name;
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 && han->ff_Task)
	    Signal(han->ff_Task, 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(packet)
DosPacket *packet;
{
    Message *mess;
    MsgPort *replyPort;

    replyPort		     = packet->dp_Port;
    mess		     = packet->dp_Link;
    packet->dp_Port	     = PktPort;
    mess->mn_Node.ln_Name    = (char *)packet;
    PutMsg(replyPort, mess);
}

void
Initialize()
{
    DeviceNode *dn;
    Process *proc = FindTask(NULL);
    DosPacket *packet;

    /*
     *	Initialize port
     */

    {
	IoSink = CreatePort("FIFO-PORT", 0);
	FreeSignal(IoSink->mp_SigBit);
	IoSink->mp_SigBit = SIGBREAKB_CTRL_F;
    }
    PktPort = CreatePort(NULL, 0);
    PktPortMask = 1 << PktPort->mp_SigBit;

#ifdef CLI_START

    /*
     *	create DOS node
     */

    MkDevice("FIFO");

    FifoBase = OpenLibrary("fifo.library", 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("fifo.library", 0);

    {
	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

    if (FifoBase == NULL)
	myexit(1);
}


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

    /*
     *	Device Node
     */

    {
	DeviceNode *dn = DevNode;

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

    /*
     *	delete ports
     */

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

    if (PktPort)
	DeletePort(PktPort);

#ifdef DEBUG
    if (DBFifo) {
	CloseFifo(DBFifo, FIFOF_EOF);
	DBFifo = NULL;
    }
#endif
    if (FifoBase) {
	CloseLibrary(FifoBase);
	FifoBase = NULL;
    }
    _exit(code);
}


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

    NewList(&port->mp_MsgList);
    port->mp_Node.ln_Name = (char *)han;
    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);
}

__geta4 __stkargs void
IntCode(port)
MsgPort *port;
{
    Message *msg;
    DosPacket *packet;
    FileHandle *fh;

#ifdef DEBUG
    if (DDebug)
	xprintf("port %08lx\n", port);
#endif
    while (msg = GetMsg(port)) {
	packet = (DosPacket *)msg->mn_Node.ln_Name;

#ifdef DEBUG
	if (DDebug)
	    xprintf("type %08lx\n", packet->dp_Type);
#endif


	switch(packet->dp_Type) {
	case ACTION_FINDUPDATE:
	case ACTION_FINDINPUT:
	case ACTION_FINDOUTPUT:
	    fh = BTOC(packet->dp_Arg1);
	    fh->fh_Port = port;
	    break;
	case ACTION_SCREEN_MODE:
	    packet->dp_Arg2 = port;
	    break;
	}
	PutMsg(PktPort, msg);
    }
}

#ifdef DEBUG

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

#ifdef CLI_START

/*
 *  DEVICE CREATION AND DELETION
 */

DosList *Dl;

void
MkDevice(devName)
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()
{
    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;
    }
}

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);
}

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

#endif
