kernel: VFS root — mount table, /system from the initrd, fs syscalls

system/kernel/vfs.zig is the resolve+redirect router: fs_resolve (#46)
walks the kernel mount table; a path under the kernel-backed /system
mount (the initrd, seeded by setInitialRamdisk with a derived directory
table) yields a permanent stateless node token served by fs_node (#47)
— read/status/readdir with copy-out, initrd reads lock-free — while a
path under a userspace mount yields the backend's endpoint (installed
in the caller's table, DEDUPLICATED so 16 slots can't be exhausted by
repeated resolves) plus the rewritten mount-relative path; the caller
then speaks the unchanged vfs-protocol rendezvous directly. The kernel
never blocks on a userspace filesystem, holds no open-file state, and
refuses create-intent on the immutable /system.

fs_mount (#48) is the syscall form of the old router's op-6 cap-pass
(possession of the backend handle is the capability; an optional
rewrite prefix maps the mount into the backend's namespace — how /var
will reach the flash volume); fs_unmount (#49) removes one. A dead
backend's mount clears lazily on resolve.

Dormant this milestone: the userspace vfs still serves runtime.fs
unchanged; the kvfs QEMU case covers the kernel side (resolution, ELF
magic read-through, /system listing, read-only + unknown refusals)
until the M-G cutover exercises the syscalls end-to-end.
This commit is contained in:
Daniel Samson
2026-07-21 16:15:05 +01:00
parent 127ea2dad9
commit 7c5645fe48
6 changed files with 619 additions and 0 deletions
+61
View File
@@ -27,6 +27,7 @@ const sync = @import("sync.zig");
const process = @import("process.zig");
const initial_ramdisk = @import("initial-ramdisk");
const kernel_log = @import("log.zig");
const kernel_vfs = @import("vfs.zig");
/// Formatted test-marker write. Goes through the kernel log (not straight to
/// serial): the log lock is what keeps marker lines from interleaving with
@@ -209,6 +210,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
initialRamdiskTest(boot_information);
} else if (eql(case, "vfs")) {
vfsTest(boot_information);
} else if (eql(case, "kvfs")) {
kernelVfsTest(boot_information);
} else if (eql(case, "input")) {
inputTest(boot_information);
} else if (eql(case, "iopass")) {
@@ -3073,6 +3076,64 @@ fn bundledInit(boot_information: *const BootInformation) ?[]const u8 {
return item.blob;
}
/// The kernel VFS root (M-F): resolve initrd paths to node tokens, read an ELF
/// header through nodeRead, enumerate /system's derived directory table, and
/// verify the read-only + unknown-path refusals. Pure kernel-side — the
/// syscall surface gets its end-to-end coverage when runtime.fs cuts over.
fn kernelVfsTest(boot_information: *const BootInformation) void {
log("DANOS-TEST-BEGIN: kvfs\n", .{});
if (boot_information.initial_ramdisk_len == 0) {
check("bootloader handed over the initial_ramdisk", false);
result();
return;
}
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
process.setInitialRamdisk(image); // also seeds the kernel VFS /system mount
// A file resolves to a kernel node token; its status and bytes are served.
const resolved = kernel_vfs.resolvePath("/system/services/init", false);
const is_file = resolved == .kernel_node;
check("/system/services/init resolves to a kernel node", is_file);
if (is_file) {
const status = kernel_vfs.nodeStatus(resolved.kernel_node);
check("its status is a non-empty regular file", status != null and status.?.kind == abi.file_kind_regular and status.?.size > 0);
var header: [4]u8 = undefined;
const n = kernel_vfs.nodeRead(resolved.kernel_node, 0, &header) orelse 0;
check("its first bytes are an ELF magic", n == 4 and header[0] == 0x7f and header[1] == 'E' and header[2] == 'L' and header[3] == 'F');
}
// Directories resolve and enumerate: /system lists services/drivers/tests.
const root_directory = kernel_vfs.resolvePath("/system", false);
check("/system resolves to a directory node", root_directory == .kernel_node);
var saw_services = false;
var saw_drivers = false;
var saw_files_in_services = false;
if (root_directory == .kernel_node) {
var cursor: u64 = 0;
var name: [64]u8 = undefined;
while (kernel_vfs.nodeReaddir(root_directory.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
if (eql(name[0..entry.name_len], "services")) saw_services = true;
if (eql(name[0..entry.name_len], "drivers")) saw_drivers = true;
}
}
check("readdir /system yields services and drivers", saw_services and saw_drivers);
const services = kernel_vfs.resolvePath("/system/services", false);
if (services == .kernel_node) {
var cursor: u64 = 0;
var name: [64]u8 = undefined;
while (kernel_vfs.nodeReaddir(services.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
if (eql(name[0..entry.name_len], "init")) saw_files_in_services = true;
}
}
check("readdir /system/services yields init", saw_files_in_services);
// Refusals: unknown paths, and create-intent on the immutable initrd.
check("an unknown path does not resolve", kernel_vfs.resolvePath("/system/services/no-such", false) == .not_found);
check("an unmounted absolute path does not resolve", kernel_vfs.resolvePath("/elsewhere", false) == .not_found);
check("create on /system is refused (read-only)", kernel_vfs.resolvePath("/system/services/new-file", true) == .not_found);
result();
}
fn spawnNamed(rd: initial_ramdisk.Reader, name: []const u8) bool {
var i: u32 = 0;
while (i < rd.count) : (i += 1) {