$title('PERIPHERAL INTERCHANGE PROGRAM')
PIPMOD:
    DO;
/* P E R I P H E R A L  I N T E R C H A N G E  P R O G R A M

         COPYRIGHT (C) 1976, 1977, 1978, 1979, 1980, 1981, 1982
         DIGITAL RESEARCH
         BOX 579
         PACIFIC GROVE, CA
         93950

         Revised:
            17 Jan 80  by  Thomas Rolander (MP/M 1.1)
            05 Oct 81  by  Ray Pedrizetti  (MP/M-86 2.0)
            18 Dec 81  by  Ray Pedrizetti  (CP/M-86 1.1)
            29 Jun 82  by  Ray Pedrizetti  (CCP/M-86 3.0) */

/*    Command lines used for CMD file generation         */

/* (on VAX)
	asm86 scd1.a86
        asm86 inpout.a86
	plm86 pip.plm debug xref optimize(3)
	link86 scd1.obj,inpout.obj,pip.obj, to pip.lnk
	loc86 pip.lnk od(sm(code,dats,data,const,stack)) -
              ad(sm(code(0), dats(10000h)))  ss(stack(+32)) to pip.
	h86 pip

   (on a micro)
	vax pip.h86 $fans
	gencmd pip data[b1000 m280 xfff]

        * note the beginning of the data segment will change when
	* the program is changed. see the 'MP2' file generated
	* by LOC86.  the constants are last to force hex generation
   */

   /* Compiler Directives */
/** $set (mpm) **/
/** $reset (cpm3) **/
/** $cond **/

declare    /* resets stack for error handling */
    reset label external;

DECLARE
    MAXB  ADDRESS EXTERNAL,     /* ADDR FIELD OF JMP BDOS */
    FCB (33) BYTE EXTERNAL,     /* DEFAULT FILE CONTROL BLOCK */
    BUFF(128)BYTE EXTERNAL;     /* DEFAULT BUFFER */

declare
    retry byte initial(0);  /* true if error has occured */

OUTD: PROCEDURE(B) external;
    DECLARE B BYTE;
    /* SEND B TO OUT: DEVICE */
    END OUTD;

INPD: PROCEDURE BYTE external;
    END INPD;

MON1: PROCEDURE(F,A) EXTERNAL;
    DECLARE F BYTE,
    A ADDRESS;
    END MON1;

MON2: PROCEDURE(F,A) BYTE EXTERNAL;
    DECLARE F BYTE,
    A ADDRESS;
    END MON2;

MON3: PROCEDURE(F,A) ADDRESS EXTERNAL;
    DECLARE F BYTE,
    A ADDRESS;
    END MON3;


plm: procedure public;

DECLARE
/** $if mpm **/
    VERSION  LITERALLY '0031H', /* REQUIRED FOR BDOS 3.1 OPERATION */
/** $else **/
/** $endif **/

    ENDFILE LITERALLY '1AH';    /* END OF FILE MARK */

DECLARE COPYRIGHT(*) BYTE DATA (
/** $if cpm3 **/
        ' (12/06/82) CP/M 3 PIP VERS 3.0 ');
/** $else **/
/** $endif **/


    /* LITERAL DECLARATIONS */
DECLARE
    LIT  LITERALLY 'LITERALLY',
    LPP  LIT '60',     /* LINES PER PAGE */
    TAB  LIT '09H',    /* HORIZONTAL TAB */
    FF   LIT '0CH',    /* FORM FEED */
    LA   LIT '05FH',   /* LEFT ARROW */
    LB   LIT '05BH',   /* LEFT BRACKET */
    RB   LIT '05DH',   /* RIGHT BRACKET */

    FSIZE  LIT '33',
    FRSIZE LIT '36',   /* SIZE OF RANDOM FCB */
    NSIZE  LIT '8',
    FNSIZE LIT '11',
    FEXT   LIT '9',
    FEXTL  LIT '3',

        /* scanner return type code */
    outt     LIT '0',   /* output device */
    PRNT     LIT '1',   /* PRINTER */
    LSTT     LIT '2',   /* list device */
    axot     lit '3',   /* auxilary output device */
    FILE     LIT '4',   /* file type */
    auxt     lit '5',   /* auxilary input/output device */
    CONS     LIT '6',   /* CONSOLE */
    axit     LIT '7',   /* auxilary input device */
    inpt     lit '8',   /* input device */
    NULT     LIT '9',   /* nul characters */
    EOFT     LIT '10',   /* EOF character */
    ERR      LIT '11',  /* error type */
    SPECL    LIT '12',  /* special character */
    DISKNAME LIT '13';  /* diskname letter */

DECLARE
    SEARFCB LIT 'FCB';   /* SEARCH FCB IN MULTI COPY */

DECLARE
    TRUE    LIT '1',
    FALSE   LIT '0',
    FOREVER LIT 'WHILE TRUE',
    cntrlc  lit '3',
    CR      LIT '13',
    LF      LIT '10',
    WHAT    LIT '63';

/** $if mpm **/
declare
    maxmcnt lit '128',    /* maximum multi sector count */
    maxmbuf lit '16384';  /* maximum multi sector buffer size */
/** $endif **/

DECLARE
    COLUMN   BYTE,      /* COLUMN COUNT FOR PRINTER TABS */
    LINENO   BYTE,      /* LINE WITHIN PAGE */
    FEEDBASE BYTE,      /* USED TO FEED SEARCH CHARACTERS */
    FEEDLEN  BYTE,      /* LENGTH OF FEED STRING */
    MATCHLEN BYTE,      /* USED IN MATCHING STRINGS */
    QUITLEN  BYTE,      /* USED TO TERMINATE QUIT COMMAND */
    CDISK    BYTE,      /* CURRENT DISK */
    SBLEN    ADDRESS,   /* SOURCE BUFFER LENGTH */
    DBLEN    ADDRESS,   /* DEST BUFFER LENGTH */
    tblen    address,   /* temp buffer length */
    SBASE    ADDRESS,   /* SOURCE BUFFER BASE */

    /* THE VECTORS DBUFF AND SBUFF ARE DECLARED WITH DIMENSION
    1024, BUT ACTUALLY VARY WITH THE FREE MEMORY SIZE */
    DBUFF(1024) BYTE AT (.MEMORY), /* DESTINATION BUFFER */
    SBUFF BASED SBASE (1024) BYTE, /* SOURCE BUFFER */

        /* source fcb, password and password mode */
    source structure (
        fcb(frsize)  byte,
/** $if mpm **/
        pwnam(nsize) byte,
        pwmode       byte,
/** $endif **/
        user         byte,
        type         byte ),

        /* temporary destination fcb, password and password mode */
    dest structure (
        fcb(frsize)  byte,
/** $if mpm **/
        pwnam(nsize) byte,
        pwmode       byte,
/** $endif **/
        user         byte,
        type         byte ),

        /* original destination fcb, password and password mode */
    odest structure (
        fcb(frsize)  byte,
/** $if mpm **/
        pwnam(nsize) byte,
        pwmode       byte,
/** $endif **/
        user         byte,
        type         byte ),

    filsize(3) byte,     /* file size random record number */

    DESTR    ADDRESS AT(.DEST.FCB(33)),    /* RANDOM RECORD POSITION */
    SOURCER  ADDRESS AT(.SOURCE.FCB(33)),  /* RANDOM RECORD POSITION */
    DESTR2   BYTE    AT(.DEST.FCB(35)),    /* RANDOM RECORD POSITION R2 */
    SOURCER2 BYTE    AT(.SOURCE.FCB(35)),  /* RANDOM RECORD POSITION R2 */

    extsave  byte,      /* temp extent byte for bdos bug */

    nsbuf   address,    /* next source buffer */ 
/** $if mpm **/
    bufsize address,    /* multsect buffer size */
    mseccnt  byte,      /* last multi sector count value */
/** $endif **/
    NSOURCE ADDRESS,    /* NEXT SOURCE CHARACTER */
    NDEST   ADDRESS;    /* NEXT DESTINATION CHARACTER */

DECLARE
    fastcopy byte,      /* true if copy directly to dbuf */
    dblbuf   byte,      /* true if both source and dest buffer used */
    concat   byte,      /* true if concatination command */
    ambig    byte,      /* true if file is ambig type */
    dfile    byte,      /* true if dest is file type */
    sfile    byte,      /* true if source is file type */
    made     byte,      /* true if destination file already made */
    opened   byte,      /* true if source file open */
    endofsrc byte,      /* true if end of source file */
    nendcmd  byte,      /* true if not end of command tail */
    insparc  byte,      /* true if in middle of sparce file */
    sparfil  byte,      /* true if sparce file being copied */
    MULTCOM  BYTE,      /* true if processing multiple commands */
    PUTNUM   BYTE,      /* SET WHEN READY FOR NEXT LINE NUM */
    CONCNT   BYTE,      /* COUNTER FOR CONSOLE READY CHECK */
    CHAR     BYTE,      /* LAST CHARACTER SCANNED */
    FLEN     BYTE;      /* FILE NAME LENGTH */

