Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, seeks 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.
  • 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.