boot: the /system tree is the system image — loader-built ramdisk, spawn by path
Retire the build-time ramdisk packer and the packed initial-ramdisk.img. make-fat-image.py now lays every user binary out at its FHS path on the boot volume (system/services, system/drivers, system/tests), and the EFI loader walks \system at boot, packing what it finds into an in-RAM v2 initial_ramdisk whose entry names are full FHS paths. init rides the table like every other binary: its dedicated handoff fields are gone and the kernel spawns PID 1 via the same lookup as everyone else (process.spawnBundled). system_spawn resolves names by exact path first, then unique basename, and normalizes argv[0] to the stored path — so task names (and, next, the tagged log ring's attribution) are honest binary paths everywhere. initrd v2 rejects the old magic so a stale image fails loudly. Groundwork for per-process logging (/var/log/<boot-stamp>/<binary-path>.log) and the kernel-VFS /system mount.
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
//! The initial_ramdisk (initial ramdisk) container format — shared by the build-time
|
||||
//! packer (tools/make-initial-ramdisk.py) and the kernel that unpacks it. Deliberately
|
||||
//! trivial: a header, a table of fixed-size entries, then the concatenated file
|
||||
//! blobs. We own both producer and consumer, so it need be no fancier.
|
||||
//! The initial_ramdisk (initial ramdisk) container format — built in RAM by the
|
||||
//! bootloader (boot/efi.zig walks the boot volume's /system tree) and unpacked by
|
||||
//! the kernel. Deliberately trivial: a header, a table of fixed-size entries, then
|
||||
//! the concatenated file blobs. We own both producer and consumer, so it need be
|
||||
//! no fancier.
|
||||
//!
|
||||
//! v2: entry names are full FHS paths ("/system/services/init"), 64 bytes — the
|
||||
//! same limit as a task name (abi.maximum_process_name), so a path-named task is
|
||||
//! never truncated. The boot volume's file tree is the single source of truth;
|
||||
//! this image is only the loader→kernel handoff snapshot of it.
|
||||
//!
|
||||
//! Layout:
|
||||
//! Header (magic, count)
|
||||
@@ -10,8 +16,14 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// "DNRD" — identifies a danos initial_ramdisk image.
|
||||
pub const magic: u32 = 0x444E5244;
|
||||
/// "DNR2" — identifies a danos initial_ramdisk image, format v2 (path names).
|
||||
/// The v1 magic ("DNRD", basename entries) is rejected: a stale image should
|
||||
/// fail loudly at Reader.init, not misparse names.
|
||||
pub const magic: u32 = 0x32524E44;
|
||||
|
||||
/// Entry name capacity. Matches abi.maximum_process_name so a spawned task can
|
||||
/// always carry its full binary path as its name.
|
||||
pub const maximum_name = 64;
|
||||
|
||||
pub const Header = extern struct {
|
||||
magic: u32,
|
||||
@@ -19,11 +31,17 @@ pub const Header = extern struct {
|
||||
};
|
||||
|
||||
pub const Entry = extern struct {
|
||||
name: [32]u8, // NUL-padded file name (basename)
|
||||
name: [maximum_name]u8, // NUL-padded FHS path, e.g. "/system/services/init"
|
||||
offset: u64, // byte offset of the blob within the image
|
||||
len: u64, // blob length in bytes
|
||||
};
|
||||
|
||||
/// The basename of a path: the final component after the last '/'.
|
||||
pub fn basename(path: []const u8) []const u8 {
|
||||
const i = std.mem.lastIndexOfScalar(u8, path, '/') orelse return path;
|
||||
return path[i + 1 ..];
|
||||
}
|
||||
|
||||
/// A validated view over an initial_ramdisk image. `init` checks the magic and that the
|
||||
/// entry table fits; `entry` bounds-checks each blob against the image.
|
||||
pub const Reader = struct {
|
||||
@@ -48,11 +66,74 @@ pub const Reader = struct {
|
||||
if (e.offset > self.image.len or e.len > self.image.len - e.offset) return null;
|
||||
// The name is stored in the entry's fixed field; return a stable slice
|
||||
// into the image (not the value copy) up to the NUL terminator.
|
||||
const name_field = self.image[off .. off + 32];
|
||||
const name_field = self.image[off .. off + maximum_name];
|
||||
const nlen = std.mem.indexOfScalar(u8, name_field, 0) orelse name_field.len;
|
||||
return .{
|
||||
.name = name_field[0..nlen],
|
||||
.blob = self.image[@intCast(e.offset)..][0..@intCast(e.len)],
|
||||
};
|
||||
}
|
||||
|
||||
/// Look a binary up by name: an exact path match wins; otherwise a unique
|
||||
/// basename match ("fat" finds "/system/services/fat") keeps pre-path callers
|
||||
/// working. The returned Item's name is always the stored full path.
|
||||
pub fn find(self: Reader, name: []const u8) ?Item {
|
||||
var i: u32 = 0;
|
||||
while (i < self.count) : (i += 1) {
|
||||
const item = self.entry(i) orelse continue;
|
||||
if (std.mem.eql(u8, item.name, name)) return item;
|
||||
}
|
||||
i = 0;
|
||||
while (i < self.count) : (i += 1) {
|
||||
const item = self.entry(i) orelse continue;
|
||||
if (std.mem.eql(u8, basename(item.name), name)) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// --- tests (host) -----------------------------------------------------------
|
||||
|
||||
fn testImage(buffer: []u8, entries: []const struct { name: []const u8, blob: []const u8 }) []const u8 {
|
||||
const table_end = @sizeOf(Header) + entries.len * @sizeOf(Entry);
|
||||
var offset: usize = table_end;
|
||||
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = magic, .count = @intCast(entries.len) };
|
||||
for (entries, 0..) |e, i| {
|
||||
var record = Entry{ .name = @splat(0), .offset = offset, .len = e.blob.len };
|
||||
@memcpy(record.name[0..e.name.len], e.name);
|
||||
std.mem.bytesAsValue(Entry, buffer[@sizeOf(Header) + i * @sizeOf(Entry) ..][0..@sizeOf(Entry)]).* = record;
|
||||
@memcpy(buffer[offset..][0..e.blob.len], e.blob);
|
||||
offset += e.blob.len;
|
||||
}
|
||||
return buffer[0..offset];
|
||||
}
|
||||
|
||||
test "find matches exact path, then unique basename; name is the stored path" {
|
||||
var buffer: [1024]u8 = undefined;
|
||||
const image = testImage(&buffer, &.{
|
||||
.{ .name = "/system/services/init", .blob = "INIT" },
|
||||
.{ .name = "/system/drivers/ps2-bus", .blob = "PS2" },
|
||||
});
|
||||
const rd = Reader.init(image).?;
|
||||
|
||||
const by_path = rd.find("/system/services/init").?;
|
||||
try std.testing.expectEqualStrings("/system/services/init", by_path.name);
|
||||
try std.testing.expectEqualStrings("INIT", by_path.blob);
|
||||
|
||||
const by_base = rd.find("ps2-bus").?;
|
||||
try std.testing.expectEqualStrings("/system/drivers/ps2-bus", by_base.name);
|
||||
try std.testing.expectEqualStrings("PS2", by_base.blob);
|
||||
|
||||
try std.testing.expect(rd.find("no-such-binary") == null);
|
||||
}
|
||||
|
||||
test "v1 magic is rejected" {
|
||||
var buffer: [64]u8 = @splat(0);
|
||||
std.mem.bytesAsValue(Header, buffer[0..@sizeOf(Header)]).* = .{ .magic = 0x444E5244, .count = 0 };
|
||||
try std.testing.expect(Reader.init(&buffer) == null);
|
||||
}
|
||||
|
||||
test "basename" {
|
||||
try std.testing.expectEqualStrings("fat", basename("/system/services/fat"));
|
||||
try std.testing.expectEqualStrings("fat", basename("fat"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user