; CPD.Z80 ; ; A ZCPR3 utility to compare two directories. ; Vers equ 15 SubVers equ ' ' ; ; USAGE: ; ; CPD {dir:}{afn} {dir:} {{/}options} ; ; If no DU or DIR is given, the current default is assumed. If no filename ; is given, all files ("*.*") is assumed. If no option is given, all ; matching files in the first directory are displayed with files flagged ; which also exist on the second directory. ; ; OPTIONS: A slash is not required if the option list is the third ; parameter on the command line. ; ; B Display only files that exist in both directories. This ; option cancels option M. ; ; N Display only files in the first directory that are missing ; from the second directory. This option cancels option B. ; (This was option M in version 1.0 and M still works.) ; ; A Set the archive attribute of files in the first directory ; that also exist in the second directory. This allows ; copying the files with a copy program that recognizes ; archived files. CPD does not remove archive attributes, ; so they should be reset with a program such as DA or FA ; before using this mode of CPD. ; ; S Include system (hidden) files. Normally CPD ignores ; system files. ; ; P Do not page screen display. Normally CPD waits for a ; key to be pressed after each screenful of filenames. ; ; O Output matching filenames to file CPDLIST. ; ; D Include DU specs with filenames in CPDLIST. ; ; L Echo display to the printer. This option also suppresses ; screen paging. ; ; F Send a final form feed to the printer, if option L is ; also active. ; ; Q Quiet mode suppresses console output. ; ; HISTORY: ; ; Version 1.5 -- January 19, 1991 -- Gene Pizzetta ; OpLFlg was not being initialized, which caused strange things ; to happen sometimes. ; ; Version 1.4 -- January 16, 1991 -- Gene Pizzetta ; Added D option to include DU specifications with filenames in ; the CPDLIST output file. Output filename configurable. Now ; uses new ZSLIB listfile output routine that saves enough bytes ; to keep CPD under 4K. ; ; Version 1.3 -- December 30, 1991 -- Gene Pizzetta ; Bugs fixed: CPD was writing CPDLIST to second (compared) directory ; when configured to write to the first (target) directory. Also, ; CPD was not stripping high bits when creating CPDLIST. ; ; Version 1.2 -- December 27, 1991 -- Gene Pizzetta ; Option O outputs matching filenames to a disk file in the default ; or target directory (configurable). Minor corrections to usage ; screen. New error codes for duplicate directory specification (16), ; out of memory (12), and disk or directory full (11). Option Q ; suppresses console output. ; ; Version 1.1 -- December 21, 1991 -- Gene Pizzetta ; The first version was a bit brain-damaged. It read only the ; first directory into memory and accessed the second disk once ; for each filename. SLOW! Now both directories are loaded into ; memory and CPD works very fast. Option M (show only files MISSING ; from second directory) has been changed to N (show only files NOT ; in second directory), which seems more intuitive to me. The M ; option still works, though. If system files are excluded from ; the first directory scan, they are also excluded from the scan ; of the second directory. CPD now displays eight columns of names ; if the environment indicates the screen has more than 121 columns. ; The between column divider is now configurable for both console ; and printer. Filenames can be displayed in upper- or lower-case ; (configurable). Sets error flag for invalid directory (2), TPA ; overflow (12), invalid option (19), and no matching file in second ; directory (FFh). The error handler is invoked on error, except ; for no matching file. ; ; Version 1.0 -- August 10, 1991 -- Gene Pizzetta ; The idea (but not the code) came from CDIR 2.0 by Robert Wilcox ; and Richard Brewster. This program could be a lot faster if ; both directories were loaded into memory first, but it does the ; job. Next time . . . ; ; Gene Pizzetta ; 481 Revere St. ; Revere, MA 02151 ; ; Voice: (617) 284-0891 ; Newton Centre Z-Node: (617) 965-7259 ; Ladera Z-Node Central: (213) 670-9465 ; ; System addresses ; Bdos equ 0005h ; BDOS entry CpmFcb equ 005Ch ; default file control block AltFcb equ 006Ch ; alternate file control block CpmDma equ 0080h ; default DMA buffer SetAtt equ 30 ; set file attributes (BDOS function) ; ; ASCII ; CtrlC equ 03h ; ^C LF equ 0Ah ; line feed FF equ 0Ch ; form feed CR equ 0Dh ; carriage return CtrlS equ 13h ; ^S ; .request zflib,zslib,z3lib,syslib ; extrn flwopen,flwclose,flput ; ZFLIB extrn eatspc,eatnspc,gcomnam,comnam,revcas,hvtinit ; ZSLIB extrn hvdinit,hvon,hvoff extrn z3init,zsyschk,dundr,getcrt,puter2,inverror ; Z3LIB extrn sctlfl,sprint,spstr,sout,scrlf,lcrlf,cout,lout ; SYSLIB extrn condin,cin,safdc,shldc,f$exist,dirq,mulhd,@fncmp extrn f$delete,retud,logud,codend ; jp Start db 'Z3ENV',1 Z3EAdr: dw 0 ; ; Configuration . . . (Bytes must be either 0 or FFh) ; dw 0 ; filler db 'CPD' ; for ZCNFG db Vers/10+'0',Vers mod 10+'0',' ' SysFlg: db 0 ; FFh=include system files BthFlg: db 0 ; FFh=only files in both DU's NotFlg: db 0 ; FFh=only files missing from second DU ArcFlg: db 0 ; FFh=set archive attribute if in both DU's PagFlg: db 0 ; FFh=no screen paging FFFlag: db 0 ; FFh=send final form feed if L option FLDu: db 0 ; FFh=include DU's in file output FLDir: db 0FFh ; FFh=file output to target DU, 0=default DU CasFlg: db 0FFh ; FFh=reverse case of filenames AbtFlg: db 0 ; FFh=invoke error handler on abort ConDiv: db '|' ; character printed between screen columns LstDiv: db '|' ; character printed between printer columns OutFcb: db 0,'CPDLIST ' ; CPDLIST abbreviated FCB ; ; Start of program . . . ; Start: ld hl,(Z3EAdr) ; environment address call zsyschk ; check for ZCPR3 ret nz ; (no, quit) ld (Stack),sp ; save stack pointer ld sp,Stack ; ..and set up new stack call z3init ; initialize environment ld hl,DftNam ; point to default program name call gcomnam ; get real program name ; call codend ; get start of buffer ld (D1Ptr),hl call hvtinit ; initialize terminal ld a,00000001b ; default to console output ld (sctlfl),a ld hl,CpmDma+1 ; point to command tail call eatspc ; anything there? jp z,Usage ; (nope) cp '/' ; help request? jr nz,Start1 ; (nope) inc hl ; a second slash? cp (hl) jp z,Usage ; (yep, help request) ; Start1: call retud ; get default DU ld (DftDU),bc ; ..and store it call GetOpt ; check for options ld a,(CpmFcb+1) ; see if file spec given cp ' ' ; a filename? jr nz,Start2 ; (yes) ld hl,CpmFcb+1 ; no, make it all "?"'s ld a,'?' ld b,11 AmbLp: ld (hl),a inc hl djnz AmbLp ; Start2: ld ix,CpmFcb ; point to file control block call GetDU ; ..and get drive and user ld (D1DU),bc ; store user and drive ld d,b ; save in DE ld e,c ; ld ix,AltFcb ; point to alternate FCB call GetDU ; ..and get drive and user ld (D2DU),bc ; store user and drive ld a,d ; make sure both DU's aren't the same cp b jr nz,Start3 ld a,e cp c jr nz,Start3 call ConOn ; turn console on and print message db 'Duplicate directories.',0 ld a,16 ; error code jp SetErr ; Start3: call PrtHdr ; print header ld bc,(D1DU) ; log into main directory call logud xor a ; null-out the drive spec ld de,CpmFcb ld (de),a ld hl,(D1Ptr) ; point to free buffer space ld a,(OpSFlg) ; non-system and maybe system files call dirq ; load drive 1 directory jp z,TPAOvr ; (DIRQ ran out of memory) ld (D1Ptr),hl ; store drive 1 file list pointer ld (D1Cnt),bc ; ..and file count ld a,b ; any matching files? or c jp z,Finish ; (nope) ld a,(ScrCol) ; set initial screen column count ld (ColCnt),a ld bc,(D2DU) ; log into compared directory call logud ld hl,(D1Cnt) ; calculate second buffer address ld de,16 ; ..first multiply drive 1 file count by 16 call mulhd ld de,(D1Ptr) ; ..then add to drive 1 buffer address add hl,de ld de,CpmFcb ld a,(OpSFlg) ; non-system and maybe system files call dirq ; load drive 2 directory jp z,TPAOvr ; (DIRQ ran out of memory) ld (D2Ptr),hl ; store drive 2 file list pointer ld (D2Cnt),bc ; ..and file count ld a,(OpOFlg) ; file output? or a call nz,OpnFil ; (yes, open it) ld bc,(D1DU) ; log back into primary directory call logud call scrlf ld hl,CpmFcb ; initialize default file control block ld b,36 ; ..for possible attribute setting xor a FillLp: ld (hl),a inc hl djnz FillLp ; MLoop: call condin ; check for key call nz,Abort ; (key pressed, check for ^C) ld hl,(D1Ptr) ; HL = address of next entry inc hl ; HL = address of filename call CmpFn ; compare it to drive 2 filenames jr nz,MLoop1 ; (no match) ; ld a,(OpNFlg) or a jp nz,SkpFil call hvon ; match, use standout call PrtFn ; print filename (in HL) call hvoff ; end standout ld a,'*' ; print asterisk call sout ld hl,(MatCnt) ; increment counter inc hl ld (MatCnt),hl ld a,(OpAFlg) ; set archive attribute? or a jr z,MLoop2 ; (no) ld hl,(D1Ptr) inc hl ; point to filename ld de,CpmFcb+1 ; ..and to file control block push de ld bc,11 ; number of bytes to move ldir pop de dec de ; DE points to file control block ld hl,11 add hl,de ; HL points to T3 set 7,(hl) ld c,SetAtt call Bdos jr MLoop2 ; MLoop1: ld a,0FFh ld (ErCode),a ld a,(OpBFlg) or a jr nz,SkpFil call PrtFn ; no match, print filename ld a,' ' call sout ; print a space ld hl,(MatCnt) inc hl ld (MatCnt),hl ; MLoop2: call ChkCnt ; check file counter jr z,Finish ; (none left) ld hl,ColCnt dec (hl) ; is line full? jr z,MLoop4 ; (yes) ld a,(OpQFlg) or a ld a,(ConDiv) call z,cout ld a,(OpLFlg) or a ld a,(LstDiv) call nz,lout ld a,' ' call sout jr SkpF2 ; ..and get next filename ; MLoop4: call scrlf ; start a new line ld a,(ScrCol) ; reset column count ld (ColCnt),a ld a,(OpPFlg) ; do we page screen? or a jr nz,SkpF2 ; (no, skip this) ld hl,LinCnt ; screen filled? dec (hl) jr nz,SkpF2 ; (no, continue) call sprint db '[more]',0 call cin ; wait for key cp CtrlC ; abort? jp z,Abort1 ; (yes) call sprint db CR,' ',CR,0 ld a,(ScrLns) ; reset line count ld (LinCnt),a jr SkpF2 ; ..and continue ; SkpFil: call ChkCnt ; check file counter jr z,Finish ; (none left) SkpF2: ld hl,(D1Ptr) ; advance file list pointer ld de,16 add hl,de ld (D1Ptr),hl jp MLoop ; Finish: call scrlf call hvon ; turn highlighting on ld hl,(MatCnt) ; number of matching files found call shldc call sprint db ' matching file',0 call PrtEss ; print an "s" if appropriate call sprint db ' found on ',0 ld bc,(D1DU) ; print DU call PrtDU call hvoff ; Exit: ld a,(OpOFlg) ; file output? or a call nz,ClsFil ; (yes, close it) call hvdinit ; de-initialize terminal ld a,(sctlfl) ; printer output? and 80h jr z,Exit1 call lcrlf ld a,(OpFFlg) or a ld a,FF call nz,lout Exit1: ld a,(ErCode) ; set error flag call puter2 ld b,a ; error code to B or a jr z,Exit2 ; (no error) inc a call nz,inverror ; invoke error handler unless code is FFh Exit2: ld sp,(Stack) ; restore stack ret ; return to Z ; ; Various problems end up here . . . ; Pause: call cin ; wait for any key ret ; Abort: cp CtrlS ; ^S jr z,Pause cp CtrlC ; ^C? ret nz ; (no, continue) call scrlf Abort1: call sprint db CR,'Aborted',0 ld a,(AbtFlg) ; check abort error flag and 4 ; set error code accordingly SetErr: ld (ErCode),a jr Exit ; InvDir: call ConOn ; turn console on and print message db 'Invalid directory.',0 ld a,2 ; error code jr SetErr ; InvOpt: call ConOn ; turn console on and print message db 'Invalid option.',0 ld a,19 ; error code jr SetErr ; TPAOvr: call ConOn ; turn console on and print message db 'Out of memory.',0 ld a,12 ; error code jr SetErr ; ; CmpFn -- Compare filename pointed by HL with filename pointed to by ; the address + 1 in D2PTR. Return zero flag set (Z) if match, zero ; flag reset (NZ) if no match. Preserves HL. ; CmpFn: ld de,(D2Ptr) inc de ; DE = address of drive 2 filename ld bc,(D2Cnt) ; BC = file list counter CmpFnL: push hl ; save pointers and counter push de push bc ld b,11 ; compare 11 characters call @fncmp pop bc pop de jr z,CmpFnX ; (match) jr nc,CmpFnN ; (past possible match) dec bc ; decrement counter ld a,b or c jr z,CmpFnN ; (end of file list) ld hl,16 ; point to next drive 2 filename add hl,de ex de,hl ; drive 2 pointer to DE pop hl ; recover drive 1 list pointer jr CmpFnL ; ..and loop ; CmpFnN: or 0FFh ; no match, reset zero flag pop hl ret ; CmpFnX: xor a pop hl ret ; ; ChkCnt -- decrements file list count. Returns zero if no more. ; ChkCnt: ld hl,(D1Cnt) ; decrement file list count dec hl ld (D1Cnt),hl ld a,l or h ret ; ; GetOpt -- initialize defaults and get command line options ; GetOpt: xor a ; initialize to nulls ld hl,MatCnt ; ..matching files counter, error code, ld b,6 ; ..option O, and option Q InitLp: ld (hl),a inc hl djnz InitLp ex de,hl ; DE points to OpBFlg ld hl,BthFlg ; initialize options from defaults ld c,6 ; B is already 0 ldir ld a,(SysFlg) or a ld a,10000000b ; default to excluding system files jr z,GetOp1 or 01000000b ; no, include them also GetOp1: ld (OpSFlg),a call getcrt ld a,(hl) ; get screen columns cp 122 ld a,8 jr nc,SetCol ld a,5 SetCol: ld (ScrCol),a inc hl ld a,(hl) ; get screen lines dec a ; make it one less ld (ScrLns),a ; ..store it dec a ; one less on first screen ld (LinCnt),a ld hl,CpmDma+1 ; point to command tail call eatspc ; skip first token call eatnspc ret z ; end of tail call eatspc ret z cp '/' ; options? jr z,OptLp ; (yes, get them) call eatnspc ; skip second token call eatspc ret z cp '/' ; slash? jr nz,OptLp1 ; (no, get options) OptLp: inc hl ; point to next OptLp1: ld a,(hl) ; get option or a ret z cp 'S' ; system files jr z,OptS cp 'B' ; both directories jr z,OptB cp 'M' ; missing from second jr z,OptN cp 'N' ; not in second jr z,OptN cp 'A' ; set archive bit jr z,OptA cp 'P' ; screen paging jr z,OptP cp 'L' ; printer output jr z,OptL cp 'F' ; form feed jr z,OptF cp 'O' ; disk file output jr z,OptO cp 'Q' ; quiet mode jr z,OptQ cp 'D' ; include DU in output jr z,OptD cp ' ' ; space is okay jp nz,InvOpt ; must be a bad option jr OptLp ; OptS: ld a,(OpSFlg) ; get flag xor 01000000b ; ..flip system file bit ld (OpSFlg),a ; ..and store it jr OptLp ; OptB: ld a,(OpBFlg) ; get flag OptB1: cpl ; ..flip it ld (OpBFlg),a ; ..and store it OptB2: or a ; if we're setting it jr nz,OptN1 ; ..reset option N jr OptLp ; OptN: ld a,(OpNFlg) ; get flag OptN1: cpl ; ..flip it ld (OpNFlg),a ; ..and store it or a ; if we're setting it jr nz,OptB1 ; ..reset option B jr OptLp ; OptA: ld a,(OpAFlg) ; get flag cpl ; ..flip it ld (OpAFlg),a ; ..and store it jr OptB2 ; if we're setting it, reset option M ; OptP: ld a,(OpPFlg) ; get flag cpl ; ..flip it OptP1: ld (OpPFlg),a ; ..and store it jr OptLp ; OptF: ld a,(OpFFlg) ; get flag cpl ; ..flip it ld (OpFFlg),a ; ..and store it jr OptLp ; OptO: ld (OpOFlg),a jr OptLp ; OptL: ld a,(sctlfl) or 10000000b ; set switch control flag for LST echo ld (OpLFlg),a OptL1: ld (sctlfl),a xor a ; ..and reset paging flag jr OptP1 ; OptQ: ld (OpQFlg),a ; set flag ld a,(sctlfl) and 10000000b ; cancel console output jr OptL1 ; OptD: ld a,(OpDFlg) ; include DU in file output cpl ld (OpDFlg),a jp OptLp ; ; PrtHdr -- prints directory header ; PrtHdr: call hvon ; turn on highlighting call sprint db 'Directory of ',0 ld bc,(D1DU) call PrtDU ; first DU ld a,(OpBFlg) or a ld hl,MsgHd3 ; option B header jr nz,PrtHd1 ld a,(OpNFlg) or a ld hl,MsgHd2 ; option M header jr nz,PrtHd1 ld hl,MsgHd1 ; default header PrtHd1: call spstr ld bc,(D2DU) call PrtDU ; second DU jp hvoff ; turn highlighting off and return ; MsgHd1: db ' -- Flagged* files ALSO on ',0 MsgHd2: db ' -- Only files NOT on ',0 MsgHd3: db ' -- Only files ALSO on ',0 ; ; GetDU -- get drive and user for file control block addressed by IX. ; Returns drive in B (A=0), user in C. Uses AF, BC. ; GetDU: ld a,(ix+15) ; check for valid directory or a jp nz,InvDir ; (nope) ld a,(ix+0) ; get drive (A=1) or a ; is there one? call z,GetDft ; (no, get default) dec a ; make A=0 ld b,a ; drive into B ld a,(ix+13) ; get user ld c,a ; put user in C ret ; ; GetDft -- gets default drive and user. Increments drive to A=1. ; GetDft: call retud ; C=user, B=drive (A=0) ld a,b ; A=drive (A=0) inc a ; A=drive (A=1) ret ; ; PrtDU -- Prints DU and directory name. Expects drive in B, user in C. ; PrtDU: ld a,b add a,'A' call sout ; print drive ld a,c call safdc ; print user ld a,':' call sout inc b ; make drive A=1 call dundr ; get directory name ret z ; (none) inc hl inc hl ld b,8 PNdrLp: ld a,(hl) cp ' ' ret z call sout inc hl djnz PNdrLp ret ; ; PrtFn -- Print filename pointed to by HL. Printing in normal or reverse ; case are in separate loops for speed. ; PrtFn: ld a,(OpOFlg) ; file output? or a ld bc,(D1DU) ; load DU ld a,(OpDFlg) ; load DU option flag call nz,flput ; (yes, do that first) ld b,8 ; first print the filename ld a,(CasFlg) ; do we reverse the case? or a jr nz,PrtFn1 ; (yes) call PFnLp ; no, use it as is jr PrtFn2 ; PrtFn1: call RFnLp ; PrtFn2: ld a,'.' ; now print the filetype call sout ld b,3 ld a,(CasFlg) ; do we reverse the case? or a jr nz,RFnLp ; (yep) ; PFnLp: ld a,(hl) ; get character and 7Fh ; reset high bit call sout inc hl ; increment pointer djnz PFnLp ; loop for length of filename ret ; RFnLp: ld a,(hl) ; get character call revcas ; reset high bit and reverse case call sout inc hl ; increment pointer djnz RFnLp ; loop for length of filename ret ; ; OpnFil -- Open disk output file. ; OpnFil: ld bc,(D1DU) ; assume logging target directory ld a,(FLDir) ; default or target directory? or a jr nz,OpnFl1 ; (target directory) ld bc,(DftDU) ; log into default directory OpnFl1: call logud ld de,OutFcb call flwopen ; open file jr nz,FilErr ; (error) ret ; ; ClsFil -- Close disk output file. ; ClsFil: call flwclose jr nz,FilErr ret ; FilErr: call ConOn ; turn console on and print message db CR,LF,'Output file error.',0 ld a,11 ; error code jp SetErr ; ; ConOn -- turn console on unconditionally and print null-terminated ; message addressed by stack. ; ConOn: ld a,(sctlfl) or 00000001b ld (sctlfl),a jp sprint ; print message and return to caller ; ; PrtEss -- checks if HL is exactly 1. If not, prints an "s". ; Uses: AF, HL ; PrtEss: dec hl ; 1 becomes 0 ld a,h or l ld a,'s' call nz,sout ; (if it's not 1, print "s") ret ; ; Usage -- displays brief usage summary, checking configuration flags ; for defaults. ; Usage: call sprint DftNam: db 'CPD Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers,CR,LF db 'Usage:',CR,LF db ' ',0 ld hl,comnam ; print invocation name call spstr call sprint db ' {dir:}{afn} {dir:} {{/}options}',CR,LF db 'Displays files in first directory,',CR,LF db 'flagging files duplicated in second directory.',CR,LF db 'Options:',CR,LF db ' B ',0 ld a,(BthFlg) or a call nz,PrtDnt call sprint db 'show only files in both directories',CR,LF db ' N ',0 ld a,(NotFlg) or a call nz,PrtDnt call sprint db 'show only files not in second directory',CR,LF db ' A ',0 ld a,(ArcFlg) or a call nz,PrtDnt call sprint db 'set archive attribute if in both directories',CR,LF db ' S ',0 ld a,(SysFlg) or a call nz,PrtDnt call sprint db 'include system files',CR,LF db ' P ',0 ld a,(PagFlg) or a call z,PrtDnt call sprint db 'page display',CR,LF db ' O output to file CPDLIST',CR,LF db ' D ',0 ld a,(FLDu) or a call nz,PrtDnt call sprint db 'include DU''s in CPDLIST',CR,LF db ' L echo to printer',CR,LF db ' F ',0 ld a,(FFFlag) or a call nz,PrtDnt call sprint db 'send final form feed',CR,LF db ' Q quiet mode',CR,LF db 'Options B and N are mutually exclusive.',0 jp Exit ; ..and quit ; PrtDnt: call sprint db 'don''t ',0 ret ; ; Uninitialized data . . . ; DSEG ; ScrCol: ds 1 ; number of display columns (5 or 8) ScrLns: ds 1 ; number of screen lines MatCnt: ds 2 ; matching files counter ErCode: ds 1 ; error code OpLFlg: ds 1 ; non-zero=echo to printer OpOFlg: ds 1 ; non-zero=disk file output OpQFlg: ds 1 ; non=zero=quiet mode OpBFlg: ds 1 ; FFh=display if in both DU's OpNFlg: ds 1 ; FFh=display if not in second DU OpAFlg: ds 1 ; FFh=set archive bit if in both DU's OpPFlg: ds 1 ; FFh=no screen paging OpFFlg: ds 1 ; FFh=final form feed to printer OpDFlg: ds 1 ; FFh=include DU in output file OpSFlg: ds 1 ; FFh=include system files DftDU: ds 2 ; default user and drive numbers D1DU: ds 2 ; drive 1 user and drive numbers D1Cnt: ds 2 ; drive 1 file list counter D1Ptr: ds 2 ; drive 1 file list pointer D2DU: ds 2 ; drive 2 user and drive numbers D2Cnt: ds 2 ; drive 2 file list counter D2Ptr: ds 2 ; drive 2 file list pointer ColCnt: ds 1 ; files per line counter LinCnt: ds 1 ; screen lines counter ds 64 ; stack Stack: ds 2 ; stack pointer storage ; end