# 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