TRS-80 DOS - LDOS 5.3.1 for the Model I - SYS8/SYS Disassembled

Page Customization

Summary:

LDOS 5.3.1 SYS8/SYS Disassembly - Dynamic File Space Allocation (Model I)

SYS8/SYS is the LDOS 5.3.1 overlay that gives a disk file more space while it is being written. Whenever the resident file-handling code in SYS0/SYS needs a record that lies beyond the granules the file already owns, it issues RST 28H with request code 9AH. The resident supervisor dispatcher at 4BCDH loads this overlay to 4E00H and enters it, and the overlay finds free space on the disk, records it in the file's directory entry, and hands back the physical position of the granule that was wanted.

The overlay is small, a single granule of 662 bytes spanning 4E00H through 5095H, plus a 2-byte load block deposited at 4BC9H in the resident padding area. Its transfer address is 4E00H. It must be present on any working SYSTEM diskette, and must be on the boot disk if a configuration file is to be loaded, because without it no file can ever grow.

An LDOS file records its disk space as a chain of extents. Four 2-byte extent entries live in the file's 32-byte directory record at DIR+16H through DIR+1DH. The first byte of an entry is the cylinder, and the second packs the starting granule on that cylinder into bits 7-5 and the number of granules in the extent, less one, into bits 4-0. A single extent can therefore describe at most 32 granules. The entry after the last one in use holds FFH. When all four entries are occupied, the link field at DIR+1EH is set to FEH and DIR+1FH names a secondary extended directory record which carries four more extent entries of its own, so the chain can be followed from record to record.

Free space is tracked by the Granule Allocation Table in sector 0 of the directory cylinder. It holds one byte per cylinder, and each set bit marks a granule on that cylinder which is already in use. The Hash Index Table in sector 1 holds one byte per directory record, carrying the hash of the file name occupying it, with zero marking a free record. SYS8/SYS reads the Granule Allocation Table into its own buffer at 5100H, immediately above its code, and uses the resident shared sector buffer SBUFF$ at 4200H for the Hash Index Table so the two never collide.

The allocation strategy favours contiguity. The overlay first works out the granule that physically follows the file's current last extent and tests that granule's bit in the Granule Allocation Table; if it is free the existing extent is simply grown by one, which costs no new extent entry at all. Only when that granule is taken, when the extent has reached its 32-granule limit, or when the next cylinder falls outside the cylinder map does the overlay start a fresh extent, scanning the table for any cylinder with a free granule. The scan makes two passes so that a search beginning part way along the disk wraps round and covers every cylinder before giving up. Each granule claimed is marked in the table image, the extent entry is built up in place, and the loop repeats until the file reaches the granule the caller asked for. The Granule Allocation Table and the directory record are then written back together.

Two conditions end the work with an error. If every cylinder is full the overlay returns error 27, disk space full. If a secondary extended directory record is needed but the Hash Index Table shows no free directory record, it returns error 30, directory full and cannot extend the file. Failures of the tables themselves return errors 20 and 21 for the Granule Allocation Table and 22 and 23 for the Hash Index Table.

On a successful return the overlay also refreshes the five-entry extent cache the resident routines keep at FCB+0EH inside the caller's File Control Block, whose entries are four bytes each, either updating the matching entry in place or shifting the cache down to open its first slot, and returns in Register A the granule's offset within the extent that holds it.

Memory Map

Address RangePurpose
4BC9H-4BCAH
2 bytes
Load block deposited into the resident padding area below the RST 28H dispatcher. SYS8/SYS writes the bytes 96H and 02H there.
4E00H-4E06H
7 bytes
Overlay entry and request dispatch. Masks the request code and accepts only sub-function 10H.
4E07H-4E4BH
69 bytes
Allocation driver, File Control Block extent cache update (five 4-byte entries from FCB+0EH), and the return of the granule's offset within its extent.
4E4CH-4E96H
75 bytes
Extent chain walk across the primary and any secondary extended directory records, with the trigger that extends the file.
4E97H-4F23H
141 bytes
Granule allocator. Reads the Granule Allocation Table and tries to grow the file's last extent contiguously.
4F24H-4F79H
86 bytes
Two-pass free granule search and extent construction, returning error 27 when the disk is full.
4F7AH-4F85H
12 bytes
Commit path. Writes the Granule Allocation Table and then the current directory record.
4F86H-4FDDH
88 bytes
Creation and linking of a secondary extended directory record, returning error 30 when the directory is full.
4FDEH-5019H
60 bytes
Hash Index Table search for a free directory record.
501AH-5032H
25 bytes
Self-modifying builders for the granule bit test and the granule bit mask.
5033H-503EH
12 bytes
Per-cylinder granule count adjustment for double-sided drives.
503FH-5095H
87 bytes
Granule Allocation Table and Hash Index Table sector read and write helpers.
5100H-51FFH
256 bytes
Granule Allocation Table buffer. Scratch space above the overlay image, not part of the loaded file.

Variables and Self-Modified Operands

Address RangePurpose
4E4AH
1 byte
Operand of the LD A,nn at 4E49H. Written at 4E12H with the target granule's offset within its extent, which is the value Register A returns to the caller.
4F36H
1 byte
Operand of the CP nn at 4F35H. Written at 4F2FH with the number of cylinders on the drive, bounding the free-space scan.
4F82H
1 byte
Operand of the LD B,nn at 4F81H. Written at 4E54H with the number of the directory record currently being processed, so the commit path rewrites the correct record of the chain. Also read back at 4FAEH and 4FC9H when a secondary record is linked.
4FD9H
1 byte
Operand of the LD (HL),nn at 4FD8H. Written at 4F9AH with the number of the newly created secondary directory record, which is then stored into the parent record's link field at DIR+1FH.
5008H
1 byte
Operand of the CP nn at 5007H. Written at 4FF8H with the number of directory records the drive holds, bounding the Hash Index Table search.
5025H
1 byte
Second opcode byte of the BIT 0,B at 5024H. Written at 5021H so the instruction tests the wanted granule's bit in a cylinder allocation byte.
5031H
1 byte
Second opcode byte of the SET 0,A at 5030H. Written at 502CH so the instruction builds a mask holding only the wanted granule's bit.
5100H-51FFH
256 bytes
Granule Allocation Table sector image. One byte per cylinder, each set bit marking an allocated granule. The per-cylinder map ends below offset CCH, which is where the table's configuration word begins.

Major Routines

AddressEntryExitName and Purpose
4E00HRegister A = request code, Register Pair IX = File Control BlockReturns to the RST 28H dispatcherOverlay Entry
Masks the request code with 70H and services only sub-function 10H, which request code 9AH selects.
4E07HRegister Pair BC = target file-relative granule number, Register Pair IX = File Control BlockRegister A = granule offset within its extent, Register Pair HL = extent word, CARRY set if the cache was shiftedAllocation Driver
Locates or creates the target granule and refreshes the five-entry, 4-byte-per-entry extent cache at FCB+0EH.
4E4CHRegister Pair BC = target granule number, Register Pair IX = File Control BlockZ set with Register Pair HL = extent word and Register Pair DE = preceding granule count, or NZ with an error code in Register AExtent Chain Walk
Adds up the granules in each extent across the primary and any secondary extended directory records until the target granule is covered, following FEH links and extending the file at an FFH terminator.
4E97HRegister Pair BC = target granule number, Register Pair DE = granules already owned, Register Pair HL = the FFH terminatorZ set when the file has been extended, or NZ with an error code in Register AGranule Allocator
Reads the Granule Allocation Table and grows the last extent contiguously where possible, otherwise moving to a fresh extent slot or a secondary directory record.
4F24HRegister Pair HL = Granule Allocation Table pointer, Register Pair DE = extent slot, Register Pair BC = granules still neededFalls into the commit path, or returns error 27 in Register AFree Granule Search
Scans every cylinder twice for a free granule, claims it in the table image, and builds the extent entry around it.
4F7AHRegister Pair IX = File Control Block, operand at 4F82H = directory record numberZ set on success, or NZ with error 21 or a directory write errorCommit Path
Writes the Granule Allocation Table image back and then rewrites the current directory record.
4F86HRegister Pair IX = File Control Block, operand at 4F82H = parent record numberZ set on success, or NZ with error 30 or a table error in Register ASecondary Record Creation
Claims a free Hash Index Table slot, builds a secondary extended directory record with empty extents, and links it from the parent record.
4FDEHRegister A = starting slot position, Register C = drive number, Register Pair HL based at the Hash Index Table imageZ set with Register Pair HL addressing a free slot, or NZ when the directory is fullHash Index Table Search
Bounds the search by the drive's real directory size and scans for a zero hash byte, wrapping once.
501AHRegister A = granule number 0-7, Register B = cylinder allocation byteZ set when the granule is freeGranule Bit Test Builder
Constructs and executes a BIT n,B instruction for the given granule number.
5027HRegister A = granule number 0-7Register A = mask with only that granule's bit setGranule Bit Mask Builder
Constructs and executes a SET n,A instruction for the given granule number.
5033HRegister A = granules per track, Register C = drive numberRegister A = granules per cylinderPer-Cylinder Adjustment
Doubles the count when Drive Code Table byte 4 bit 5 shows a double-sided drive.
503FHRegister C = drive numberZ set on success, or NZ with error 20Read Granule Allocation Table
Reads sector 0 of the directory cylinder into the buffer at 5100H.
5052HRegister C = drive numberZ set on success, or NZ with error 21Write Granule Allocation Table
Writes 5100H back to sector 0 with a deleted-data address mark and verifies it.
506BHRegister C = drive numberZ set on success, or NZ with error 22Read Hash Index Table
Reads sector 1 of the directory cylinder into SBUFF$ at 4200H.
507DHRegister C = drive numberZ set on success, or NZ with error 23Write Hash Index Table
Writes 4200H back to sector 1 with a deleted-data address mark and verifies it.

