M8: initrd handoff + multi-binary shipping
Ship more than one user binary: the bootloader now ferries an initrd bundle
(the VFS server + future drivers) alongside sbin/init, and the kernel unpacks
and spawns each program.
- src/user/proto/initrd.zig: the container format (Header{magic,count} +
Entry{name[32],offset,len} + blobs) with a validating Reader, shared by the
kernel and the packer.
- tools/mkinitrd.py: host-side packer (Python — trivial format, and sidesteps
the reworked Zig 0.16 std fs/args API). build.zig runs it on the built user
binaries via addSystemCommand and installs initrd.img to zig-out/bin + the
ESP root.
- BootInfo gains initrd_base/initrd_len; efi.zig loadInit refactored into a
shared loadFile, and loadInitrd ferries \initrd.img into surviving
LoaderData like init.
- main.zig startInitrdBinaries(): parse the image, spawn every entry (kernel-
spawns-all for now; init takes over via sys_spawn later).
- sbin/vfs.zig: a heartbeat stub (the real VFS server is M9), packed into the
initrd to prove the pipeline.
- New `initrd` test: parse + spawn + confirm the vfs stub reaches ring 3 and
heartbeats. Harness ships initrd.img on the ESP. Suite 30/30.
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
//! The initrd (initial ramdisk) container format — shared by the build-time
|
||||
//! packer (tools/mkinitrd.zig) 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.
|
||||
//!
|
||||
//! Layout:
|
||||
//! Header (magic, count)
|
||||
//! Entry * count (name, offset, len) — offset/len into the image
|
||||
//! blob bytes... (each entry's file, at its offset)
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// "DNRD" — identifies a danos initrd image.
|
||||
pub const magic: u32 = 0x444E5244;
|
||||
|
||||
pub const Header = extern struct {
|
||||
magic: u32,
|
||||
count: u32,
|
||||
};
|
||||
|
||||
pub const Entry = extern struct {
|
||||
name: [32]u8, // NUL-padded file name (basename)
|
||||
offset: u64, // byte offset of the blob within the image
|
||||
len: u64, // blob length in bytes
|
||||
};
|
||||
|
||||
/// A validated view over an initrd image. `init` checks the magic and that the
|
||||
/// entry table fits; `entry` bounds-checks each blob against the image.
|
||||
pub const Reader = struct {
|
||||
image: []const u8,
|
||||
count: u32,
|
||||
|
||||
pub fn init(image: []const u8) ?Reader {
|
||||
if (image.len < @sizeOf(Header)) return null;
|
||||
const h = std.mem.bytesToValue(Header, image[0..@sizeOf(Header)]);
|
||||
if (h.magic != magic) return null;
|
||||
const table_end = @sizeOf(Header) + @as(usize, h.count) * @sizeOf(Entry);
|
||||
if (table_end > image.len) return null;
|
||||
return .{ .image = image, .count = h.count };
|
||||
}
|
||||
|
||||
pub const Item = struct { name: []const u8, blob: []const u8 };
|
||||
|
||||
pub fn entry(self: Reader, i: u32) ?Item {
|
||||
if (i >= self.count) return null;
|
||||
const off = @sizeOf(Header) + @as(usize, i) * @sizeOf(Entry);
|
||||
const e = std.mem.bytesToValue(Entry, self.image[off..][0..@sizeOf(Entry)]);
|
||||
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 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)],
|
||||
};
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user