CoCo Disk BASIC disk structure – part 4

I did a thing.

I wrote a BASIC program which will create 68 files named “0.TXT” to “67.TXT”. Each file is 2304 bytes so it takes up a full granule. (That is not really important. It just helps makes things obvious if you look at the disk with a hex editor and want to know which file is at which sector.)

After making these files, it uses code from some of my other examples to scan through the directory and display it (FILEGRAN.BAS code, shown later in this post) and then it scans the directory and prints which granule each 1-gran file is using.

I can start with a freshly formatted disk then run this program and see where RS-DOS put each file.

Will it match the order RS-DOS used when making one huge file that takes up all 68 grans? Let’s find out…

10 '68FILES.BAS
20 PRINT "RUN THIS ON A BLANK DISK."
30 INPUT "DRIVE #";DR
40 'SWITCH TO THAT DRIVE
50 DRIVE DR
60 'GOTO 140
70 'MAKE FILES 0-67
80 FOR G=0 TO 67
90 F$=MID$(STR$(G),2)+".TXT"
100 PRINT "MAKING ";F$;
110 OPEN "O",#1,F$
120 CLOSE #1:PRINT
130 NEXT
140 'FILEGRAN.BAS
150 'DIR WITHOUT FILE SIZES
160 CLEAR 512:DIM SP$(1)
170 ' S - SECTOR NUMBER
180 FOR S=3 TO 11
190 ' SP$(0-1) - SECTOR PARTS
200 DSKI$ DR,17,S,SP$(0),SP$(1)
210 ' P - PART OF SECTOR
220 FOR P=0 TO 1
230 ' E - DIR ENTRY (4 P/SECT.)
240 FOR E=0 TO 3
250 ' GET 32 BYTE DIR ENTRY
260 DE$=MID$(SP$(P),1+E*32,32)
270 ' FB - FIRST BYTE OF NAME
280 FB=ASC(LEFT$(DE$,1))
290 ' SKIP DELETED FILES
300 IF FB=0 THEN 440
310 ' WHEN 255, DIR IS DONE
320 IF FB=255 THEN 470
330 ' PRINT NAME AND EXT.
340 'PRINT LEFT$(DE$,8);TAB(9);MID$(DE$,9,3);
350 ' FIRST TWO CHARS ONLY
360 PRINT LEFT$(DE$,2);"-";
361 'PRINT #-2,LEFT$(DE$,2);",";
370 ' FILE TYPE
380 'PRINT TAB(13);ASC(MID$(DE$,12,1));
390 ' BINARY OR ASCII
400 'IF ASC(MID$(DE$,13,1))=0 THEN PRINT "B"; ELSE PRINT "A";
410 ' STARTING GRANULE
420 PRINT USING("## ");ASC(MID$(DE$,14,1));
421 'PRINT #-2,ASC(MID$(DE$,14,1))
430 CL=CL+1:IF CL=5 THEN CL=0:PRINT
440 NEXT
450 NEXT
460 NEXT
470 END

