
/*
 *  PIPE.C  V2.03
 *
 *  YOU MUST COMPILE  +BCDL,  Aztec (32 bit ints).
 *
 *  This represents a complete turnaround in functionality from V1.  I've
 *  re-written it completely from scratch.  The PIPE: device now supports
 *  bi-directional connections through a single file-handle, dynamic buffer
 *  sizing, signal capability, and an almost-terminal-like capability.
 *
 *  NOTE NOTE NOTE: In most cases you simply use the same format as Version
 *  1.. that is, redirect <pipe:name to get the data redirected >pipe:name,
 *  don't get turned off by the technical discussion below.
 *
 */

/*
 * Some #include's may be missing since I simply give an Aztec compiler
 * option to load the entire symbol table and it doesn't actually go
 * through these includes.
 */


#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/devices.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/console.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <local/xmisc.h>

#define MPC (MEMF_PUBLIC|MEMF_CLEAR)        /* options to AllocMem()    */

#define ACTION_READWRITE    1004
#define ACTION_FIND_INPUT   1005	    /* various ACTION's supported   */
#define ACTION_FIND_OUTPUT  1006
#define ACTION_END	    1007
#define ACTION_EXAMINE	    23
#define ACTION_EXAMINENEXT  24
#define ACTION_LOCATE	    8
#define ACTION_FREELOCK     15
#define ACTION_COPYDIR	    19

#undef	BADDR
#define BADDR(x)   ((APTR)((long)x << 2))   /* convert BCPL->APTR       */

#define DOS_FALSE    0
#define DOS_TRUE     -1

#define ST_REMOTE   0x01    /* this is the remote side		*/
#define ST_WASOPEN  0x02    /* this side openned at least once	*/
#define ST_REMEOF   0x04    /* other side closed		*/
#define ST_TERM     0x08    /* terminal pipe:name[/flags]	*/
#define ST_COOKED   0x10    /* cooked mode.			*/
#define ST_STAR     0x20    /* CLI strangeness '*' open         */
#define ST_WATER    0x40    /* waiting to go below low-water mark   */


typedef struct FileLock     LOCK;
typedef struct FileInfoBlock FIB;
typedef struct DosPacket    DOSPACKET;
typedef struct Process	    PROC;
typedef struct DeviceNode   DEVNODE;
typedef struct FileHandle   FH;
typedef struct CommandLineInterface CLI;
typedef unsigned char u_char;
typedef struct Message	    MSG;

extern XLIST *llink(), *lunlink();

/*
 * a PBUF is part of a linked list of memory buffers waiting to be read
 * on a pipe.
 */

typedef struct _PBUF {
    struct _PBUF *next;
    long    bytes;
    long    index;
    char    buf[4];
} PBUF;

/*
 * The PIPE structure itself.  Actually consists of a linked pair of
 * PIPE structures.  The other is accessed through pipe->remote and
 * vise-versa.	One of the structures is the MASTER, the other is the
 * SLAVE.  The SLAVE has the ST_REMOTE flag set. The XLIST and NAME
 * entries are used only by the MASTER end for bookkeeping.
 */


typedef struct _PIPE {
    XLIST   list;		/* linked list of all pipes	*/
    MSG     *rpend;		/* whos waiting to read from me */
    MSG     *wpend;		/* whos waiting to write to me	*/
    char    *name;		/* name of this pipe		*/
    struct _PIPE *remote;	/* linked pair... other PIPE	*/
    short   refs;		/* # Open references		*/
    short   stat;		/* Status.. ST_XXXX flags	*/
    PBUF    *pbase, *plast;	/* buffers waiting to be read?	*/
    long    total;		/* total # bytes in buffers	*/
    long    lowwater;		/* below which writes are re-enabled	*/
    long    maxbytes;		/* Maximum total # bytes allowed	*/
    PROC    *procsignal;	/* process to signal on data present	*/
    short   signum;		/* signal number			*/
} PIPE;