declare
    f1     byte,        /* f1 user attribute flag */
    f2     byte,        /* f2 user attribute flag */
    f3     byte,        /* f3 user attribute flag */
    f4     byte,        /* f4 user attribute flag */
    ro     byte,        /* read only attribute flag */
    sys    byte,        /* system attribute flag */
/** $if mpm **/
    exten  byte,        /* extention error code */
    odcnt  byte,        /* saves dcnt for open dest file */
    eretry byte,        /* error return flag */
/** $endif **/
    dcnt   byte;        /* error code or directory code */


DECLARE CBUFF(130) BYTE,   /* COMMAND BUFFER */
    MAXLEN       BYTE AT (.CBUFF(0)),  /* MAX BUFFER LENGTH */
    COMLEN       BYTE AT (.CBUFF(1)),  /* CURRENT LENGTH */
    COMBUFF(128) BYTE AT (.CBUFF(2)),  /* COMMAND BUFFER CONTENTS */
    CBP          BYTE;                 /* COMMAND BUFFER POINTER */

DECLARE
    CUSER BYTE, /* CURRENT USER NUMBER */
    last$user byte;

DECLARE /* CONTROL TOGGLE VECTOR */
    CONT(26) BYTE,   /* ONE FOR EACH ALPHABETIC */
    /* 00 01 02 03 04 05 06 07 08 09 10 11 12 13
        A  B  C  D  E  F  G  H  I  J  K  L  M  N
       14 15 16 17 18 19 20 21 22 23 24 25
        O  P  Q  R  S  T  U  V  W  X  Y  Z   */
    archiv byte  at(.cont(0)),     /* file archive */
    confrm byte  at(.cont(2)),     /* confirm copy */
    DELET  BYTE  AT(.CONT(3)),     /* DELETE CHARACTERS */
    ECHO   BYTE  AT(.CONT(4)),     /* ECHO CONSOLE CHARACTERS */
    FORMF  BYTE  AT(.CONT(5)),     /* FORM FILTER */
    GETU   BYTE  AT(.CONT(6)),     /* GET FILE, USER # */
    HEXT   BYTE  AT(.CONT(7)),     /* HEX FILE TRANSFER */
    IGNOR  BYTE  AT(.CONT(8)),     /* IGNORE :00 RECORD ON FILE */
    kilds  byte  at(.cont(10)),    /* kill filename display */
    LOWER  BYTE  AT(.CONT(11)),    /* TRANSLATE TO LOWER CASE */
    NUMB   BYTE  AT(.CONT(13)),    /* NUMBER OUTPUT LINES */
    OBJ    BYTE  AT(.CONT(14)),    /* OBJECT FILE TRANSFER */
    PAGCNT BYTE  AT(.CONT(15)),    /* PAGE LENGTH */
    QUITS  BYTE  AT(.CONT(16)),    /* QUIT COPY */
    RSYS   BYTE  AT(.CONT(17)),    /* READ SYSTEM FILES */
    STARTS BYTE  AT(.CONT(18)),    /* START COPY */
    TABS   BYTE  AT(.CONT(19)),    /* TAB SET */
    UPPER  BYTE  AT(.CONT(20)),    /* UPPER CASE TRANSLATE */
    VERIF  BYTE  AT(.CONT(21)),    /* VERIFY EQUAL FILES ONLY */
    WRROF  BYTE  AT(.CONT(22)),    /* WRITE TO R/O FILE */
    ZEROP  BYTE  AT(.CONT(25));    /* ZERO PARITY ON INPUT */

DECLARE ZEROSUP BYTE,  /* ZERO SUPPRESSION */
    (C3,C2,C1) BYTE;     /* LINE COUNT ON PRINTER  */


/** $if mpm **/
retcodes: procedure(a);
    declare a address;
    dcnt = low(a);
    exten = high(a);
    end retcodes;
/** $endif **/

BOOT: PROCEDURE;
    /* SYSTEM REBOOT */
    CALL MON1(0,0);
    END BOOT;


RDCHAR: PROCEDURE BYTE;
    /* READ CONSOLE CHARACTER */
    RETURN MON2(1,0);
    END RDCHAR;

PRINTCHAR: PROCEDURE(CHAR);
    DECLARE CHAR BYTE;
    CALL MON1(2,CHAR AND 7FH);
    END PRINTCHAR;

CRLF: PROCEDURE;
    CALL PRINTCHAR(CR);
    CALL PRINTCHAR(LF);
    END CRLF;

printx: procedure(a);
    declare a address;
    call mon1(9,a);
    end printx;

PRINT: PROCEDURE(A);
    DECLARE A ADDRESS;
    /* PRINT THE STRING STARTING AT ADDRESS A UNTIL THE
    NEXT DOLLAR SIGN IS ENCOUNTERED */
    CALL CRLF;
    CALL printx(A);
    END PRINT;

RDCOM: PROCEDURE;
    /* READ INTO COMMAND BUFFER */
    MAXLEN = 128;
    CALL MON1(10,.MAXLEN);
    END RDCOM;

CVERSION: PROCEDURE ADDRESS;
    RETURN MON3(12,0); /* VERSION NUMBER */
    END CVERSION;

SETDMA: PROCEDURE(A);
    DECLARE A ADDRESS;
    CALL MON1(26,A);
    END SETDMA;

/** $if mpm **/
setpw: procedure(fcba);
    declare fcba address;
    declare fcbs based fcba structure (
        fcb(frsize) byte,
        pwnam(nsize) byte );
    call setdma(.fcbs.pwnam(0));
    end setpw;
/** $endif **/

OPEN: PROCEDURE(fcba);
    DECLARE fcba ADDRESS;
    declare fcb based fcba (frsize) byte;
/** $if mpm **/
    CALL SETPW(fcba);
    call retcodes(mon3(15,fcba));
/** $else **/
/** $endif **/
    if dcnt <> 255 and rol(fcb(8),1) then
        do; call mon1(16,fcba);
        dcnt = 255;
/** $if mpm **/
        exten = 0;
/** $endif **/
        end;
    END OPEN;

CLOSE: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(MON3(16,FCB));
/** $else **/
/** $endif **/
    END CLOSE;

SEARCH: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(MON3(17,FCB));
/** $else **/
/** $endif **/
    END SEARCH;

SEARCHN: PROCEDURE;
/** $if mpm **/
    call retcodes(MON3(18,0));
/** $else **/
/** $endif **/
    END SEARCHN;

DELETE: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    CALL SETPW(FCB);
    call retcodes(MON3(19,FCB));
/** $else **/
/** $endif **/
    END DELETE;

DISKRD: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(MON3(20,FCB));
/** $else **/
/** $endif **/
    END DISKRD;

DISKWRITE: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(MON3(21,FCB));
/** $else **/
/** $endif **/
    END DISKWRITE;

MAKE: procedure(fcba);
    declare fcba address;
/** $if mpm **/
    declare fcbs based fcba structure (
        fcb(frsize) byte,
        pwnam(nsize) byte );
    if fcbs.pwnam(0) = 0 then   /* zero if no password */
        fcbs.fcb(6) = fcbs.fcb(6) and 7fh;  /* reset password attribute */
    else do;
        fcbs.fcb(6) = fcbs.fcb(6) or 80h;  /* set password attribute */
        call setdma(.fcbs.pwnam(0));  /* set password dma */
        end;
    call retcodes(mon3(22,fcba));
/** $else **/
/** $endif **/
    END MAKE;

RENAME: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    CALL SETPW(FCB);
    call retcodes(MON3(23,FCB)) ;
/** $else **/
/** $endif **/
    END RENAME;

getdisk: procedure byte;
    return mon2(25,0);
    end getdisk;

SETIND: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(MON3(30,FCB));
/** $else **/
/** $endif **/
    END SETIND;

GETUSER: PROCEDURE BYTE;
    RETURN MON2(32,0FFH);
    END GETUSER;

SETUSER: PROCEDURE(USER);
    DECLARE USER BYTE;
    if last$user <> user then
        CALL MON1(32,(last$user:=USER));
    END SETUSER;

SETCUSER: PROCEDURE;
    CALL SETUSER(CUSER);
    END SETCUSER;

setduser: procedure;
    call setuser(odest.user);
    end setduser;

SETSUSER: PROCEDURE;
    CALL SETUSER(source.user);
    END SETSUSER;

RD$RANDOM: PROCEDURE(FCB) BYTE;
    DECLARE FCB ADDRESS;
