; SEQUENTIAL FILE I/O LIBRARY ; VRS 2.2 Jack Riley, Boulder Colorado(RCPM phone: (303)499-9169) ; ; This is a highly modified version of the original by unknown author ; believed to be Ward Christensen. ; It has been expanded to include the following new features: ; 1) An APPEND mode to the FILE macro to allow the opening of files ; with automatic positioning to the EOF. Both GET and PUT macros ; are expanded to allow full random access to the file. Random ; access reads and writes are used instead of sequential(and also ; in other modes of use of the FILE macro so 1.4 is now incompatible). ; 2) PUBLIC and NONLOC options have been added to the FILE macro ; to allow access to files not in the current user area or on the ; current disk drive. The GET and PUT macros also handle the switching ; needed to provide for multiple opens in multiple areas. The way ; they work is to momentarily switch the user area to the one needed ; for the file undergoing an IO operation. A return is made to the ; 'home' user area to allow for 'local' file accesses or switches ; to other areas to access other files. This is not completely ; satisfactory and one could wish for a more elegant method which ; should have been available under CPM. Also an additional byte ; has been added to the FCB generated by FILLFCB to contain the ; user area. The NONLOC option prevents an otherwise automatic ; sequence to look first in the current user area and on the current ; disk for the file, then switch the user area, then the disk to ; the default locations. When PUBLIC is included in an invocation ; of FILE, then code accessing default and current values is made. ; The allocations for these variables is shown below. ; DEFAULT$USER: ; DB 0 ; or other user area ; DEFAULT$DISK: ; DB 'x'-'A' ; where x is the default ; CUR$USER: ; DB 0FFH ; necessary initial value ; CUR$DISK: ; DB 0FFH ; " " ; ; The intention was to allow the default values to be modified ; at run time(one of the failings of MACRO-economics) so that ; determinations of the availability of hard disks, for example, ; could be included. Also it is sometimes nice to have these ; values at the very beginning of a program so that DDT-style ; customizations can be made. ; 3) A SECTBUF parameter has been added to FILE to turn off the ; standard character buffering previously provided. It seemed ; reasonable to provide this new open machinery even when ; simple sector buffering was intended. Also when SECTBUF=NONE ; all buffering is turned off and only the new open code is ; produced. This can also be done through use of the POPEN macro ; directly(without FCB's being generated). FILERR SET 0000H ;REBOOT AFTER ERROR @FALSE SET 0000H @TRUE SET NOT @FALSE @BDOS EQU 0005H ;BDOS ENTRY POINT @TFCB EQU 005CH ;DEFAULT FILE CONTROL BLOCK @TBUF EQU 0080H ;DEFAULT BUFFER ADDRESS ; ; BDOS FUNCTIONS @MSG EQU 9 ;SEND MESSAGE @OPN EQU 15 ;FILE OPEN @CLS EQU 16 ;FILE CLOSE @DIR EQU 17 ;DIRECTORY SEARCH @DEL EQU 19 ;FILE DELETE @MAK EQU 22 ;FILE MAKE @REN EQU 23 ;FILE RENAME @DMA EQU 26 ;SET DMA ADDRESS @FRD EQU 33 ;FILE RANDOM READ OPERATION @FWR EQU 34 ;FILE RANDOM WRITE OPERATION @CFS EQU 35 ;COMPUTE FILE SIZE @SETRR EQU 36 ;SET RANDOM RECORD ; @SECT EQU 128 ;SECTOR SIZE EOF EQU 1AH ;END OF FILE @CR EQU 0DH ;CARRIAGE RETURN @LF EQU 0AH ;LINE FEED TAB EQU 09H ;HORIZONTAL TAB ; @KEY EQU 1 ;KEYBOARD @CON EQU 2 ;CONSOLE DISPLAY @RDR EQU 3 ;READER @PUN EQU 4 ;PUNCH @LST EQU 5 ;LIST DEVICE ; ; KEYWORDS FOR "FILE" MACRO NONE EQU 1 SECTBUFF EQU @TRUE NONLOC EQU @TRUE INFILE EQU 1 ;INPUT FILE OUTFILE EQU 2 ;OUTPUTFILE SETFILE EQU 3 ;SETUP NAME ONLY APPEND EQU 4 ;APPEND TO FILE ; ; THE FOLLOWING MACROS DEFINE SIMPLE SEQUENTIAL ; FILE OPERATIONS: ; FILLNAM MACRO FC,C ;; FILL THE FILE NAME/TYPE GIVEN BY FC FOR C CHARACTERS @CNT SET C ;;MAX LENGTH IRPC ?FC,FC ;;FILL EACH CHARACTER ;; MAY BE END OF COUNT OR NUL NAME IF @CNT=0 OR NUL ?FC EXITM ENDIF DB '&?FC' ;;FILL ONE MORE @CNT SET @CNT-1 ;;DECREMENT MAX LENGTH ENDM ;;OF IRPC ?FC ;; ;; PAD REMAINDER REPT @CNT ;;@CNT IS REMAINDER DB ' ' ;;PAD ONE MORE BLANK ENDM ;;OF REPT ENDM ; FILLDEF MACRO FCB,?FL,?LN ;; FILL THE FILE NAME FROM THE DEFAULT FCB ;; FOR LENGTH ?LN (9 OR 12) LOCAL PSUB JMP PSUB ;;JUMP PAST THE SUBROUTINE @DEF: ;;THIS SUBROUTINE FILLS FROM THE TFCB (+16) MOV A,M ;;GET NEXT CHARACTER TO A STAX D ;;STORE TO FCB AREA INX H INX D DCR C ;;COUNT LENGTH DOWN TO 0 JNZ @DEF RET ;; END OF FILL SUBROUTINE PSUB EQU $ FILLDEF MACRO ?FCB,?F,?L LXI H,@TFCB+?F ;;EITHER @TFCB OR @TFCB+16 LXI D,?FCB MVI C,?L ;;LENGTH = 9,12 CALL @DEF ENDM FILLDEF FCB,?FL,?LN ENDM ; FILLNXT MACRO ;; INITIALIZE BUFFER AND DEVICE NUMBERS @NXTB SET 0 ;;NEXT BUFFER LOCATION @NXTD SET @LST+1 ;;NEXT DEVICE NUMBER FILLNXT MACRO ENDM ENDM ; FILLFCB MACRO MD,FID,DN,FN,FT,BS,BA ;; FILL THE FILE CONTROL BLOCK WITH DISK NAME ;; DEFINE FILE USING MODE MD: ;; INFILE = 1 INPUT FILE ;; OUTFILE = 2 OUTPUT FILE ;; SETFILE = 3 SETUP FCB ;; FID IS AN INTERNAL NAME FOR THE FILE, ;; DN IS THE DRIVE NAME (A,B..), OR BLANK ;; FN IS THE FILE NAME, OR BLANK ;; FT IS THE FILE TYPE ;; BS IS THE BUFFER SIZE ;; BA IS THE BUFFER ADDRESS LOCAL PFCB ;; FID&TYP SET MD ;;SET MODE FOR LATER REF'S ;; SET UP THE FILE CONTROL BLOCK FOR THE FILE ;; LOOK FOR FILE NAME = 1 OR 2 @C SET 1 ;;ASSUME TRUE TO BEGIN WITH IRPC ?C,FN ;;LOOK THROUGH CHARACTERS OF NAME IF NOT ('&?C' = '1' OR '&?C' = '2') @C SET 0 ;;CLEAR IF NOT 1 OR 2 ENDIF ENDM ;; @C IS TRUE IF FN = 1 OR 2 AT THIS POINT IF @C ;;THEN FN = 1 OR 2 ;; FILL FROM DEFAULT AREA IF NUL FT ;;TYPE SPECIFIED? @C SET 12 ;;BOTH NAME AND TYPE ELSE @C SET 9 ;;NAME ONLY ENDIF FILLDEF FCB&FID,(FN-1)*16,@C ;;TO SELECT THE FCB JMP PFCB ;;PAST FCB DEFINITION DS @C ;;SPACE FOR DRIVE/FILENAME/TYPE FILLNAM FT,12-@C ;;SERIES OF DB'S ELSE JMP PFCB ;;PAST INITIALIZED FCB IF NUL DN DB 0 ;;USE DEFAULT DRIVE IF NAME IS ZERO ELSE DB '&DN'-'A'+1 ;;USE SPECIFIED DRIVE ENDIF FILLNAM FN,8 ;;FILL FILE NAME ;; NOW GENERATE THE FILE TYPE WITH PADDED BLANKS FILLNAM FT,3 ;;AND THREE CHARACTER TYPE ENDIF FCB&FID EQU $-12 ;;BEGINNING OF THE FCB DB 0 ;;EXTENT FIELD 00 FOR SETFILE ;; NOW DEFINE THE 3 BYTE FIELD, AND DISK MAP DS 23 ;;X,X,RC,DM0...DM15,CR,R0,R1,R2 FIELDS DB 0FFH ;; DEFAULT CURRENT USER AREA ;; IF FID&TYP<=2 ;;IN/OUTFILE ;; GENERATE CONSTANTS FOR INFILE/OUTFILE FILLNXT ;;@NXTB=0 ON FIRST CALL IF BS+0<@SECT ;; BS NOT SUPPLIED, OR TOO SMALL @BS SET @SECT ;;DEFAULT TO ONE SECTOR ELSE ;; COMPUTE EVEN BUFFER ADDRESS @BS SET (BS/@SECT)*@SECT ENDIF ;; ;; NOW DEFINE BUFFER BASE ADDRESS IF NUL BA ;; USE NEXT ADDRESS AFTER @NXTB FID&BUF SET BUFFERS+@NXTB ;; COUNT PAST THIS BUFFER @NXTB SET @NXTB+@BS ELSE FID&BUF SET BA ENDIF ;; FID&BUF IS BUFFER ADDRESS FID&ADR EQU $ DW FID&BUF ;; FID&SIZ EQU @BS ;;LITERAL SIZE FID&LEN EQU $ DW @BS ;;BUFFER SIZE FID&PTR EQU $ DS 2 ;;SET IN INFILE/OUTFILE ;; SET DEVICE NUMBER @&FID SET @NXTD ;;NEXT DEVICE @NXTD SET @NXTD+1 ENDIF ;;OF FID&TYP<=2 TEST PFCB EQU $ ENDM ; FILE MACRO FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF ;; (SEE FILLFCB FOR PARAMETERS) FID&FLG SET 1 IF NUL PU FID&PUB SET 0 ELSE FID&PUB SET 1 ENDIF @SETRC SET @SETRR IF FMODE=APPEND @SETRC SET @CFS GFILE FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,0 FID&TYP SET OUTFILE ;;SET MODE FOR LATER REF'S ENDIF GFILE FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,@SETRC ENDM ; GFILE MACRO FMODE,FID,DN,FN,FT,BS,BA,PU,NOLOC,SECTBUF,@SETRC LOCAL PSUB,MSG,PMSG LOCAL PND,EOD,EOB,PNC,GLOOP,SAMEUSR ;; CONSTRUCT THE FILE CONTROL BLOCK ;; MD SET FMODE IF FMODE=APPEND IF @SETRC=0 MD SET INFILE ELSE MD SET OUTFILE ENDIF ENDIF IF FID&FLG FILLFCB MD,FID,DN,FN,FT,BS,BA ENDIF IF MD=SETFILE ;;SETUP FCB ONLY, SO EXIT EXITM ENDIF ;; FILE CONTROL BLOCK AND RELATED PARAMETERS ;; ARE CREATED INLINE, NOW CREATE IO FUNCTION BLOCKING SET @TRUE IF NUL SECTBUF ;;INPUT FILE JMP PSUB ;;PAST INLINE SUBROUTINE IF MD=OUTFILE PUT&FID EQU $ PUSH PSW ;;SAVE OUTPUT CHARACTER ELSE GET&FID EQU $ ENDIF LHLD FID&LEN ;;LOAD CURRENT BUFFER LENGTH XCHG ;;DE IS LENGTH LHLD FID&PTR ;;LOAD NEXT TO GET/PUT TO HL MOV A,L ;;COMPUTE CUR-LEN SUB E MOV A,H SBB D ;;CARRY IF NEXT ;; SKIP ALL BUT OUTPUT FILES IF ?F&TYP=OUTFILE LOCAL EOB?,PEOF,MSG,PMSG,SAMEUSR ;; WRITE ALL PARTIALLY FILLED BUFFERS EOB? EQU $ ;;ARE WE AT THE END OF A BUFFER? LHLD ?F&PTR ;;NEXT TO FILL MOV A,L ;;ON BUFFER BOUNDARY? ANI (@SECT-1) AND 0FFH JNZ PEOF ;;PUT EOF IF NOT 00 IF @SECT>255 ;; CHECK HIGH ORDER BYTE ALSO MOV A,H ANI (@SECT-1) SHR 8 JNZ PEOF ;;PUT EOF IF NOT 00 ENDIF ;; ARRIVE HERE IF END OF BUFFER, SET LENGTH ;; AND WRITE ONE MORE BYTE TO CLEAR BUFFS SHLD ?F&LEN ;;SET TO SHORTER LENGTH PEOF EQU $ MVI A,EOF ;;WRITE ANOTHER EOF PUSH PSW ;;SAVE ZERO FLAG CALL PUT&?F POP PSW ;;RECALL ZERO FLAG JNZ EOB? ;;NON ZERO IF MORE ;; BUFFERS HAVE BEEN WRITTEN, CLOSE FILE IF ?F&PUB LDA FCB&?F+36 ;; GET USER AREA OF FILE CPI 0FFH JZ SAMEUSR MVI C,32 MOV E,A CALL @BDOS ;; GO TO FILE USER AREA SAMEUSR EQU $ ENDIF LXI D,FCB&?F ;;FCB ADDRESS TO DE MVI C,@CLS CALL @BDOS ;; CLOSE THE FILE IF ?F&PUB CALL RESET$SYSTEM ENDIF INR A ;;255 IF ERR BECOMES 00 JNZ PMSG ;; FILE CANNOT BE CLOSED MVI C,@MSG LXI D,MSG CALL @BDOS JMP PMSG ;;ERROR MESSAGE PRINTED MSG EQU $ DB @CR,@LF DB 'CANNOT CLOSE &?F' DB '$' PMSG EQU $ ENDIF ENDM ;;OF THE IRP ENDM ; ERASE MACRO FID ;; DELETE THE FILE(S) GIVEN BY FID IRP ?F, MVI C,@DEL LXI D,FCB&?F CALL @BDOS ENDM ;;OF THE IRP ENDM ; DIRECT MACRO FID ;; PERFORM DIRECTORY SEARCH FOR FILE ;; SETS ZERO FLAG IF NOT PRESENT LXI D,FCB&FID MVI C,@DIR CALL @BDOS INR A ;00 IF NOT PRESENT ENDM ; RENAME MACRO NEW,OLD ;; RENAME FILE GIVEN BY "OLD" TO "NEW" LOCAL PSUB,REN0 ;; INCLUDE THE RENAME SUBROUTINE ONCE JMP PSUB @RENS EQU $ ;;RENAME SUBROUTINE, HL IS ADDRESS OF ;;OLD FCB, DE IS ADDRESS OF NEW FCB PUSH H ;;SAVE FOR RENAME LXI B,16 ;;B=00,C=16 DAD B ;;HL = OLD FCB+16 REN0 EQU $ LDAX D ;;NEW FCB NAME MOV M,A ;;TO OLD FCB+16 INX D ;;NEXT NEW CHAR INX H ;;NEXT FCB CHAR DCR C ;;COUNT DOWN FROM 16 JNZ REN0 ;; OLD NAME IN FIRST HALF, NEW IN SECOND HALF POP D ;;RECALL BASE OF OLD NAME MVI C,@REN ;;RENAME FUNCTION CALL @BDOS RET ;;RENAME COMPLETE PSUB EQU $ RENAME MACRO N,O ;;REDEFINE RENAME LXI H,FCB&O ;;OLD FCB ADDRESS LXI D,FCB&N ;;NEW FCB ADDRESS CALL @RENS ;;RENAME SUBROUTINE ENDM RENAME NEW,OLD ENDM ; GET MACRO DEV ;; READ CHARACTER FROM DEVICE IF @&DEV <= @LST ;; SIMPLE INPUT MVI C,@&DEV CALL @BDOS ELSE CALL GET&DEV ENDM ; POPEN MACRO NOLOC ; DE is assumed to point to the file FCB on entry OPEN SET 0FH LOCAL PSUB,LEAVE ;OPEN MAST.CAT * OPTION 1: TRY TO OPEN FILE IN CURRENT USER NUMBER ON CURRENT DISK JMP PSUB @OPEN EQU $ PUSH D ; save the FCB MVI A,0FFH ; DECLARE CURRENT USER AREA ON FILE STA FILEUA MVI C,12 ; GET VERSION NUMBER CALL @BDOS MOV A,H ; CP/M 1.X? ORA L JZ START2$DISK ; CHECK FOR DEFAULT DISK IF SO * OPTION 2: TRY TO OPEN FILE IN USER 0 ON CURRENT DISK MVI E,0FFH ; GET CURRENT USER NUMBER MVI C,32 ; GET USER CODE CALL @BDOS MOV C,A LDA DEFAULT$USER ; CHECK IF AT DEFAULT USER CMP C JZ START2$DISK ; DON'T TRY IF AT DEFAULT USER AREA STA FILEUA ; WHERE THE FILE IS IF ANYWHERE MOV E,A MOV A,C STA CUR$USER ; WHERE WE ARE(SAVE FOR LATER) MVI C,32 ; SET USER CODE TO DEFAULT$USER CALL @BDOS IF NUL NOLOC POP D ; GET BACK FCB PUSH D ; PRESERVE THE STACK MVI C,OPEN CALL @BDOS ; TRY TO OPEN FILE AGAIN CPI 255 ; NOT PRESENT? JNZ LEAVE ENDIF ; NUL NOLOC * OPTION 3: TRY TO OPEN FILE IN USER 0 ON DEFAULT DISK IF NOT CURRENT DISK START2$DISK EQU $ MVI C,25 ; DETERMINE IF CURRENT DISK IS THE DEFAULT CALL @BDOS MOV C,A LDA DEFAULT$DISK ; CHECK IF AT DEFAULT DISK CMP C IF NUL NOLOC JZ LEAVE ;FAILURE TO OPEN SINCE NOTHING LEFT TO TRY ENDIF POP H ; FCB INTO HL PUSH H ; PRESERVE STACK IF NUL NOLOC ELSE JZ START3$DISK ENDIF INR A ; ADD ONE TO DISK NUMBER MOV M,A ; PUT INTO FCB START3$DISK EQU $ XCHG ; FCB INTO DE MVI C,15 ; OPEN FILE CALL @BDOS CPI 255 ; NOT PRESENT? LEAVE EQU $ POP D ; GET THE FCB AGAIN(AND CLEAN UP STACK) PUSH PSW ; SAVE OPEN STATUS ON FILE LXI H,36 DAD D LDA FILEUA ; GET THE USER AREA FOR THE FILE MOV M,A ; PUT USER AREA INTO FCB POP PSW RET ; RESET$SYSTEM EQU $ PUSH PSW LDA CUR$USER ; CHECK USER CPI 0FFH ; 0FFH=NO CHANGE JZ RESET$RET MOV E,A ; USER IN E MVI C,32 ; GET/SET USER CODE CALL @BDOS RESET$RET EQU $ POP PSW RET FILEUA EQU $ DS 1 PSUB EQU $ POPEN MACRO CALL @OPEN ENDM POPEN ENDM