diff --git a/local/AGENTS.md b/local/AGENTS.md index 89107645..bcb1ea68 100644 --- a/local/AGENTS.md +++ b/local/AGENTS.md @@ -448,7 +448,7 @@ recipes/core/fatd → ../../local/recipes/core/fatd - `fatd`: ✅ Compiles (links on Redox target only — expected). ✅ `frename` + rmdir non-empty check implemented. NOT runtime-tested (requires QEMU/bare metal). - Phase 4 (runtime auto-mount): Deferred to runtime validation. Static init service exists. - Known limitation: fatfs v0.3.6 strictly requires `total_sectors_16 == 0` for FAT32, rejecting some Linux `mkfs.fat` images -- `cargo test`: 56 unit tests (25 scheme + 7 label + 24 check) + 13+ integration edge cases +- `cargo test`: 60 unit tests (25 scheme + 7 label + 28 check) + 13+ integration edge cases ## BRANDING ASSETS diff --git a/local/docs/VFAT-IMPLEMENTATION-PLAN.md b/local/docs/VFAT-IMPLEMENTATION-PLAN.md new file mode 100644 index 00000000..a3d0a500 --- /dev/null +++ b/local/docs/VFAT-IMPLEMENTATION-PLAN.md @@ -0,0 +1,916 @@ +# VFAT Implementation Plan — Red Bear OS + +**Date:** 2026-04-17 +**Status:** Implemented (Phase 1–3 complete, Phase 2b complete, Phase 4 deferred to runtime validation) +**Scope:** FAT12/16/32 with LFN (VFAT) — data volumes and ESP only (NOT root filesystem) +**Reference Implementation:** `local/recipes/core/ext4d/` (ext4 scheme daemon) + +## 1. Executive Summary + +Implement full VFAT support in Red Bear OS: a FAT scheme daemon (`fatd`) for mounting +FAT filesystems at runtime, management tools (mkfs, label, check), installer ESP +integration, and runtime auto-mount for USB storage and SD cards. + +FAT is **not** a root filesystem target — RedoxFS and ext4 remain the root options. +FAT serves for: EFI System Partitions, USB mass storage, SD cards, and data exchange +with other operating systems. + +**Recommended crate:** `fatfs` v0.3.6 (MIT, 356 stars, already in dependency tree via +installer). It provides FAT12/16/32, LFN, formatting, read/write, and `no_std` support. + +**Estimated effort:** 6–10 weeks for a complete, tested implementation. + +## 2. Current State + +### What Exists + +| Component | Location | Status | +|-----------|----------|--------| +| RedoxFS (default root FS) | `recipes/core/redoxfs/` | ✅ Stable | +| ext4 (alternate root FS) | `local/recipes/core/ext4d/` | ✅ Scheme daemon + mkfs + installer wired | +| `fatfs` crate in installer | `local/patches/installer/redox.patch` | ✅ Host-side EFI partition formatting only | +| `redox-fatfs` library | `recipes/libs/redox-fatfs/` | ❌ Commented out, dead code | +| Bootloader FAT reading | `recipes/core/bootloader/` | ❌ Reads RedoxFS only, no FAT | +| GRUB FAT reading | GRUB EFI image | ✅ GRUB `fat` module reads ESP | +| exfat-fuse | `recipes/wip/fuse/exfat-fuse/` | ❌ WIP, not compiled | + +### What Is Missing (the gaps this plan fills) + +| Gap | Priority | Description | +|-----|----------|-------------| +| VFAT scheme daemon | Critical | No `fatd` scheme for mounting FAT at runtime | +| FAT block device adapter | Critical | No adapter bridging Redox block I/O → `fatfs` traits | +| FAT management tools | High | No mkfs.fat, fatlabel, fsck.fat equivalents | +| Runtime auto-mount | High | No service to detect and mount FAT block devices | +| FAT filesystem checker | Medium | No verification or repair tool | + +### Key Architectural Decision + +The `ext4d` workspace at `local/recipes/core/ext4d/source/` is the exact template for +this implementation. It demonstrates: + +1. **Block device adapter** — `ext4-blockdev/` with FileDisk (Linux) + RedoxDisk (Redox) +2. **Scheme daemon** — `ext4d/` with full FSScheme via `redox_scheme::SchemeSync` +3. **Management tool** — `ext4-mkfs/` as a standalone binary +4. **Workspace structure** — Workspace Cargo.toml, resolver=3, edition=2024 +5. **Feature flags** — `default = ["redox"]`, redox = ["dep:libredox", ...] +6. **Recipe** — `template = "custom"` with `COOKBOOK_CARGO_PATH` + +## 3. Implementation Phases + +### Phase 1: FAT Scheme Daemon (`fatd`) — 3–4 weeks + +**Goal:** A working VFAT scheme daemon that can mount and serve FAT filesystems. + +#### 1.1 Workspace Setup + +Create `local/recipes/core/fatd/` workspace mirroring ext4d structure: + +``` +local/recipes/core/fatd/ +├── recipe.toml ← Custom build script +└── source/ + ├── Cargo.toml ← Workspace: fat-blockdev, fatd, fat-mkfs, fat-label, fat-check + ├── fat-blockdev/ + │ ├── Cargo.toml + │ └── src/ + │ ├── lib.rs ← Re-exports + FatError type + │ ├── file_disk.rs ← FileDisk: std::fs backed (Linux host) + │ └── redox_disk.rs ← RedoxDisk: libredox backed (Redox target) + ├── fatd/ + │ ├── Cargo.toml + │ └── src/ + │ ├── main.rs ← Daemon entry: fork, SIGTERM, dispatch + │ ├── mount.rs ← Scheme event loop (SchemeSync) + │ ├── scheme.rs ← FatScheme: full FSScheme impl + │ └── handle.rs ← FileHandle, DirHandle, Handle types + ├── fat-mkfs/ + │ ├── Cargo.toml + │ └── src/ + │ └── main.rs ← Create FAT filesystems + ├── fat-label/ + │ ├── Cargo.toml + │ └── src/ + │ └── main.rs ← Read/write volume labels + └── fat-check/ + ├── Cargo.toml + └── src/ + └── main.rs ← Verify + repair FAT filesystems +``` + +**Recipe** (`recipe.toml`): +```toml +[source] +path = "source" + +[build] +template = "custom" +script = """ +COOKBOOK_CARGO_PATH=fatd cookbook_cargo +COOKBOOK_CARGO_PATH=fat-mkfs cookbook_cargo +COOKBOOK_CARGO_PATH=fat-label cookbook_cargo +COOKBOOK_CARGO_PATH=fat-check cookbook_cargo +""" +``` + +**Workspace `Cargo.toml`**: +```toml +[workspace] +members = ["fat-blockdev", "fatd", "fat-mkfs", "fat-label", "fat-check"] +resolver = "3" + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT" + +[workspace.dependencies] +fatfs = "0.3.6" +fscommon = "0.1.1" +redox_syscall = "0.7.3" +redox-scheme = "0.11.0" +libredox = "0.1.13" +redox-path = "0.3.0" +log = "0.4" +env_logger = "0.11" +libc = "0.2" +``` + +**Symlink**: `recipes/core/fatd → ../../local/recipes/core/fatd` + +#### 1.2 Block Device Adapter (`fat-blockdev`) + +The `fatfs` crate uses `Read + Seek` and `Read + Write + Seek` traits for block device +access. We need adapters that wrap Redox's block I/O into these traits. + +**`file_disk.rs`** (Linux host): +```rust +// Wraps std::fs::File to implement Read+Write+Seek +// Identical pattern to ext4-blockdev/src/file_disk.rs +// Uses fscommon::BufStream for caching +pub struct FileDisk { ... } +impl Read for FileDisk { ... } +impl Write for FileDisk { ... } +impl Seek for FileDisk { ... } +``` + +**`redox_disk.rs`** (Redox target, feature-gated): +```rust +// Wraps libredox fd to implement Read+Write+Seek +// Uses syscall::call::open/read/write/lseek/fstat +// Pattern from ext4-blockdev/src/redox_disk.rs +pub struct RedoxDisk { + fd: usize, + size: u64, // from fstat +} +impl Read for RedoxDisk { ... } +impl Write for RedoxDisk { ... } +impl Seek for RedoxDisk { ... } +``` + +**Critical detail**: Wrap the disk in `fscommon::BufStream` for performance — +`fatfs` does no internal caching and performs poorly without buffering. + +```rust +let disk = RedoxDisk::open(disk_path)?; +let buf_disk = fscommon::BufStream::new(disk); +let fs = fatfs::FileSystem::new(buf_disk, fatfs::FsOptions::new())?; +``` + +#### 1.3 VFAT Scheme Daemon (`fatd`) + +**Architecture**: Single `fatfs::FileSystem` instance per daemon process. The `fatfs` +crate is NOT safe for concurrent access from multiple `FileSystem` objects on the same +device. One daemon = one mounted filesystem = one `FileSystem` instance. + +**`handle.rs`** — Handle types: + +```rust +pub enum Handle { + File(FileHandle), + Directory(DirectoryHandle), + SchemeRoot, +} + +pub struct FileHandle { + path: String, + offset: u64, + flags: usize, +} + +pub struct DirectoryHandle { + path: String, + entries: Vec, // cached readdir results + offset: usize, + flags: usize, +} +``` + +**Key difference from ext4d**: `fatfs` does not have persistent file handles like +rsext4's `OpenFile`. Files must be re-opened on each read/write operation. The +`FileHandle` stores the path and offset, and the scheme re-opens the file on each +`read`/`write` call. + +**`scheme.rs`** — FatScheme implementing `SchemeSync`: + +Required methods and their `fatfs` mapping: + +| SchemeSync method | fatfs operation | +|-------------------|-----------------| +| `scheme_root()` | Return SchemeRoot handle | +| `openat()` | `fs.root_dir().open_dir(path)` or `open_file(path)` | +| `read()` | Re-open file, seek to offset, `file.read(buf)` | +| `write()` | Re-open file, seek to offset, `file.write(buf)` | +| `fsize()` | Re-open file, `file.len()` | +| `fstat()` | `dir.iter().find()` for entry, construct `Stat` | +| `fstatvfs()` | `fs.stats()` for block/free counts | +| `getdents()` | `dir.iter()` collect entries, serve from handle cache | +| `ftruncate()` | Re-open file, `file.truncate()` | +| `fsync()` | `file.flush()` | +| `unlinkat()` | `dir.remove(name)` or `dir.remove_dir(name)` | +| `fcntl()` | Return handle flags | +| `fpath()` | Return mounted_path + handle path | +| `on_close()` | Remove from handle map | + +**Permission mapping**: FAT has limited permissions (read-only, hidden, system, +archive). Map to Unix permissions: +- Read-only attribute → `mode & !0o222` +- Otherwise → `0o644` for files, `0o755` for directories +- Owner/group always 0 (FAT has no ownership concept) +- Timestamps from FAT directory entry (2-second precision, date range 1980–2107) + +**Error mapping** (fatfs error → syscall error): +```rust +fn fat_error(err: fatfs::Error) -> syscall::error::Error { + match err { + fatfs::Error::NotFound => Error::new(ENOENT), + fatfs::Error::AlreadyExists => Error::new(EEXIST), + fatfs::Error::InvalidInput => Error::new(EINVAL), + fatfs::Error::IsDirectory => Error::new(EISDIR), + fatfs::Error::NotDirectory => Error::new(ENOTDIR), + fatfs::Error::DirectoryNotEmpty => Error::new(ENOTEMPTY), + fatfs::Error::WriteZero => Error::new(ENOSPC), + fatfs::Error::UnexpectedEof => Error::new(EIO), + _ => Error::new(EIO), + } +} +``` + +**`main.rs`** — Daemon lifecycle: +- Parse args: `fatd [--no-daemon] ` +- Fork (optional daemonization) +- Install SIGTERM handler for clean unmount +- Open block device → create BufStream → `fatfs::FileSystem::new()` +- Call `mount::mount()` to register scheme and enter event loop +- On SIGTERM: `fs.unmount()` (or just drop — fatfs flushes on drop) + +**`mount.rs`** — Event loop (identical pattern to ext4d mount.rs): +- `Socket::create()` +- `register_sync_scheme(&socket, mountpoint, &mut scheme)` +- Loop: `socket.next_request(SignalBehavior::Restart)` → dispatch to scheme +- On exit: `scheme.cleanup()` for clean unmount + +#### 1.4 LFN Support + +The `fatfs` crate handles LFN transparently when the `lfn` feature is enabled: + +```toml +fatfs = { version = "0.3.6", default-features = false, features = ["lfn", "alloc"] } +``` + +This provides: +- Long filename read via `DirEntry::file_name()` (returns full long name) +- Long filename write on `Dir::create_file()` and `Dir::create_dir()` +- Automatic 8.3 short name generation (e.g., "MYLONG~1.TXT") +- LFN checksum computation (handled internally) + +**No special LFN code needed in the scheme daemon** — `fatfs` abstracts it away. +The scheme daemon just passes filenames through. + +#### 1.5 FAT12/16/32 Auto-Detection + +`fatfs::FileSystem::new()` automatically detects FAT12, FAT16, or FAT32 based on +the BPB (BIOS Parameter Block) in the first sector. No explicit type selection needed. + +`fatfs::format_volume()` with `FormatVolumeOptions::new()` auto-selects FAT type +based on volume size: +- < 16 MB → FAT12 (or FAT16) +- 16 MB – 32 MB → FAT16 +- > 32 MB → FAT32 + +Explicit type selection: `FormatVolumeOptions::new().fat_type(FatType::Fat32)`. + +### Phase 2: Management Tools — 2–3 weeks + +#### 2.1 `fat-mkfs` — Create FAT Filesystems + +**Binary**: `fat-mkfs [options]` + +Options: +- `-F <12|16|32>` — Force FAT type (default: auto) +- `-n