/** $if mpm **/
    call retcodes(mon3(33,fcb));
/** $else **/
/** $endif **/
    return dcnt;
    END RD$RANDOM;

write$random: procedure(fcb) byte;
    declare fcb address;
/** $if mpm **/
    call retcodes(mon3(34,fcb));
/** $else **/
/** $endif **/
    return dcnt;
    end write$random;

retfsize: procedure(fcb) byte;
    declare fcb address;
    return mon2(35,fcb);
    end retfsize;

SET$RANDOM: PROCEDURE(FCB);
    DECLARE FCB ADDRESS;
    /* SET RANDOM RECORD POSITION */
    CALL MON1(36,FCB);
    END SET$RANDOM;

/** $if mpm **/
multsect: procedure(cnt);
    declare cnt byte;
    if mseccnt <> cnt then
        call mon1(44,(mseccnt := cnt));
    end multsect;

flushbuf: procedure;
    call mon1(48, 0ffh);	/* 0FFH = flush and discard buffers */
    end flushbuf;

conatlst: procedure byte;
    return mon2(161,0);
    end conatlst;
/** $endif **/


MOVE: PROCEDURE(S,D,N);
    DECLARE (S,D) ADDRESS, N BYTE;
    DECLARE A BASED S BYTE, B BASED D BYTE;
        DO WHILE (N:=N-1) <> 255;
        B = A; S = S+1; D = D+1;
        END;
    END MOVE;

    /* errtype error messages */
    declare er00(*) byte data ('DISK READ$');
    declare er01(*) byte data ('DISK WRITE$');
    declare er02(*) byte data ('VERIFY$');
    declare er03(*) byte data ('INVALID DESTINATION$');
    declare er04(*) byte data ('INVALID SOURCE$');
    declare er05(*) byte data ('USER ABORTED$');
    declare er06(*) byte data ('BAD PARAMETER$');
    declare er07(*) byte data ('INVALID USER NUMBER$');
    declare er08(*) byte data ('INVALID FORMAT$');
    declare er09(*) byte data ('HEX RECORD CHECKSUM$');
    declare er10(*) byte data ('FILE NOT FOUND$');
    declare er11(*) byte data ('START NOT FOUND$');
    declare er12(*) byte data ('QUIT NOT FOUND$');
    declare er13(*) byte data ('INVALID HEX DIGIT$');
    declare er14(*) byte data ('CLOSE FILE$');
    declare er15(*) byte data ('UNEXPECTED END OF HEX FILE$');
    declare er16(*) byte data ('INVALID SEPARATOR$');
    declare er17(*) byte data ('NO DIRECTORY SPACE$');
    declare er18(*) byte data ('INVALID FORMAT WITH SPARCE FILE$');
/** $if mpm **/
    declare er19(*) byte data ('MAKE FILE$');
    declare er20(*) byte data ('OPEN FILE$');
    declare er21(*) byte data ('PRINTER BUSY$');
    declare er22(*) byte data ('CAN''T DELETE TEMP FILE$');
/** $endif **/

    declare errmsg(*) address data(
        .er00,.er01,.er02,.er03,.er04,
        .er05,.er06,.er07,.er08,.er09,
        .er10,.er11,.er12,.er13,.er14,
        .er15,.er16,.er17,.er18
/** $if mpm **/
        ,.er19,.er20,.er21,.er22
/** $endif **/
        );

    declare sper00(*) byte data ('NO DIRECTORY SPACE$');
    declare sper01(*) byte data ('NO DATA BLOCK$');
    declare sper02(*) byte data ('CAN''T CLOSE CURRENT EXTENT$');
    declare sper03(*) byte data ('SEEK TO UNWRITTEN EXTENT$');
    declare sper05(*) byte data ('RANDOM RECORD OUT OF RANGE$');
    declare sper06(*) byte data ('RECORDS DON''T MATCH$');
    declare sper07(*) byte data ('RECORD LOCKED$');
    declare sper08(*) byte data ('INVALID FILENAME$');
    declare sper09(*) byte data ('FCB CHECKSUM$');

    declare numspmsgs lit '10';  /* number of extended messages */
    declare special$msg(numspmsgs) address data(
        .sper00,.sper01,.sper02,.sper03,.sper00,
        .sper05,.sper06,.sper07,.sper08,.sper09);

/** $if mpm **/
    /* extended error messages */
    declare ex00(*) byte data ('$');  /* NO MESSAGE */
    declare ex01(*) byte data ('NONRECOVERABLE$');
    declare ex02(*) byte data ('R/O DISK$');
    declare ex03(*) byte data ('R/O FILE$');
    declare ex04(*) byte data ('INVALID DISK SELECT$');
    declare ex05(*) byte data ('INCOMPATIBLE MODE$');
    declare ex07(*) byte data ('INVALID PASSWORD$');
    declare ex08(*) byte data ('ALREADY EXISTS$');
    declare ex10(*) byte data ('LIMIT EXCEEDED$');

    declare nummsgs lit '11';  /* number of extended messages */
    declare extmsg(nummsgs) address data(
        .ex00,.ex01,.ex02,.ex03,.ex04,
        .ex05,.sper09,.ex07,.ex08,.sper08,
        .ex10);
/** $endif **/

error$cleanup: procedure;
/** $if mpm **/
    call multsect(1);
/** $endif **/
    eretry = 0;   /* initialize to no error retry */
    if opened then   /* if source file opened */
        do; call setsuser;
        call close(.source);
        opened = false;
        end;
    if made then
        do; call setduser;
        call close(.dest);
        call delete(.dest);  /* delete destination scratch file */
        end;
    /* Zero the command length in case this is a single command */
       comlen = 0;
       retry = true;
       call print(.('ERROR: $'));
    end error$cleanup;

error: procedure (errtype);
    declare errtype byte;

    call error$cleanup;
    call printx(errmsg(errtype));
    call crlf;
    go to reset;
    end error;

xerror: procedure (funcno,fileadr);
    declare temp    byte,
            i       byte,
            sdcnt   byte,
            sexten  byte,
            funcno  byte,
            fileadr address,
            fcb based fileadr (fsize) byte;

    declare message$index$tbl(17) byte data
            (2,18,13,15,9,3,10,20,14,10,22,17,19,0,1,0,1);

    sdcnt = dcnt;
    sexten = exten;
    call error$cleanup;

    if (funcno < 6) or (sdcnt <> 0ffh) then
       sexten = 0;
    else sexten = sexten and 0fh;

    call printx(errmsg(message$index$tbl(funcno)));

    if (funcno > 12) and (funcno < 17) and
       (sdcnt <> 0ffh) and (sdcnt <= numspmsgs) then
       do; call printchar(' ');
       call printx(special$msg(sdcnt-1));
       sexten = 0;
       end;

/** $if mpm **/
    if sexten < nummsgs then
       do; call printchar(' ');
       call printx(extmsg(sexten));
       end;
/** $endif **/

    call printx(.(' - $'));
    if fileadr <> 0 then
        do; call printchar('A' + fcb(0) - 1);
        call printchar(':');
            do i = 1 to fnsize;
            if (temp := fcb(i) and 07fh) <> ' ' then
                do; if i = fext then call printchar('.');
                call printchar(temp);
                end;
            end;
        end;
    call crlf;

    if (sdcnt = 3) or (sdcnt = 4) or (sdcnt = 6) or (sdcnt = 8) then
       eretry = ambig;
    else
       if (sexten = 3) or ((sexten > 4) and (sexten < 9)) or (sexten > 9) then
            eretry = ambig;

    go to reset;
    end xerror;

FORMERR: PROCEDURE;
    call error(8);  /* invalid format */
    END FORMERR;

CONBRK: PROCEDURE;
    /* CHECK CONSOLE CHARACTER READY */
    if mon2(11,0) <> 0 then
        if mon2(6,0fdh) = cntrlc then
           call error(5);
    END CONBRK;

MAXSIZE: procedure byte;
    /* three byte compare of random record field
       returns true if source.fcb.ranrec >= filesize */

    if (source.fcb(35) < filsize(2)) then
      return false;
    if (source.fcb(35) = filsize(2)) then
      do;
      if (source.fcb(34) < filsize(1)) then
        return false;
      if (source.fcb(34) = filsize(1)) then
        do;
        if (source.fcb(33) < filsize(0)) then
          return false;
        end;
      end;
    return true;
    end maxsize;

SETUPDEST: PROCEDURE;
    call setduser;  /* destination user */
/** $if mpm **/
    call move(.odest,.dest,(frsize + nsize + 1));  /* save original dest */
/** $else **/
/** $endif **/
    /* MOVE THREE CHARACTER EXTENT INTO DEST FCB */
    CALL MOVE(.('$$$'),.DEST.FCB(FEXT),FEXTL);