Error Codes Returned

CodeRaised AtMeaning
14H (20)504FHGAT read error. Sector 0 of the directory cylinder could not be read.
15H (21)5066HGAT write error. The updated Granule Allocation Table could not be written or failed to verify.
16H (22)507AHHIT read error. Sector 1 of the directory cylinder could not be read.
17H (23)5091HHIT write error. The updated Hash Index Table could not be written or failed to verify.
1BH (27)4F49HDisk space full. Both passes of the free granule search found every cylinder fully allocated.
1EH (30)4F95HDirectory full, cannot extend file. No free directory record was available for a secondary extended entry.

Cross-References

SYS8/SYS is reached only through the resident supervisor. The resident record-positioning code in SYS0/SYS issues RST 28H with request code 9AH when a record lies beyond the space a file owns. The call goes through the launch stub at 4AB3H (LD A,9AH then RST 28H), reached by the CALL 4AB3H at 4A98H, and the dispatcher at 4BCDH loads this overlay through the loader at 4BF5H. On return the resident code plants the offset this overlay leaves in Register A into its own operand at 4AE5H, and tests the CARRY flag at 4AAFH to decide whether the freed extent cache slot still has to be filled at 4AD5H. The overlay in turn calls back into the resident core for every disk operation: DIRRD at 4B10H and DIRWR at 4B1FH for directory records, DIRCYL at 4B65H and RDSSEC at 4B45H to reach the directory cylinder, WRPROT at 4768H and VERSEC at 4772H to write and verify the Granule Allocation Table and Hash Index Table sectors with a deleted-data address mark, DCTBYT at 479CH for drive geometry, and MULTEA at 4B6CH and DIVEA at 4B7BH for the arithmetic. It also reads the resident tick counter TIMER$ at 4040H and the constant search-origin cylinder byte at 4767H, and uses the resident shared sector buffer SBUFF$ at 4200H.

The work SYS8/SYS does is undone by SYS3/SYS, which on closing a file releases any granules that were allocated but never used, unlinking and de-hashing a secondary extended directory record that becomes empty. The error codes returned here are turned into readable text by SYS4/SYS, which services @ERROR through request code 96H. The directory record and extent layout it maintains is the same structure written by SYS3/SYS and read by the directory display in SYS6/SYS.

Disassembly:

4BC9H - Overlay Load Block

The two bytes every SYS overlay deposits into the padding area of the resident core immediately below the RST 28H dispatcher. They are data, not instructions, and are never executed. SYS8/SYS writes 96H and 02H here, in the same way SYS3/SYS writes A6H and 02H and SYS4/SYS writes AFH and 03H.

4BC9
DEFB 96H, 02H 96 02
Two-byte load block written into the resident padding area at 4BC9H through 4BCAH by the overlay loader as SYS8/SYS is brought into memory. The bytes are data and are not part of the executable image, which begins at 4E00H.

4E00H - Overlay Entry and Request Dispatch

SYS8/SYS is entered here by the resident RST 28H dispatcher at 4BCDH with the request code in Register A and Register Pair IX addressing the caller's File Control Block. Only sub-function 10H, which request code 9AH selects, is implemented; every other value returns immediately.

4E00
AND 70H E6 70
Register A holds the RST 28H request code that the resident supervisor dispatcher at 4BCDH passed to this overlay, which is 9AH for the allocate-file-space service. Mask off everything except bits 6-4 to isolate the sub-function selector, leaving 10H for request code 9AH.
4E02
CP 10H FE 10
Compare the isolated sub-function in Register A against 10H, the only sub-function this overlay implements (allocate additional space to an open disk file). If Register A equals 10H, the Z FLAG is set; otherwise the NZ FLAG is set.
4E04
If the Z FLAG has been set (the request is sub-function 10H, allocate file space), JUMP to 4E07H to carry out the allocation.
4E06
RET C9
Any other sub-function is not serviced by this overlay; return to the RST 28H dispatcher at 4BCDH having taken no action.

4E07H - Allocation Driver and File Control Block Extent Cache Update

The driver locates the granule the caller asked for, extending the file if the extent chain is too short, and then refreshes the five-entry extent cache held in the File Control Block at FCB+0EH so the resident record-positioning routines can convert record numbers into physical cylinders and granules. It returns the granule's offset within its extent in Register A.

