Phase 2a: FAT engine mutations + O_TRUNC (fix the overwrite corruption)

The FAT engine could create, read, write, and grow files, but never free clusters
or make directories — so overwriting a shorter file left a stale tail (a real bug:
corrupt boot-log re-flushes, and later corrupt compiler cache/.o files). This adds
the mutation half of the engine, with the corruption fix wired all the way through.

Engine (system/services/fat/engine.zig), all host-tested:
- freeChain: return a cluster chain to the pool (bounded against a corrupt cycle) —
  the shared primitive under truncate and remove.
- truncate: free the chain and zero the entry's size/first-cluster (O_TRUNC).
- createDirectory (mkdir): allocate + initialise a cluster with "." and ".." and add
  the directory entry to the parent.
- removeFile (unlink): free the chain and mark the 8.3 entry plus any preceding
  long-name entries deleted, so a reused slot can't inherit an orphaned long name.
  Refuses directories.
- createFile now shares a common addEntry helper with createDirectory.

O_TRUNC wired end to end: a truncate open-flag (vfs protocol) that the router already
forwards; runtime.fs.OpenOptions.truncate; and fat's handleOpen calls engine.truncate
on an existing file. The boot-log flush (log-flush + init) now opens with truncate, so
a shorter log on a later boot of the same stick leaves no stale tail — closing the
caveat from the boot-log work.

mkdir/unlink are engine-complete and host-tested but not yet exposed as VFS
operations / runtime.fs methods (they are new path-based ops needing router cases);
rename and the richer stat (mtime/mode, blocked on wall-clock) remain. See
docs/zig-self-hosting.md (Phase 2).

Verified: zig build, zig build test (8 engine host tests, incl. truncate, the
overwrite-no-stale-tail regression, remove, and mkdir), zig build check-fat-image, and
a sequential QEMU sweep — fat-mount, log-flush (DANOS.LOG read back at 11804 bytes,
clean, with the truncate-based final flush), vfs, vfs-client-death, usb-storage,
orderly-shutdown, initial-ramdisk, smoke — all green.
This commit is contained in:
Daniel Samson
2026-07-13 20:02:01 +01:00
parent 347a041d85
commit a32eed877d
7 changed files with 301 additions and 18 deletions
+12 -3
View File
@@ -251,6 +251,14 @@ Because `std.fs`/`std.Io` have no per-OS branches, finishing this in `runtime.os
lights up the whole file tower for the compiler at once. Environment can stay an empty
map until the kernel populates a non-empty `envp`.
**Status (Phase 2a landed):** `truncate` is done end to end — `engine.truncate` frees
the cluster chain, an O_TRUNC open flag wires it through the VFS and `runtime.fs`, and
the boot-log flush uses it (closing the stale-tail bug). The engine also has
`createDirectory` (mkdir) and `removeFile` (unlink), both host-tested (LFN-aware
deletion included). Still to do: expose mkdir/unlink as VFS operations + `runtime.fs`
methods (they are new path-based ops needing router cases); `rename`; and the richer
`stat` (blocked on wall-clock).
### Phase 3 — Single-threaded, self-linked compiler bring-up
Build the compiler with **two load-bearing flags**:
@@ -318,9 +326,10 @@ Two current decisions fall out of this roadmap:
as the concrete backing for that entry — not as a bespoke `std.Io.Writer`-only shim.
Decide it once, at the seam, and program stdout, diagnostics, and file writes all
flow through the same danos VFS/console path.
2. **The boot-log `truncate` caveat graduates from cosmetic to a real bug.** It is the
same `writeFile`-only-grows gap; on the self-hosting road it corrupts build output.
Fix it in Phase 2, not "someday."
2. **The boot-log `truncate` caveat is now fixed** (Phase 2a). It was the same
`writeFile`-only-grows gap that on the self-hosting road would corrupt build output;
`engine.truncate` + an O_TRUNC open flag now free the old chain so a shorter rewrite
leaves no stale tail, and the boot-log flush opens with it.
## Related