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:
+51
-6
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user