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.
68 lines
2.8 KiB
Zig
68 lines
2.8 KiB
Zig
//! system/services/log-flush — a one-shot that copies the kernel's in-memory
|
|
//! diagnostic log to a file on the mounted USB FAT volume, so the boot log
|
|
//! survives to be read on another machine. On a headless or real board there is
|
|
//! no host capturing serial, so without this the log is lost at power-off; this
|
|
//! is the on-disk equivalent of QEMU's `-serial file:`.
|
|
//!
|
|
//! It reads the whole kernel log back through `klog_read` (the RAM sink in
|
|
//! system/kernel/log.zig) and writes it to /mnt/usb/DANOS.LOG. The name is 8.3
|
|
//! (FAT short-name rule: base <= 8, extension <= 3) and lives at the mount root
|
|
//! (there is no mkdir on the FAT path yet). init spawns this once the boot
|
|
//! services are up; init itself repeats the flush at shutdown for a fuller log.
|
|
//!
|
|
//! If no USB volume is mounted — no stick, or the initial-ramdisk sweep that
|
|
//! spawns every bundled binary bare with no VFS — it waits briefly, then exits
|
|
//! silently, deranging no other test's output.
|
|
|
|
const std = @import("std");
|
|
const runtime = @import("runtime");
|
|
const fs = runtime.fs;
|
|
|
|
const log_path = "/mnt/usb/DANOS.LOG";
|
|
|
|
/// Copy the whole kernel log to the open file, looping klog_read -> write until
|
|
/// the log is exhausted. Returns the number of bytes written.
|
|
fn drainKernelLog(file: *fs.File) usize {
|
|
var chunk: [4096]u8 = undefined;
|
|
var offset: usize = 0;
|
|
while (true) {
|
|
const got = runtime.system.klogRead(offset, &chunk);
|
|
if (got == 0) break; // reached the end of the accumulated log
|
|
if (file.writeAll(chunk[0..got]) == null) break; // storage went away
|
|
offset += got;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
pub fn main() void {
|
|
// Wait for the fat server to mount /mnt/usb (it must bring up the whole USB
|
|
// storage chain first, so it races us at boot). Bounded: if the mount never
|
|
// appears — no volume, or the no-VFS ramdisk sweep — give up silently.
|
|
var ready = false;
|
|
var tries: u32 = 0;
|
|
while (tries < 1400) : (tries += 1) {
|
|
if (fs.openDirectory("/mnt/usb")) |directory| {
|
|
var dir = directory;
|
|
dir.close();
|
|
ready = true;
|
|
break;
|
|
}
|
|
runtime.system.sleep(50);
|
|
}
|
|
if (!ready) return; // /mnt/usb never became available — nothing to persist to
|
|
|
|
// Truncate on open: each flush replaces the file, so a shorter log on a later
|
|
// boot of the same stick leaves no stale tail from a previous, longer one.
|
|
var file = fs.open(log_path, .{ .create = true, .truncate = true }) orelse return;
|
|
const written = drainKernelLog(&file);
|
|
file.close();
|
|
|
|
var line: [96]u8 = undefined;
|
|
_ = runtime.system.write(std.fmt.bufPrint(&line, "log-flush: wrote {d} bytes to {s}\n", .{ written, log_path }) catch return);
|
|
}
|
|
|
|
pub const panic = runtime.panic;
|
|
comptime {
|
|
_ = &runtime.start._start; // pull the runtime entry shim into the image
|
|
}
|