# FAT32 (read-only filesystem backer)

This is a provenance map for the read-only FAT32 `Directory`/`File` backer, part
of the real-filesystem role-split
(`docs/proposals/real-filesystem-decision.md`). It is a **filesystem-format**
reader layered over a block device, not a hardware device page; like
`atapi-iso9660.md` it documents an on-disk format and the capOS cap surface over
it, citing the spec and the vendored parser rather than re-specifying FAT.

The backer lives in `kernel/src/cap/fat_fs.rs` and reads through the vendored
`fatfs` no_std crate (`vendor/fatfs-no_std/`). Its sector reads go through a
`BlockSource` seam with two mutually-exclusive variants (mirroring
`readonly_fs.rs`): a `Virtio` arm (compiled under `storage_fat_read`,
reading the kernel-owned virtio-blk device) and an `Nvme` arm (compiled
under `cloud_fat_read_over_nvme_proof`, reading a cloud-attached NVMe namespace
through the always-built brokered read window op). The module compiles under
either feature.

## 1. Spec basis

- **Format**: FAT32, the File Allocation Table filesystem as standardized by
  Microsoft's *FAT: General Overview of On-Disk Format* (the FAT32 File System
  Specification, v1.03) and the EFI FAT specification. The relevant structures
  are the BIOS Parameter Block (BPB) in the boot sector, the FAT32 FSInfo
  sector, the File Allocation Table itself (the cluster chain), and the
  directory-entry records (8.3 short entries plus VFAT long-file-name entries).
- **Parser provenance**: full FAT parsing is delegated to the vendored `fatfs`
  crate (`vendor/fatfs-no_std/rust-fatfs-0.4.0`, upstream rafalh/rust-fatfs,
  commit pinned in `vendor/fatfs-no_std/VENDORED_FROM.md`, MIT). capOS supplies
  the block-backed storage adapter, the cap surface above it, and an independent
  bounded validation subset over the BPB, root chain, root entries, and
  root-level file FAT chains before exposing a root cap.
- **Already a boot-path format**: FAT32 is the EFI System Partition format
  Limine reads, so it is structurally part of the boot path already
  (`docs/backlog/hardware-boot-storage.md`); this backer is the first *capOS*
  reader of a host-authored FAT32 image.

## 2. Wire format (implemented subset)

Only the read path is exercised; FAT write (cluster allocation, FSInfo/FAT
mutation, directory-entry creation) is **out of scope** and fails closed.

- **Mount** (`fat_fs::mount_fatfs` -> `fatfs::FileSystem::new`): capOS first
  performs a bounded FAT32 preflight over the boot sector / BPB and primary FAT:
  512-byte sectors, FAT32 geometry, root cluster in range, bounded root
  directory chain, and bounded root-level file chains. It then lets `fatfs`
  mount and asserts `FileSystem::fat_type() == FatType::Fat32`, failing the
  grant closed for FAT12/FAT16 or malformed images. The mount performs **no
  writes** (it reads the boot sector + FSInfo only); a clean `mkfs.fat` image
  keeps the BPB dirty flag clear, so the `fatfs` `Drop`/unmount path performs no
  writes either. The virtio arm (`fat_fs::mount_root`) mounts **eagerly** at
  grant time; the NVMe arm (`fat_fs::mount_root_nvme`) **defers** the mount to
  the first `Directory.list`/`open` (the `FatMount::Deferred` -> `Ready`
  transition in `FatMount::ensure`), because the brokered NVMe controller is
  brought up by the userspace provider after the grant resolves -- mirroring
  `readonly_fs::mount_root_nvme`.
- **Directory listing** (`Directory.list @1`): `fatfs::Dir::iter()` walks the
  root directory entries; capOS copies each entry's `file_name()` (LFN or
  case-normalized 8.3), `len()`, and `is_dir()` into the `DirEntry` reply
  (`FatFsDirectoryCap::collect_entries`). The volume-label entry is skipped by
  the iterator. capOS bounds the exposed root to `MAX_DIRECTORY_ENTRIES` (64)
  visible entries.
