diff --git a/build.zig b/build.zig index 0b6eebe1..0129e5ce 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const utils = @import("./src/utils.zig"); + +const TBOOT_INITRD_NAME = "tboot-initrd"; pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions( @@ -46,84 +49,19 @@ pub fn build(b: *std.Build) !void { }); const linux_headers_module = linux_headers_translated.addModule("linux_headers"); - if (with_loader) { - const tboot_loader = b.addExecutable(.{ - .name = "tboot-loader", - .root_source_file = b.path("src/tboot-loader.zig"), - .target = target, - .optimize = tboot_loader_optimize, - .strip = optimize != std.builtin.OptimizeMode.Debug, - }); - tboot_loader.root_module.addAnonymousImport("test_key", .{ - .root_source_file = b.path("tests/keys/tboot/key.der"), - }); - tboot_loader.root_module.addImport("linux_headers", linux_headers_module); - - var run_tboot_initrd = b.addSystemCommand(&.{"tboot-initrd"}); - - // TODO(jared): Would be nicer to have generic - // --file=tboot_loader:/init CLI interface, but don't know how to - // obtain path and string format it into that form. Further, would - // be nicer to not shell-out to a separate tool at all and just do - // the CPIO generation in here. - run_tboot_initrd.addPrefixedFileArg("-i", tboot_loader.getEmittedBin()); - - if (firmware_directory) |directory| { - const directory_ = b.addWriteFiles().addCopyDirectory( - .{ .cwd_relative = directory }, - "", - .{}, - ); - - run_tboot_initrd.addPrefixedDirectoryArg("-d", directory_); - } - - const cpio_output_file = run_tboot_initrd.addPrefixedOutputFileArg( - "-o", - "tboot-loader.cpio", - ); - - run_tboot_initrd.expectExitCode(0); - - const cpio_archive = b.addInstallFile( - cpio_output_file, - "tboot-loader.cpio", - ); - - // install the cpio archive during "zig build install" - b.getInstallStep().dependOn(&cpio_archive.step); - - const runner_tool = b.addRunArtifact(b.addExecutable(.{ - .name = "tboot-runner", - .target = b.graph.host, - .root_source_file = b.path("src/runner.zig"), - })); - runner_tool.step.dependOn(&cpio_archive.step); - runner_tool.addArg(b.makeTempPath()); - runner_tool.addFileArg(cpio_archive.source); - - var env = try std.process.getEnvMap(b.allocator); - defer env.deinit(); - - if (env.get("TINYBOOT_KERNEL")) |kernel| { - runner_tool.addArg(kernel); - } - - // extra args passed through to qemu - if (b.args) |args| { - runner_tool.addArgs(args); - } - - const run_step = b.step("run", "Run in qemu"); - run_step.dependOn(&runner_tool.step); - } + // For re-usage with building the tboot-loader initrd, if we are also + // building tools. + var maybe_tboot_initrd_tool: ?*std.Build.Step.Compile = null; if (with_tools) { - const tboot_initrd_tool = b.addExecutable(.{ - .name = "tboot-initrd", + maybe_tboot_initrd_tool = b.addExecutable(.{ + .name = TBOOT_INITRD_NAME, .target = target, + .optimize = optimize, .root_source_file = b.path("src/tboot-initrd.zig"), }); + + var tboot_initrd_tool = maybe_tboot_initrd_tool.?; tboot_initrd_tool.linkLibC(); tboot_initrd_tool.linkSystemLibrary("liblzma"); tboot_initrd_tool.root_module.addImport("clap", clap.module("clap")); @@ -146,6 +84,7 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, .strip = optimize != std.builtin.OptimizeMode.Debug, }); + tboot_bless_boot_generator.root_module.addImport("clap", clap.module("clap")); b.installArtifact(tboot_bless_boot_generator); const tboot_sign = b.addExecutable(.{ @@ -195,6 +134,83 @@ pub fn build(b: *std.Build) !void { b.installArtifact(tboot_vpd); } + if (with_loader) { + const tboot_loader = b.addExecutable(.{ + .name = "tboot-loader", + .root_source_file = b.path("src/tboot-loader.zig"), + .target = target, + .optimize = tboot_loader_optimize, + .strip = optimize != std.builtin.OptimizeMode.Debug, + }); + tboot_loader.root_module.addAnonymousImport("test_key", .{ + .root_source_file = b.path("tests/keys/tboot/key.der"), + }); + tboot_loader.root_module.addImport("linux_headers", linux_headers_module); + + // First look for a local build of tboot-initrd, helpful for iteration + // on the tool itself, otherwise use the one that exists on $PATH. + var run_tboot_initrd = if (maybe_tboot_initrd_tool) |tboot_initrd_tool| + b.addRunArtifact(tboot_initrd_tool) + else + b.addSystemCommand(&.{TBOOT_INITRD_NAME}); + + // TODO(jared): Would be nicer to have generic + // --file=tboot_loader:/init CLI interface, but don't know how to + // obtain path and string format it into that form. Further, would + // be nicer to not shell-out to a separate tool at all and just do + // the CPIO generation in here. + run_tboot_initrd.addPrefixedFileArg("-i", tboot_loader.getEmittedBin()); + + if (firmware_directory) |directory| { + const directory_ = b.addWriteFiles().addCopyDirectory( + .{ .cwd_relative = directory }, + "", + .{}, + ); + + run_tboot_initrd.addPrefixedDirectoryArg("-d", directory_); + } + + const cpio_output_file = run_tboot_initrd.addPrefixedOutputFileArg( + "-o", + "tboot-loader.cpio", + ); + + run_tboot_initrd.expectExitCode(0); + + const cpio_archive = b.addInstallFile( + cpio_output_file, + "tboot-loader.cpio", + ); + + // install the cpio archive during "zig build install" + b.getInstallStep().dependOn(&cpio_archive.step); + + const runner_tool = b.addRunArtifact(b.addExecutable(.{ + .name = "tboot-runner", + .target = b.graph.host, + .root_source_file = b.path("src/runner.zig"), + })); + runner_tool.step.dependOn(&cpio_archive.step); + runner_tool.addArg(b.makeTempPath()); + runner_tool.addFileArg(cpio_archive.source); + + var env = try std.process.getEnvMap(b.allocator); + defer env.deinit(); + + if (env.get("TINYBOOT_KERNEL")) |kernel| { + runner_tool.addArg(kernel); + } + + // extra args passed through to qemu + if (b.args) |args| { + runner_tool.addArgs(args); + } + + const run_step = b.step("run", "Run in qemu"); + run_step.dependOn(&runner_tool.step); + } + const unit_tests = b.addTest(.{ .root_source_file = b.path("src/test.zig"), .target = target, diff --git a/compress-firmware.nix b/compress-firmware.nix new file mode 100644 index 00000000..dfc42a75 --- /dev/null +++ b/compress-firmware.nix @@ -0,0 +1,51 @@ +# This is adapted from https://github.com/nixos/nixpkgs/blob/5e4947a31bd21b33ccabcb9ff06d685b68d1e9c4/pkgs/build-support/kernel/compress-firmware.nix, +# but dereferences all symlinks so that the zig build system is capable of +# including all paths we want. See https://github.com/ziglang/zig/blob/53216d2f22053ca94a68f5da234038c01f73d60f/lib/std/Build/Step/WriteFile.zig#L232. + +{ + runCommand, + lib, + zstd, +}: + +firmwares: + +let + compressor = { + ext = "xz"; + nativeBuildInputs = [ ]; + cmd = file: target: ''xz -9c -T1 -C crc32 --lzma2=dict=2MiB "${file}" > "${target}"''; + }; +in + +runCommand "firmware-xz" + { + allowedRequisites = [ ]; + inherit (compressor) nativeBuildInputs; + } + ( + lib.concatLines ( + map (firmware: '' + mkdir -p $out/lib + (cd ${firmware} && find lib/firmware -type d -print0) | + (cd $out && xargs -0 mkdir -v --) + (cd ${firmware} && find lib/firmware -type f -print0) | + (cd $out && xargs -0rtP "$NIX_BUILD_CORES" -n1 \ + sh -c '${compressor.cmd "${firmware}/$1" "$1.${compressor.ext}"}' --) + (cd ${firmware} && find lib/firmware -type l) | while read link; do + target="$(readlink "${firmware}/$link")" + if [ -f "${firmware}/$link" ]; then + cp -vL -- "''${target/^${firmware}/$out}.${compressor.ext}" "$out/$link.${compressor.ext}" + else + echo HI + cp -vrL -- "''${target/^${firmware}/$out}" "$out/$link" + fi + done + + find $out + + echo "Checking for broken symlinks:" + find -L $out -type l -print -execdir false -- '{}' '+' + '') firmwares + ) + ) diff --git a/flake.lock b/flake.lock index d155c85f..3d22d46f 100644 --- a/flake.lock +++ b/flake.lock @@ -102,11 +102,11 @@ ] }, "locked": { - "lastModified": 1739320185, - "narHash": "sha256-PkKANseOVBpxdaWSWFgvyC8VXmGuMoYpGTjjNZy0tVU=", + "lastModified": 1739362309, + "narHash": "sha256-p9TEl8E+LKKsrU2WqXsFilKg8geHnHjzOY3KXSbGwQE=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "e24790ca464357d7286d62f3558228dfb8d81aee", + "rev": "1d0da603c0177b84a73768000e7fa40c46598149", "type": "github" }, "original": { diff --git a/options.nix b/options.nix index d576f806..fd7b0cb9 100644 --- a/options.nix +++ b/options.nix @@ -193,14 +193,7 @@ in firmware = mkOption { type = types.listOf types.package; default = [ ]; - apply = - list: - pkgs.buildEnv { - name = "firmware"; - paths = map pkgs.compressFirmwareXz list; - pathsToLink = [ "/lib/firmware" ]; - ignoreCollisions = true; - }; + apply = pkgs.callPackage ./compress-firmware.nix { }; }; }; diff --git a/pkgs/linux/check_config.bash b/pkgs/linux/check_config.bash deleted file mode 100644 index fa1d8d44..00000000 --- a/pkgs/linux/check_config.bash +++ /dev/null @@ -1,24 +0,0 @@ -# shellcheck shell=bash - -set -o errexit -set -o nounset -set -o pipefail - -start_config=$1 -end_config=$2 - -missing=() -while read -r line; do - if ! grep --silent "$line" "$end_config"; then - missing+=("$line") - fi -done <"$start_config" - -if [[ ${#missing[@]} -gt 0 ]]; then - echo - for line in "${missing[@]}"; do - echo "\"$line\" not found in final config!" - done - echo - exit 1 -fi diff --git a/pkgs/linux/default.nix b/pkgs/linux/default.nix index a59b069d..1fe38d30 100644 --- a/pkgs/linux/default.nix +++ b/pkgs/linux/default.nix @@ -25,7 +25,25 @@ stdenv.mkDerivation { postConfigure = '' cat $kconfigPath $extraConfigPath > all.config make -j$NIX_BUILD_CORES ARCH=${stdenv.hostPlatform.linuxArch} KCONFIG_ALLCONFIG=1 allnoconfig - bash ${./check_config.bash} all.config .config + + start_config=all.config + end_config=.config + + missing=() + while read -r line; do + if ! grep --silent "$line" "$end_config"; then + missing+=("$line") + fi + done <"$start_config" + + if [[ ''${#missing[@]} -gt 0 ]]; then + echo + for line in "''${missing[@]}"; do + echo "\"$line\" not found in final config!" + done + echo + exit 1 + fi ''; buildFlags = [ "DTC_FLAGS=-@" diff --git a/scripts/install.bash b/scripts/install.bash deleted file mode 100644 index 66ea61c5..00000000 --- a/scripts/install.bash +++ /dev/null @@ -1,7 +0,0 @@ -# shellcheck shell=bash - -flashrom --programmer internal --wp-disable -vpd -f OLD_ROM -l | grep VPD_INHERIT_FROM_OLD_GREP_FLAGS | xargs -n1 vpd -f NEW_ROM -s -flashrom --programmer internal --wp-range 0 0 -flashrom --programmer internal --write NEW_ROM EXTRA_FLAGS -flashrom --programmer internal --wp-range START LENGTH --wp-enable diff --git a/scripts/update.bash b/scripts/update.bash deleted file mode 100644 index aa69c1fa..00000000 --- a/scripts/update.bash +++ /dev/null @@ -1,4 +0,0 @@ -# shellcheck shell=bash - -# TODO(jared): detect which slot to write to -flashrom --programmer internal --noverify-all --fmap --include RW_SECTION_A --write NEW_ROM diff --git a/src/cpio.zig b/src/cpio.zig index fae06eb2..ba2f300b 100644 --- a/src/cpio.zig +++ b/src/cpio.zig @@ -36,7 +36,7 @@ const CpioEntryType = enum { pub const CpioArchive = @This(); -dest: *std.io.StreamSource, +destination: *std.io.StreamSource, ino: u32 = 0, total_written: usize = 0, @@ -45,8 +45,8 @@ const Error = error{ UnexpectedSource, }; -pub fn init(dest: *std.io.StreamSource) !@This() { - return @This(){ .dest = dest }; +pub fn init(destination: *std.io.StreamSource) !@This() { + return @This(){ .destination = destination }; } pub fn addEntry( @@ -103,7 +103,7 @@ pub fn addEntry( .namesize = @intCast(filepath_len), }; - try self.dest.writer().print("{X:0>6}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}", .{ + try self.destination.writer().print("{X:0>6}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}{X:0>8}", .{ header.magic, header.ino, header.mode, @@ -121,13 +121,13 @@ pub fn addEntry( }); self.total_written += ASCII_CPIO_HEADER_SIZE; - try self.dest.writer().writeAll(path); - try self.dest.writer().writeByte(0); // null terminator + try self.destination.writer().writeAll(path); + try self.destination.writer().writeByte(0); // null terminator self.total_written += filepath_len; // pad the file name const header_padding = (4 - ((ASCII_CPIO_HEADER_SIZE + filepath_len) % 4)) % 4; - try self.dest.writer().writeByteNTimes(0, header_padding); + try self.destination.writer().writeByteNTimes(0, header_padding); self.total_written += header_padding; if (source) |source_| { @@ -139,14 +139,14 @@ pub fn addEntry( while (pos < end) { try source_.seekTo(pos); const bytes_read = try source_.read(&buf); - try self.dest.writer().writeAll(buf[0..bytes_read]); + try self.destination.writer().writeAll(buf[0..bytes_read]); self.total_written += bytes_read; pos += bytes_read; } // pad the file data const filedata_padding = (4 - (end % 4)) % 4; - try self.dest.writer().writeByteNTimes(0, filedata_padding); + try self.destination.writer().writeByteNTimes(0, filedata_padding); self.total_written += filedata_padding; } } @@ -184,17 +184,17 @@ pub fn finalize(self: *@This()) !void { // Maintain a block size of 512 by adding padding to the end of the // archive. - try self.dest.writer().writeByteNTimes(0, (512 - (self.total_written % 512)) % 512); + try self.destination.writer().writeByteNTimes(0, (512 - (self.total_written % 512)) % 512); } fn handleFile( arena: *std.heap.ArenaAllocator, kind: std.fs.File.Kind, filename: []const u8, - directory_path: []const u8, + current_directory: []const u8, starting_directory: []const u8, archive: *CpioArchive, - directory: std.fs.Dir, + directory: *std.fs.Dir, ) anyerror!void { var fullpath_buf: [std.fs.max_path_bytes]u8 = undefined; const full_entry_path = try directory.realpath(filename, &fullpath_buf); @@ -212,7 +212,7 @@ fn handleFile( arena, starting_directory, archive, - sub_directory, + &sub_directory, ); }, .file => { @@ -221,6 +221,9 @@ fn handleFile( const stat = try file.stat(); var source = std.io.StreamSource{ .file = file }; + + std.log.debug("adding file to archive at {s}", .{entry_path}); + try archive.addFile(entry_path, &source, @intCast(stat.mode)); }, .sym_link => { @@ -232,7 +235,7 @@ fn handleFile( if (std.mem.startsWith(u8, resolved_path, starting_directory)) { const symlink_path = try std.fs.path.join( arena.allocator(), - &.{ directory_path, filename }, + &.{ current_directory, filename }, ); try archive.addSymlink(symlink_path, entry_path); @@ -243,22 +246,13 @@ fn handleFile( arena, stat.kind, resolved_path, - directory_path, + current_directory, starting_directory, archive, directory, ); - - // std.log.warn( - // "Resolved symlink {s} is outside of {s}, refusing to add to CPIO archive", - // .{ resolved_path, starting_directory }, - // ); } }, - // We explicitly don't add these kinds of files. - .block_device => {}, - .character_device => {}, - .unix_domain_socket => {}, else => { std.log.warn( "Do not know how to add file {s} of kind {} to CPIO archive", @@ -272,7 +266,7 @@ pub fn walkDirectory( arena: *std.heap.ArenaAllocator, starting_directory: []const u8, archive: *CpioArchive, - directory: std.fs.Dir, + directory: *std.fs.Dir, ) anyerror!void { var iter = directory.iterate(); @@ -281,8 +275,12 @@ pub fn walkDirectory( const full_directory_path = try directory.realpath(".", &fullpath_buf); const directory_path = try std.fs.path.relative(arena.allocator(), starting_directory, full_directory_path); + std.log.debug("walking directory {s}", .{full_directory_path}); + // We don't need to add the root directory, as it will already exist. if (!std.mem.eql(u8, directory_path, "")) { + // Before iterating through the directory, add the directory itself to + // the archive. try archive.addDirectory(directory_path, 0o755); } diff --git a/src/runner.zig b/src/runner.zig index afaf8eba..f383d1c1 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -1,13 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); - -fn pathExists(p: []const u8) bool { - std.fs.cwd().access(p, .{}) catch { - return false; - }; - - return true; -} +const utils = @import("./utils.zig"); pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -34,7 +27,7 @@ pub fn main() !void { else => @compileError("don't know how to run qemu on build system"), }); - if (builtin.target.os.tag == .linux and pathExists("/dev/kvm")) { + if (builtin.target.os.tag == .linux and utils.absolutePathExists("/dev/kvm")) { try qemu_args.append("-enable-kvm"); } diff --git a/src/tboot-bless-boot-generator.zig b/src/tboot-bless-boot-generator.zig index 09b3af9a..dc88fa18 100644 --- a/src/tboot-bless-boot-generator.zig +++ b/src/tboot-bless-boot-generator.zig @@ -1,22 +1,53 @@ +const builtin = @import("builtin"); const std = @import("std"); +const clap = @import("clap"); -const GeneratorError = error{ - MissingNormalDir, - MissingEarlyDir, - MissingLateDir, -}; +pub const std_options = std.Options{ .log_level = if (builtin.mode == .Debug) .debug else .info }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); - var args = std.process.args(); + const params = comptime clap.parseParamsComptime( + \\-h, --help Display this help and exit. + \\