title 'JOB 1.5 (86/10/15)' ; An improved submit facility for CP/m systems. ; evolved from public domain supersub by Ron Fowler ; ; copyright (c) 1983,1984,1986 ; By C.B. Falconer, ; 680 Hartford Tpk, Hamden, CT 06517. (203) 281-1438 ; released to the public domain ; ; JOB allows parameters to be terminated by commas, thus a pair ; (i.e. ",,") can specify a null parameter. In addition, parameters ; can be "quoted strings", to allow any input whatsoever. In a quoted ; string a quote must be represented by two quotes. JOB also accepts ; empty input lines (e.g. PIP exit command). ; ; JOB creates the $$$.SUB file on drive A, (user 0 as of JOB15) and ; thus can be executed with any setting of the default drive. In ; addition JOB searches both the default and A drives for the .JOB ; file when no drive is specified in the command line, and (ver 1.4) ; if no .JOB file has yet been found searches default and system disks ; for a component of JOBS.LBR file. ; ; Thus a set of small .JOB files can be packed into JOBS.LBR and ; executed directly. When many .JOB files are used this can ; significantly reduce disk storage ; ; An initial line in the .JOB file beginning with ";;" (double ; semi-colon) signifies that the line specifies a default set of ; parameters ($0 thru $9). These are only used when the execution ; command line does not supply parameters. ; ; JOB does not arbitrarily upshift anything, however an unmodified CCP ; will probably upshift all input lines. CCPLUS can be set to avoid ; this upshifting. ; ; JOB will accept most files created for SUBMIT, unless supplied ; parameters include commas and quotes. Since the reverse is not true ; JOB expects its' input files to be of type .JOB. ; ; JOB15 up is organized to co-operate with CCP+ and execute jobs thru ; changes in user number. To perform correctly, a minor patch to BDOS ; is required, listed below. ; ; A test file to demonstrate JOB appears below (note null line): ;;testingjob parm1, "parm2",, parm4 parm5, ," parm ""7"", " ; null defaults for parm3, parm6, parm8, parm9 ;$1; ;$2; ;$3,$4,$5,$6; ;$7; ;$8; ;$9; ;$0; <> ; try A>JOB TESTJOB ; where testjob is the above file from the ";;" ; This simply shows a set of comment commands on the console. ; ; Revision history ; ================ ; ; 1.5 86/10/15 (cbf) Forced $$$.SUB file on user 0. After all, CPM ; is a single user system. Cooperates with CCP+ v21 up. ; A patch for cpm2.2 for submit jobs to execute while changing ; user numbers. This co-operates with CCP+ v2.1 and JOB v1.5. ; ; All values are shown for an unrelocated BDOS. marked bytes ; may be different by a constant ; ; Location ; (from start ; of BDOS) Was Becomes Comment ; 06DE 3e 7e mvi a,0e5 --> mov a,m ; 06DF e5 fe ; 06E0 be e5 cmp m --> cpi 0e5 ; 06E1 ca ; 06E2 d2 jz ... unchanged ; 06E3 06 * ; 06E4 3a 18 lda usrno --> jr $+3 ; 06E5 41 01 ; 06E6 03 * (code byte skipped) ; 06E7 be b7 cmp m --> ora a ; ; The WAS items marked by '*' will vary with the location of ; the CPM system, and should not be altered. The patch uses ; the Z80 JR instruction, so for Z80 systems only. This has ; been carefully designed to be position independant. ; ; When completed, the original CCP will no longer execute ; SUBMIT jobs when the user number is non-zero. It would never ; allow a submit job to change users, which can now be done ; when using CCP+ and JOB15. (earlier versions of JOB will ; work if the job is started on user 0 only.) ; ; This causes BDOS to return 0ffh for call 13 (reset disks) ; whenever a '$*.*' file exists on user 0. This flag is ; used by CCP+, ZCPR, and the original CCP to decide whether ; to bother searching for the $$$.SUB file. The original ; code only returned 0ffh when the file existed on the current ; user. ; ; 1.4 84/06/23 (cbf) Added library file search ; ver equ 15; update with each revision ; cr equ 0dh lf equ 0ah tab equ 9 eol equ '$' parmchar equ '$' slash equ '/' comma equ ',' quote equ '"' semi equ ';' blank equ ' ' linesep equ '|' escape equ '^'; to control chars eofmark equ 01ah; sub=cntrl-z maxparms equ 9 ; putchar equ 2; cp/m functions putstring equ 9 getstring equ 10 fopen equ 15 fclose equ 16 fpurge equ 19 rdseq equ 20 wrtseq equ 21 newfile equ 22 gsuser equ 32; get/set user rdran equ 33 ; reboot equ 0; cp/m connector locations bdos equ 00005h tfcb equ 05ch tfcb.nam equ tfcb+1 tfcb.ext equ tfcb+9 cmdline equ 080h defdma equ cmdline cmd.lgh equ cmdline cmd.chars equ cmdline+1 ; ; aseg org 0100h ; begin: lxi h,0 dad sp shld stksave lxi sp,stack mvi a,gsuser mvi e,0ffh call dos sta defusr; save input user call job db 'JOB V' db ver / 10 + '0', '.', ver MOD 10 + '0',cr,lf,eol ; job: pop d lda tfcb.nam cpi blank jz help mvi a,putstring call dos; sign-on call init call getcmnd call chkinf; for blank or .job extension lxi h,0 shld linenum call openinf; zero flag for interactive mode cnz freadjob; put 1st line in buffer jnz jobempty; read failed call default; set default params call inputlns xra a; REMOVE this for use with original mov e,a; CCP or ZCPR. mvi a,gsuser call dos; jam to user 0 for subf operations call opnsubf call wrtsubf lda defusr mov e,a mvi a,gsuser call dos; restore entry user jmp reboot ; ; Check that any extension is ".JOB", or insert such ; if the supplied extension is blank. ; a,f,b,d,e,h,l chkinf: lxi h,tfcb.ext mov a,m cpi blank jz chkinf2 lxi d,job.xt mvi b,3 call qmatch rz ; " " nofile: call errexit; leaves forever db '.' job.xt: db 'JOB file not found',eol ; chkinf2: xchg lxi h,job.xt mvi b,3 chkinf3: mov a,m; move "JOB" into place stax d inx h inx d dcr b jnz chkinf3 ret ; getcmnd: lxi h,cmd.chars call skipblks sta mode rc cpi slash jnz getcmnd3; normal operation inx h getcmnd1: shld cursor mov a,m sta onelnflg cpi blank rnz inx h jmp getcmnd1 getcmnd3: call skipblks getcmnd4: cnc setparm jnc getcmnd3 dcx h; back to eol mark for null param push h lhld parmindx mov a,m ora a pop h jz getcmnd4; setup a null parameter ret ; ; skipblanks in hl^, return 1st non blank. cy for eol, eof ; a,f,h,l skipblks: mov a,m inx h ora a stc rz cpi cr stc rz cpi eofmark stc rz cpi blank jz skipblks cpi tab jz skipblks dcx h ora a ret ; ; return carry if eol encountered setparm: call setdelim push h; save input index lhld freeptr xchg lhld parmindx mov a,m ora a jnz manyparm mov m,e inx h mov m,d inx h shld parmindx pop h ; " " ; save parameter ; (hl)^ is start of parameter string, ; (de)^ to location to save parameter savparm: push d inx d mvi b,0 savparm1: lda delim cpi quote jnz savparm2; not a quoted string mov a,m inx h ora a jz parmerr; no terminal quote cpi quote jnz savparm4; not terminal quote, go save char cmp m; is it a double quote? jnz savparm5; no, string is terminated inx h jmp savparm4; yes, store a quote savparm2: mov a,m inx h call chkdelim jz savparm5 savparm4: stax d inx d inr b jmp savparm1 savparm5: xchg shld freeptr pop h mov m,b xchg; pointer to terminal char cpi quote jnz savparm6; not a quoted string mov a,m cpi comma jnz savparm6 inx h; skip terminating comma on mov a,m; a quoted string savparm6: ora a stc rz cmc ret ; ; setup delimiter for parameters. Advance past any initial '"' ; a,f,h,l setdelim: mvi a,' ' sta delim mov a,m cpi quote rnz; not a quoted string sta delim inx h; advance past the initial quote ret ; ; Set up default parameters ; The first line is in defdma. If the mode is not interactive, and ; the line begins with ";;", then replace each null parameter with ; the value in this first line, starting with 0. Note that the 0th ; parameter is the jobfile name, and cannot be null. The replacement ; parameters may be null. Such an initial line will be treated as a ; comment when executed by CCP, displaying the defaults. default: lda mode cpi slash rz; interactive mode lda defdma cpi semi rnz; not initial ";;" lda defdma+1 cpi semi rnz; not initial ";;" lxi h,defdma+1; before first possible character mvi b,0; parameter number ; WHILE NOT eoln DO BEGIN ; delimit_parameter; ; IF non-null THEN ; IF parameter (b) is null THEN BEGIN ; expand_parm_storage ; insert_default; END; ; delimit next parameter; END; default1: inx h; bypass previous delimiter call skipblks rc; no more call setdelim call scan; to parm end ; now (c) is parameter lgh, (de) is start, (b) is id ; watch out for doubled quotes, counted as singles ; (hl)^ is terminating char, not part of parm push h push d mov a,c ora a jz default8; default param is null mov a,b; parameter id call findparm ora a jnz default8; cmdline input is non-null ; now store the default parameter and ; update the index to it. The previous ; storage for a null parameter is discarded. lhld freeptr xchg mov m,e inx h mov m,d pop h; ^ param start push h push b call savparm pop b default8: pop d pop h mov a,m call chklnend rz; done inr b; next parameter id jmp default1; go try for next ; ; scan to parameter end. (hl)^ is start ; return (de)^ to start, (c) = length ; (hl)^ to delimiting character ; a,f,c,d,e,h,l scan: mov d,h mov e,l; save starting point mvi c,0; length, default 0 lda delim cpi quote jz scan2; looking for terminal quote scan1: mov a,l ora a jz parmerr; no end found mov a,m call chkdelim jz scan9 inx h inr c; count length jmp scan1 scan2: mov a,l ora a jz parmerr mov a,m cpi cr jz parmerr cpi quote jz scan4; go check for double scan3: inr c; count chars inx h jmp scan2 scan4: inx h cmp m jz scan3; double quote, keep 1 only mov a,m call chkdelim jnz parmerr scan9: ret; m points to terminating char. ; ; check (a) for possible delimiter, zero flag if so ; a,f chkdelim: cpi blank rz cpi tab rz cpi comma rz ; " " ; check (a) for line ending char, zero flag if so ; a,f chklnend: cpi cr rz cpi eofmark rz ora a ret ; ; open input file. z flag for interactive mode openinf: lda mode cpi slash rz; interactive mode lxi d,tfcb call openf rnz; success lxi h,lbrfcb call trylb; look in JOBS.LBR if not found jz nofile; failed ret ; ; Try to find component (de)^ in library file (hl)^ ; z flag for failure ; If successful set up (de)^ [TFCB] for component access ; Note that the component size is needed in case the item ; ends on a record boundary without any eof mark. ; ** This routine needs cleanup to return component size ; in (bc), and use only the input pointers (and defdma) ; for access. This will decouple it from this program. ; a,f,b,c trylb: xchg mvi m,0; mark default again call openf; automatically tries system disk xchg rz; failed xchg mvi a,rdseq call dos jnz trylb9; else first directory rcd read call chksz; set (a) = entry count jz trylb9; not a valid library mov b,a; entry count push h; save component id pointer trylb1: dcr b jz trylb8; no more entries, not found mov a,b; Now check (max - b)th entry ani 3; for the one we want mvi a,rdseq cz dos; get next directory chunk mov a,b cma inr a ani 3 add a add a add a add a add a; * 32 bytes per entry adi defdma; point to this entry pop h push h; get component pointer push d push b; THIS needs cleanup to use only the xchg mov l,a; entry parameters for TFCB etc. mvi h,0; and return a record count in (bc) mvi b,12; Does this entry match call qmatch; the one we want? pop b pop d jnz trylb1; no, try next entry mov a,m; yes, set up file access etc. sta lb.rrn; m points to loc'n entry inx h mov a,m sta lb.rrn+1; record pointer inx h mov a,m inx h mov h,m mov l,a; record count shld maxrecs; to force an eof when reading xra a sta lb.rrn+2; now do a random read to set ptrs push d mvi a,rdran lxi d,lbrfcb call dos lxi h,tfcb mvi b,36 trylb7: ldax d; Move the complete opened fcb mov m,a; to tfcb inx h inx d dcr b jnz trylb7; More pop d pop h ori 1 ret; signal success trylb8: pop h trylb9: xchg; Not found, restore mov a,m inr m ora a; If was default disk then jz trylb; go try system disk xra a; else signal failure ret ; ; check size of library directory (de)^. 1st record in defdma ; Return (a) = count of entries (0 if not a library) ; a,f chksz: push b push d push h lxi h,defdma mov a,m ora a jnz chksz2 mvi b,11 chksz1: inx h mov a,m cpi ' ' jnz chksz2; invalid director dcr b jnz chksz1 inx h mov a,m inx h ora m inx h inx h ora m jnz chksz2 dcx h mov a,m; get directory size add a add a; in directory entries jmp chksz3; 0 causes error signal chksz2: xra a; signal none chksz3: pop h pop d pop b ret ; ; Open file (de)^. Check default and system disks ; z flag for failure ; a,f openf: push h lxi h,32 dad d mvi m,0; set record pointer to zero pop h mvi a,fopen call dos inr a rnz; success ldax d ora a jz openf1; was default, try system xra a ret; with z flag for failure openf1: mvi a,1; try system disk stax d jmp openf ; inputlns: lhld linenum; set up for line inx h shld linenum lhld lineptr xchg lhld freeptr shld lineptr mov m,e inx h mov m,d inx h push h inx h mvi c,0 inputl3: call nextch; store a line jc inputl5 db 0,0,0; was call upshift cpi eofmark jz inputl5 cpi lf jz inputl3 cpi cr jz inputl4 mov m,a inx h call memcheck inr c jm toolong jmp inputl3 inputl4: shld freeptr pop h mov m,c jmp inputlns; next line inputl5: shld freeptr pop h mov m,c ret ; nextch: push h push d push b lda mode cpi slash jnz nextch1; get from file call getch; get from terminal jmp nextch3 nextch1: lda inptr ora a cm freadjob jnc nextch2; success, (a) is zero mvi a,eofmark jmp nextch3 nextch2: mov e,a mvi d,0 inr a sta inptr lxi h,cmd.lgh dad d mov a,m nextch3: pop b pop d pop h ora a ret ; freadjob: mvi a,0 sta inptr lhld maxrecs mov a,h ora l dcx h shld maxrecs mvi a,1; to signal eof if no read mvi a,rdseq lxi d,tfcb cnz dos; records left ora a stc rnz; eof cmc ret ; getch: lda onelnflg ora a jnz getch2; one line only lda buf.size ora a cm userline jc getch3 dcr a sta buf.size jp getch2 getch1: mvi a,cr ret getch2: lhld cursor mov a,m inx h shld cursor cpi linesep jz getch1 ora a rnz getch3: mvi a,eofmark ret ; userline: lxi d,prompter mvi a,putstring call dos lxi d,buf.max mvi a,getstring call dos lda buf.size lxi h,buf.data shld cursor ora a stc rz cmc ret ; memcheck: lda bdos+2 dcr a cmp h rnc call errexit db 'Memory full',eol ; opnsubf: mvi a,fopen call xsubf inr a jz opnsubf1 lda sub.size sta sub.rnum; prepare to append ret opnsubf1: sta sub.rnum; assumes (a)=0 mvi a,newfile call xsubf inr a rnz call errexit db 'Directory full',eol ; wrtsubf: lhld lineptr mov a,h ora l jz jobempty mov e,m inx h mov d,m inx h xchg shld lineptr xchg mov a,m ora a jz wrtsubf3; empty line wrtsubf1: call wrtln lhld linenum dcx h shld linenum lhld lineptr mov a,h ora l jz wrtsubf4; no more mov e,m inx h mov d,m inx h xchg shld lineptr xchg jmp wrtsubf1 wrtsubf3: lhld linenum dcx h shld linenum jmp wrtsubf wrtsubf4: mvi a,fclose ; " " ; subfile operation (a) ; a,f,d,e xsubf: lxi d,subfcb ; " " ; Bdos call, function (a), return in (a), preserve registers ; Set flags on (a) ; a,f dos: push h push d push b mov c,a call bdos ora a pop b pop d pop h ret ; ; Copy the line, making any parameter substitutions wrtln: mov a,m inx h sta chrsleft shld @nextch lxi h,cmd.chars shld putptr xra a sta cmd.lgh mov b,l wrtln1: mov m,a inx h inr b jnz wrtln1 wrtln2: call getchar jc putsubf cpi escape jnz wrtln3 call getchar jc cntrlch cpi escape jz wrtln3; doubled to use escape char. call upshift sui '@' jc cntrlch cpi blank jnc cntrlch wrtln3: cpi parmchar jnz wrtln4 lda mode cpi slash mvi a,parmchar; In case interactive, use it jz wrtln4; '$' not special in interactive mode call chknxtch jc parmerr cpi parmchar jnz wrtln5; Go expand a parameter call getchar wrtln4: call putoutch jmp wrtln2 wrtln5: call getchar; a parameter identifier call qnum jc parmerr; not numeric, no good sui '0'; one digit only allowed call findparm jc parmerr mov b,a; length inr b; may be zero wrtln6: dcr b jz wrtln2; end of substitution mov a,m inx h push h call putoutch pop h jmp wrtln6 ; putsubf: mvi a,wrtseq call xsubf ora a rz call errexit db 'Disk full',eol ; getchar: lxi h,chrsleft mov a,m dcr a stc rm mov m,a lhld @nextch mov a,m inx h shld @nextch cmc ret ; putoutch: lxi h,cmd.lgh inr m jm toolong lhld putptr mov m,a inx h shld putptr ret ; chknxtch: lda chrsleft ora a stc rz mov a,m cmc ret ; ; find parameter (a). Returns (a)=length, ; (hl)^ to start of parameter, ; (de)^ to pointer to lgh field (index table) ; a,f,d,e,h,l findparm: cpi maxparms+1 jnc manyparm mov l,a mvi h,000h dad h lxi d,parmxpnd dad d mov e,m inx h mov d,m dcx h xchg mov a,m inx h ret ; qmatch: ldax d cmp m rnz inx h inx d dcr b jnz qmatch ret ; ; carry if (a) is not a numeric character ; f qnum: cpi '0' rc cpi '9'+1 cmc ret ; ; write (hl) as decimal number, suppress leading zeroes tdzs: push psw push h push b lxi b,0f00ah; c=divisor=10; b=iter.cnt=-16 xra a; clear tdzs1: dad h ral; shift off into (a) cmp c jc tdzs2; no bit sub c inx h; bit=1 tdzs2: inr b jm tdzs1; not done pop b push psw; save output digit mov a,h ora l cnz tdzs; recursive pop psw pop h adi '0' call cout pop psw ret ; ; crlf to console ; a,f crlf: mvi a,cr call cout mvi a,lf ; " " ; (a) to console ; a,f cout: push d mov e,a mvi a,putchar call dos pop d ret ; ; Upshift (a) ; a,f upshift: cpi 'a' rc cpi 'z'+1 rnc ani 05fh ret ; parmerr: call errexit db 'Parameter',eol ; manyparm: call errexit db 'Too many parameters:',eol ; toolong: call errexit db 'Line too long:',eol ; jobempty: call errexit db 'Job file empty',eol ; cntrlch: call errexit db 'Control character',eol ; ; Message (tos)^ and exit errexit: pop d mvi a,putstring call dos lxi d,errmsg mvi a,putstring call dos lhld linenum call tdzs call crlf mvi a,fpurge call xsubf jmp reboot ; errmsg: db ' error on line number: ',eol prompter: db cr,lf,'*',eol ; init: lxi h,clrbegin lxi b,buf.max-clrbegin init1: mvi m,000h; clear inx h dcx b mov a,b ora c jnz init1 lxi h,parmxpnd shld parmindx lxi h,-1; mark end of parameter pointers shld lastparm shld maxrecs; allow 65535 recs on non lbr file lxi h,storage shld freeptr mvi a,080h sta inptr sta buf.size sta buf.max ret ; help: lxi d,helpmsg mvi a,putstring call dos lhld stksave sphl ret ; helpmsg: db cr,lf,'How to use JOB:',cr,lf,lf db 'JOB : ' db 'print this message',cr,lf db 'JOB / : ' db 'interactive mode',cr,lf db 'JOB / : ' db 'use SUMMARY mode',cr,lf db 'JOB : as standard ' db 'SUBMIT.COM, but JOB will search',cr,lf db 'for A:file.JOB after the default ' db 'disk if no drive was specified,',cr,lf db 'and then as a component of "JOBS.LBR" ' db 'if not already found.',cr,lf db 'The SUB file is created on "A:", ' db ' thus JOB may be used while any',cr,lf db 'drive is the default. Parameters ' db 'may be delimited by commas and',cr,lf db 'may be "quoted strings". An initial ' db 'line beginning with ";;" can',cr,lf db 'be used to specify default params ' db '(0..9) at execution time.',cr,lf,lf db 'In "/" (interactive) mode, JOB ' db 'will prompt you a line at a time',cr,lf db 'for the "SUBMIT" job input. ' db 'Logical lines may be combined on the',cr,lf db 'same input line by separating them ' db 'with "|". Example:',cr,lf db ' A>JOB /STAT|DIR',cr,lf db 'specifies two commands on the' db ' same input line.',cr,lf,lf db 'Submitted jobs may nest...JOB ' db 'inserts ahead of an existing job.',cr,lf,lf db 'To insert a control character ' db 'into the output, prefix it with',cr,lf db 'a "^" ("^^" for real "^").',cr,lf,eol ; lbrfcb: db 0,'JOBS LBR',0,0,0,0 ds 16 lb.rno: ds 1 lb.rrn: ds 3; ; subfcb: db 1,'$$$ SUB'; LAST initialized storage ; clrbegin: ds 3; This area cleared at initialization sub.size: ds 17 sub.rnum: ds 1 ds 3; for random records ; delim: ds 1; delimiter for parameter input freeptr: ds 2 parmindx: ds 2 linenum: ds 2 lineptr: ds 2 chrsleft: ds 1 @nextch: ds 2 putptr: ds 2 inptr: ds 1 cursor: ds 2 onelnflg: ds 1 mode: ds 1 maxrecs: ds 2; to simulate eof on .lbr file parmxpnd: ds 2*(maxparms+1); a list of pointers to ; lgh, char string lastparm: ds 2; -1 marks end of parmxpnd array ; End of area cleared at initialization time ; defusr: ds 1; user at initialization time buf.max: ds 1 buf.size: ds 1 buf.data: ds 128 stksave: ds 2 ds 200; stack space stack: storage: ds 0; actually the rest ; end