- **Open** (`Directory.open @0`): resolves a root-level file name to its size
  and raw FAT directory-entry timestamp metadata
  (`FatFsDirectoryCap::lookup_file_metadata`, rejecting directories and missing
  names) and mints a `File` cap recording the name, size, and bounded timestamp
  metadata. The write-implying `CREATE`/`TRUNCATE` flag bits, nested
  (`/`-bearing) paths, and files larger than `MAX_FILE_BYTES` (64 KiB) are
  rejected.
- **Read** (`File.read @0`): re-opens the file by name through the shared locked
  mount, `seek`s to the requested offset, and reads up to `length` bytes via
  `fatfs::Read`, walking the cluster chain. The covering read is clamped to
  end-of-file. `fatfs` resolves the FAT cluster chain; a multi-cluster file
  exercises the chain walk, not just the root entry. capOS preflight bounds the
  root-level file chain length and rejects cycles/bad/out-of-range cluster
  values before exposing the root cap.
- **Stat** (`File.stat @2`): reports the file size plus FAT directory-entry
  `created`/`modified` timestamps when the FAT fields are valid. capOS converts
  the FAT date/time fields to Unix epoch nanoseconds by interpreting the
  timezone-free FAT local-time value as UTC for this bounded local proof. Missing,
  zero, or invalid FAT date/time fields map to `0`, the schema's
  unstamped/unsupported value, rather than inventing trusted time. The `File.stat`
  ABI remains schema-stable and carries timestamp values only; proof logs label
  the source as `metadata_provenance=fat-directory-entry`,
  `clock_provenance=none`, and `trusted_clock=false`. FAT modification time has
  two-second granularity; FAT creation time has an optional high-resolution byte
  (`0..=199` in `10ms` units), and out-of-range high-resolution values are
  rejected before timestamp conversion.
- **Not implemented**: every mutation (`Directory.mkdir`/`remove`/`sub`/
  `create`/`rename`, `File.write`/`truncate`/`sync`) returns a typed error at the
  cap layer; the FAT/FSInfo/long-name **write** paths in `fatfs` are never
  reached.

## 3. capOS mapping

- **Binding (kernel-owned read; behind `read_only_fs_root`)**: the FAT32 root
  `Directory` is granted through the existing `KernelCapSource::ReadOnlyFsRoot`
  source. Under a plain `qemu` build that source resolves to the capOS-authored
  `CAPOSRO1` backer (`cap::readonly_fs`); under `storage_fat_read` it resolves to
  this FAT backer's virtio arm (`cap::fat_fs::mount_root`); under
  `cloud_fat_read_over_nvme_proof` it resolves to the FAT backer's NVMe arm
  (`cap::fat_fs::mount_root_nvme`, bound to the live `device_mmio` handle the
  production grant source staged -- the `device_mmio` grant must precede
  `read_only_fs_root` in the manifest). All three are wired in the
  `KernelCapSource::ReadOnlyFsRoot` arms of `kernel/src/cap/mod.rs` (boot PID 1)
  and `kernel/src/cap/process_spawner.rs` (spawned services). Selecting the backer
  by feature mirrors how the same source already selects its `Virtio` vs NVMe
  backend, so **no new `KernelCapSource` and no `schema/capos.capnp` change** is
  needed -- the `Directory`/`File` contract already carries every field.
- **`Directory`/`File` cap surface**: `FatFsDirectoryCap` / `FatFsFileCap`
  implement `Directory.list`/`open` + `File.read`/`stat`/`close`; every mutating
  method fails closed. Read-only is structural (distinct `CapObject` types that
  expose no mutation), not a rights flag. `open` mints the `File` result cap
  `Copy`/`SameSession`, so a holder can forward a read view to a same-session
  spawn child without conferring write authority (the same posture
  `readonly_fs`/`installable_image` use).
- **MMIO / Interrupt**: none authored by the backer. It holds no device registers
  and binds no interrupt. The virtio arm reads sectors through the kernel-owned
  virtio-blk `BlockDevice` free functions (`crate::virtio::block_device_info` /
  `block_device_read_blocks` / `block_device_max_sectors_per_request`); the NVMe
  arm reads through the always-built brokered read window op
  (`device_manager::nvme_brokered_io_sync_read_window_op_for_cap`) bound to the
  granted `device_mmio` handle/owner -- the same read arm the NVMe `BlockDevice`
  graduation and `readonly_fs.rs`'s NVMe arm use. The NVMe controller bring-up
  (reset/enable/IDENTIFY/CREATE I/O queue) is driven by the userspace provider and
  the shared cap-waiter `Interrupt` route, not by this backer.
