1use 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}