The VFS routes operations through each mount’s delegate. The live system uses a ramdisk; FAT volumes appear under /mount/fat<ID>, and an installed system uses its boot volume as /.

FAT volume control

libstorage requests mount, flush and unmount through the storage manager. Its administrative API separates partition-table changes from filesystem formatting. FAT32 formatting initializes an unmounted volume while preserving its partition table and neighboring volumes. Unmount rejects open files, active transactions and the boot root.

See Installation for the disk layout, copy/verification sequence and the handoff from Limine bootstrap files to the FAT root filesystem.

Task delegates

Driver threads serve filesystems through ghost/filesystem/delegate.h and the functions in ghost/filesystem.h:

  • g_fs_delegate_register mounts /mount/NAME and returns the mount ID and a shared g_fs_delegate_transaction_storage control page.

  • g_fs_delegate_node_create stages child nodes during lookup, creation or directory refresh. Successful completion publishes them; failure discards them.

  • g_fs_delegate_transaction_complete acknowledges a request.

  • g_fs_delegate_unregister detaches an owned mount between transactions.

Physical node IDs are opaque 64-bit values scoped to the delegate. An existing name retains its type and ID; replacing a deleted file requires a new ID. Lookup returns the child’s virtual ID in data.lookup.result_node_id.

Requests and shared memory

One transaction is active per control page. The serving thread receives g_fs_delegate_request messages and must check G_FS_DELEGATE_MESSAGE_SENDER and the storage version, size, mount, operation and 64-bit transaction ID against the shared header. Filesystem transaction IDs are separate from IPC IDs.

Read/write data uses a separate 4 KiB mapped_buffer. Transfers can be short; clients must loop as needed. The kernel owns both shared pages, and g_unmap cannot release them. They are not DMA buffers.

The driver writes result fields before completing the transaction. The completion syscall’s status is separate from the operation’s status. SUCCESSFUL or STALE_TRANSACTION acknowledges that the driver has stopped using the mapping; other completion errors leave it outstanding.

Cancellation and lifetime

Requests have a 15-second deadline, including queue time. Timeout or caller termination fails the operation and isolates the caller’s buffer. Shared pages remain reserved until the serving thread acknowledges cancellation or exits. Unregister returns BUSY while a transaction remains unacknowledged.

Serving-task exit detaches the mount and wakes clients. Old descriptors retain the retired backend identity and cannot follow a replacement mount. The serving thread must not access its own mount while handling a request.

Retired VFS nodes remain tombstones until reboot. Limits are 64 active mounts, 1,024 registrations per boot and 16,384 staged or retained delegate nodes.

Mutations

Protocol version 2 supports CREATE, RESIZE, REMOVE and FLUSH with g_fs_mutation_status results. Applications use g_open_f with CREATE, g_create_directory, g_unlink, g_unlink_directory, g_truncate and g_sync. CREATE|EXCLUSIVE rejects an existing name; writes and truncation require a writable descriptor. Append serializes EOF selection with writes/truncation on the same node. Extending past EOF zero-fills the gap.

FAT file data can remain in the disk cache after a successful write. Use fsync() or g_sync for durability; volume flush and unmount also flush pending writes.

Cancellation does not undo writes already accepted by a backend. Drivers must not retry uncertain mutations. A cancelled successful OPEN releases its private open reference after STALE_TRANSACTION; completed mutations retain their effects.