[comp.sys.amiga] PIPE: device

dillon@CORY.BERKELEY.EDU.UUCP (02/06/87)

	Here's a PIPE device.  I would like to thank Phillip Lindsay
for providing the skeleton device driver which I used as a base.  This
will ONLY WORK WITH 1.2!!.  Example (run all at once):

CLI window 1:	copy hugefile pipe:a
CLI window 2:	copy pipe:a pipe:b
CLI window 3:	copy pipe:b pipe:c
CLI window 4:	copy pipe:c pipe:d
CLI window 5:	copy pipe:d pipe:e
CLI window 6:	wordcount pipe:e		(or something similar)

	You can, of course, use any rendezvous name you wish.  The device
uses a 4K internal buffer per name, but is optimized to take advantage of
the situation in which there is a pending read and a pending write. In this
case it copies direct rather than go through its internal buffer.  This is
a true pipe, and thus the source and destination processes must be distinct
(i.e. not the same process) so as to prevent lockout situations.  The buffer
is transparent in that data written, no matter how little, is immediately
available to be read by the other process.

	You must place PIPE.DEVICE in 'DEVS:'.  Append the MOUNTLIST file to
DEVS:MOUNTLIST (or just put it there if you do not have a DEVS:MOUNTLIST),
and then do a 'MOUNT PIPE:' in your startup script.

	The source is provided also.  Remember, since this is a device, you
do NOT link with any startup module.  I have done a couple of other tests
and have found that you get a huge efficiency increase when piping an IO
bound program through a CPU bound program or vise versa.  This is totally
public domain except for MISC.C, which is (C) to Phillip (but redistributable).
The binary itself is totally public domain.

USES:

	I hope to have my shell use the PIPE: device in it's next release,
but this will require huge modifications to the shell so don't expect the
next release anytime soon. 

Apart from that, the PIPE: device can be useful when you have, say, two
application programs and want to transfer huge amounts of data from one 
(write) to the other (read) without using a temporary file in RAM: or on
disk.  Assuming the application does not attempt a Seek(), you simply
specify 'PIPE:name' and it looks like an ordinary file to the application.

For those terminal programs which do not use asyncronous writes, you can
fix the jerkyness in CAPTURE by capturing to a pipe, and having another
CLI Copy command running from the pipe to a file.

					Have fun and report any bugs to me,

					-Matt

	
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	misc.c
#	mountlist
#	pipe.c
#	pipe.device.uue
# This archive created: Fri Feb  6 11:46:11 1987
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'misc.c'" '(1790 characters)'
if test -f 'misc.c'
then
	echo shar: "will not over-write existing file 'misc.c'"
else
cat << \!Funky!Stuff! > 'misc.c'

/*
 *  misc.c  - support routines - Phillip Lindsay (C) Commodore 1986
 *  You may freely distribute this source and use it for Amiga Development -
 *  as long as the Copyright notice is left intact.
 *
 * 30-SEP-86
 *
 *  Modified by Matthew Dillon for my PIPE: device.
 */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/ports.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>

extern void returnpkt();

/* returnpkt() - packet support routine
 * here is the guy who sends the packet back to the sender...
 *
 * (I modeled this just like the BCPL routine [so its a little redundant] )
 */

void
returnpktplain(packet, myproc)
struct DosPacket *packet;
struct Process *myproc;
{
    returnpkt(packet, myproc, packet->dp_Res1, packet->dp_Res2);
}

void
returnpkt(packet, myproc, res1, res2)
struct DosPacket *packet;
struct Process *myproc;
ULONG  res1, res2;
{
    struct Message *mess;
    struct MsgPort *replyport;

    packet->dp_Res1          = res1;
    packet->dp_Res2          = res2;
    replyport                = packet->dp_Port;
    mess                     = packet->dp_Link;
    packet->dp_Port          = &myproc->pr_MsgPort;
    mess->mn_Node.ln_Name    = (char *) packet;
    mess->mn_Node.ln_Succ    = NULL;
    mess->mn_Node.ln_Pred    = NULL;
    PutMsg(replyport, mess);
}


