From e3f4ffd1c99a9aba6eb43a4948c18e6ee20b699e Mon Sep 17 00:00:00 2001 From: Vasilito Date: Sat, 18 Apr 2026 00:13:46 +0100 Subject: [PATCH] Add VFAT implementation plan and update AGENTS.md FAT documentation 916-line plan covering: workspace structure, implementation phases, API design, build integration, success criteria, test results, and comprehensive quality assessment (Section 12 with 12 subsections). AGENTS.md updated with FAT workspace layout, tool verification status, and config integration details. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- local/AGENTS.md | 2 +- local/docs/VFAT-IMPLEMENTATION-PLAN.md | 916 +++++++++++++++++++++++++ 2 files changed, 917 insertions(+), 1 deletion(-) create mode 100644 local/docs/VFAT-IMPLEMENTATION-PLAN.md 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