/** $if mpm **/
    odest.fcb(6) = odest.fcb(6) or 80h;
    call open(.odest);  /* try to open destination file */
    odcnt = dcnt;       /* and save error code */
    if odcnt <> 255 then
        call close(.odest);
    else if (exten and 0fh) <> 0 then /* file exists */
        call xerror(7,.odest);        /* but can't open - error */

    CALL DELETE(.DEST); /* REMOVE OLD $$$ FILE */
    if dcnt = 255 and exten <> 0 then
        /* cant delete temp file */
        call xerror(10,.dest);
    CALL MAKE(.DEST); /* CREATE A NEW ONE */
    IF DCNT = 255 THEN 
        if (exten and 0fh) = 0 then 
            call xerror(11,.dest);  /* no directory space */
        else call xerror(12,.dest);  /* make file error */
/** $else **/
/** $endif **/
    DEST.FCB(32) = 0;
    made = true;
    END SETUPDEST;

SETUPSOURCE: PROCEDURE;
    declare (i,j) byte;
    CALL SETSUSER; /* SOURCE USER */
/** $if mpm **/
    source.fcb(6) = source.fcb(6) or 80h;
/** $endif **/
    CALL OPEN(.SOURCE);  /* open source */
    if dcnt <> 255 then
        opened = true;
    IF (NOT RSYS) AND ROL(SOURCE.FCB(10),1) THEN
        /* skip system file */
        DCNT = 255;
    IF DCNT = 255 THEN
/** $if mpm **/
        if (exten and 0fh) = 0 then
            call xerror(6,.source);  /* file not found */
        else
            call xerror(7,.source);  /* open file error */
/** $else **/
/** $endif **/
    f1 = source.fcb(1) and 80h;  /* save file atributes */
    f2 = source.fcb(2) and 80h;
    f3 = source.fcb(3) and 80h;
    f4 = source.fcb(4) and 80h;
    ro = source.fcb(9) and 80h;
    sys = source.fcb(10) and 80h;
    dcnt = retfsize(.source);
    call move(.source.fcb(33),.filsize,3);
    SOURCE.FCB(32) = 0;
    source.fcb(33),source.fcb(34),source.fcb(35) = 0;
    /* cause immediate read with no preceding write */
    NSOURCE = 0ffffh;
    END SETUPSOURCE;

WRITEDEST: PROCEDURE;
    /* WRITE OUTPUT BUFFERS UP TO BUT NOT INCLUDING POSITION
    NDEST - THE LOW ORDER 7 BITS OF NDEST ARE ZERO */
    DECLARE (J,DATAOK) BYTE,
        (tdest,n)      address;
    if not made then call setupdest;
    if (n := ndest and 0ff80h) = 0 then return;
    tdest = 0;
    call setduser;  /* destination user */
    if (sparfil := (sparfil or insparc)) then
        /* set up fcb from random record no. */
        do;
/** $if mpm **/
        call multsect(1);
/** $endif **/
        CALL SETDMA(.dbuff(tdest));
        if write$random(.dest) <> 0 then
            call xerror(16,.dest);  /* DISK WRITE ERROR */
        end;
    else
        CALL SETRANDOM(.DEST); /* SET BASE RECORD FOR VERIFY */
/** $if mpm **/
    if fastcopy then
        do; bufsize = maxmbuf;
        call multsect(maxmcnt);
        end;
    else
        do; bufsize = 128;
        call multsect(1);
        end;
/** $endif **/

        do while n - tdest > 127;
/** $if mpm **/
        if fastcopy and (n - tdest < maxmbuf) then
            do; bufsize = n - tdest;
            call multsect(low(shr(bufsize,7)));
            end;
/** $endif **/
        /* SET DMA ADDRESS TO NEXT BUFFER */
        CALL SETDMA(.dbuff(tdest));
        call diskwrite(.dest);
        IF dcnt <> 0 THEN
            call xerror(14,.dest);  /* DISK WRITE ERROR */
/** $if mpm **/
        tdest = tdest + bufsize;
/** $else **/
/** $endif **/
        END;

    IF VERIF THEN /* VERIFY DATA WRITTEN OK */
        DO;
        call flushbuf;
        tdest = 0;
/** $if mpm **/
        call multsect(1);
/** $endif **/
        CALL SETDMA(.BUFF); /* FOR COMPARE */
            do while tdest < n;
            DATAOK = (RDRANDOM(.DEST) = 0);
            if (DESTR := DESTR + 1) = 0 then  /* 3 byte inc for */
                destr2 = destr2 + 1;  /* next random record */
            J = 0;
                /* PERFORM COMPARISON */
                DO WHILE DATAOK AND J < 80H;
                DATAOK = (BUFF(J) = DBUFF(tdest+J));
                J = J + 1;
                END;
            tdest = tdest + 128;
            IF NOT DATAOK THEN
                call xerror(0,.dest);  /* VERIFY ERROR */
            END;
        call diskrd(.dest);
        /* NOW READY TO CONTINUE THE WRITE OPERATION */
        END;
    CALL SETRANDOM(.DEST); /* set base record for sparce copy */
    call move(.dbuff(tdest),.dbuff(0),low(ndest := ndest - tdest));
    END WRITEDEST;

FILLSOURCE: PROCEDURE;
    /* FILL THE SOURCE BUFFER */
    call conbrk;
/** $if mpm **/
    if fastcopy then
        do; bufsize = maxmbuf;
        call multsect(maxmcnt);
        end;
    else do;
        bufsize = 128;
        call multsect(1);
        end;
/** $endif **/
    CALL SETSUSER; /* SOURCE USER NUMBER SET */
    nsource = nsbuf;
        do while sblen - nsbuf > 127; 
        if fastcopy and (sblen - nsbuf < maxmbuf) then
            do; bufsize = (sblen - nsbuf) and 0ff80h;
            call multsect(low(shr(bufsize,7)));
            end;
        /* SET DMA ADDRESS TO NEXT BUFFER POSIITION */
        CALL SETDMA(.SBUFF(nsbuf));
        extsave = source.fcb(12);  /* save extent field */
        call diskrd(.source);
        IF dcnt <> 0 THEN
            DO; IF dcnt <> 1 THEN
                call xerror(13,.source);  /* DISK READ ERROR */
            /* END - OF - FILE */
/** $if mpm **/
            if fastcopy then   /* add no. sectors copied */
                nsbuf = nsbuf + shl(double(exten),7);
             /* nsbuf = nsbuf + shl(double(exten and 0f0h),3); */
/** $endif **/
            /* check boundry condition for bug in bdos and correct */
            if (source.fcb(12) <> extsave) and (source.fcb(32) = 80h) then
                source.fcb(32) = 0;  /* zero current record */
            call set$random(.source);
            if (insparc := not maxsize) then
                do;
                if concat or (not fastcopy) then
                    /* invalid format with sparce file */
                    call xerror(1,.source);
                end;
            else
                do;
                call close(.source);
                opened = false;
                end;
            endofsrc = true;  /* set end of source file */
            SBUFF(nsbuf) = ENDFILE; return;
            END;
        ELSE
/** $if mpm **/
            nsbuf = nsbuf + bufsize;
/** $else **/
/** $endif **/
        END;
    END FILLSOURCE;

PUTDCHAR: PROCEDURE(B);
    DECLARE B BYTE;
    /* WRITE BYTE B TO THE DESTINATION DEVICE GIVEN BY ODEST.TYPE */
    IF B >= ' ' THEN
        DO; COLUMN = COLUMN + 1;
        IF DELET > 0 THEN /* MAY BE PAST RIGHT SIDE */
            DO; IF COLUMN > DELET THEN RETURN;
            END;
        END;
    if echo then call mon1(2,b);  /* echo to console */
	do case odest.type;
        /* CASE 0 IS OUT */
            CALL OUTD(B);
        /* CASE 1 IS PRN, TABS EXPANDED, LINES LISTED */
            call mon1(5,b);
        /* CASE 2 IS LST */
            CALL MON1(5,B);
        /* CASE 3 IS axo */
axocase:
/** $if not mpm **/
            CALL MON1(4,B);
/** $else **/
/** $endif **/
        /* CASE 4 IS DESTINATION FILE */
            DO;
            IF NDEST >= DBLEN THEN CALL WRITEDEST;
            DBUFF(NDEST) = B;
            NDEST = NDEST+1;
            END;
        /* CASE 5 IS AUX */
           goto axocase;
        /* CASE 6 IS CON */
            CALL MON1(2,B);
        END; /* of case */
    END PUTDCHAR;

