blob: 28455fed3eeaa32807cb412081aa7afa448d0d76 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{Error, Result};
use std::collections::BTreeMap;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
/// Builder for the Zircon Boot Image (ZBI), which takes in a kernel, BootFS, boot args, and kernel
/// command line.
#[derive(Default)]
pub struct ZbiBuilder {
kernel: Option<String>,
bootfs_files: BTreeMap<String, String>,
bootargs: Vec<String>,
cmdline: Vec<String>,
}
impl ZbiBuilder {
/// Set the kernel to be used.
pub fn set_kernel(&mut self, kernel: &str) {
self.kernel = Some(kernel.to_string());
}
/// Add a BootFS file to the ZBI.
pub fn add_bootfs_file(&mut self, source: &str, destination: &str) {
if self.bootfs_files.contains_key(&destination.to_string()) {
println!("Found duplicate bootfs destination: {}", destination);
return;
}
self.bootfs_files.insert(destination.to_string(), source.to_string());
}
/// Add a boot argument to the ZBI.
pub fn add_boot_arg(&mut self, arg: &str) {
self.bootargs.push(arg.to_string());
}
/// Add a kernel command line argument.
pub fn add_cmdline_arg(&mut self, arg: &str) {
self.cmdline.push(arg.to_string());
}
/// Build the ZBI.
pub fn build(&self, gendir: &PathBuf, output: &Path) -> Result<()> {
// Create the BootFS manifest file that lists all the files to insert
// into BootFS.
let mut bootfs_manifest_path = gendir.clone();
bootfs_manifest_path.push("bootfs_files.list");
let mut bootfs_manifest = File::create(&bootfs_manifest_path)
.map_err(|e| Error::new(e).context("failed to create the bootfs manifest"))?;
self.write_bootfs_manifest(&mut bootfs_manifest)?;
// Create the boot args file.
let mut boot_args_path = gendir.clone();
boot_args_path.push("boot_args.txt");
let mut boot_args = File::create(&boot_args_path)
.map_err(|e| Error::new(e).context("failed to create the boot args"))?;
self.write_boot_args(&mut boot_args)?;
// Run the zbi tool to construct the ZBI.
let zbi_args = self.build_zbi_args(&bootfs_manifest_path, &boot_args_path, output)?;
let status = Command::new("host_x64/zbi")
.args(&zbi_args)
.status()
.expect("Failed to run the zbi tool");
if !status.success() {
anyhow::bail!("zbi exited with status: {}", status);
}
Ok(())
}
fn write_bootfs_manifest(&self, out: &mut impl Write) -> Result<()> {
for (destination, source) in &self.bootfs_files {
write!(out, "{}={}\n", destination, source)?;
}
Ok(())
}
fn write_boot_args(&self, out: &mut impl Write) -> Result<()> {
for arg in &self.bootargs {
write!(out, "{}\n", arg)?;
}
Ok(())
}
fn build_zbi_args(
&self,
bootfs_manifest_path: &Path,
boot_args_path: &Path,
output_path: &Path,
) -> Result<Vec<String>> {
// Ensure the supplied kernel is a valid file.
let kernel_path;
if let Some(kernel) = &self.kernel {
kernel_path = Path::new(kernel);
} else {
anyhow::bail!("No kernel image supplied");
}
let mut args: Vec<String> = Vec::new();
args.push("--type=container".to_string());
args.push(kernel_path.to_string_lossy().into_owned());
args.push("--files".to_string());
args.push(bootfs_manifest_path.to_string_lossy().into_owned());
args.push("--type=image_args".to_string());
args.push(format!("--entry={}", boot_args_path.to_string_lossy()));
args.push("--type=cmdline".to_string());
for cmd in &self.cmdline {
args.push(format!("--entry={}", cmd));
}
args.push(format!("--output={}", output_path.to_string_lossy()));
Ok(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bootfs_manifest() {
let mut builder = ZbiBuilder::default();
let mut output: Vec<u8> = Vec::new();
builder.write_bootfs_manifest(&mut output).unwrap();
assert_eq!(output, b"");
output.clear();
builder.add_bootfs_file("path/to/file2", "bin/file2");
builder.add_bootfs_file("path/to/file1", "lib/file1");
builder.write_bootfs_manifest(&mut output).unwrap();
assert_eq!(output, b"bin/file2=path/to/file2\nlib/file1=path/to/file1\n");
}
#[test]
fn boot_args() {
let mut builder = ZbiBuilder::default();
let mut output: Vec<u8> = Vec::new();
builder.write_boot_args(&mut output).unwrap();
assert_eq!(output, b"");
output.clear();
builder.add_boot_arg("boot-arg1");
builder.add_boot_arg("boot-arg2");
builder.write_boot_args(&mut output).unwrap();
assert_eq!(output, b"boot-arg1\nboot-arg2\n");
}
#[test]
fn zbi_args_missing_kernel() {
let bootfs_manifest = Path::new("bootfs");
let boot_args = Path::new("bootargs");
let output = Path::new("output");
let builder = ZbiBuilder::default();
// We should fail without a kernel.
assert!(builder.build_zbi_args(bootfs_manifest, boot_args, output).is_err());
}
#[test]
fn zbi_args_with_kernel() {
let bootfs_manifest = Path::new("bootfs");
let boot_args = Path::new("bootargs");
let output = Path::new("output");
let mut builder = ZbiBuilder::default();
builder.set_kernel("path/to/kernel");
let args = builder.build_zbi_args(bootfs_manifest, boot_args, output).unwrap();
assert_eq!(
args,
[
"--type=container",
"path/to/kernel",
"--files",
"bootfs",
"--type=image_args",
"--entry=bootargs",
"--type=cmdline",
"--output=output",
]
);
}
#[test]
fn zbi_args_with_cmdline() {
let bootfs_manifest = Path::new("bootfs");
let boot_args = Path::new("bootargs");
let output = Path::new("output");
let mut builder = ZbiBuilder::default();
builder.set_kernel("path/to/kernel");
builder.add_cmdline_arg("cmd-arg1");
builder.add_cmdline_arg("cmd-arg2");
let args = builder.build_zbi_args(bootfs_manifest, boot_args, output).unwrap();
assert_eq!(
args,
[
"--type=container",
"path/to/kernel",
"--files",
"bootfs",
"--type=image_args",
"--entry=bootargs",
"--type=cmdline",
"--entry=cmd-arg1",
"--entry=cmd-arg2",
"--output=output",
]
);
}
}