- **DMA**: no new DMA surface. The storage adapter (`fat_fs::BlockStorage`)
  translates the `fatfs` byte cursor to whole sectors and reads through the
  active `BlockSource` arm's bounce-read path: the virtio bounce path, or the
  NVMe brokered window op (bounded fail-closed to one 4 KiB PRP1 page per read,
  with a manager-owned bounce page whose PRP1 never reaches userspace). There is
  no host-physical/IOVA export on either arm.
- **Fail-closed / validation rules**: capOS does not treat `fatfs` as a hostile
  FAT validator. The wrapper performs its own bounded preflight before granting
  or lazily completing the NVMe mount: BPB/device geometry must fit the active
  medium; the root directory FAT chain must end within the root byte budget and
  be cycle-free; the visible root entry count is capped at 64; each root-level
  regular file must be at most `MAX_FILE_BYTES` (64 KiB), and its FAT chain must
  end within that bounded file budget without cycles, bad clusters, or
  out-of-range cluster values. The storage adapter also clamps every read to the
  device byte capacity (`BlockStorage::read`); the virtio arm's
  `block_device_read_blocks` and the NVMe arm's window op each range-validate the
  LBA against the device/namespace geometry before issuing the request. The
  virtio arm queries the live virtio-blk geometry at construction; the NVMe
  arm's capacity comes from the IDENTIFY Namespace claim
  (`device_manager::nvme_namespace_geometry_for_cap`, NSZE + active-LBA-format
  size) -- the same IDENTIFY-derived geometry the `readonly_fs`/
  `persistent_store`/`writable_fs` NVMe arms consult -- and the deferred mount
  fails closed while that claim is unavailable or reports a non-512-byte active
  LBA format. The adapter's `Write` impl always errors.
- **QEMU-emulable vs hardware-only**: fully QEMU-emulable on both arms. The host
  image is built with real `mkfs.fat` + `mcopy`
  (`tools/mkstorage-fat-read-image.py`, a 64 MiB FAT32 image, >= 2 files, one
  multi-cluster, with deterministic FAT directory-entry timestamps on the known
  files). The virtio arm attaches it as a virtio-blk disk
  (`make run-storage-fat-read`); the combined timestamp/provenance proof runs
  that virtio arm plus the NVMe arm through
  `make run-storage-fat32-timestamp-provenance`. The NVMe arm attaches the same
  image as a pre-populated `-device nvme` namespace
  (`make run-cloud-provider-fat-read-over-nvme`), reading the multi-cluster file
  back through `Directory.open` -> `File.read` over the NVMe `BlockSource` and
  asserting the round-tripped bytes, `File.stat` timestamp values and provenance
  proof lines, plus the fail-closed mutations. No hardware-only path.

## Related

- `kernel/src/cap/fat_fs.rs` -- the FAT32 `Directory`/`File` backer, the
  `BlockSource` seam (`Virtio` / `Nvme`), the `BlockStorage` adapter, the deferred
  `FatMount`, and the `mount_root` / `mount_root_nvme` grant entry points.
- `kernel/src/cap/fat_read_over_nvme_proof.rs` -- the NVMe-arm cap-waiter
  `Interrupt` route + headline marker (`provider-fat-read-over-nvme`) for the
  NVMe proof.
- `vendor/fatfs-no_std/` -- the vendored `fatfs` no_std read parser and its
  `VENDORED_FROM.md` provenance.
- `kernel/src/cap/readonly_fs.rs` -- the `CAPOSRO1` backer the `read_only_fs_root`
  source resolves to under a plain `qemu` build, and the `BlockSource` pattern the
  FAT NVMe arm mirrors.
- `docs/proposals/real-filesystem-decision.md` -- the role-split decision and
  the phased plan this read-only FAT32 backer is part of.
