; SAPZ.MAC ; ; Sorts And packs directory. For ZCPR3 only. ; Vers equ 11 ; version SubVers equ ' ' ; revision level ; ; This program reads the disk directory tracks, sorts them alphabetically, ; erases the tracks with E5's, and then rewrites them. Sorting the ; directory in this manner offers several advantages: ; (1) it allows 'DIR' to show an alphabetized listing ; (2) it minimizes problems when using UNERASE ; (3) it speeds up access by directory programs ; (4) it assists on working directly on the disk with 'DU', etc. ; (5) it prevents somebody else from reading files you erased ; Optionally, all null (zero-length) files can be erased, except disk ; labels starting with a special tag character. ; ; USAGE: ; ; SAPZ dir: ; ; Either a DU or a DIR specification must be used; the default drive ; is not assumed. Only the drive is significant. ; ; General equates . . . ; BDOS EQU 0005H CR EQU 0DH LF EQU 0AH BS EQU 08H BEL EQU 07H DPBLEN EQU 15 ; size of disk parameter block TBUFF EQU 80H FCB EQU 5CH ; ; BDOS functions . . . ; VERNO EQU 12 ; get version RESET EQU 13 ; reset all drives SELDRV EQU 14 ; select drive OPEN EQU 15 CLOSE EQU 16 READFN EQU 20 WRITFN EQU 21 GETDSK EQU 25 ; get disk DMAFN EQU 26 ATTFN EQU 30 USERFN EQU 32 ; get/set user SRESET EQU 37 ; reset specific drives DOSTYP EQU 48 ; get enhanced DOS type ; .request Z3LIB,SYSLIB ; ext cout,eprint,epstr,codend ext z3init,zsyschk,prtname ; public print ; for zsyschk, prtname ; MACLIB Z80 ; extended Intel mnemonics ; jmp Start ; db 'Z3ENV' db 1 Z3EAdr: dw 0FE00h ; ; Configuration bytes . . . ; dw 0 ; filler db 'SAPZ',Vers/10+'0',Vers mod 10+'0',' ' NulFlg: db 0 ; non-zero=erase null files, 0=don't erase TagChr: db 0 ; tag character for disk labels, 0=none ; ; Start of program . . . ; ; Obtain BIOS vectors Start: lhld Z3EAdr ; set up environment call zsyschk ; is this a Z-system rnz ; (nope) call z3init lxi d,WBOOT lhld 0001h ; Get BIOS address lxi b,48 ; size of vector table ldir ; move it ; LDA FCB+1 CPI ' ' ; anything in FCB? jrnz Start1 ; (nope) lda FCB ; check for drive ora a jrnz Start2 ; (it's there) ; Start1: call eprint db 'SAP-Z Version ' db Vers/10+'0','.',Vers mod 10+'0',SubVers,CR,LF db 'Sorts and packs directory.',CR,LF db 'Usage:',CR,LF,' ',0 call prtname call eprint db ' dir:',CR,LF db 'Only drive is significant.',0 ret ; Start2: lxi h,Stack sphl ; set up stack call codend ; set up buffers shld Buffer mvi c,VERNO ; check for CP/M 2.2 call BDOS mov a,l ; HL = 0022h if CP/M 2.2 cpi 22h+1 ; check for MP/M or CP/M 3.0 jnc CPM3 ; exit if CP/M 3.0, we can't use it sta VERFLG ; store the version ; ; Main loop . . . ; SAP: CALL SETUP CALL TSTWRT CALL RDDIR CALL CLEAN CALL SORT CALL WRDIR ; Write directory and DateStamper file call Relog ; relog drive(s) call eprint db 'Done',0 ; EXIT: LDA ODISK ; Restore login status MOV E,A MVI C,SELDRV ; Sets BIOS drive too CALL BDOS LDA OUSER MOV E,A MVI C,USERFN CALL BDOS RST 0 ; Warm boot - required after ; Change in directory checksum ; ; Initialization . . . ; ; Setup for selecting drive and loading disk parameter block ; SETUP: XRA A STA CLNFLG MVI C,USERFN ; Save original drive and user number MVI E,0FFH CALL BDOS STA OUSER MVI C,GETDSK CALL BDOS STA ODISK STA CURDSK ; Gets selected drive SETUP1: LDA FCB ; See if any drive was requsted DCR A ; Change to DRI's drive requirement STA CURDSK ; Store for current disk ; LOGIT: MOV E,A ; Log in designated drive thru BDOS MVI C,SELDRV CALL BDOS MVI E,0 ; Set user 0 MVI C,USERFN CALL BDOS LDA CURDSK ; BIOS call to get DPH to HL MOV C,A CALL SELDSK CALL CPM22 LHLD DRM ; Number of directory entries INX H ; Relative to 1 SHLD SCOUNT PUSH H DAD H ; Allocate 2*#dir entries lded Buffer ; For pointer words DAD D SHLD BUFBAS POP H PUSH H CALL ROTRHL ; Divide by 4 CALL ROTRHL ; To get record count SHLD DIRLEN CALL ROTRHL ; And by 8 for time&date SHLD TDCNT ; Check for sufficient memory POP H ; # entries *32 DAD H ; x2 DAD H ; x4 DAD H ; x8 DAD H ; x16 DAD H ; x32 XCHG LHLD BUFBAS ; + BUFBASE DAD D XCHG LHLD 6 ; - available TPA CALL SUBDE RNC CALL eprint DB CR,LF,BEL,'Not enough memory!',0 JMP EXIT ; ; CP/M 2.2 routine ; CPM22: MOV E,M INX H MOV D,M INX H XCHG SHLD RECTBL XCHG LXI D,8 ; Offset to DPB within header DAD D ; Returned by SELDSK in CP/M 2.2 MOV A,M ; Get adrress of DPB INX H MOV H,M MOV L,A LXI D,DPB ; Point to destestination: our DPB lxi B,DPBLEN ldir ; move it ret ; ; Read and write first directory record to ensure writable disk ; TSTWRT: MVI C,RESET CALL BDOS CALL SETCUR LHLD SYSTRK CALL DOTRAK LXI H,1 CALL DOREC LXI H,TBUFF MOV B,H MOV C,L CALL SETDMA CALL READ ORA A jrnz RTERR MVI C,1 ; Directory write forces flush CALL WRITE ORA A jrnz WTERR CALL CKTD ; See if DateStamper(TM) file is on disk RET ; WTERR: CALL eprint DB CR,LF,BEL,'Can''t write disk -- write-protect tab?',0 JMP EXIT ; RTERR: CALL eprint DB CR,LF,BEL,'Can''t read disk!',0 JMP EXIT ; ; Read and write directory ; ; Write directory WRDIR: LDA NOSWAP ORA A jrnz WRDIR1 CALL eprint DB '(Previously sorted) - ',0 LDA CLNFLG ; If in sorted order ORA A ; And no erasures RZ ; We're all done ; WRDIR1: CALL eprint DB 'Writing, ',0 WRDIR2: CALL DMA80 ; Set default DMA LHLD DIRLEN SHLD DIRCNT lhld Buffer ; Set initial pointer SHLD PTR MVI A,1 ; Flag write operation CALL DODIR CALL DODATE ; Then update the DateStamper(TM) file RET ; ; Read directory, get current drive to include in display ; RDDIR: MVI C,GETDSK ; Get the current disk drive CALL BDOS ADI 'A' ; Convert to ASCII STA RDDIR1 CALL eprint DB ' Sorting and packing drive ' RDDIR1: DB ' :',CR,LF,' --> Reading, ',0 LHLD DIRLEN SHLD DIRCNT LHLD BUFBAS SHLD ADDR ; For read DMA address lhld Buffer SHLD PTR MVI A,0 ; READFLG DODIR: STA WRFLAG LHLD SYSTRK CALL DOTRAK ; Set the track LXI H,0 SHLD RECORD DLOOP: LHLD RECORD ; Get records per track INX H XCHG LHLD SPT ; Current record CALL SUBDE ; Record - SPT XCHG jrnc NOTROV ; Track overflow, bump to next LHLD TRACK INX H CALL DOTRAK LXI H,1 ; Rewind record number NOTROV: CALL DOREC ; Set current record LDA WRFLAG ; Time to figure out ORA A ; If we are reading jrnz DWRT ; Or writing ; ; Reading LHLD ADDR MOV B,H ; Set up DMA address MOV C,L CALL SETDMA CALL READ ORA A ; Test flags on read jnz RERROR ; NZ=error LHLD ADDR MVI B,4 ; Install pointers for 4 entries in this XCHG ; record. LHLD PTR PLP: MOV M,E INX H MOV M,D INX H PUSH H LXI H,32 DAD D XCHG POP H DCR B jrnz PLP SHLD PTR XCHG SHLD ADDR ; New DMA ; Common Read/write code MORE: LHLD DIRCNT ; Countdown entries DCX H SHLD DIRCNT MOV A,H ; Test for zero left ORA L jrnz DLOOP ; Loop till zero ; Directory I/O done, reset DMA address DMA80: LXI B,TBUFF JMP SETDMA ; ; Write-directory code ; DWRT: MVI B,4 LXI D,TBUFF DWRT1: PUSH B ; Copy 4 sorted entries to buffer CALL NXTENT lxi b,32 ldir ; move it POP B DCR B jrnz DWRT1 MVI C,0 ; Write allocated... LHLD DIRCNT DCX H MOV A,H ORA L jrnz DWRT3 ; Unless it's the last record MVI C,1 ; Which must be flushed DWRT3: CALL WRITE ORA A jnz WERROR jr MORE ; ; Return HL = pointer to next sorted entry ; NXTENT: PUSH D LHLD PTR MOV E,M INX H MOV D,M INX H SHLD PTR XCHG POP D RET ; ; Track and record update routines ; DOTRAK: SHLD TRACK MOV B,H MOV C,L jmp SETTRK ; DOREC: SHLD RECORD MOV B,H MOV C,L LHLD RECTBL XCHG DCX B CALL RECTRN MOV B,H MOV C,L LDA VERFLG ORA A RZ jmp SETREC ; ; Clean out erased and temporary ($$$) entries, and any zero-length files, ; if so configured. Preserve tagged (TagChr) zero-length disk labels. ; CLEAN: LXI H,0 ; IND = 0 CLNLOP: SHLD IND CALL INDEX ; HL = BUF + 32 * IND MOV A,M ; Jump if this is a deleted file CPI 0E5H jrz FILLE5 MOV B,H ; Save index in BC MOV C,L LXI D,9 ; If filetype is '$$$' DAD D MVI A,'$' CMP M jrnz CLN1 INX H CMP M jrnz CLN1 INX H CMP M jrz FILLE5 ; Erase it ; CLN1: LXI H,12 DAD B MOV A,M ; Check extent field ORA A jrnz CLBUMP ; Skip if not extent 0 INX H ; Point to record count field INX H MOV A,M ; Get S2 byte (extended RC) ANI 0FH ; For CP/M 2.2 MOV E,A INX H MOV A,M ; Check record count field ORA E jrnz CLBUMP ; Jump if non-zero ; LDA NulFlg ; Erase 0-length files? ORA A jrz CLBUMP ; Zero does not erase so exit ; LHLD IND ; Clear all 32 bytes of CALL INDEX ; Directory entry to E5 INX H ; point to first character of filename lda TagChr ; get tag character ora a ; is there one? jrz FILLE5 ; (no, so forget it) cmp m ; disk label? jrz CLBUMP ; (yes, don't erase) ; FILLE5: LHLD IND ; Recompute entry address of this file CALL INDEX MVI C,32 ; Number of bytes to clear MVI A,0E5H ; Fill with E5's FILLE6: CMP M jrnz FILLE7 INX H DCR C jrnz FILLE6 jr CLBUMP ; Already clean ; FILLE7: STA CLNFLG FILLOP: MOV M,A ; Make it all E5's INX H DCR C jrnz FILLOP ; CLBUMP: LHLD DRM ; Get count of filenames INX H XCHG LHLD IND ; Our current count INX H PUSH H CALL SUBDE ; Subtract POP H jrc CLNLOP ; Loop till all cleaned RET ; ; Type 'FILENAME.TYP' at (HL) ; FNFT: MVI B,8 CALL TYPEFN MVI A,'.' CALL cout MVI B,3 TYPEFN: PUSH B MOV A,M CALL cout INX H POP B DCR B jrnz TYPEFN RET ; INDEX: DAD H ; x2 for *32 DAD H ; x4 DAD H ; x8 DAD H ; x16 DAD H ; x32 XCHG LHLD BUFBAS DAD D RET ; ; Sort directory. This sort routine is adapted from SOFTWARE TOOLS by ; Kernigan and Plaugher. Routine extracted from SD. ; SORT: XRA A STA NOSWAP ; Zero the flag in case already sorted CALL eprint DB 'Sorting, ',0 LHLD SCOUNT ; Number of entries LDA TDFLAG ORA A jrz L0 DCX H ; Skip past TIME&DAT entry SHLD SCOUNT L0: ORA A ; Clear carry MOV A,H ; GAP=GAP/2 RAR MOV H,A MOV A,L RAR MOV L,A ORA H ; Is it zero? RZ ; Then none left MOV A,L ; Make GAP odd ORI 1 MOV L,A SHLD GAP INX H ; IIN=GAP+1 L2: SHLD IND XCHG LHLD GAP MOV A,E ; JND=IND-GAP SUB L MOV L,A MOV A,D SBB H MOV H,A L3: SHLD JND XCHG LHLD GAP ; JG=JND+GAP DAD D SHLD JG CALL COMPAR ; Compare (JND) and (JG) L3A: JP L5 ; If A(JND)<=A(JG) LHLD JND XCHG LHLD JG CALL SWAP ; Exchange A(JND) and A(JG) LHLD JND ; JND=JND-GAP XCHG LHLD GAP MOV A,E SUB L MOV L,A MOV A,D SBB H MOV H,A JM L5 ; If JND>0 GOTO L3 ORA L ; Check for zero jrnz L3 ; * shortened L5: LHLD SCOUNT ; For later XCHG LHLD IND ; IND=IND+1 INX H MOV A,E ; If IND<=N GOTO L2 SUB L MOV A,D SBB H JP L2 LHLD GAP jr L0 ; ; Returns SIGNED comparison ; COMPAR: CALL GETBAS DAD H ; *2 DAD B ; +base XCHG ; 1st pointer to DE temporarily DAD H DAD B XCHG ; 2nd pointer now in DE, first in HL MOV C,M ; Put 1st pointer in BC INX H MOV B,M XCHG ; 2nd pointer now in HL, first in BC MOV E,M INX H MOV D,M XCHG ; Should be 1+11+ext+s2, sort by USERNO, NAME,TYPE, EXTENT and S2 byte MVI E,12 ; Will do S2 independently, making 13 COMPBH: MOV A,M ; 7-bit signed compare of (BC), (HL) ANI 7FH ; Strip high bit MOV D,A LDAX B ANI 7FH ; Strip high bit CMP D INX B INX H RNZ DCR E jrnz COMPBH ; User number file name and file type are equal, now check S2 byte for ; any files in excess of 512k INX B INX H INX B INX H MOV A,M ; 4-bit signed compare of (BC), (HL) ANI 0FH ; Strip all but low order nibble MOV D,A LDAX B ANI 0FH ; Strip all but low order nibble CMP D RNZ ; S2 byte is equal, now go back to extent DCX B DCX H DCX B DCX H MOV A,M ; 7-bit signed compare of (BC), (HL) ANI 7FH ; Strip any high bits set MOV D,A LDAX B ANI 7FH ; Strip any high bits set CMP D RET ; ; Swap entries in the order table ; SWAP: MVI A,0FFH STA NOSWAP CALL GETBAS DAD H ; *2 DAD B ; + base XCHG DAD H ; *2 DAD B ; + base MOV C,M LDAX D XCHG MOV M,C STAX D INX H INX D MOV C,M LDAX D XCHG MOV M,C STAX D RET ; GETBAS: lbcd Buffer ; if TIME&DAT file dcx b dcx b LDA TDFLAG ORA A RZ INX B ; Start at 2nd entry INX B RET ; ; DateStamper support code . . . ; (1) checks for presence of DateStamper file ; (2) re-writes time and date entries in sorted order corresponding ; to the new directory order ; ; Check 1st directory entry for the DateStamper file CKTD: LXI H,TDNAM0 ; User # 0 too MVI B,12 PUSH H PUSH B LXI D,TDFCB ; Initialize USERNO.NAME in FCB now lxi b,12 ldir XRA A MVI B,36-12 ZLP: STAX D INX D DCR B jrnz ZLP POP B POP H LXI D,TBUFF ; See if it's the time&dat file CALL MATCH7 jrnz NOTD MVI A,0FFH jr SETTD ; NOTD: XRA A SETTD: STA TDFLAG ; Set flag if special file present RET ; ; Rewrite the TIME&DAT file in sorted order ; (1) read the file to (bufbase) ; (2) use pointers to index to each 16-byte entry ; (3) write new records ; DODATE: LDA TDFLAG ORA A RZ ; No TIME&DAT file ; call eprint db 'Saving Dates, ',0 MVI C,RESET ; Directory has been changed CALL BDOS ; Force new checksum in BDOS CALL SETCUR ; open file to get all attributes, and reset read-only bit LXI D,TDFCB PUSH D MVI C,OPEN CALL BDOS INR A POP D jz TDOERR LXI H,TDFCB+9 ; Set file R/W MOV A,M ANI 7FH MOV M,A MVI C,ATTFN CALL BDOS DOD1: MVI B,0 ; Record counter LHLD BUFBAS TDRLP: XCHG PUSH D PUSH B MVI C,DMAFN CALL BDOS LXI D,TDFCB MVI C,READFN CALL BDOS ORA A POP B POP D jrnz RDDONE INR B LXI H,80H DAD D jr TDRLP ; RDDONE: LHLD BUFBAS ; Check the checksum for all records CKLP: PUSH B CALL CKSUM CMP M INX H POP B jrz SOK CALL eprint DB CR,LF,BEL,'Checksum error in original ' DB '"!!!TIME&.DAT" file -- proceeding',CR,LF,0 SOK: DCR B jrnz CKLP ; Initialize for writing XRA A STA TDFCB+12 ; Extent STA TDFCB+32 ; Currebt record CALL DMA80 lhld Buffer ; Initialize pointer SHLD PTR LHLD TDCNT WTLP1: PUSH H ; Copy 8 TIME&DAT entries to TBUFF LXI D,TBUFF MVI B,8 WTLP2: PUSH B ; +1 PUSH D ; +2 LHLD PTR ; Get pointer to next entry MOV E,M INX H MOV D,M INX H SHLD PTR ; Save next pointer ; DateStamper entries are 16 bytes LHLD BUFBAS ; Get: BUFBASE + [(PTR)-BUFBASE]/2 PUSH H XCHG CALL SUBDE ; (PTR)-BUFBASE CALL ROTRHL ; /2 POP D ; + BUFBASE DAD D ; POP D ; move it to tbuff lxi b,16 ldir ; DE points to next slot in tbuff POP B ; +0 DCR B jrnz WTLP2 LXI H,TBUFF ; Update the record's checksum byte CALL CKSUM MOV M,A LXI D,TDFCB ; Write the record MVI C,WRITFN DBUG: CALL BDOS ORA A POP H jrnz TDWERR DCX H ; Count down MOV A,H ORA L jrnz WTLP1 LXI D,TDFCB ; Close TIME&DAT file PUSH D MVI C,CLOSE CALL BDOS POP D INR A jrz TDCERR LXI H,TDFCB+9 ; Return file to R/O status MOV A,M ORI 80H MOV M,A MVI C,ATTFN JMP BDOS ; ; Checksum 1st 127 bytes at (HL) ; CKSUM: MVI B,127 XRA A CKSU1: ADD M INX H DCR B jrnz CKSU1 RET ; TDNAM0: DB 0,'!!!TIME&DAT' ; TDOERR: CALL eprint DB CR,LF,'Can''t open ',0 FNERR: CALL eprint DB '"!!!TIME&.DAT" file!',BEL,CR,LF,0 RET ; TDWERR: CALL eprint DB CR,LF,'Write error ',0 jr FNERR ; TDCERR: CALL eprint DB CR,LF,'Close error ',0 jr FNERR ; ; Miscellaneous support routines . . . ; SETCUR: LDA CURDSK MOV E,A ; Put drive back MVI C,SELDRV JMP BDOS ; ; Compare B bytes at DE and HL (without attributes ) ; MATCH7: LDAX D XRA M ANI 7FH ; Ignore attributes RNZ INX H INX D DCR B jrnz MATCH7 RET ; ; Utility subtraction subroutine...HL = HL-DE ; SUBDE: MOV A,L SUB E MOV L,A MOV A,H SBB D MOV H,A RET ; ; Divide HL by 2 ; ROTRHL: ORA A ; Clear carry MOV A,H RAR MOV H,A MOV A,L RAR MOV L,A RET ; ; Come here if we get a read error ; RERROR: CALL eprint DB CR,LF,BEL,'=> READ ERROR - NO CHANGE made',0 JMP EXIT ; ; Come here if we get a write error ; WERROR: CALL eprint db CR,LF,BEL,'=> WRITE ERROR - ' db 'directory left in UNKNOWN condition',0 JMP EXIT ; ; CP/M 3.0 not allowed by this program ; CPM3: call eprint db BEL,'Not for Z3Plus',0 rst 0 ; Warm boot ; ; This jmp prevents linking of PRINT, PSTR, CCOUT, and SATCCC. ; print: jmp eprint ; ; Resets entire disk system, except resets only specific drive under ; ZSDOS and ZDDOS using function 37. ; Relog: call eprint db 'Relogging, ',0 mvi c,DOSTYP call BDOS ; ZRDOS or CP/M? mov a,h ora a jrnz Relog0 ; (no, assume it's ZSDOS) lxi d,0FFFFh ; take no chances, reset all drives mvi c,SRESET call BDOS mvi c,RESET ; both ways jmp BDOS ; ..and return to caller ; ; Reset specific drive Relog0: lda CURDSK ; get current drive (A=0) inr a ; make A=1 lxi h,1 ; set bit 0 for drive A Relog1: dcr a jrz Relog2 ; (done) dad h ; shift vector to next bit jr Relog1 ; Relog2: xchg ; move vector to DE mvi c,SRESET jmp BDOS ; reset specific drive and return ; DSEG ; ; Uninitialized data ; ADDR: DS 2 DIRLEN: DS 2 DIRCNT: DS 2 IND: DS 2 JND: DS 2 GAP: DS 2 JG: DS 2 RECTBL: DS 2 RECORD: DS 2 TRACK: DS 2 TDCNT: DS 2 NOSWAP: DS 1 VERFLG: DS 1 WRFLAG: DS 1 TDFLAG: DS 1 CLNFLG: DS 1 ; ; Disk parameter block ; DPB: SPT: DS 2 BSH: DS 1 BLM: DS 1 EXM: DS 1 DSM: DS 2 DRM: DS 2 AL0: DS 1 AL1: DS 1 CKS: DS 2 SYSTRK: DS 2 CURDSK: DS 1 ODISK: DS 1 OUSER: DS 1 BUFBAS: DS 2 PTR: DS 2 SCOUNT: DS 2 Buffer: ds 2 TDFCB: DS 36 ; DateStamper file control block ; ; BIOS jump vector storage ; WBOOT: ds 3 CSTS: ds 3 CI: ds 3 CO: ds 3 LO: ds 3 PO: ds 3 RI: ds 3 HOME: ds 3 SELDSK: ds 3 SETTRK: ds 3 SETREC: ds 3 SETDMA: ds 3 READ: ds 3 WRITE: ds 3 LSTS: ds 3 RECTRN: ds 3 ; ds 40 ; Minimum stack depth Stack: ds 0 ; END