I know I must have learned at least some basic stuff about the layout of an RS-DOS disk, because I had a directory searching routine in my first commercial CoCo program – Huffman K1 Librarian sold by Rulaford Research.
That product was a MIDI program that would load or save sound patches (synthesizer voices) to and from a Kawai K1 synthesizer. For functions where you were going to send a patch, it would allow showing all directory of the files of that type. The extension was used to determine if it was a single patch or a block. Though today, I cannot remember the details on what a “block” was for the K1.
Looking at that program now, it would have been nice if I had allowed the user to just cursor around the files and select one, rather than having the user type the name in. Maybe I’ll fix that in a version 1.3 someday … though I sold my K1 long ago, as well as all my CoCo MIDI gear, so I wouldn’t have any way to actually test the update. So maybe I won’t.
Anatomy of an RS-DOS disk
Back in those days, we’d refer to Disk Extended Color BASIC (DECB) as “RS-DOS”. I’m not sure why “Radio Shack DOS” was used for a name, since I don’t recall it saying this anywhere on the screen or in the manuals, but someone must have come up with it and it caught on. (Much like the nickname “CoCo”.)
RS-DOS had a simple file system that was described in detail in the back of the Disk BASIC manual. Back then, most of this was probably beyond me, since looking at it today it still is. It’s interesting that it described the technical details of the disk beyond just the tracks and sector data that could actually be used from BASIC — at least in the 1981 TRS-80 Color Computer Disk System Owners Manual & Programming Guide.
I also found it interesting that by the 1986 version of the manual, which was the version available after the CoCo 3 was released, this technical information had been removed.
Above, out of those 338 bytes, the only section we got to use was the 256 data bytes. The rest was used by the FD1773 floppy drive controller chip.
Looking back at the 1981 manual, the format of an RS-DOS disk was pretty clearly defined. The disk drive was a single-sided 35 track device, and each of those tracks contain 18 sectors. Each sector was 256 bytes. Each track of 18 256-byte sectors could hold 4608 bytes. This meant that a disk could hold 161,280 bytes of data! (35 * 18 * 256) Wow, 157K of storage!
Side Note: Although Radio Shack never updated the Disk BASIC ROM to take advantage of it, the floppy controller was capable of supporting up to three double-sided 80 track (720K) floppy drives. Others came up with patches (or replacement DOS ROMs) that let BASIC handle this. This was also supported in disk operating systems such as OS-9 (which was sold by Radio Shack) and FLEX. But, we’re sticking with generic Disk Extended Color BASIC for this article series, so 35 tracks it is…
While 157K sounds pretty amazing, we didn’t actually get to use all of that from BASIC. Track 17 was used to store the directory and file allocation table (FAT). Yep, even back then, Microsoft (who wrote this Disk BASIC) already had a FAT file system… Just not the FAT we became familiar with via PC-DOS/MS-DOS a few years later.
Since track 17 could not be used for data storage, that left us with 34 tracks we could use — 156,672 bytes. Oh well, 153K of high speed disk access sure beats cassette storage. Or, as we learned, “68 granules” of high speed disk access.
Side Note: Tracks are numbers from 0 to 34, so track 17 is actually the 18th track. Sectors, however, are numbers 1 to 18. Go figure. Drives were numbers 0 to 3, so sectors are really the odd one here.
Here is a representation of the 35 tracks on a disk (numbers 0 to 34):
+----------+ | Track 00 | - 4608 bytes +----------+ | Track 01 | - 4608 bytes +----------+ | ... | | ... | +----------+ | Track 17 | - File Allocation Table and Directory +----------+ | ... | | ... | +----------+ | Track 33 | - 4608 bytes +----------+ | Track 34 | - 4608 bytes +----------+
And here is a representation of the 256-byte sectors inside a track (numbered 1 to 18):
Track X +----+----+----+-----+----+----+----+ | 01 | 02 | 03 | ... | 06 | 07 | 18 | - 256 bytes each +----+----+----+-----+----+----+----+
gran·ule /ˈɡranˌyo͞ol/ noun– https://www.merriam-webster.com/dictionary/granule
a small compact particle of a substance.
Each of the available 34 tracks was split in half (each half being 9 256-byte sectors) and called granules. Each granule was, therefore, 9 * 256 bytes in size — 2304 bytes.
To see how much space was free on disk, we could type:
…and that would print a number, such as 68 for a completely empty disk, or 0 for a full one. Granules never made much sense to me back then, and I don’t suppose they really do today except that it was “half a track.” I don’t know if that would have meant much to me before I got in to OS-9 years later and learned way more about floppy disks.
Track 17, where the directory and FAT were stored, was two more granules of storage we didn’t get to use.
Here is a representation of the granules of a disk, numbers 0 to 68:
Track 00 +------------+ | Granule 00 | - Sectors 1-9 (2304 bytes) | Granule 01 | - Sectors 10-18 (2304 bytes) Track 01 +------------+ | Granule 02 | - Sectors 1-9 (2304 bytes) | Granule 03 | - Sectors 10-18 (2304 bytes) Track xx +------------+ | ... | Track 16 +------------+ | Granule 33 | - Sectors 1-9 (2304 bytes) | Granule 34 | - Sectors 10-18 (2304 bytes) Track 17 +------------+ | FAT & | - 4608 bytes | Directory | Track 18 +------------+ | Granule 35 | - Sectors 1-9 (2304 bytes) | Granule 36 | - Sectors 10-18 (2304 bytes) Track xx +------------+ | ... | Track 34 +------------+ | Granule 67 | - Sectors 1-9 (2304 bytes) | Granule 68 | - Sectors 10-18 (2304 bytes) +------------+
Here is a simple program that displays this information on the CoCo 32-column screen. Use UP/DOWN arrows to go track by track, or SHIFT-UP/SHIFT-DOWN to go a page at a time:
10 'DISKMAP.BAS 20 ' 30 ' TRACKS & SECTORS 40 ' 50 CLS 60 PRINT"TRACK SECTORS" 70 PRINT" (1-9) (10-18)" 80 PRINT STRING$(27,"-"); 90 ST=0:PG=12 100 FOR A=0 TO PG 110 TR=ST+A 120 PRINT@96+32*A,TR; 130 IF TR=17 THEN PRINT TAB(10);"FAT & DIRECTORY ";:GOTO 160 140 IF TR<17 THEN GR=(TR*2)+1 ELSE GR=((TR-1)*2)+1 150 PRINT TAB(9);"GRAN";GR;TAB(19);"GRAN";GR+1; 160 NEXT 170 A$=INKEY$:IF A$="" THEN 170 180 IF A$=CHR$(94) THEN ST=ST-1 190 IF A$=CHR$(95) THEN ST=ST-PG-1 200 IF A$=CHR$(10) THEN ST=ST+1 210 IF A$=CHR$(91) THEN ST=ST+PG+1 220 IF ST<0 THEN ST=0 230 IF ST>34-PG THEN ST=34-PG 240 GOTO 100
If you wanted to use all of an RS-DOS disk, a CoCo program could use disk access commands to read/write sectors to any spot on the disk — including track 17 — and manually use all 70 granules for storage. But, if it did that, typing “DIR” would not produce expected results (they would be no directory) and trying to SAVE something on this disk would overwrite data (if it worked at all; it would have needed valid directory information to even do this).
But I digress…
Of the 18 sectors contained in track 17, sectors 1 and 12-18 were “for future use.” Radio Shack never used them, as far as I know, but third party patches to Disk BASIC did use them for other features, such as supporting 40 track drives.
- Sector 1 – Unused (“for future use”)
- Sector 2 – File Allocation Table (FAT)
- Sectors 3-11 – Directory Entries
- Sectors 13-18 – Unused (“for future use”)
FAT (File Allocation Table)
The first 68 bytes of Sector 2 contained the file allocation table. Each byte represented the status of one of the available granules on the disk. If the granule was not used by any file, the byte representing it would be set to 255 (&HFF). I expect that the FREE() command simply read Track 17, Sector 2, and quickly scanned the first 68 bytes, counting how many were 255.
DSKI$ / DSKO$
Let’s do a quick tangent here. Disk BASIC provided two commands for reading and writing sectors on the disk. DSKI (disk input) and DSKO (disk output) needed a drive number (0-3), track number (0-34), and a sector number (1-18) to read or write from/to. Since a Color BASIC string variable could not be longer than 255 (the maximum size a byte could represent for length), a string could not hold an entire sector. Because of this, DSKI and DSKO split the sector up in to two 128-byte strings like this:
CLERA 256 DSKI$ 0,17,2,A$,B$
Above, the CLEAR 256 is needed to increase string space from the default 200 bytes to enough to store the full sector in A$ and B$ and two 128 byte strings. Keep in mind, more memory will be needed when you do any manipulation on either of those strings. As you will see below, CLEAR 384 is really needed at the very least, since if you do a MID$() or LEFT$() on A$ or B$, enough string memory has to be available to hold a copy of that string (256+128 is 384). See my string abuse article for a deep dive in to why that is the case.
For DSKI$, the first parameter is the drive (0-3), the second is the track (0-34) and the third is the sector (1-18). After that are two string variables that will hold the two halves of the 256-byte sector. In this example, A$ holds the fist 128 bytes, and B$ holds the second 128 bytes.
I only wanted to mention this so I could show a BASIC program that calculates how much space is free on a disk by reading the FAT bytes. It might look something like this:
0 'DISKFREE.BAS 10 CLEAR 384 20 INPUT "DRIVE";DR 30 DSKI$ DR,17,2,A$,B$ 40 FOR I=1 TO 68 50 IF MID$(A$,I,1)=CHR$(255) THEN FG=FG+1 60 NEXT 70 PRINT "FREE GRANULES:";FG
This could, of course, be made smaller and faster. And, if you wanted to show the free space in bytes, you could just multiply the free granules (FG) variable by 2304, the side of a granule:
70 PRINT "FREE SPACE:";FG*2304;"BYTES"
Of course, the FREE(0) command could also have been used for this, even getting the value in a variable:
10 PRINT "FREE GRANULES:";FREE(0) 10 PRINT "FREE SPACE:";FREE(0)*2304;"BYTES" 10 FG=FREE(0):PRINT "FREE GRANULES:";FG 10 FS=FREE(0)*2304:PRINT "FREE SPACE:";FS;"BYTES"
But I digress.
But what if the granule is being used by a file? If you wanted to see the values in the non-free granules used on the disk, you could modify the program as follows:
0 'DISKFREE.BAS 10 CLEAR 384 20 INPUT "DRIVE";DR 30 DSKI$ DR,17,2,A$,B$ 40 FOR I=1 TO 68 50 GN=ASC(MID$(A$,I,1)) 55 IF GN=255 THEN FG=FG+1 ELSE PRINT GN; 60 NEXT:PRINT 70 PRINT "FREE GRANULES:";FG
If you run that, you will probably see values that are outside of the range of a granule number (0-67). This will be explained later when we discuss the FAT in more detail.
Track 17 sectors 3-11 are used for the directory. The 1981 Color Computer Disk System manual described the directory layout on page 58 as follows:
Using DSKI$, we can write a program that will display the names of files in the directory. We can do this by reading each sector and then parsing the 32-byte directory entries.
10 CLEAR 512:DIM SP$(1) 20 INPUT "DRIVE";DR 30 ' S - SECTOR NUMBER 40 FOR S=3 TO 11 50 ' SP$(0-1) - SECTOR PARTS 60 DSKI$ DR,17,S,SP$(0),SP$(1) 70 ' P - PART OF SECTOR 80 FOR P=0 TO 1 90 ' E - DIR ENTRY (4 P/SECT.) 100 FOR E=0 TO 3 110 ' GET 32 BYTE DIR ENTRY 120 DE$=MID$(SP$(P),1+E*32,32) 130 ' FB - FIRST BYTE OF NAME 140 FB=ASC(LEFT$(DE$,1)) 150 ' SKIP DELETED FILES 160 IF FB=0 THEN 250 170 ' WHEN 255, DIR IS DONE 180 IF FB=255 THEN END 190 ' PRINT NAME AND EXT. 200 PRINT LEFT$(DE$,8);TAB(9);MID$(DE$,9,3); 210 ' FILE TYPE 220 PRINT TAB(13);ASC(MID$(DE$,12,1)); 230 ' BINARY OR ASCII 240 IF ASC(MID$(DE$,13,1))=0 THEN PRINT "B" ELSE PRINT "A" 250 NEXT 260 NEXT 270 NEXT
Running this program will produce output similar to the DIR command, except without the file size. Calculating the file size is more involved, and requires scanning through the FAT to find the series of granules that are allocated for the file. We’ll get to that in the next part of this series.
Bonus: File Exists?
Here’s some useless code. This routine will determine if a file exists. Rather than parse each 32 byte portion of the sectors, I decided to use INSTR() to see if the target filename string exists anywhere in the sector strings. To make sure “EMP” didn’t show up as a match for a file named “EMPTY”, I pad the target string with spaces just like they are stored in the directory.
10 INPUT "FILE NAME";F$ 20 ' PAD NAME WITH SPACES 30 F$=F$+STRING$(8-LEN(F$)," ") 40 FOR S=3 TO 17 50 DSKI$ 0,17,S,A$,B$ 60 IF INSTR(A$,F$) OR INSTR(B$,F$) THEN PRINT "FOUND":END 70 NEXT
This could be improved by having the process stop as soon as it finds an entry starting with 255 (no more directories after that point). To keep the code small, a simple “55 IF ASC(LEFT$(A$,1))=255 THEN END” would be enough. It might still read a sector more than it needs to, since it’s not checking every entry in the string, but that would be a way to do it with minimal code.
We’ll do something less useless in part 2.