Sliding into BDOS Part I..III, Michael J. Karas
http://www.classiccmp.org/cpmarchives/cpm/Software/WalnutCD/cpminfo/easybdos.lbr
als pdf easybdos.pdf
Ausführliche Beschreibung der BDOS-Schnittstelle. Original 3 Teile, Wordstar-Dokument, PD.
SLIDING INTO BDOS THE SMOOTH AND EASY WAY by: Michael J. Karas 2468 Hansen Court Simi Valley, CA 93065 What is this thing everybody is talking about called BDOS? This series will attempt to answer this question in some detail but first we need a little basis to understand WHY in the first place. Digital Research CP/M is an operating system for smaller type micro processor computer systems that is designed to remove much of the normal computer operation drudgery experienced by the computer operator. The operating system software embodies a "system philosophy" that structures and generalizes upon the operating environment of a piece of electronics hardware. The environment presented actually allows that piece of quiet, transistorized machinery to be used at a much higher level. The full impact of what this operating system provides to a computer is most probably felt by the typical micro computer hacker that worked the hard way to get a computer system up and running. While building, debugging, and integrating the pieces, the computer was just a whole bunch of parts interfaced together in an organized manner. However, when the thing is finally a "computer" how does it get used. The low level process of poking data into memory from a front panel or even filling, dumping, or block moving memory data with an EPROM based "monitor program" hardly makes this computer "useful". The process of putting on disks and bringing up CP/M lights the torch for computer usability. In this case the hacker experiences an elated feeling now "NOW I CAN DO SOMETHING!" Buried inside of the total operating system presentation is the concept of generalization brought up in the previous paragraph. One of the major requirements in order to make a computer useful is that there has to be applications software that performs the jobs intended for the computer. Jobs like accounting, word processing, spread sheet data analysis, or inventory control. Unfortunately the process of producing applications software is very, very expensive. A good package may take anywhere from one to ten man years of development effort to make. If the process of making an applications package had to be custom taylored to a specific hardware environment, then there would not be affordable software available for use upon a given XYZ computer. Generalization in the operation of a computer environment solves this problem however. With the understanding that at a certain level "all microprocessor computer systems are alike" it is possible, with minimum constraints, to define a set of logical type operations that make a computer useful. This logical set of operations, for the Digital Research CP/M operating system, is defined within the BDOS portion of the operating system. Here in about 3 1/2 K bytes of tightly written assembly language is the "generalization converter" that takes I/O requests for hardware independant applications programs and turns them into a lower level set of simplistic hardware oriented functions that are then processed through the BIOS. This conversion process is beneficial in the light that CP/M Ver 2.2 can be setup to run on a typical brand XYZ computer for about one half of the effort needed to convert even one of the simplest application packages had that application been written in a hardware dependant manner. Conclusion; software developers can make better, more sophisticated applications available for lower cost and computer users find a competitive software market place where there are many times multiple packages available that perform similar functions. The thrust of this presentation is to show the prospective applications programmer how to use most of the generalized set of "BDOS System Calls" within Digital Researches CP/M Ver 2.2. The presentation scheme will be to describe all of the functions and use simple examples. The reader is assumed to be modistly familiar with 8080 Assembly Language Programming as all of the examples will be given in machine language. Likewise, in this environment it is assumed by default that the prospective programmer is planning to code in assembly language. If a CP/M compatible high level language is used for programming, such as Digital Research PL/I-80 or Microsoft BASIC-80, then of course the program interface at the "System Call" level becomes transparent to the programmer. Run time subroutines make the high level coded application get converted through yet another step. (One major reason applications code in a high level language runs slower than the equivalent function written in assembly language). SUMMARY OF CP/M SYSTEM CALLS The set of system or "BDOS" I/O entry points available to the CP/M programmer is complete yet simple. The primary beauty of the CP/M system is this small world of completeness. Many programmers familair with other operating systems complain that the CP/M system is weak, unflexible, and incomplete. However, in a microprocessor type computer world, the generalization level defined for the CP/M system allows 85% of all microprocessor type appliciation jobs to be programmed with relative ease. Also, in my opinion, 8-bit microprocessor hardware is easily capable of performing about 90 percent of the typical tasks targeted for microcomputers. So what is this set of functions? The chart of Figure 1 summarizes, in function number order, all of the system operations specific to CP/M Version 2.2 that will be covered in this presentation. In the subsequent sections that follow the functions will be grouped into categories so that related operations may become familiar with reference to one another. FIGURE 1. DETAILED SUMMARY OF CP/M 2.2 SYSTEM CALLS Function Entry Value to Return Value from Number BDOS Passed in BDOS Passed in DEC HEX Function (DE) or (E) regs (HL) or (A) register ------------------------------------------------------------------------- 0 00 | System Reset | **** | **** | 1 01 | Console Input | **** | (A)=character | 2 02 | Console Output | (E)=character | **** | 3 03 | Reader Input | **** | (A)=character | 4 04 | Punch Output | (E)=character | **** | 5 05 | Printer Output | (E)=character | **** | 6 06 | Direct Console I/O | (E)=0FFH is input| (A)=character | | | (E)=chr is output| **** | 7 07 | Get IOBYTE | **** | (A)=IOBYTE | 8 08 | Set IOBYTE | (E)=IOBYTE | **** | 9 09 | Display Console String | (DE)=string addr | **** | 10 0A | Input Console String | (DE)=string addr | (A)=# chr input | 11 0B | Get Console Status | **** | (A)=000H idle | | | | (A)=0FFH ready | 12 0C | Get CP/M Version Number| **** | (HL)=Version # | 13 0D | Reset Disk Subsystem | **** | **** | 14 0E | Select Disk Drive | (E)=disk number | **** | 15 0F | Open a File | (DE)=FCB address | (A)=dir code | 16 10 | Close a File | (DE)=FCB address | (A)=dir code | 17 11 | Search for File | (DE)=FCB address | (A)=dir code | 18 12 | Search for Next | **** | (A)=dir code | 19 13 | Delete File | (DE)=FCB address | (A)=dir code | 20 14 | Read next Record | (DE)=FCB address | (A)=error code | 21 15 | Write next Record | (DE)=FCB address | (A)=error code | 22 16 | Create New File | (DE)=FCB address | (A)=dir code | 23 17 | Rename File | (DE)=FCB address | (A)=dir code | 24 18 | Get Login Vector | **** | (HL)=login vector| 25 19 | Get Logged Disk Number | **** | (A)=logged disk | 26 1A | Set R/W Data Buff Addr | (DE)=buffer addr | **** | 27 1B | Get Allocation Vector | **** | (HL)=alloc vector| | | | address | 28 1C | Write Protect Disk | (E)=disk number | **** | 29 1D | Get Read Only Vector | **** | (HL)=R/O vector | 30 1E | Set File Attributes | (DE)=FCB address | (A)=dir code | 31 1F | Get Addr of Disk Parms | **** | (HL)=parm addr | 32 20 | Get/Set User Select | (E)=0FFH get | (A)=current user | 33 21 | Read Random Record | (DE)=long FCB adr| (A)=error code | 34 22 | Write Random Record | (DE)=long FCB adr| (A)=error code | 35 23 | Get Size of File | (DE)=long FCB adr| (r0-2=rec cnt) | 36 24 | Set Random Record Num | (DE)=long FCB adr| (r0-2=rec numb) | 37 25 | Reset Drive | (DE)=drive vector| **** | 38 26 | Not used | | | 39 27 | Not used | | | 40 28 | Write Random with | (DE)=long FCB adr| (A)=error code | ------------------------------------------------------------------------- The technical means required to "use" or interface to the CP/M system for each function contains a certain common structure that will be discussed here. The base memory page of a CP/M system memory map includes, at a specific memory address, a JUMP instruction to the CP/M BDOS entry point. For most CP/M systems this is address 00005H. To accomplish BDOS I/O the number of the function is placed into the (C) register. If the parameter requires input parameters, then they are passed in the (DE) register pair or the individual (E) register depending upon whether the parameter is a word or byte value. Result information returned by some functions is sent back to the users program in either the (A) register or the (HL) register pair depending upon if the value is a byte or word. The following simple program segment demonstrates the scheme used to output the 26 characters A-Z to the console screen through the use of function number 2. BDOS EQU 0005H ;SYSTEM ENTRY CONOUT EQU 2 ;OUTPUT FUNCTION ORG 0100H ;TPA BASE MVI B,26 ;PRINT 26 COUNTER MVI C,'A' ;START WITH 'A' ; LOOP: PUSH B ;SAVE COUNTER & LETTER MOV E,C ;LETTER TO (E) FOR OUTPUT MVI C,CONOUT ;BDOS FUNC TO (C) CALL BDOS ;GO GO OUTPUT POP B INR C ;SEQUENCE TO NEXT CHAR DCR B ;DECREASE CHR COUNTER JNZ LOOP ;MORE TO DO IF NOT TO ZERO RET ;IMMEDIATE CCP RETURN SYSTEM CALLS FOR OPERATOR CONSOLE INPUT AND OUTPUT Intrinsic to the operation of any computer system, especially of the CP/M gender, is the operator console. The device provides the human interface to the machine and as such the BDOS includes a generalized set of operator communication functions to perform I/O with the console device. The various options available will each be presented with a brief example. INPUT FROM CONSOLE KEYBOARD: Function 1. This function waits for and reads in a character from the console device keyboard. The operator typed character is echoed automatically back to the console display if the character is an ASCII printable character (020H to 07EH) or it is a carriage return, line feed, back space, or tab. Note that the BDOS automatically expands tabs to columns of eight characters. Upon outputting the character for the echo, a check is made for console start/stop, CTL-S, and if so the console input routine does not return to the users program until another arbitrary key is depressed. ;CONSOLE INPUT EXAMPLE ; CONIN EQU 001H ;FUNC # 1 BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START MVI C,CONIN ;FUNCTION CALL BDOS ;GO GET CHARACTER STA INCHAR ;SAVE FOR WHATEVER REASON RET ;IMMEDIATE CCP RETURN ; INCHAR: DS 1 ;PLACE TO STORE INPUT CHAR ; END OUTPUT TO CONSOLE DISPLAY: Function 2. The ASCII character in the (E) register is sent to the console display device. The output may be any byte value but many times the hardware driver BIOS routines automatically strip off the upper bit of the byte. Upon output the printer echo flag within BDOS is checked (CTL-P) and if set the character is also sent to the printer peripheral device. Note that the BDOS automatically expands output tabs to columns of eight characters. Upon outputting the character a check is made for input of console start/stop, CTL-S, and if so the console output routine does not return to the users program until another arbitrary key is depressed. ;CONSOLE OUTPUT EXAMPLE ; CONOUT EQU 002H ;FUNC # 2 BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LDA OUTCHAR ;GET CHARACTER TO OUTPUT MOV E,A MVI C,CONOUT ;FUNCTION CALL BDOS ;GO SEND CHARACTER RET ;IMMEDIATE CCP RETURN ; OUTCHAR: DB 'X' ;PLACE TO GET OUTPUT CHAR ; END DIRECT USER INTERFACE TO CONSOLE: Function 6. Some programming applications require that the BDOS not monitor the input/output character stream as is done with functions 1 & 2. To allow for these functions the direct I/O function is supported. The following example shows how it is used to input values and echo them until an input control-Z character is typed. ;DIRECT CONSOLE I/O EXAMPLE ; DIRCIO EQU 006H ;FUNCTION NUMBER BDOS EQU 0005H ;SYSTEM ENTRY POINT CTLZ EQU 'Z'-040H ;ASCII CTL-Z CHARACTER INPUT EQU 0FFH ;DIRECT INPUT FLAG ORG 0100H ;CONSOLE INPUT ; LOOP: MVI E,INPUT ;SET FOR INPUT MVI C,DIRCIO ;FUNCTION CALL BDOS ;GET INPUT OR STATUS ORA A ;IF (A)=0 NO CHAR WAS READY JZ LOOP ;CONTINUE TO WAIT FOR INPUT CPI CTLZ ;IF INPUT WAS CTL Z THEN END RZ ;CCP RETURN ON END MOV E,A ;CHARACTER TO (E) FOR OUTPUT MVI C,DIRCIO ;SAME FUNCTION NUMBER AGAIN CALL BDOS ;GO OUTPUT IT JMP LOOP ;NEXT CHARACTER INPUT LOOP ; END PRINTING STRINGS OF CHARACTERS TO THE CONSOLE: Function 9. Message string sequences of characters to be sent to the console are quite common in applications programming. Typical uses may be for user prompt messages, program sign-on messages etc. The BDOS provides a convenient mechanism to allow the programmer to output a whole string of characters rather than having to loop with single character outputs. The string is intended to be stored in consecutive memory locations and end with the ASCII '$' character. The (DE) registers are used to point to the start of the string. The '$' signals the end of the string to display and is not sent to the console. The output bytes may be any 8-bit value but many times the hardware driver BIOS routines automatically strip off the upper bit of the byte. Upon output of each character the printer echo flag within BDOS is checked (CTL-P) and if set the character is also sent to the printer peripheral device. Note that the BDOS automatically expands output tabs to columns of eight characters. Upon outputting each character a check is made for input of console start/stop, CTL-S, and if so the console string output routine does not return to the users program until another arbitrary key is depressed. ;CONSOLE STRING PRINT EXAMPLE ; CONSTR EQU 009H ;FUNC # 9 BDOS EQU 0005H ;SYSTEM ENTRY CR EQU 0DH ;ASCII CARRIAGE RETURN LF EQU 0AH ;ASCII LINE FEED ORG 0100H ;START LXI D,MESSAGE ;POINT AT STRING TO SEND MVI C,CONSTR ;FUNCTION CALL BDOS ;GO SEND STRING RET ;IMMEDIATE CCP RETURN ; MESSAGE: DB CR,LF,'Hello Operator',CR,LF,'$' ; END READING A STRING OF CHARACTERS IN FROM KEYBOARD: Function 10. The CP/M console command processor (CCP) assumed to be vary familiar to most CP/M system operators allows buffered command input with editing features. It turns out that this operation is a much needed function for getting in strings of text from the operator console. Use of this function allows standardization of the command input functions so that the operator can easily learn the editing key functions. It also removes the pain of writing the same function over and over again by the applications programmer. The read string command inputs the edited text to a buffer pointerd to by the (DE) register pair. The caller specifies the maximum length desired and the BDOS returns the actual length of string entered if carriage return is entered prior to exceeding the maximum input length. The input length is returned in both the (A) register and as part of the buffer. Bytes in the string buffer past the end of the entered text are uninitialized. The example shown below gives an assembly language view point of the buffer structure and how to program an input function. The editing functions supported are the following control and/or special characters: rub/del removes and echos the last entered char ctl-C initiates system reboot if first char ctl-E echos a CR & LF to console without putting them into buffer ctl-H (or back space key) back spaces one char removing last entered character ctl-J (or line feed key) terminates line input ctl-M (or carriage return) terminates input ctl-R retypes currently entered characters under current line ctl-U deletes all of currently entered data and restarts buffer input on new line ctl-X deletes all of currently entered data and restarts buffer input on same line ;CONSOLE INPUT BUFFER EXAMPLE ; CONBUF EQU 00AH ;STRING INPUT FUNCTION BDOS EQU 0005H ;SYSTEM ENTRY POINT LENGTH EQU 32 ;DESIRED MAXIMUM CHARACTERS ORG 0100H ;START POINT LXI D,STRING ;POINT AT BUFFER AREA MVI C,CONBUF ;FUNCTION NUMBER CALL BDOS ;GO GET STRING RET ;RETURN TO CCP WITHOUT ;...DOING ANYTHING WITH DATA ; ; ;CONSOLE INPUT BUFFER LAYOUT ; STRING: DB LENGTH ;MAXIMUM DESIRED INPUT LENGTH AMOUNT: DS 1 ;BYTE WHERE BDOS RETURNS ;..ACTUAL BYTE COUNT STRBF: DS LENGTH ;RESERVED STORAGE FOR UP TO ;"LENGTH" NUMBER OF CHARACTERS ; END DETERMINING IF THERE IS PENDING KEYBOARD INPUT: Function 11. Some computer programs are designed to spend large amounts of time processing inside of the computer or manipulating data within disk files without stopping to ask the user if he/she desires to stop the processing sequence. Also it is many times desirable to have a "terminate" capability for application programs without waiting for the operator to answer a character input request. If the normal console input function is used the user computer is not resumed until a character is already input. The console input status check function may be used to poll the user keyboard to determine if a character input is pending. If no input is ready then the user program is immediately resumed with an indication of if there was a pending input. If a character is pending a 0FFH is returned in the (A) register. Otherwise a 000H value is returned. The following example illustrates the use of console status to terminate a normally endless loop that prints the same string over and over. ;CONSOLE STATUS USAGE EXAMPLE ; CONSTAT EQU 00BH ;FUNC # 11 CONSTR EQU 009H ;PRINT STRING FUNCTION BDOS EQU 0005H ;SYSTEM ENTRY CR EQU 0DH ;ASCII CARRIAGE RETURN LF EQU 0AH ;ASCII LINE FEED ORG 0100H ;START LOOP: LXI D,MESSAGE ;POINT AT STRING TO SEND MVI C,CONSTR ;FUNCTION CALL BDOS ;GO SEND STRING MVI C,CONSTAT ;GET ABORT STATUS CALL BDOS ORA A ;CHECK STATUS JZ LOOP ;NO KEY SO CONTINUE LOOP RET ;IMMEDIATE CCP RETURN IF ABORT ; MESSAGE: DB CR,LF,'Depress any Key to STOP','$' ; END AUXILLIARY PERIPHERAL CHARACTER INPUT AND OUTPUT FUNCTIONS The generalized CP/M BDOS provides the capability for three character by character logical I/O devices to be atteched to the computer system. This requirement stems from the fact that most computers are designed to interface to the real world in more ways than just a console device. The three devices are classified as: a) A lister type device that is generally expected to be a printer of some sort. This classification is an output only device. b) An input device supporting character input from a source other than the console. The device is specifcally an input type unit. CP/M jargon refers to this device as the "READER" for no particular reason. c) A generalized character output only device used as a specific data destination other than the console or standard list device. Some computer systems use this device, often times referred to as the "PUNCH" device as a second printer output. The three following examples illustrate the programming techniques used to talk to each of these three devices. ;LIST DEVICE OUTPUT EXAMPLE ; LIST EQU 005H ;FUNC # 5 BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LDA LSTCHAR ;GET CHARACTER TO OUTPUT MOV E,A MVI C,LIST ;FUNCTION CALL BDOS ;GO SEND CHARACTER RET ;IMMEDIATE CCP RETURN ; LSTCHAR: DB 'L' ;PLACE TO GET OUTPUT CHAR ; END ;READER DEVICE INPUT EXAMPLE ; READER EQU 003H ;FUNC # 3 BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START MVI C,READER ;FUNCTION CALL BDOS ;GO GET CHARACTER STA RDRCHR ;SAVE FOR WHATEVER REASON RET ;IMMEDIATE CCP RETURN ; RDRCHR: DS 1 ;PLACE TO STORE INPUT CHAR ; END ;PUNCH DEVICE OUTPUT EXAMPLE ; PUNCH EQU 004H ;FUNC # 4 BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LDA PNCHCHR ;GET CHARACTER TO OUTPUT MOV E,A MVI C,PUNCH ;FUNCTION CALL BDOS ;GO SEND CHARACTER RET ;IMMEDIATE CCP RETURN ; PNCHCHR: DB 'P' ;PLACE TO GET OUTPUT CHAR ; END SYSTEM CONTROL BDOS FUNCTIONS This family of system calls supported by the CP/M BDOS are designed to allow the programmer a degree of flexibility in manipulating the operation of general CP/M environment. Each function here will generally be discussed individually due to the unique nature of each operation. SYSTEM RESET: Function 0. The system reset function is designed to allow restart of the CP/M system command processor after a user application completes execution or is aborted. The system reset function is equivalent to a JMP to address 0000H or a CTL-C which forces a system WARM Reboot. The reboot operation de-activates all active drives except drive A: which is re-logged. Operation is extremely simple as: RESET EQU 000H ;SYSTEM RESET FUNC BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H MVI C,RESET JMP BDOS ;CALL ALSO PERMISSABLE ;EXCEPT THAT FUNCTION ;DOES NOT RETURN TO USER ;PROGRAM GET AND SET IOBYTE: Functions 7 & 8. The generalized CP/M operating system environment communicates via I/O to "logical" type devices. This means that the console, lister, "reader", and "punch" are just treated as a generic device classsifications. The CP/M system allows for and supports, to a degree, the capability for the hardware to contain multiple physical devices (peripherals and/or real I/O devices) within each of the generic logical device classifications. The means to support the assignment of multiple physical devices to a given classification is done through the IOBYTE, normally stored at address 00003H of the base page of the CP/M memory. The BIOS hardware I/O software may thusly be written to easily know which one of two printers to talk to when the BDOS requires output to one of two printers. A "default standard" IOBYTE format has been adopted based upon an 8-bit microprocessor system convention developed by Intel Corp as follows: (lister) (punch) (reader) (console) Logical Devices => LST: PUN: RDR: CON: IOBYTE bits => 7 6 5 4 3 2 1 0 --------------------------------------------------------- Bit pattern dec binary 0 00 TTY: TTY: TTY: TTY: 1 01 CRT: PTP: PTR: CRT: 2 10 LPT: UP1: UR1: BAT: 3 11 UL1: UP2: UR2: UC1: The designators in the table specify the "standard types of physical devices and are defined as follows: TTY: A teletype console with keyboard, hard copy display and possibly an integral tape reader/punch CRT: An interactive cathode ray type terminal with keyboard input and display screen BAT: A batch processor workstation with a card reader type input device and a hard copy display/output device UC1: A user defined alternate "console" unit LPT: Line printer UL1: A user defined list device PTR: Paper Tape Reader UR1: User defined "reader" character input device UR2: User defined "reader" character input device PTP: Paper Tape Punch UP1: User defined "punch" character output device UP2: User defined "punch" character output device The BDOS support for the I/O device assignment is a standard mechanism to access the IOBYTE's current value and switch it to some other value. Suppose a CP/M computer had two printers connected as LST: and UL1:. If the applications program needs to switch printing output to another printer, the process could be handeled as follows: ;GET AND SET IOBYTE EXAMPLE ; SETIOB EQU 008H ;SET IOBYTE FUNCTION GETIOB EQU 007H ;GET IOBYTE FUNCTION BDOS EQU 00005H ;SYSTEM ENTRY POINT LSTMASK EQU 11$00$00$00B ;IOBYTE MASK FOR LIST ;..DEVICE LPT EQU 10$00$00$00B ;BIT VALUE FOR LPT #1 UL1 EQU 11$00$00$00B ;BIT VALUE FOR LPT #2 ORG 0100H ;PROGRAM START MVI C,GETIOB ;GO GET CURRENT IOBYTE VAL CALL BDOS ANI (NOT LSTMASK) AND 0FFH ;KEEP ALL OTHER BITS ORI UL1 AND LSTMASK ;SET IOBYTE FOR PRINTER #2 MOV E,A MVI C,SETIOB ;FUNCTION TO RESET THE IOBYTE CALL BDOS RET ;IMMEDIATE CCP RETURN ; END GET CP/M VERSION NUMBER: Function 12. Sometimes it is necessary for an applications program to "know" what version of CP/M the program is running under. Version 2.0 and above support a feature to tell the application program what the version number is. One reason is to permit version dependant functions such as random record file I/O to be used if it is supported by the version of CP/M being used. The system call to get the version number returns a two byte value split into two parts as follows: if (H)=0 then this is a CP/M System (H)=1 then this is an MP/M System (L)=version number in hex if (L)=00 then older than CP/M 2.0 (L)=20 then version CP/M 2.0 (L)=21 then version CP/M 2.1 (L)=22 then version CP/M 2.2 A program to read the CP/M version number is as follows: ;VERSION NUMBER EXAMPLE ; GETVERS EQU 00CH ;FUNCTION 12 BDOS EQU 00005H ;SYSTEM ENTRY POINT ORG 0100H ;PROGRAM START MVI C,GETVERS ;FETCH VERSION NUMBER CALL BDOS MOV A,L ;SAVE CP/M VERSION NUMBER STA CURVERS RET ;BACK TO CCP ; CURVERS: DS 1 ;STORE THE VERSION NUM HERE END RESETTING THE CP/M DISK SYSTEM: Function 13. The CP/M operating system contains features to control access to files upon the disk drives. A directory checksum scheme, beyond the scope of this presentation, permits the operating system to determine when a disk has been changed in a drive thus preventing the a wrong disk from being written upon. This is neat except that in many cases an appliciations program may require disk changes as functions are changed or new files are required. This system control function permits the application to force read/write status to be set for all drives, drive A: to be logged, and reset of the default disk record buffer address to its default value of 080H within the CP/M base page. The following program sequence shows how to reset the disk system. ;RESET DISK SYSTEM EXAMPLE ; RESET EQU 0DH ;FUNCTION 13 BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H ;PROGRAM START MVI C,RESET ;SET UP FUNCTION CALL BDOS ;GO RESET THE DRIVES RET ;BACK TO THE CCP ; END GET AND SET OF CURRENT USER CODE: Function 32. CP/M Version 2.2 permits the file system on a given drive to be partitioned into up to 15 individual directory areas so that usage areas can be setup. For instance, the system operator could put all assembly language development programs in one user area while having disk utility programs in another. The BDOS allows the application programmer to determine the currently logged user number and to modify it if necessary. The following example sets the current user number up by one. If the highest user number is currently logged then the user 0 area is selected. ;GET/SET USER EXAMPLE ; GSUSR EQU 020H ;FUNCTION 20 GET EQU 0FFH ;GET FLAG BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H ;START UP POINT MVI E,SET ;MAKE THIS A FETCH NUM RQST MVI C,GSUSR CALL BDOS ;GET THE CURRENT USER # INR A ;BUMP RETURNED USER UP 1 ANI 00FH ;MASK TO MOD(15) MOV E,A ;MOVE FOR SET TO NEW USER MVI C,GSUSR CALL BDOS RET ;CCP GETS US BACK ; END SYSTEM FUNCTIONS THAT CONTROL THE DISKS The data storage files for applications programs are stored upon the disk drives attached to the CP/M computer. The BDOS supports a number of functions that allow the state and selection status of the drives to be controlled. SELECT DISK: Function 14. The simplest control function is to select the current disk with which to refer to as the logged or default disk. The function is equivalent to the console CCP command: A>B:<cr> B> Which changed the currently logged disk to drive B:. A BDOS program to affect the same thing is given in the example program of the next section below. Drive numbers correspond to the console displayed drive designators as follows: A: = Drive # 0 B: = Drive # 1 *** P: = Drive # 15 Once a drive has been selected it has its directory "activated" and is maintained in a logged in status until the next warm boot, cold boot, or disk reset BDOS function. DETERMINE LOGGED DISK: Function 25. An applications program can determine which disk drive is the currently logged or default drive through use of this function. The BDOS will return in the (A) register the number of the currently selected drive according to the table given above. The program segment below shows a sequence of BDOS interface code that first determines if drive B: is selected, and if not then does a BDOS call to change it. ;SELECT AND POLL LOGGED DISK DRIVE EXAMPLE ; SELECT EQU 0EH ;FUNCTION 14 ASKDRV EQU 19H ;FUNCTION 25 BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H ;PROG START MVI C,ASKDRV ;FIND OUT IF B: IS SELECTED CALL BDOS CPI 'B'-'A' RZ ;DONT SELECT IF ALREADY ;..LOGGED MVI E,'B'-'A' ;SET TO LOG AND SELECT B: MVI C,SELECT CALL BDOS RET ;FINISHED WITH ANOTHER PROG ; END DRIVE STATUS SET AND RESET: Functions 28 & 37. Drive status may be individually controlled by these functions. Operation 28 allows a the currently selected drive to be write protected (set to read/only). The process is simply: WPDSK EQU 01CH BDOS EQU 0005H MVI C,WPDSK ;WRITE PROTECT DISK CALL BDOS The write protect status of a specific disk may be removed by function 37 which deactivates the directories of each drive specified at call time. Each drive by default then becomes read/write again but requires reactivation through reselection. The reset drive vector is a 16-bit value passed to the BDOS with a "1" bit in each bit position for a drive that equires resetting. The most significant bit of the 16 bit quanity corresponds to drive P: and the LSB to drive A:. The code sequence to reset drive B: would be: RESDSK EQU 025H BDOS EQU 0005H MVI C,RESDSK ;FUNCTION CODE LXI D,0000$0000$0000$0010B ;DRIVE B: BIT SET CALL BDOS GET DRIVE LOGIN AND READ?ONLY VECTORS: Function 24 & 29. The BDOS keeps track of all drives that have been selected since the last boot or disk reset functions. These drives are considered in a online status in that the system knows immediately what the space allocation map of the drive is and whether the drive is in read/only status or not. Function 24 allows the application program to determine what subset of the current drive complement are in this online logged status. The vector returned in the (HL) register pair is a bit map like above where a "1" bit means the drive is active. The most significant bit of the 16-bit number corresponds to drive P:. The code below fetches the vector and saves it in a local data area. ;LOGIN VECTOR EXAMPLE ; LOGIN EQU 018H ;FUNCTION 24 BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H MVI C,LOGIN ;FUNCTION CALL BDOS SHLD LOCLOG ;SAVE VECTOR HERE RET ;TO CCP ; LOCLOG: DS 2 END In a similar manner the BDOS allows determination of which drives are in the write protected read/only status. A "1" bit in the returned vector indicates read/only status for a specific drive. The code here shows how to fetch it. ;READ/ONLY VECTOR EXAMPLE ; ROVEC EQU 01DH ;FUNCTION 29 BDOS EQU 0005H ;SYSTEM ENTRY POINT ORG 0100H MVI C,ROVEC ;FUNCTION CALL BDOS SHLD LOCROV ;SAVE VECTOR HERE RET ;TO CCP ; LOCROV: DS 2 END GET ALLOCATION VECTOR AND DISK PARM POINTER: Function 27 & 31. Two more miscellaneous disk drive interface functions are provided that permit several special types of functions to be performed. The first, function 27 returns an address in the (HL) registers that points to a bit string in memory that corresponds to the data block allocation map of the currently selected drive. The map contains one bits in each position where a block allocated, starting with the MSB of the forst byte in the string. The length of the bit string depends upon the total capacity of the drive in allocatable blocks. Function 31 permits an application to determine the characteristics of the currently selected drive. The BDOS returns an address in the (HL) registers that points to a table of 33 bytes that describe the current drive. Data in the table includes such data as number of possible directory entries on the disk, number of allocatable blocks on the disk, and, indirectly, the size of each disk block. The program below is a comprehensive example of how these functions can be used to determine the remaining space left on a the selected drive. The program stores the available space of the drive specified in the first byte of the default FCB into memory location "KPDISK" and then exits to the CCP. The reader can adapt the code as desired. ; ;CP/M BDOS INTERFACE EQUATES ; BASE EQU 0000H ;BASE OF CP/M SYSTEM LOGDRIV EQU 0004H+BASE ;LOCATION OF CURRENTLY LOGGED DRIVE BDOS EQU 0005H+BASE ;THE BDOS I/O VECTOR SLCTDSK EQU 14 ;SELECT DISK DRIVE GALVEC EQU 27 ;GET ADDRESS ALLOCATION VECTOR GDSKP EQU 31 ;GET ADDRESS OF DISK PARAMETER TABLE ; ; ORG 0100H ; ; ;PROGRAM TO FETCH REMAINING DISK SPACE IN KBYTES ; SPCGET: LDA LOGDRIV ;GET CURRENTLY LOGGED DRIVE AND SAVE ANI 0FH ;STRIP OUT USER NUMBER STA SAVDRIV ;SAVE CODE ; LDA FCB ;CHECK IF SAME AS SELECT DCR A ;ADJUST FCB DRIVE TO MATCH SELECT DRIVE MOV E,A ;..SELECT IN BDOS MVI C,SLCTDSK ;SELECT DISK FUNCTION CALL BDOS ; MVI C,GDSKP ;FIND ADDRESS OF DISK PARAMETER HEADER CALL BDOS LXI B,0002H ;INDEX TO BLOCK SHIFT FACTOR DAD B MOV B,M ;(B) = BYTE BLOCK SHIFT FACTOR INX H INX H INX H MOV E,M ;(DE) = WORD DISK BLOCK COUNT INX H MOV D,M INX D ; MOV A,B ;ADJUST SHIFT FOR KBYTE SIZE SUI 03H LXI H,0001H ;CALCULATE BLOCK SIZE SPCCAL: ORA A ;KNOW KBYTES PER BLOCK? JZ SPCKNW DAD H ;DOUBLE # SECTORS PER TRACK DCR A ;DECREMENT BLOCK SHIFT JMP SPCCAL ; SPCKNW: MOV C,L ;(BC)=KBYTES PER BLOCK MOV B,H LXI H,0 ;INITIALIZE KPDISK SHLD KPDISK PUSH B ;SAVE KBYTES/BLOCK PUSH D ;SAVE NUMBER OF BLOCKS MVI C,GALVEC ;NOW POINT TO THE ALLOCATION VECTOR CALL BDOS ;(HL)=ALLOCATION VECTOR ADDRESS POP D POP B ; SHLD ALLSAVE ;SAVE ALLOCATION POINTER MVI H,1 ;SET MINIMUM START BIT COUNT ; UALLOC: DCR H ;DEC BIT COUNT JNZ STACT ;STILL ACTIVE BYTE ; LHLD ALLSAVE ;GET POINTER MOV A,M INX H SHLD ALLSAVE ;SAVE NEW POINTER MVI H,08H ;SET BIT COUNTER TO MAX ; STACT: RLC ;GET ALLOCATION BIT TO CARRY JC ALLOC ;DONT COUNT ALLOCATED BLOCKS PUSH H LHLD KPDISK ;GET KBYTES LEFT COUNT DAD B ;ADD IN ONE MORE BLOCK COUNT SHLD KPDISK POP H ; ALLOC: DCX D ;DEC TOTAL BLOCK COUNT MOV L,A MOV A,D ORA E ;ALL BLOCKS SCANNED YET MOV A,L ;RESTORE ALLOC BIT PATTERN JNZ UALLOC ;MORE TO COUNT ; LDA SAVDRIV ;RETURN DISK SELECT TO PREVIOUS MOV E,A ;..SELECT IN BDOS MVI C,SLCTDSK ;SELECT DISK FUNCTION CALL BDOS RET ;BACK TO THE CCP ; ; ;PROGRAM DATA STORAGE ALLOCATIONS ; BLKSIZ: DS 2 ;STORAGE FOR ALLOCATION BLOCK SIZE ALLSAVE: DS 2 ;STORAGE FOR ALLOCATION PNT SAVE SAVDRIV: DS 1 ;SAVE CURRENT DISK SELECT DURING RELOG KPDISK: DS 2 ;STORAGE FOR KBYTES PER DRIVE LEFT ; END The next part in this series will present the the CP/M file system as viewed from the BDOS interface aspect. The FILE CONTROL BLOCK (FCB) will be presented. In addition the procedures to prepare files for I/O and then the actual I/O procedures will be presented. The series will round out to a conclusion with a comprehensive programming example that presents a sequential file I/O set of subroutines that permit character by character I/O with a file to be done.
SLIDING INTO BDOS (Part II) WITH FILES MADE EASY by: Michael J. Karas 2468 Hansen Court Simi Valley, CA 93065 (805) 527-7922 Since I know that all devoted Life Lines readers have anxiously been waiting for this "second in a series" tutorial on using files with the CP/M BDOS, I will not go on a long time telling you why this thing about CP/M BDOS file interface is so important. Nor will I try to justify why the turorial should be valuable. You wouldn't be reading here at this time if you had any inclination to find my work disinteresting. If you are new on the scene and have some questions about what this is all about I would like to direct your attention to the November 1982 issue of Life Lines where the first part of this tutorial series was presented. There the purpose of the BDOS and the general interface concepts were presented. The article went on to include a description of the physical device system calls and other miscellaneous system control type functions. THIS TIME IT'S FILES This month the tutorial continues with a description of the sequential file I/O system supported within the BDOS. The con- cepts of CP/M file storage are to be described along with appropiate CP/M directory structure definition as it relates to the access of the files stored upon a CP/M disk. The FILE CONTROL BLOCK (FCB) will be described in terms of its functions as related the a file to be accessed upon a disk. I have also included a comprehensive programming example that allows a sequential file to be accessed character by character. HOW FILES ARE STORED UPON THE DISK The CP/M operating system manages the available space on a disk by dividing the total available space up into a number of relatively small data block storage areas called "GROUPS". A group size is usually described as the minimum allocatable space that a file can occupy. What this means is that the operating system, in its disk space management scheme, lumps sets of the normal 128 byte logical records of a file together into these things called groups. The number of groups that may be contained on a disk depends upon the total file storage space of the disk in logical 128 byte records divided by the number of 128 byte logical records lumped together into a group. (A note to the less casual reader is that the number of groups on a disk is limited by design to 65K groups. Secondly a group is always an integral power of two number of 128 byte logical records with a minimum size of 8 records (1K byte). Group size is necessarily limited to 16K bytes due to the extent system described below). As a file is stored upon a CP/M disk it consumes disk space in 128 byte logical records. Each time a group becomes filled with records the operating system allocates another group to the file. Hence the term "minimum allocatable size". If, as the file grows in size, the last allocated group assigned to a file is not completely filled the remaining space in the group is "burned" in that it is not usable by other files. The CP/M system keeps track of the group assignments made to the various files on a disk, the files names, and the total number of 128 byte logical records in each file through a stored directory. The first portion of the disk is reserved for the file directory. A fixed number of directory entries, determined by the system's BIOS design, are available, usually a number like 64, 128, or 256, depending upon the size of the disk. Each file has a unique directory entry "set" that describes the file location upon the disk. A "set" of directory entries is specified because each entry is designed to "point to" or store the group allocation numbers for that file. Each directory entry has a number slots where group numbers can be stored. The system design allows each directory entry to specify the storage for 16K bytes of storage space. For files larger than 16K bytes a seperate directory entry is used for each 16k bytes (or remainder portion thereof). Each such piece of a file is referred to as an "EXTENT" of the file. The directory entry "set" for a file contains a byte in each extent directory entry that stores the extent number of the file. Extent numbers start with 0 and may increase to a theoretical limit of 255 or the size of the disk in 16K byte pieces, whichever is smaller. The chart below describes the functions of all bytes in a typical directory entry. Each entry is 32 bytes long and they are packed four to a logical sector with the number of logical sectors filled up with directory entries limited to the predetermined number of directory entries divided by four. Figure 1. DISK DIRECTORY ENTRY DEFINITION byte 00 byte 01 byte 02 byte 03 byte 04 byte 05 byte 06 byte 07 +-------+-------+-------+-------+-------+-------+-------+-------+ |Active | | | |Entry | Eight Character ASCII File Name Bytes 01 to 08 | |& User | | |Flag | | +-------+-------+-------+-------+-------+-------+-------+-------+ byte 08 byte 09 byte 10 byte 11 byte 12 byte 13 byte 14 byte 15 +-------+-------+-------+-------+-------+-------+-------+-------+ |Last | | | |Record | | |File | Three character ASCII |Extent | Two Bytes |Count | |Name | File Name extension |Number | Reserved |of this| |Char | | | |Extent | +-------+-------+-------+-------+-------+-------+-------+-------+ byte 16 byte 17 byte 18 byte 19 byte 20 byte 21 byte 22 byte 23 +-------+-------+-------+-------+-------+-------+-------+-------+ | | | Group Number storage for groups attached to this file | | One byte used per group number if disk contains less | | 255 groups. Two bytes if greater than 256. | +-------+-------+-------+-------+-------+-------+-------+-------+ byte 24 byte 25 byte 26 byte 27 byte 28 byte 29 byte 30 byte 31 +-------+-------+-------+-------+-------+-------+-------+-------+ | Additional Group Number storage. | | Group Number storage for groups attached to this file | | One byte used per group number if disk contains less | | 255 groups. Two bytes if greater than 256. | +-------+-------+-------+-------+-------+-------+-------+-------+ The bytes of the disk directory entry are each described in the following paragraphs. The first byte stored in an entry is set to indicate if this slot in the predetermined directory area is empty or if it describes an active file extent. A value of 0E5H indicates an empty slot. This value was chosen presumably due to that a freshly formatted diskette contains all 0E5H bytes in the empty sectors, thus making such disk appear to have no files contained thereon. If the byte value is non 0E5H, then the slot contains a valid file extent descriptor. The CP/M user number area to which an active file is associated is stored in the first directory entry byte. User number values range from 0 to 15. The next eight bytes contain the primary name of the file in ASCII characters. If the name is shorter than 8 characters then the name is padded to the right with spaces. Following the name field is a three byte file name extension field in ASCII characters. The extension field, if shorter than 3 characters is padded to the right with spaces. For CP/M version 2.2, the upper bits (bit 7) of the extent name bytes are used to describe certain attributes about the file. If the upper bit of the first extent name character is set, then the file is described as a read-only file. The upper bit of the second extent name character, if set, indicates that the file name should not be displayed in directory listings. Each directory entry, as a file descriptor extent, has the next byte set to a number that specifies which 16K byte chunk of the file that this entry describes. Two bytes after the extent byte are not used within the directory and are normally set to zero by default. The number of records stored in the extent, described by this directory entry, is recorded in the byte 15 position. The maximum value for the record count is 128 (080H) which if equal to (128 * 128) or 16K bytes, the maximum size of an extent. Byte positions 16 to 31 contain the group numbers upon the disk that contain the data belonging to the file named in the directory entry. The number of bytes within the total 16 available that are used for group number storage is dependant upon the amount of file data described by this extent and by the group size of the disk. The group numbers are single byte numbers, up to 16 total, if the number of groups upon the disk is less than or equal to 255. If the number of groups upon the disk is more than 255 then byte positions 16 to 31 contain two byte group numbers, stored in low byte/high byte order. The group numbers contained within a directory entry do not have to be in increasing sequential order nor do they have to be consecutive. The figure below shows two logical records of the directory from a single sided double density disk with 2K byte groups. The total number of groups available is 243 so the group numbers are single byte numbers. Note that only one half of the 16 byte space for group numbers is used due to the fact that 8 entries for 2K byte groups is all that is needed to describe the storage for one full 16K byte extent. Figure 2. EXAMPLE HEX/ASCII DIRECTORY RECORD DISPLAY 00 00414449 52202020 20434F4D 0000000B .ADIR COM.... 10 07000000 00000000 00000000 00000000 ................ 20 004D4552 47505249 4E4F5652 0000003C .MERGPRINOVR...< 30 16171819 00000000 00000000 00000000 ................ 40 00434F50 59202020 20434F4D 0000000E .COPY COM.... 50 0C000000 00000000 00000000 00000000 ................ 60 00435243 4B202020 20434F4D 0000000A .CRCK COM.... 70 0D000000 00000000 00000000 00000000 ................ 00 E5555345 52202020 204C4F47 00000030 eUSER LOG...0 10 04050600 00000000 00000000 00000000 ................ 20 00444454 20202020 20434F4D 00000026 .DDT COM...& 30 0F101100 00000000 00000000 00000000 ................ 40 0044552D 56373520 20434F4D 0000002E .DU-V75 COM.... 50 12131400 00000000 00000000 00000000 ................ 60 00464F52 4D415420 20434F4D 0000000C .FORMAT COM.... 70 15000000 00000000 00000000 00000000 ................ The above examlpes all show files that are less than 16K bytes each. Note also the display showing the erased "USER.LOG" file. HOW FILES ARE ACCESSED The files upon a disk are accessed through a user description block called a File Control Block (FCB for short). The file control block, used by virtually all file access BDOS system calls, has the structure as shown in Figure 3. This chart is taken from a Digital Research CP/M manual and is included here for quick educational reference. Note that the structure of a file control block is much the same as that of a directory entry with a few minor changes. The changes and/or differences are as follows, otherwise the byte descriptions are the same as for the disk directory entry. The first byte of an FCB allows the programmer to specify which drive should be used for the file access. Drive A: to P: are specified as 1 to 16 respectively while a value of zero indicates that the currently logged default drive should be used for the access. An FCB contains four additional bytes that are used as pointers for file access position. The "cr", current record number, indicates the sequential record number of this extent that will be accessed upon the next file read or file write system call. The user normally sets the "cr" byte to zero to begin file access at the first logical record of the file. Each time a read or write is performed the current record number is incremented. When the "cr" byte attains a value of 080H during a sequential file operation the BDOS automatically realizes that the current extent of the file has been fully accessed and performs the necessary disk directory accesses to setup the FCB to allow file access to the next extent. For reading this simply means that the next extent descriptor directory entry from the disk, for this file, is read into memory (ie. the group allocation numbers from the disk are copied into the d0-dn bytes of the FCB, the extent number becomes one greater, the record count from the disk for the new extent is copied into the "rc" byte and the cr byte is zeroed). During a writing operation the "cr" byte attaining a value of 080H indicates that the current extent of the file is full and so the BDOS automatically finds the appropiate directory entry spot on the disk to write in the newly assigned group allocation bytes, record count value and extent number. The BDOS will then create another directory entry on the disk for the new extent of the file. In this case the d0- dn bytes of the FCB are zeroed to indicate that storage has not yet been allocated for this extent. Figure 3. FILE CONTROL BLOCK DESCRIPTION ------------------------------------------------------------ |dr|f1|f2|/ /|f8|t1|t2|t3|ex|s1|s2|rc|d0|/ /|dn|cr|r0|r1|r2| ------------------------------------------------------------ 00 01 02 ... 08 09 10 11 12 13 14 15 16 ... 31 32 33 34 35 where: dr drive code (0 - 16) 0 => use default drive for file access 1 => select drive A: for file access 2 => select drive B: for file access ... 16=> select drive P: for file access f1...f8 contain the files name in ASCII upper case with high bits equal to zero. t1,t2,t3 contain the file type in ASCII upper case with high bits normally equal zero. tn' denotes the high bit of these bit positions. t1' = 1 => Read/Only file t2' = 1 => SYS file, no DIR list ex contains the current extent number, normally set to 00 by the user, but is in the range 0 - 31 during file I/O. s1 reserved for internal system use s2 reserved for internal system use, set to zero on call to OPEN, MAKE, SEARCH system calls. rc record count for extent "ex," takes on values 0 to 128. d0...dn filled-in by BDOS to indicate file group numbers for this extent. cr current record to read or write in a sequential file operation. Normally set to zero by the user upon initial access to a file. r0,r1,r2 optional random record number in the range of 0 to 65535, with overflow to r2. r0/r1 are a 16 bit value in low/high byte order. The last three bytes of the FCB, r0,r1, & r2 are used for random record file I/O and will be covered in the third and final part of this turorial. For simpler sequential I/O the FCB in fact does not even need to be setup for the 36 bytes of storage. 33 bytes suffice for all sequential file I/O FCB operations. FILE ACCESS SETUP SYSTEM FUNCTIONS The procedure for the programmer to use in accessing a file generally starts in one of two ways. The first senario starts with, "Lets see if our file exists on the disk?" There are two BDOS system calls related to the functions of searching the disk directory for a file name match against the FCB specified by the user. These operations allow for the programmer to find out if a specific file name already exists upon the disk. In addition it provides a mechanism to scan a directory to determine all file names that exist in the directory. The second situation comes into being if the programmer is already aware of the file status with respect to "presence" on the disk or as the logical sequence of events following the first senario. These latter functions are used to work with specific files for opening, closing, creating, renaming and deleting. SEARCH FIRST AND SEARCH NEXT: Functions 17 and 18. The search functions scan the directory for match of a file name that compares with the user specified FCB pointed to by the (DE) register pair. The match is made on the basis of comparing the f1-f8, t1-t3, and ex bytes of the FCB to the corresponding bytes of the disk directory entries. Any FCB position that contains an ASCII question mark "?" (03FH) is specified as a "match any character" from the disk directory. The function calls return a value of 0FFH in the (A) register if no more matched directory entries can be found. The search functions cause the currently valid disk buffer address and the following 128 bytes to be filled with a copy of the directory record containing the matched entry, if one is found. The (A) register is returned with a 0 to 3 value to indicate which one of the four possible 32 byte chuncks of the directory record contain the matched entry. Search first means to find the first occurrance of a matched entry to the FCB. The search next function scans the directory from the current search position instead of from the beginning. Note that it is not normally valid to perform the search next functon without first performing the search first function. Also it is not valid to perform other directory or file operations between the search first and search next functions. The program example below shows a technique for reading all directory entries from the disk drive specified by the first FCB byte into a memory resident list. The list starts at the LIST label with the total matched file count stored in the FILECNT variable. The LISTPOS label stores the next available list load point during the directory scan operation. The search FCB uses the CP/M default FCB location at address 05CH and specifies a total wild card (*.*) match. The "ex" byte is zeroed before the search first call so that only the zero extents of the files are returned. The file names are stored in the list in character strings of 16 bytes each with a preceeding drive designator byte and padded to the right with 4 zero bytes. Please note that this program is a segment only and will not directly assemble and run as a CP/M .COM file without a little added lead in and error exit coding. Listing 1. A DIRECTORY SCANNING PROGRAM BUFR EQU 80H+BASE ;DEFAULT CP/M BUFFER BDOS EQU 0005H ;ENTRY POINT FOR BDOS OPERATIONS ; SRCHF EQU 17 ;SEARCH DIR FOR FIRST OCCUR. SRCHN EQU 18 ;SEARCH DIR FOR NEXT OCCUR. STDMA EQU 26 ;SET DMA ADDRESS ; FCB EQU 5CH+BASE ;DEFAULT FILE CONTROL BLOCK FCBEXT EQU FCB+12 ;EXTENT BYTE IN FCB FCBRNO EQU FCB+32 ;RECORD NUMBER IN FCB ; ; ;SETUP SIZE OF ELEMENTS IN THE FILE NAME LIST ; ITEMSZ EQU 16 ;EACH LIST ITEM IS 16 BYTES ; ; ;SETUP WILD CARD FILE IMAGE LIKE *.* ; LXI H,FCB+1 ;PLACE TO PUT WILD CARD IMAGE MVI B,11 ;SIZE TO SET ALFN: MVI M,'?' ;PUT IN A JOKER CHAR INX H ;BUMP FILL POINTER DCR B ;DCR BYTE COUNTER JNZ ALFN ; ; ;ZERO INITIAL TOTAL FILE COUNT ; LXI H,0000H SHLD FILECNT ; ; ;HERE IF NAME PROPERLY POSITIONED IN THE DEFAULT FCB AREA FOR LIST BUILD ; NAMEPRES: MVI C,STDMA ;INITIALIZE DMA ADDRESS TO DEFAULT BUFFER LXI D,BUFR CALL BDOS ; XRA A ;CLEAR APPROPIATE FIELDS OF SEARCH FCB STA FCBEXT ;EXTENT BYTE STA FCBRNO ;AND RECORD NUMBER ; LXI D,FCB ;USE DEFAULT FCB FOR SEARCH MVI C,SRCHF ;SEARCH FOR FIRST OCCURRANCE CALL BDOS CPI 0FFH ;SEE IF FOUND JNZ LOADLIST ;IF SOME FOUND THEN GO BUILD LIST ; ; ;PUT INSTRUCTIONS HERE TO HANDLE A SITUATION WHERE NO FILES ;MATCHING THE FCB WILD CARD IMAGE ARE FOUND. ; JMP ERROR$EXIT ;TO USER SUPPLIED ROUTINE ; ; ;BUILD UP LIST WITH ALL FOUND ENTRIES ; LOADLIST: LXI H,LIST ;INITIALIZE LIST POINTER PARAMETERS SHLD LISTPOS ;START = CURRENT POS OF LIST ; ; ;PUT CURRENTLY FOUND NAME TO LIST ;(A) = OFFSET IN DEFAULT BUFFER OF NAME ; ; NM2LST: ANI 3 ;ZERO BASED TWO BIT INDEX ADD A ;TIMES 32 TO MAKE POSITION INDEX ADD A ADD A ADD A ADD A MOV C,A ;PUT IN BC XRA B ;CLEAR HIGH ORDER LXI H,BUFR ;TO NAME POSITION IN DEFAULT BUFFER DAD B ;(HL) = CURRENT FOUND NAME POINTER LDA FCB ;PUT DISK DRIVE NUMBER INTO NAME PLACE MOV M,A ;INTO BUFFER XCHG LHLD LISTPOS ;POINTER TO CURRENT LOAD POINT IN LIST XCHG MVI B,12 ;MOVE DRIVE DESIGNATOR AND NAME TO LIST MOVLP: MOV A,M ;GET NAME BYTE FROM DEFAULT BUFFER STAX D ;PLACE INTO LIST INX H ;BUMP POINTERS INX D DCR B ;CHECK MOVE BYTE COUNT JNZ MOVLP XCHG ;(DE) WAS LEFT WITH LEXT LOAD POINT ADDRESS ; MVI B,ITEMSZ-12 ;REMAINING LIST ITEM SPACES TO ZERO OUT FILZRO: MVI M,00H ;PUT IN A ZERO BYTE INX H DCR B ;ALL REST FILLED YET JNZ FILZRO ; SHLD LISTPOS ;KEEP NEXT LOAD POINT IN SAFE PLACE LHLD FILECNT ;INCREASE FILE COUNT FOR EACH FILE INX H SHLD FILECNT ; ; ;SEARCH FOR NEXT OCCURANCE OF SPECIFIED FILE NAME ; MVI C,SRCHN ;SEARCH NEXT FUNCTION CODE LXI D,FCB ;FILE NAME SPECIFICATION FIELD CALL BDOS CPI 0FFH ;SEE IF ALL THROUGH DIRECTORY YET JNZ NM2LST ;IF NOT GO PUT NAME INTO LIST ; ; ;PROGRAM EXECUTION TO HERE IF THE LIST CONTAINS SOME FILE NAMES ;FROM THE DISKETTE ; ;USER DOES HIS OWN THING FROM HERE ; ; ;DIRECTORY NAME LIST FOR STORAGE OF INPUT NAMES ; FILECNT: DS 2 ;COUNTER FOR NUMBER OF FILES LISTPOS: DS 2 ;STORAGE FOR CURRENT LIST ;LOAD POINTER ; LIST: DS 1 ;START POINT FOR FILE NAME LIST ; ;+++...END OF LISTING 1. OPEN FILE: Function 15. An existing file on a disk may not be read until the user FCB contains the information about where the file is stored upon the diskette. Function 15 provides a means where the user fills in the file name and then calls the operating system to get the d1-dn bytes of the FCB filled in. Once the file is OPEN then it may be read because subsequent calls to the BDOS to READ will "know where" the file is located. The OPEN function returns a value of 0FFH if the file cannot be found, otherwise the (A) register contains a value of 0 to 3 to indicate that the file was successfully opened. To open a file the programming procedure is simply: ; ;OPEN FILE EXAMPLE ; OPEN EQU 15 ;OPEN FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,FCB ;POINT AT FILE CONTROL BLOCK MVI C,OPEN ;FUNCTION CALL BDOS CPI 0FFH ;CHECK IF NOT FOUND JZ ERROR RET ;IF OPEN GO TO CCP ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'FILE NOT FOUND','$' ; ; ;FILE ACCESS FILE CONTROL BLOCK ; FCB: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES DB 0 ;CURRENT RECORD BYTE ; END CLOSE FILE: Function 16. Whenever a file is accessed for writing new space is allocated for that file on the disk. This implies that the user FCB contains disk group numbers that are not stored upon the diskette in the directory entry for the file. Function 16 provides a means where the user completes the file writing operation and then calls the operating system to set the directory entry group allocation bytes, the rc byte and the extent byte from the corresponding bytes of the FCB. A file that has been opened for reading only need not be closed because there is no change in the stored disk directory information. The CLOSE function returns a value of 0FFH if the file cannot be found, otherwise the (A) register contains a value of 0 to 3 to indicate that the file was successfully closed. To close a file the programming procedure is simply: ; ;CLOSE FILE EXAMPLE ; CLOSE EQU 16 ;CLOSE FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,FCB ;POINT AT FILE CONTROL BLOCK MVI C,CLOSE ;FUNCTION CALL BDOS CPI 0FFH ;CHECK IF NOT FOUND JZ ERROR RET ;IF CLOSED GO TO CCP ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'FILE NOT FOUND','$' ; ; ;FILE ACCESS FILE CONTROL BLOCK ; FCB: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES DB 0 ;CURRENT RECORD BYTE ; END DELETE FILE: Function 19. Often time the programmer will create and write files which will subsequently not be needed. The file or files may be deleted through use of function 19. The user sets an FCB to the appropiate file name in the f1-f8, and t1-t3 bytes. The BDOS function then removes the specified file from the directory of the appropiate disk. The user specified file name in the FCB may contain ASCII question marks in which case the delete function may delete multiple files if the file name matches more than one file on the disk with the name. The "?" matches any character at the position of its occurrance in the name. The DELETE function returns a value of 0FFH if the file(s) cannot be found, otherwise the (A) register contains a value of 0 to 3 to indicate that the file was successfully deleted. To delete a file the programming procedure is simply: ; ;DELETE FILE EXAMPLE ; DELETE EQU 19 ;CLOSE FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,FCB ;POINT AT FILE CONTROL BLOCK MVI C,DELETE ;FUNCTION CALL BDOS CPI 0FFH ;CHECK IF NOT FOUND JZ ERROR RET ;IF CLOSED GO TO CCP ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'FILE NOT FOUND','$' ; ; ;FILE ACCESS FILE CONTROL BLOCK ; FCB: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES DB 0 ;CURRENT RECORD BYTE ; END CREATE FILE: Function 22. Whenever a new file is desired it must first be created so that there is a spot in the directory to later save the file allocation information (see close function above). The BDOS assumes that the programmer has specified a file name that does not exist upon the disk. If there is a chance that a new file is desired that may duplicate the name of one already upon the disk the peviously described delete function should be used to erase the old file before creating the new file. Otherwise the directory may contain two files by the same name. The CREATE function returns a value of 0FFH if there is no room in the directory to store the freshly created directory entry, otherwise the (A) register contains a value of 0 to 3 to indicate that the file was successfully created. A newly created file may be immediately written since the BDOS prepares the user FCB to look like an empty file. To create a file the programming procedure is simply: ; ;CREATE FILE EXAMPLE ; CREATE EQU 22 ;CREATE FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,FCB ;POINT AT FILE CONTROL BLOCK MVI C,CREATE ;FUNCTION CALL BDOS CPI 0FFH ;CHECK IF DIRECTORY FULL JZ ERROR RET ;IF CLOSED GO TO CCP ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'DIRECTORY FULL','$' ; ; ;FILE ACCESS FILE CONTROL BLOCK ; FCB: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES DB 0 ;CURRENT RECORD BYTE ; END RENAME FILE: Function 23. Sometimes it is necessary to change the name of a disk file from that already existing in the disk directory. With function 23 the user specifies the name of an existing file on the disk with a standard FCB format except that on calling the BDOS the d1-dn byte area of the FCB are set to the new name desired for the file. All occurrances of the existing file name (ie. all extents) are changed to match the new name. The drive select byte specifies the drive upon which the rename operation should be done. The first byte of the second 16 bytes of the FCB (d0) is expected to be zero. The RENAME function returns a value of 0FFH if the old name file could not be found, otherwise the (A) register contains a value of 0 to 3 to indicate that the file was successfully renamed. To rename a file the programming procedure is simply: ; ;RENAME FILE EXAMPLE ; RENAME EQU 23 ;RENAME FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,FCB ;POINT AT FILE CONTROL BLOCK MVI C,RENAME ;FUNCTION CALL BDOS CPI 0FFH ;CHECK IF DIRECTORY FULL JZ ERROR RET ;IF CLOSED GO TO CCP ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'FILE NOT FOUND','$' ; ; ;FILE ACCESS FILE CONTROL BLOCK ; FCB: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 ;OLD NAME DB 00H ;BYTE ASSUMED TO BE ZERO DB 'NEWNAME DAT',0,0,0,0 ;NEW NAME DB 0 ;CURRENT RECORD BYTE ; END ACCESSING FILE DATA The previous section showed the reader how to find and setup files for subsequent I/O. Other file/directory handling functions were also presented. This has all led up to the big moment when the users program is finally ready to read or write data from/to a disk file. So here it is at last... CP/M disk file data is moved between the disk and memory in blocks of 128 bytes called logical records or "sectors" in older fashioned CP/M lingo. Two functions to be presented here are included in the CP/M BDOS function code to allow sequential access to blocks of data in a file. The READ function starts at the beginning of a file and reads data blocks till the end of the file. The opposing WRITE operation moves data blocks to a new disk file and writes till the end of the users data when the file is closed (or the disk is full if the programmer has too much data). The BDOS includes one other function that allows the user to specify the area in his program where the 128 byte disk record buffer is to be located. These three functions will each be individually described below. SET DISK BUFFER ADDRESS: Function 26. The 128 byte data buffer that is to be used by the BDOS for file I/O is based at an address commonly referred to as the "DMA ADDRESS". This address or "buffer pointer" is passed to the BDOS in the (DE) registers when performing function 26. The program below simply sets the buffer address to "DATBF", a storage area after the end of the short program. ; ;SET BUFFER ADDRESS EXAMPLE ; STDMA EQU 26 ;SET BUFFER ADDRESS FUNCTION CODE BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,DATBF ;POINT AT DATA BUFFER MVI C,STDMA ;FUNCTION CALL BDOS RET ;BACK TO CCP ; DATBF: DS 128 ;SETUP 128 BYTE BUFFER ; END READ AND WRITE DISK RECORDS: Functions 20 and 21. The disk read and write functions are very similar in operation in that both move 128 bytes of data to/from the users program. The READ assumes entry with (DE) pointing to an active FCB setup by the open file function. The read sequential function reads the 128 byte record specified by the "cr" field of the FCB into the buffer pointer to by the current disk buffer address. After each READ operation the "cr" field is incremented to the next record number. If the "cr" field overflows past the end of the extent without encountering the end of the file then the BDOS automatically opens the next extent in preparation for the next read operation. The READ function returns a 00H code in the (A) register if the READ was performed successfully. If the end of file is encountered a non zero value is returned in (A). The WRITE function assumes, on entry to the BDOS, that the (DE) registers point at a validly opened of created FCB. The WRITE will move 128 bytes of data from the buffer specified by the current disk buffer address to the disk. The written record is placed at the "cr" record position of the extent. As each record is written the "cr" field is incremented in preparation for the next write operation. Similar to the READ, if the "cr" field overflows past the end of the current extent, the BDOS automatically closes the current extent and creates a new extent in preparation for the next write operation. The WRITE command may be performed on an existing file. If the file currently contains data at the "cr" record then the WRITE will overlay the current data with the new 128 byte record. The WRITE function returns a 00H value in the (A) register if the operation is successful. A non-zero value is returned if the write function was unsuccessful due to a full disk or directory. The small program below is designed to read the first record of a file 'TEST.DAT', and write it into the small file 'ONEREC.DAT'. The program should be reasonably self documenting. ; ;READ AND WRITE FUNCTION EXAMPLES ; READ EQU 20 ;READ FUNCTION CODE WRITE EQU 21 ;WRITE FUNCTION CODE OPEN EQU 15 ;OPEN FUNCTION CODE CLOSE EQU 16 ;CLOSE FUNCTION CODE DELETE EQU 19 ;DELETE FUNCTION CODE CREATE EQU 22 ;CREATE NEW FILE STDMA EQU 26 ;SET DISK BUFFER ADDRESS BDOS EQU 0005H ;SYSTEM ENTRY ORG 0100H ;START LXI D,DATBF ;POINT AT DATA BUFFER MVI C,STDMA ;FUNCTION CALL BDOS ; LXI D,FCBIN ;POINT AT AND OPEN INPUT FILE MVI C,OPEN CALL BDOS CPI 0FFH ;CHECK FOR OPEN ERROR JZ ERROR ; LXI D,FCBOUT ;DEFAULT DELETE OF NEW FILE MVI C,DELETE ;..IN CASE IT EXISTS ALREADY CALL BDOS LXI D,FCBOUT ;POINT AT FILE CONTROL BLOCK MVI C,CREATE ;FUNCTION TO MAKE NEW FILE CALL BDOS CPI 0FFH ;CHECK IF DIRECTORY FULL JZ ERROR XRA A ;CLEAR THE INPUT CR FIELD TO READ STA INCR ;..FIRST RECORD LXI D,FCBIN ;READ FIRST FILE MVI C,READ CALL BDOS ORA A ;CHECK IF READ WAS O.K. JNZ ERROR LXI D,FCBOUT ;WRITE TO OUTPUT FILE MVI C,WRITE CALL BDOS ORA A ;CHECK THAT DISK WASNT FULL JNZ ERROR ; LXI D,FCBOUT ;CLOSE THE OUTPUT FILE MVI C,CLOSE CALL BDOS CPI 0FFH ;CHECK CLOSE STATUS RNZ ;BACK TO CCP IF NO ERROR ; ERROR: MVI C,9 ;PRINT ERROR MESSAGE LXI D,ERRMS CALL BDOS RET ; ERRMS: DB 'PROGRAM FILE ERROR','$' ; ; ;FILE ACCESS FILE CONTROL BLOCKS ; FCBIN: DB 00H ;SET TO USE DEFAULT DRIVE DB 'TEST DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES INCR: DB 0 ;CURRENT RECORD BYTE ; FCBOUT: DB 00H ;SET TO USE DEFAULT DRIVE DB 'ONEREC DAT',0,0,0,0 DS 16 ;STORAGE FOR D1 TO DN BYTES DB 0 ;CURRENT RECORD BYTE ; DATBF: DS 128 ;SETUP 128 BYTE BUFFER ; END SEQUENTIAL FILE I/O PROGRAMMING EXAMPLE The assembly language code of Listing 2 presents a comprehensive set of I/O routines that allow either an input or output sequential file to be processed on a byte by byte basis. The routines perform all necessary sector buffering. The reader is encouraged to fully study the code and gain an understanding of how it all works. The program uses most of the BDOS functions presented in this turorial. Listing 2. CHARACTER BY CHARACTER DISK I/O ROUTINES ;**************************************************************** ; ; DEMONSTRATION SEQUENTIAL CP/M FILE CHARACTER BY ; CHARACTER I/O ROUTINES. NOTE THAT THE MAIN BODY ; OF THIS PROGRAM IS NOT DESIGNED TO RUN AS IS IN ; ANY NORMAL MANNER. ; ; MANY THANKS ARE DUE TO WARD CHRISTENSEN WHO PREPARED THE ; ORIGINAL SET OF SIMILAR I/O ROUTINES BURIED INSIDE OF ; THE CP/M USERS GROUP MODEM PROGRAM THAT HAS BECOME SO ; VERY POPULAR. THANKS AGAIN WARD. ; ;****************************************************************** ; ; ;CP/M BDOS EQUATES ; RDCON EQU 1 WRCON EQU 2 PRINT EQU 9 OPEN EQU 15 ;OPEN FILE CLOSE EQU 16 ;CLOSE FILE SRCHF EQU 17 ;SEARCH FOR FIRST ERASE EQU 19 ;DELETE FILE READ EQU 20 ;READ FILE RECORD WRITE EQU 21 ;WRITE FILE RECORD MAKE EQU 22 ;CREATE NEW FILE STDMA EQU 26 ;SET DATA BUFFER POINTER BDOS EQU 0005H ;SYSTEM I/O ENTRY POINT FCB EQU 5CH ;SYSTEM FCB FCBEXT EQU FCB+12 ;FILE EXTENT FCBSNO EQU FCB+32 ;SECTOR # FCB2 EQU 6CH ;SECOND FCB DSKBUF EQU 080H ;DEFAULT DISK BUFFER ADDRESS SECSIZ EQU 080H ;CP/M SECTOR SIZE ; WBOOT EQU 00 ;CP/M WARM BOOT ENTRY ADDRESS ; ; ;DEFINE ASCII CHARACTERS USED ; LF EQU 10 ;LINEFEED CR EQU 13 ;CARRIAGE RETURN EOFCHR EQU 01AH ;CP/M END OF FILE CHAR ; ; ;START OF EXECUTABLE CODE ; ORG 100H LXI SP,STACK ;SETUP A STACK TO USE ; ; ;SEQUENTIAL I/O WRITE OF CP/M FILE ENABLED BY USING THIS SEQUENCE ;OF SUBROUTINE CALLS. THE FILE CONTROL BLOCK IS ASSUMED TO BE ;STORED AT THE DEFAULT LOCATION AT 05CH IN THE BASE PAGE OF ;CP/M MEMORY MAP. ; SIOWR: CALL ERASFIL ;ERASE RECIEVED FILE CALL MAKEFIL ;ESTABLISH NEW FILE CALL INITWR ;INITIALIZE FILE WRITE PARAMETERS ; ; ;MAKE FOLLOWING CALL TO PLACE A CHARACTER FROM THE (A) REGISTER ;INTO THE CP/M FILE. LOOP DOING THIS TILL YOU HAVE ALL IN FILE THAT ;IS NEEDED. ; CALL WRCHAR ;PUT CHAR IN FILE ; CALL WREOF ;FLUSH LAST SECTOR TO CP/M FILE CALL CLOSFIL ;CLOSE IT UP ; ; ;SEQUENCE OF COMMAND CALLS TO OPEN AND USE A SEQUENTIAL CHARACTER ;FILE FOR READING. THE FILE CONTROL BLOCK IS ASSUMED TO BE LOCATED ;AT THE DEFAUT LOCATION OF 05CH IN THE BASE CP/M PAGE. ;ONCE THE FILE IS INITIALIZED THE CHARACTERS CAN BE READ ONE BY ;ONE UNTIL THE RDCHAR SUBROUTINE RETURNS A SET CARRY FLAG ;INDICATING A END OF PHYSICAL FILE CONDITION. EOF IS SENSED AS ;PHYSICAL END OR 01AH CHARACTER WHICHEVER COMES FIRST ; SIORD: CALL OPENFIL ;OPEN THE CP/M FILE CALL INITRD ;GO INIT FOR FILE READ CALL RDCHAR ;GET CHAR FROM CP/M FILE JC EOF ;CHECK FOR EOF ; EOF: ; PLACE CODE HERE FOR END OF FILE HANDLING ; ;I/O HANDLING SUBROUTINES ; ; ; ;>--> ERASFIL: ERASE THE INCOMING FILE. ; ;IF IT EXISTS, ASK IF IT MAY BE ERASED. ; ERASFIL: LXI D,FCB ;POINT TO CTL BLOCK MVI C,SRCHF ;SEE IF IT.. CALL BDOS ;..EXISTS INR A ;FOUND? RZ ;..NO, RETURN CALL ILPRT ;PRINT: DB '++CP/M FILE EXISTS, TYPE Y TO ERASE: ',0 CALL KEYIN ;GET A CHARACTER FROM CONSOLE ANI 5FH ;MAKE UPPER CASE CPI 'Y' ;WANT ERASED? JNZ EXIT ;QUIT IF NOT ERASE CALL CRLF ;BACK TO START OF LINE ; ; ;ERASE OLD FILE ; LXI D,FCB ;POINT TO FCB MVI C,ERASE ;GET BDOS FNC CALL BDOS ;DO THE ERASE RET ;FROM "ERASFIL" ; ; ;>--> MAKEFIL: MAKES THE FILE TO BE RECEIVED ; MAKEFIL: LXI D,FCB ;POINT TO FCB MVI C,MAKE ;GET BDOS FNC CALL BDOS ;TO THE MAKE INR A ;FF=BAD? RNZ ;OPEN OK ; ; ;DIRECTORY FULL - CAN'T MAKE FILE ; CALL ERXIT DB '++ERROR - CANNOT MAKE FILE',CR,LF DB '++DIRECTORY MUST BE FULL',CR,LF,'$' ; ; ;>--> OPENFIL: OPENS THE FILE TO BE SENT ; OPENFIL: LXI D,FCB ;POINT TO FILE MVI C,OPEN ;GET FUNCTION CALL BDOS ;OPEN IT INR A ;OPEN OK? RNZ ;FILE OPENED OK CALL ERXIT ;..NO, ABORT DB '++CANNOT OPEN CP/M FILE','$' ; ; ;>--> CLOSFIL: CLOSES THE RECEIVED FILE ; CLOSFIL: LXI D,FCB ;POINT TO FILE MVI C,CLOSE ;GET FUNCTION CALL BDOS ;CLOSE IT INR A ;CLOSE OK? RNZ ;..YES, RETURN CALL ERXIT ;..NO, ABORT DB '++CANNOT CLOSE CP/M FILE','$' ; ; ;>--> INITRD: INITIALIZES FILE READ PARAMETERS ; INITRD: MVI A,00H ;SET THE BUF CNT TO EMPTY STA CHRINBF LXI D,DSKBUF ;SET THE DMA BUFFER POINTER PUSH D MVI C,STDMA CALL BDOS POP D XCHG ;SET SECTOR POINTER SHLD SECPTR RET ; ; ;>--> RDCHAR: READS A CHARACTER FROM FILE ; ;RETURN IS WITH DESIRED CHARACTER IN ;THE A REGISTER. IF EOF, THEN ;RETURN IS WITH THE CARRY FLAG SET. ; RDCHAR: LDA CHRINBF ;GET NUMBER OF CHAR IN BUF ORA A ;CHECK IF BUFFER EMPTY JZ RDBLOCK ;GO GET A SECTOR IF EMPTY DCR A ;DECREMENT STA CHRINBF LHLD SECPTR ;GET BUFFER POINTER MOV A,M ;GET CHARACTER FOR CALLER INX H ;INCREMENT POINTER SHLD SECPTR CPI EOFCHR ;CHECK FOR LOGICAL CP/M EOF STC RZ ;RETURN EXIT FOR LOGICAL EOF CMC ;CLEAR CARRY SO EOF NOT INDICATED ;ON NORMAL RETURN RET ;FROM "RDCHAR" ; ; ;BUFFER IS EMPTY - READ IN ANOTHER SECTOR ; RDBLOCK: LXI D,FCB MVI C,READ CALL BDOS ORA A ;READ OK? JZ RDBFULL ;YES DCR A ;EOF? JZ REOF ;GOT EOF ; ; ;READ ERROR ; CALL ERXIT DB '++CP/M FILE READ ERROR','$' ; REOF: STC ;SET CARRY FLAG FOR EOF EXIT RET ; ; ;BUFFER IS FULL ; RDBFULL: MVI A,SECSIZ ;INIT BUF CHAR COUNT STA CHRINBF LXI H,DSKBUF ;INIT BUFFER.. SHLD SECPTR ;..POINTER JMP RDCHAR ;PASS CHAR TO CALLER ; ; ;>--> INITWR: INITIALIZES FILE WRITE PARAMETERS ; INITWR: MVI A,00H ;SET THE BUF CNT TO EMPTY STA CHRINBF LXI D,DSKBUF ;SET THE DMA BUFFER POINTER PUSH D MVI C,STDMA CALL BDOS POP D XCHG ;SET SECTOR POINTER SHLD SECPTR RET ; ; ;>--> WRCHAR: WRITE A CHARACTER TO FILE ; ;ENTRY IS WITH CHARACTER IN A ;ENTRY AT WREOF FILLS REMAINING BYTES ;OF SECTOR WITH 01AH PER CP/M CONVENTION. ; WRCHAR: LHLD SECPTR ;PUT CHAR IN BUFFER MOV M,A INX H ;BUMP POINTER SHLD SECPTR LDA CHRINBF ;INCR CHAR COUNT INR A STA CHRINBF CPI SECSIZ ;CHECK IF SECTOR FULL RNZ ;GO BACK IF OK ; WRBLOCK: LXI D,FCB ;IF FULL THEN WRITE MVI C,WRITE ;..THE.. CALL BDOS ;..BLOCK ORA A JNZ WRERR ;OOPS, ERROR MVI A,00H ;RESET THE CHAR CNT STA CHRINBF LXI H,DSKBUF ;RESET BUFFER.. SHLD SECPTR ;..POINTER RET ; WRERR: CALL ERXIT ;EXIT W/MSG: DB '++ERROR WRITING CP/M FILE',CR,LF,'$' ; WREOF: LDA CHRINBF ;FILL REST OF SECTOR WITH 01AH LHLD SECPTR MVI B,EOFCHR WREND: MOV M,B ;PUT IN THE CP/M EOF CODE INX H INR A ;INC THE CHAR CNT CPI SECSIZ ;BUFFER FULL YET JNZ WREND JMP WRBLOCK ;GO PUT FILLED BLOCK ON DISK ; ; ;>--> KEYIN: GETS A KEY CODE IN FROM CONSOLE ; KEYIN: PUSH B ;SAVE.. PUSH D ;..ALL.. PUSH H ;..REGS MVI C,RDCON ;GET CON CHAR FUNCTION CODE CALL BDOS ;GET CHARACTER MOV A,E POP H ;RESTORE.. POP D ;..ALL.. POP B ;..REGS RET ; ; ;>--> CTYPE: TYPES VIA CP/M SO TABS ARE EXPANDED ; CTYPE: PUSH B ;SAVE.. PUSH D ;..ALL.. PUSH H ;..REGS MOV E,A ;CHAR TO E MVI C,WRCON ;GET BDOS FNC CALL BDOS ;PRIN THE CHR POP H ;RESTORE.. POP D ;..ALL.. POP B ;..REGS RET ;FROM "CTYPE" ; ; ;>--> CRLF: TYPE A CARRAGE RETURN LINE FEED PAIR AT CONSOLE ; CRLF: MVI A,CR CALL CTYPE MVI A,LF CALL CTYPE RET ; ; ;>--> ILPRT: INLINE PRINT OF MSG ; ;THE CALL TO ILPRT IS FOLLOWED BY A MESSAGE, ;BINARY 0 AS THE END. BINARY 1 MAY BE USED TO ;PAUSE (MESSAGE 'PRESS RETURN TO CONTINUE') ; ILPRT: XTHL ;SAVE HL, GET HL=MSG ILPLP: MOV A,M ;GET CHAR ORA A ;END OF MSG? JZ ILPRET ;..YES, RETURN CPI 1 ;PAUSE? JZ ILPAUSE ;..YES CALL CTYPE ;TYPE THE CHARACTER OF MESSAGE ILPNEXT: INX H ;TO NEXT CHAR JMP ILPLP ;LOOP ; ; ;PAUSE WHILE TYPING HELP SO INFO DOESN'T ; SCROLL OFF OF VIDEO SCREENS ; ILPAUSE: CALL ILPRT ;PRINT: DB CR,LF,'PRESS RETURN TO CONTINUE OR ^C TO EXIT' DB CR,LF,0 CALL KEYIN ;GET ANY CHAR CPI 'C'-40H ;REBOOT? JZ EXIT ;YES. JMP ILPNEXT ;LOOP ; ILPRET: XTHL ;RESTORE HL RET ; & RETURN ADDR PAST MESSAGE ; ; ;>--> PRTMSG: PRINTS MSG POINTED TO BY (DE) ; ;A '$' IS THE ENDING DELIMITER FOR THE PRINT. ;NO REGISTERS SAVED. ; PRTMSG: MVI C,PRINT ;GET BDOS FNC JMP BDOS ;PRINT MESSAGE, RETURN ; ; ;>--> ERXIT: EXIT PRINTING MSG FOLLOWING CALL ; ERXIT: POP D ;GET MESSAGE CALL PRTMSG ;PRINT IT ; EXIT: LXI D,080H ;RESET DEFAULT DMA ADDRESS FOR EXIT MVI C,STDMA CALL BDOS LHLD STACK ;GET ORIGINAL STACK SPHL ;RESTORE IT JMP WBOOT ;GO DO A WARM BOOT OF CP/M TO BRING ;BACK IN CCP ; ; ;FOLLOWING 2 USED BY THE CP/M DISK BUFFERING ROUTINES ; SECPTR DW DSKBUF ;POINTER TO DISK BUFFER POS CHRINBF DB 0 ;# OF CHARACTERS IN BUFFER ; ; ;SETUP A STACK AREA ; DS 38 ;STACK AREA STACK DS 2 ;STACK POINTER ; ; -------------- ; END ; ;+++...END OF LISTING 2 The reader is invited to be with us again next month when the tutorial continues into its third and final part. The functions of random record file I/O will be presented with complete programming examples to show how random I/O works. Several special file I/O tricks will be shown that permit unique problems to be solved under the CP/M operating system. One of these will be a program that does "update" on an exisiting file without the use of the random record I/O capabilities. So long till January and I hope that all Life Lines readers have a joyous holiday season.
SLIDING INTO BDOS (Part III) UNDERSTANDING RANDOM FILES by: Michael J. Karas 2468 Hansen Court Simi Valley, CA 93065 (805) 527-7922 The time has arrived to complete the third and final part of this series on the operation of the CP/M BDOS as viewed from the assembly language programmers perspective. Presently we will build upon the extensive treatment of sequential files presented in Part II of the series to provide a basis for understanding the CP/M 2.2 random file I/O capability. Please note that functions of the BDOS presented here are specific to CP/M Versions 2.2 and 3.0. Older CP/M systems using Version 1.4 do not directly support random access file I/O and as such are not compatible with the programming examples presented below. WHY RANDOM FILE I/O ANYWAY In the beginning of the CP/M era, sometime around the release of Version 1.3 by Digital Research, small inexpensive single-user micro processor systems were typically used for simple-minded data processing applications. Most computing operations were linear with respect to the data handling by the CPU. Data entered from paper tape, cassette, card readers, or human entry from a keyboard tended to be limited to a sequential processing from start to finish. The usage of such data by the computer in data analysis, program compilation, or logging applications was also largely sequential. Finally the data output operations based upon the needs of hard copy, backup, and transmission from micro to micro were relegated to sequential processing applications. Anticipated applications of micro type computer hardware by operating system designers, at that time, seemed to dictate that the disk file structures of the operating systems should be sequential in nature. This was true for the earliest releases of CP/M and Intel's ISIS II operating system. Other simple floppy disk operating systems like PERTEC's FDOS and MITS' Disk Extended Basic operating systems were also strictly sequential in the treatment of the disk file allocation and storage. However, these two systems permitted random record I/O within the bounds of an already existng file provided the space to store the records was previously pre-allocated as contiguous disk space in the file structure. The process of random I/O was then easy as a relative offset between the beginning record number for the file and the offset desired within the file. As the micro processor applications market opened up in the late 1970's it seemed that new uses for computers were being found weekly. It has gotton to the point that micro processor computer users have a large array of very sophisticated software packages to choose from and utilize in their business and hobby activities. The main thing that can be pointed out about many of these packages is that the processes they perform are hardly linear with respect to the handling of data. Interactive programs like word processors, data base managers, spelling checkers, and spread sheet analysis programs may very well need to be able to store or access data to/from a disk file in a manner that cannot be handled in the old sequential manner. The sequential philosophy generally limited file update to appending to the end of the file and read access to a particular record had to read N- 1 records from the beginning of the file prior to being able to read record N. Random access file I/O within an operating system anticipates the requirements of non-sequential I/O by permitting access to various records directly. Any record that was previously written may be read upon demand. Likewise the user/programmer may write any record desired. The Digital Research CP/M operating system supports this type of I/O in a powerful yet elegantly simple manner through a set of four BDOS system functions. These calls allow random access disk files to be implemented within the standard CP/M compatible file structure. RANDOM FILE STRUCTURE UNDER CP/M 2.2 The structure of random files under the CP/M operating system is much the same as that for sequential files. Part II of this series (Lifelines, January 1982) described and illustrated the sequential structure in detail. The reader will recall that CP/M treats disk data in fixed records of 128 bytes. These records are collected together into "groups" that are stored on the disk as an allocated group. The disk space reserved for a given file, in its directory entry, is always marked, identified, and allocated in the even multiples of the "allocation group size". I previously mentioned two older operating systems that supported random file I/O within the confines of a pre-allocated file. This system requires that all of the space for an "N" record file be reserved as contiguous disk space even if the file only contains two records (#0 and #N). Making a random access file bigger than the pre allocated size was virtually impossible. The CP/M Ver 2.2 random file access system has overcome the problems described above. A random file under CP/M contains only the number of allocated groups required to hold the stored records. The holes between the defined records do not consume unused disk space. If a file under CP/M is created with only random record 0 of the file written then that file contains 128 bytes of real data and consumes one allocation group of disk space. The allocation group consumed also may contain other adjacent random records to fill out the size of the group. For instance, on single density 8" disks with a 1024 byte allocation group size, a one record (#0) file would be able to be written with additional record numbers 1 to 7 within the same allocation group. Likewise if a single record file was created with only record number 9 written, that file would consume only one allocation group of disk space. Additional record numbers 8, and 10 to 15 could then be written without requiring additional disk space. RANDOM FILE I/O SYSTEM CALLS Let us next investigate the five BDOS system calls that CP/M supports for random I/O within files. The chart of Figure 1 on the following page details the look of a random access file control block. Note that the file control block contains three bytes at the end that are used to store the random record number that will currently be accessed. The random access system calls all utilize this field to determine the portion of the file to access at read/write time. A CP/M random file may contain up to 64K records of 128 bytes numbered from 0 to 65535. Two bytes of the file control block hold this record number, r0 as the low byte and r1 as the high byte. This provides accessability to records up to a maximum file size of 8 megabytes. The r2 byte of the file control block is not used except as the overflow or carry out of the r1 byte. If byte r2 ever contains a value that is non-zero the record number is beyond the end of the 8 megabyte limit for the file. To access a random file, it must first be opened in the normal manner with the "open" BDOS function call. In the case of creating a new random file the make file BDOS call is sufficient in that the the results of the make operation are equivalent to the open function on a zero length file. READ RANDOM RECORD: Function 33. This system call is made with the (DE) register pair pointing to a 36 byte file control block. Bytes r0-r2 are set up with the random record to read. The BDOS then fetches the addressed record from the file and places it in the callers record buffer pointed to by the last set buffer address function Figure 1. FILE CONTROL BLOCK DESCRIPTION ------------------------------------------------------------ |dr|f1|f2|/ /|f8|t1|t2|t3|ex|s1|s2|rc|d0|/ /|dn|cr|r0|r1|r2| ------------------------------------------------------------ 00 01 02 ... 08 09 10 11 12 13 14 15 16 ... 31 32 33 34 35 where: dr drive code (0 - 16) 0 => use default drive for file access 1 => select drive A: for file access 2 => select drive B: for file access ... 16=> select drive P: for file access f1...f8 contain the files name in ASCII upper case with high bits equal to zero. t1,t2,t3 contain the file type in ASCII upper case with high bits normally equal zero. tn' denotes the high bit of these bit positions. t1' = 1 => Read/Only file t2' = 1 => SYS file, no DIR list ex contains the current extent number, normally set to 00 by the user, but is in the range 0 - 31 during file I/O. s1 reserved for internal system use s2 reserved for internal system use, set to zero on call to OPEN, MAKE, SEARCH system calls. rc record count for extent "ex," takes on values 0 to 128. d0...dn filled in by BDOS to indicate file group numbers for this extent. cr current record to read or write in a sequential file operation. Normally set to zero by the user upon initial access to a file. r0,r1,r2 optional random record number in the range of 0 to 65535, with overflow to r2. r0/r1 are a 16 bit value in low/high byte order. call. The r0-r2 fields of the file control block are not changed as a result of the random read function such that a subsequent random read operation would read the same record. The random read function may return a number of error codes as described below: Error Code 00 - The random read function worked without error and the user buffer contains the desired data. Error Code 01 - The random read operation addresses a record that is contained in a disk allocation group not allocated to the file. This means that the group field number slot of the appropriate extent of the file that should contain the record is equal to 0. Error Code 03 - The random read operation just requested required that a different extent descriptor directory entry had to be open for the impending operation, however prior to opening the new extent the current extent could not be closed due to disk read/only status or a disk change. Error Code 04 - The random read operation just requested required access to an extent of the file that does not exist on the disk. Error Code 06 - The random read operation just requested required access to a record number beyond the bounds of the disk drive, ie the disk drive is less than 8 megabytes and the record requested is within an allocation group beyond the end of the disk. WRITE RANDOM RECORD: Function 34. This system call is made with the (DE) register pair pointing to a 36 byte file control block. Bytes r0-r2 are set up with the random record to write. The BDOS then moves the data in the callers record buffer pointed to by the last set buffer address function call to the addressed record in the file. The r0-r2 fields of the file control block are not changed as a result of the random write function such that a subsequent random write operation would write the same record. The random write function may return a number of error codes as described below: Error Code 00 - The random write function worked without error and the user buffer contains the desired data. Error Code 03 - The random write operation just requested required that a different extent descriptor directory entry had to be open for the impending operation, however prior to opening the new extent the current extent could not be closed due to disk read/only status or a disk change. Error Code 05 - The random write operation just requested required access to an extent of the file that does not exist on the disk. In the process of creating the new extent the disk directory was found to be full. Error Code 06 - The random write operation just requested required access to a record number beyond the bounds of the disk drive, ie the disk drive is less than 8 megabytes and the record requested is within an allocation group beyond the end of the disk. WRITE RANDOM RECORD WITH ZERO FILL: Function 40. This system call is made with the (DE) register pair pointing to a 36 byte file control block. Bytes r0-r2 are set up with the random record to write. The BDOS then moves the data in the callers record buffer, pointed to by the last set buffer address function call, to the addressed record in the file. The r0-r2 fields of the file control block are not changed as a result of the random write function such that a subsequent random file operation would access the same record. If the random write operation caused a new allocation group to be allocated to the file the other records of the same block are filled with zeros. The random write with zero fill function may return a number of error codes identical to those described for function number 34 above. COMPUTE FILE SIZE: Function 35. This system call determines the number of 128 byte records in a file and sets the number of records into the r0 and r1 bytes of the 36 byte file control block addressed by the (DE) register pair. The returned size is a virtual size in that if the file was created by random write operations and the file contains "holes" the file size function does not take the holes into account. Another way of looking at this is to think of this function as returning a record number that is one greater than the maximum record number currently in the file. If the file had no "holes" or it had been written in the conventional sequential fashion, then the file size reported by this function is the real file size. This function provides a convenient function of positioning a file at its end so that subsequent sequential or random update could be performed. SET RANDOM RECORD: Function 36: The (DE) register pair is set to point to a 36 byte file control block that has previously been used to reference a file in the sequential mode. Upon reference with this system call the r0 to r2 fields are filled in with the random record number that corresponds to the current file position, ie the BDOS simply computes the real current record number as follows: The current extent number is multiplied by 128, the number of records per extent, and to this product is added the numerical value of the CR field, current record in this extent. The final result is placed into the r0-r2 fields of the FCB. LOOKING AT SOME EXAMPLES The following simple assembly language program is designed to write record numbers 0 and 143 into a file on the disk. The write random function is used to write the first record with all A's and the second record, # 143, with all B's. ; ; ;RANDOM RECORD I/O DEMONSTRATION FOR CP/M 2.2 ; ; THIS FIRST LEVEL DEMONSTRATION IS DESIGNED TO ; SHOW HOW TO INITIALLY SET UP A FILE TO BE A RANDOM FILE ; AND TO WRITE TWO RECORDS INTO THE FILE SUCH THAT THE ; FIRST RECORD (RECORD NUMBER 0) AND THE SEVENTEENTH ; RECORD OF THE SECOND EXTENT (RECORD NUMBER 143) BOTH ; CONTAIN DATA. THE PURPOSE IS TO DEMONSTRATE THE ; RESULTING DISK DIRECTORY ENTRIES THAT RESULT FROM ; AN INCOMPLETE FILE. THIS DEMO PROGRAM DOES NO RANDOM ; WRITE ERROR CHECKING. ; ; ;SYSTEM LEVEL INTERFACE EQUATES ; BDOS EQU 0005H ;SYSTEM INTERFACE VECTOR MAKE EQU 22 ;MAKE NEW FILE FUNCTION SBADDR EQU 26 ;SET DISK BUFFER ADDR OPEN EQU 15 ;OPEN FILE FUNCTION CLOSE EQU 16 ;FILE CLOSE FUNCTION DELETE EQU 19 ;DELETE FILE FUNCTION RRAND EQU 33 ;READ RANDOM FUNCTION WRAND EQU 34 ;WRITE RANDOM FUNCTION WRANDF EQU 40 ;WRITE RANDOM WITH 00 FILL ; ; ORG 0100H ;START OF A PROGRAM ; XRA A ;ZERO BYTES OF THE FCB STA EXT ;EXTENT FIELD STA CR ;CURRENT RECORD COUNT STA RR+2 ;AND THE R2 FIELD LXI H,0000H ;ALSO ZERO RANDOM RECORD FIELED SHLD RR ; LXI D,BUFFER ;SET DISK BUFFER ADDRESS MVI C,SBADDR CALL BDOS ; LXI D,RANDFCB ;POINT AT OUR FCB MVI C,DELETE ;ERASE TEST FILE IF IT ALREADY EXISTS CALL BDOS ; LXI D,RANDFCB ;MAKE A NEW FILE FOR TEST MVI C,MAKE CALL BDOS ; MVI A,'A' ;FILL FIRST RECORD WITH A'S CALL FILL ;GO FILL LXI H,0000H ;SET RECORD NUMBER TO WRITE A'S INTO SHLD RR LXI D,RANDFCB ;WRITE RECORD OF A'S MVI C,WRAND ;NORMAL WRITE RANDOM FUNCTION CALL BDOS ; MVI A,'B' ;FILL NEXT RECORD WITH B'S CALL FILL ;GO FILL LXI H,143 ;SET RECORD NUMBER TO WRITE B'S INTO SHLD RR LXI D,RANDFCB ;WRITE RECORD OF B'S MVI C,WRAND ;NORMAL WRITE RANDOM FUNCTION CALL BDOS ; LXI D,RANDFCB ;CLOSE JUST WRITTEN FILE MVI C,CLOSE CALL BDOS ; ; RET ;BACK TO CCP BY IMMEDIATE RETURN ; ; ;SUBROUTINE TO FILL BUFFER WITH A PATTERN ; ; ENTRY WITH (A) CONTAINING BYTE TO FILL BUFFER WITH ; FILL: LXI H,BUFFER ;POINT AT BUFFER FOR FILL MVI B,128 ;FILL BYTE COUNTER FILLP: MOV M,A ;PUT A BYTE INTO BUFFER INX H ;BUMP POINTER DCR B ;DECREMRNT BYTE COUNT JNZ FILLP ;CONTINUE TILL BUFFER FULL RET ; ; ;RANDOM FILE TEST DATA AREA ; RANDFCB: DB 00 ;USE CURRENT LOGGED DRIVE FOR TEST DB 'RANDFILE' ;NAME OF FILE TO PLAY WITH DB 'TST' ;..AND THE EXTENSION NAME EXT: DB 00,00,00,00 ;EXTENT, S1, S2, AND FCBSZ BYTES DS 16 ;STORAGE FOR THE ALLOCATION NUMBERS CR: DS 1 ;CURRENT RECORD BYTE RR: DS 2 ;RANDOM RECORD NUMBER (R0,R1) DS 1 ;RANDOM RECORD OVERFLOW BYTE (R2) ; ; ;RANDOM DISK I/O DATA BUFFER ; BUFFER: DS 128 ;ONE RECORD BUFFER ; END The above program was assembled and caused to run on an empty single density disk in the default disk drive. The following display shows how the directory upon the disk looked after running the program. Notice that the file only consumes two allocated groups. Due to the fact that this was a single density disk with 1024 byte allocation groups of 8 records each, then if record number 8 was subsequently written the directory entries would change to include an allocation block number in the second group number slot of the first extent of the file. G=00:00, T=2, S=1, PS=1 00 0052414E 4446494C 45545354 00000001 *.RANDFILETST....* 10 02000000 00000000 00000000 00000000 *................* 20 0052414E 4446494C 45545354 01000010 *.RANDFILETST....* 30 00030000 00000000 00000000 00000000 *................* 40 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 50 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 60 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 70 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* The following two sector displays off the single density disk show the A's and B's written by the program above. All other sectors in the group numbers 02 and 03 were empty, ie contained whatever data that used to be there. This brings up the subject of the write random with zero fill function. A small segment of G=02:00, T=2, S=17, PS=20 00 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 10 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 20 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 30 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 40 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 50 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 60 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* 70 41414141 41414141 41414141 41414141 *AAAAAAAAAAAAAAAA* G=03:07, T=3, S=6, PS=5 00 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 10 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 20 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 30 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 40 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 50 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 60 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* 70 42424242 42424242 42424242 42424242 *BBBBBBBBBBBBBBBB* the first demonstration program was changed to cause the second write operation to be done with zero fill. The changed portion of the program is shown below: LXI D,RANDFCB ;WRITE RECORD OF A'S MVI C,WRAND ;NORMAL WRITE RANDOM FUNCTION CALL BDOS ; MVI A,'B' ;FILL NEXT RECORD WITH B'S CALL FILL ;GO FILL LXI H,143 ;SET RECORD NUMBER TO WRITE B'S INTO SHLD RR LXI D,RANDFCB ;WRITE RECORD OF B'S MVI C,WRANDF ;WRITE RANDOM ZERO FILL FUNCTION CALL BDOS ; LXI D,RANDFCB ;CLOSE JUST WRITTEN FILE Note from the directory display below that there is no change in the appearance of the entries from the first example. This time the only thing that changed was the data in allocation group 3. Due to the second write this allocation group contains a sector of B's at GROUP=03:07 with the other seven sectors of the group now containing zeroes from the zero fill operation. The function of zero fill is to leave a clean slate on records numbers subsequently read from the same allocation block. The BDOS is capable of reporting unwritten record information for records that correspond to group number slots in the directory entries that contain a '00' byte indicating unallocated. However once a group is allocated for one record the BDOS cannot determine if other sectors of that group have been written or not. Thus ero function may be issued when creating a random access file for the first time. The programmer may then use a record of 128 zeroes to indicate that the record is not used as opposed to accidentally mistaking the garbage data from un- initialized sectors written without zero fill as real data. G=00:00, T=2, S=1, PS=1 00 0052414E 4446494C 45545354 00000001 *.RANDFILETST....* 10 02000000 00000000 00000000 00000000 *................* 20 0052414E 4446494C 45545354 01000010 *.RANDFILETST....* 30 00030000 00000000 00000000 00000000 *................* 40 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 50 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 60 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* 70 E5E5E5E5 E5E5E5E5 E5E5E5E5 E5E5E5E5 *eeeeeeeeeeeeeeee* The next example program is included here to show a clever means of implementing arbitrary record selection I/O within a file without resorting to random file I/O. The intent is not to indicate that the following scheme is the preferred method. The program below was developed with the CP/M Ver 1.4 operating system in mind. However the algorithm works fine with CP/M 2.2 as well. The technique used to play with random records by using sequential read and write operations is to manipulate the "cr" field of a standard 33 byte file control block. The "cr" byte is the only meand that the BDOS uses to indicate the next record to access. The programmer may change this byte value to force the BDOS to go to any record within the current extent. If the first extent of a file is opened, the group allocation values for that extent lie in the file control block. If the technique of performing "your own" random I/O is done, the code must access record numbers not to excede 07fh without first closing the current extent and opening the next. This can be done with either the conventional open and close operations or the programmer, when done working with the current extent may open next automatically by performing a dummy read of record 080H of the current extent. The programming example below uses the "roll your own" technique but does not anticipate a file size greater than 16K (one extent size). The program below is a skeleton structure of a .COM file serialization procedure. The idea is to insert a six byte serial number string into the target file PROG.COM on drive B:. The serial number is inserted into the file at the places specified by the labels in the table at the start of the listing. These values are stripped out of the symbol table that is generated at the assembly of the PROG.ASM file. If the assembler does not generate a symbol table then the label values may be pulled off the .PRN listing output. The insert points are places within the "to be serialized" program where the programmer has determined that he would like to place the serial number string. Within the file itself, the labels point to the place where the string is to be inserted with respect to run time load address. The real file offset is 0100H bytes less. In addition, the scheme does not insert all six bytes of the program serial number at each location. The byte at each label address minus one contains a value between 1 and 6 of thenumber of serial number bytes that should actually be inserted at seralization time. The list of label values in the program below is used to build, at assembly time, a table of record numbers where the specific serial number strings are to be inserted. This table is then used to fill in the "cr" byte of the file control block as each serial number is to be inserted. The table also contains the byte offset within the record where the insert point is to start. As each serial number is to be inserted the appropriate record is read, the number is inserted (with length specified by the value from the file record just accessed), and the record is written back to the disk. Sequentail read and write operations are used for both operations. Logic within the code listing below also provides for the occurrance that the serial number string may cross the end of the first record and flow into the next record. In this case the first is rewritten followed by reading of the next with the remainder of the insert proceeding from the beginning of the second record. Please note that the program example is given as a skeleton only and the serial number entry process, increment process, and the disk I/O error exit points are left for the reader/programmer to fill in with code of his own choosing. ; ; ;PROGRAM SERIAL NUMBER INSERTION EQUATES ; EACH ADDRESS IS A VALUE INSIDE OF THE "PROG.COM" ; FILE THAT IS THE PLACE TO PUT THE SERIAL NUMBER. ; SERA EQU 0132H SERB EQU 01E9H SERC EQU 0278H SERD EQU 039AH SERE EQU 06FFH SERF EQU 0732H SERG EQU 0BBCH SERH EQU 0C08H ; ; ;CP/M BDOS SYSTEM CALLS FUNCTION NUMBERS ; BOOT EQU 0000H ;REBOOT LOCATION ENTRY POINT BDOS EQU 0005H ;BDOS FUNCTION ENTRY POINT RESET EQU 13 ;RESET DISK SYSTEM OPEN EQU 15 ;OPEN FILE FUNCTION CLOSE EQU 16 ;CLOSE FILE FUNCTION DMAADR EQU 26 ;SET DATA BUFFER ADDRESS READ EQU 20 ;READ SEQUENTIAL WRITE EQU 21 ;WRITE SEQUENTIAL ; ; ;DEFINE BASE EXECUTION AREA FOR THIS PROGRAM ; START EQU 0100H ; ; ORG START ;BASE OF EXECUTION AREA ; ; ;START UP HERE WITH PROGRAM INITIALIZATION AND ;DEFINE PROCEDURE TO FETCH IN SERIAL NUMBER TO INSERT INTO ;THE FILE ; SERASK: ; ;ENTER APPROPIATE CODE HERE TO PUT A SIX BYTE SERIAL NUMBER ;INTO VARIABLE "SERSTR" ; ; ; ;SERIAL NUMBER INSERT POINT PROCESSING ; ; SERCOPY: MVI C,RESET ;RESET DISK SYSTEM UPON INSERT CALL BDOS LXI D,PROGFCB ;SET TO OPEN THE PROG.COM FILE MVI C,OPEN CALL BDOS INR A ;CHECK IF OPEN ERROR JNZ SERCP1 ;OPEN SO GO START WRITE ; ;PRINT ERROR MESSAGE HERE AS TO INDICATE THAT THE FILE ;"PROG.COM" IS NOT PRESENT ON DRIVE B:. ; JMP SERASK ;IF ERROR BACK TO GET A NEW SERIAL ;..NUMBER OR TO EXIT SERCP1: MVI B,00H ;INDEX COUNTER FOR TABLE VALUES SERIST: MOV L,B MVI H,00H DAD H ;DOUBLE TO WORDS LXI D,INSTAB ;INTO TABLE DAD D MOV A,M ;GET RECORD NUMBER FOR PLACE STA PROGFCB+32 ;SET TO READ THIS RECORD INX H MOV C,M ;GET BYTE LOCATION OF COUNTER PUSH B LXI D,PROGFCB ;USE PROG FCB TO READ MVI C,READ CALL BDOS ;GO READ SECTOR POP B ;INDEX TO LENGTH MOV L,C MVI H,0 LXI D,080H ;BASE OF DEFAULT BUFFER DAD D MOV C,M ;GET LENGTH INX H ;POINT TO NEXT BUFFER BYTE LXI D,SERSTR ;POINT (DE) TO SERIAL LOCATION ; MOVLP: MOV A,H ;SEE IF PAST THE END OF BUFFER CPI 01H JNZ SAMSEC ;STILL IN THE SAME SECTOR ; MVI H,0 ;RESET TO NEXT SECTOR BASE PUSH B PUSH H PUSH D LXI H,PROGFCB+32 ;DECREASE RECORD FOR WRITE DCR M LXI D,PROGFCB MVI C,WRITE ;WRITE LAST SECTOR CALL BDOS LXI D,PROGFCB MVI C,READ ;READ NEXT SECTOR CALL BDOS POP D POP H POP B ; SAMSEC: PUSH B LDAX D ;GET A SERIAL NUMBER BYTE MOV M,A ;AND SLAM INTO BUFFER POP B INX H INX D DCR C ;DONE ALL BYTES HERE YET JNZ MOVLP ; PUSH B LXI H,PROGFCB+32 ;SET BACK CURRENT RECORD FOR WRITE DCR M LXI D,PROGFCB MVI C,WRITE ;REWRITE THIS SECTOR CALL BDOS POP B INR B ;BUMP TABLE SCAN INDEX LDA TABLEN ;CHECK FOR DONE CMP B JNC SERIST ;GO FOR NEXT TABLE ENTRY ; ;PUT IN LOGIC HERE TO SPECIFY THE NEXT OF SEQUENTIAL SERIAL NUMBERS ;OR TO GO BACK TO THE TOP OF THE PROGRAM TO GET A NEW SERIAL NUMBER. ; ; ; ;PARAMETER DATA AREA FOR SERAL NUMBER PROGRAM ; ; ;"PROG.COM" FILE ACCESS CONTROL BLOCK ; PROGFCB: DB 'B'-040H ;DISK DRIVE B: ALL THE TIME DB 'PROG COM',0,0,0,0 DS 17 ;ALLOCATION SPACE ; ; ; ;SERIAL NUMBER INSERTION POINT REFERENCE TABLE ; INSTAB: DB ((SERA-0100H-1)/128) ;RECORD NUMBER DB ((SERA-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERB-0100H-1)/128) ;RECORD NUMBER DB ((SERB-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERC-0100H-1)/128) ;RECORD NUMBER DB ((SERC-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERD-0100H-1)/128) ;RECORD NUMBER DB ((SERD-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERE-0100H-1)/128) ;RECORD NUMBER DB ((SERE-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERF-0100H-1)/128) ;RECORD NUMBER DB ((SERF-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERG-0100H-1)/128) ;RECORD NUMBER DB ((SERG-0100H-1) AND 07FH) ;BYTE OFFSET DB ((SERH-0100H-1)/128) ;RECORD NUMBER DB ((SERH-0100H-1) AND 07FH) ;BYTE OFFSET ; TABLEN: DB (($-INSTAB)/2)-1 ;NUMBER OF TABLE ENTRIES ; ;..MINUS 1 FOR LOOP EASE SERSTR: DS 10H ;PLACE TO KEEP BINARY SERIAL NUMBER ; ; END ; ; ;...END OF SERIAL NUMBER INSERT PROGRAM The next and final example is a fully functional program that uses random record I/O under CP/M 2.2 to perform a "useful" function. The program mixes up the records of a file in an ordered yet bizarre way in order that the file contents may be encoded to prevent its use until such time that it is unscrambled. The unmixing process is also performed by the program below. The records or "sectors" of the file are mixed and unmixed in place on the disk in that the disk file is not copied. Random access file I/O is used to swap records directly. The comment block at the beginning of the program listing contains an explanation of the program "intent" and the record mixing algorithm chosen. Operation of the program, should the reader wish to utilize the encoding and decoding functions provided, is also described in the listing. This example program is presented as a working example of random file I/O in use. Detailed description of the internal workings of the program are beyond the scope of this tutorial but may be inferred by studying the listing and reading the rather prolific comment statements. For readers that would like to avoid the aggravation of typing in the source code for the program below or for the other programs presented in this BDOS tutorial series, Part I in Lifelines, November 1982 and Part II in Lifelines, January 1983, a machine readable copy of the source code files on an eight inch single density diskette may be obtained from Michael J. Karas, 2468 Hansen Court, Simi Valley, California 93065. Please send diskettes preformatted, labeled and in a returnable mailer of some sort. Also include either stamps or money for return postage (no postage meter tapes, those are accepted on date of printing only) for your return package. LISTING FOR SECRET.ASM A RANDOM I/O PROGRAM EXAMPLE ; ; ;RANDOM RECORD I/O DEMONSTRATION FOR CP/M 2.2 ; ; THIS THIRD LEVEL DEMONSTRATION PROGRAM IS DESIGNED TO ; DEMONSTRATE RANDOM FILES BY DEVELOPING A 'NOT NECESSARILY ; PRACTICAL' ALGORITHM FOR ENCODING A PROGRAM FILE ON A DISK. ; THE INTENT IS TO MAKE THE TRANSMISSION OF AN OBJECT FILE ; ARBITRARILY SCRAMBLED ON A 128 BYTE BY 128 BYTE RECORD BASIS ; SUCH THAT IF THE TRANSMITTED FILE, EITHER ON FLOPPY DISKETTE ; OR ON THE PHONE LINE WERE INTERCEPTED BY AN ILLICIT THIRD ; PARTY, THEN THE THIRD PARTY WOULD RECEIVE GARBAGE UNLESS ; HE HAD POSSESSION OF THE DECODING ALGORITHM. ; ; THIS PROGRAM WILL IMPLEMENT SUCH AN ALGORITHM IN BOTH AN ; ENCODING AND DECODING FORMAT. HERE IS THE ALGORITHM USED. ; (OBVIOUSLY DUE TO THE FACT THAT THIS APPEARS IN THE ; PUBLIC IMAGE AS A MAGAZINE ARTICLE WILL PREVENT THE FOLLOWING ; ALGORITHM TO BE OF 'SECRET' USE). ; ; THE OPERATOR ENTERS THE COMMAND TO RUN THE PROGRAM AS: ; ; A>SECRET filename.typ E<cr> ; ; where filename.typ is the ; file to encode. And "E" ; indicates to encode the file ; ; or: ; ; A>SECRET filename.typ D<cr> ; ; where filename.typ is the ; file to decode. And "D" ; indicates to decode the file ; ; THE ENCODING PROCESS WRITES THE ENCODED FILE RIGHT IN PLACE ; WITHIN THE USER SPECIFIED FILE. NO MEANS IS USED TO SPECIFY ; IN THE ENCODED FILE THAT IT IS ENCODED. ; ; THE DECODE PROCESS READS AND DECODES THE FILE RIGHT IN PLACE ; WITHIN THE USER SPECIFIED FILE NAME. ; ; THE ALGORITHM LEAVES THE FIRST RECORD OF THE FILE INTACT AND ; DOES NOT ENCODE THE PART OF A FILE BEYOND 128 RECORDS IN SIZE. ; FOR FILES LARGER THAN 128 RECORDS THE FINAL RECORDS BEYOND THE ; 128'TH ARE LEFT UNTOUCHED. THE BDOS IS CALLED TO DETERMINE THE ; SIZE OF THE FILE SO THE NUMBER OF RECORDS IN THE FILE ARE ; KNOWN. THIS NUMBER OF RECORDS WILL BE REFERRED TO HERE AS "NR". ; IF "NR" IS GREATER THAN 128 THEN "NR" IS SET TO 128. THEN THE ; FIRST "NR-1" BYTES OF THE FIRST RECORD ARE READ SEQUENTIALLY ; TO MAKE A LIST OF ONE BYTE BINARY NUMBERS WITH A NUMBER OF ; ENTRIES EQUAL TO THE NUMBER OF RECORDS IN THE FILE MINUS ONE, ; UP TO A MAXIMUM OF 127 NUMBERS. ; ; THIS LIST IS THEN PROCESSED TO CONVERT ALL OF THE NUMBERS IN THE ; LIST TO BE WITHIN THE RANGE OF 1 TO "NR-1". THIS CONVERSION IS ; DONE BY FIRST "ANDING" EACH OF THE BYTES IN THE LIST WITH A MASK. ; THE MASK HAS A NUMERICAL VALUE EQUAL TO "NR-1" ROUNDED UP TO ; THE NEXT BIGGEST [(2 ^ N) - 1] VALUE, IE IF THE FILE HAS 5 ; RECORDS THE MASK IS 07H. IF THE FILE HAS 59 RECORDS THE MASK ; HAS A VALUE OF 3FH. THE LIST IS THEN SCANNED FOR VALUES THAT ; ARE GREATER THAN "NR-2". EACH VALUE THAT IS GREATER THAN ; "NR-2" IS DIVIDED BY TWO IGNORING THE REMAINDER. FINALLY EACH ; LIST VALUE IS INCREMENTED BY ONE TO MAKE A REAL FILE READABLE ; RECORD NUMBER. ; ; THE LIST IS THEN USED AS A RECORD SCRAMBLE/UNSCRAMBLE LIST. ; FOR SCRAMBLING IT IS SCANNED FROM THE BEGINNING WHILE ; UNSCRAMBLING SCANS THE LIST FROM THE END. SCRAMBLING PROCEDES ; AS FOLLOWS (THE UNSCRAMBLE PROCESS IS THE REVERSE): ; ; THE SECOND FILE RECORD IS NOW INTERCHANGED IN ; POSITION WITH THE RECORD POINTED BY THE FIRST ; NUMBER IN THE LIST. THE THIRD FILE RECORD IS ; INTERCHANGED WITH THE RECORD POINTED TO BY THE ; SECOND LIST VALUE. THIS PROCESS CONTINUES UNTIL ; THE END OF THE LIST. DURING THE PROCESS OF ; INTERCHANGING THE FILE SECTORS IN THIS RATHER ; BIZARRE MANNER, EACH TIME A LIST VALUE IS FOUND ; TO HAVE A LEAST SIGNIFICANT BIT THAT IS EQUAL ; TO "1" THEN THAT RECORD HAS EACH BYTE XOR'ED ; WITH THE RECORD NUMBER. ; ; WRITTEN BY: ; MICHAEL J. KARAS ; 2468 HANSEN COURT ; SIMI VALLEY, CA 93065 ; (805) 527-7922 ; ; ; ;SYSTEM LEVEL INTERFACE EQUATES ; BDOS EQU 0005H ;SYSTEM INTERFACE VECTOR MAKE EQU 22 ;MAKE NEW FILE FUNCTION SBADDR EQU 26 ;SET DISK BUFFER ADDR OPEN EQU 15 ;OPEN FILE FUNCTION CLOSE EQU 16 ;FILE CLOSE FUNCTION DELETE EQU 19 ;DELETE FILE FUNCTION RRAND EQU 33 ;READ RANDOM FUNCTION WRAND EQU 34 ;WRITE RANDOM FUNCTION WRANDF EQU 40 ;WRITE RANDOM WITH 00 FILL PRINT EQU 9 ;PRINT STRING TILL $ FSIZE EQU 35 ;COMPUTE FILE SIZE FUNCTION DEFCB EQU 05CH ;DEFAULT FILE CONTROL BLOCK DEFBUF EQU 080H ;DEFAULT BUFFER LOCATION ; EXEC EQU 08000H ;EXECUTE SPOT FOR SMALL PROGRAM BOOT EQU 00000H ;SYSTEM REBOOT ENTRY POINT ; ; ;ASCII CHARACTER DEFINITIONS ; CR EQU 0DH ;CARRIAGE RETURN LF EQU 0AH ;LINE FEED ; ; ORG 0100H ;START OF A PROGRAM LXI SP,STACK ;SETUP A STACK FOR EXECUTION LXI D,SNGMSG ;PRINT SIGNON MESSAGE MVI C,PRINT CALL BDOS ; ; ;CHECK IF THERE WAS A COMMAND LINE FILE NAME ; LDA DEFCB+1 ;IF FIRST BYTE 20 THEN NO NAME CPI ' ' JZ CMDERR ;IF NO FILE NAME PRINT ERROR LDA DEFCB+17 ;GET OPTION CHARACTER CPI 'E' ;CHECK FOR ENCODE JZ PROCESS ;GO TO PROCESS IF ENCODE CPI 'D' ;CHECK IF DECODE JZ PROCESS ;GO PROCESS OF DECODE ; CMDERR: LXI D,ERRM1 ;PRINT ERROR MESSAGE MVI C,PRINT CALL BDOS JMP BOOT ;EXIT IF NO FILE NAME OR OPTION ; ; ;HERE IF AN ENTRY FILE NAME AND A VALID OPTION ; PROCESS: STA OPTION ;SAVE OPTION CHAR FOR LATER ;...REFERENCE XRA A ;SETUP FCB FOR OPEN STA DEFCB+12 ;ZERO EXTENT BYTE STA DEFCB+32 ;ZERO CURRENT RECORD BYTE STA DEFCB+35 ;ZERO R2 BYTE LXI H,0000H SHLD DEFCB+33 ;ZERO RANDOM RECORD NUMBER ; MVI C,OPEN ;OPEN FILE USER SPECIFIED LXI D,DEFCB ;USE DEFAULT FCB BUILT BY CCP CALL BDOS ;GO ATTEMPT OPEN INR A ;CHECK IF FOUND JNZ FOUND ; MVI C,PRINT ;PRINT NOT FOUND ERROR LXI D,ERRM2 CALL BDOS JMP BOOT ;EXIT ; ; ;FOUND FILE SO LETS NEXT COMPUTE ITS FILE SIZE ; FOUND: LXI D,DEFCB ;THAT SAME FCB AGAIN MVI C,FSIZE CALL BDOS ;GET THE FILES SIZE IN RECORDS LHLD DEFCB+33 ;GET SIZE OF THE FILE MOV A,H ;CHECK IF GREATER THAN 128 RECORDS ORA A JNZ TOBIG MOV A,L ORA A ;CHECJ IF FILE EMPTY OR ONLY ONE RECORD JZ TOSMALL CPI 1 JZ TOSMALL CPI 129 JC SIZINA ;WE HAVE SIZE IN (A) TOBIG: MVI A,128 ;SET SIZE TO 128 DEFAULT SIZINA: STA NR ;SAVE NUMBER OF RECORDS JMP READFST ; TOSMALL: MVI C,PRINT ;PRINT FILE SIZE ERROR MESSAGE LXI D,ERRM3 CALL BDOS JMP BOOT ; ; ;READ FIRST RECORD INTO LIST BUFFER ; READFST: LXI D,LIST ;SET DMA ADDRESS TO LIST BUFFER MVI C,SBADDR CALL BDOS LXI H,0000H ;SET FIRST RECORD SHLD DEFCB+33 XRA A STA DEFCB+35 ;CLEAR R2 BYTE MVI C,RRAND ;READ RANDOM FIRST RECORD LXI D,DEFCB CALL BDOS ;NO NEED TO CHECK READ ERROR BECAUSE ;..WE KNOW THAT THESE RECORDS EXIST ; ; ;HERE TO PROCESS LIST INTO A SET OF NUMBERS THAT FIT OUT FILE ;RECORD COUNT RANGE. ; LDA NR ;FETCH NUMBER OF RECORDS DCR A ;SET NR-1 ; MVI B,0FFH ;INITIAL MASK VALUE MVI C,07H ;NUMBER OF TIMES TO ROTATE FOR MASK ; MKLP: RAL ;CHECK FOR ZERO BIT IN NR-1 JC HMSK ;EXIT WE HAVE OUR MASK ONE BIT FROM (A) PUSH PSW MOV A,B ;PUT A ZERO BIT INTO MASK ORA A ;CLEAR CARRY RAR ;PUT ZERO IN MOV B,A POP PSW DCR C ;DEBUMP SHIFT COUNT JNZ MKLP ; HMSK: ;HERE IF (B) HAS LIST MASK VALUE LDA NR ;GET NUMBER OF VALUES IN LIST DCR A MOV C,A ;PUT LOOP COUNTER INTO (C) MOV D,A ;SAVE NR-1 IN (D) LXI H,LIST ;POINT AT LIST LSTPROC: MOV A,M ;GET A LIST BYTE ANA B ;MASK IT CMP D ;IS RESULT GREATER THAN NR-2 JC VALOK ;VALUE IS OK ORA A ;DIVIDE BY TWO IF TOO BIG RAR VALOK: INR A ;SET VALUES TP FOR REAL RECORD NUMBERS MOV M,A ;PUT CONVERTED NUMBER INTO LIST AGAIN INX H ;BUMP LIST POINTER DCR C ;DEC LOOP COUNTER JNZ LSTPROC ;DO ALL BYTES OF LIST ; ; ;ENCODE/DECODE THE FILE HERE ; ENCODE: LXI H,LIST ;KEEP A POINTER TO THE LIST LDA OPTION ;IF OPTION IS 'E' WE GO FORWARD CPI 'E' MVI A,1 ;DEFAULT FORWARD CURRENT RECORD JZ FORWA ;GO FORWARD LDA NR ;INDEX TO END OF LIST FOR DECODE DCR A ;SET START RECORD FOR DECODE MOV E,A DCR E ;ZERO BASE INDEX MVI D,0 DAD D ; FORWA: SHLD LISTP ;SAVE LIST POINTER STA CURR ;SET CURRENT RECORD NUMBER TO START LDA NR DCR A STA CNTR ;SET NUMBER OF SWAPS ; ENCLP: LXI D,BUF1 ;SET BUFFER ONE AS DMA ADDRESS MVI C,SBADDR CALL BDOS LDA CURR ;READ CURRENT RECORD MOV L,A MVI H,00 SHLD DEFCB+33 ;SET RECORD NUMBER LXI D,DEFCB MVI C,RRAND ;READ THAT RECORD CALL BDOS ORA A ;CHECK ERROR JNZ DSKERR ; LXI D,BUF2 ;SET BUFFER 2 AS DMA ADDRESS MVI C,SBADDR CALL BDOS LHLD LISTP ;GET SWAP POSITION MOV L,M MVI H,00 SHLD DEFCB+33 ;SET SWAP RECORD NUMBER LXI D,DEFCB MVI C,RRAND ;READ SWAP RECORD CALL BDOS ORA A ;CHECK ERROR JNZ DSKERR ; LHLD LISTP ;IS SWAP RECORD AN ODD NUMB MOV B,M ;SABE XOR PATTERN IN (B) MOV A,M RAR JNC SWRT ;GO DO SWAP WRITE DIRECTLY IF EVEN LDA OPTION ;WHICH BUFFER TO XOR LXI H,BUF2 ;DEFAULT FOR 'E' CPI 'E' JZ INB2 ;USE BUFFER 2 LXI H,BUF1 ;IF DECODE USE BUFFER 1 INB2: MVI C,128 ;BUTE COUNT OF XOR XORLP: MOV A,M ;GET A BYTE TO XOR XRA B MOV M,A ;PUT BYTE BACK INX H ;BUMP BUFFER POINTER FOR XORING DCR C ;DEC BYTE COUNT JNZ XORLP ; SWRT: LXI D,BUF1 ;SET BUFFER ONE AS DMA ADDRESS MVI C,SBADDR CALL BDOS LHLD LISTP ;GET SWAP POSITION MOV L,M MVI H,00 SHLD DEFCB+33 ;SET SWAP RECORD NUMBER LXI D,DEFCB MVI C,WRAND ;WRITE SWAP RECORD CALL BDOS ORA A ;CHECK ERROR JNZ DSKERR ; LXI D,BUF2 ;SET BUFFER 2 AS DMA ADDRESS MVI C,SBADDR CALL BDOS LDA CURR ;WRITE CURRENT RECORD MOV L,A MVI H,00 SHLD DEFCB+33 ;SET RECORD NUMBER LXI D,DEFCB MVI C,WRAND ;WRITE THAT RECORD CALL BDOS ORA A ;CHECK ERROR JNZ DSKERR ; LDA CURR ;FETCH LOOP PARMS MOV B,A LHLD LISTP ; LDA OPTION ;CHECK OPTION CPI 'E' JZ INCF ;IF ENCODE INCR FORWARD ; DECB: DCX H ;DECREMENT DOWN THROUGH LOOP DCR B JMP PSVE ;SAVE PARMS INCF: INX H INR B PSVE: SHLD LISTP ;SAVE NEW LIST POSITION MOV A,B STA CURR ; LDA CNTR ;FETCH LOOP COUNTER DCR A STA CNTR JNZ ENCLP ;GO TO LOOP TO PROCESS MORE IF ;NOT DONE YET ; ; ;HERE WE ARE DONE WRITING SO LETS CLOSE UP AND GO HOME ; LXI D,DEFCB MVI C,CLOSE CALL BDOS INR A ;CHECK ERROR CODE JZ DSKERR ; MVI C,PRINT ;PRINT DONE MESSAGE LXI D,DONMSG CALL BDOS JMP BOOT ;EXIT ; ; ;EXIT POINT WITH ERROR MESSAGE IF THE DISK WRITE OPERATION ;RESULTED IN AN ERROR ; DSKERR: LXI D,ERRM4 ;PRINT GARBAGE FILE ERROR MVI C,PRINT CALL BDOS JMP BOOT ;EXIT FOR THE POOR GUY ; ; ;PROGRAM OPERATIONAL MESSAGES ; SNGMSG: DB CR,LF,'MICRO RESOURCES Disk File Scramble and' DB CR,LF,'Unscramble Utility Designed to Demonstrate' DB CR,LF,'CP/M Ver 2.2 Random Record I/O. (1/24/82)','$' ; DONMSG: DB CR,LF,'File Processing Complete','$' ; ERRM1: DB CR,LF,'No File Name Specified or Improper Option','$' ; ERRM2: DB CR,LF,'Specified File Not Found','$' ; ERRM3: DB CR,LF,'Cannot Process Files with 0 or 1 Record(s)','$' ; ERRM4: DB CR,LF,'File I/O Error, This Error Should NOT Normally' DB CR,LF,'Happen, But the File is now Garbaged...','$' ; ; ;PROGRAM DATA STORAGE SECTION ; OPTION: DS 1 ;PLACE TO STORE COMMAND LINE OPTION CHAR ; NR: DS 1 ;NUMBER OF RECORDS TO SWAP ; CNTR: DS 1 ;ENCODE/DECODE LOOP COUNTER ; CURR: DS 1 ;CURRENT SWAP SECTOR ; LISTP: DS 2 ;LIST SCAN POINTER ; LIST: DS 128 ;LIST BUFFER ; BUF1: DS 128 ;DATA BUFFER 1 ; BUF2: DS 128 ;DATA BUFFER 2 ; DS 36 STACK EQU $ ;USER STACK AREA ; ; END ; ; ;+++...END OF FILE