Utilities 3 (Disk 38) (Mar 1987) : c / pipedevice / 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 */
}