PUTDESTC: PROCEDURE(B);
    DECLARE (B,I) BYTE;
    /* WRITE DESTINATION CHARACTER, TAB EXPANSION */
    IF B <> TAB THEN CALL PUTDCHAR(B);
    ELSE IF TABS = 0 THEN CALL PUTDCHAR(B);
    ELSE  /* B IS TAB CHAR, TABS > 0 */
        DO; I = COLUMN;
            DO WHILE I >= TABS;
            I = I - TABS;
            END;
        I = TABS - I;
            DO WHILE I > 0;
            I = I - 1;
            CALL PUTDCHAR(' ');
            END;
        END;
    IF B = CR THEN COLUMN = 0;
    END PUTDESTC;

PRINT1: PROCEDURE(B);
    DECLARE B BYTE;
    IF (ZEROSUP := ZEROSUP AND B = 0) THEN
        CALL PUTDESTC(' ');
    ELSE
        CALL PUTDESTC('0'+B);
    END PRINT1;

PRINTDIG: PROCEDURE(D);
    DECLARE D BYTE;
    CALL PRINT1(SHR(D,4)); CALL PRINT1(D AND 1111B);
    END PRINTDIG;

NEWLINE: PROCEDURE;
    DECLARE ONE BYTE;
    ONE = 1;
    ZEROSUP = (NUMB = 1);
    C1 = DEC(C1+ONE); C2 = DEC(C2 PLUS 0); C3 = DEC(C3 PLUS 0);
    CALL PRINTDIG(C3); CALL PRINTDIG(C2); CALL PRINTDIG(C1);
    IF NUMB = 1 THEN /* USUALLY PRINTER OUTPUT */
        DO; CALL PUTDESTC(':'); CALL PUTDESTC(' ');
        END;
    ELSE
        CALL PUTDESTC(TAB);
    END NEWLINE;

PUTDEST: PROCEDURE(B);
    DECLARE (I,B) BYTE;
    /* WRITE DESTINATION CHARACTER, CHECK TABS AND LINES */
    IF FORMF THEN /* SKIP FORM FEEDS */
        DO; IF B = FF THEN RETURN;
        END;
    IF PUTNUM THEN /* END OF LINE OR START OF FILE */
        DO;
        IF (B <> FF) and (b <> endfile) THEN
            DO;    /* NOT FORM FEED or end of file */
            IF (I:=PAGCNT) <> 0 THEN /* PAGE EJECT */
                DO; IF I=1 THEN I=LPP;
                IF (LINENO := LINENO + 1) >= I THEN
                    DO; LINENO = 0; /* NEW PAGE */
                    CALL PUTDESTC(FF);
                    END;
                END;
            IF NUMB > 0 THEN
                CALL NEWLINE;
            PUTNUM = FALSE;
            END;
        END;
    IF B = FF THEN LINENO = 0;
    CALL PUTDESTC(B);
    IF B = LF THEN PUTNUM = TRUE;
    END PUTDEST;


UTRAN: PROCEDURE(B) BYTE;
    DECLARE B BYTE;
    /* TRANSLATE ALPHA TO UPPER CASE */
    IF B >= 110$0001B AND B <= 111$1010B THEN /* LOWER CASE */
        B = B AND 101$1111B; /* TO UPPER CASE */
    RETURN B;
    END UTRAN;

LTRAN: PROCEDURE(B) BYTE;
    DECLARE B BYTE;
    /* TRANSLATE TO LOWER CASE ALPHA */
    IF B >= 'A' AND B <= 'Z' THEN
        B = B OR 10$0000B; /* TO LOWER */
    RETURN B;
    END LTRAN;

GETSOURCEC: PROCEDURE BYTE;
    /* READ NEXT SOURCE CHARACTER */
    DECLARE (B,CONCHK) BYTE;

    CONCHK = TRUE; /* CONSOLE STATUS CHECK BELOW */
        DO CASE source.type;
        /* CASE 0 IS out */
            go to notsource;
        /* CASE 1 IS prn */
            go to notsource;
        /* CASE 2 IS lst */
            notsource:
                  call error(4);  /* INVALID SOURCE */
        /* CASE 3 IS axo */
            go to notsource;
        /* CASE 4 IS SOURCE FILE */
            DO;
            IF NSOURCE >= SBLEN THEN
                do; if dblbuf or (not dfile) then
                    nsbuf = 0;
                else if (nsource <> 0ffffh) then
                    do; call writedest;
                    nsbuf = ndest;
                    end;
                CALL FILLSOURCE;
                end;
            B = SBUFF(NSOURCE);
            NSOURCE = NSOURCE + 1;
            END;
        /* CASE 5 IS AUX */
           goto axicase;
        /* CASE 6 IS CON */
            DO; CONCHK = FALSE; /* DON'T CHECK CONSOLE STATUS */
            B = MON2(1,0);
            END;
        /* CASE 7 IS axi */
axicase:
/** $if not mpm **/
            B = MON2(3,0) AND 7FH;
/** $else **/
/** $endif **/
        /* CASE 7 IS INP */
            B = INPD;
        END; /* OF CASES */

    IF CONCHK THEN /* TEST FOR CONSOLE CHAR READY */
        DO;
        IF obj THEN /* SOURCE IS AN OBJECT FILE */
            CONCHK = ((CONCNT := CONCNT + 1) = 0);
        ELSE /* ASCII */
            CONCHK = (B = LF);
        IF CONCHK THEN
            DO;
            call CONBRK;
            END;
        END;
    IF ZEROP THEN B = B AND 7FH;
    IF UPPER THEN RETURN UTRAN(B);
    IF LOWER THEN RETURN LTRAN(B);
    RETURN B;
    END GETSOURCEC;

GETSOURCE: PROCEDURE BYTE;
    /* GET NEXT SOURCE CHARACTER */
    DECLARE CHAR BYTE;
    MATCH: PROCEDURE(B) BYTE;
        /* MATCH START AND QUIT STRINGS */
        DECLARE (B,C) BYTE;
        IF (C:=COMBUFF(B:=(B+MATCHLEN))) = ENDFILE THEN /* END MATCH */
            DO; COMBUFF(B) = CHAR; /* SAVE CURRENT CHARACTER */
            RETURN TRUE;
            END;
        IF C = CHAR THEN MATCHLEN = MATCHLEN + 1;
        ELSE
            MATCHLEN = 0; /* NO MATCH */
        RETURN FALSE;
        END MATCH;

    IF QUITLEN > 0 THEN
        DO; IF (QUITLEN := QUITLEN - 1) = 1 THEN RETURN LF;
        RETURN ENDFILE; /* TERMINATED WITH CR,LF,ENDFILE */
        END;
    DO FOREVER; /* LOOKING FOR START */
    IF FEEDLEN > 0 THEN /* GET SEARCH CHARACTERS */
        DO; FEEDLEN = FEEDLEN - 1;
        CHAR = COMBUFF(FEEDBASE);
        FEEDBASE = FEEDBASE + 1;
        RETURN CHAR;
        END;
    IF (CHAR := GETSOURCEC) = ENDFILE THEN RETURN ENDFILE;
    IF STARTS > 0 THEN /* LOOKING FOR START STRING */
        DO; IF MATCH(STARTS) THEN
            DO; FEEDBASE = STARTS; STARTS = 0;
            FEEDLEN = MATCHLEN + 1;
            matchlen = 0;
            END; /* OTHERWISE NO MATCH, SKIP CHARACTER */
        END;
    ELSE IF QUITS > 0 THEN /* PASS CHARACTERS TIL MATCH */
        DO; IF MATCH(QUITS) THEN
            DO; QUITS = 0; QUITLEN = 2;
            /* SUBSEQUENTLY RETURN CR, LF, ENDFILE */
            RETURN CR;
            END;
        RETURN CHAR;
        END;
    ELSE
        RETURN CHAR;
    END; /* OF DO FOREVER */
    END GETSOURCE;

RD$EOF: PROCEDURE BYTE;
    /* RETURN TRUE IF END OF FILE */
    CHAR = GETSOURCE;
    IF obj THEN RETURN (endofsrc and (nsource > nsbuf));
    RETURN (CHAR = ENDFILE);
    END RD$EOF;