/*
 * NOTE: Globals in the Bss space are not automatically initialized to
 * 0 when you don't use Aztec's startup _main, which we can't since
 * this is a device.
 */

extern DOSPACKET *taskwait();       /* wait for a message           */
extern void *AllocMem();
extern void *AllocDS();
extern void *strcpy();
extern void attemptread();          /* the MEAT                     */
extern PROC *FindTask();

long SysBase;			    /* required to make Exec calls  */
long DOSBase, Fh;		    /* Debugging.. required for DOS */
char Buf[256];			    /* Scratch buffer		    */

_whocareswhatthisisnamed()
{
    PROC	*myproc;
    DEVNODE	*mynode;
    PIPE	*Pipe = NULL;
    u_char	notdone;

    SysBase = *(long *)4;
    Fh = NULL;
    DOSBase = NULL;

    myproc  = (PROC *)FindTask(0L);

    /*
     * INITIAL STARTUP MESSAGE
     */

    {
	register DOSPACKET *mypkt;

	mypkt	= taskwait(myproc);
	mynode	= (DEVNODE *)BADDR(mypkt->dp_Arg3);
	mynode->dn_Task = &myproc->pr_MsgPort;
	returnpkt(mypkt, myproc, DOS_TRUE, 0);
    }


top:

#ifdef XDEBUG
    DOSBase = OpenLibrary("dos.library", 0);
    Fh = Open("con:0/0/400/100/pipedevice", 1006);
#endif

    /*
     * MAIN LOOP
     */

    notdone = 1;
    while(notdone) {
	register DOSPACKET *mypkt;	/* dos packet received	    */
	register PIPE *pipe;		/* pointer to current PIPE  */
	long type;			/* type of packet	    */

	mypkt = taskwait(myproc);       /* wait/get next packet     */
#ifdef XDEBUG
	if (Fh)
	    fhprintf(Fh, "OldRes1 = %8lx\n", mypkt->dp_Res1);
#endif
	mypkt->dp_Res1 = DOS_TRUE;	/* default return value     */
	mypkt->dp_Res2 = 0;		/* default no error	    */
	type = mypkt->dp_Type;		/* packet type		    */

	/*
	 *  Extract pipe pointer (only applies to read/write)
	 */

	pipe  = (PIPE *)mypkt->dp_Arg1;

#ifdef XDEBUG
	if (Fh) {
	    sprintf(Buf, "Packet: %4ld for %ld, pipe: %-8lx\n", mypkt->dp_Type, mypkt->dp_Arg3, pipe);
	    Write(Fh, Buf, strlen(Buf));
	}
#endif

	switch(type) {
	case ACTION_FIND_INPUT:
	case ACTION_FIND_OUTPUT:
	case ACTION_READWRITE:
	    {
		register PIPE *premote;     /* linked pair: other PIPE	*/
		register u_char *ptr, *s2;  /* scratch vars		*/
		register FH *fh;	    /* File handle from dp_Arg1 */
		short remflags = 0;	    /* ST_XXX flags for premote */
		short termflag = 0;	    /* standard flags		*/
		long  maxbytes = 4096;	    /* default max. buffer size */
		PROC *procsignal = NULL;    /* default process to sig.	*/
		short signum = -1;
		short queryflag = 0;	    /* query option?		*/

		fh = (FH *)BADDR(mypkt->dp_Arg1);       /* File handle  */
		ptr = (u_char *)BADDR(mypkt->dp_Arg3);  /* PATHNAME     */
		BMov(ptr+1, Buf, *ptr);                 /* BCPL strangeness */
		Buf[*ptr] = 0;

		/*
		 *  If '*' is the PATHNAME, then it is the CLI asking us
		 *  to duplicate it's proc->pr_CIS entry.
		 */

#ifdef XDEBUG
		if (Fh) {
		    fhprintf(Fh, "fh->fh_Buf == %08lx\n", fh->fh_Buf);
		    fhprintf(Fh, "fh->fh_Pos == %08lx\n", fh->fh_Pos);
		    fhprintf(Fh, "fh->fh_End == %08lx\n", fh->fh_End);
		    fhprintf(Fh, "fh->fh_Arg1 == %08lx\n", fh->fh_Arg1);
		    fhprintf(Fh, "fh->fh_Arg2 == %08lx\n", fh->fh_Arg2);
		}
#endif
		if (strcmp(Buf, "*") == 0) {
		    register PROC *proc;
		    register FH *fh;

		    proc = (PROC *)mypkt->dp_Port->mp_SigTask;
#ifdef XDEBUG
		    if (Fh) {
			fhprintf(Fh, "CLI -> STDIN %08lx ARG1 %08lx\n", ((CLI *)BADDR(proc->pr_CLI))->cli_StandardInput,
				((FH *)BADDR(((CLI *)BADDR(proc->pr_CLI))->cli_StandardInput))->fh_Arg1);
			fhprintf(Fh, "CLI -> STDOU %08lx ARG1 %08lx\n", ((CLI *)BADDR(proc->pr_CLI))->cli_StandardOutput,
				((FH *)BADDR(((CLI *)BADDR(proc->pr_CLI))->cli_StandardOutput))->fh_Arg1);
			fhprintf(Fh, "CLI -> CURIN %08lx ARG1 %08lx\n", ((CLI *)BADDR(proc->pr_CLI))->cli_CurrentInput,
				((FH *)BADDR(((CLI *)BADDR(proc->pr_CLI))->cli_CurrentInput))->fh_Arg1);
			fhprintf(Fh, "CLI -> CUROU %08lx ARG1 %08lx\n", ((CLI *)BADDR(proc->pr_CLI))->cli_CurrentOutput,
				((FH *)BADDR(((CLI *)BADDR(proc->pr_CLI))->cli_CurrentOutput))->fh_Arg1);
			fhprintf(Fh, "OPEN*: %08lx %08lx %08lx\n", mypkt->dp_Arg1, mypkt->dp_Arg2, mypkt->dp_Arg3);
			fhprintf(Fh, "  pr_CIS: %08lx // Arg1 %08lx\n", proc->pr_CIS, ((FH *)BADDR(proc->pr_CIS))->fh_Arg1);
			fhprintf(Fh, "  pr_COS: %08lx // Arg1 %08lx\n", proc->pr_COS, ((FH *)BADDR(proc->pr_COS))->fh_Arg1);
		    }
#endif
		    if (proc->pr_CIS) {
			pipe = (PIPE *)((FH *)BADDR(proc->pr_CIS))->fh_Arg1;
			pipe->stat |= ST_STAR;		/* FLAG IT..is CLI  */
			type = ACTION_FIND_INPUT;	/* SLAVE ONLY	    */
			goto skip;
		    } else {
			pipe = Pipe;			/* most recent open */
			pipe->stat |= ST_STAR;
			type = ACTION_FIND_INPUT;	/* SLAVE ONLY	    */
			goto skip;
		    }
		}

		/*
		 *  Process PIPE options.  Note that I don't manually
		 *  skip over ascii-numerics since that happens
		 *  automatically in the inner for() loop.
		 *
		 *  FLAGS ARE NOT PART OF THE PIPE NAME!  pipe:a and
		 *  pipe:a/n refer to the same PIPE.
		 */

		for (ptr = (u_char *)Buf; *ptr; ++ptr) {
		    if (*ptr == '/') {
			*ptr = 0;		    /* not part of name */
			for (++ptr; *ptr; ++ptr) {
			    if (*ptr == 'S')        /* SLAVE    */
				type = ACTION_FIND_INPUT;
			    if (*ptr == 'M')        /* MASTER   */
				type = ACTION_FIND_OUTPUT;
			    if (*ptr == 'c')
				;
			    if (*ptr == 'q')        /* QUERY    */
				queryflag = 1;
			    if (*ptr == 'n')        /* INFINITE BUFFERING   */
				maxbytes = 0x1FFFFFFF;
			    if (*ptr == 't')        /* TTY                  */
				termflag |= ST_TERM;
			    if (*ptr == 'b') {      /* BUFFER SIZE          */
				maxbytes = atoi(ptr+1);
				if (maxbytes < 0)
				    maxbytes = 0;
				if (maxbytes > 0x1FFFFFFF)
				    maxbytes = 0x1FFFFFFF;
			    }
			    if (*ptr == 's') {      /* SIGNAL              */
				signum = atoi(ptr+1);
				procsignal = (PROC *)mypkt->dp_Port->mp_SigTask;
			    }
			}
			break;
		    }
		}

		/*
		 *  See if PIPE exists already.
		 */

		for (pipe = Pipe; pipe; pipe = (PIPE *)pipe->list.next) {
		    if (strcmp(pipe->name, Buf) == 0)
			break;
		}
		if (pipe == NULL) {
		    if (queryflag)      /* Query Failed */
			goto fail;
		    pipe    = (PIPE *)AllocMem(sizeof(PIPE), MPC);
		    premote = (PIPE *)AllocMem(sizeof(PIPE), MPC);
		    pipe->name	    = (char *)strcpy(AllocMem(strlen(Buf)+1, 0), Buf);
		    pipe->remote    = premote;
		    pipe->stat	    |= termflag;
		    pipe->maxbytes  = premote->maxbytes = 4096;
		    pipe->lowwater  = premote->lowwater = 2048;
		    premote->remote = pipe;
		    premote->stat   |= ST_REMOTE|termflag;
		    llink(&Pipe, pipe);

#ifdef XDEBUG
		    if (Fh) {
			sprintf(Buf, "Pipe: %8lx, Pipe->next %8lx\n",
			    Pipe, Pipe->list.next);
			Write(Fh, Buf, strlen(Buf));
		    }
#endif
		}

		/*
		 * If openning the SLAVE, we should point to the SLAVE
		 * part of the pipe structure.
		 */

		if (type == ACTION_FIND_INPUT)
		    pipe = pipe->remote;

		if (queryflag) {
		    if (pipe->remote->refs == 0) {
fail:			mypkt->dp_Res1 = DOS_FALSE;
			mypkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
			goto done;
		    }
		}
skip:
		++pipe->refs;
		pipe->procsignal = procsignal;
		pipe->signum = signum;
		pipe->remote->maxbytes = maxbytes;
		pipe->remote->lowwater = maxbytes >> 1;
		pipe->remote->stat |= remflags;
		if (pipe->remote->lowwater < 2048)
		    pipe->remote->lowwater = maxbytes;

		/*
		 *  the ST_WASOPEN flag is not set on open for 'reads'.
		 *  Rather, it is set when the read operation is actually
		 *  done.  This is so programs like C:COPY can do an
		 *  Open(name,1005)/Close() to check for existance before
		 *  actually openning the destination 1006 without
		 *  screwing up the PIPE.
		 */

		if (type == ACTION_FIND_OUTPUT)
		    pipe->stat |= ST_WASOPEN;

		pipe->remote->stat &= ~ST_REMEOF;
		fh->fh_Arg1 = (long)pipe;
		fh->fh_Port = (struct MsgPort *)DOS_TRUE;
		if ((pipe->wpend || pipe->pbase) && pipe->procsignal)
		    Signal(pipe->procsignal, 1 << pipe->signum);
	    }
done:
	    returnpktplain(mypkt, myproc);
	    break;

	case ACTION_END:			    /* CLOSE	*/
	    --pipe->refs;

	    /*
	     * If closing process had a signal, we had better not
	     * signal that process anymore.
	     */

	    if (pipe->procsignal == (PROC *)mypkt->dp_Port->mp_SigTask)
		pipe->procsignal = NULL;

	    /*
	     * Check to see if we can deallocate the PIPE.  The ST_REMEOF
	     * flag means that one end has closed communications and
	     * exited while the other end was still active.  This causes
	     * an EOF on Read() and an error on Write().
	     */

	    if (pipe->refs == 0) {
		if (pipe->stat & ST_WASOPEN)
		    pipe->remote->stat |= ST_REMEOF;
		attemptread(pipe->remote, myproc);
		if (pipe->remote->stat & ST_WASOPEN) {
		    if (pipe->remote->refs == 0) {
			register PIPE *master = pipe;
			if (pipe->stat & ST_REMOTE)
			    master = pipe->remote;
			lunlink(master);

#ifdef XDEBUG
			if (Fh) {
			    sprintf(Buf, "Unlink %8lx\n", master);
			    Write(Fh, Buf, strlen(Buf));
			}
#endif

			FreeMem(master->name, strlen(master->name)+1);
			freepbufs(master);
			freepbufs(master->remote);
			FreeMem(master->remote, sizeof(PIPE));
			FreeMem(master, sizeof(PIPE));
			if (Pipe == NULL)       /* Can we exit the process? */
			    notdone = 0;
#ifdef XDEBUG
			if (Fh) {
			    sprintf(Buf, "Pipe = %8lx\n", Pipe);
			    Write(Fh,Buf,strlen(Buf));
			}
#endif
		    }
		}
	    }
	    returnpktplain(mypkt, myproc);
	    break;
	case ACTION_READ:
	    pipe->stat |= ST_WASOPEN;
	    mypkt->dp_Res1 = 0;     /* default return value is 0    */
	    llinkend(&pipe->rpend, mypkt->dp_Link);
	    attemptread(pipe, myproc);
	    break;
	case ACTION_WRITE:
	    mypkt->dp_Res1 = 0;     /* default return value is 0    */
	    if (pipe->stat & ST_REMEOF) {
		mypkt->dp_Res1 = -1;
		mypkt->dp_Res2 = ERROR_SEEK_ERROR;
		returnpktplain(mypkt, myproc);
		break;
	    }
	    llinkend(&pipe->remote->wpend, mypkt->dp_Link);
	    attemptread(pipe->remote, myproc);
	    break;
	case ACTION_COPYDIR:	/*  1:lock res1:newlock    */
	    {
		register LOCK *lock = AllocDS(sizeof(LOCK));

		BMov(mypkt->dp_Arg1 << 2, lock, sizeof(LOCK));
		returnpkt(mypkt, myproc, (long)lock << 2, 0);
	    }
	    break;
	case ACTION_LOCATE:	/*  1:lock 2:name 3:mode res1:lock/NULL */
	    {
		register LOCK *lock;
		register char *str = (char *)(mypkt->dp_Arg2 << 2);

		pipe = (PIPE *)&Pipe;
		if (str[str[0]] != ':') {
		    for (pipe = Pipe; pipe; pipe = (PIPE *)pipe->list.next) {
			if (str[0] == strlen(pipe->name) && strncmp(str+1, pipe->name, str[0]) == 0)
			    break;
		    }
		}
		if (pipe) {
		    lock = AllocDS(sizeof(LOCK));
		    lock->fl_Key = (long)pipe;
		    lock->fl_Access = mypkt->dp_Arg3;
		    lock->fl_Task = &myproc->pr_MsgPort;
		    lock->fl_Volume = (BPTR)((long)mynode >> 2);
		    returnpkt(mypkt, myproc, (long)lock >> 2, 0);
		} else {
		    returnpkt(mypkt, myproc, NULL, ERROR_OBJECT_NOT_FOUND);
		}
	    }
	    break;
	case ACTION_FREELOCK:	/*  1:lock res1:bool	*/
	    {
		if (mypkt->dp_Arg1)
		    FreeDS(mypkt->dp_Arg1 << 2);
		returnpkt(mypkt, myproc, DOS_TRUE, 0);
	    }
	    break;
	case ACTION_EXAMINE:	/*  1:lock 2:fib res1:bool  */
	    {
		register LOCK *lock = (LOCK *)(mypkt->dp_Arg1 << 2);
		register FIB  *fib  = (FIB  *)(mypkt->dp_Arg2 << 2);

		BZero(fib, sizeof(FIB));
		pipe = (PIPE *)(fib->fib_DiskKey = lock->fl_Key);
		if (pipe == (PIPE *)&Pipe) {
		    fib->fib_DirEntryType = 1;
		    strcpy(fib->fib_FileName+1, "PIPE:");
		} else {
		    fib->fib_DirEntryType = -1;
		    strcpy(fib->fib_FileName+1, pipe->name);
		    fib->fib_Size = pipe->total + pipe->remote->total;
		}
		fib->fib_FileName[0] = strlen(fib->fib_FileName+1);
	    }
	    returnpkt(mypkt, myproc, DOS_TRUE, 0);
	    break;
	case ACTION_EXAMINENEXT:    /*	1:lock 2:fib res1:bool	*/
	    {
		register FIB  *fib  = (FIB  *)(mypkt->dp_Arg2 << 2);
		register PIPE *pipe = (PIPE *)fib->fib_DiskKey;

		if (pipe == (PIPE *)&Pipe)
		    pipe = Pipe;
		else
		    pipe = (PIPE *)pipe->list.next;
		if (pipe) {
		    fib->fib_DirEntryType = -1;
		    strcpy(fib->fib_FileName+1, pipe->name);
		    fib->fib_Protection = 0;
		    fib->fib_Size = pipe->total + pipe->remote->total;
		    fib->fib_NumBlocks = 0;
		    /*
		    sprintf(fib->fib_Comment+1, "this is a comment");
		    */
		    fib->fib_Comment[1] = 0;

		    fib->fib_DiskKey = (long)pipe;
		    fib->fib_FileName[0] = strlen(fib->fib_FileName+1);
		    fib->fib_Comment [0] = strlen(fib->fib_Comment +1);
		    returnpkt(mypkt, myproc, DOS_TRUE, 0);
		} else {
		    returnpkt(mypkt, myproc, DOS_FALSE, ERROR_NO_MORE_ENTRIES);
		}
	    }
	    break;
	default:
	    returnpkt(mypkt, myproc, DOS_FALSE, ERROR_ACTION_NOT_KNOWN);
	    break;
	}
    }

#ifdef XDEBUG
    if (Fh)
	Close(Fh);
#endif
    if (DOSBase)
	CloseLibrary(DOSBase);

    /*
     *	Can only exit if no messages pending.  There might be a window
     *	here, but there is nothing that can be done about it.
     */

    Forbid();
    if (taskpktrdy(myproc)) {
	Permit();
	goto top;
    }
    mynode->dn_Task = FALSE;
    Permit();

    /* we are a process "so we fall off the end of the world" */
    /* MUST fall through */
}

