title 'FCRUNCH multi-file cruncher' ; ver equ 11 ; ; Crunch file 1 to file 2. By C.B. Falconer. ; ; d>FCRUNCH [-[c][k][p][q]] [d[u]:]afnin.aft [d[u]:][afnout.aft] ; crunches afnin.aft to afnout.aft ; (with full du addressing, across users, drives) ; the (optional) -c causes confirmation per file crunched ; -k causes no existing files to be erased ; -p causes existing output file erasure ; -q suppresses running messages ; Default output name is input with a middle Z in filetype. ; ; 1.1 (86/12/20) Only version # changed, relinked to CRN v25, which ; allows for trapping of particular output sequences. This now ; traps the sequence <0dh>,<040h> (with possible high bits set) ; and injects a nulcode to output. This avoids problems with ; Telenet/PcPursuit (or so I am told). CRN can be configured ; to trap any 2 byte sequence, by reassembly. - cbf ; ; Created as a modification of FCOPY 1.1. Needs BUFFLIB v1.4 up ; ; ASSEMBLY (rmac is not usable without changes) ; A>m80 fcrunch,=fcrunch ; or ; A>slrmac fcrunch/r (much faster) ; ; CAUTION - nnnn/mmmm numbers below are examples, can change ; ; LINKING with L80 (slowest by far) ; ; A>l80 /p:100,/d:4000,fcrunch,crn,bufflib/s,/u,/r ; ; Link-80 3.44 . . . . ; Data 4000 405A < 90> ; Program 0100 0ADD < dddd> ; ; */p:100,/d:nnnn,fcrunch,crn,bufflib/s,fcrunch/n,/e ; ^ using nnnn = 0ADD from above line (example) ; ; LINKING with SLRNK ; ; A>slrnk /p:100,/d:4000,fcrunch,crn,bufflib/s,/u,/r ; ; Superlinker . . . . ; 0100-0ADC (09DD) 973E left ; Superlinker . . . . ; ; %/p:100,/d:mmmm,fcrunch,crn,bufflib/s,fcrunch/n,/e ; ^ using mmmm = 0ADD, i.e. 0ADC + 1 (example) ; ; LINKING with SLRNK+ (fastest and easiest, no copying numbers) ; ; A>slrnk+ /a:100,/j,fcrunch,crn,bufflib/s,fcrunch/n,/e ; (does it all) ; ; Externals in BUFFLIB. See BUFFERS.DOC extrn .getusr, .xltusr, .setusr; user parsing extrn .wildx, .wldck, .pfnmdu; file parsing extrn .options, .skipblks; command parsing extrn .fncpy, .nxtout, .nxtfn; fname operations extrn .dos; bdos(a), save regs extrn .idiv, .imul, .dcmp extrn .tdzs, .tfnam, .crlf; utility extrn .couta, .tdzsf, .blk extrn .initfcb, .drvlock extrn .bfwopen, .bfclose; write opens/close extrn .bfropen; read opens extrn .bfgetc, .bfputc; buffered file i/o extrn b.ohead; ext. constants extrn .bver; library version extrn .endata; Available memory area ; extrn crn; crunch package extrn incnt, outcnt, nxtcod, ttotal; display vars entry getchr, outbyt; crn i/o ; buflibver equ 14; minimum to link stkmargin equ 5*1400h + 256; bytes for crn & stack ; boot equ 0 bdos equ boot+5 tfcb equ boot+05ch; used to hold outname template defdma equ boot+080h ; cr equ 0dh lf equ 0ah t equ 09h crnchid equ 076feh; Standard header stpmax equ 40; max size (+1) of any stamp ; ; errors xhelp equ 0; no error, give help xempty equ 1; file empty xcrn equ 2; file already crunched/squeezed xstkovf equ 3; memory overflow xwild equ 4; incompatible wildcards xtoself equ 5; copying into self xnodir equ 6; fcreate failure xwrterr equ 7; write error xusrabt equ 8; user abort xbadlib equ 9; linked to wrong library xsaved equ 10; Old file not eraseable xbadnam equ 11; Unable to create new name xnofile equ 12; input file not found xok equ 13; done, without errors ; ; Dos function calls cin equ 1 pstrg equ 9 csta equ 11 @fopen equ 15 @purge equ 19 ; ; Initialize begin: jmp bgn ; ; Option image. Zero locations to pre-set. ; Make letters lower case to forbid setting. optimg: db '-',optlgh db 'C'; Confirm "crunch this file" option db 'K'; Keep pre-existing output files db 'P'; purge pre-existing output files db 'Q'; Quiet option. optlgh equ $-optimg-2 ; ; Start up here bgn: lhld bdos+1 mvi l,0; set stack at top of memory sphl call .getusr sta entryusr call .bver; This also ensures the lib version cpi buflibver; module is linked and identified mvi a,xbadlib jc exeunt; linked to wrong library lxi h,0 shld filesdone; zero count of files processed shld stamp; default empty stamp lxi d,defdma call markeol; of input command line inx d lxi b,optimg lxi h,options call .options; from de^ ; " " ; parse in/out file names lxi h,fcbout call .initfcb lxi h,fcbin call .initfcb; init in and out fcbs call .pfnmdu mov a,b; is 0 for default, else usr+1 call usrlok; now 1..max sta inuser lxi h,tfcb call .initfcb mvi b,0; in case no 2nd file parsed call .skipblks cpi '[' cnz .pfnmdu; no 2nd spec, reached the stamp push d; save input parse pointer mov a,b call usrlok; now 1..max, no defaults left sta outuser lxi d,fcbin; ensure locked to drive for compare call .drvlock; else default and specific drives lxi d,tfcb; may erroneously appear different call .drvlock ldax d sta fcbout; set output drive id lda fcbin+1 pop d; get input parse pointer back sui ' ' jz exeunt; a=0, help message ; " " ; Parse the remaining tail into the stamp buffer call rstamp; from de^ ; " " ; Set default output names, check compatible wild cards lxi h,fcbin lxi d,tfcb call sdefault; if no output, make same as input call .wldck; Check wild cards compatible mvi a,xwild jc exeunt; Incompatible wildcards ; " " ; Expand any wild cards lxi h,.endata shld fnptr lda inuser dcr a call .setusr lxi d,fcbin call .wildx; expand fcbin into wild list mvi a,xstkovf jc exeunt; Ran out of memory shld @outbuff; mark available memory xchg; de := @outbuff mov l,c mov h,b shld fncount; number of files matched ; " " ; Calculate space available for buffers and allocate them lxi h,b.ohead+b.ohead dad d xchg; adjust base of storage lxi h,-stkmargin dad sp mvi a,0; round down to page boundary sub e mov e,a mov a,h sbb d mov d,a; form buffer size available xchg lxi d,0; 0 extend lxi b,3 call .idiv; one third for outbuffer xchg mov a,l ani 080h; round down to multiple of 128 mov l,a shld bufsize ora h mvi a,xstkovf jz exeunt; 0 size buffers just wont do lxi d,b.ohead dad d; space needed for complete buffer xchg lhld @outbuff dad d shld @inbuff; locate past output buffer lxi d,b.ohead dad d xchg lhld bufsize dad h; double, two thirds for input buffer dad d shld @freemem; and locate available memory ; " " ; Now tfcb holds output file pattern, fnptr points to next input file ; name, and fncount holds file count to process. @outbuff points just ; above the file list and is allocated, @inbuff is allocated, memory ; from @freemem up, less an allowance for stack use, is available. copy: lhld fncount mov a,h ora l dcx h shld fncount jz done; no more files call setfiles; setup input/output file names mvi a,xbadnam push psw call showf; file names set up pop psw jc exeunt; Can't setup this name lda confirm ora a cz askcfm jz copy; ignore, try next lhld outuser; to l lda inuser cmp l lxi h,fcbout lxi d,fcbin cz cmpfns; users same, compare file names/drv mvi a,xtoself jz exeunt; can't copy to self lhld bufsize dad h; * 2 for input mov c,l mov b,h lhld @inbuff lxi d,fcbin lda inuser call .bfropen; open buffered input system mvi a,xnofile jc exeunt; input file not found lxi d,fcbout lda purge ora a lda outuser cnz chkpurge; Check for erasure of old file mvi a,xsaved jnz copy2; and treat NO as an error call .crlf lda outuser lhld bufsize mov c,l mov b,h lhld @outbuff call .bfwopen; open buffered output file mvi a,xnodir jc exeunt; fopen error call crnch jc copy1 lhld filesdone inx h shld filesdone jmp copy; no error ; ; error (a). If non-terminal delete output file and continue, ; otherwise abort everything. copy1: call delout copy2: cpi xstkovf jz exeunt; abort everything call msgptr call tstr jmp copy ; ; purge output file delout: push psw push d lda outuser dcr a call .setusr lxi d,fcbout mvi a,@purge call .dos; remove the partial file pop d pop psw ret ; done: lhld filesdone mov a,h ora l mvi a,xnofile jz exeunt; no files found call .crlf call .tdzs mvi a,xok ; " " ; output message index (a) and exit exeunt: call msgptr call tstr lda entryusr; restore user at entry call .setusr jmp boot ; ; convert error index (a) into pointer (de) to error message ; a,f,d,e,h,l msgptr: lxi h,errtbl add a add l mov l,a; point to msgtable entry (a) adc h sub l mov h,a mov e,m; get pointer to message inx h mov d,m ret ; ; messages for error codes 0 up ; ERROR MESSAGES ERROR CODES (0 up) errtbl: dw helpmsg, emptymsg; xhelp, xempty dw xcrnmsg, nomemsg; xcrn, xstkovf dw wildmsg, selfmsg; xwild, xtoself dw nodirmsg, wrterrmsg; xnodir, xwrterr dw abtmsg, badlibmsg; xusrabt, xbadlib dw ignoremsg, badnamsg; xsaved, xbadnam dw nofind, filesmsg; xnofile, xok ; helpmsg: db 'FCRUNCH v', ver/10 + '0', '.', ver MOD 10 + '0' db ' by C.B. Falconer',cr,lf,lf ; 1234567-1234567-1234567-1234567-1234567-1234567-1234567- db ' keep old quiet input file',t, 'output file',cr,lf db t, ' \ \',t, t, ' \',t, t, '\',cr,lf db 'FCRUNCH {-{c}{k}{p}{q}} {d{u}:}afnin.aft {d{u}:}{afout.aft} {[id]}' db cr,lf db t, ' / /',t, ' /',t, t, ' /',t, t, ' /' db cr,lf db ' confirm purge source',t,'destination',t,t, 'idstring' db cr,lf,lf ; 1234567-1234567-1234567-1234567-1234567-1234567-1234567-1234567- db t,'[idstring] is anything enclosed in []',cr,lf db t,'default destination is source name with modified "typ"',cr,lf,lf db 'ex: FCRUNCH -p b5:fcopy.* c6:',t,'(crunches to C6: and erases)' db cr,lf,'$' xcrnmsg: db 'File already squeezed/crunched',cr,lf,'$' emptymsg: db 'Empty file',cr,lf,'$' nomemsg: db cr,lf,'Insufficient memory, ' abtmsg: db '..ABORTED..$' wildmsg: db 'Incompatible wild cards, from/to$' selfmsg: db 'Can''t crunch file to itself$' nodirmsg: db 'Can''t create, directory full?$' wrterrmsg: db 'Write error, disk full?$' badlibmsg: db 'Linked to obsolete library$' ignoremsg: db ' Not erased..',cr,lf,'$' badnamsg: db ' Rename this input file$' nofind: db ' Not found, no' filesmsg: db ' files crunched$' ; ; mark end-of-line with nul, text buffer de^ ; a,f,h,l markeol: ldax d mov l,a xra a mov h,a dad d inx h mov m,a; mark eol ret ; ; Check for existance of file de^, user a. If found, query for ; erasure. Return z flag if not found, or if erasure permitted ; a,f chkpurge: dcr a; Using buffers variant for user call .setusr mvi a,@fopen call .dos inr a rz; not found, all well lda keepem ora a jz chkpg1; 0 is not a Y, no purge push d lxi d,query call tstr pop d call cupsft chkpg1: cpi 'Y'; Z flag for permission ret ; query: db ' Exists, ok to purge (y/N) ? $' qcnfm: db ' Crunch it (Y/n) ? $' ; ; ask to confirm squeezing this file. Z flag for no ; a,f askcfm: push d lxi d,qcnfm call tstr pop d ; " " ; Console input char and kludgy upshift. Compared to 'N' ; a,f cupsft: mvi a,cin call .dos ani 05fh cpi 'N' ret ; ; make file de^ same name as hl^ (name/ext only) if no spec ; a,f sdefault: inx d ldax d sui ' ' sta outspec; non-zero means output specified ldax d dcx d rnz jmp .fncpy; no specification, copy it ; ; Column header for noisy display colhdr: lxi d,colmsg ; " " ; String de^ to console ; a,f tstr: mvi a,pstrg jmp .dos colmsg: db ' in out ratio ca cr',cr,lf db ' == === ===== == ==',cr,lf,'$' ; ; All files open, and buffers assigned. ; Crunch fcbin to fcbout byte by byte. crnch: lda quiet ora a cnz colhdr lxi h,0 shld cksum dad sp shld savesp; in case of error call header; Make the standard header lhld @freemem; Where to put the tables mvi a,044h; use existing stack assignments & call crn; allow reset when tbl full & 1024 ca's rc; error exit lhld cksum mov a,l call outbyt mov a,h call outbyt; include the checksum lhld @outbuff call .bfclose; close the output file lhld xtraout inx h inx h shld xtraout; allow for checksum lda quiet ora a rz call show call .blk mvi a,'(' call .couta call inout; hl := input; de := output rcds xchg call .dcmp; flags on hl-de xchg cnc chksav; not smaller jnc crnch8; not saved, de has message ptr. call dvd8hl; rounding up call .tdzs; input kbytes xchg lxi d,ktok call tstr call dvd8hl call .tdzs; output kbytes lxi d,kend crnch8: call tstr xra a; no error ret ; ktok: db 'k --> $' kend: db 'k)',cr,lf,'$' nosave: db 'Not smaller, not saved)',cr,lf,'$' ; ; Check for save of output file. Forced to no save here ; Set carry if file to be saved. ; Reset carry if file purged, when de is message pointer. ; a,f (de if no save only) chksav: call delout ora a lxi d,nosave ret ; setup input/output file names in fcbin/fcbout, using the globals ; fnptr, tfcb. Carry if unable to create a suitable file name. ; a,f,b,c,d,e,h,l setfiles: lxi d,fcbin lhld fnptr call .nxtfn; load the next file name lxi d,16 dad d shld fnptr; advance source name pointer lxi d,fcbout; setup fcbout by lxi h,tfcb; copying template lxi b,fcbin; and replacing wild loc'ns call .nxtout lda outspec ora a rnz; file specified ; " " ; Now modify the output file type lxi h,fcbout+9 mov a,m cpi ' ' jz setf1; no extension, make it ZZZ inx h mov a,m cpi 'Z' jnz setf2; Revised file type to xZx mvi m,'Z' inx h mov a,m cpi 'Z' jnz setf2; xZZ does it mvi m,'Z' dcx h dcx h mov a,m cpi 'Z' jnz setf2; ZZZ does it stc ret; Can't rename this file setf1: mvi m,'Z' inx h mvi m,'Z' inx h setf2: mvi m,'Z' ora a; clear any carry ret ; ; show file to be transferred ; a,f,b,c,d,e,h,l showf: lda quiet ora a cnz .crlf lda inuser dcr a; to cpm usage lxi d,fcbin call .tfnam; input file id lxi d,xfrtomsg call tstr; '==>' lda outuser dcr a; to cpm usage lxi d,fcbout call .tfnam; output file id ; " " ; Check for user abort ; a,f ckabt: mvi a,csta call .dos rz; no console interrupt mvi a,cin call .dos cpi 3 mvi a,xusrabt jz exeunt; user abort ret ; xfrtomsg: db ' ==> $' ; ; check fcbs de^ and hl^ are different names, else zero flag ; a,f cmpfns: push b push d push h dcx h; pre-decrement dcx d mvi b,12; names and drive ids cmpfn1: inx h inx d ldax d xra m ani 07fh; ignore attributes jnz cmpfn2 dcr b jnz cmpfn1 cmpfn2: pop h pop d pop b ret ; ; Make a standard crunched file header header: mvi a,crnchid shr 8 ora a; set nz, no display yet call outbyt mvi a,crnchid AND 0ffh ora a; set nz, no display yet call outbyt lxi h,3; 2 already, 0 byte coming xchg lxi h,fcbin call outnm; mark the source file id lxi h,stamp call outstg; emit the user stamp xchg shld xtraout; save string size etc. ori 0ffh; reset z flag mvi a,0; emit the 0 string terminator ; " " ; output a to buffered output file. Savesp initialized. ; Input Z flag triggers display mechanism. CRN module linkage ; a,f,c outbyt: mov c,a push h cz show; display statistics etc. lhld @outbuff call .bfputc pop h rnc; no error lhld savesp sphl mvi a,xwrterr jmp exeunt; i/o error ; ; Linkage for crn module ; a,f,h,l getchr: lhld @inbuff call .bfgetc; get a byte. Carry for eof. rc; no checksum update at eof lhld cksum push psw add l mov l,a adc h sub l mov h,a shld cksum pop psw ret ; ; Output string hl^ until 0 byte. Countem in de ; a,f,c,h,l outstg: mov a,m ora a rz inx d; count chars call outbyt; nz flag, will not trip display inx h jmp outstg ; ; Output file name hl^. Full blank fill the type field outnm: push b mvi b,8; max chars in file name push h; save start point outnm1: inx h mov a,m inx d; count chars emitted cpi ' ' jz outnm2; done file name call outbyt; nz, no display trip dcr b jnz outnm1 inx d; for coming '.' outnm2: mvi a,'.' ora a; nz, prevent display call outbyt pop h; get start point back lxi b,8; point to type field dad b mvi b,3 outnm3: inx h mov a,m inx d ora a; nz, prevent display call outbyt dcr b jnz outnm3 pop b ret ; ; Get input/output records to hl, de respectively ; a,f,d,e,h,l inout: lhld incnt+1; LS byte is zero dad h; double, in records lda incnt add a jnc inout1; not an extra record inx h inout1: adi 252; 2 * (127-1). EOF was counted at end. jnc inout2; no partial record to round up inx h inout2: push h lhld outcnt; only useful at end mvi h,0 xchg lhld xtraout dad d lxi d,127; round up to records dad d dad h; double, records to h mov a,h; additional lhld outcnt+1; LS byte is zero dad h; double, to records add l mov l,a; + the extra bits adc h sub l mov h,a; form records emitted (rounded up) xchg pop h ret ; ; Show any statistics etc. ; a,f,h,l show: call ckabt; check for user abort lda quiet ora a rz; in quiet mode push b push d mvi a,cr call .couta call inout; get input/output records call tdzs6f; show input records push h xchg call tdzs6f; show output records xchg lxi b,200; for rounding call .imul; dehl := de*bc pop b call .idiv; de := dehl/bc = input/output*200 xchg inx h; round result call dvd2hl; divide / 2 (for 200 above) mvi a,5 call .tdzsf mvi a,'%' call .couta lhld nxtcod call tdzs6f lhld ttotal call tdzs6f pop d pop b ret ; ; Shift hl right 3 (divide by 8), rounding up ; a,f,h,l dvd8hl: push d lxi d,7 dad d pop d call dvd2hl call dvd2hl ; " " ; Shift hl right 1. RH bit to carry ; a,f,h,l dvd2hl: mov a,h ora a rar mov h,a mov a,l rar mov l,a ret ; ; Write hl (dec) in 6 char. field. with at least 1 leading blank ; a,f tdzs6f: mvi a,6 jmp .tdzsf ; ; Parse the command tail into the stamp buffer ; a,f,b,d,e,h,l rstamp: lxi h,stamp call .skipblks rc; EOL, no tail cpi '[' rnz; not a valid marker mvi b,stpmax-1 rstp1: dcr b jz rstp2; max storage used mov m,a inx h cpi ']' jz rstp3 inx d; (CCP+ can allow lc command tails) ldax d; not .nextch, don't upshift cpi ' ' jnc rstp1 rstp2: mvi m,']'; jam in the missing ']' inx h rstp3: mvi m,0; default EOL ret ; ; Lock the user # (0 means default) to an absolute value. ; Returns in range 1..maxuser, and cpm calls use this -1. ; This is because the buffer system can record "default user" ; as distinct from "specified user". ; a,f usrlok: call .xltusr; now range 0..maxuser inr a; now 0 is forbidden. ret ; dseg; LINK AFTER all code areas options: ds 2; Standard header confirm: ds 1; Flog for confirmation per file keepem: ds 1; Flag to keep old files purge: ds 1; Flag for copy verification quiet: ds 1; flag for quiet operation if ($-options)-2 ne optlgh +++ Error in option storage assignment +++ endif ; entryusr: ds 1; User in effect at startup outspec: ds 1; non-zero if output file specified ; ; inuser/outuser are 0 for current user, user+1 if specified inuser: ds 1; User # for fcbin outuser: ds 1; User # for fcbout ; fcbin: ds 36; Input fcb fcbout: ds 36; Output fcb bufsize: ds 2; space available for outbuff @inbuff: ds 2; pointer to input buffer, 2 * bufsize @outbuff: ds 2; pointer to output buffer, 1 * bufsize @freemem: ds 2; pointer to available memory cksum: ds 2; for checksum accumulation filesdone: ds 2; count of files processed fncount: ds 2; count of files to process fnptr: ds 2; pointer to NEXT file to process savesp: ds 2; for aborts during writes xtraout: ds 2; keep track of overhead bytes added stamp: ds stpmax; User entered stamp end