[assembly] Assemble the ZBI
1. Generate the merkle root for the base package
2. Create a ZbiBuilder
3. Pass the command-line arguments (with the merkle) to ZbiBuilder
4. Pass the kernel to ZbiBuilder
5. Pass the bootfs files to ZbiBuilder
6. Build the ZBI with ZbiBuilder
Test: fx test zbi_test ffx_assembly_test
Bug: 73175
Change-Id: Ia770e3c2d65894a2298ce4f6599fc70931d0a072
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/521135
Reviewed-by: Aaron Wood <aaronwood@google.com>
Commit-Queue: Aidan Wolter <awolter@google.com>
Fuchsia-Auto-Submit: Aidan Wolter <awolter@google.com>
diff --git a/src/developer/ffx/plugins/assembly/BUILD.gn b/src/developer/ffx/plugins/assembly/BUILD.gn
index 87c8bee..320757c 100644
--- a/src/developer/ffx/plugins/assembly/BUILD.gn
+++ b/src/developer/ffx/plugins/assembly/BUILD.gn
@@ -21,8 +21,11 @@
deps = [
"//src/lib/assembly/base_package",
"//src/lib/assembly/vbmeta",
+ "//src/lib/assembly/zbi",
"//src/lib/zerocopy",
"//src/sys/pkg/lib/far/rust:fuchsia-archive",
+ "//src/sys/pkg/lib/fuchsia-hash",
+ "//src/sys/pkg/lib/fuchsia-merkle",
"//src/sys/pkg/lib/fuchsia-pkg",
"//third_party/rust_crates:hex",
"//third_party/rust_crates:serde",
diff --git a/src/developer/ffx/plugins/assembly/src/operations/image.rs b/src/developer/ffx/plugins/assembly/src/operations/image.rs
index 3b9dc11..52c3bdb 100644
--- a/src/developer/ffx/plugins/assembly/src/operations/image.rs
+++ b/src/developer/ffx/plugins/assembly/src/operations/image.rs
@@ -4,26 +4,34 @@
pub mod config;
-use anyhow::Result;
+use anyhow::{Context, Result};
use assembly_base_package::BasePackageBuilder;
use config::Config;
use ffx_assembly_args::ImageArgs;
-use ffx_core::ffx_bail;
+use ffx_core::{ffx_bail, ffx_error};
+use fuchsia_hash::Hash;
+use fuchsia_merkle::MerkleTree;
use fuchsia_pkg::PackageManifest;
-use std::fs::File;
+use std::fs::{File, OpenOptions};
use std::io::BufReader;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
+use zbi::ZbiBuilder;
pub fn assemble(args: ImageArgs) -> Result<()> {
let config = read_config(&args.config)?;
- let _base_package = construct_base_package(&args.gendir, &config)?;
+ let base_package = construct_base_package(&args.gendir, &config)?;
+ let base_merkle = MerkleTree::from_reader(&base_package)
+ .context("Failed to calculate the base merkle")?
+ .root();
+ println!("Base merkle: {}", base_merkle);
+ let _zbi = construct_zbi(&args.gendir, &config, Some(base_merkle))?;
+
Ok(())
}
fn read_config(config_path: &String) -> Result<Config> {
let mut config = File::open(config_path)?;
- let config = Config::from_reader(&mut config)
- .or_else(|e| ffx_bail!("Failed to read the image config: {}", e))?;
+ let config = Config::from_reader(&mut config).context("Failed to read the image config")?;
println!("Config indicated version: {}", config.version);
Ok(config)
}
@@ -42,7 +50,11 @@
let pkg_manifest = pkg_manifest_from_path(pkg_manifest_path);
base_pkg_builder.add_cache_package(pkg_manifest);
}
- let mut base_package = File::create("base.far")
+ let mut base_package = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open("base.far")
.or_else(|e| ffx_bail!("Failed to create the base package file: {}", e))?;
base_pkg_builder
.build(gendir, &mut base_package)
@@ -51,8 +63,58 @@
Ok(base_package)
}
-fn pkg_manifest_from_path(path: &String) -> PackageManifest {
- let manifest_file = std::fs::File::open(path).unwrap();
+fn pkg_manifest_from_path(path: &str) -> PackageManifest {
+ let manifest_file = File::open(path).unwrap();
let pkg_manifest_reader = BufReader::new(manifest_file);
serde_json::from_reader(pkg_manifest_reader).unwrap()
}
+
+fn construct_zbi(gendir: &PathBuf, config: &Config, base_merkle: Option<Hash>) -> Result<File> {
+ let mut zbi_builder = ZbiBuilder::default();
+
+ // Add the kernel image.
+ zbi_builder.set_kernel(&config.kernel_image);
+
+ // Instruct devmgr that a /system volume is required.
+ zbi_builder.add_boot_arg("devmgr.require-system=true");
+
+ // If a base merkle is supplied, then add the boot arguments for startup up pkgfs with the
+ // merkle of the Base Package.
+ if let Some(base_merkle) = base_merkle {
+ // Specify how to launch pkgfs: bin/pkgsvr <base-merkle>
+ zbi_builder.add_boot_arg(&format!("zircon.system.pkgfs.cmd=bin/pkgsvr+{}", base_merkle));
+
+ // Add the pkgfs blobs to the boot arguments, so that pkgfs can be bootstrapped out of blobfs,
+ // before the blobfs service is available.
+ let pkgfs_manifest: PackageManifest = config
+ .base_packages
+ .iter()
+ .map(String::as_str)
+ .map(pkg_manifest_from_path)
+ .find(|m| m.name() == "pkgfs")
+ .ok_or_else(|| ffx_error!("Failed to find pkgfs in the base packages"))?;
+
+ pkgfs_manifest.into_blobs().into_iter().filter(|b| b.path != "meta/").for_each(|b| {
+ zbi_builder.add_boot_arg(&format!("zircon.system.pkgfs.file.{}={}", b.path, b.merkle));
+ });
+ }
+
+ // Add the command line.
+ for cmd in &config.kernel_cmdline {
+ zbi_builder.add_cmdline_arg(cmd);
+ }
+
+ // Add the BootFS files.
+ for bootfs_entry in &config.bootfs_files {
+ zbi_builder.add_bootfs_file(&bootfs_entry.source, &bootfs_entry.destination);
+ }
+
+ // Build and return the ZBI.
+ zbi_builder.build(gendir, Path::new("myfuchsia.zbi"))?;
+ let zbi = OpenOptions::new()
+ .read(true)
+ .open("myfuchsia.zbi")
+ .or_else(|e| ffx_bail!("Failed to open the zbi: {}", e))?;
+ println!("ZBI: myfuchsia.zbi");
+ Ok(zbi)
+}
diff --git a/src/developer/ffx/plugins/assembly/src/operations/image/config.rs b/src/developer/ffx/plugins/assembly/src/operations/image/config.rs
index 1c744cd..6b4bcba 100644
--- a/src/developer/ffx/plugins/assembly/src/operations/image/config.rs
+++ b/src/developer/ffx/plugins/assembly/src/operations/image/config.rs
@@ -16,7 +16,7 @@
pub meta_packages: Vec<String>,
pub kernel_image: String,
pub kernel_cmdline: Vec<String>,
- pub bootfs_files: Vec<String>,
+ pub bootfs_files: Vec<BootFsEntry>,
pub vbmeta_key: String,
pub vbmeta_key_metadata: String,
pub version: String,
@@ -24,6 +24,12 @@
}
#[derive(Deserialize, Serialize)]
+pub(crate) struct BootFsEntry {
+ pub source: String,
+ pub destination: String,
+}
+
+#[derive(Deserialize, Serialize)]
pub(crate) struct BoardConfig {
pub name: String,
pub bootloader: String,
@@ -70,7 +76,12 @@
meta_packages: ["package5", "package6"],
kernel_image: "path/to/kernel",
kernel_cmdline: ["arg1", "arg2"],
- bootfs_files: ["file1", "file2"],
+ bootfs_files: [
+ {
+ source: "path/to/source",
+ destination: "path/to/destination",
+ },
+ ],
vbmeta_key: "key",
vbmeta_key_metadata: "metadata",
version: "0.1.2",
diff --git a/src/lib/assembly/BUILD.gn b/src/lib/assembly/BUILD.gn
index 741734e7..0d4c5e7 100644
--- a/src/lib/assembly/BUILD.gn
+++ b/src/lib/assembly/BUILD.gn
@@ -7,5 +7,6 @@
deps = [
"base_package:tests",
"vbmeta:tests",
+ "zbi:tests",
]
}
diff --git a/src/lib/assembly/zbi/BUILD.gn b/src/lib/assembly/zbi/BUILD.gn
new file mode 100644
index 0000000..02f69a5
--- /dev/null
+++ b/src/lib/assembly/zbi/BUILD.gn
@@ -0,0 +1,23 @@
+# 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.
+
+import("//build/rust/rustc_library.gni")
+
+rustc_library("zbi") {
+ version = "0.1.0"
+ with_unit_tests = true
+ deps = [
+ "//third_party/rust_crates:anyhow",
+ "//third_party/rust_crates:tempfile",
+ ]
+ sources = [
+ "src/lib.rs",
+ "src/zbi.rs",
+ ]
+}
+
+group("tests") {
+ testonly = true
+ deps = [ ":zbi_test($host_toolchain)" ]
+}
diff --git a/src/lib/assembly/zbi/src/lib.rs b/src/lib/assembly/zbi/src/lib.rs
new file mode 100644
index 0000000..517dc31
--- /dev/null
+++ b/src/lib/assembly/zbi/src/lib.rs
@@ -0,0 +1,11 @@
+// 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.
+
+#![deny(missing_docs)]
+
+//! Library for constructing the ZBI.
+
+mod zbi;
+
+pub use zbi::ZbiBuilder;
diff --git a/src/lib/assembly/zbi/src/zbi.rs b/src/lib/assembly/zbi/src/zbi.rs
new file mode 100644
index 0000000..28455fe
--- /dev/null
+++ b/src/lib/assembly/zbi/src/zbi.rs
@@ -0,0 +1,217 @@
+// 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",
+ ]
+ );
+ }
+}