void *
AllocDS(bytes)
{
    register long *ptr;
    bytes += 4;

    ptr = AllocMem(bytes, MEMF_PUBLIC|MEMF_CLEAR);
    *ptr = bytes;
    return(ptr + 1);
}

FreeDS(ptr)
register long *ptr;
{
    if (ptr)
	FreeMem(ptr - 1, *(ptr-1));
}


/*
 *  All purpose routine which does everything.	Basically:
 *
 *  (A) Fill as many read requests as possible with data from the SBUFs
 *  (B) Fill as many read requests as possible with data from pending
 *	write requests.
 *  (C) Place as many write requests into SBUFs so we can return them.
 *
 *  Also handles EOF and signalling conditions.
 */

void
attemptread(pipe, myproc)
register PIPE *pipe;
PROC *myproc;
{
    MSG *mr, *mw;
    register DOSPACKET *rp, *wp;
    register PBUF *pb;
    register long rleft, wleft, len;

#ifdef XDEBUG
    if (Fh) {
	if (pipe->stat & ST_REMOTE)
	    Write(Fh, "Reader: ", 8);
	else
	    Write(Fh, "Writer: ", 8);
    }
#endif

    /*
     *	Fill as many read requests as possible from buffer list.
     */

    while ((mr = pipe->rpend) && (pb = pipe->pbase)) {
	rp = (DOSPACKET *)mr->mn_Node.ln_Name;

	rleft = rp->dp_Arg3 - rp->dp_Res1;
	wleft = pb->bytes - pb->index;

	if (wleft > rleft)
	    wleft = rleft;
	BMov(pb->buf + pb->index, rp->dp_Arg2 + rp->dp_Res1, wleft);

	if ((rp->dp_Res1 += wleft) == rp->dp_Arg3) {
	    lunlink(mr);
	    returnpktplain(rp, myproc);
	}
	if ((pb->index += wleft) == pb->bytes) {
	    pipe->pbase = pb->next;
	    if (pipe->pbase == NULL)
		pipe->plast = NULL;
	    pipe->total -= pb->bytes;
#ifdef XDEBUG
	    if (Fh) {
		sprintf(Buf, "FreeMem: %8lx %ld\n", pb, sizeof(PBUF)- sizeof(pb->buf) + pb->bytes);
		Write(Fh, Buf, strlen(Buf));
	    }
#endif
	    FreeMem(pb, sizeof(PBUF) - sizeof(pb->buf) + pb->bytes);
	}
    }
    if (pipe->total < pipe->lowwater)
	pipe->stat &= ~ST_WATER;

    /*
     *	Fill as many read requests as possible from pending write requests
     */

    while ((mr = pipe->rpend) && (mw = pipe->wpend)) {
	rp = (DOSPACKET *)mr->mn_Node.ln_Name;
	wp = (DOSPACKET *)mw->mn_Node.ln_Name;

	/* wp->dp_Arg2 is buf, wp->dp_Arg3 is #, wp->dp_Res1 is actual	*/
	/* rp->dp_Arg2 is buf, rp->dp_Arg3 is #, rp->dp_Res1 is actual	*/

	rleft = rp->dp_Arg3 - rp->dp_Res1;  /* # left to read	*/
	wleft = wp->dp_Arg3 - wp->dp_Res1;  /* # left to write	*/

	if (wleft > rleft)  /* wleft is amount to actually transfer */
	    wleft = rleft;
	BMov(wp->dp_Arg2 + wp->dp_Res1, rp->dp_Arg2 + rp->dp_Res1, wleft);
	if ((wp->dp_Res1 += wleft) == wp->dp_Arg3) {
#ifdef XDEBUG
	    if (Fh)
		Write(Fh, "wexhaust ", 9);
#endif
	    lunlink(mw);
	    returnpktplain(wp, myproc);
	}
	if ((rp->dp_Res1 += wleft) == rp->dp_Arg3) {
#ifdef XDEBUG
	    if (Fh)
		Write(Fh, "rexhaust ", 9);
#endif
	    lunlink(mr);
	    returnpktplain(rp, myproc);
	}
    }

    /*
     * If REMOTE EOF, return any remaining read requests.  However, if
     * was from a NEWCLI, then do big hack and don't return the requests
     * since it will crash the system.	That is, the requests stay pending.
     * If we were openned with a signal option,  return remaining read
     * requests with an error (-1).
     *
     * Note: partially filled read requests are returned normally.
     */

    if ((pipe->stat & (ST_REMEOF|ST_STAR)) == ST_REMEOF) {
	while (mr = pipe->rpend) {
	    lunlink(mr);
	    rp = (DOSPACKET *)mr->mn_Node.ln_Name;
	    if (pipe->procsignal && rp->dp_Res1 == 0) {
		rp->dp_Res1 = -1;
		rp->dp_Res2 = ERROR_NO_MORE_ENTRIES;	/* nothing else comes close.	*/
	    }
	    returnpktplain(rp, myproc);
	}
	return;
    }

    /*
     * If a pipe has a signal attached to it:
     *	(A) signal the process if any writes are pending
     *	(B) return ALL read requests always immediately whether they are
     *	fullfilled or not.
     */

    if (pipe->procsignal) {
	if (pipe->wpend)
	    Signal(pipe->procsignal, 1 << pipe->signum);
	while (mr = pipe->rpend) {
	    lunlink(mr);
	    rp = (DOSPACKET *)mr->mn_Node.ln_Name;
	    returnpktplain(rp, myproc);
	}
    }

    /*
     * Put any remaining write requests into buffers, if possible.
     * Do not copy new write requests if we haven't gotten back to the
     * low water mark yet
     */

    while ((mw = pipe->wpend) && !(pipe->stat & ST_WATER)) {
	wp = (DOSPACKET *)mw->mn_Node.ln_Name;
	wleft = wp->dp_Arg3 - wp->dp_Res1;
	if (pipe->total + wleft > pipe->maxbytes) {
	    pipe->stat |= ST_WATER;
	    break;
	}
	pb = (PBUF *)AllocMem(sizeof(PBUF) + wleft - sizeof(pb->buf), 0);

#ifdef XDEBUG
	if (Fh) {
	    sprintf(Buf, "Allocate: %8lx %8ld %ld\n", pb, wleft, sizeof(PBUF) + wleft - sizeof(pb->buf));
	    Write(Fh, Buf, strlen(Buf));
	}
#endif

	if (pb == NULL)     /* Sorry, no go.    */
	    break;
	BMov(wp->dp_Arg2 + wp->dp_Res1, pb->buf, wleft);
	pb->next = NULL;
	pb->bytes = wleft;
	pb->index = 0;
	if (pipe->plast) {
	    pipe->plast->next = pb;
	    pipe->plast = pb;
	} else {
	    pipe->pbase = pipe->plast = pb;
	}
	wp->dp_Res1 += wleft;
	lunlink(mw);
	returnpktplain(wp, myproc);
	pipe->total += wleft;
    }

    /*
     * If in Terminal mode, a read request need only a single byte
     * to return.
     */

    if ((pipe->stat & ST_TERM) && (mr = pipe->rpend)) {
	rp = (DOSPACKET *)mr->mn_Node.ln_Name;
	if (rp->dp_Res1) {
	    lunlink(mr);
	    returnpktplain(rp, myproc);
	}
    }

#ifdef XDEBUG
    if (Fh)
	Write(Fh, "\n", 1);
#endif
}

/*
 *  Link a request onto the end of a doubly linked list.  Requests must
 *  be processed in order.
 */

llinkend(px, link)
register XLIST **px, *link;
{
    while (*px)
	px = &(*px)->next;
    *px = link;
    link->prev = px;
    link->next = NULL;
}

freepbufs(pipe)
register PIPE *pipe;
{
    register PBUF *pb, *npb;

    for (pb = pipe->pbase; pb; pb = npb) {
	npb = pb->next;
	FreeMem(pb, sizeof(PBUF) - sizeof(pb->buf) + pb->bytes);
    }
    pipe->pbase = pipe->plast = NULL;
}

XLIST *
llink(list, en)
register XLIST *en, **list;
{
    en->next = *list;
    en->prev = list;
    *list = en;
    if (en->next)
	en->next->prev = &en->next;
    return(en);
}

XLIST *
lunlink(en)
register XLIST *en;
{
    if (en) {
	if (en->next)
	    en->next->prev = en->prev;
	*en->prev = en->next;
	en->next = NULL;
	en->prev = NULL;
    }
    return(en);
}

