M9: user-space VFS server + client file API (open/read/write/stat + stdio)

The payoff milestone: files are served by a user-space process, reached over
IPC — the kernel never sees a path or an fd.

- lib/vfs_proto.zig: the VFS wire protocol (Op, Request/Reply fixed header +
  inline payload, Stat), shared by client and server; one message <= MSG_MAX.
- lib/ipc.zig: replyWait() — the server-side dual-return stub (length in rax,
  badge in rdx via a "+{rdx}" read-write operand), deferred from M7.
- sbin/vfs.zig: the real VFS server — an in-heap ramfs (open creates a node)
  with an IPC_ReplyWait dispatch loop serving open/read/write/stat/close.
  Registers its endpoint under the well-known vfs id at startup.
- lib/unistd.zig: POSIX-style client API in rt — a per-process fd table +
  open/close/read/write/lseek/stat, each an IPC_Call to the VFS. The kernel
  knows nothing of fds; the table lives here.
- lib/stdio.zig: C stdio over unistd — FILE + fopen/fclose/fread/fwrite/
  fseek/ftell/rewind/feof/ferror/fputs/fputc/fgetc (unbuffered for now).
- sbin/vfstest.zig: a client that opens/writes/seeks/reads a file and only
  heartbeats "vfstest: ok" if the round trip matched. Packed in the initrd.
- New `vfs` test drives it end to end. initrd test relaxed to generic
  liveness. Suite 31/31.
This commit is contained in:
Daniel Samson
2026-07-09 07:48:24 +01:00
parent 750a73f050
commit b0f894f50c
10 changed files with 583 additions and 14 deletions
+51 -6
View File
@@ -107,6 +107,8 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
processTest(boot_info);
} else if (eql(case, "initrd")) {
initrdTest(boot_info);
} else if (eql(case, "vfs")) {
vfsTest(boot_info);
} else if (eql(case, "poweroff")) {
powerTest(.off);
} else if (eql(case, "reboot")) {
@@ -970,17 +972,60 @@ fn initrdTest(boot_info: *const BootInfo) void {
}
check("every initrd binary spawned", spawned == rd.count);
// Wait for the spawned program(s) to heartbeat (the vfs stub sleeps ~1 s).
// Wait for the spawned programs to run and make syscalls (they write + sleep).
sched.setPriority(1);
const deadline = arch.millis() + 8000;
while (process.write_count < 2 and arch.millis() < deadline) sched.yield();
sched.setPriority(4);
const prefix = "vfs: alive";
const beat_ok = process.write_len >= prefix.len and eql(process.write_buf[0..prefix.len], prefix);
check("an initrd process heartbeats (>=2)", process.write_count >= 2);
check("heartbeat text arrived intact", beat_ok);
check("heartbeats came from user mode (CPL 3)", process.write_from_user);
check("initrd processes ran and made syscalls (>=2)", process.write_count >= 2);
check("syscalls came from user mode (CPL 3)", process.write_from_user);
result();
}
/// The full VFS path: spawn the user-space VFS server and a client from the
/// initrd. The client opens a file through the rt file API, writes, seeks, reads
/// it back, and — only if the round trip matched — heartbeats "vfstest: ok". So
/// seeing that marker proves client open/write/read reached the server over IPC
/// and came back correct. (The client retries until the server registers.)
fn vfsTest(boot_info: *const BootInfo) void {
log("DANOS-TEST-BEGIN: vfs\n", .{});
if (boot_info.initrd_len == 0) {
check("bootloader handed over an initrd", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(danos.physToVirt(boot_info.initrd_base)))[0..boot_info.initrd_len];
const rd = initrd.Reader.init(image) orelse {
check("initrd image is valid", false);
result();
return;
};
process.write_count = 0;
process.write_from_user = false;
var i: u32 = 0;
while (i < rd.count) : (i += 1) {
const item = rd.entry(i) orelse continue;
process.spawnProcess(item.blob, 4) catch |err| {
log("DANOS-VFS-ERR: {s}: {s}\n", .{ item.name, @errorName(err) });
};
}
// Wait for the client's success heartbeat (it round-trips, then beats ~1/s).
const prefix = "vfstest: ok";
sched.setPriority(1);
const deadline = arch.millis() + 10000;
while (arch.millis() < deadline) {
if (process.write_len >= prefix.len and eql(process.write_buf[0..prefix.len], prefix) and process.write_count >= 2) break;
sched.yield();
}
sched.setPriority(4);
const ok = process.write_len >= prefix.len and eql(process.write_buf[0..prefix.len], prefix);
check("client completed the VFS round trip (open/write/read matched)", ok);
check("the round trip ran repeatedly (server stays up)", process.write_count >= 2);
check("client syscalls came from user mode (CPL 3)", process.write_from_user);
result();
}