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;
18use crate::kernel::KernelInternal;
19
20#[context("Building UKI")]
30pub(crate) async fn build_ukify(
31 rootfs: &Utf8Path,
32 extra_kargs: &[String],
33 args: &[OsString],
34 kernel: Option<KernelInternal>,
35 allow_missing_fsverity: bool,
36 write_dumpfile_to: Option<&Utf8Path>,
37) -> Result<()> {
38 if !extra_kargs.is_empty() {
40 tracing::warn!(
41 "The --karg flag is temporary and will be removed as soon as possible \
42 (https://github.com/bootc-dev/bootc/issues/1826)"
43 );
44 }
45
46 if !crate::utils::have_executable("ukify")? {
48 anyhow::bail!(
49 "ukify executable not found in PATH. Please install systemd-ukify or equivalent."
50 );
51 }
52
53 let root = Dir::open_ambient_dir(rootfs, cap_std_ext::cap_std::ambient_authority())
55 .with_context(|| format!("Opening rootfs {rootfs}"))?;
56
57 let kernel_final = match kernel {
58 Some(ref kernel) => kernel,
59 None => &crate::kernel::find_kernel(&root)?
60 .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?,
61 };
62
63 let (vmlinuz_path, initramfs_path) = match &kernel_final.k_type {
65 crate::kernel::KernelType::Vmlinuz { path, initramfs } => (path, initramfs),
66 crate::kernel::KernelType::Uki { path, .. } => {
67 anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
68 }
69 };
70
71 if kernel.is_some() {
76 if !vmlinuz_path.exists() {
77 anyhow::bail!("Kernel not found at {vmlinuz_path}");
78 }
79
80 if !initramfs_path.exists() {
81 anyhow::bail!("Initramfs not found at {initramfs_path}");
82 }
83 } else {
84 if !root
85 .try_exists(&vmlinuz_path)
86 .context("Checking for vmlinuz")?
87 {
88 anyhow::bail!("Kernel not found at {vmlinuz_path}");
89 }
90
91 if !root
92 .try_exists(&initramfs_path)
93 .context("Checking for initramfs")?
94 {
95 anyhow::bail!("Initramfs not found at {initramfs_path}");
96 }
97 }
98
99 let composefs_digest = compute_composefs_digest(rootfs, write_dumpfile_to).await?;
101
102 let mut cmdline = crate::bootc_kargs::get_kargs_in_root(&root, std::env::consts::ARCH)?;
104
105 cmdline.extend(&Cmdline::from(
107 ComposefsCmdline::build(&composefs_digest, allow_missing_fsverity).to_string(),
108 ));
109
110 for karg in extra_kargs {
112 cmdline.extend(&Cmdline::from(karg));
113 }
114
115 let cmdline_str = cmdline.to_string();
116
117 let mut cmd = Command::new("ukify");
119 cmd.current_dir(rootfs);
120 cmd.arg("build")
121 .arg("--linux")
122 .arg(&vmlinuz_path)
123 .arg("--initrd")
124 .arg(&initramfs_path)
125 .arg("--uname")
126 .arg(&kernel_final.kernel.version)
127 .arg("--cmdline")
128 .arg(&cmdline_str)
129 .arg("--os-release")
130 .arg("@usr/lib/os-release");
131
132 cmd.args(args);
134
135 tracing::debug!("Executing ukify: {:?}", cmd);
136
137 cmd.run_inherited().context("Running ukify")?;
139
140 Ok(())
141}
142
143#[cfg(test)]
144mod tests {
145 use bootc_utils::create_minimal_pe;
146
147 use super::*;
148 use std::fs;
149
150 #[tokio::test]
151 async fn test_build_ukify_no_kernel() {
152 let tempdir = tempfile::tempdir().unwrap();
153 let path = Utf8Path::from_path(tempdir.path()).unwrap();
154
155 let result = build_ukify(path, &[], &[], None, false, None).await;
156 assert!(result.is_err());
157 let err = format!("{:#}", result.unwrap_err());
158 assert!(
159 err.contains("No kernel found") || err.contains("ukify executable not found"),
160 "Unexpected error message: {err}"
161 );
162 }
163
164 #[tokio::test]
165 async fn test_build_ukify_already_uki() {
166 let tempdir = tempfile::tempdir().unwrap();
167 let path = Utf8Path::from_path(tempdir.path()).unwrap();
168
169 fs::create_dir_all(tempdir.path().join("boot/EFI/Linux")).unwrap();
171 fs::write(
172 tempdir.path().join("boot/EFI/Linux/test.efi"),
173 &create_minimal_pe(),
174 )
175 .unwrap();
176
177 let result = build_ukify(path, &[], &[], None, false, None).await;
178 assert!(result.is_err());
179 let err = format!("{:#}", result.unwrap_err());
180 assert!(
181 err.contains("already contains a UKI") || err.contains("ukify executable not found"),
182 "Unexpected error message: {err}"
183 );
184 }
185}