/*
 * taskwait() ... Waits for a message to arrive at your port and
 *   extracts the packet address which is returned to you.
 */

struct DosPacket *
taskwait(myproc)
struct Process *myproc;
{
    struct MsgPort *myport;
    struct Message *mymess;

    myport = &myproc->pr_MsgPort;
    WaitPort(myport);
    mymess = (struct Message *)GetMsg(myport);
    return((struct DosPacket *)mymess->mn_Node.ln_Name);
}

/* end of misc.c    */


!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'mountlist'" '(119 characters)'
if test -f 'mountlist'
then
	echo shar: "will not over-write existing file 'mountlist'"
else
cat << \!Funky!Stuff! > 'mountlist'

PIPE:      Handler = devs:pipe.device
           Stacksize = 5000
           Priority = 5
           GlobVec  = 1
#


!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'pipe.c'" '(13238 characters)'
if test -f 'pipe.c'
then
	echo shar: "will not over-write existing file 'pipe.c'"
else
cat << \!Funky!Stuff! > 'pipe.c'

/*
 *  PIPE: device driver.
 *
 *  Usage:
 *      The writer opens PIPE:somename and begins writing to it.  The reader
 *      opens PIPE:samename and begins reading from it.  It doesn't matter
 *      who opens PIPE:somename first.  Note that if the writer opens the
 *      handle first, writes <BUFSIZE bytes, then closes, the Close() will
 *      not return until a reader has openned the same pipe.
 *
 *      -Only two opens can be made on a specific pipe
 *      -One of the opens must always write while the other must always
 *       read.
 *
 *      If the reader closed, any further writes will return an error
 *      If the writer closed, any further reads (after the buffer empties)
 *       will return 0.
 *
 *  NOTE:   Like the filesystem DOS device, I assume that no more than one
 *  request for a specific file handle will be queued at a time.  This makes
 *  things a lot easier for me.
 *
 *
 */


#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>

typedef struct DosPacket    DOSPACKET;
typedef struct Process      PROC;
typedef struct DeviceNode   DEVNODE;
typedef struct FileHandle   FH;
typedef unsigned char u_char;

#define BUFSIZE 4096

#undef  BADDR
#define BADDR(x)   ((APTR)((long)x << 2))

#define ACTION_FIND_INPUT       1005L
#define ACTION_FIND_OUTPUT      1006L
#define ACTION_END              1007L

#define DOS_FALSE    0
#define DOS_TRUE     -1

#define ST_EOF      0x01        /*  Handle has been closed      */
#define ST_WPEND    0x04        /*  pending packet is a write   */
#define ST_RPEND    0x08        /*  pending packet is a read    */
#define ST_CPEND    0x10        /*  close pending (writer)      */

#define OC_FIRST    1           /*  first open, needs to be another */
#define OC_BOTH     2           /*  both reader and writer open     */
#define OC_LAST     3           /*  one closed, one remaining       */
#define OC_WAITSECOND   4       /*  first open was closed before second was openned */


extern long AbsExecBase;
extern DOSPACKET *taskwait();
extern char *AllocMem();
long SysBase;

typedef struct _PIPE {
    struct _PIPE *next, **prev;
    DOSPACKET *pkt;             /* Current pending packet, if any       */
    char    buf[BUFSIZE];       /* Output Buffer                        */
    char    *name;              /* name (allocated strlen(name)+1)      */
    short   s, e, l;            /* FIFO start, end, size                */
    char    state;              /* Current state                        */
    char    openstate;
} PIPE;


