Completes Phase 2: the FAT filesystem now stamps and reports a real modification
time, built on the Phase 2d(i) kernel wall-clock. This is the last stat field the
compiler's build cache needs to reason about (source vs cached output).
- on-disk.zig: fatToEpoch / epochToFatDateTime convert between the two 16-bit DOS
date/time fields and Unix epoch seconds (UTC — FAT has no timezone). Host-tested
round-trip + an absolute check (1577836800 == 2020-01-01).
- engine: a settable current_time_epoch that create/write stamp into the entry's
write (and creation) date/time; Node/Listing gained an mtime decoded from those
fields on read. Host test: a create stamps the mtime, read back through resolve
and listEntry.
- vfs protocol FileStatus + runtime.fs.Attributes gained an mtime field; the fat
server sets current_time_epoch from runtime.system.wallClock() per request and
returns mtime from stat. The flat ramfs reports 0 (it has no timestamps).
- fat-test reads the created file's mtime through stat and checks it is a real
current time, behind a new `fat-mtime` QEMU case.
Verified against the host: the guest stamped mtime 1783971676 while the host clock
was 1783971680 (boot+test lag) — the file's mtime is real current time. zig build,
zig build test (the epoch<->DOS conversions + the engine mtime test),
zig build check-fat-image, and a sequential QEMU sweep — fat-mount, fat-mutations,
fat-rename, fat-mtime, vfs, vfs-client-death, log-flush, orderly-shutdown,
initial-ramdisk, smoke, wall-clock, usb-storage — all green. mode/inode remain.
Completes the Phase 2 FAT mutation set (truncate, mkdir, unlink, rename).
- engine: rename(dir, old_name, new_name) rewrites an existing entry's 8.3 name in
place within the same directory. Refuses a missing source, a non-8.3 target, or a
name that already exists; drops any long-name entries on the old file (it takes
its new 8.3 name), LFN-aware like removeFile. Cross-directory and long-name-
preserving rename are noted limitations. Host-tested (rename keeps contents;
collision, non-8.3, and missing-source are refused).
- vfs protocol: a `rename` operation whose payload is old-path, a 0x00 separator,
then new-path.
- VFS router: a forwardRename helper + a `.rename` case that requires both paths
under the same mount (cross-filesystem rename is refused) and forwards the
mount-relative old+new.
- fat server: a `.rename` handler that requires the same parent directory and calls
engine.rename.
- runtime.fs: rename(old_path, new_path).
- fat-test now renames the file it created (before removing it) and asserts the old
name is gone, behind a new `fat-rename` QEMU case.
Verified: zig build, zig build test (the engine rename unit test), zig build
check-fat-image, and a sequential QEMU sweep — fat-mount, fat-mutations, fat-rename,
vfs, vfs-client-death, log-flush, orderly-shutdown, initial-ramdisk, smoke — green.
The engine gained mkdir/unlink in Phase 2a; this exposes them as first-class
filesystem operations so programs can use them.
- vfs protocol: two new path-based operations, mkdir and unlink (appended, so
existing opcodes/offsets are unchanged).
- VFS router: a forwardPath helper relays a path-based op under a mount to its
backend; the mkdir/unlink cases forward to the mounted filesystem (the flat
ramfs refuses them — it has no directories).
- fat server: mkdir -> engine.createDirectory, unlink -> engine.removeFile, each
resolving the parent via a shared splitParent helper (also used by open-create).
- runtime.fs: makeDirectory(path) and remove(path).
- fat-test now exercises the whole path — mkdir /mnt/usb/TESTDIR, create + write +
read a file inside it, then remove it — behind a new `fat-mutations` QEMU case.
Verified: zig build, zig build test, zig build check-fat-image, and a sequential
QEMU sweep — fat-mount, fat-mutations (mkdir/write/read/unlink through the mount),
vfs, vfs-client-death, log-flush, orderly-shutdown, initial-ramdisk, smoke — green.
The first step of the Zig self-hosting roadmap (docs/zig-self-hosting.md): give
danos programs a danos-native file API and remove the premature POSIX compatibility
shim. This also resolves the earlier misplacement of a full-write helper into the
compat layer — that behaviour now lives natively in runtime.fs.File.writeAll.
- library/runtime/fs.zig: the danos-native file client over the VFS (open/read/
write/writeAll/seekTo/attributes/close, directory listing, mount). Handles are
*values* — a File/Directory owns its VFS node id and byte offset — so there is no
per-process fd table or descriptor limit, unlike the POSIX fd model the shim
emulated. This is where the operations that later become std.os.danos are staged.
- Retire library/posix/ (unistd, stdio): only five call sites used it, all file
operations, all migrated to runtime.fs — fat (mount), the vfs-test and fat-test
clients, and init/log-flush (the boot-log flush). stdio was already dead.
- build.zig: drop the posix module, its addUserBinary parameter, the per-binary
import, and the ~26 call-site arguments.
- Docs: the VFS protocol's client is now runtime.fs; the docs index and
coding-standards note posix is retired and the foreign-ABI naming exception now
applies to the future std.os.danos seam; the process-lifecycle note points the
future musl layer at that same seam rather than the deleted directory.
Deferred by design (see the roadmap): the C-ABI runtime.os errno seam is built at
fork time (its shape must match std/os/danos.zig); truncate/mkdir/rename are
Phase 2; stdio-byte fds and cwd are later slices.
Verified: zig build, zig build test, zig build check-fat-image, and a sequential
QEMU sweep — vfs, vfs-client-death (the park/hold-handle path), fat-mount, log-flush,
orderly-shutdown, initial-ramdisk (log-flush silent in the bare sweep), smoke, init,
usb-storage, device-manager — all green.
Add a FAT12/16/32 filesystem the VFS mounts at /mnt/usb, reading and writing a
USB stick through the block device. Verified end to end under QEMU: the fat
server mounts the volume, the VFS routes /mnt/usb to it, and a client lists the
root and reads a file (the ELF magic of /mnt/usb/system/kernel).
- engine.zig: the FAT engine over a BlockDevice interface — mount (a bare FAT or,
as QEMU's VVFAT and most real sticks present it, an MBR-partitioned disk), FAT
chain walk (12/16/32), cluster allocation, directory traversal with long-name
read, and file read / write / create. Host-tested against a RAM-backed FAT16
image (create, cluster-spanning write, mid-file overwrite, read-back, list).
- on-disk.zig: the align(1) boot-sector / directory / long-name / FSInfo structs
and the cluster-count FAT-type detection.
- fat.zig: the server — wraps the .block device (a DMA bounce buffer) in a
BlockDevice, mounts the FAT, serves the vfs-protocol as a backend, and mounts
itself into the VFS at /mnt/usb. Spawned by init as a boot service.
- runtime.block: the block-device client (geometry / read / write by physical
address, so whole sectors never cross IPC).
- Raise the kernel service-name registry (maximum_services) 8 -> 16: it is
indexed directly by ServiceId, and fat = 8 was being rejected, so the fat
server exited before registering.
- VFS: an absolute path with no matching mount is now not-found rather than
silently created in the flat ramfs — so /mnt/usb fails cleanly until mounted.
Tests: fat-mount (the full stack: block -> FAT -> VFS mount -> list + file read)
passes; host units cover the engine and on-disk structs; the vfs, shutdown, and
USB regression suite stays green (10/10).