Skip to main content

bootc_lib/
aboot.rs

1//! Build Android boot or ukiboot images using aboot-update.
2
3use std::fs::File;
4use std::io::BufReader;
5use std::process::Command;
6
7use anyhow::{Context, Result, ensure};
8use bootc_utils::CommandRunExt;
9use camino::Utf8Path;
10use fn_error_context::context;
11use linux_kernel_cmdline::utf8::Cmdline;
12
13use crate::boot_artifact::prepare_boot_artifact;
14use crate::cli::BootArtifactErofsVersionArg;
15
16#[context("Building aboot image")]
17pub(crate) async fn build_aboot(
18    rootfs: &Utf8Path,
19    kernel_dir: Option<&Utf8Path>,
20    out: Option<&Utf8Path>,
21    allow_missing_fsverity: bool,
22    erofs_version: Option<BootArtifactErofsVersionArg>,
23    write_dumpfile_to: Option<&Utf8Path>,
24) -> Result<()> {
25    if !crate::utils::have_executable("aboot-update")? {
26        anyhow::bail!("aboot-update executable not found in PATH");
27    }
28
29    let inputs = prepare_boot_artifact(
30        rootfs,
31        kernel_dir,
32        &[],
33        allow_missing_fsverity,
34        erofs_version,
35        write_dumpfile_to,
36    )
37    .await?;
38
39    let mut cmd = Command::new("aboot-update");
40    cmd.arg("--root")
41        .arg(rootfs)
42        .arg("--cmdline")
43        .arg(&inputs.cmdline);
44    if let Some(kernel_dir) = kernel_dir {
45        cmd.arg("--kernel-dir").arg(kernel_dir);
46    }
47    if let Some(out) = out {
48        cmd.arg("--out").arg(out);
49    }
50    cmd.arg(&inputs.kernel_version);
51
52    tracing::debug!("Executing aboot-update: {:?}", cmd);
53    cmd.run_inherited().context("Running aboot-update")?;
54
55    let output_dir = out
56        .map(ToOwned::to_owned)
57        .unwrap_or_else(|| rootfs.join("boot"));
58    let image_path = output_dir.join(format!("aboot-{}.img", inputs.kernel_version));
59    let actual_cmdline = read_cmdline(&image_path)?;
60    validate_composefs_cmdline(&inputs.cmdline, &actual_cmdline)
61}
62
63fn read_cmdline(path: &Utf8Path) -> Result<String> {
64    let image = File::open(path).with_context(|| format!("Opening generated image {path}"))?;
65    let mut image = BufReader::new(image);
66    let (_, cmdline) = crate::kernel::read_aboot_metadata(&mut image)
67        .with_context(|| format!("Parsing generated aboot image {path}"))?;
68    Ok(cmdline.to_string())
69}
70
71fn validate_composefs_cmdline(expected: &str, actual: &str) -> Result<()> {
72    let composefs_args = |cmdline: &str| -> Vec<String> {
73        Cmdline::from(cmdline)
74            .iter_str()
75            .filter(|arg| arg.starts_with("composefs=") || arg.starts_with("composefs.digest="))
76            .map(str::to_owned)
77            .collect()
78    };
79    let mut expected = composefs_args(expected);
80    ensure!(
81        !expected.is_empty(),
82        "Generated command line has no composefs parameter"
83    );
84    let mut actual = composefs_args(actual);
85    expected.sort_unstable();
86    actual.sort_unstable();
87    ensure!(
88        actual == expected,
89        "Generated image has the wrong composefs parameters"
90    );
91    Ok(())
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn validates_composefs_cmdline() {
100        validate_composefs_cmdline("quiet composefs=?abcd", "bootconfig composefs=?abcd").unwrap();
101        validate_composefs_cmdline(
102            "composefs.digest=v1-sha512-12:abcd composefs=efgh",
103            "composefs=efgh composefs.digest=v1-sha512-12:abcd",
104        )
105        .unwrap();
106
107        for actual in [
108            "quiet",
109            "composefs=efgh",
110            "composefs=abcd composefs=abcd",
111            "composefs=?abcd composefs=?abcd",
112            "composefs.digest=v1-sha512-12:abcd composefs=efgh composefs=efgh",
113        ] {
114            assert!(validate_composefs_cmdline("composefs=?abcd", actual).is_err());
115        }
116    }
117}