4E07
GOSUB to 4E4CH to walk the file's chain of directory extents looking for the granule whose file-relative number is in Register Pair BC (the granule the caller needs in order to continue writing). That routine allocates and appends granules whenever the chain is too short. On return the Z FLAG is set for success, Register Pair HL holds the located extent word (Register H = the packed starting-granule and count byte from the extent, Register L = the cylinder), and Register Pair DE holds the number of granules that precede that extent in the file.
4E0A
RET NZ C0
If the NZ FLAG has been set, 4E4CH reported a failure and Register A holds the error code (1BH disk space full, 1EH directory full, or a Granule Allocation Table or Hash Index Table error). Return that error to the caller.
4E0B
PUSH HL E5
Save the extent word returned by 4E4CH (Register H = packed starting-granule and count byte, Register L = cylinder) onto the stack while Register Pair HL is reused for arithmetic.
4E0C
LD H,B 60
Copy Register B, the high byte of the caller's target file-relative granule number, into Register H.
4E0D
LD L,C 69
Copy Register C, the low byte of the caller's target file-relative granule number, into Register L, so Register Pair HL now holds the target granule number.
4E0E
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so that the subtract with carry which follows is an exact subtraction.
4E0F
SBC HL,DE ED 52
Subtract Register Pair DE (the number of granules preceding the located extent) from Register Pair HL (the target file-relative granule number). Register Pair HL is left holding the target granule's offset within that extent.
4E11
LD A,L 7D
Move the low byte of that within-extent granule offset from Register L into Register A. An extent can span at most 32 granules, so the offset always fits in one byte.
4E12
LD (4E4AH),A 32 4A 4E
Self-Modifying Code
Store the within-extent granule offset held in Register A into 4E4AH, which is the immediate operand of the LD A,nn instruction at 4E49H. The exit path of this routine executes that instruction, so the offset is what Register A returns to the caller.
4E15
POP HL E1
Restore the extent word saved at 4E0BH into Register Pair HL (Register H = packed starting-granule and count byte, Register L = cylinder).
4E16
PUSH DE D5
Save Register Pair DE, the count of granules preceding the located extent, onto the stack so it can be handed back to the caller at 4E48H.
4E17
PUSH IX DD E5
Push Register Pair IX, which the caller set to the address of the File Control Block for the open file, onto the stack.
4E19
EX (SP),HL E3
Exchange the top of the stack with Register Pair HL. Register Pair HL now holds the File Control Block address and the extent word is parked on the stack.
4E1A
LD DE,000EH 11 0E 00
Load Register Pair DE with 000EH, the offset of the extent cache inside the File Control Block. That cache holds five 4-byte entries which the resident file routines consult to convert a record number into a physical cylinder and granule. The resident code at 4AD5H fills a slot with four bytes and the shift at 4AC3H scales its byte count by four.
4E1D
ADD HL,DE 19
Add 000EH to the File Control Block address in Register Pair HL, so Register Pair HL now points at FCB+0EH, the first entry of the File Control Block extent cache.
4E1E
POP DE D1
Recover the extent word from the stack into Register Pair DE (Register D = packed starting-granule and count byte, Register E = cylinder).
4E1F
LD B,05H 06 05
Load Register B with 05H, the number of 4-byte extent entries cached in the File Control Block, to serve as the loop counter.
4E21
LD A,(HL) 7E
Loop Start
Fetch the cylinder byte of the extent cache entry addressed by Register Pair HL into Register A.
4E22
INC HL 23
Advance Register Pair HL to the packed starting-granule and count byte of this cache entry.
4E23
CP E BB
Compare the cache entry's cylinder in Register A against Register E, the cylinder of the extent just located in the directory. If they are equal, the Z FLAG is set; otherwise the NZ FLAG is set.
4E24
If the NZ FLAG has been set (this cache entry describes a different cylinder), JUMP to 4E2CH to step on to the next cache entry.
4E26
LD A,(HL) 7E
The cylinders match. Fetch this cache entry's packed starting-granule and count byte from the address in Register Pair HL into Register A.
4E27
XOR D AA
Exclusive-OR Register A (the cache entry's packed byte) against Register D (the packed byte of the extent located in the directory). Every bit that matches becomes 0.
4E28
AND 0E0H E6 E0
Keep only bits 7-5 of the result, which is the starting-granule field of the packed byte. If those three bits are all zero the two extents start at the same granule.
4E2A
If the Z FLAG has been set (the starting granule also matches, so this cache entry already describes the located extent), JUMP to 4E45H to refresh the entry in place.
4E2C
DEC B 05
Decrement Register B, the count of File Control Block extent cache entries still to be examined.
4E2D
If the Z FLAG has been set (all five cache entries have been examined without finding a match), JUMP to 4E34H to shift the cache and open a slot for this extent.
4E2F
INC HL 23
Advance Register Pair HL by one byte; three of these instructions together step from one 4-byte extent cache entry to the next, together with the advance already made at 4E22H.
4E30
INC HL 23
Advance Register Pair HL by a second byte toward the next 4-byte extent cache entry.
4E31
INC HL 23
Advance Register Pair HL by a third byte, so Register Pair HL now addresses the cylinder byte of the next extent cache entry.
4E32
LOOP BACK to 4E21H to compare the next File Control Block extent cache entry against the located extent.
Loop End
4E34
PUSH DE D5
No cache entry matched. Save the extent word in Register Pair DE (Register D = packed starting-granule and count byte, Register E = cylinder) onto the stack.
4E35
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, so Register Pair DE holds the current position in the File Control Block extent cache and Register Pair HL is free for the block-move setup.
4E36
LD HL,0FFFCH 21 FC FF
Load Register Pair HL with 0FFFCH, which is minus four, ready to bias the cache pointer backwards by four bytes.
4E39
ADD HL,DE 19
Add the cache pointer in Register Pair DE to the minus four in Register Pair HL, so Register Pair HL becomes the source address four bytes below the destination address held in Register Pair DE.
4E3A
LD BC,000CH 01 0C 00
Load Register Pair BC with 000CH, that is twelve bytes, the size of the four extent cache entries that must be pushed down to free the first slot.
4E3D
LDDR ED B8
Block move with decrement and repeat. Source is the address in Register Pair HL, destination is the address in Register Pair DE, and Register Pair BC counts twelve bytes. Both pointers decrement, so the four older File Control Block extent cache entries are shifted one slot toward the end of the cache and the first slot at FCB+0EH is freed for the newly located extent.
4E3F
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL so Register Pair HL addresses the freed extent cache slot.
4E40
POP BC C1
Recover the extent word saved at 4E34H into Register Pair BC (Register B = packed starting-granule and count byte, Register C = cylinder) so the caller receives it.
4E41
XOR A AF
Set Register A to ZERO and clear all flags.
4E42
SCF 37
Set the CARRY flag, which reports to the caller that the File Control Block extent cache was shifted and the freed slot addressed by Register Pair HL still has to be filled in with the extent word now held in Register Pair BC.
4E43
JUMP to 4E48H, the common exit, to restore the preceding-granule count and return the within-extent offset.
4E45
LD (HL),D 72
A cache entry already described this extent. Store Register D, the packed starting-granule and count byte of the extent as it now stands in the directory, over the cached copy addressed by Register Pair HL, so the cache reflects the granule that was just added.
4E46
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL, leaving the extent word in Register Pair HL and the cache pointer in Register Pair DE.
4E47
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, which reports to the caller that the File Control Block extent cache was not shifted.
4E48
POP DE D1
Restore the count of granules preceding the located extent, saved at 4E16H, into Register Pair DE.
4E49
LD A,00H 3E 00
Self-Modifying Code
The 00H operand at 4E4AH was overwritten at 4E12H with the target granule's offset within its extent. Register A therefore returns that offset to the caller.
4E4B
RET C9
Return to the RST 28H dispatcher at 4BCDH with the Z FLAG set for success, Register A holding the granule's offset within its extent, and the extent word available to the resident file routines.

4E4CH - Extent Chain Walk and Extend Trigger

This routine reads the file's primary directory record and walks its four extent entries, adding up the granules each one contains until the running total covers the target granule. An FEH marker in an extent's cylinder byte links to a secondary extended directory record, whose extents are walked in turn; an FFH marker ends the chain and triggers the allocator at 4E97H.

4E4C
PUSH BC C5
Save the caller's target file-relative granule number, held in Register Pair BC, onto the stack while Register Pair BC is reused to address the directory.
4E4D
LD DE,0000H 11 00 00
Clear Register Pair DE to zero. After the exchange at 4E61H this becomes the running total of granules counted so far along the file's extent chain.
4E50
LD B,(IX+07H) DD 46 07
Load Register B with FCB+07H, the directory record number of the file's primary directory entry, taken from the File Control Block addressed by Register Pair IX.
4E53
LD A,B 78
Copy the directory record number now in Register B into Register A so it can be planted in the directory-write helper.
4E54
LD (4F82H),A 32 82 4F
Self-Modifying Code
Store the directory record number held in Register A into 4F82H, the immediate operand of the LD B,nn instruction at 4F81H. The commit routine at 4F7AH executes that instruction, so it always rewrites whichever directory record of the chain is currently being processed.
4E57
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4E5A
GOSUB to the resident DIRRD routine at 4B10H to read directory record number Register B from drive Register C. On return Register Pair HL points at that 32-byte directory record inside the resident sector buffer and the Z FLAG is set if the read succeeded.
4E5D
LD BC,0016H 01 16 00
Load Register Pair BC with 0016H, the offset of the first extent field within a 32-byte LDOS directory record.
4E60
ADD HL,BC 09
Add 0016H to the directory record address in Register Pair HL, so Register Pair HL now points at DIR+16H, the first of the four 2-byte extent entries in this record.
4E61
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL. Register Pair DE now walks the extent list, and Register Pair HL becomes the running total of granules counted so far, which is zero on the first pass.
4E62
POP BC C1
Restore the caller's target file-relative granule number from the stack into Register Pair BC.
4E63
RET NZ C0
If the NZ FLAG has been set, the resident DIRRD routine at 4B10H failed to read the directory record; return that error to the caller.
4E64
LD A,(DE) 1A
Loop Start
Fetch the cylinder byte of the extent entry addressed by Register Pair DE into Register A. A value below FEH is a real cylinder number; FEH marks a link to a secondary extended directory record; FFH marks the end of the file's extent chain.
4E65
CP 0FEH FE FE
Compare the extent's first byte in Register A against 0FEH. If Register A is below 0FEH the CARRY FLAG is set; if Register A is 0FEH or 0FFH the NO CARRY FLAG is set.
4E67
If the NO CARRY FLAG has been set (the byte is FEH or FFH and therefore is not a data extent), JUMP to 4E88H to either follow the link to a secondary directory record or extend the file.
4E69
INC DE 13
This is a real extent. Advance Register Pair DE to the extent's second byte, the packed starting-granule and count byte.
4E6A
LD A,(DE) 1A
Fetch that packed byte into Register A. Bits 7-5 hold the granule on the cylinder at which the extent starts, and bits 4-0 hold the number of granules in the extent minus one.
4E6B
PUSH HL E5
Save the running total of granules counted so far, held in Register Pair HL, before this extent is added to it.
4E6C
AND 1FH E6 1F
Keep only bits 4-0 of the packed byte in Register A, which is the number of granules in this extent minus one.
4E6E
INC A 3C
Increment Register A so it holds the true number of granules contained in this extent.
4E6F
ADD A,L 85
Add this extent's granule count in Register A to Register L, the low byte of the running granule total.
4E70
LD L,A 6F
Store the sum back into Register L, the low byte of the running total of granules counted along the chain.
4E71
If the NO CARRY FLAG has been set (the addition did not overflow the low byte), JUMP to 4E74H and skip the high-byte adjustment.
4E73
INC H 24
Propagate the carry into Register H, the high byte of the running total of granules counted along the chain.
4E74
PUSH HL E5
Save the updated running granule total held in Register Pair HL, which is now the count of granules covered by the file up to and including this extent.
4E75
DEC HL 2B
Decrement Register Pair HL so it holds the highest file-relative granule number covered so far, since granule numbering starts at zero.
4E76
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the subtract with carry which follows is exact.
4E77
SBC HL,BC ED 42
Subtract the caller's target file-relative granule number in Register Pair BC from the highest granule number covered so far in Register Pair HL. If the target lies beyond the end of this extent the CARRY FLAG is set.
4E79
POP HL E1
Restore the running granule total, saved at 4E74H, into Register Pair HL.
4E7A
If the NO CARRY FLAG has been set, the target granule falls inside this extent; JUMP to 4E80H to return its position to the caller.
4E7C
INC DE 13
The target lies beyond this extent. Advance Register Pair DE to the cylinder byte of the next extent entry in the directory record.
4E7D
POP AF F1
Discard the running granule total that was saved at 4E6BH, since the total held in Register Pair HL has already been advanced past this extent.
4E7E
LOOP BACK to 4E64H to examine the next extent entry in the chain.
Loop End
4E80
POP HL E1
The target granule lies within this extent. Recover the running granule total as it stood before this extent was added, which is the number of granules in the file that precede this extent.
4E81
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL. Register Pair DE now holds the count of granules preceding this extent, and Register Pair HL points at the extent's packed starting-granule and count byte.
4E82
LD A,(HL) 7E
Fetch the packed starting-granule and count byte from the address in Register Pair HL into Register A.
4E83
DEC HL 2B
Step Register Pair HL back one byte to the extent's cylinder byte.
4E84
LD L,(HL) 6E
Load the extent's cylinder number from the address in Register Pair HL into Register L.
4E85
LD H,A 67
Move the packed starting-granule and count byte from Register A into Register H, so Register Pair HL is the complete extent word with the cylinder in Register L and the packed granule field in Register H.
4E86
XOR A AF
Set Register A to ZERO and clear all flags, which sets the Z FLAG to report success to 4E07H.
4E87
RET C9
Return to 4E07H with Register Pair HL holding the extent word and Register Pair DE holding the count of granules preceding that extent.
4E88
PUSH BC C5
The extent byte in Register A was FEH or FFH. Save the caller's target file-relative granule number in Register Pair BC onto the stack.
4E89
EX DE,HL EB
Exchange Register Pair DE with Register Pair HL. Register Pair HL now points at the FEH or FFH marker byte, and Register Pair DE holds the running total of granules the file currently owns.
4E8A
The comparison at 4E65H left the Z FLAG set only for FEH. If the NZ FLAG has been set the marker is FFH, meaning the extent chain has ended and the file must be given more space, so JUMP to 4E90H.
4E8C
INC HL 23
The marker is FEH, a link to a secondary extended directory record. Advance Register Pair HL to the following byte, which holds the record number of that secondary directory record.
4E8D
LD B,(HL) 46
Load the secondary directory record number from the address in Register Pair HL into Register B, ready for the directory read at 4E5AH.
4E8E
LOOP BACK to 4E53H to read the linked secondary directory record and carry on counting granules through its extent entries.
4E90
The extent chain ended with FFH and the file is short of space. GOSUB to 4E97H to allocate the granules still needed and splice them into the file's extent list.
4E93
POP BC C1
Restore the caller's target file-relative granule number from the stack into Register Pair BC.
4E94
RET NZ C0
If the NZ FLAG has been set, the allocation failed and Register A holds the error code (1BH disk space full, 1EH directory full, or a Granule Allocation Table or Hash Index Table error). Return that error.
4E95
LOOP BACK to 4E4CH to walk the extent chain again from the primary directory record, now that the file owns more space, so the target granule can be located and returned.

4E97H - Granule Allocator

The allocator reads the drive's Granule Allocation Table, works out how many granules the file is short by, and tries first to grow the file's current last extent with the granule that physically follows it. When that granule is taken, when the extent already holds its maximum of 32 granules, or when the candidate cylinder lies outside the cylinder map, it moves on to a fresh extent slot, and when all four slots of the record are in use it creates a secondary extended directory record.

4E97
PUSH BC C5
Save the caller's target file-relative granule number, held in Register Pair BC, onto the stack.
4E98
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4E9B
GOSUB to 503FH to read the Granule Allocation Table sector of drive Register C into this overlay's Granule Allocation Table buffer at 5100H. That table carries one byte per cylinder, with a set bit marking each granule that is already allocated.
4E9E
POP BC C1
Restore the caller's target file-relative granule number from the stack into Register Pair BC.
4E9F
RET NZ C0
If the NZ FLAG has been set, the Granule Allocation Table could not be read; return to 4E90H with error 14H in Register A.
4EA0
PUSH HL E5
Save Register Pair HL, which points at the FFH end-of-chain marker in the directory record's extent list, onto the stack.
4EA1
LD H,B 60
Copy Register B, the high byte of the target file-relative granule number, into Register H.
4EA2
LD L,C 69
Copy Register C, the low byte of the target file-relative granule number, into Register L, so Register Pair HL holds the target granule number.
4EA3
XOR A AF
Set Register A to ZERO and clear all flags, in particular the CARRY flag, so the subtract with carry which follows is exact.
4EA4
SBC HL,DE ED 52
Subtract Register Pair DE, the number of granules the file already owns, from the target granule number in Register Pair HL, leaving the shortfall in Register Pair HL.
4EA6
LD B,H 44
Copy Register H, the high byte of the shortfall, into Register B.
4EA7
LD C,L 4D
Copy Register L, the low byte of the shortfall, into Register C, so Register Pair BC holds the shortfall.
4EA8
INC BC 03
Increment Register Pair BC so it holds the number of granules that must be allocated for the file to reach the caller's target granule.
4EA9
POP DE D1
Recover the pointer to the FFH end-of-chain marker, saved at 4EA0H, into Register Pair DE.
4EAA
INC DE 13
Advance Register Pair DE one byte past the FFH end-of-chain marker so the position tests at 4EC7H measure the slot that follows the last extent in use.
4EAB
LD A,R ED 5F
Read the Z80 memory-refresh register into Register A. Its value changes with every instruction fetched, so it supplies a varying quantity.
4EAD
RLA 17
Rotate Register A left through the CARRY flag.
4EAE
LD L,A 6F
Place the varying value from Register A into Register L.
4EAF
LD A,(4040H) 3A 40 40
Fetch the resident TIMER$ interrupt tick counter at 4040H into Register A. It is advanced by the real-time-clock interrupt handler on every tick, so it too supplies a varying quantity.
4EB2
LD H,A 67
Place the tick counter from Register A into Register H, completing a varying 16-bit quantity in Register Pair HL. Register L is reloaded at 4EC2H and Register H at 4EC4H before Register Pair HL is read again, so this quantity does not reach the allocation itself. The search origin comes instead from the constant byte at 4767H, which no code in the resident core writes.
4EB3
PUSH BC C5
Save the count of granules still to be allocated, held in Register Pair BC, onto the stack.
4EB4
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4EB7
LD A,06H 3E 06
Load Register A with 06H to select byte 6 of the Drive Code Table entry, which holds the highest cylinder number on the drive.
4EB9
GOSUB to the resident DCTBYT routine at 479CH, which returns in Register A the Drive Code Table geometry byte selected by Register A for the drive in Register C. Register A comes back holding DCT+06H, the highest cylinder number on that drive.
4EBC
INC A 3C
Increment Register A so it holds the total number of cylinders on the drive rather than the highest cylinder number.
4EBD
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4EBE
PUSH AF F5
Save the cylinder count now in Register A onto the stack while Register A is used to fetch the search origin.
4EBF
LD A,(4767H) 3A 67 47
Fetch the resident byte at 4767H into Register A. That byte is the spare slot of the WRSECT primitive at 4763H in the resident disk-primitive table, where each entry occupies five bytes and leaves one over. Nothing in the resident core ever writes it, so it holds the constant 01H and the search of the Granule Allocation Table for free space always begins at cylinder 1.
4EC2
LD L,A 6F
Place that starting cylinder number from Register A into Register L, where it becomes the offset into the Granule Allocation Table image.
4EC3
POP AF F1
Restore the drive's cylinder count from the stack into Register A.
4EC4
LD H,51H 26 51
Load Register H with 51H. Register Pair HL now addresses 5100H plus the starting cylinder number, which is that cylinder's allocation byte inside the Granule Allocation Table buffer.
4EC6
PUSH BC C5
Save the count of granules still to be allocated, held in Register Pair BC, onto the stack.
4EC7
LD A,E 7B
Copy Register E, the low byte of the extent-list pointer in Register Pair DE, into Register A so the pointer's position within the 32-byte directory record can be tested.
4EC8
AND 1EH E6 1E
Keep only bits 4-1 of the pointer's low byte, which give the even offset of this slot within the 32-byte directory record.
4ECA
CP 16H FE 16
Compare that offset in Register A against 16H, the offset of the very first extent field in a directory record. If Register A equals 16H, the Z FLAG is set; otherwise the NZ FLAG is set.
4ECC
If the Z FLAG has been set, the end-of-chain marker stood in the very first extent slot, so this directory record holds no extents at all and there is no previous extent to grow. JUMP to 4F24H to start a fresh extent.
4ECE
DEC E 1D
Step Register Pair DE back one byte from the slot following the end-of-chain marker.
4ECF
DEC E 1D
Step Register Pair DE back a second byte, so Register Pair DE now addresses the packed starting-granule and count byte of the file's current last extent.
4ED0
LD A,(DE) 1A
Fetch the packed starting-granule and count byte of the file's current last extent, addressed by Register Pair DE, into Register A. Bits 7-5 hold the granule on the cylinder at which it starts and bits 4-0 hold its granule count minus one.
4ED1
AND 1FH E6 1F
Keep only bits 4-0 of Register A, the last extent's granule count minus one.
4ED3
INC A 3C
Increment Register A so it holds the true number of granules in the file's current last extent.
4ED4
LD C,A 4F
Copy that granule count from Register A into Register C, where it is kept while Register A is reused.
4ED5
CP 20H FE 20
Compare the last extent's granule count in Register A against 20H, that is 32, the largest number of granules a single extent can describe. If Register A equals 20H, the Z FLAG is set.
4ED7
If the Z FLAG has been set the last extent is already at its 32-granule maximum and cannot be grown, so JUMP to 4F0DH to move on to a fresh extent slot.
4ED9
LD A,(DE) 1A
Fetch the last extent's packed starting-granule and count byte, addressed by Register Pair DE, into Register A again.
4EDA
AND 0E0H E6 E0
Keep only bits 7-5 of Register A, the granule on the cylinder at which the last extent starts.
4EDC
RLCA 07
Rotate Register A left circularly; three of these instructions together bring the starting-granule field down from bits 7-5 into bits 2-0.
4EDD
RLCA 07
Rotate Register A left circularly a second time, continuing to shift the starting-granule field down into the low three bits.
4EDE
RLCA 07
Rotate Register A left circularly a third time, so Register A now holds the last extent's starting granule as a plain number from 0 to 7.
4EDF
ADD A,C 81
Add Register C, the last extent's granule count, to its starting granule in Register A. Register A now holds the granule number that immediately follows the end of the last extent.
4EE0
PUSH DE D5
Save the extent-list pointer in Register Pair DE onto the stack while Register Pair DE is reused.
4EE1
LD E,A 5F
Move the granule number that follows the last extent from Register A into Register E, ready for the division at 4EF5H.
4EE2
LD A,08H 3E 08
Load Register A with 08H to select byte 8 of the Drive Code Table entry, which packs the number of granules per track into bits 7-5 and the number of sectors per granule into bits 4-0.
4EE4
PUSH BC C5
Save the count of granules still to be allocated, held in Register Pair BC, onto the stack.
4EE5
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4EE8
GOSUB to the resident DCTBYT routine at 479CH, which returns in Register A the Drive Code Table geometry byte selected by Register A for the drive in Register C, here DCT+08H with granules per track in bits 7-5.
4EEB
RLCA 07
Rotate Register A left circularly; three of these instructions together bring the granules-per-track field down from bits 7-5 into bits 2-0.
4EEC
RLCA 07
Rotate Register A left circularly a second time, continuing to shift the granules-per-track field into the low three bits.
4EED
RLCA 07
Rotate Register A left circularly a third time, so the granules-per-track field now occupies bits 2-0 of Register A.
4EEE
AND 07H E6 07
Keep only bits 2-0 of Register A, discarding the sectors-per-granule field that shared the byte.
4EF0
INC A 3C
Increment Register A so it holds the true number of granules on one track rather than that count minus one.
4EF1
GOSUB to 5033H, which doubles Register A when the drive is double sided, so Register A holds the number of granules on one whole cylinder.
4EF4
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4EF5
GOSUB to the resident DIVEA divide primitive at 4B7BH to divide the granule number that follows the last extent, held in Register E, by the number of granules per cylinder in Register A. The quotient is how many whole cylinders beyond the extent's own cylinder that granule lies, and the remainder is its granule position on that cylinder.
4EF8
LD B,A 47
Move the quotient, the number of whole cylinders to step forward, from Register A into Register B.
4EF9
LD C,E 4B
Move the remainder, the granule position on the target cylinder, from Register E into Register C.
4EFA
POP DE D1
Recover the extent-list pointer saved at 4EE0H into Register Pair DE.
4EFB
DEC DE 1B
Step Register Pair DE back one byte to the cylinder byte of the file's current last extent.
4EFC
LD A,(DE) 1A
Fetch that extent's cylinder number, addressed by Register Pair DE, into Register A.
4EFD
INC DE 13
Advance Register Pair DE again to the packed starting-granule and count byte of the last extent.
4EFE
ADD A,B 80
Add Register B, the number of whole cylinders to step forward, to the last extent's cylinder in Register A. Register A now holds the cylinder that carries the granule immediately following the extent.
4EFF
LD L,A 6F
Move that cylinder number from Register A into Register L, the offset into the Granule Allocation Table image.
4F00
LD H,51H 26 51
Load Register H with 51H, so Register Pair HL addresses that cylinder's allocation byte inside the Granule Allocation Table buffer based at 5100H.
4F02
CP 0CBH FE CB
Compare the candidate cylinder number in Register A against 0CBH. The per-cylinder allocation bytes occupy the start of the Granule Allocation Table sector and end below the configuration word at offset CCH, so a value of 0CBH or above lies outside the cylinder map. If Register A is below 0CBH the CARRY FLAG is set.
4F04
If the NO CARRY FLAG has been set the candidate cylinder lies past the end of the cylinder map, so the extent cannot be grown; JUMP to 4F0DH to move on to a fresh extent slot.
4F06
LD A,C 79
Move the candidate granule's position on that cylinder from Register C into Register A.
4F07
LD B,(HL) 46
Fetch that cylinder's allocation byte from the Granule Allocation Table buffer, addressed by Register Pair HL, into Register B. Each set bit marks a granule on the cylinder that is already in use.
4F08
GOSUB to 501AH, which plants and executes a BIT n,B instruction for the granule number in Register A, testing that granule's bit in the allocation byte held in Register B. The Z FLAG comes back set if the granule is free.
4F0B
If the Z FLAG has been set the granule immediately following the file's last extent is free, so the extent can simply be grown; JUMP to 4F60H to claim that granule.
4F0D
INC E 1C
The last extent cannot be grown. Advance Register E, the low byte of the extent-list pointer, by one byte.
4F0E
INC E 1C
Advance Register E by a second byte, so Register Pair DE now addresses the next 2-byte extent slot in the directory record.
4F0F
LD A,E 7B
Copy Register E, the low byte of the extent-list pointer, into Register A to test how far through the record it has moved.
4F10
AND 1EH E6 1E
Keep only bits 4-1 of the pointer's low byte, giving the even offset of this slot within the 32-byte directory record.
4F12
CP 1EH FE 1E
Compare that offset in Register A against 1EH, the offset of the extended-directory link field which follows the four extent slots. If Register A equals 1EH, the Z FLAG is set.
4F14
If the NZ FLAG has been set there is still a free extent slot in this directory record, so JUMP to 4F24H to start a fresh extent in it.
4F16
All four extent slots in this directory record are in use. GOSUB to 4F7AH to write the Granule Allocation Table and this directory record back to disk before a secondary record is created.
4F19
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4F1A
RET NZ C0
If the NZ FLAG has been set the Granule Allocation Table or directory write failed; return that error code in Register A to 4E90H.
4F1B
PUSH BC C5
Save the count of granules still to be allocated, held in Register Pair BC, onto the stack again.
4F1C
GOSUB to 4F86H to create a new secondary extended directory record for this file, hash it into the Hash Index Table, and link it from the record just written.
4F1F
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4F20
RET NZ C0
If the NZ FLAG has been set the secondary directory record could not be created; Register A holds 1EH for a full directory or a Hash Index Table error code. Return it to 4E90H.
4F21
JUMP to 4E4CH to walk the extent chain again from the primary directory record, now that a secondary extended directory record with four empty extent slots has been linked to the file.

4F24H - Free Granule Search and Extent Construction

This section scans the Granule Allocation Table image for a cylinder that still has a free granule, making two passes so that a search which starts part way along the disk wraps around and covers every cylinder. The chosen granule is marked allocated in the table image and folded into the extent slot, whose cylinder, starting granule and granule count fields are built up as each granule is claimed. When no free granule exists anywhere the routine returns error 27, disk space full.

4F24
LD A,06H 3E 06
Load Register A with 06H to select byte 6 of the Drive Code Table entry, which holds the highest cylinder number on the drive.
4F26
PUSH BC C5
Save the count of granules still to be allocated, held in Register Pair BC, onto the stack.
4F27
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4F2A
GOSUB to the resident DCTBYT routine at 479CH, which returns in Register A the Drive Code Table geometry byte selected by Register A for the drive in Register C, here DCT+06H, the highest cylinder number.
4F2D
INC A 3C
Increment Register A so it holds the total number of cylinders on the drive.
4F2E
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4F2F
LD (4F36H),A 32 36 4F
Self-Modifying Code
Store the drive's cylinder count held in Register A into 4F36H, the immediate operand of the CP nn instruction at 4F35H, so the scan below stops at the last cylinder that actually exists on this drive.
4F32
LD B,02H 06 02
Load Register B with 02H, allowing the Granule Allocation Table to be scanned twice so that a search which began part way along the disk can wrap around and cover the cylinders it skipped.
4F34
LD A,L 7D
Loop Start
Copy Register L, the cylinder number currently being examined in the Granule Allocation Table image, into Register A.
4F35
CP 00H FE 00
Self-Modifying Code
Compare the current cylinder number in Register A against the operand at 4F36H, which was set to the drive's cylinder count at 4F2FH. If Register A is below that count the CARRY FLAG is set.
4F37
If the NO CARRY FLAG has been set the scan has run past the last cylinder on the drive, so JUMP to 4F40H to wrap it back to cylinder zero.
4F39
LD A,(HL) 7E
Fetch the allocation byte for the current cylinder from the Granule Allocation Table buffer, addressed by Register Pair HL, into Register A. Each set bit marks an allocated granule, so 0FFH means the cylinder is completely full.
4F3A
INC A 3C
Increment Register A. A completely full cylinder holding 0FFH becomes zero and sets the Z FLAG, while any cylinder with a free granule leaves the NZ FLAG set.
4F3B
If the NZ FLAG has been set this cylinder still has at least one free granule, so JUMP to 4F4DH to claim one of them.
4F3D
INC L 2C
The cylinder is full. Increment Register L to step the search on to the next cylinder's allocation byte in the Granule Allocation Table buffer.
4F3E
LOOP BACK to 4F34H to test the next cylinder for free space.
Loop End
4F40
LD L,00H 2E 00
Reset Register L to zero so the search wraps back to the allocation byte of cylinder 0 at the base of the Granule Allocation Table buffer.
4F42
DECrement Register B and loop back to 4F34H if it has not reached zero, giving the scan its second pass so that every cylinder on the drive is examined once.
4F44
POP BC C1
Both passes found every cylinder full. Restore the count of granules still to be allocated from the stack into Register Pair BC.
4F45
GOSUB to 4F7AH to write the Granule Allocation Table and the current directory record back to disk so that any granules already claimed during this call are not lost.
4F48
RET NZ C0
If the NZ FLAG has been set the Granule Allocation Table or directory write itself failed; return that error code in Register A.
4F49
LD A,1BH 3E 1B
Load Register A with 1BH, that is decimal 27, the LDOS error code for a disk space full condition, since no free granule remains anywhere on the drive.
4F4B
OR A B7
OR Register A with itself. This leaves the value 1BH unchanged and sets the NZ FLAG so the caller treats the return as an error.
4F4C
RET C9
Return to 4E90H with error 27, disk space full, in Register A.
4F4D
LD A,0FFH 3E FF
A cylinder with free space was found. Load Register A with 0FFH, the value that marks an extent slot as unused.
4F4F
LD (DE),A 12
Store 0FFH into the byte addressed by Register Pair DE, initialising the new extent slot's packed starting-granule and count field so that the increments at 4F5AH and 4F6FH build it up from a known state.
4F50
LD C,00H 0E 00
Clear Register C to zero; it counts the granule position being tested on the chosen cylinder.
4F52
LD B,(HL) 46
Fetch the chosen cylinder's allocation byte from the Granule Allocation Table buffer, addressed by Register Pair HL, into Register B. Each set bit marks a granule on that cylinder which is already in use.
4F53
LD A,C 79
Loop Start
Copy the granule position being tested from Register C into Register A.
4F54
GOSUB to 501AH, which plants and executes a BIT n,B instruction for the granule number in Register A, testing that granule's bit in the cylinder allocation byte held in Register B. The Z FLAG comes back set if the granule is free.
4F57
If the Z FLAG has been set this granule is free, so JUMP to 4F60H to claim it for the file.
4F59
LD A,(DE) 1A
The granule is already in use. Fetch the new extent slot's packed starting-granule and count byte, addressed by Register Pair DE, into Register A.
4F5A
ADD A,20H C6 20
Add 20H to Register A. That is one unit in bits 7-5, so this advances the extent's recorded starting granule by one to match the granule position now being tested.
4F5C
LD (DE),A 12
Store the adjusted packed starting-granule and count byte back into the extent slot addressed by Register Pair DE.
4F5D
INC C 0C
Increment Register C to move on to the next granule position on this cylinder.
4F5E
LOOP BACK to 4F53H to test the next granule on the chosen cylinder.
Loop End
4F60
LD A,C 79
A free granule has been found. Copy its position on the cylinder from Register C into Register A.
4F61
GOSUB to 5027H, which plants and executes a SET n,A instruction to build in Register A a mask containing only the bit belonging to that granule.
4F64
OR (HL) B6
OR that single-granule mask in Register A with the cylinder's existing allocation byte, fetched from the Granule Allocation Table buffer at the address in Register Pair HL.
4F65
LD (HL),A 77
Store the updated allocation byte back into the Granule Allocation Table buffer at the address in Register Pair HL, marking this granule as allocated. The buffer is written to disk later by 5052H.
4F66
DEC E 1D
Step Register E, the low byte of the extent-list pointer, back one byte so Register Pair DE addresses the extent slot's cylinder byte.
4F67
LD A,(DE) 1A
Fetch that extent slot's cylinder byte, addressed by Register Pair DE, into Register A.
4F68
INC A 3C
Increment Register A. An unused slot holding 0FFH becomes zero and sets the Z FLAG, while a slot that already carries a cylinder number leaves the NZ FLAG set.
4F69
If the NZ FLAG has been set this extent slot already records a cylinder and is simply being grown, so JUMP to 4F6DH and leave the cylinder byte alone.
4F6B
LD A,L 7D
This is a brand new extent. Copy the chosen cylinder number from Register L, the current offset into the Granule Allocation Table image, into Register A.
4F6C
LD (DE),A 12
Store that cylinder number into the extent slot's cylinder byte, addressed by Register Pair DE, so the extent now records where its granules live.
4F6D
INC E 1C
Advance Register E, the low byte of the extent-list pointer, so Register Pair DE addresses the extent slot's packed starting-granule and count byte again.
4F6E
LD A,(DE) 1A
Fetch that packed starting-granule and count byte, addressed by Register Pair DE, into Register A.
4F6F
INC A 3C
Increment Register A. Bits 4-0 hold the granule count minus one, so this records that the extent now contains one more granule.
4F70
LD (DE),A 12
Store the updated packed starting-granule and count byte back into the extent slot addressed by Register Pair DE.
4F71
POP BC C1
Restore the count of granules still to be allocated from the stack into Register Pair BC.
4F72
DEC BC 0B
Decrement Register Pair BC, since one more granule has now been added to the file.
4F73
PUSH BC C5
Save the reduced count of granules still to be allocated, held in Register Pair BC, back onto the stack.
4F74
LD A,B 78
Copy Register B, the high byte of the outstanding granule count, into Register A.
4F75
OR C B1
OR Register A with Register C, the low byte of the outstanding granule count. The Z FLAG is set only when both halves are zero, that is when the file has been given all the granules it needs.
4F76
If the NZ FLAG has been set the file still needs more granules, so JUMP to 4ED0H to try to extend the last extent again with the next one.
4F79
POP BC C1
Every granule the caller asked for has been allocated. Restore the now exhausted count from the stack into Register Pair BC and fall through into the commit routine at 4F7AH.

4F7AH - Commit the Granule Allocation Table and Directory Record

The common commit path writes the updated Granule Allocation Table image back to sector 0 of the directory cylinder and then rewrites the directory record whose number was planted at 4F82H, so that the granules just claimed and the extent entries that describe them reach the disk together.

4F7A
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4F7D
GOSUB to 5052H to write the updated Granule Allocation Table buffer at 5100H back to sector 0 of the directory cylinder of drive Register C, committing the newly claimed granules to the disk.
4F80
RET NZ C0
If the NZ FLAG has been set the Granule Allocation Table could not be written; return to the caller with error 15H in Register A.
4F81
LD B,00H 06 00
Self-Modifying Code
Load Register B with the operand at 4F82H, which 4E54H set to the number of the directory record currently being processed, so the write below updates the correct record of the file's chain.
4F83
JUMP to the resident DIRWR routine at 4B1FH to write directory record Register B on drive Register C back to the disk, recording the file's newly extended extent list. That routine returns directly to this routine's caller.

4F86H - Create and Link a Secondary Extended Directory Record

When all four extent slots of a directory record are in use, the file is given a further record. A free slot is found in the Hash Index Table and given the same name hash as the primary record, the new record is built as a secondary entry with empty extent slots, and the parent record's link field at DIR+1EH is set to FEH followed by the new record number. A full directory returns error 30.

4F86
LD C,(IX+06H) DD 4E 06
Load Register C with FCB+06H, the number of the drive the file resides on, taken from the File Control Block addressed by Register Pair IX.
4F89
GOSUB to 506BH to read the Hash Index Table sector of drive Register C into the resident sector buffer SBUFF$ at 4200H. That table carries one hash byte per directory record, and a zero byte marks a free directory slot.
4F8C
RET NZ C0
If the NZ FLAG has been set the Hash Index Table could not be read; return to the caller with error 16H in Register A.
4F8D
LD A,(IX+07H) DD 7E 07
Load Register A with FCB+07H, the directory record number of the file's primary directory entry, from the File Control Block addressed by Register Pair IX.
4F90
AND 1FH E6 1F
Keep only bits 4-0 of that record number, giving the file's slot position within a Hash Index Table group, so the search for a free slot starts alongside the file's own entry.
4F92
GOSUB to 4FDEH to search the Hash Index Table for a free directory record, starting from the slot position in Register A. On return the Z FLAG is set and Register Pair HL addresses the free slot, with Register L holding the new directory record number.
4F95
LD A,1EH 3E 1E
Load Register A with 1EH, that is decimal 30, the LDOS error code for a directory that is full and cannot be extended, ready in case the search failed.
4F97
RET NZ C0
If the NZ FLAG has been set no free directory record exists on this drive; return to the caller with error 30 in Register A.
4F98
LD B,L 45
Copy the new directory record number from Register L into Register B, ready for the directory read and write calls below.
4F99
LD A,L 7D
Copy the new directory record number from Register L into Register A as well.
4F9A
LD (4FD9H),A 32 D9 4F
Self-Modifying Code
Store the new directory record number held in Register A into 4FD9H, the immediate operand of the LD (HL),nn instruction at 4FD8H, so the parent record's extended-directory link is later made to point at this new record.
4F9D
LD D,H 54
Copy Register H, the high byte of the address of the free Hash Index Table slot, into Register D.
4F9E
LD E,(IX+07H) DD 5E 07
Load Register E with FCB+07H, the primary directory record number, from the File Control Block addressed by Register Pair IX. Register Pair DE now addresses the primary record's own byte in the Hash Index Table.
4FA1
LD A,(DE) 1A
Fetch the primary directory record's hash byte, addressed by Register Pair DE, into Register A. It is the hash of the file's name and extension.
4FA2
LD (HL),A 77
Store that same hash byte into the free Hash Index Table slot addressed by Register Pair HL, so the new secondary record hashes to the same file name as the primary record it extends.
4FA3
GOSUB to 507DH to write the updated Hash Index Table sector at 4200H back to sector 1 of the directory cylinder, claiming the new directory record.
4FA6
If the Z FLAG has been set the Hash Index Table write succeeded, so GOSUB to the resident DIRRD routine at 4B10H to read directory record Register B on drive Register C, giving Register Pair HL the address of the new record's 32 bytes in the resident buffer.
4FA9
RET NZ C0
If the NZ FLAG has been set either the Hash Index Table write failed with error 17H or the directory read failed; return that error code in Register A to the caller.
4FAA
LD (HL),90H 36 90
Store 90H into the new record's attribute byte at DIR+00H, addressed by Register Pair HL. Bit 7 marks the record as a secondary extended directory entry rather than a primary one, and bit 4 marks the directory slot as in use.
4FAC
INC L 2C
Advance Register L to DIR+01H of the new directory record.
4FAD
PUSH BC C5
Save Register Pair BC, which holds the new directory record number in Register B and the drive number in Register C, onto the stack.
4FAE
LD A,(4F82H) 3A 82 4F
Self-Modifying Code
Fetch the operand at 4F82H into Register A. 4E54H set it to the number of the directory record that this new record extends.
4FB1
LD (HL),A 77
Store that parent record number into DIR+01H of the new record, addressed by Register Pair HL, creating the back link from the secondary record to the record it extends.
4FB2
INC L 2C
Advance Register L to DIR+02H, the first byte of the area that must be cleared.
4FB3
LD B,14H 06 14
Load Register B with 14H, that is twenty, the number of bytes from DIR+02H through DIR+15H that must be cleared in the new record.
4FB5
LD (HL),00H 36 00
Loop Start
Store zero into the byte of the new directory record addressed by Register Pair HL, clearing the date, end-of-file, logical record length, name, extension and password fields which a secondary record does not use.
4FB7
INC L 2C
Advance Register L to the next byte of the new directory record.
4FB8
DECrement Register B and loop back to 4FB5H if it has not reached zero, clearing all twenty bytes from DIR+02H through DIR+15H.
Loop End
4FBA
PUSH HL E5
Save Register Pair HL, which now addresses DIR+16H, the first extent slot of the new record, onto the stack.
4FBB
LD B,0AH 06 0A
Load Register B with 0AH, that is ten, the number of bytes covering the four 2-byte extent slots at DIR+16H through DIR+1DH plus the 2-byte extended-directory link field at DIR+1EH.
4FBD
LD (HL),0FFH 36 FF
Loop Start
Store 0FFH into the byte of the new directory record addressed by Register Pair HL, marking every extent slot and the extended-directory link of the new record as unused.
4FBF
INC L 2C
Advance Register L to the next byte of the new directory record.
4FC0
DECrement Register B and loop back to 4FBDH if it has not reached zero, filling all ten bytes from DIR+16H through DIR+1FH with 0FFH.
Loop End
4FC2
POP DE D1
Recover the address of DIR+16H, saved at 4FBAH, into Register Pair DE.
4FC3
INC DE 13
Advance Register Pair DE to DIR+17H, so it addresses the packed starting-granule and count byte of the new record's first extent slot, which is where the allocator resumes when the chain is re-walked.
4FC4
POP BC C1
Restore Register Pair BC from the stack, giving back the new directory record number in Register B and the drive number in Register C.
4FC5
GOSUB to the resident DIRWR routine at 4B1FH to write the newly built secondary extended directory record Register B out to drive Register C.
4FC8
RET NZ C0
If the NZ FLAG has been set the new directory record could not be written; return that error code in Register A to the caller.
4FC9
LD A,(4F82H) 3A 82 4F
Self-Modifying Code
Fetch the operand at 4F82H into Register A. It holds the number of the parent directory record which must now be linked to the record just created.
4FCC
LD B,A 47
Copy that parent directory record number from Register A into Register B for the directory read.
4FCD
GOSUB to the resident DIRRD routine at 4B10H to read the parent directory record Register B from drive Register C back into the resident buffer, returning its address in Register Pair HL.
4FD0
RET NZ C0
If the NZ FLAG has been set the parent directory record could not be read; return that error code in Register A to the caller.
4FD1
LD A,L 7D
Copy Register L, the low byte of the parent record's address in the resident buffer, into Register A.
4FD2
ADD A,1EH C6 1E
Add 1EH to Register A, the offset of the extended-directory link field within a 32-byte directory record.
4FD4
LD L,A 6F
Move the result back into Register L, so Register Pair HL now addresses DIR+1EH of the parent record.
4FD5
LD (HL),0FEH 36 FE
Store 0FEH into DIR+1EH of the parent record, addressed by Register Pair HL. That is the marker the extent walk at 4E65H recognises as a link to a secondary extended directory record.
4FD7
INC L 2C
Advance Register L to DIR+1FH of the parent record, the byte that carries the linked record number.
4FD8
LD (HL),00H 36 00
Self-Modifying Code
Store the operand at 4FD9H, which 4F9AH set to the number of the newly created secondary directory record, into DIR+1FH of the parent record addressed by Register Pair HL, completing the link between the two records.
4FDA
GOSUB to the resident DIRWR routine at 4B1FH to write the parent directory record Register B back to drive Register C with its new extended-directory link in place.
4FDD
RET C9
Return to 4F1CH with the result of the directory write, the Z FLAG set if the secondary directory record was successfully created and linked.

4FDEH - Hash Index Table Free Record Search

This routine works out how many directory records the drive actually holds from its Drive Code Table geometry, then scans the Hash Index Table image for a byte of zero, which marks a free directory record. The scan starts alongside the file's own slot position and steps through the groups of thirty-two records, wrapping once so that every slot is examined.

4FDE
PUSH AF F5
Save Register A, which holds the slot position within the Hash Index Table group at which the search for a free directory record is to begin, onto the stack.
4FDF
LD A,07H 3E 07
Load Register A with 07H to select byte 7 of the Drive Code Table entry, whose bits 4-0 hold the highest sector number on a track.
4FE1
GOSUB to the resident DCTBYT routine at 479CH, which returns in Register A the Drive Code Table geometry byte selected by Register A for the drive in Register C, here DCT+07H.
4FE4
PUSH DE D5
Save Register Pair DE onto the stack while its halves are used to unpack the geometry byte.
4FE5
LD D,A 57
Copy the whole geometry byte from Register A into Register D so the fields above bit 4 are preserved.
4FE6
AND 1FH E6 1F
Keep only bits 4-0 of Register A, the highest sector number on a track.
4FE8
LD E,A 5F
Move that highest sector number from Register A into Register E.
4FE9
INC E 1C
Increment Register E so it holds the true number of sectors on a track.
4FEA
XOR D AA
Exclusive-OR Register A against Register D, cancelling the sector-count field that both hold and leaving in Register A only the bits above bit 4 of the geometry byte.
4FEB
RLCA 07
Rotate Register A left circularly; three of these instructions together bring that upper field down into the low bits.
4FEC
RLCA 07
Rotate Register A left circularly a second time, continuing to shift the upper field down.
4FED
RLCA 07
Rotate Register A left circularly a third time, so the field now occupies the low bits of Register A.
4FEE
INC A 3C
Increment Register A so it holds the true count rather than that count minus one.
4FEF
GOSUB to the resident MULTEA multiply primitive at 4B6CH to multiply the sector count in Register E by the value in Register A, giving in Register A the number of sectors that make up the directory.
4FF2
GOSUB to 5033H, which doubles Register A when the drive is double sided, so Register A covers both sides of the directory cylinder.
4FF5
POP DE D1
Restore Register Pair DE from the stack.
4FF6
SUB 02H D6 02
Subtract 02H from Register A to discount the Granule Allocation Table in sector 0 and the Hash Index Table in sector 1, leaving the number of sectors that actually hold directory records.
4FF8
LD (5008H),A 32 08 50
Self-Modifying Code
Store that directory sector count held in Register A into 5008H, the immediate operand of the CP nn instruction at 5007H, so the scan below never runs past the last directory record that exists on this drive.
4FFB
POP AF F1
Restore the starting slot position within the Hash Index Table group from the stack into Register A.
4FFC
LD L,A 6F
Move that starting slot position from Register A into Register L, so Register Pair HL addresses the corresponding byte of the Hash Index Table image in the resident buffer at 4200H.
4FFD
GOSUB to 5004H to scan the Hash Index Table from this slot position for a byte of zero, which marks a free directory record.
5000
RET Z C8
If the Z FLAG has been set a free directory record was found and Register Pair HL addresses its Hash Index Table byte; return that slot to the caller at 4F92H.
5001
LD L,3FH 2E 3F
No free record was found from the starting slot onwards. Reset Register L to 3FH so that the increment which follows restarts the scan at slot 00H and covers the slots that were skipped.
5003
INC L 2C
Increment Register L to move the scan on to the next slot position within the Hash Index Table group.
5004
LD A,L 7D
Copy Register L, the current offset into the Hash Index Table image, into Register A.
5005
AND 1FH E6 1F
Keep only bits 4-0 of Register A, giving the slot position within the current group of directory records.
5007
CP 00H FE 00
Self-Modifying Code
Compare the current slot position in Register A against the operand at 5008H, which 4FF8H set to the number of directory sectors on this drive. If Register A is below that count the CARRY FLAG is set.
5009
If the NO CARRY FLAG has been set the scan has reached a slot position that does not exist on this drive, so JUMP to 5018H to report that the directory is full.
500B
LD A,(HL) 7E
Fetch the Hash Index Table byte for this directory record, addressed by Register Pair HL, into Register A. It holds the name hash of the file occupying the record, or zero if the record is free.
500C
OR A B7
OR Register A with itself to test it. The Z FLAG is set only when the byte is zero.
500D
RET Z C8
If the Z FLAG has been set this directory record is free; return to 4FFDH with Register Pair HL addressing its Hash Index Table byte and Register L holding the record number.
500E
LD A,L 7D
The record is in use. Copy Register L, the current offset into the Hash Index Table image, into Register A.
500F
ADD A,20H C6 20
Add 20H to Register A to step to the same slot position in the next group of thirty-two directory records.
5011
LD L,A 6F
Move the stepped offset back into Register L, so Register Pair HL addresses the next group's byte in the Hash Index Table image.
5012
If the NO CARRY FLAG has been set the offset is still within the Hash Index Table sector, so JUMP to 5004H to test the next group.
5014
CP 1FH FE 1F
The offset wrapped past the end of the sector. Compare the wrapped offset in Register A against 1FH, the last slot position in a group. If Register A equals 1FH, the Z FLAG is set.
5016
If the NZ FLAG has been set there are further slot positions still to try, so JUMP to 5003H to advance to the next one and scan its groups.
5018
OR A B7
Every directory record on the drive is occupied. OR Register A with itself to set the NZ FLAG, reporting to 4F92H that no free directory record could be found.
5019
RET C9
Return to 4FFDH or to 4F92H with the NZ FLAG set for a full directory or the Z FLAG set with Register Pair HL addressing the free slot.

501AH - Granule Allocation Bit Test Builder

A granule number from 0 to 7 cannot be used directly by a Z80 bit instruction, so this helper constructs the second opcode byte of a BIT n,B instruction for that granule and plants it in the instruction at 5024H before executing it against a cylinder's allocation byte.

501A
AND 07H E6 07
Keep only bits 2-0 of Register A, which hold the number, from 0 to 7, of the granule on the cylinder whose allocation bit is to be tested.
501C
RLCA 07
Rotate Register A left circularly; three of these instructions together move the granule number up into bits 5-3, which is where the bit selector sits in a BIT n,r opcode.
501D
RLCA 07
Rotate Register A left circularly a second time, continuing to move the granule number into bits 5-3.
501E
RLCA 07
Rotate Register A left circularly a third time, so the granule number now occupies bits 5-3 of Register A.
501F
OR 40H F6 40
OR Register A with 40H, the base opcode byte for BIT 0,B. Register A now holds the second opcode byte of a BIT n,B instruction for the wanted granule number.
5021
LD (5025H),A 32 25 50
Self-Modifying Code
Store the constructed opcode byte held in Register A into 5025H, which is the second byte of the BIT 0,B instruction at 5024H, turning it into a test of the wanted granule's bit.
5024
BIT 0,B CB 40
Self-Modifying Code
Test the bit of Register B selected by the operand planted at 5025H. Register B holds a cylinder's Granule Allocation Table byte, so the Z FLAG comes back set when that granule is free and the NZ FLAG when it is already allocated.
5026
RET C9
Return to the caller with the Z FLAG reporting whether the granule is free.

5027H - Granule Allocation Bit Mask Builder

The companion of the bit test builder constructs the second opcode byte of a SET n,A instruction for a granule number and plants it in the instruction at 5030H, producing in Register A a mask holding only that granule's bit so it can be merged into a cylinder's allocation byte.

5027
RLCA 07
Rotate Register A left circularly; three of these instructions together move the granule number held in Register A up into bits 5-3, which is where the bit selector sits in a SET n,r opcode.
5028
RLCA 07
Rotate Register A left circularly a second time, continuing to move the granule number into bits 5-3.
5029
RLCA 07
Rotate Register A left circularly a third time, so the granule number now occupies bits 5-3 of Register A.
502A
OR 0C7H F6 C7
OR Register A with 0C7H, the base opcode byte for SET 0,A. Register A now holds the second opcode byte of a SET n,A instruction for the wanted granule number.
502C
LD (5031H),A 32 31 50
Self-Modifying Code
Store the constructed opcode byte held in Register A into 5031H, which is the second byte of the SET 0,A instruction at 5030H, turning it into a set of the wanted granule's bit.
502F
XOR A AF
Set Register A to ZERO and clear all flags, so that the instruction below leaves only the one wanted bit set.
5030
SET 0,A CB C7
Self-Modifying Code
Set the bit of Register A selected by the operand planted at 5031H. Register A was cleared at 502FH, so it comes back holding a mask with only the wanted granule's bit set, ready to be merged into a cylinder's Granule Allocation Table byte.
5032
RET C9
Return to the caller with the single-granule allocation mask in Register A.

5033H - Per-Cylinder Granule Count Adjustment

Drive Code Table byte 8 records the number of granules on one track. This helper doubles that figure when Drive Code Table byte 4 shows the drive is double sided, so callers work in granules per cylinder.

5033
LD D,A 57
Save the count held in Register A, which is a number of granules on one track, into Register D while Register A is used to read the drive geometry.
5034
LD A,04H 3E 04
Load Register A with 04H to select byte 4 of the Drive Code Table entry, whose bit 5 records whether the drive is double sided.
5036
GOSUB to the resident DCTBYT routine at 479CH, which returns in Register A the Drive Code Table geometry byte selected by Register A for the drive in Register C, here DCT+04H.
5039
BIT 5,A CB 6F
Test bit 5 of Register A, the double-sided flag of the Drive Code Table entry. The Z FLAG is set when the drive is single sided.
503B
LD A,D 7A
Restore the granule count saved at 5033H from Register D back into Register A.
503C
RET Z C8
If the Z FLAG has been set the drive is single sided, so the per-track count is already the per-cylinder count; return it in Register A unchanged.
503D
ADD A,A 87
The drive is double sided, so a cylinder holds two tracks. Add Register A to itself, doubling the granule count so Register A holds the granules on a whole cylinder.
503E
RET C9
Return to the caller with the per-cylinder granule count in Register A.

503FH - Read the Granule Allocation Table

Sector 0 of the directory cylinder holds the Granule Allocation Table, one byte per cylinder with a set bit for every granule in use. It is read into this overlay's own buffer at 5100H, immediately above the overlay code, so that the resident shared buffer at 4200H remains free for directory records. A failure returns error 20.

503F
PUSH DE D5
Save Register Pair DE onto the stack; the resident sector routines called below use it.
5040
PUSH HL E5
Save Register Pair HL onto the stack; it is holding the caller's working pointer.
5041
GOSUB to the resident DIRCYL routine at 4B65H, which selects drive Register C and positions to the directory cylinder, where the Granule Allocation Table and Hash Index Table live.
5044
LD E,00H 1E 00
Load Register E with 00H, the sector number of the Granule Allocation Table on the directory cylinder.
5046
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H, this overlay's Granule Allocation Table buffer, which lies immediately above the overlay's code and holds one allocation byte per cylinder.
5049
GOSUB to the resident RDSSEC routine at 4B45H to read sector Register E of the directory cylinder into the buffer addressed by Register Pair HL.
504C
POP HL E1
Restore the caller's working pointer from the stack into Register Pair HL.
504D
POP DE D1
Restore Register Pair DE from the stack.
504E
RET Z C8
If the Z FLAG has been set the Granule Allocation Table sector was read successfully; return to the caller with no error.
504F
LD A,14H 3E 14
Load Register A with 14H, that is decimal 20, the LDOS error code for a Granule Allocation Table read error.
5051
RET C9
Return to the caller with the NZ FLAG set and error 20 in Register A.

5052H - Write the Granule Allocation Table

The updated Granule Allocation Table image at 5100H is written back to sector 0 of the directory cylinder with a deleted-data address mark, as every directory-cylinder sector is, and then verified. A failure returns error 21.

5052
PUSH DE D5
Save Register Pair DE onto the stack; the resident sector routines called below use it.
5053
PUSH HL E5
Save Register Pair HL onto the stack; it is holding the caller's working pointer.
5054
GOSUB to the resident DIRCYL routine at 4B65H, which selects drive Register C and positions to the directory cylinder.
5057
LD E,00H 1E 00
Load Register E with 00H, the sector number of the Granule Allocation Table on the directory cylinder.
5059
LD HL,5100H 21 00 51
Load Register Pair HL with 5100H, this overlay's Granule Allocation Table buffer, whose contents were updated at 4F65H as granules were claimed.
505C
GOSUB to the resident WRPROT routine at 4768H to write the buffer addressed by Register Pair HL to sector Register E of the directory cylinder. That routine issues the WD1771 Write Sector command with a deleted-data address mark, which is how every directory-cylinder sector is recorded.
505F
If the NZ FLAG has been set the write failed outright, so JUMP to 5066H to report a Granule Allocation Table write error.
5061
GOSUB to the resident VERSEC routine at 4772H to verify the sector just written by reading its address mark and checking the cyclic redundancy check.
5064
CP 06H FE 06
Compare the verify status in Register A against 06H. If they are equal the Z FLAG is set and the sector verified correctly.
5066
LD A,15H 3E 15
Load Register A with 15H, that is decimal 21, the LDOS error code for a Granule Allocation Table write error, which is returned only when the flags set above report a failure.
5068
POP HL E1
Restore the caller's working pointer from the stack into Register Pair HL.
5069
POP DE D1
Restore Register Pair DE from the stack.
506A
RET C9
Return to the caller, with the Z FLAG set when the Granule Allocation Table was written and verified and the NZ FLAG set with error 21 in Register A when it was not.

506BH - Read the Hash Index Table

Sector 1 of the directory cylinder holds the Hash Index Table, one hash byte per directory record with zero marking a free record. It is read into the resident shared sector buffer SBUFF$ at 4200H. A failure returns error 22.

506B
PUSH BC C5
Save Register Pair BC onto the stack; it holds the directory record number and the drive number.
506C
PUSH DE D5
Save Register Pair DE onto the stack; the resident sector routines called below use it.
506D
GOSUB to the resident DIRCYL routine at 4B65H, which selects drive Register C and positions to the directory cylinder.
5070
LD E,01H 1E 01
Load Register E with 01H, the sector number of the Hash Index Table on the directory cylinder.
5072
LD HL,4200H 21 00 42
Load Register Pair HL with 4200H, the resident shared sector buffer SBUFF$, which is where the Hash Index Table is held while it is searched and updated.
5075
GOSUB to the resident RDSSEC routine at 4B45H to read sector Register E of the directory cylinder into the buffer addressed by Register Pair HL.
5078
POP DE D1
Restore Register Pair DE from the stack.
5079
POP BC C1
Restore Register Pair BC from the stack, giving back the directory record number and the drive number.
507A
LD A,16H 3E 16
Load Register A with 16H, that is decimal 22, the LDOS error code for a Hash Index Table read error, which is returned only when the read left the NZ FLAG set.
507C
RET C9
Return to the caller, with the Z FLAG set when the Hash Index Table was read and the NZ FLAG set with error 22 in Register A when it was not.

507DH - Write the Hash Index Table

The updated Hash Index Table image at 4200H is written back to sector 1 of the directory cylinder with a deleted-data address mark and then verified, claiming the directory record taken for a secondary extended entry. A failure returns error 23.

507D
PUSH BC C5
Save Register Pair BC onto the stack; it holds the new directory record number and the drive number.
507E
PUSH DE D5
Save Register Pair DE onto the stack; the resident sector routines called below use it.
507F
GOSUB to the resident DIRCYL routine at 4B65H, which selects drive Register C and positions to the directory cylinder.
5082
LD E,01H 1E 01
Load Register E with 01H, the sector number of the Hash Index Table on the directory cylinder.
5084
LD HL,4200H 21 00 42
Load Register Pair HL with 4200H, the resident shared sector buffer SBUFF$, which holds the Hash Index Table image updated at 4FA2H with the new record's hash byte.
5087
GOSUB to the resident WRPROT routine at 4768H to write the buffer addressed by Register Pair HL to sector Register E of the directory cylinder using a deleted-data address mark.
508A
If the NZ FLAG has been set the write failed outright, so JUMP to 5091H to report a Hash Index Table write error.
508C
GOSUB to the resident VERSEC routine at 4772H to verify the sector just written by reading its address mark and checking the cyclic redundancy check.
508F
CP 06H FE 06
Compare the verify status in Register A against 06H. If they are equal the Z FLAG is set and the sector verified correctly.
5091
LD A,17H 3E 17
Load Register A with 17H, that is decimal 23, the LDOS error code for a Hash Index Table write error, which is returned only when the flags set above report a failure.
5093
POP DE D1
Restore Register Pair DE from the stack.
5094
POP BC C1
Restore Register Pair BC from the stack, giving back the directory record number and the drive number.
5095
RET C9
Return to 4FA3H, with the Z FLAG set when the Hash Index Table was written and verified and the NZ FLAG set with error 23 in Register A when it was not.