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
fatfscrate (vendor/fatfs-no_std/rust-fatfs-0.4.0, upstream rafalh/rust-fatfs, commit pinned invendor/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 letsfatfsmount and assertsFileSystem::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 cleanmkfs.fatimage keeps the BPB dirty flag clear, so thefatfsDrop/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 firstDirectory.list/open(theFatMount::Deferred->Readytransition inFatMount::ensure), because the brokered NVMe controller is brought up by the userspace provider after the grant resolves – mirroringreadonly_fs::mount_root_nvme. - Directory listing (
Directory.list @1):fatfs::Dir::iter()walks the root directory entries; capOS copies each entry’sfile_name()(LFN or case-normalized 8.3),len(), andis_dir()into theDirEntryreply (FatFsDirectoryCap::collect_entries). The volume-label entry is skipped by the iterator. capOS bounds the exposed root toMAX_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 aFilecap recording the name, size, and bounded timestamp metadata. The write-implyingCREATE/TRUNCATEflag bits, nested (/-bearing) paths, and files larger thanMAX_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 tolengthbytes viafatfs::Read, walking the cluster chain. The covering read is clamped to end-of-file.fatfsresolves 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-entrycreated/modifiedtimestamps 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 to0, the schema’s unstamped/unsupported value, rather than inventing trusted time. TheFile.statABI remains schema-stable and carries timestamp values only; proof logs label the source asmetadata_provenance=fat-directory-entry,clock_provenance=none, andtrusted_clock=false. FAT modification time has two-second granularity; FAT creation time has an optional high-resolution byte (0..=199in10msunits), 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 infatfsare never reached.
3. capOS mapping
- Binding (kernel-owned read; behind
read_only_fs_root): the FAT32 rootDirectoryis granted through the existingKernelCapSource::ReadOnlyFsRootsource. Under a plainqemubuild that source resolves to the capOS-authoredCAPOSRO1backer (cap::readonly_fs); understorage_fat_readit resolves to this FAT backer’s virtio arm (cap::fat_fs::mount_root); undercloud_fat_read_over_nvme_proofit resolves to the FAT backer’s NVMe arm (cap::fat_fs::mount_root_nvme, bound to the livedevice_mmiohandle the production grant source staged – thedevice_mmiogrant must precederead_only_fs_rootin the manifest). All three are wired in theKernelCapSource::ReadOnlyFsRootarms ofkernel/src/cap/mod.rs(boot PID 1) andkernel/src/cap/process_spawner.rs(spawned services). Selecting the backer by feature mirrors how the same source already selects itsVirtiovs NVMe backend, so no newKernelCapSourceand noschema/capos.capnpchange is needed – theDirectory/Filecontract already carries every field. Directory/Filecap surface:FatFsDirectoryCap/FatFsFileCapimplementDirectory.list/open+File.read/stat/close; every mutating method fails closed. Read-only is structural (distinctCapObjecttypes that expose no mutation), not a rights flag.openmints theFileresult capCopy/SameSession, so a holder can forward a read view to a same-session spawn child without conferring write authority (the same posturereadonly_fs/installable_imageuse).- 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
BlockDevicefree 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 granteddevice_mmiohandle/owner – the same read arm the NVMeBlockDevicegraduation andreadonly_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-waiterInterruptroute, not by this backer. - DMA: no new DMA surface. The storage adapter (
fat_fs::BlockStorage) translates thefatfsbyte cursor to whole sectors and reads through the activeBlockSourcearm’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
fatfsas 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 mostMAX_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’sblock_device_read_blocksand 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 thereadonly_fs/persistent_store/writable_fsNVMe arms consult – and the deferred mount fails closed while that claim is unavailable or reports a non-512-byte active LBA format. The adapter’sWriteimpl 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 throughmake run-storage-fat32-timestamp-provenance. The NVMe arm attaches the same image as a pre-populated-device nvmenamespace (make run-cloud-provider-fat-read-over-nvme), reading the multi-cluster file back throughDirectory.open->File.readover the NVMeBlockSourceand asserting the round-tripped bytes,File.stattimestamp values and provenance proof lines, plus the fail-closed mutations. No hardware-only path.
Related
kernel/src/cap/fat_fs.rs– the FAT32Directory/Filebacker, theBlockSourceseam (Virtio/Nvme), theBlockStorageadapter, the deferredFatMount, and themount_root/mount_root_nvmegrant entry points.kernel/src/cap/fat_read_over_nvme_proof.rs– the NVMe-arm cap-waiterInterruptroute + headline marker (provider-fat-read-over-nvme) for the NVMe proof.vendor/fatfs-no_std/– the vendoredfatfsno_std read parser and itsVENDORED_FROM.mdprovenance.kernel/src/cap/readonly_fs.rs– theCAPOSRO1backer theread_only_fs_rootsource resolves to under a plainqemubuild, and theBlockSourcepattern 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.