_main()
{
    PROC        *myproc;     /* my process                             */
    DOSPACKET   *mypkt;      /* a pointer to the dos packet sent       */
    BSTR        parmdevname; /* pointer to device name in parmpkt Arg1 */
    long        parmextra;   /* extra info passed in parmpkt      Arg2 */
    DEVNODE     *mynode;     /* our device node passed in parmpkt Arg3 */
    FH          *fh;         /* a pointer to our file handle           */
    PIPE        *pipe;       /* current PIPE handle                    */
    PIPE        *Pipe = NULL;/* linked list base for all pipes         */
    char        *str;
    u_char      *ptr;
    long        run = TRUE;  /* handler main loop flag                 */
    int         ret;         /* nominal packet return value            */
    int         totalcnt = 0;/* total # active pipes                   */

    SysBase = AbsExecBase;

    myproc      = (PROC *)FindTask(0L);     /* find myself                  */
    mypkt       = taskwait(myproc);         /*  Wait for startup message    */
    parmdevname = (BSTR)mypkt->dp_Arg1;     /* BSTR name passed to handler  */
    parmextra   = mypkt->dp_Arg2;           /* Extra Info passed            */
    mynode      = (DEVNODE *)BADDR(mypkt->dp_Arg3); /* ptr to device node   */

    /* if taskid NOT installed, every ref creates new  */
    /* code must be reentrant                          */

    mynode->dn_Task = &myproc->pr_MsgPort;
    returnpkt(mypkt, myproc, DOS_TRUE, mypkt->dp_Res2);

    while(run) {
        mypkt = taskwait(myproc);
        ret = DOS_TRUE;
        pipe = (PIPE *)mypkt->dp_Arg1;

        switch(mypkt->dp_Type) {
        case ACTION_FIND_INPUT:
        case ACTION_FIND_OUTPUT:
            fh = (FH *)BADDR(mypkt->dp_Arg1);       /* File handle  */
            ptr = (u_char *)BADDR(mypkt->dp_Arg3);  /* File name    */
            str = AllocMem(*ptr + 1, 0);
            if (str == NULL) {
                ret = DOS_FALSE;
                goto opfail;
            }
            bmov(ptr+1, str, *ptr);
            str[*ptr] = 0;
            for (pipe = Pipe; pipe; pipe = pipe->next) {
                if (strcmp(pipe->name, str) == 0) {
                    FreeMem(str, *ptr + 1);
                    goto openok;
                }
            }
            pipe = (PIPE *)AllocMem(sizeof(PIPE), 0);
            if (pipe == NULL) {
                ret = DOS_FALSE;
                FreeMem(str, *ptr + 1);
                goto opfail;
            }
            ++totalcnt;

            bzero(pipe, sizeof(*pipe));
            pipe->l = BUFSIZE;
            pipe->name = str;
            if (Pipe)
                Pipe->prev = &pipe->next;
            pipe->next = Pipe;
            pipe->prev = &Pipe;
            Pipe = pipe;
openok:
            switch(pipe->openstate) {
            case 0:
                pipe->openstate = OC_FIRST;
                break;
            case OC_FIRST:
                pipe->openstate = OC_BOTH;
                break;
            case OC_WAITSECOND:
                pipe->openstate = OC_LAST;
                pipe->state &= ~ST_CPEND;
                returnpkt(pipe->pkt, myproc, DOS_TRUE, pipe->pkt->dp_Res2);
                break;
            case OC_BOTH:
            case OC_LAST:
                ret = DOS_FALSE;        /* more than 2 opens    */
                goto opfail;
            }
            fh->fh_Arg1 = (long)pipe;
            fh->fh_Port = (struct MsgPort *)DOS_TRUE;
opfail:
            returnpkt(mypkt, myproc, ret, mypkt->dp_Res2);
            break;
        case ACTION_END:    /*  If pending read, return pend bytes read
                             *  If pending write, return pend bytes written
                             *  return pending message, if any
                             */
            switch(pipe->openstate) {
            case OC_FIRST:
                pipe->openstate = OC_WAITSECOND;
                break;
            case OC_BOTH:
                pipe->openstate = OC_LAST;
                break;
            case OC_LAST:
                pipe->openstate = 0;
                break;
            }

            pipe->state |= ST_EOF;

            if (pipe->openstate == OC_WAITSECOND) {
                pipe->pkt = mypkt;
                pipe->state |= ST_CPEND;
                break;
            }

            if (pipe->state & (ST_RPEND|ST_WPEND)) {
                returnpktplain(pipe->pkt, myproc);
                pipe->state &= ~(ST_RPEND|ST_WPEND);
            }
            if (pipe->openstate == 0) {
                FreeMem(pipe->name, strlen(pipe->name)+1);
                *pipe->prev = pipe->next;
                if (pipe->next)
                    pipe->next->prev = pipe->prev;
                FreeMem(pipe, sizeof(*pipe));
                --totalcnt;
                if (totalcnt == 0)
                    run = 0;
            }
            returnpkt(mypkt, myproc, ret, mypkt->dp_Res2);
            break;
        case ACTION_READ:   /*  Take chars from buffer.  If buffer empty
                             *  and read not satisfied, check for pending
                             *  write and take bytes from it's buffer.
                             *  When done, must check to see if buffer will
                             *  hold ALL of pending write.
                             *  When done, if read still is not satisfied,
                             *  make it pending.
                             */

            mypkt->dp_Res1 = 0;

            /*
             * Load from buffer until empty or read fulfilled
             */

            while (pipe->s != pipe->e && mypkt->dp_Res1 != mypkt->dp_Arg3) {
                int avail = (pipe->s < pipe->e) ?   pipe->e - pipe->s :
                                                    pipe->l - pipe->s;
                int bytes = mypkt->dp_Arg3 - mypkt->dp_Res1;
                if (bytes < avail)
                    avail = bytes;
                bmov(pipe->buf+pipe->s, mypkt->dp_Arg2+mypkt->dp_Res1,avail);
                pipe->s += avail;
                mypkt->dp_Res1 += avail;
                if (pipe->s == pipe->l)
                    pipe->s = 0;
            }

            /*
             * If write packet was pending, the read will either exhaust
             * the write and possibly become pending, or not exhaust the
             * write and be returned.
             */

            if (mypkt->dp_Res1 != mypkt->dp_Arg3 && (pipe->state & ST_WPEND)) {
                int bytes = mypkt->dp_Arg3 - mypkt->dp_Res1;
                int avail = pipe->pkt->dp_Arg3 - pipe->pkt->dp_Res1;
                if (bytes > avail)
                    bytes = avail;
                bmov(pipe->pkt->dp_Arg2 + pipe->pkt->dp_Res1, mypkt->dp_Arg2 + mypkt->dp_Res1, bytes);
                mypkt->dp_Res1 += bytes;
                pipe->pkt->dp_Res1 += bytes;
                if (pipe->pkt->dp_Res1 == pipe->pkt->dp_Arg3) {
                    returnpktplain(pipe->pkt, myproc);
                    pipe->state &= ~ST_WPEND;
                }
            }

            /* If read packet is made pending, buffer is always empty   */

            if (mypkt->dp_Res1 != mypkt->dp_Arg3 && !(pipe->state&ST_EOF)) {
                if (pipe->state & (ST_RPEND|ST_WPEND|ST_CPEND)) {
                    returnpkt(pipe->pkt, myproc, DOS_FALSE, ERROR_OBJECT_IN_USE);
                    pipe->state &= ~(ST_RPEND|ST_WPEND|ST_CPEND);
                }
                pipe->pkt = mypkt;
                pipe->state |= ST_RPEND;
            } else {
                returnpktplain(mypkt, myproc);
            }
            break;
        case ACTION_WRITE:  /*
                             *  If pending read then buffer is empty, place
                             *  chars directly into pending read.  If pending
                             *  write, error.
                             *  If nothing pending and write buffer not big
                             *  enough, make the write pend without filling
                             *  the buffer.  Otherwise, move write data into
                             *  the buffer and return the packet.
                             */

            mypkt->dp_Res1 = 0;

            if (pipe->state & ST_EOF) {
                returnpkt(mypkt, myproc, DOS_FALSE, ERROR_SEEK_ERROR);
                break;
            }
            if (pipe->state & ST_RPEND) {           /* write->read      */
                int avail = mypkt->dp_Arg3 - mypkt->dp_Res1;
                int bytes = pipe->pkt->dp_Arg3 - pipe->pkt->dp_Res1;
                if (avail < bytes)
                    bytes = avail;
                bmov(mypkt->dp_Arg2+mypkt->dp_Res1, pipe->pkt->dp_Arg2+pipe->pkt->dp_Res1, bytes);
                mypkt->dp_Res1 += bytes;
                pipe->pkt->dp_Res1 += bytes;
                if (pipe->pkt->dp_Res1 == pipe->pkt->dp_Arg3) {
                    returnpktplain(pipe->pkt, myproc);
                    pipe->state &= ~ST_RPEND;
                }
            }

            /*  write into buffer   */

            while (mypkt->dp_Res1 != mypkt->dp_Arg3 && pipe->s != ((pipe->e+1)%pipe->l)) {
                int avail = mypkt->dp_Arg3 - mypkt->dp_Res1;
                int bytes;
                if (pipe->e < pipe->s)
                    bytes = pipe->s - pipe->e - 1;
                else
                    bytes = pipe->l - pipe->e - (pipe->s == 0);
                if (avail < bytes)
                    bytes = avail;
                bmov(mypkt->dp_Arg2 + mypkt->dp_Res1, pipe->buf + pipe->e, bytes);
                pipe->e += bytes;
                mypkt->dp_Res1 += bytes;
                if (pipe->e == pipe->l)
                    pipe->e = 0;
            }

            if (mypkt->dp_Res1 != mypkt->dp_Arg3) {
                if (pipe->state & (ST_RPEND|ST_WPEND|ST_CPEND)) {
                    returnpkt(pipe->pkt, myproc, DOS_FALSE, ERROR_OBJECT_IN_USE);
                    pipe->state &= ~(ST_RPEND|ST_WPEND|ST_CPEND);
                }
                pipe->pkt = mypkt;
                pipe->state |= ST_WPEND;
            } else {
                returnpktplain(mypkt, myproc);
            }
            break;
        default:
            returnpkt(mypkt, myproc, DOS_FALSE, ERROR_ACTION_NOT_KNOWN);
            break;
        }
    }
    mynode->dn_Task = FALSE;

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



!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'pipe.device.uue'" '(4502 characters)'
if test -f 'pipe.device.uue'
then
	echo shar: "will not over-write existing file 'pipe.device.uue'"
else
cat << \!Funky!Stuff! > 'pipe.device.uue'
begin 644 pipe.device
M```#\P`````````(``````````<````"`````0```EP````&````#`````4`
M```A````$0```^D````"3OD```````````/L`````0````(````"````````
M`_(```/K`````0```_(```/I```"7$Y6_\1(YR`@D<@M2/_@<`$M0/_4(_D`
M```$`````"\(+4C_S$ZY````,%B/+P`M0/_\3KD```CJ6(\@0"UH`!3_]"UH
M`!C_\"(H`!SE@2)N__S2_`!<)$$E20`(+R@`$'3_+P(O+O_\+P`M0/_X+4'_
M[$ZY```(F$_O`!!*KO_49P`'Y"\N__Q.N0``".I8CW+_+4'_T"!`+6@`%/_D
M+4#_^"`H``AR*`2!````"&L`!YBPNQ@(9O!.^Q@&````5V``!3@```!28``#
M*````^]@``'N```#[F````H```/M8````B!N__@@*``4Y8`B*``<Y8%T`"!!
M%!!2@D*G+P(M0/_H+4'_V$ZY`````%"/+4#_W$J`9@A"KO_08``!@B!N_]A2
MB'``(F[_V!`1+P`O+O_<+PA.N0````!/[P`,<``@;O_8$!`@;O_<T<!"$"UN
M_^#_Y$JN_^1G/B\N_]P@;O_D+R@0#$ZY```)'%"/2H!F''``(&[_V!`04H`O
M`"\N_]Q.N0```!A0CV```(P@;O_D+5#_Y&"\0J<O/```$!A.N0````!0CRU`
M_^1*@&8@0J[_T'``(&[_V!`04H`O`"\N_]Q.N0```!A0CV```-!2KO_,+SP`
M`!`8+R[_Y$ZY`````%"/(&[_Y#%\$``0%"%N_]P0#$JN_^!G"")N_^`C2``$
M(&[_Y""N_^!#[O_@(4D`!"U(_^`@;O_D$"@0%TB`2,`,@`````5D8N.`3OL(
M`F`(8!)@4&!.8!@@;O_D$7P``1`78$8@;O_D$7P``A`78#H@;O_D$7P``Q`7
M$"@0%@(`_^\10!`6(&@`""\H`!!P_R\`+R[__"\(3KD```B83^\`$&`&0J[_
MT&`0(&[_Z"%N_^0`)'#_(4``!"!N__@O*``0+R[_T"\N__PO"$ZY```(F$_O
M`!!@`/V^(&[_Y!`H$!=(@$C`#(`````#9R@,@`````)G%`R``````68@(&[_
MY!%\``00%V`4(&[_Y!%\``,0%V`((&[_Y$(H$!<@;O_D$"@0%@````$10!`6
M$B@0%UD!9A8A;O_X``@0*!`6````$!%`$!9@`/U,(&[_Y!`H$!8"```,2@!G
M("\N__PO*``(3KD```AX4(\@;O_D$"@0%@(`__,10!`6(&[_Y!`H$!=*`&9D
M+R@0#$ZY`````%B/4H`O`"!N_^0O*!`,3KD````84(\B;O_D(&D`!")1((D@
M;O_D2I!G#"!0(F[_Y"%I``0`!"\\```0&"\N_^1.N0```!A0CR`N_\Q3@"U`
M_\Q*@&8$0J[_U"!N__@O*``0+R[_T"\N__PO"$ZY```(F$_O`!!@`/R,(&[_
M^$*H``P@;O_D,"@0$#(H$!*P06<``+8@;O_X("@`#+"H`!QG``"F(&[_Y#`H
M$!!(P$C!L(%L!)*`8!0@;O_D,"@0%$C`,B@0$$C!D($B`"!N__@@*``<D*@`
M#"U`_\0M0?_(L(%L!"U`_\@@;O_D,"@0$$C`T/P`#-'`(F[_^"`I`!C0J0`,
M+R[_R"\`+PA.N0````!/[P`,(&[_Y#`H$!!(P"(N_\C0@3%`$!`B;O_XTZD`
M##`H$!`R*!`4L$%F`/]$0F@0$&``_SP@;O_X("@`#+"H`!QG``"P(&[_Y!`H
M$!8(```"9P``H"!N__@@*``<D*@`#")N_^0@:0`((B@`')*H``PM0/_(+4'_
MQ+"!;P0M0?_((F[_Y"!I``@@*``8T*@`#"!N__@B*``8TJ@`#"\N_\@O`2\`
M3KD`````3^\`#"`N_\@@;O_XT:@`#")N_^0@:0`((B@`#-"!(4``#+"H`!QF
M("\N__PO*0`(3KD```AX4(\@;O_D$"@0%@(`__L10!`6(&[_^"`H``RPJ``<
M9UH@;O_D$"@0%@@```!F3`(``!Q*`&<J+SP```#*0J<O+O_\+R@`"$ZY```(
MF$_O`!`@;O_D$"@0%@(`_^,10!`6(&[_Y"%N__@`"!`H$!8````($4`0%F``
M^I@O+O_\+R[_^$ZY```(>%"/8`#ZA'``(&[_^"%```PB;O_D$"D0%@@```!G
M'"\\````VT*G+R[__"\(3KD```B83^\`$&``^E`@;O_D$"@0%@@```-G``"@
M(&[_^"`H`!R0J``,(F[_Y"!I``@B*``<DJ@`#"U`_\@M0?_$L(%L!"U`_\0@
M;O_X("@`&-"H``PB;O_D(&D`""(H`!C2J``,+R[_Q"\!+P!.N0````!/[P`,
M("[_Q"!N__C1J``,(F[_Y"!I``@B*``,T($A0``,L*@`'&8@+R[__"\I``A.
MN0``"'A0CR!N_^00*!`6`@#_]Q%`$!8@;O_X("@`#+"H`!QG``#H(&[_Y#`H
M$!)(P%*`,B@0%$C!3KD`````,"@0$$C`L(%G``#$(&[_^"`H`!R0J``,(&[_
MY#(H$!)(P30H$!!(PBU`_\BR@FP*E(%3@BU"_\1@)B!N_^0P*!`42,`R*!`2
M2,&0@3(H$!!*05?"1`)(@DC"D((M0/_$("[_R+"N_\1L!"U`_\0@;O_X("@`
M&-"H``P@;O_D,B@0$DC!T/P`#-'!+R[_Q"\(+P!.N0````!/[P`,(&[_Y#`H
M$!)(P"(N_\30@3%`$!(B;O_XTZD`##`H$!(R*!`4L$%F`/\40F@0$F``_PP@
M;O_X("@`#+"H`!QG5"!N_^00*!`6`@``'$H`9RHO/````,I"IR\N__PO*``(
M3KD```B83^\`$"!N_^00*!`6`@#_XQ%`$!8@;O_D(6[_^``($"@0%@````01
M0!`68`#X2B\N__PO+O_X3KD```AX4(]@`/@V+SP```#10J<O+O_\+R[_^$ZY
M```(F$_O`!!@`/@8(&[_[$*H``A,WP0$3EY.=0``3E8``"!N``@O*``0+R@`
M#"\N``PO"&$(3^\`$$Y>3G5.5O_X2.<`,"!N``@A;@`0``PA;@`4`!`B:``$
M)%`F;@`,UOP`7"%+``0E2``*D<@DB"5(``0O"B\)+4G_^"U*__Q.N0```$10
MCTS?#`!.7DYU3E;_^"!N``C0_`!<+P@M2/_\3KD```!P6(\O+O_\3KD```!<
M6(\B0"!I``H@"$Y>3G5.5@``2.<`#"IN``@H;@`,2A5G+DH49RH0%1(4L`%D
M"G#_3-\P`$Y>3G40%1(4L`%C"G`!3-\P`$Y>3G52C5*,8,X0%1(4L`%FSG``
M3-\P`$Y>3G4```/L`````0````$````:````$`````(```@^```&Q@``!?``
M``5H```#3````6H```A:```(`@``!B(```6T```#Y@```K0```*`````=```
M`(H````T`````0````,```-T````!0````0```>8```&D```!3(```2&```!
M.`````$````%```!X@````H````&```)"@``"/X```C<```#N@```X@```'(
M```!A@```:0```$*````)@````$````'```'``````````/R```#Z0````8@
M;P`$2AAF_)'O``21_`````$@"$YU``````/R```#Z0````P@;P`$(F\`""`O
M``QG```>L\AG```8;P``#M'`T\`3(%.`9OI.=1+84X!F^DYU``````/R```#
MZ0````4@;P`$("\`"&<```A"&%.`9OI.=0```_(```/I````(2\.+'D`````
M3.\``P`(3J[_.BQ?3G4``"\.+'D`````(F\`""`O``Q.KO\N+%].=2\.+'D`
M````(F\`"$ZN_MHL7TYU+PXL>0````!,[P,```A.KOZ2+%].=0``+PXL>0``
M```@;P`(3J[^C"Q?3G4O#BQY`````"!O``A.KOZ`+%].=0```^P````&````
M`0```'0```!@````2````#0````<````!`````````/P`````U]786ET4&]R
M=````````'`````"7T=E=$US9P````!<`````E]0=71-<V<`````1`````-?
M1FEN9%1A<VL````````P`````E]&<F5E365M````&`````-?06QL;V--96T`
M`````````````````_(```/I````$4CG/``J`6<R:@)$@2@`9RAJ`D2`0H)V
M'^.`XY*T@64$E(%2@%'+__(B`KF%:@)$@+.$:@A$@6`$0H%"@$S?`#Q.=0``
$```#\I*T
`
end
!Funky!Stuff!
fi  # end of overwriting check
exit 0
#	End of shell archive

FATQW@USU.BITNET (02/19/88)

Could somebody send me a copy of a PIPE: device?  I need one for some
stuff I'm doing.
Thanks in advance

                                Bryan

        Bryan Ford                    //// A computer does what \\\\
Snail:  1790 East 1400 North         //// you tell it to do, not \\\\
        Logan, UT 84321         \\\XX///  what you want it to do. \\\XX///
Email:  FATQW@USU.BITNET         \XXXX/ Murphy's Law Calendar 1986 \XXXX/