HEXRECORD: PROCEDURE;
    DECLARE (h, hbuf, RL, CS, RT) BYTE,
        zerorec byte,  /* true if last record had length of zero */
        LDA ADDRESS;  /* LOAD ADDRESS WHICH FOLLOWS : */

    ckhex: procedure byte;
        IF H - '0' <= 9 THEN
            RETURN H-'0';
        IF H - 'A' > 5 THEN
            CALL xerror(2,.source);  /* invalid hex digit */
        RETURN H - 'A' + 10;
        end ckhex;

    rdhex: procedure byte;
        call putdest(h := getsource);
        return ckhex;
        end rdhex;

    RDCS: PROCEDURE BYTE;
        /* READ BYTE WITH CHECKSUM */
        RETURN CS := CS + (SHL(RDHEX,4) OR RDHEX);
        END RDCS;

    RDADDR: PROCEDURE ADDRESS;
        /* READ DOUBLE BYTE WITH CHECKSUM */
        RETURN SHL(DOUBLE(RDCS),8) OR RDCS;
        END RDADDR;

    /* READ HEX FILE AND CHECK EACH RECORD
    FOR VALID DIGITS, AND PROPER CHECKSUM */
    zerorec = false;
    /* READ NEXT RECORD */
    h = getsource;
        do forever;
        /* SCAN FOR THE ':' */
            DO WHILE h <> ':';
            IF (h = ENDFILE) THEN
                do; if zerorec then return;
                CALL xerror(3,.source);  /* unexpected end of hex file */
                end;
            call putdest(h);
            h = getsource;
            END;

        /* ':' FOUND */
        /* check for end of hex record */
        h = getsource;
        rl = shl(ckhex,4);
        hbuf = h; h = getsource;
        rl = rl or ckhex;
        if (rl = 0) then zerorec = true;
        else zerorec = false;
        if (zerorec and ignor) then
            do while (h <> ':') and (h <> endfile);
            h = getsource;
            end;
        else do; call putdest(':');
            call putdest(hbuf);
            call putdest(h);
            cs = rl;
            LDA = RDADDR; /* LOAD ADDRESS */

            /* READ WORDS UNTIL RECORD LENGTH EXHAUSTED */
            RT = RDCS; /* RECORD TYPE */
                DO WHILE RL <> 0; RL = RL - 1;
                hbuf = RDCS;
                /* INCREMENT LA HERE FOR EXACT ADDRESS */
                END;

            /* CHECK SUM */
            IF rdcs <> 0 THEN
                CALL xerror(4,.source);  /* hex record checksum */
            h = getsource;
            end;
        end; /* do forever */
    END HEXRECORD;

CK$STRINGS: PROCEDURE;
    IF STARTS > 0 THEN
        call error(11);  /* START NOT FOUND */
    IF QUITS  > 0 THEN
        call error(12);  /* QUIT NOT FOUND */
    END CK$STRINGS;

CLOSEDEST: PROCEDURE;
        DO WHILE (LOW(NDEST) AND 7FH) <> 0;
        CALL PUTDEST(ENDFILE);
        END;
    CALL CK$STRINGS;
    CALL WRITEDEST;
    call setduser;  /* destination user */
    CALL CLOSE(.DEST);
    IF DCNT = 255 THEN
/** $if mpm **/
        call xerror(8,.dest);  /* CLOSE FILE */
    IF odcnt <> 255 THEN /* FILE EXISTS */
        do;
/** $else **/
/** $endif **/
        IF ROL(odest.fcb(9),1) THEN /* READ ONLY */
            DO;
            IF NOT WRROF THEN
                DO;
                    do while ((dcnt <> 'Y') and (dcnt <> 'N'));
                    CALL PRINT (.('DESTINATION IS R/O, DELETE (Y/N)? $'));
                    dcnt = utran(rdchar);
                    end;
                IF dcnt <> 'Y' THEN
                    DO; CALL PRINT(.('**NOT DELETED**$'));
                    CALL CRLF;
                    CALL DELETE(.DEST);
                    RETURN;
                    END;
                CALL CRLF;
                END;
            END;
        /* reset r/o and sys attributes */
        odest.fcb(9) = odest.fcb(9) and 7fh;
        odest.fcb(10) = odest.fcb(10) AND 7FH;
        CALL SETIND(.odest);
        CALL DELETE(.odest);
        END;
    CALL MOVE(.odest.fcb,.dest.fcb(16),16); /* READY FOR RENAME */
    CALL RENAME(.DEST);
    /* set destination attributes same as source */
    odest.fcb(1) = (odest.fcb(1) and 07fh) or f1;
    odest.fcb(2) = (odest.fcb(2) and 07fh) or f2;
    odest.fcb(3) = (odest.fcb(3) and 07fh) or f3;
    odest.fcb(4) = (odest.fcb(4) and 07fh) or f4;
    odest.fcb(8) = (odest.fcb(8) and 07fh);
    odest.fcb(9) = (odest.fcb(9) and 07fh) or ro;
    odest.fcb(10) = (odest.fcb(10) and 07fh) or sys;
    odest.fcb(11) = (odest.fcb(11) and 07fh);
    call setind(.odest);
    if archiv then   /* set archive bit */
        do; call setsuser;
        source.fcb(11) = source.fcb(11) or 080h;
        source.fcb(12) = 0;
        call setind(.source);
        end;
    END CLOSEDEST;

SIZE$MEMORY: PROCEDURE;
    /* SET UP SOURCE AND DESTINATION BUFFERS */
    if not dblbuf then
        do;  /* ABSORB THE SOURCE BUFFER INTO THE DEST BUFFER */
        sbase = .memory;
        sblen,dblen = ((maxb - .memory) and 0ff80h) - 128;
        end;
    else do;  /* may need to write destination buffer */
        sblen,dblen = (shr((maxb - .memory),1) and 0ff80h) - 128;
        sbase = .memory + dblen + 128;
        if ndest >= dblen then call writedest;
        nsbuf = 0;
        end;
    END SIZE$MEMORY;

setupeob: procedure;
    /* sets nsbuf to end of source buffer */
    declare i byte;
    if (not obj) and (nsbuf <> 0) then
        do; tblen = nsbuf - 128;
            do i = 0 to 128;
            if (sbuff(tblen + i)) = endfile then
                do; nsbuf = tblen + i;
                return;
                end;
            end;
        end;
    end setupeob;

SIMPLECOPY: PROCEDURE;
    DECLARE I BYTE;
    declare
        fast lit '0',  /* fast file to file copy */
        chrt lit '1',  /* character transfer option */
        dubl lit '2';  /* double buffer required for file copy */
    declare optype(26) byte data (
    /* option type for each option character */
        fast,  /* for A option */
        fast,  /* for B option */
        fast,  /* for C option */
        dubl,  /* for D option */
        chrt,  /* for E option */
        dubl,  /* for F option */
        fast,  /* for G option */
        chrt,  /* for H option */
        dubl,  /* for I option */
        fast,  /* for J option */
        fast,  /* for K option */
        chrt,  /* for L option */
        fast,  /* for M option */
        dubl,  /* for N option */
        fast,  /* for O option */
        dubl,  /* for P option */
        dubl,  /* for Q option */
        fast,  /* for R option */
        dubl,  /* for S option */
        dubl,  /* for T option */
        chrt,  /* for U option */
        fast,  /* for V option */
        fast,  /* for W option */
        fast,  /* for X option */
        fast,  /* for Y option */
        chrt); /* for Z option */

    chkrandom: procedure;
        call setsuser;
        call set$random(.source);
/** $if mpm **/
        call multsect(1);
/** $endif **/
        call setdma(.buff);
            do forever;
            if (((dcnt := rd$random(.source)) = 0) or maxsize) then
                do; destr = sourcer;
                destr2 = sourcer2;
                endofsrc = false;
                return;
                end;
            if dcnt = 1 then
                do; if (sourcer := sourcer + 1) = 0 then
                    sourcer2 = sourcer2 + 1;
                end;
            else if dcnt = 4 then
                do;
                if (sourcer := (sourcer + 128) and 0ff80h) = 0 then
                    sourcer2 = sourcer2 + 1;
                end;
            else
                call xerror(15,.source);
            end;
        end chkrandom;

    fastcopy = (sfile and dfile);
    endofsrc = false;
    dblbuf = false;
    sparfil = false;
    insparc = false;
        /* LOOK FOR PARAMETERS */
        DO I = 0 TO 25;
        IF CONT(I) <> 0 THEN
            DO;
            IF optype(i) = chrt THEN
                FASTCOPY = FALSE;
            else
            if optype(i) = dubl then
                do; dblbuf = (sfile and dfile);
                fastcopy = false;
                end;
            END;
        END;

    CALL SIZE$MEMORY;
    if sfile then 
        CALL SETUPSOURCE;
        /* FILES READY FOR COPY */

    if fastcopy then
        do while not endofsrc;
        CALL FILLSOURCE;
        if endofsrc and concat then
            do; call setupeob;
            ndest = nsbuf;
            if nendcmd then return;
            end;
        ndest = nsbuf;
        CALL WRITEDEST;
        nsbuf = ndest;
        if (endofsrc and insparc) then
            call chkrandom;
        end;

    else do;
        /* PERFORM THE ACTUAL COPY FUNCTION */
        IF HEXT OR IGNOR THEN /* HEX FILE */
            call hexrecord;
        ELSE
            DO WHILE NOT RD$EOF;
            CALL PUTDEST(CHAR);
            END;
        if concat and nendcmd then
            do; nsbuf = ndest;
            return;
            end;
        end;

    if dfile then
        CALL CLOSEDEST;
    END SIMPLECOPY;

