Storage: from one filesystem to several
Where this starts
There is no filesystem abstraction. kernel/arch/x86_64/arch_hw.c calls
vibeos_x86_64_fat_open, fat_read_at, fat_list, fat_write_file and their
siblings directly, from twenty places across the syscall layer. In the kernel
as it stands, "filesystem" and "FAT" are the same word.
There is also no block cache: every read reaches the device. FAT survives that because its structures are small and mostly sequential. Nothing more complex would.
So the work is not "add filesystems". It is "introduce the layer that makes a second filesystem possible", and then add filesystems as clients of it. The order below is chosen so that each stage is verifiable on its own, and so that the abstraction is designed against two real clients rather than one imagined one.
Stage 1 - a block layer with a cache
A device is a function pair: read this sector into this buffer, write this buffer to this sector. Nothing above it should know it is virtio, and nothing in it should know what the bytes mean.
On top of that, a cache of fixed-size blocks with write-back and an explicit flush. Write-back rather than write-through because the point is to make metadata updates affordable; explicit flush because durability has to be something a filesystem asks for at a moment it chooses, not a side effect.
Portable and host-testable: a fake device backed by an array exercises
eviction, dirty write-back, flush ordering and read-after-write without
hardware. That matters - kernel/fs/fat_chain.c already showed that moving
logic out of the arch file is what let its bug be found by a test sweep rather
than by a boot.
Stage 2 - the VFS interface
Two objects, kept deliberately small:
- a mounted filesystem: given a path, resolve it to a node; list a directory; create and remove
- a node: read at an offset, write at an offset, report size and kind
Everything the syscall layer needs today - open, read, write, close,
getdents64, unlink, mkdir, fstat, newfstatat, execve - is expressed
in those terms. The twenty direct calls in arch_hw.c become calls through a
mount table.
The interface gets designed here and proven in stage 3, but it is not considered settled until stage 5, when something that is not FAT has to fit into it.
Stage 3 - FAT becomes a client
Port the existing driver behind the interface with no behaviour change. The boot gate is the check: BusyBox reads files, lists directories and execs programs, so if FAT still works through the new layer, the layer carries the existing contract. Nothing new is added here on purpose - a refactor and a feature in the same step means neither is verified.
Stage 4 - partition tables
MBR and GPT. Today the volume is mounted directly, which works only because QEMU hands us a bare FAT image. A real disk has a partition table, and every filesystem after this one needs to be found before it can be mounted.
Stage 5 - ext2, read-only
The first client that is not FAT, and the reason it comes before the easier ones: it has a genuinely different shape - superblock, block groups, inodes, indirect blocks - so it is what tells us whether the stage 2 interface was designed or merely extracted from FAT. Expect to change the interface here. That is the point.
Stage 6 - the cheap ones
ISO9660 and exFAT, in that order. ISO9660 is small and immediately useful: it is what an installation CD carries. exFAT is conceptually FAT with a 32-bit table and an allocation bitmap, and its specification has been published since 2019.
Stage 7 - NTFS, read-only
Feasible and worth doing: everything is a file, including $MFT, and the
records have a regular structure. The work is in sector fixups, resident versus
non-resident attributes, and run lists. Read-only keeps it bounded.
Stage 8 - durability (roadmap M16)
Ordered metadata updates and a journal, with the exit criterion already written in the roadmap: cutting power during a write must not corrupt the volume. This is the same work as M16, not a separate track, and the block cache from stage 1 is where the ordering is enforced.
What is deliberately not planned
NTFS write. Not because it is uninteresting, but because it is a different
size of problem: $LogFile, $Bitmap, an $MFT that grows, compression,
ACLs, and formats Microsoft never fully documented. Reliable NTFS writing on
Linux arrived after two decades and by way of a driver donated by a company
that did it professionally. If a concrete need appears, it gets planned then,
on evidence.
How each stage is verified
Stages 1, 5, 6 and 7 are mostly parsing and bookkeeping, so they belong in
kernel/fs/ as portable code with host tests - the same arrangement that let
fat_chain.c be swept exhaustively against a reference implementation.
Stages 2, 3 and 4 change what the kernel does at runtime, so the boot gate is the check: it already asserts that a real program reads a real file from the volume.
Stage 8 needs a test that does not exist yet: kill the machine mid-write and mount the result. That test is the milestone, more than the journal is.
Status
All eight stages are built. Stage 8 delivered the journal, the device-level barrier it needs, and the power-cut sweep; what it did not deliver is a filesystem that routes its metadata through the journal, so M16's exit criterion is met for the mechanism and not yet for a volume. That connection is the next piece of storage work.
Documentation debt, settled
The list this section used to carry has been worked through: the two roadmap
files were deleted, the filesystem and progress documents rewritten around the
layer that now exists, and docs/graphics.md and docs/process_semantics.md
written for the two areas that had code and no document.