I modified this program to output to the printer (PRINT #-2) and then capture that output in the Xroar emulator in a text file. That gave me data which I put in a spreadsheet.

 68 Files
FILE GRAN
0 32
1 33
2 34
3 35
4 30
5 31
6 36
7 37
8 28
9 29
10 38
11 39
12 26
13 27
14 40
15 41
16 24
17 25
18 42
19 43
20 22
21 23
22 44
23 45
24 20
25 21
26 46
27 47
28 18
29 19
30 48
31 49
32 16
33 17
34 50
35 51
36 14
37 15
38 52
39 53
40 12
41 13
42 54
43 55
44 10
45 11
46 56
47 57
48 8
49 9
50 58
51 59
52 6
53 7
54 60
55 61
56 4
57 5
58 62
59 63
60 2
61 3
62 64
63 65
64 0
65 1
66 66
67 67

Next, I used a second program on a freshly formatted disk to create one big file fully filling up the disk. (The very last PRINT to the file will create a ?DF ERROR, which I now think is a bug. It should not do that until I try to write the next byte, I think.)

10 '1BIGFILE.BAS
20 PRINT"RUN THIS ON A BLANK DISK."
30 INPUT "DRIVE #";DR
40 'SWITCH TO THAT DRIVE
50 DRIVE DR
60 'MAKE ONE BIG 68 GRAN FILE
70 OPEN "O",#1,"1BIGFILE.TXT"
80 FOR G=0 TO 67
90 PRINT G;
100 T$=STRING$(128,G)
110 FOR T=1 TO 18
120 PRINT ".";
130 PRINT #1,T$;
140 NEXT
150 PRINT
160 NEXT
170 CLOSE #1
180 END

I ran another test program which would read the directory, then print out the granule chain of each file on the disk.

10 ' FILEGRAN.BAS
20 '
30 ' 0.0 2025-11-20 BASED ON FILEINFO.BAS
40 '
50 ' E$(0-1) - SECTOR HALVES
60 ' FT$ - FILE TYPE STRINGS
70 '
80 CLEAR 1500:DIM E$(1),FT$(3)
90 FT$(0)="BPRG":FT$(1)="BDAT":FT$(2)="M/L ":FT$(3)="TEXT "
100 '
110 ' DIR HOLDS UP TO 72 ENTRIES
120 '
130 ' NM$ - NAME
140 ' EX$ - EXTENSION
150 ' FT - FILE TYPE (0-3)
160 ' AF - ASCII FLAG (0/255)
170 ' FG - FIRST GRANULE #
180 ' BU - BYTES USED IN LAST SECTOR
190 ' SZ - FILE SIZE
200 ' GM - GRANULE MAP
210 '
220 DIM NM$(71),EX$(71),FT(71),AF(71),FG(71),BU(71),SZ(71),GM(67)
230 '
240 INPUT "DRIVE";DR
250 '
260 ' FILE ALLOCATION TABLE
270 ' 68 GRANULE ENTRIES
280 '
290 DIM FA(67)
300 DSKI$ DR,17,2,G$,Z$:Z$=""
310 FOR G=0 TO 67
320 FA(G)=ASC(MID$(G$,G+1,1))
330 NEXT
340 '
350 ' READ DIRECTORY
360 '
370 DE=0
380 FOR S=3 TO 11
390 DSKI$ DR,17,S,E$(0),E$(1)
400 '
410 ' PART OF SECTOR
420 '
430 FOR P=0 TO 1
440 '
450 ' ENTRY WITHIN SECTOR PART
460 '
470 FOR E=0 TO 3
480 '
490 ' DIR ENTRY IS 32 BYTES
500 '
510 E$=MID$(E$(P),E*32+1,32)
520 '
530 ' NAME IS FIRST 8 BYTES
540 '
550 NM$(DE)=LEFT$(E$,8)
560 '
570 ' EXTENSION IS BYTES 9-11
580 '
590 EX$(DE)=MID$(E$,9,3)
600 '
610 ' FILE TYPE IS BYTE 12
620 '
630 FT(DE)=ASC(MID$(E$,12,1))
640 '
650 ' ASCII FLAG IS BYTE 13
660 '
670 AF(DE)=ASC(MID$(E$,13,1))
680 '
690 ' FIRST GRANUAL IS BYTE 14
700 '
710 FG(DE)=ASC(MID$(E$,14,1))
720 '
730 ' BYTES USED IN LAST SECTOR
740 ' ARE IN BYTES 15-16
750 '
760 BU(DE)=ASC(MID$(E$,15,1))*256+ASC(MID$(E$,16,1))
770 '
780 ' IF FIRST BYTE IS 255, END
790 ' OF USED DIR ENTRIES
800 '
810 IF LEFT$(NM$(DE),1)=CHR$(255) THEN 1500
820 '
830 ' IF FIRST BYTE IS 0, FILE
840 ' WAS DELETED
850 '
860 IF LEFT$(NM$(DE),1)=CHR$(0) THEN 1480
870 '
880 ' SHOW DIRECTORY ENTRY
890 '
900 PRINT NM$(DE);TAB(9);EX$(DE);" ";FT$(FT(DE));" ";
910 IF AF(DE)=0 THEN PRINT"BIN"; ELSE PRINT "ASC";
920 '
930 ' CALCULATE FILE SIZE
940 ' SZ - TEMP SIZE
950 ' GN - TEMP GRANULE NUM
960 ' SG - SECTORS IN LAST GRAN
970 ' GC - GRANULE COUNT
980 '
990 SZ=0:GN=FG(DE):SG=0:GC=0
1000 '
1010 ' GET GRANULE VALUE
1020 ' GV - GRAN VALUE
1030 '
1040 GV=FA(GN):GM(GC)=GN:GC=GC+1
1050 '
1060 ' IF TOP TWO BITS SET (C0
1070 ' OR GREATER), IT IS THE
1080 ' LAST GRANULE OF THE FILE
1090 ' SG - SECTORS IN GRANULE
1100 '
1110 IF GV>=&HC0 THEN SG=(GV AND &H1F):GOTO 1280
1120 '
1130 ' IF NOT, MORE GRANS
1140 ' ADD GRANULE SIZE
1150 '
1160 SZ=SZ+2304
1170 '
1180 ' MOVE ON TO NEXT GRANULE
1190 '
1200 GN=GV
1210 GOTO 1040
1220 '
1230 ' DONE WITH GRANS
1240 ' CALCULATE SIZE
1250 '
1260 ' FOR EMPTY FILES
1270 '
1280 IF SG>0 THEN SG=SG-1
1290 '
1300 ' FILE SIZE IS SZ PLUS
1310 ' 256 BYTES PER SECTOR
1320 ' IN LAST GRAN PLUS
1330 ' NUM BYTES IN LAST SECT
1340 '
1350 SZ(DE)=SZ+(SG*256)+BU(DE)
1360 PRINT " ";SZ(DE)
1370 '
1380 ' SHOW GRANULE MAP
1390 '
1400 C=0:PRINT " ";
1410 FOR I=0 TO GC-1
1420 PRINT USING"##";GM(I);
1430 C=C+1:IF C=10 THEN PRINT:PRINT " ";:C=0 ELSE PRINT " ";
1440 NEXT:PRINT
1450 '
1460 ' INCREMENT DIR ENTRY
1470 '
1480 DE=DE+1
1490 NEXT:NEXT:NEXT
1500 END
1510 ' SUBETHASOFTWARE.COM

Since there is only one big file on this disk, fully filling it, it only has one 68-entry granule chain to print. I modified the code to PRINT#-2 these values to the virtual printer so I could then copy the numbers into the same spreadsheet:

 68 Files  Big File
FILE GRAN GRAN
0 32 32
1 33 33
2 34 34
3 35 35
4 30 36
5 31 37
6 36 38
7 37 39
8 28 40
9 29 41
10 38 42
11 39 43
12 26 44
13 27 45
14 40 46
15 41 47
16 24 48
17 25 49
18 42 50
19 43 51
20 22 52
21 23 53
22 44 54
23 45 55
24 20 56
25 21 57
26 46 58
27 47 59
28 18 60
29 19 61
30 48 62
31 49 63
32 16 64
33 17 65
34 50 66
35 51 67
36 14 30
37 15 31
38 52 28
39 53 29
40 12 26
41 13 27
42 54 24
43 55 25
44 10 22
45 11 23
46 56 20
47 57 21
48 8 18
49 9 19
50 58 16
51 59 17
52 6 14
53 7 15
54 60 12
55 61 13
56 4 10
57 5 11
58 62 8
59 63 9
60 2 6
61 3 7
62 64 4
63 65 5
64 0 2
65 1 3
66 66 0
67 67 1

Now it seems clearly obvious that RS-DOS does something different when making a new file, versus what it does when expanding an existing file into a new granule.

I wanted a way to visualize this so, of course, I wrote a program to help me create a full ASCII representation of the granules, then edited the rest by hand.

                           68    1 Big
Files File
Track 0 +------------+
| Granule 0 | 64 66
| Granule 1 | 65 67
Track 1 +------------+
| Granule 2 | 60 64
| Granule 3 | 61 65
Track 2 +------------+
| Granule 4 | 56 62
| Granule 5 | 57 63
Track 3 +------------+
| Granule 6 | 52 60
| Granule 7 | 53 61
Track 4 +------------+
| Granule 8 | 48 58
| Granule 9 | 49 59
Track 5 +------------+
| Granule 10 | 44 56
| Granule 11 | 45 57
Track 6 +------------+
| Granule 12 | 40 54
| Granule 13 | 41 55
Track 7 +------------+
| Granule 14 | 36 52
| Granule 15 | 37 53
Track 8 +------------+
| Granule 16 | 32 50
| Granule 17 | 33 51
Track 9 +------------+
| Granule 18 | 28 48
| Granule 19 | 29 49
Track 10 +------------+
| Granule 20 | 24 46
| Granule 21 | 25 47
Track 11 +------------+
| Granule 22 | 20 44
| Granule 23 | 21 45
Track 12 +------------+
| Granule 24 | 16 42
| Granule 25 | 17 43
Track 13 +------------+
| Granule 26 | 12 40
| Granule 27 | 13 41
Track 14 +------------+
| Granule 28 | 8 38
| Granule 29 | 9 39
Track 15 +------------+
| Granule 30 | 4 36
| Granule 31 | 5 37
Track 16 +------------+
| Granule 32 | 0 0 <- both start the same
| Granule 33 | 1 1
Track 17 +------------+
| FAT & |
| Directory |
Track 18 +------------+
| Granule 34 | 2 2
| Granule 35 | 3 3
Track 19 +------------+
| Granule 36 | 6 4 <- then big file continues
| Granule 37 | 7 5 writing to the end
Track 20 +------------+
| Granule 38 | 10 6
| Granule 39 | 11 7
Track 21 +------------+
| Granule 40 | 14 8
| Granule 41 | 15 9
Track 22 +------------+
| Granule 42 | 18 10
| Granule 43 | 19 11
Track 23 +------------+
| Granule 44 | 22 12
| Granule 45 | 23 13
Track 24 +------------+
| Granule 46 | 26 14
| Granule 47 | 27 15
Track 25 +------------+
| Granule 48 | 30 16
| Granule 49 | 31 17
Track 26 +------------+
| Granule 50 | 34 18
| Granule 51 | 35 19
Track 27 +------------+
| Granule 52 | 38 20
| Granule 53 | 39 21
Track 28 +------------+
| Granule 54 | 42 22
| Granule 55 | 43 23
Track 29 +------------+
| Granule 56 | 46 24
| Granule 57 | 47 25
Track 30 +------------+
| Granule 58 | 50 26
| Granule 59 | 51 27
Track 31 +------------+
| Granule 60 | 54 28
| Granule 61 | 55 29
Track 32 +------------+
| Granule 62 | 58 30
| Granule 63 | 59 31
Track 33 +------------+
| Granule 64 | 62 32
| Granule 65 | 63 33
Track 34 +------------+
| Granule 66 | 66 34
| Granule 67 | 67 35 <- then big file continues
+------------+ at Track 16

Interesting! For small files, it alternates tracks starting before Track 17 (FAT/Directory) then after, repeating. For a big file, it starts like that before Track 17, then after and continues to the end of Track 35, then goes before Track 17 and works back to the start of the disk.

Do I understand the sequence correctly?

To be continued…

One thought on “CoCo Disk BASIC disk structure – part 4

  1. William Astle

    Extending a file makes sense to keep the tracks sequential (either increasing or decreasing) since they’re likely to be read sequentially. New files make sense to allocate close to the directory track since the first granule data is faster to access after opening the file.

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.