reorg: move test fixtures to test/system/services (source + boot volume)
The 11 QEMU-suite fixtures lived mixed into system/services/ with their binaries bundled at /system/tests/<name>. Now the repo path is the boot path, like every real service: test/system/services/<name>. fat-test moves out of the fat server's directory into its own; display-demo stays a boot service. - kernel VFS: setInitialRamdisk derives one read-only initrd mount per top-level tree named by the ramdisk entry paths (/system, /test), registers ancestors generically with self-parented roots, and refuses backend shadowing of any initrd tree - EFI loader: the fallback walk also enumerates \test (optional — a volume without fixtures still boots); manifest and capsule unchanged - path literals: vfs-test self-open + create probe, process-test process_enumerate matches, the args-echo argv[0] expectation; the kvfs case now covers the /test root end to end - docs: DFHS /test rows, tree diagrams, loader prose, and the location convention gain the third home; fixed the input-source link 100/100 QEMU cases pass.
This commit is contained in:
@@ -187,7 +187,7 @@ test "wrap reclaims whole records and keeps tail on a boundary" {
|
||||
while (i < 200) : (i += 1) {
|
||||
var message: [64]u8 = undefined;
|
||||
const m = std.fmt.bufPrint(&message, "line {d} padding padding padding", .{i}) catch unreachable;
|
||||
_ = ring.append(1, "/system/tests/writer", .info, i, m, false);
|
||||
_ = ring.append(1, "/test/system/services/writer", .info, i, m, false);
|
||||
}
|
||||
try std.testing.expect(ring.head - ring.tail <= 4096);
|
||||
|
||||
|
||||
+24
-7
@@ -3105,7 +3105,7 @@ fn argsTest(boot_information: *const BootInformation) void {
|
||||
while (process.write_count < 1 and architecture.millis() < deadline) scheduler.yield();
|
||||
scheduler.setPriority(4);
|
||||
|
||||
const expected = "args: /system/tests/args-echo alpha beta-42\n";
|
||||
const expected = "args: /test/system/services/args-echo alpha beta-42\n";
|
||||
const echoed = process.write_len == expected.len and eql(process.write_buffer[0..process.write_len], expected);
|
||||
if (!echoed and process.write_len > 0) log("DANOS-ARGS: got \"{s}\"\n", .{process.write_buffer[0..process.write_len]});
|
||||
check("argv arrived intact (argv[0] = name, argv[1..] = spawn arguments)", echoed);
|
||||
@@ -3116,7 +3116,7 @@ fn argsTest(boot_information: *const BootInformation) void {
|
||||
/// Spawn the initial_ramdisk binary named `name` as a ring-3 process. Returns false if it
|
||||
/// isn't in the image or fails to load.
|
||||
/// The init ELF image out of the initial_ramdisk — init rides the table like
|
||||
/// every other binary since the loader packs the whole /system tree.
|
||||
/// every other binary since the loader packs the whole boot tree.
|
||||
fn bundledInit(boot_information: *const BootInformation) ?[]const u8 {
|
||||
if (boot_information.initial_ramdisk_len == 0) return null;
|
||||
const image = @as([*]const u8, @ptrFromInt(boot_handoff.physicalToVirtual(boot_information.initial_ramdisk_base)))[0..boot_information.initial_ramdisk_len];
|
||||
@@ -3137,7 +3137,7 @@ fn kernelVfsTest(boot_information: *const BootInformation) void {
|
||||
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
|
||||
process.setInitialRamdisk(image); // also seeds the kernel VFS /system and /test mounts
|
||||
|
||||
// A file resolves to a kernel node token; its status and bytes are served.
|
||||
const resolved = kernel_vfs.resolvePath("/system/services/init", false);
|
||||
@@ -3151,21 +3151,22 @@ fn kernelVfsTest(boot_information: *const BootInformation) void {
|
||||
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.
|
||||
// Directories resolve and enumerate: /system lists services/drivers.
|
||||
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_stray_in_root = 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;
|
||||
if (eql(name[0..entry.name_len], "services")) saw_services = true else if (eql(name[0..entry.name_len], "drivers")) saw_drivers = true else saw_stray_in_root = true;
|
||||
}
|
||||
}
|
||||
check("readdir /system yields services and drivers", saw_services and saw_drivers);
|
||||
check("readdir /system yields nothing else (no /test leakage)", !saw_stray_in_root);
|
||||
const services = kernel_vfs.resolvePath("/system/services", false);
|
||||
if (services == .kernel_node) {
|
||||
var cursor: u64 = 0;
|
||||
@@ -3176,10 +3177,26 @@ fn kernelVfsTest(boot_information: *const BootInformation) void {
|
||||
}
|
||||
check("readdir /system/services yields init", saw_files_in_services);
|
||||
|
||||
// Refusals: unknown paths, and create-intent on the immutable initrd.
|
||||
// The /test tree is a second initrd root, resolvable and enumerable like
|
||||
// /system (the fixtures live at /test/system/services, mirroring the repo).
|
||||
const test_root = kernel_vfs.resolvePath("/test", false);
|
||||
check("/test resolves to a directory node", test_root == .kernel_node);
|
||||
check("/test/system/services/args-echo resolves to a kernel node", kernel_vfs.resolvePath("/test/system/services/args-echo", false) == .kernel_node);
|
||||
var saw_test_subtree = false;
|
||||
if (test_root == .kernel_node) {
|
||||
var cursor: u64 = 0;
|
||||
var name: [64]u8 = undefined;
|
||||
while (kernel_vfs.nodeReaddir(test_root.kernel_node, cursor, &name)) |entry| : (cursor += 1) {
|
||||
if (eql(name[0..entry.name_len], "system")) saw_test_subtree = true;
|
||||
}
|
||||
}
|
||||
check("readdir /test yields system", saw_test_subtree);
|
||||
|
||||
// Refusals: unknown paths, and create-intent on the immutable initrd trees.
|
||||
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);
|
||||
check("create on /test is refused (read-only)", kernel_vfs.resolvePath("/test/system/services/new-file", true) == .not_found);
|
||||
result();
|
||||
}
|
||||
|
||||
|
||||
+21
-21
@@ -4,7 +4,7 @@
|
||||
//! the mechanism is **resolve + redirect**:
|
||||
//!
|
||||
//! - `fs_resolve(path)` walks the mount table. A path under a KERNEL-backed
|
||||
//! mount (the initrd at /system, the scratch ram nodes) resolves to a
|
||||
//! mount (the initrd trees at /system and /test, the scratch ram nodes) resolves to a
|
||||
//! stateless node TOKEN served directly by `fs_node` (read/status/readdir
|
||||
//! with copy-out). A path under a USERSPACE mount (the fat server at
|
||||
//! /mnt/usb and /var) resolves to the backend's ENDPOINT: the kernel
|
||||
@@ -85,7 +85,7 @@ const maximum_directories = 8;
|
||||
const Directory = struct {
|
||||
path: [maximum_prefix]u8 = undefined,
|
||||
path_len: usize = 0,
|
||||
parent: usize = 0, // index into `directories`; 0 is /system itself
|
||||
parent: usize = 0, // index into `directories`; a top-level tree root is its own parent
|
||||
|
||||
fn slice(self: *const Directory) []const u8 {
|
||||
return self.path[0..self.path_len];
|
||||
@@ -122,37 +122,35 @@ fn parentOf(path: []const u8) []const u8 {
|
||||
|
||||
// --- boot wiring -------------------------------------------------------------
|
||||
|
||||
/// Publish the initrd as the kernel-backed /system mount and derive its bounded
|
||||
/// directory table (the unique parents of the entry paths). Called once at boot.
|
||||
/// Publish the initrd as kernel-backed mounts — one per top-level tree its
|
||||
/// entry paths name (/system, /test) — and derive the bounded directory table
|
||||
/// (every ancestor directory of the entry paths). Called once at boot.
|
||||
pub fn setInitialRamdisk(image: []const u8) void {
|
||||
ramdisk_image = image;
|
||||
installMount("/system", .kernel_initrd, null, "");
|
||||
|
||||
// Directory 0 is /system itself.
|
||||
directories[0] = .{ .parent = 0 };
|
||||
@memcpy(directories[0].path[0..7], "/system");
|
||||
directories[0].path_len = 7;
|
||||
directory_count = 1;
|
||||
directory_count = 0;
|
||||
|
||||
const rd = initial_ramdisk.Reader.init(image) orelse return;
|
||||
var i: u32 = 0;
|
||||
while (i < rd.count) : (i += 1) {
|
||||
const item = rd.entry(i) orelse continue;
|
||||
// Register every ancestor directory strictly below /system.
|
||||
// Register every ancestor directory, top-level tree roots included.
|
||||
var parent = parentOf(item.name);
|
||||
while (parent.len > 7) : (parent = parentOf(parent)) {
|
||||
while (parent.len > 1) : (parent = parentOf(parent)) {
|
||||
if (directoryIndex(parent) == null and directory_count < maximum_directories) {
|
||||
var d = &directories[directory_count];
|
||||
@memcpy(d.path[0..parent.len], parent);
|
||||
d.path_len = parent.len;
|
||||
d.parent = 0; // fixed up below once all exist
|
||||
d.parent = directory_count; // fixed up below once all exist
|
||||
directory_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parent links (a second pass so out-of-order registration doesn't matter).
|
||||
for (directories[1..directory_count]) |*d| {
|
||||
d.parent = directoryIndex(parentOf(d.slice())) orelse 0;
|
||||
// A top-level root keeps itself as parent and becomes an initrd mount.
|
||||
for (directories[0..directory_count], 0..) |*d, index| {
|
||||
const parent = parentOf(d.slice());
|
||||
d.parent = directoryIndex(parent) orelse index;
|
||||
if (parent.len == 1) installMount(d.slice(), .kernel_initrd, null, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +191,7 @@ pub const Resolved = union(enum) {
|
||||
};
|
||||
|
||||
/// Longest-prefix match over the mount table, then per-kind resolution.
|
||||
/// `create`-intent on the immutable /system fails here (EROFS-style).
|
||||
/// `create`-intent on the immutable initrd trees fails here (EROFS-style).
|
||||
pub fn resolvePath(path: []const u8, wants_create: bool) Resolved {
|
||||
if (!isAbsolute(path)) {
|
||||
return .{ .not_found = {} }; // bare names have no kernel namespace (ramfs retired)
|
||||
@@ -295,11 +293,11 @@ pub fn nodeReaddir(node_token: u64, cursor: u64, name_out: []u8) ?struct { heade
|
||||
const self_path = directories[@intCast(directory_index)].slice();
|
||||
|
||||
var index: u64 = 0;
|
||||
// Subdirectories whose parent is this directory.
|
||||
// Subdirectories whose parent is this directory (roots are self-parented,
|
||||
// so they never appear as anyone's child).
|
||||
for (directories[0..directory_count], 0..) |*d, i| {
|
||||
if (i == directory_index) continue;
|
||||
if (d.parent != directory_index) continue;
|
||||
if (i == 0) continue;
|
||||
if (index == cursor) {
|
||||
const name = d.slice()[self_path.len + 1 ..];
|
||||
const n = @min(name.len, name_out.len);
|
||||
@@ -330,11 +328,13 @@ pub fn nodeReaddir(node_token: u64, cursor: u64, name_out: []u8) ?struct { heade
|
||||
|
||||
/// Mount `backend` at `prefix` with an optional backend-side `rewrite` prefix.
|
||||
/// The endpoint reference is taken by the caller (process.zig bumps it); refuses
|
||||
/// shadowing or replacing /system.
|
||||
/// shadowing or replacing the initrd trees (/system, /test).
|
||||
pub fn mountBackend(prefix: []const u8, backend: *ipc.Endpoint, rewrite: []const u8) bool {
|
||||
if (!isAbsolute(prefix) or prefix.len < 2 or prefix.len > maximum_prefix) return false;
|
||||
if (rewrite.len > maximum_rewrite) return false;
|
||||
if (underMount(prefix, "/system") != null) return false; // the initrd is not shadowable
|
||||
for (&mounts) |*m| { // the initrd trees are not shadowable
|
||||
if (m.used and m.kind == .kernel_initrd and underMount(prefix, m.prefixSlice()) != null) return false;
|
||||
}
|
||||
installMount(prefix, .backend, backend, rewrite);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user