Acorn File Server Filesystem Structure ====================================== http://mdfs.net/Docs/Comp/Disk/Format/AFS0 Acorn file servers use a filesystem with an "AFS0" identifier, and so are sometimes called "AFS0 filesystems" or "AFS0 disk format". AFS0 File Server disks use 256-byte logical disk sectors. Logical sectors are counted with 24-bit numbers starting from &000000 at the start of the filesystem, so the largest possible filesystem is 4G. The filesystem uses 24-bit file lengths, so the largest possible file is 16M-1. The Level 2 File Server uses a 12-bit allocation map, so the maximum Level2FS filesystem size is 1024K. The underlying ADFS API used by Level 3 File Server code to access disks uses the top three bits of the sector address to specify the drive, leaving 21 bits to specify the logical sector, so in practice the largest possible Level 3 filesystem is 512M. Floppy disks are formatted to 2 sides, 80 tracks, 256-byte sectors. Single density disks have 10 sectors (numbered 0-9) giving 400K of storage, and double density disks have 16 sectors (numbered 0-15) giving 640K of storage. Tracks are sequential, that is all the way up side 0, and then all the way up side 1, so: single density logical_sector=sector+(side*80+track)*10 double density logical_sector=sector+(side*80+track)*16 This can also be written as: single density logical_sector=sector+track*10+side*800 double density logical_sector=sector+track*16+side*1280 It is reported that FileStore disks are interleaved, so: double density logical_sector=sector+track*32+side*16 The layout of the disk and whether it is a Level 2 or Level 3 filesystem can be found by reading the Disk Information Block and following it to find the root directory. Logical sector zero is zero bytes from the start of the physical disk (ie, sector &000000). Hard drives have 256-byte sectors. Logical sector zero is zero bytes from the start of the physical disk (ie sector &000000). The Acorn Level 2 File Server uses single-density disks, although the DSCMGR program has code to initialise 16-sector disks. The Acorn Level 3 File Server and the Acorn FileStore use double density disks. Level3FS disks usually coexist with an ADFS filesystem on the same disk. Part of the ADFS free space map points to the start of the area of the disk used by the Level3FS filesystem. All logical sector numbers within the Level3FS filesystem are offset from the start of the physical disk, not the start of the disk partition. Effectively, the ADFS partition forms a hidden area within the Level3FS filesystem. While the filesystem layout refers to cylinders (ie tracks), none of this is relevant as the disk access APIs always take a logical sector number. A physical disk can have a Level3FS filesystem initialised on it with a different claimed number of cylinders that it actually has. The cylinders number is only used for splitting the partition up into logical tracks with a sector free space map for each logical track. Later versions of the FileStore and the Level 3 File Server take advantage of this to optimise the layout of the allocation maps. All objects on the disk are refered to by their System Internal Name (SIN). On Level2FS disks the SIN is the sector number of the start of the object. On Level3FS disks the SIN is the sector number of the object's allocation map. Sector 0/1 - Level 2 Disk Information Block =========================================== The Level 2 File Server stores the disk information in sector 0. The Level 3 File Server reserves sector 0 and 1 for an ADFS free space map with a pointer to the File Server partition. 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | A | F | S | 0 | - - - - - - - - Disk Title - - - - - - - - | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | DSize | Root SIN | CDate | Map A SIN | Map B SIN |SEC| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00-03: "AFS0" identifier. 04-13: Disk title, padded with spaces if less than 16 characters. 14-15: Number of sectors on one side of disk, ie &0320 for a 400K floppy. 16-18: System Internal Name (SIN) of root directory. 19-1A: Initialisation date in filing system standard date format. 1B-1D: System Internal Name (SIN) of Map A 1E-20: System Internal Name (SIN) of Map B. 21 : Number of sectors of map (set to zero on disks examined) 22-23: Zero 24+ : Filled with &00000080 words Sector 1 is filled with &00 bytes and is unused. There is a copy of sector 0 in sector 10. On double-sided disks sector 801 (side 1, track 0, sector 1) is a copy of sector 1, and is unused. Sector 1 and sector 801 are probably filled with zeros so that if examined by DFS they look like an empty disk with no free space. Sector 0/1 - Level 3 ADFS Free Space Map ======================================== The Level 3 File Server filesystem coexists on the disk with an ADFS filesystem. This ADFS filesystem may or may not be valid. The first two sectors contain the ADFS free space map with pointers to the start of the Level 3 FS partition. 00 01 02 03 04 05 ... F6 F7 F8 F9 FA FB FC FD FE FF +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000+|Start 1|Start 2|Start 3|...| Lv3FS+1 |&00|&00|&00| Disk Size |CHK| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 100+|Length1|Length2|Length3|...|Lv3FS+Cyl+1|&00|&00|Disk ID|OPT|FSL|CHK| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000-002 Start sector of first ADFS free space 003-005 Start sector of second ADFS free space 006-008 Start sector of third ADFS free space ... 0F6-0F8 (Start of Level3FS partition)+1 Note that RISC OS stores half the disk name at 0F7-0FB, interleaved with the even characters at 1F6-1FA. 0F9-0FB Reserved (set to zero) 0FC-0FE Total number of sectors on disk 0FF Checksum of sector 0 100-102 Length of first ADFS free space 103-105 Length of second ADFS free space 106-108 Length of third ADFS free space ... 1F6-1F8 (Start of Level3FS partition)+1+sectors_per_track Note that RISC OS stores half the disk name at 1F6-1FA, interleaved with the odd characters at 0F7-0FB) 1F9-1FA Reserved (set to zero) 1FB-1FC ADFS Disk identifier 1FD ADFS Boot option, set with *OPT 4 1FE Pointer to end of ADFS free space list ie, 3*(number of free space blocks) 1FF Checksum of sector 1 The relevant information for the Level3FS filesystem is: 0F6-0F8 (Start of Level3FS partition)+1 1F6-1F8 (Start of Level3FS partition)+1+sectors_per_track The pointers at 0F6-0F8 and 1F6-1F8 actually point to the first and second copies of the "NFS First Sector", which will always be in physical sector 1 of the first and second tracks used by the Level3FS partition. So, with a 64 sectors/track disk, if 0F6-0F8 pointed to &002001 then 1F6-1F8 would point to &002041 and the partition would start at &002000. When the partition is initialised with WFSInit, the program assumes the disk has been compacted and that the first free space entry is the only entry and refers to the whole free space on the disk. It then reduces this to leave 16K for ADFS and the rest of the disk for the Level3FS partition. Consequently, if the disk is /not/ compacted, any files after the first free space will be overwritten. A better method is for WFSInit to scan through the ADFS free space map to find the /final/ entry and then use that to create the parition. The checksums are calculated by starting with 255, then adding with carry the 255 bytes of data, adding the bytes counting downwards from byte 254 to byte 0. The following BASIC code will do this. DEFFNadfs_sum(mem%):LOCAL sum%:sum%=255 FOR A%=254 TO 0 STEP -1 IF sum%>255:sum%=(sum%+1)AND255 sum%=sum%+mem%?A%:NEXT:=sum%AND255 The Disk Identifier is set to a random 16-bit number on initialisation. Level 3 FS Partition -------------------- The Level 3 FS parition is assumed to be aligned on whole tracks (ie cylinders) with a sector bitmap in the first sector of each track. NFS Sector 1 - Level 3 Disk Information Block --------------------------------------------- This is &100 bytes into the Level 3 FS partition, and is pointed to by the pointer at &F6-&F8 in sector 0. 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | A | F | S | 0 | - - - - - - - - Disk Title - - - - - - - - | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Tracks| Disk Size |DSK|Sec/Trk|BMP|NXT|&00| Root SIN | CDate |FreeCyl| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00-03: "AFS0" identifier. 04-13: Disk title, padded with spaces if less than 16 characters. 14-15: Number of tracks (cylinders) on whole disk. 16-18: Number of sectors on whole disk, ie &000A00 for a 640K floppy. 19 : Number of partitions on this disk, usually 1. 1A-1B: Number of sectors per track, ie 16 for a 640K floppy. 1C : Number of sectors per bitmap, usually 1. 1D : Increment to next drive, usually 0 for only one drive. 1E : Unused, set to zero. 1F-21: System Internal Name (SIN) of root directory. 22-23: Initialisation date in filing system standard date format. 24-25: First free cylinder. Level3FS Sector Bitmaps ----------------------- The first sector on each track/cylinder (ie physical sector 0) contains a sector used/free bitmap for that track. Bit 0 of byte 0 refers to sector 0, bit 1 to sector 1, and so on. If a bit is set then that sector is available. If it is clear, then it is used or does not exist. All sector bitmaps will have byte 0 bit 0 clear as the bitmap occupies sector zero. The first and second tracks' bitmaps will initially also have byte 0 b1 clear as the "1st NFS sector" copies also occupy sector 1. Directories =========== Directories occupy up to 26 sectors - &1A00 bytes - which can be scattered around according to their allocation map. By default, a directory is created with 19 entries, &200 bytes long. A directory has the following layout. 0000 : Directory Header 0011 : First directory entry (parent directory where implemented) 002B : Second directory entry 0045 : Third directory entry ... 19C3 : 254th directory entry 19DD : 255th directory entry 19F7 : Directory footer Directory Header ---------------- 00-01: Pointer to first entry in linked list of directory entries 02 : Cycle number of directory, same as last byte 03-0C: Directory name padded with spaces 0D-0E: Pointer to first free entry in directory 0F : Number of entries in directory 10 : Unused, set to zero The linked list arranges the directory entries in a case-insensitive sorted order. Each linked list is termined by &0000. Later versions of the FileStore and the Level 3 File Server reserve the first entry to point to the parent directory to implement '^'. Only the SIN of this entry has any meaning. Directory Entries ----------------- 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 15 16 17 18 19 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |Next | Object name | Load Addr | Exec Addr |Ac|MDate| Sector | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 00-01: Pointer to next entry in the list or &FFFF for parent directory entry 02-0B: Object name padded with spaces 0C-0F: Load address 10-13: Execution address 14 : Access byte in internal NFS format: b7: reserved b3: owner write b6: reserved b2: owner read b5: directory b1: public write b4: locked b0: public read Files are created with 'WR/' access, directories are created with 'DL/' access. 15-16: Modification date in standard format 17-19: SIN of object Note that the NetFS access byte format is different to the standard filing system access byte format. The internal byte can be converted to the standard access byte with the following code: access%=(nfsacc%AND3)*16+(nfsacc%AND12)/4+(nfsacc%AND16)/2 This converts the NetFS access byte --DLWRwr to the filing system access byte 00wrL0WR. 8-bit NetFS returns the two 'E' bits as duplicates of the 'R' bits on reading and ignores the 'E' bits on writing. RISC OS NetFS returns the Owner 'E' bit clear and the public 'E' bit as bit 6 of the NetFS byte. The standard date format is: Byte 0 b0-b4: day of month. b5-b7: (year-1981) b4-b6. Byte 1 b0-b3: month. b4-b7: (year-1981) b0-b3. Directory Footer ---------------- xxx : Copy of cycle number. The directory footer follows the last entry regardless of directory size. If the two cycle numbers do not match the fileserver will generate a "Broken directory" error. Parent directory ---------------- The parent directory entry is only created by later versions of the FileStore Level 3 File Server. If it exists it is always the first entry in the directory, the 'Next' entry is &FFFF, the SIN points to the parent and all other bytes are &00. Note: this includes the filename entry, so it appears to be named "", and includes the Attribute entry, so appears to not actually be a directory, it has access "/". Onject Allocation Map ===================== All objects are refered to by their SIN (System Internal Name), which is for Level2FS the sector number of the start of the object, and for Level3FS the sector number of the object's allocation map. An object's length is not stored in the directory, but in the object's allocation map. Level2FS Allocation Map ----------------------- The contents of an object on a Level2FS disk is found by using the object's SIN as an index into the disk's allocation map. The SIN in the directory entry points to the object's first sector. That SIN is used as an index into the allocation map to find the next sector. That sector number is then used as another index into the allocation map for the next sector, and so on until the end of the file. 00 : Used to indicate current map, map with the highest number is current map 01-02: Number of entries 03-04: Pointer to first free chain 05-06: Entry for sector 0 07-08: Entry for sector 1 09-0A: Entry for sector 2 0B- : Entry for subsequent sectors The entry for sector n is MAP + (n*2)+5,(n*2)+6. For each entry LSB : LSB of sector number of "next" sector or number of bytes in this sector MSB : b0-3 : MSB of sector number of "next" sector b4 = 1 : if this chain (of dir) is empty b5 = 1 : if this sector is start of a chain, so the second sector of the object b6 = 1 : if this sector is last in a chain, the LSB is number of bytes in this sector b7 = 0 : if this sector is unwritten. An empty allocation map has each entry pointing to the next entry. Because the sector numbers in the allocation map are 12-bit numbers, the maximum disk size is &FFF+1 sectors = 1M. A 400K disk has &640 sectors, so the allocation map is &640*2+5 = &C85 bytes, so occupies 13 sectors. A blank 400K disk has: 26 sectors for the two maps 1 sector for the Disk Information Block 2 sectors for the blank DFS catalogs 2 sectors for an empty root directory 31 used sectors = 7.75K 1569 free sectors = 392.25K The amount of free space can be found by following the free space pointer at 03-04 and counting the number of entries in the chain. The amount of used space is found by subtracting the free space from the disk size in the Disk Information Block. Level3FS Allocation Map ----------------------- The contents of an object on a Level3FS disk is found by using the sector pointed to by the object's SIN. This sector then contains a list of sector numbers holding the object, possibly chaining to further sectors. 00-05: "JesMap" identifier. 06 : Map chain squence number. 07 : Unused, set to zero. 08 : LSB of object length. 09 : Unused, set to zero. 0A-0E: First group of allocated sectors. 0A-0C: Logical sector number. 0D-0E: Number of logical sectors. 0F-13: Second group of allocated sectors. 0F-11: Logical sector number. 12-13: Number of logical sectors. ... F5-F9: Final group of allocated sectors. F5-F7: Logical sector number. F8-F9: Number of logical sectors. FA-FE: SIN of next allocation sector or 0. FA-FC: Logical sector number. FD-FE: Number of logical sectors, normally 1. FF : Copy of map chain sequence number. An allocation map can occupy more than one sector, with the allocation block at &FA-&FE pointing to the next allocation map. Subsequent allocation maps have the first six bytes set to &00 instead of "JesMap" and the LSB of the object length set to zero, the actual length is in the first allocation map. The amount of free space on a disk is found by examining the free space bitsmaps. (details needed) Version History =============== 1.00 25-Oct-2007 First draft. 1.01 25-Nov-2010 Compared with Application Note 75 and clarified details where AppNote is wrong. 1.02 25-Mar-2020 Investigation clarified chained Level3FS allocation maps. 1.03 28-Apr-2021 Added diagrams, some editing and updates.