Replace the linear keep-earliest RAM buffer with a 512 KiB ring of framed records (log-ring.zig, host-tested): every debug_write becomes one record per payload line, stamped by the kernel with the sender's pid, task name (its binary path), level, per-boot sequence number, and monotonic timestamp. Attribution is structural — a payload cannot forge another sender's tag, and newline injection lands inside the forger's own next record. Oldest records are overwritten when full; sequence gaps make the loss countable. debug_write gains a level argument (err/warn/info/debug/raw; old two-arg callers clamp to raw). klog_read becomes a stream-offset read that fails once the cursor falls behind the ring's tail; the new klog_status (#45) returns the cursors plus the boot wall-clock anchor — what the logger service will name per-boot log directories with. The log now guards itself with a dedicated spinlock (BKL -> log lock order, never the reverse); panic paths use a bounded try-acquire and fall back to sinks-only. Serial rendering keeps the historical transcript byte-identical for kernel and legacy raw output; leveled records get a kernel-rendered name prefix. log-flush/init's interim drains start at the ring tail and write framed records until the logger service replaces them.
67 lines
3.0 KiB
Zig
67 lines
3.0 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;
|
|
// Interim (until the logger service in M-E): the stream is now framed
|
|
// records, so the file holds raw frames rather than plain text. Start at
|
|
// the ring's tail — offset 0 is gone once the ring has wrapped.
|
|
var offset: u64 = if (runtime.system.klogStatus()) |s| s.tail else 0;
|
|
const start = offset;
|
|
while (true) {
|
|
const got = runtime.system.klogRead(offset, &chunk) orelse break; // cursor lost
|
|
if (got == 0) break; // caught up
|
|
if (file.writeAll(chunk[0..got]) == null) break; // storage went away
|
|
offset += got;
|
|
}
|
|
return offset - start;
|
|
}
|
|
|
|
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);
|
|
}
|