1use std::ffi::OsString;
7use std::process::Command;
8
9use anyhow::{Context, Result};
10use bootc_kernel_cmdline::utf8::Cmdline;
11use bootc_utils::CommandRunExt;
12use camino::Utf8Path;
13use cap_std_ext::cap_std::fs::Dir;
14use fn_error_context::context;
15
16use crate::bootc_composefs::digest::compute_composefs_digest;
17use crate::bootc_composefs::status::ComposefsCmdline;
18
19#[context("Building UKI")]
29pub(crate) async fn build_ukify(
30 rootfs: &Utf8Path,
31 extra_kargs: &[String],
32 args: &[OsString],
33 allow_missing_fsverity: bool,
34 write_dumpfile_to: Option<&Utf8Path>,
35) -> Result<()> {
36 if !extra_kargs.is_empty() {
38 tracing::warn!(
39 "The --karg flag is temporary and will be removed as soon as possible \
40 (https://github.com/bootc-dev/bootc/issues/1826)"
41 );
42 }
43
44 if !crate::utils::have_executable("ukify")? {
46 anyhow::bail!(
47 "ukify executable not found in PATH. Please install systemd-ukify or equivalent."
48 );
49 }
50
51 let root = Dir::open_ambient_dir(rootfs, cap_std_ext::cap_std::ambient_authority())
53 .with_context(|| format!("Opening rootfs {rootfs}"))?;
54
55 let kernel = crate::kernel::find_kernel(&root)?
57 .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?;
58
59 let (vmlinuz_path, initramfs_path) = match kernel.k_type {
61 crate::kernel::KernelType::Vmlinuz { path, initramfs } => (path, initramfs),
62 crate::kernel::KernelType::Uki { path, .. } => {
63 anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
64 }
65 };
66
67 if !root
69 .try_exists(&vmlinuz_path)
70 .context("Checking for vmlinuz")?
71 {
72 anyhow::bail!("Kernel not found at {vmlinuz_path}");
73 }
74 if !root
75 .try_exists(&initramfs_path)
76 .context("Checking for initramfs")?
77 {
78 anyhow::bail!("Initramfs not found at {initramfs_path}");
79 }
80
81 let composefs_digest = compute_composefs_digest(rootfs, write_dumpfile_to).await?;
83
84 let mut cmdline = crate::bootc_kargs::get_kargs_in_root(&root, std::env::consts::ARCH)?;
86
87 cmdline.extend(&Cmdline::from(
89 ComposefsCmdline::build(&composefs_digest, allow_missing_fsverity).to_string(),
90 ));
91
92 for karg in extra_kargs {
94 cmdline.extend(&Cmdline::from(karg));
95 }
96
97 let cmdline_str = cmdline.to_string();
98
99 let mut cmd = Command::new("ukify");
101 cmd.current_dir(rootfs);
102 cmd.arg("build")
103 .arg("--linux")
104 .arg(&vmlinuz_path)
105 .arg("--initrd")
106 .arg(&initramfs_path)
107 .arg("--uname")
108 .arg(&kernel.kernel.version)
109 .arg("--cmdline")
110 .arg(&cmdline_str)
111 .arg("--os-release")
112 .arg("@usr/lib/os-release");
113
114 cmd.args(args);
116
117 tracing::debug!("Executing ukify: {:?}", cmd);
118
119 cmd.run_inherited().context("Running ukify")?;
121
122 Ok(())
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use std::fs;
129
130 #[tokio::test]
131 async fn test_build_ukify_no_kernel() {
132 let tempdir = tempfile::tempdir().unwrap();
133 let path = Utf8Path::from_path(tempdir.path()).unwrap();
134
135 let result = build_ukify(path, &[], &[], false, None).await;
136 assert!(result.is_err());
137 let err = format!("{:#}", result.unwrap_err());
138 assert!(
139 err.contains("No kernel found") || err.contains("ukify executable not found"),
140 "Unexpected error message: {err}"
141 );
142 }
143
144 #[tokio::test]
145 async fn test_build_ukify_already_uki() {
146 let tempdir = tempfile::tempdir().unwrap();
147 let path = Utf8Path::from_path(tempdir.path()).unwrap();
148
149 fs::create_dir_all(tempdir.path().join("boot/EFI/Linux")).unwrap();
151 fs::write(tempdir.path().join("boot/EFI/Linux/test.efi"), b"fake uki").unwrap();
152
153 let result = build_ukify(path, &[], &[], false, None).await;
154 assert!(result.is_err());
155 let err = format!("{:#}", result.unwrap_err());
156 assert!(
157 err.contains("already contains a UKI") || err.contains("ukify executable not found"),
158 "Unexpected error message: {err}"
159 );
160 }
161}