diff --git a/src/boot.zig b/src/boot.zig index 27f3fca..6b04b50 100644 --- a/src/boot.zig +++ b/src/boot.zig @@ -137,7 +137,6 @@ pub const BootDevice = struct { entries: []const BootEntry, }; -// TODO(jared): Use vtable setup like std.mem.Allocator. pub const BootLoader = union(enum) { bls: *BootLoaderSpec, xmodem: *Xmodem, @@ -149,9 +148,9 @@ pub const BootLoader = union(enum) { } /// Caller is responsible for all memory corresponding to return value. - pub fn probe(self: @This(), allocator: std.mem.Allocator) ![]const BootDevice { + pub fn probe(self: @This()) ![]const BootDevice { return switch (self) { - inline else => |boot_loader| try boot_loader.probe(allocator), + inline else => |boot_loader| try boot_loader.probe(), }; } @@ -185,6 +184,8 @@ fn autobootWrapper(self: *Autoboot) void { } } +// TODO(jared): more fine-grained return values, we can fail to kexec-load for +// many reasons. /// Returns true if kexec has been successfully loaded. fn autoboot(self: *Autoboot) !bool { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -206,7 +207,7 @@ fn autoboot(self: *Autoboot) !bool { return false; } - const boot_devices = try boot_loader.probe(arena.allocator()); + const boot_devices = try boot_loader.probe(); if (self.needToStop()) { return false; } diff --git a/src/boot/bls.zig b/src/boot/bls.zig index 28ee34a..79322fe 100644 --- a/src/boot/bls.zig +++ b/src/boot/bls.zig @@ -301,15 +301,10 @@ pub const BootLoaderSpec = struct { self.external_mounts = try external_mounts.toOwnedSlice(); } - fn searchForEntries( - self: *@This(), - mount: Mount, - tmp_allocator: std.mem.Allocator, - final_allocator: std.mem.Allocator, - ) !BootDevice { - _ = self; - - var entries = std.ArrayList(BootEntry).init(final_allocator); + fn searchForEntries(self: *@This(), mount: Mount) !BootDevice { + const allocator = self.arena.allocator(); + + var entries = std.ArrayList(BootEntry).init(allocator); errdefer entries.deinit(); const loader_conf: LoaderConf = b: { @@ -319,7 +314,7 @@ pub const BootLoaderSpec = struct { }; defer file.close(); std.log.debug("found loader.conf on \"{s}\"", .{mount.disk_name}); - const contents = try file.readToEndAlloc(tmp_allocator, 4096); + const contents = try file.readToEndAlloc(allocator, 4096); break :b LoaderConf.parse(contents); }; @@ -335,7 +330,7 @@ pub const BootLoaderSpec = struct { continue; } - var entry_filename = EntryFilename.parse(tmp_allocator, dir_entry.name) catch |err| { + var entry_filename = EntryFilename.parse(allocator, dir_entry.name) catch |err| { std.log.err("invalid entry filename for {s}: {}", .{ dir_entry.name, err }); continue; }; @@ -356,18 +351,18 @@ pub const BootLoaderSpec = struct { std.log.debug("inspecting BLS entry {s} on \"{s}\"", .{ dir_entry.name, mount.disk_name }); // We should definitely not get any boot entry files larger than this. - const entry_contents = try entry_file.readToEndAlloc(tmp_allocator, 1 << 16); - var type1_entry = Type1Entry.parse(tmp_allocator, entry_contents) catch |err| { + const entry_contents = try entry_file.readToEndAlloc(allocator, 1 << 16); + var type1_entry = Type1Entry.parse(allocator, entry_contents) catch |err| { std.log.err("failed to parse {s} as BLS type 1 entry: {}", .{ dir_entry.name, err }); continue; }; defer type1_entry.deinit(); - const linux = try mount.dir.realpathAlloc(final_allocator, type1_entry.linux orelse { + const linux = try mount.dir.realpathAlloc(allocator, type1_entry.linux orelse { std.log.err("missing linux kernel in {s}", .{dir_entry.name}); continue; }); - errdefer final_allocator.free(linux); + errdefer allocator.free(linux); // NOTE: Multiple initrds won't work if we have IMA appraisal // of signed initrds, so we can only load one. @@ -377,7 +372,7 @@ pub const BootLoaderSpec = struct { var initrd: ?[]const u8 = null; if (type1_entry.initrd) |_initrd| { if (_initrd.len > 0) { - initrd = try mount.dir.realpathAlloc(final_allocator, _initrd[0]); + initrd = try mount.dir.realpathAlloc(allocator, _initrd[0]); } if (_initrd.len > 1) { @@ -386,27 +381,27 @@ pub const BootLoaderSpec = struct { } errdefer { if (initrd) |_initrd| { - final_allocator.free(_initrd); + allocator.free(_initrd); } } var options_with_bls_entry: [linux_headers.COMMAND_LINE_SIZE]u8 = undefined; const options = b: { if (type1_entry.options) |opts| { - const orig = try std.mem.join(tmp_allocator, " ", opts); + const orig = try std.mem.join(allocator, " ", opts); break :b try std.fmt.bufPrint(&options_with_bls_entry, "{s} tboot.bls-entry={s}", .{ orig, entry_filename.name }); } else { break :b try std.fmt.bufPrint(&options_with_bls_entry, "tboot.bls-entry={s}", .{entry_filename.name}); } }; - const final_options = try final_allocator.dupe(u8, options); - errdefer final_allocator.free(options); + const final_options = try allocator.dupe(u8, options); + errdefer allocator.free(options); - const context = try final_allocator.create(EntryContext); - errdefer final_allocator.destroy(context); + const context = try allocator.create(EntryContext); + errdefer allocator.destroy(context); context.* = .{ - .full_path = try entries_dir.realpathAlloc(final_allocator, dir_entry.name), + .full_path = try entries_dir.realpathAlloc(allocator, dir_entry.name), }; try entries.append( @@ -420,31 +415,22 @@ pub const BootLoaderSpec = struct { } return .{ - .name = try final_allocator.dupe(u8, mount.disk_name), + .name = try allocator.dupe(u8, mount.disk_name), .timeout = loader_conf.timeout, .entries = try entries.toOwnedSlice(), }; } /// Caller is responsible for the returned slice. - pub fn probe(self: *@This(), allocator: std.mem.Allocator) ![]const BootDevice { - // A temporary allocator for stuff not saved after the probe and not - // included in the return value. - var tmp_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer tmp_arena.deinit(); - + pub fn probe(self: *@This()) ![]const BootDevice { std.log.debug("BLS probe start", .{}); - var devices = std.ArrayList(BootDevice).init(allocator); + var devices = std.ArrayList(BootDevice).init(self.arena.allocator()); // Mounts of external devices are ordered before external mounts so // they are prioritized in the boot process. std.log.debug("BLS probe found {} external device(s)", .{self.external_mounts.len}); for (self.external_mounts) |mount| { - try devices.append(self.searchForEntries( - mount, - tmp_arena.allocator(), - allocator, - ) catch |err| { + try devices.append(self.searchForEntries(mount) catch |err| { std.log.err( "failed to search for entries on \"{s}\": {}", .{ mount.disk_name, err }, @@ -455,11 +441,7 @@ pub const BootLoaderSpec = struct { std.log.debug("BLS probe found {} internal device(s)", .{self.internal_mounts.len}); for (self.internal_mounts) |mount| { - try devices.append(self.searchForEntries( - mount, - tmp_arena.allocator(), - allocator, - ) catch |err| { + try devices.append(self.searchForEntries(mount) catch |err| { std.log.err( "failed to search for entries on \"{s}\": {}", .{ mount.disk_name, err }, @@ -1003,40 +985,45 @@ const Type1Entry = struct { var options = std.ArrayList([]const u8).init(allocator); errdefer options.deinit(); - while (all_split.next()) |line| { - if (std.mem.eql(u8, line, "")) { + while (all_split.next()) |unprocessed_line| { + if (std.mem.eql(u8, unprocessed_line, "")) { continue; } - var line_split = std.mem.splitSequence(u8, line, " "); + const line_without_tabs = try self.allocator.dupe(u8, unprocessed_line); + defer self.allocator.free(line_without_tabs); + const num_replacements = std.mem.replace(u8, unprocessed_line, "\t", " ", line_without_tabs); + _ = num_replacements; + const line = std.mem.collapseRepeats(u8, line_without_tabs, ' '); + var line_split = std.mem.splitSequence(u8, std.mem.trim(u8, line, " "), " "); const key = line_split.next() orelse continue; if (std.mem.eql(u8, key, "title")) { - self.title = line_split.rest(); + self.title = try self.allocator.dupe(u8, line_split.rest()); } else if (std.mem.eql(u8, key, "version")) { - self.version = line_split.rest(); + self.version = try self.allocator.dupe(u8, line_split.rest()); } else if (std.mem.eql(u8, key, "machine-id")) { - self.machine_id = line_split.rest(); + self.machine_id = try self.allocator.dupe(u8, line_split.rest()); } else if (std.mem.eql(u8, key, "sort_key")) { - self.sort_key = line_split.rest(); + self.sort_key = try self.allocator.dupe(u8, line_split.rest()); } else if (std.mem.eql(u8, key, "linux")) { - self.linux = std.mem.trimLeft(u8, line_split.rest(), "/"); + self.linux = try self.allocator.dupe(u8, std.mem.trimLeft(u8, line_split.rest(), "/")); } else if (std.mem.eql(u8, key, "initrd")) { - try initrd.append(std.mem.trimLeft(u8, line_split.rest(), "/")); + try initrd.append(try self.allocator.dupe(u8, std.mem.trimLeft(u8, line_split.rest(), "/"))); } else if (std.mem.eql(u8, key, "efi")) { - self.efi = std.mem.trimLeft(u8, line_split.rest(), "/"); + self.efi = try self.allocator.dupe(u8, std.mem.trimLeft(u8, line_split.rest(), "/")); } else if (std.mem.eql(u8, key, "options")) { while (line_split.next()) |next| { - try options.append(next); + try options.append(try self.allocator.dupe(u8, next)); } } else if (std.mem.eql(u8, key, "devicetree")) { - self.devicetree = std.mem.trimLeft(u8, line_split.rest(), "/"); + self.devicetree = try self.allocator.dupe(u8, std.mem.trimLeft(u8, line_split.rest(), "/")); } else if (std.mem.eql(u8, key, "devicetree-overlay")) { var devicetree_overlay = std.ArrayList([]const u8).init(allocator); errdefer devicetree_overlay.deinit(); while (line_split.next()) |next| { - try devicetree_overlay.append(std.mem.trimLeft(u8, next, "/")); + try devicetree_overlay.append(try self.allocator.dupe(u8, std.mem.trimLeft(u8, next, "/"))); } self.devicetree_overlay = try devicetree_overlay.toOwnedSlice(); } else if (std.mem.eql(u8, key, "architecture")) { @@ -1057,30 +1044,75 @@ const Type1Entry = struct { pub fn deinit(self: *@This()) void { if (self.initrd) |initrd| { - self.allocator.free(initrd); + defer self.allocator.free(initrd); + for (initrd) |_initrd| { + self.allocator.free(_initrd); + } } if (self.options) |options| { - self.allocator.free(options); + defer self.allocator.free(options); + for (options) |option| { + self.allocator.free(option); + } } if (self.devicetree_overlay) |dt_overlay| { - self.allocator.free(dt_overlay); + defer self.allocator.free(dt_overlay); + for (dt_overlay) |dt| { + self.allocator.free(dt); + } + } + if (self.linux) |linux| { + self.allocator.free(linux); + } + if (self.title) |title| { + self.allocator.free(title); + } + if (self.version) |version| { + self.allocator.free(version); + } + if (self.machine_id) |machine_id| { + self.allocator.free(machine_id); + } + if (self.sort_key) |sort_key| { + self.allocator.free(sort_key); + } + if (self.efi) |efi| { + self.allocator.free(efi); } } }; test "type 1 boot entry parsing" { - const simple = - \\title Foo - \\linux /EFI/foo/Image - \\options console=ttyAMA0 loglevel=7 - \\architecture aa64 - ; - - var type1_entry = try Type1Entry.parse(std.testing.allocator, simple); - defer type1_entry.deinit(); - - try std.testing.expectEqualStrings("EFI/foo/Image", type1_entry.linux.?); - try std.testing.expect(type1_entry.options.?.len == 2); - try std.testing.expectEqualStrings("console=ttyAMA0", type1_entry.options.?[0]); - try std.testing.expectEqualStrings("loglevel=7", type1_entry.options.?[1]); + { + const simple = + \\title Foo + \\linux /EFI/foo/Image + \\options console=ttyAMA0 loglevel=7 + \\architecture aa64 + ; + + var type1_entry = try Type1Entry.parse(std.testing.allocator, simple); + defer type1_entry.deinit(); + + try std.testing.expectEqualStrings("EFI/foo/Image", type1_entry.linux.?); + try std.testing.expect(type1_entry.options.?.len == 2); + try std.testing.expectEqualStrings("console=ttyAMA0", type1_entry.options.?[0]); + try std.testing.expectEqualStrings("loglevel=7", type1_entry.options.?[1]); + } + { + const weird_formatting = + \\title Foo + \\linux /EFI/foo/Image + \\options console=ttyAMA0 loglevel=7 + \\architecture aa64 + ; + + var type1_entry = try Type1Entry.parse(std.testing.allocator, weird_formatting); + defer type1_entry.deinit(); + + try std.testing.expectEqualStrings("EFI/foo/Image", type1_entry.linux.?); + try std.testing.expect(type1_entry.options.?.len == 2); + try std.testing.expectEqualStrings("console=ttyAMA0", type1_entry.options.?[0]); + try std.testing.expectEqualStrings("loglevel=7", type1_entry.options.?[1]); + } } diff --git a/src/boot/xmodem.zig b/src/boot/xmodem.zig index c62eaf0..f337e9e 100644 --- a/src/boot/xmodem.zig +++ b/src/boot/xmodem.zig @@ -12,19 +12,23 @@ const linux_headers = @import("linux_headers"); pub const Xmodem = struct { allocator: std.mem.Allocator, tmp_dir: tmp.TmpDir, - original_tty: ?Tty = null, - tty_fd: posix.fd_t, - tty_name: []const u8, + original_serial: ?Tty = null, + serial_fd: posix.fd_t, + serial_name: []const u8, skip_initrd: bool = false, pub fn init(allocator: std.mem.Allocator, opts: struct { - tty_name: []const u8, - tty_fd: posix.fd_t, + /// A human-friendly name of the serial console + serial_name: []const u8, + /// The file descriptor associated with this serial console + serial_fd: posix.fd_t, + /// Indicates whether the initrd should be fetched in addition to the + /// kernel and cmdline parameters. skip_initrd: bool, }) !@This() { return .{ - .tty_name = opts.tty_name, - .tty_fd = opts.tty_fd, + .serial_name = opts.serial_name, + .serial_fd = opts.serial_fd, .skip_initrd = opts.skip_initrd, .allocator = allocator, .tmp_dir = try tmp.tmpDir(.{}), @@ -32,27 +36,27 @@ pub const Xmodem = struct { } pub fn setup(self: *@This()) !void { - self.original_tty = try setupTty(posix.STDIN_FILENO, .file_transfer_recv); + self.original_serial = try setupTty(self.serial_fd, .file_transfer_recv); } - pub fn probe(self: *@This(), final_allocator: std.mem.Allocator) ![]const BootDevice { + pub fn probe(self: *@This()) ![]const BootDevice { errdefer { - if (self.original_tty) |tty| { + if (self.original_serial) |tty| { tty.reset(); } - self.original_tty = null; + self.original_serial = null; } var linux = try self.tmp_dir.dir.createFile("linux", .{}); defer linux.close(); - try xmodemRecv(posix.STDIN_FILENO, linux.writer()); + try xmodemRecv(self.serial_fd, linux.writer()); if (!self.skip_initrd) { var initrd = try self.tmp_dir.dir.createFile("initrd", .{}); defer initrd.close(); - try xmodemRecv(posix.STDIN_FILENO, initrd.writer()); + try xmodemRecv(self.serial_fd, initrd.writer()); } var params_file = try self.tmp_dir.dir.createFile("params", .{ @@ -61,36 +65,36 @@ pub const Xmodem = struct { defer params_file.close(); try xmodemRecv( - posix.STDIN_FILENO, + self.serial_fd, params_file.writer(), ); - if (self.original_tty) |tty| { + if (self.original_serial) |tty| { tty.reset(); } - self.original_tty = null; + self.original_serial = null; try params_file.seekTo(0); const kernel_params_bytes = try params_file.readToEndAlloc( - final_allocator, + self.allocator, linux_headers.COMMAND_LINE_SIZE, ); // trim out whitespace characters const kernel_params = std.mem.trim(u8, kernel_params_bytes, " \t\n"); - var devices = std.ArrayList(BootDevice).init(final_allocator); - var entries = std.ArrayList(BootEntry).init(final_allocator); + var devices = std.ArrayList(BootDevice).init(self.allocator); + var entries = std.ArrayList(BootEntry).init(self.allocator); try entries.append(.{ - .context = try final_allocator.create(struct {}), + .context = try self.allocator.create(struct {}), .cmdline = if (kernel_params.len > 0) kernel_params else null, .initrd = if (!self.skip_initrd) try self.tmp_dir.dir.realpathAlloc( - final_allocator, + self.allocator, "initrd", ) else null, .linux = try self.tmp_dir.dir.realpathAlloc( - final_allocator, + self.allocator, "linux", ), }); @@ -98,7 +102,7 @@ pub const Xmodem = struct { try devices.append(.{ .timeout = 0, .entries = try entries.toOwnedSlice(), - .name = self.tty_name, + .name = self.serial_name, }); return devices.toOwnedSlice(); } @@ -110,7 +114,7 @@ pub const Xmodem = struct { pub fn teardown(self: *@This()) !void { self.tmp_dir.cleanup(); - if (self.original_tty) |tty| { + if (self.original_serial) |tty| { tty.reset(); } } diff --git a/src/client.zig b/src/client.zig index d9ded00..180bb55 100644 --- a/src/client.zig +++ b/src/client.zig @@ -642,8 +642,8 @@ pub const Command = struct { fn run(c: *Command, args: *ArgsIterator) !?ClientMsg { var xmodem = try Xmodem.init(c.allocator, .{ - .tty_name = "client-stdin", - .tty_fd = posix.STDIN_FILENO, + .serial_name = "client-stdin", + .serial_fd = posix.STDIN_FILENO, .skip_initrd = if (args.next()) |next| std.mem.eql(u8, next, "-n") else @@ -653,7 +653,7 @@ pub const Command = struct { defer boot_loader.teardown() catch {}; try boot_loader.setup(); - const devices = try boot_loader.probe(c.allocator); + const devices = try boot_loader.probe(); for (devices) |device| { for (device.entries) |entry| { if (kexecLoad(c.allocator, entry.linux, entry.initrd, entry.cmdline)) { diff --git a/src/tboot-nixos-install.zig b/src/tboot-nixos-install.zig index b508eb9..ade6f73 100644 --- a/src/tboot-nixos-install.zig +++ b/src/tboot-nixos-install.zig @@ -283,7 +283,11 @@ pub fn main() !void { }; defer res.deinit(); - if (res.args.help > 0 or res.positionals.len != 1 or res.args.@"private-key" == null or res.args.@"public-key" == null) { + if (res.args.help != 0) { + return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}); + } + + if (res.positionals.len != 1 or res.args.@"private-key" == null or res.args.@"public-key" == null) { try diag.report(stderr, error.InvalidArgs); try clap.usage(stderr, clap.Help, ¶ms); return; diff --git a/src/tboot-sign.zig b/src/tboot-sign.zig index eb66586..92c8e4f 100644 --- a/src/tboot-sign.zig +++ b/src/tboot-sign.zig @@ -300,7 +300,11 @@ pub fn main() !void { }; defer res.deinit(); - if (res.args.help > 0 or res.positionals.len != 2 or res.args.@"private-key" == null or res.args.@"public-key" == null) { + if (res.args.help != 0) { + return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}); + } + + if (res.positionals.len != 2 or res.args.@"private-key" == null or res.args.@"public-key" == null) { try diag.report(stderr, error.InvalidArgs); try clap.usage(stderr, clap.Help, ¶ms); return; diff --git a/src/xmodem.zig b/src/xmodem.zig index d07d449..a48765c 100644 --- a/src/xmodem.zig +++ b/src/xmodem.zig @@ -359,7 +359,11 @@ pub fn main() !void { }; defer res.deinit(); - if (res.args.help > 0 or res.positionals.len != 1 or res.args.file == null or res.args.tty == null) { + if (res.args.help != 0) { + return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}); + } + + if (res.positionals.len != 1 or res.args.file == null or res.args.tty == null) { try diag.report(stderr, error.InvalidArgs); try clap.usage(std.io.getStdErr().writer(), clap.Help, ¶ms); return;