SLIDING INTO BDOS

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.

PART I

 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 1100ドル00ドル00ドルB ;IOBYTE MASK FOR LIST
 ;..DEVICE
LPT EQU 1000ドル00ドル00ドルB ;BIT VALUE FOR LPT #1
UL1 EQU 1100ドル00ドル00ドルB ;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,00000000ドル0000ドル0010ドルB ;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.

PART II

 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.

PART III

 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
  • cpm/sliding_into_bdos.txt
  • Zuletzt geändert: 2022年12月27日 12:55
  • von volkerp
Anmelden