MULTCOPY: PROCEDURE;
    DECLARE (NEXTDIR, NDCNT, NCOPIED) ADDRESS;

    PRNAME: PROCEDURE;
        /* PRINT CURRENT FILE NAME */
        DECLARE (I,C) BYTE;
        CALL CRLF;
            DO I = 1 TO FNSIZE;
            IF (C := odest.fcb(I)) <> ' ' THEN
                DO; IF I = FEXT THEN CALL PRINTCHAR('.');
                CALL PRINTCHAR(C);
                END;
            END;
        END PRNAME;

    archck: procedure byte;
        /* check if archive bit is set in any extent of source file */
        if not archiv then
            return 1;
        call setsuser;
        source.fcb(12) = what;
        call search(.source);
            do while dcnt <> 255;
            call move(.buff+shl(dcnt and 11b,5)+1,.source.fcb(1),15);
            if not rol(source.fcb(11),1) then
                return 1;
            call searchn;
            end;
        return 0;
        end archck; 

/** $if mpm **/
    /* initialize counters if not error retry */
    if eretry = 0 then NEXTDIR, NCOPIED = 0;
/** $else **/
/** $endif **/

        DO FOREVER;
        /* FIND A MATCHING ENTRY */
        CALL SETSUSER; /* SOURCE USER */
        CALL SETDMA(.BUFF);
        searfcb(12) = 0;
        CALL SEARCH(.SEARFCB);
        NDCNT = 0;
            DO WHILE (DCNT <> 255) AND NDCNT < NEXTDIR;
            NDCNT = NDCNT + 1;
            CALL SEARCHN;
            END;
        /* FILE CONTROL BLOCK IN BUFFER */
        IF DCNT = 255 THEN
            DO; IF NCOPIED = 0 THEN
                call xerror(9,.searfcb);  /* file not found */
            if not kilds then
                CALL CRLF;
            RETURN;
            END;
        NEXTDIR = NDCNT + 1;
        /* GET THE FILE CONTROL BLOCK NAME TO DEST */
        CALL MOVE(.BUFF + SHL(DCNT AND 11B,5)+1,.odest.fcb(1),15);
        CALL MOVE(.odest.fcb(1),.SOURCE.FCB(1),15); /* FILL BOTH FCB'S */
        if archck then
            do; odest.fcb(12) = 0;
            source.fcb(12) = 0;
            IF RSYS OR NOT ROL(odest.fcb(10),1) THEN /* OK TO READ */
                DO; if not kilds then    /* kill display option */
                    do; IF NCOPIED = 0 THEN
                        CALL PRINT(.('COPYING -$'));
                    dcnt = false;
                        do while ((dcnt <> 'Y') and (dcnt <> 'N'));
                        call prname;
                        if confrm then
                            do; call printx(.(' (Y/N)? $'));
                            dcnt = utran(rdchar);
                            end;
                        else
                            dcnt = 'Y';
                        end;
                    end;
                ncopied = ncopied + 1;
                made = false;  /* destination file not made */
                if (dcnt = 'Y') or (kilds) then
                    CALL SIMPLECOPY;
                END;
            end;
        END;
    END MULTCOPY;

CK$DISK: PROCEDURE;
    /* error if same user and same disk */
    IF (odest.user = source.user) and (odest.fcb(0) = source.fcb(0)) THEN
        CALL FORMERR;
    END CK$DISK;

GNC: PROCEDURE BYTE;
    IF (CBP := CBP + 1) >= COMLEN THEN RETURN CR;
    RETURN UTRAN(COMBUFF(CBP));
    END GNC;

DEBLANK: PROCEDURE;
        DO WHILE (CHAR := GNC) = ' ';
        END;
    END DEBLANK;

CK$EOL: PROCEDURE;
    CALL DEBLANK;
    IF CHAR <> CR THEN CALL FORMERR;
    END CK$EOL;

SCAN: PROCEDURE(FCBA);
    DECLARE FCBA ADDRESS,         /* ADDRESS OF FCB TO FILL */
    fcbs based fcba structure (    /* FCB STRUCTURE */
        fcb(frsize) byte,
/** $if mpm **/
        pwnam(nsize) byte,
        pwmode byte,
/** $endif **/
        user byte,
        type byte );
    DECLARE (I,K) BYTE;  /* TEMP COUNTERS */

    /* SCAN LOOKS FOR THE NEXT DELIMITER, DEVICE NAME, OR FILE NAME.
    THE VALUE OF CBP MUST BE 255 UPON ENTRY THE FIRST TIME */

    DELIMITER: PROCEDURE(C) BYTE;
        DECLARE (I,C) BYTE;
        DECLARE DEL(*) BYTE DATA
        (' =.:;,<>',CR,LA,LB,RB);
            DO I = 0 TO LAST(DEL);
            IF C = DEL(I) THEN RETURN TRUE;
            END;
        RETURN FALSE;
        END DELIMITER;

    PUTCHAR: PROCEDURE;
        FCBS.FCB(FLEN:=FLEN+1) = CHAR;
        IF CHAR = WHAT THEN AMBIG = TRUE; /* CONTAINS AMBIGUOUS REF */
        END PUTCHAR;

    FILLQ: PROCEDURE(LEN);
        /* FILL CURRENT NAME OR TYPE WITH QUESTION MARKS */
        DECLARE LEN BYTE;
        CHAR = WHAT; /* QUESTION MARK */
            DO WHILE FLEN < LEN; 
            CALL PUTCHAR;
            END;
        END FILLQ;

    SCANPAR: PROCEDURE;
        DECLARE (I,J) BYTE;
        /* SCAN OPTIONAL PARAMETERS */
        CHAR = GNC; /* SCAN PAST BRACKET */
            DO WHILE NOT(CHAR = CR OR CHAR = RB);
            IF (I := CHAR - 'A') > 25 THEN /* NOT ALPHA */
                DO; IF CHAR = ' ' THEN
                    CHAR = GNC;
                ELSE
                    call error(6);  /* BAD PARAMETER */
                END;
            ELSE
                DO; /* SCAN PARAMETER VALUE */
                IF CHAR = 'S' OR CHAR = 'Q' THEN
                    DO; /* START OR QUIT COMMAND */
                    J = CBP + 1; /* START OF STRING */
                        DO WHILE NOT ((CHAR := GNC) = ENDFILE OR CHAR = CR);
                        END;
                     CHAR=GNC;
                    END;
                ELSE IF (J := (CHAR := GNC) - '0') > 9 THEN
                    J = 1;
                ELSE
                    DO WHILE (K := (CHAR := GNC) - '0') <= 9;
                    J = J * 10 + K;
                    END;
                CONT(I) = J;
                IF I = 6 THEN /* SET SOURCE USER */
                    DO;
                    IF J > 15 THEN
                        call error(7);  /* INVALID USER NUMBER */
                    fcbs.user = J;
                    END;
                END;
            END;
        CHAR = GNC;
        END SCANPAR;


    /* scan procedure entry point */

    /* INITIALIZE FILE CONTROL BLOCK TO EMPTY */
    fcbs.type = ERR; CHAR = ' '; FLEN = 0;
/** $if mpm **/
        DO WHILE FLEN < (FRSIZE + NSIZE);
        IF FLEN = FNSIZE THEN CHAR = 0;
        ELSE IF FLEN = FRSIZE THEN CHAR = ' ';
        call putchar;
        END;
    fcbs.pwnam(0) = 0;
    fcbs.pwmode = 1;
