; PPIP-3.Z80 ; <<3>> Disk I/O module ; This module handles disk input/output operations, including file open, ; read, write, close, etc. ;----------------------------------------------------------------------- ; Copy a file ; ; Entry: HL = source FCB ; DE = destination FCB (if the destination file exists it should ; be deleted BEFORE this routine is called). ; Return: HL and DE preserved ; CARRY SET if error FCOPY: XOR A ; Get a zero in accumulator LD (EOFLAG),A ; And turn off the end-of-file-flag LD (CRCVAL),A ; Also, set crc to zero LD (CRCVAL+1),A EX DE,HL ; Get source FCB in DE CALL FOPEN ; Open source file for READ JP NC,FCPY1 ; Continue if open ok LD HL,0 ; Else error CALL ERRET DB 'can''t open source',0 FCPY1: IF Z80DOS CALL GETSTMP ; Get time stamp of source file ENDIF ; Z80DOS IF DATESTAMP PUSH HL LD HL,(TDSECTOR) ; Save source sector number LD (SSECTOR),HL POP HL LD A,(TDINDEX) ; Save source directory index LD (SINDEX),A ENDIF ; DATESTAMP EX DE,HL ; Get dest FCB in DE CALL NEWFILE ; Create the destination file for WRITE RET C ; Return if we can't make the file FCPY2: IF DATESTAMP PUSH HL LD HL,(TDSECTOR) ; Save destination sector number LD (DSECTOR),HL POP HL LD A,(TDINDEX) ; Save destination directory index LD (DINDEX),A ENDIF ; DATESTAMP EX DE,HL ; HL = dest, DE = source PUSH HL ; Save dest. FCB on stack LD HL,(BUFRECS) ; Get number of records to read LD B,H ; Into BC LD C,L LD HL,(IOBUF) ; And buffer address in HL CALL BLKRD ; Read a buffer-full JP NC,FCPY3 ; Move on if NOT EOF LD A,(EOFLAG) ; Else switch on the CPL ; Flag LD (EOFLAG),A FCPY3: LD B,H ; Get number of records to write LD C,L ; Into BC POP HL ; Get dest FCB back EX DE,HL ; Now HL = source, DE = dest PUSH HL ; Save source FCB on stack LD HL,(IOBUF) ; Get buffer address in HL CALL BLKWRT ; And write the block POP HL ; HL = source, DE = dest JP NC,FCPY4 ; Continue if no error CALL FDELETE ; Delete the dest. file we just made LD HL,0 CALL ERRET ; Issue error message and quit DB 'Disk full. Copy deleted.',0 FCPY4: LD A,(EOFLAG) ; Now check to see if end of file OR A JP Z,FCPY2 ; Loop if not IF Z80DOS CALL USESTMP ENDIF ; Z80DOS CALL FCLOSE ; Close dest. file RET NC ; And return if ok LD HL,0 ; Else error CALL ERRET DB 'can''t close destination',0 ;----------------------------------------------------------------------- ; Write a block of records to a file ; ; Entry: DE points to FCB for file to write. ; BC = number of records to write ; HL = starting address of memory block ; file must have already been opened ; Return: HL = count of records actually written ; BC -= records written, DE preserved, PSW lost ; CARRY SET if file write error BLKWRT: CALL FSETUSR ; Set to correct user PUSH HL ; Save buffer address LD HL,0 ; Initialize record count BLKW1: EX (SP),HL ; Now HL = addr., stack = record count LD A,B ; Test record count OR C JP Z,BLKWDUN ; And return if zero CALL FACCESS ; Set DMA address CALL FWRITE ; Write one record JP C,BLKWDUN ; Return now if error PUSH DE ; Save reg. LD DE,RECSZ ; So we can use it to bump DMA address ADD HL,DE ; By one record POP DE ; Then restore reg. EX (SP),HL ; Retrieve rec. count INC HL ; And update it DEC BC ; Decrement number of records to write JP BLKW1 ; And loop BLKWDUN: POP HL ; Get record count in HL RET ; And return ;----------------------------------------------------------------------- ; Read a block of records from a file with CRC ; ; Entry: DE points to FCB for file to read. ; BC = number of records to read ; HL = starting address of memory block ; file must have already been opened ; Return: HL = count of records actually read ; BC -= records read, DE preserved, PSW lost ; CRCVAL (globally defined) is updated ; CARRY SET if End-Of-File BLKRD: CALL FSETUSR ; Set user number PUSH HL ; Save buffer address LD HL,0 ; Initialize record count BLKRD1: EX (SP),HL ; Now HL = addr., stack = record count LD A,B ; Test record count OR C JP Z,BLKRDUN ; And return if zero CALL FACCESS ; Set DMA address CALL FREAD ; Read one record JP C,BLKRDUN ; Return now if error LD A,(VERIFY) ; Is CRC verification being done? CP ON CALL Z,RECRC ; YES - do crc check on entire record PUSH DE ; Save reg. LD DE,RECSZ ; So we can use it to bump DMA address ADD HL,DE ; By one record POP DE ; Then restore reg. EX (SP),HL ; Retrieve rec. count INC HL ; And update it DEC BC ; Decrement number of records to read JP BLKRD1 ; And loop BLKRDUN: POP HL ; Get record count in HL RET ; And return ;----------------------------------------------------------------------- ; Open a file ; Entry: DE = pointer to completed FCB for file to open (FCB[0-1] = user). ; Returns: all regs. except PSW preserved ; CARRY SET if error FOPEN: PUSH BC ; Save all PUSH DE PUSH HL CALL ZEROFCB ; Zero the extent and current record CALL FSETUSR ; Set user LD C,OPEN ; Do CP/M function call CALL BDOS IF DATESTAMP LD (TDSECTOR),DE ; Save sector number returned by DateStamper LD (TDINDEX),A ; Save offset in directory entry ENDIF ; DATESTAMP ADD A,1 ; From BDOS, Acc. = 0FFh if ERROR POP HL ; Restore all POP DE POP BC RET ;----------------------------------------------------------------------- ; Set CP/M Direct Memory Access address ; Entry: HL = the address of the next record to be read/written ; Return: none ; all registers except PSW preserved FACCESS: PUSH BC ; Save all PUSH DE PUSH HL EX DE,HL ; Put DMA address in DE LD C,SETDMA ; Do CP/M function to CALL BDOS ; Set the DMA address to our buffer POP HL ; Restore POP DE POP BC RET ;----------------------------------------------------------------------- ; Sequential WRITE of one record to a file ; Entry: DE = pointer to completed FCB for file to write ; Current DMA address holds the record to be written ; Return: all but PSW are preserved ; CARRY SET if error FWRITE: PUSH BC ; Save old stuff PUSH DE PUSH HL IF Z80DOS CALL USESTMP ENDIF ; Z80DOS LD C,WRITE CALL BDOS ADD A,0FFH ; From BDOS, Acc. = 00h if OK POP HL ; Restore POP DE POP BC RET ;----------------------------------------------------------------------- ; Sequential READ of one record from a file ; Entry: DE points to FCB ; Return: next record from file is in the current DMA buffer or ; CARRY SET if PHYSICAL end-of-file ; all but PSW are preserved FREAD: PUSH BC ; Save old regs. PUSH DE PUSH HL LD C,READ ; Do CP/M function CALL BDOS ADD A,0FFH ; From BDOS, Acc. = 00h if OK POP HL ; Restore POP DE POP BC RET ;----------------------------------------------------------------------- ; Close a file ; Entry: DE points to FCB of file to close. ; Return: CARRY SET if error ; all regs. except PSW preserved FCLOSE: PUSH BC ; Save all PUSH DE PUSH HL LD C,CLOSE ; Do CP/M function CALL BDOS ADD A,1 ; From BDOS, Acc. = 0FFh if ERROR POP HL ; Restore all POP DE POP BC RET ;----------------------------------------------------------------------- ; Create a new file. Includes user inquiry and Read-Only check. ; Entry: DE points to FCB for file ; EMEND and WIPE options should be pre-set. ; Return: EXISTS = non-zero if file exists ; RO = non-zero if file is read-only ; CARRY SET if new file cannot be created. ; all except PSW are preserved unless error NEWFILE: XOR A ; Zero Acc. LD (EXISTS),A ; Initialize exists-flag to "does not" LD (RO),A ; Initialize Read-Only flag to "is not" CALL FXIST ; See if file already exists JP C,NEWFI4 ; NO - go create the file CALL COPYDIR ; FILE EXISTS - copy directory entry to FCB LD A,ON ; And set exists-flag LD (EXISTS),A PUSH HL ; Save HL LD HL,ROOFF ; Get offset to the R/O byte ADD HL,DE ; Add the FCB base address LD A,(HL) ; Get R/O attribute byte AND 080H ; Mask R/O bit and test LD A,(HL) ; In any event, get byte back again RLA ; And lop off the 7th (R/O) bit RRCA ; (without affecting the zero flag) LD (HL),A ; Put that back - file will be R/W POP HL ; Restore old HL JP Z,NEWFI2 ; If file was R/W then move on LD A,ON ; FILE IS R/O - set flag LD (RO),A LD A,(WIPE) ; Get wipe option CP ON ; Is it ON ? JP Z,NEWFI1 ; YES - then delete the R/O file CALL ILPRT ; Tell them it's read-only DB ' R/O!',0 CALL QDELET ; Ask them if we should delete RET C ; Return now if they don't say yes NEWFI1: CALL ATTRBUT ; Set attribute to R/W JP NEWFI3 ; Then go delete the file NEWFI2: LD A,(EMEND) ; FILE IS R/W - get emend option CP ON ; Is it ON ? JP Z,NEWFI3 ; YES - then delete without asking LD A,(WIPE) ; Get the wipe option CP ON ; Is it ON? JP Z,NEWFI3 ; YES - it works for r/w also, so delete CALL ILPRT ; Else tell them that the file exists DB ' Exists!',0 CALL QDELET ; Ask them if we should delete RET C ; NO - then don't mess with it NEWFI3: CALL FDELETE ; Delete the old file NEWFI4: CALL FCREAT ; Create the new file RET NC ; Return if all ok LD HL,0 ; Else error and return CALL ERRET DB 'directory is full',0 ;----------------------------------------------------------------------- ; Create a file ; Entry: DE points to FCB of file to create ; Return: CARRY SET if error ; all registers except PSW are preserved FCREAT: PUSH BC ; Save all PUSH DE PUSH HL CALL ZEROFCB ; Zero the extent and current rec. CALL FSETUSR ; Set the user IF Z80DOS CALL USESTMP ENDIF ; Z80DOS LD C,CREATE ; Do CP/M function CALL BDOS IF DATESTAMP LD (TDSECTOR),DE ; Save sector number returned by DateStamper LD (TDINDEX),A ; Save directory entry offset ENDIF ; DATESTAMP ADD A,1 ; From BDOS, Acc. = 0FFh if ERROR POP HL ; Restore all POP DE POP BC RET ;----------------------------------------------------------------------- ; Delete a file ; Entry: DE points to FCB of file to delete ; Return: all regs. except PSW preserved ; no error check is made FDELETE: PUSH BC ; Save all PUSH DE PUSH HL LD C,DELETE ; Do CP/M function CALL BDOS POP HL ; Restore all POP DE POP BC RET ;----------------------------------------------------------------------- ; Set file attributes ; Entry: DE = FCB with attribute bytes properly set ; Return: none ; all registers preserved ATTRBUT: PUSH BC ; Save all PUSH DE PUSH HL CALL FSETUSR ; Set appropriate user number LD C,SETATT ; Do CP/M set attribute function CALL BDOS POP HL ; Restore all POP DE POP BC RET ;----------------------------------------------------------------------- ; Set user ; Entry: DE = FCB address. User number must be at FCB[0-1] ; Return: none ; all registers preserved except PSW FSETUSR: PUSH BC ; Save all PUSH DE PUSH HL DEC DE ; Point to user as specified in FCB LD A,(DE) ; Get it LD E,A ; Put it in E LD C,SETUSR ; Get CP/M function number CALL BDOS ; Set the user POP HL ; Restore all POP DE POP BC RET ; And done ;----------------------------------------------------------------------- ; Get current drive and user ; Entry: none ; Return: B = drive (0 = A) ; C = user ; all registers may be affected GETDU: LD C,GETDSK ; Get current disk drive number CALL BDOS PUSH AF ; Save it temporarily LD E,0FFH ; Get current user number LD C,GETUSR CALL BDOS LD C,A ; Put user in C POP AF ; Get drive back LD B,A ; Put drive in B RET ;----------------------------------------------------------------------- ; Set drive and user ; Entry: B = drive (0 = A) ; C = user ; Return: DE and HL are preserved SETDU: PUSH DE ; Save regs. PUSH HL PUSH BC ; Save original LD E,B ; Put drive number into E LD C,SELDSK ; And select new drive CALL BDOS POP BC ; Get original back LD E,C ; Put user number in E LD C,SETUSR ; And set new user CALL BDOS POP HL ; And old registers POP DE RET ; And done ;----------------------------------------------------------------------- ; Select user and see if a file exists ; Entry: DE = pointer to FCB[0]. Drive byte should be set, and ; FCB[0-1] must be set with user number. ; Return: HL and DE are preserved ; BC and PSW are destroyed ; if found, Acc. = offset byte from BDOS search call ; if not found, CARRY SET FXIST: PUSH HL ; Save registers PUSH DE ; Save FCB address CALL ZEROFCB ; Set extent and current record to zero LD HL,CMDBUF ; Set DMA address CALL FACCESS ; To CP/M's default buffer CALL FSETUSR ; And set user LD C,SRCHFST ; Get search-first function number CALL BDOS ; Do it POP DE ; FCB address now back in DE POP HL ; Restore registers ADD A,1 ; Set carry if result = 0FFh DEC A ; But restore original offset in Acc. RET ; Done ;----------------------------------------------------------------------- ; Translate the offset that CP/M gives us after a "search" call to an ; absolute address in the default CP/M DMA block. ; Entry: Acc. = offset from search call ; Returns: HL = address in DMA of the matching file name ; PSW destroyed, other registers preserved. FNOFF: PUSH DE ; Save register. ADD A,A ; Shift result * 2 ADD A,A ; * 4 ADD A,A ; * 8 ADD A,A ; * 16 ADD A,A ; * 32 LD L,A ; Put name offset in HL LD H,0 LD DE,CMDBUF ; And add this offset to DMA[0] address ADD HL,DE POP DE ; Restore old DE RET ; And return ;----------------------------------------------------------------------- ; Search the directory for all matches to a name ; ; Entry: DE = pointer to completed FCB, with user number at FCB[0-1]. ; Return: NARGC = number of matches found ; NARGBUF will contain the names in FCB-image fields. ; DE preserved, all other registers affected ; CARRY SET if no match was found. DIRSRCH: CALL FXIST ; See if file exists RET C ; Return now if not DIRS1: PUSH DE ; Save the FCB address from harm CALL FNOFF ; Get address of file name in HL INC HL ; Skip the drive byte EX DE,HL ; Put this address in DE LD HL,(IOBUF) ; Get buffer pointer in HL EX DE,HL ; Now HL = file name, DE = buffer LD B,FNAMSZ ; B = size of FCB-image file name CALL MOVE ; Move the file name into narg. buffer EX DE,HL ; Get buffer pointer back in HL LD (IOBUF),HL ; And store it for next time LD HL,(NARGC) ; Get new argument count INC HL ; Increment it LD (NARGC),HL ; And re-save it EX DE,HL ; Then put it in DE LD HL,MAXNARG ; Get max. count CALL SUB16 ; And find difference POP DE ; Restore FCB address RET Z ; And return if we've reached max. count PUSH DE ; Else, save FCB address from harm LD C,SRCHNXT ; Search for next occurrence CALL BDOS POP DE ; Get FCB address back INC A ; Test result for 0FFh, but make sure OR A ; Carry is off since we found at least one RET Z ; Return if no more matches DEC A ; Else undo what the test did JP DIRS1 ; And repeat ;----------------------------------------------------------------------- ; Copy directory entry file name found by FXIST into an FCB ; Entry: DE = FCB to receive the name ; Acc. = offset from FXIST: subroutine ; Return: none, all registers except PSW are preserved COPYDIR: PUSH BC ; Save all PUSH DE PUSH HL INC DE ; Point to file name field in our FCB CALL FNOFF ; Get address of directory entry in HL INC HL ; Point to file name field in dir. entry LD B,FNAMSZ ; Get full size of file name CALL MOVE ; And move directory name to our FCB POP HL ; Restore old POP DE POP BC RET ; And done ; end of disk I/O module ;-----------------------------------------------------------------------