mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
07_dos: - file_io.md: 108→240+ lines — buffered I/O (FRead/FWrite/SetVBuf), access mode comparison, FileHandle struct with offsets, standard handles, Printf %ld warning, FileInfoBlock, practical patterns (copy file, get size, load to RAM), error code table - filesystem.md: 91→270+ lines — full disk geometry (ADF/HDF), all 8 DOS\x filesystem IDs, root block byte-level layout, file header layout with reverse-order pointer quirk, OFS vs FFS data blocks with efficiency numbers, bitmap blocks, extension blocks, checksum algorithm, Python ADF reader - locks_examine.md: 113→270+ lines — lock semantics diagram, FileLock struct with handler discovery, ExAll bulk scan, practical patterns (atomic write, path resolution, volume info), 4 antipatterns (leaked locks, exclusive too long, unchecked IoErr, DupLock), pattern matching 10_devices: - audio.md: 73→240+ lines — hardware architecture diagram, channel registers with offsets, period/frequency table, priority allocation, double-buffering, audio interrupts, AM/PM modulation, direct HW - timer.md: 80→230+ lines — CIA timer hardware, all 5 units with decision flowchart, non-blocking delays, signal-based waiting, time arithmetic, ReadEClock, periodic game loop pattern, pitfalls - trackdisk.md: 82→210+ lines — MFM encoding, track format, disk geometry, read/write/motor, change notification, track caching, direct hardware access, FPGA timing implications - keyboard.md: 58→220+ lines — CIA-A serial handshake protocol with sequence diagram, bit rotation quirk, complete key code map, key matrix bitmap, reset sequence, FPGA notes
304 lines
11 KiB
Markdown
304 lines
11 KiB
Markdown
[← Home](../README.md) · [AmigaDOS](README.md)
|
||
|
||
# Filesystem — FFS/OFS Block Structure and Disk Layout
|
||
|
||
## Overview
|
||
|
||
AmigaOS supports two native filesystem types: **OFS** (Old File System, OS 1.x) and **FFS** (Fast File System, OS 2.0+). Both use a block-based layout with 512-byte blocks. FFS differs by storing data blocks without headers, improving throughput. Understanding the on-disk layout is essential for FPGA core developers implementing virtual filesystems, HDF support, and ADF image handling.
|
||
|
||
---
|
||
|
||
## Disk Geometry and Layout
|
||
|
||
### ADF (Amiga Disk File) — Floppy Disk
|
||
|
||
```
|
||
Total: 880 KB = 1,760 sectors = 80 tracks × 2 sides × 11 sectors
|
||
Block size: 512 bytes
|
||
Blocks 0–1: Boot block (1 KB)
|
||
Block 880: Root block (always at disk midpoint)
|
||
```
|
||
|
||
### HDF (Hard Disk File) — Hard Drive
|
||
|
||
```
|
||
Block 0: Reserved (or RDB — Rigid Disk Block)
|
||
Block 1+: RDB partition table (if present)
|
||
Partition: Starts at first block of partition
|
||
Root block at partition midpoint
|
||
```
|
||
|
||
---
|
||
|
||
## Block Types
|
||
|
||
| Type | ID | Constant | Purpose |
|
||
|---|---|---|---|
|
||
| Boot block | — | (bytes 0–1023) | Boot code + filesystem ID (`DOS\0` to `DOS\7`) |
|
||
| Root block | 2 | `T_HEADER` | Volume root directory — always at midpoint |
|
||
| File header | 2 | `T_HEADER` | File metadata + first 72 data block pointers |
|
||
| Directory | 2 | `T_HEADER` | Subdirectory — contains 72-slot hash table |
|
||
| Data block | 8 | `T_DATA` | OFS: 24-byte header + 488 data bytes |
|
||
| Extension | 16 | `T_LIST` | Overflow block pointers for files >72 blocks |
|
||
| Bitmap block | — | — | Free/allocated block tracking |
|
||
|
||
---
|
||
|
||
## Boot Block (Blocks 0–1)
|
||
|
||
```
|
||
Offset Size Field
|
||
────── ──── ─────────────────────
|
||
$00 4 Filesystem ID — "DOS\0" to "DOS\7"
|
||
$04 4 Checksum
|
||
$08 4 Root block number (usually 880)
|
||
$0C 1012 Boot code (optional — loaded by ROM bootstrap)
|
||
```
|
||
|
||
### Filesystem ID Variants
|
||
|
||
| ID | Hex | Description |
|
||
|---|---|---|
|
||
| `DOS\0` | $444F5300 | OFS (original) |
|
||
| `DOS\1` | $444F5301 | FFS (fast file system) |
|
||
| `DOS\2` | $444F5302 | OFS + International mode (case-insensitive) |
|
||
| `DOS\3` | $444F5303 | FFS + International mode |
|
||
| `DOS\4` | $444F5304 | FFS + Directory cache |
|
||
| `DOS\5` | $444F5305 | OFS + International + Directory cache |
|
||
| `DOS\6` | $444F5306 | OFS + Long filenames (OS 3.5+) |
|
||
| `DOS\7` | $444F5307 | FFS + Long filenames (OS 3.5+) |
|
||
|
||
---
|
||
|
||
## Root Block (Block 880 on Floppy)
|
||
|
||
The root block is the entry point for the entire filesystem. It always lives at the midpoint of the partition:
|
||
|
||
```
|
||
root_block_number = (total_blocks) / 2 /* e.g., 1760/2 = 880 */
|
||
```
|
||
|
||
### Root Block Layout (512 bytes)
|
||
|
||
```
|
||
Offset Size Field Description
|
||
────── ────── ────────────────────── ────────────────────────────────
|
||
$000 4 type Always 2 (T_HEADER)
|
||
$004 4 header_key Own block number
|
||
$008 4 high_seq 0 (unused for root)
|
||
$00C 4 ht_size Hash table size (72 for DD floppy)
|
||
$010 4 first_data 0 (unused)
|
||
$014 4 checksum Block checksum
|
||
$018 288 ht[72] Hash table: LONG[72] block pointers
|
||
for directory entries (0 = empty slot)
|
||
$138 4 bm_flag Bitmap valid flag (-1 = valid)
|
||
$13C 100 bm_pages[25] LONG[25] pointers to bitmap blocks
|
||
$1A0 4 bm_ext Pointer to extended bitmap block
|
||
$1A4 12 last_root_alteration DateStamp: root last modified
|
||
$1B0 32 disk_name BSTR: volume name (length-prefixed)
|
||
$1D0 4 (reserved)
|
||
$1D4 12 last_disk_alteration DateStamp: disk last modified
|
||
$1E0 12 creation_date DateStamp: volume creation date
|
||
$1EC 4 (reserved)
|
||
$1F0 4 extension 0 (unused for root)
|
||
$1F4 4 sec_type 1 = ST_ROOT
|
||
$1F8 4 (reserved)
|
||
$1FC ← end of 512-byte block
|
||
```
|
||
|
||
---
|
||
|
||
## Hash Function
|
||
|
||
File and directory names are hashed into the hash table slots:
|
||
|
||
```c
|
||
/* The canonical AmigaDOS hash function: */
|
||
ULONG HashName(const char *name, ULONG table_size)
|
||
{
|
||
ULONG hash = (ULONG)strlen(name);
|
||
for (int i = 0; name[i]; i++)
|
||
{
|
||
hash = (hash * 13 + toupper((unsigned char)name[i])) & 0x7FF;
|
||
}
|
||
return hash % table_size;
|
||
}
|
||
/* table_size = 72 for DD floppy, 128 for HD floppy */
|
||
```
|
||
|
||
### Hash Collision Resolution
|
||
|
||
Collisions are resolved by **chaining**: each file/directory header has a `hash_chain` pointer (at offset `$1F0`) linking to the next entry that hashed to the same slot. The chain ends with 0.
|
||
|
||
```
|
||
Hash Table (root block):
|
||
Slot 0: → 0 (empty)
|
||
Slot 1: → Block 950 ("Startup-Sequence")
|
||
└→ Block 1100 ("System-Startup") ← hash_chain
|
||
└→ 0 (end)
|
||
Slot 2: → Block 882 ("Libs")
|
||
...
|
||
```
|
||
|
||
---
|
||
|
||
## File Header Block
|
||
|
||
```
|
||
Offset Size Field Description
|
||
────── ────── ────────────────────── ──────────────────────────────
|
||
$000 4 type 2 (T_HEADER)
|
||
$004 4 header_key Own block number
|
||
$008 4 high_seq Number of data block pointers stored here
|
||
$00C 4 data_size 0 (unused in file header)
|
||
$010 4 first_data First data block (OFS only — FFS uses table)
|
||
$014 4 checksum
|
||
$018 288 data_blocks[72] LONG[72] — block pointers to data blocks
|
||
Stored in REVERSE ORDER: [71]=first, [0]=last
|
||
$138 — ... (padding/reserved)
|
||
$144 4 protect Protection bits (RWED)
|
||
$148 4 byte_size File size in bytes
|
||
$14C 80 comment BSTR: file comment
|
||
$19C 12 date DateStamp: file modification date
|
||
$1A8 32 filename BSTR: file name (length-prefixed)
|
||
$1D0 4 real_entry For hard links: the real file header
|
||
$1D4 4 next_link For hard links: next link to same file
|
||
$1EC 4 hash_chain Next entry in same hash slot (0 = end)
|
||
$1F0 4 parent Block number of parent directory
|
||
$1F4 4 extension Block number of extension block (0 = none)
|
||
$1F8 4 sec_type -3 = ST_FILE
|
||
```
|
||
|
||
> **Reverse order**: Data block pointers in `data_blocks[]` are stored **last-to-first**. Index 71 points to the first data block, index 70 to the second, etc. This is a BCPL heritage quirk.
|
||
|
||
---
|
||
|
||
## OFS vs FFS — Data Block Differences
|
||
|
||
### OFS Data Block (T_DATA = 8)
|
||
|
||
```
|
||
$000 4 type 8 (T_DATA)
|
||
$004 4 header_key Pointer back to file header
|
||
$008 4 seq_num Sequence number (1-based)
|
||
$00C 4 data_size Bytes of valid data in this block
|
||
$010 4 next_data Next data block (0 = last)
|
||
$014 4 checksum
|
||
$018 488 data[488] Actual file data (488 usable bytes)
|
||
```
|
||
**Efficiency**: 488 / 512 = **95.3%** — 24 bytes wasted per block on headers.
|
||
|
||
### FFS Data Block
|
||
|
||
```
|
||
$000 512 data[512] Pure file data — no header overhead
|
||
```
|
||
**Efficiency**: 512 / 512 = **100%**
|
||
|
||
| Feature | OFS | FFS |
|
||
|---|---|---|
|
||
| Bytes per data block | 488 | 512 |
|
||
| Header overhead | 24 bytes/block | 0 |
|
||
| Self-describing blocks | Yes (can recover from corruption) | No |
|
||
| Max filename | 30 chars | 30 chars (107 with DOS\6/\7) |
|
||
| Throughput | ~5% slower | Baseline |
|
||
| International mode | DOS\2 | DOS\3 |
|
||
| Directory cache | No | DOS\4 |
|
||
| Min OS version | 1.0 | 2.0 |
|
||
|
||
---
|
||
|
||
## Bitmap Blocks — Free Space Tracking
|
||
|
||
The bitmap tracks which blocks are free (1) or allocated (0):
|
||
|
||
```c
|
||
/* Each bitmap block covers up to (512-4) × 8 = 4064 blocks */
|
||
struct BitmapBlock {
|
||
ULONG checksum; /* block checksum */
|
||
ULONG map[127]; /* bit 1 = free, bit 0 = allocated */
|
||
/* bit 0 of map[0] = block corresponding to this bitmap's range */
|
||
};
|
||
```
|
||
|
||
The root block's `bm_pages[25]` array can reference up to 25 bitmap blocks, covering 25 × 4064 = **101,600 blocks** (≈49 MB). Larger partitions need `bm_ext` extension blocks.
|
||
|
||
---
|
||
|
||
## Checksum Algorithm
|
||
|
||
```c
|
||
LONG ComputeBlockChecksum(ULONG *block, LONG longs)
|
||
{
|
||
LONG sum = 0;
|
||
block[5] = 0; /* clear checksum field before computing */
|
||
for (int i = 0; i < longs; i++)
|
||
sum += block[i];
|
||
return -sum; /* store at block[5] so total = 0 */
|
||
}
|
||
/* Verify: if sum of all 128 longs (incl. checksum) = 0, block is valid */
|
||
```
|
||
|
||
---
|
||
|
||
## File Extension Blocks
|
||
|
||
Files larger than 72 data blocks need extension blocks to store additional pointers:
|
||
|
||
```
|
||
extension_block.data_blocks[72] → next 72 data block pointers
|
||
extension_block.extension → next extension block (or 0)
|
||
```
|
||
|
||
Each extension block adds 72 more data block pointers. With FFS (512 bytes/block):
|
||
- 72 blocks = 36 KB directly in file header
|
||
- +72 per extension = 36 KB more per extension
|
||
- Maximum chain depth is effectively unlimited
|
||
|
||
---
|
||
|
||
## Practical: Reading an ADF Image
|
||
|
||
```python
|
||
import struct
|
||
|
||
def read_adf(filename):
|
||
with open(filename, 'rb') as f:
|
||
data = f.read()
|
||
|
||
# Boot block — filesystem type
|
||
fs_type = data[0:4]
|
||
print(f"Filesystem: {fs_type}") # b'DOS\x00' = OFS, b'DOS\x01' = FFS
|
||
|
||
# Root block at block 880 (offset 880 * 512 = 450560)
|
||
root_off = 880 * 512
|
||
root = data[root_off:root_off + 512]
|
||
|
||
# Volume name (BSTR at offset $1B0)
|
||
name_len = root[0x1B0]
|
||
vol_name = root[0x1B1:0x1B1 + name_len].decode('ascii')
|
||
print(f"Volume: {vol_name}")
|
||
|
||
# Hash table: 72 entries starting at offset $018
|
||
ht = struct.unpack('>72I', root[0x18:0x18 + 72 * 4])
|
||
for i, blk in enumerate(ht):
|
||
if blk != 0:
|
||
# Read the file/dir header at that block
|
||
hdr_off = blk * 512
|
||
hdr = data[hdr_off:hdr_off + 512]
|
||
fname_len = hdr[0x1A8]
|
||
fname = hdr[0x1A9:0x1A9 + fname_len].decode('ascii')
|
||
sec_type = struct.unpack('>i', hdr[0x1F4:0x1F8])[0]
|
||
kind = "DIR" if sec_type == 2 else "FILE"
|
||
print(f" [{i:2d}] {kind} {fname} (block {blk})")
|
||
```
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- NDK39: `dos/filehandler.h`
|
||
- Ralph Babel: *The AmigaDOS Manual* (3rd edition) — definitive FFS reference
|
||
- Laurent Clévy: *The Amiga Filesystem* — http://lclevy.free.fr/adflib/
|
||
- See also: [packet_system.md](packet_system.md) — filesystem handler protocol
|
||
- See also: [locks_examine.md](locks_examine.md) — lock/examine API layer
|