/** $else **/
/** $endif **/
    fcbs.fcb(0) = cdisk +1;    /* initialize to current disk */
    fcbs.user = cuser;    /* and current user */
    /* CLEAR PARAMETERS */
        DO I = 0 TO 25; CONT(I) = 0;
        END;
    FEEDLEN,MATCHLEN,QUITLEN = 0;

    /* DEBLANK COMMAND BUFFER */
    CALL DEBLANK;

    /* CHECK PERIPHERALS AND DISK FILES */
    /* SCAN NEXT NAME */
        DO FOREVER;
        FLEN = 0;
            DO WHILE NOT DELIMITER(CHAR);
            IF FLEN >= NSIZE THEN /* ERROR, FILE NAME TOO LONG */
                RETURN;
            IF CHAR = '*' THEN CALL FILLQ(NSIZE);
            ELSE CALL PUTCHAR;
            CHAR = GNC;
            END;

        /* CHECK FOR DISK NAME OR DEVICE NAME */
        IF CHAR = ':' THEN
            DO; IF FLEN = 1 THEN
                /* MAY BE DISK NAME A ... P */
                DO;
                IF (fcbs.fcb(0) := fcbs.fcb(1) - 'A' + 1) > 16 THEN
                    RETURN;  /* ERROR, INVALID DISK NAME */
                CALL DEBLANK; /* MAY BE DISK NAME ONLY */
                IF DELIMITER(CHAR) THEN
                    DO; IF CHAR = LB THEN
                        CALL SCANPAR;
                    CBP = CBP - 1;
                    fcbs.type = DISKNAME;
                    RETURN;
                    END;
                END;
            ELSE
            /* MAY BE A THREE CHARACTER DEVICE NAME */
            IF FLEN <> 3 THEN /* ERROR, CANNOT BE DEVICE NAME */
                RETURN;
            ELSE
                /* LOOK FOR DEVICE NAME */
                DO; DECLARE (I,J,K) BYTE, M LITERALLY '9',
                IO(*) BYTE DATA
                ('OUTPRNLSTAXO',
                  0,0,0,         /* fake area for file type */
                 'AUX',
                 'CONAXIINPNULEOF',0);

                J = 255;
                    DO K = 0 TO M;
                    I = 0;
                        DO WHILE ((I:=I+1) <= 3) AND
                        IO(J+I) = fcbs.fcb(I);
                        END;
                    IF I = 4 THEN /* COMPLETE MATCH */
                        DO; fcbs.type = k;
                        /* SCAN PARAMETERS */
                        IF GNC = LB THEN CALL SCANPAR;
                        CBP = CBP - 1;
                        RETURN;
                        END;
                    J = J + 3;  /* OTHERWISE TRY NEXT DEVICE */
                    END;
                RETURN;  /* ERROR, NO DEVICE NAME MATCH */
                END;
            IF CHAR = LB THEN /* PARAMETERS FOLLOW */
                CALL SCANPAR;
            END;
        ELSE
            /* CHAR IS NOT ':', SO FILE NAME IS SET. SCAN REMAINDER */
            DO; IF FLEN = 0 THEN /* ERROR, NO PRIMARY NAME */
                RETURN;
            FLEN = NSIZE;
            IF CHAR = '.' THEN /* SCAN FILE TYPE */
                DO WHILE NOT DELIMITER(CHAR := GNC);
                IF FLEN >= FNSIZE THEN
                    RETURN;  /* ERROR, TYPE FIELD TOO LONG */
                IF CHAR = '*' THEN CALL FILLQ(FNSIZE);
                ELSE CALL PUTCHAR;
                END;
/** $if mpm **/
            FLEN = 0;
            IF CHAR = ';' THEN  /* SCAN PASSWORD */
                DO WHILE NOT DELIMITER(CHAR := GNC);
                IF FLEN >= NSIZE THEN
                    /* ERROR, PW TOO LONG */ RETURN;
                ELSE  /* SAVE PASSWORD */
                    FCBS.PWNAM(FLEN) = CHAR;
                    FLEN = FLEN + 1;
                END;
/** $endif **/
            IF CHAR = LB THEN
                CALL SCANPAR;
            /* RESCAN DELIMITER NEXT TIME AROUND */
            CBP = CBP - 1;
            fcbs.type = FILE;
            FCBS.FCB(32) = 0;
            RETURN;
            END;
        END;
    END SCAN;


/* PLM (PIP) ENTRY POINT */
    /* BUFFER AT 80H CONTAINS REMAINDER OF LINE TYPED
    FOLLOWING THE COMMAND 'PIP' - IF ZERO THEN PROMPT TIL CR */

    if not retry then
        do; CALL MOVE(.BUFF,.COMLEN,80H);
        MULTCOM = (COMLEN = 0);

        /* GET CURRENT CP/M VERSION */
        IF low(CVERSION) < VERSION THEN
            DO;
/** $if cpm3 **/
            CALL PRINT(.('REQUIRES CP/M 3$'));
/** $else **/
/** $endif **/
            CALL BOOT;
            END;

        call mon1(45,255);  /* set return error mode */

/** $if cpm3 **/
        call mon1(109,1);  /* set CP/M 3 control-C status mode */
/** $endif **/

        if multcom then
            do;
/** $if cpm3 **/
            call printx(.('CP/M 3 PIP VERSION 3.0$'));
/** $else **/
/** $endif **/
            call crlf;
            end;

        cuser,last$user = getuser;  /* GET CURRENT USER */
        cdisk = getdisk;            /* GET CURRENT DISK */
/** $if mpm **/
        mseccnt = 1;
/** $endif **/
        eretry = false; /* need to initialize here for first time */
        end;


    /* START HERE ON RESET EXIT FROM THE PROCEDURE 'ERROR' */
/** $if mpm **/
    if eretry <> 0 then 
        do; call multcopy;
        comlen = multcom;
        end;
/** $endif **/
    /* MAIN PROCESSING LOOP.  PROCESS UNTIL CR ONLY */
        DO FOREVER;
        C1, C2, C3 = 0;     /* LINE COUNT = 000000 */
        CONCNT,COLUMN = 0;  /* PRINTER TABS */
        ndest,nsbuf = 0;
        ambig = false;
        made = false;       /* destination file not made */
        opened = false;     /* source file not opened */
        concat = false;
        eretry = false;
        PUTNUM = TRUE;      /* ACTS LIKE LF OCCURRED ON ASCII FILE */
        dfile,sfile = true;
        nendcmd = true;
        LINENO = 254;       /* INCREMENTED TO 255 > PAGCNT */
        /* READ FROM CONSOLE IF NOT A ONELINER */
        IF MULTCOM THEN
            DO; CALL PRINTCHAR('*'); CALL RDCOM;
            CALL CRLF;
            END;
        CBP = 255;
        IF COMLEN = 0 THEN      /* character = <CR> */
            do; call setcuser;  /* restore current user */
            CALL BOOT;          /* normal exit from pip here */
            end;

        /* LOOK FOR SPECIAL CASES FIRST */

        CALL SCAN(.odest);
        if ambig then
            call xerror(5,.odest);  /* invalid destination */
        call deblank;  /* check for equal sign or left arrow */
        if (char <> '=') and (char <> la) then call formerr;
        call scan(.source);

        IF odest.type = DISKNAME THEN
            DO;
            IF source.type <> file then call formerr;
            CALL CK$EOL;
            CALL CK$DISK;
            odest.type = file;  /* set for character transfer */
            /* MAY BE MULTI COPY */
            IF AMBIG THEN  /* FORM IS A:=B:AFN */
                DO;
                CALL MOVE(.source.fcb(0),.searfcb(0),frsize);
                CALL MULTCOPY;
                END;
            ELSE DO;  /* FORM IS A:=B:UFN */
                CALL MOVE(.source.fcb(1),.odest.fcb(1),frsize - 1);
                CALL SIMPLECOPY;
                END;
            END;

        else IF (odest.type = FILE) and (source.type = DISKNAME) THEN
                DO;
                CALL CK$EOL;
                CALL CK$DISK;
                source.type = file;  /* set for character transfer */
/** $if mpm **/
                call move(.odest.fcb(1),.source.fcb(1),(frsize+nsize));
/** $else **/
/** $endif **/
                CALL SIMPLECOPY;
                END;

        else if (odest.type > cons) then
            call error(3);  /* invalid destination */
        else do;
            IF odest.type <> FILE THEN dfile = false;
/** $if not mpm **/
            /* no conditional attach list device */
/** $else **/
/** $endif **/
            /* SCAN AND COPY UNTIL CR */
                DO WHILE nendcmd;
                sfile = true;
                call deblank;
                IF (CHAR <> ',' AND CHAR <> CR) THEN
                    call error(16);  /* invalid separator */
                concat = concat or (nendcmd := (char = ','));
                IF odest.type = PRNT THEN
                    DO; NUMB = 1;
                    IF TABS = 0 THEN TABS = 8;
                    IF PAGCNT = 0 THEN PAGCNT = 1;
                    END;
                IF (source.type < file) or (source.type > eoft) or ambig THEN
                    call error(4);  /* invalid source */
                IF source.type <> FILE THEN  /* NOT A SOURCE FILE */
                    sfile = false;
                IF source.type = NULT THEN
                    /* SEND 40 NULLS TO OUTPUT DEVICE */
                    DO sfile = 0 TO 39; CALL PUTDEST(0);
                    END;
                ELSE IF source.type = EOFT THEN
                    CALL PUTDEST(ENDFILE);
                else call simplecopy;
 
                CALL CK$STRINGS;
                /* READ ENDFILE, GO TO NEXT SOURCE */

                if nendcmd then call scan(.source);
                END;
            end;

        /* COMLEN SET TO 0 IF NOT PROCESSING MULTIPLE COMMANDS */
        COMLEN = MULTCOM;

        END; /* DO FOREVER */
    end plm;
    END;

EOF
