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::composefs_consts::COMPOSEFS_CMDLINE;
18
19#[context("Building UKI")]
29pub(crate) fn build_ukify(
30 rootfs: &Utf8Path,
31 extra_kargs: &[String],
32 args: &[OsString],
33) -> Result<()> {
34 if !extra_kargs.is_empty() {
36 tracing::warn!(
37 "The --karg flag is temporary and will be removed as soon as possible \
38 (https://github.com/bootc-dev/bootc/issues/1826)"
39 );
40 }
41
42 if !crate::utils::have_executable("ukify")? {
44 anyhow::bail!(
45 "ukify executable not found in PATH. Please install systemd-ukify or equivalent."
46 );
47 }
48
49 let root = Dir::open_ambient_dir(rootfs, cap_std_ext::cap_std::ambient_authority())
51 .with_context(|| format!("Opening rootfs {rootfs}"))?;
52
53 let kernel = crate::kernel::find_kernel(&root)?
55 .ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?;
56
57 let (vmlinuz_path, initramfs_path) = match kernel.path {
59 crate::kernel::KernelPath::Vmlinuz { path, initramfs } => (path, initramfs),
60 crate::kernel::KernelPath::Uki(path) => {
61 anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
62 }
63 };
64
65 if !root
67 .try_exists(&vmlinuz_path)
68 .context("Checking for vmlinuz")?
69 {
70 anyhow::bail!("Kernel not found at {vmlinuz_path}");
71 }
72 if !root
73 .try_exists(&initramfs_path)
74 .context("Checking for initramfs")?
75 {
76 anyhow::bail!("Initramfs not found at {initramfs_path}");
77 }
78
79 let composefs_digest = compute_composefs_digest(rootfs, None)?;
81
82 let mut cmdline = crate::bootc_kargs::get_kargs_in_root(&root, std::env::consts::ARCH)?;
84
85 let composefs_param = format!("{COMPOSEFS_CMDLINE}={composefs_digest}");
87 cmdline.extend(&Cmdline::from(composefs_param));
88
89 for karg in extra_kargs {
91 cmdline.extend(&Cmdline::from(karg));
92 }
93
94 let cmdline_str = cmdline.to_string();
95
96 let mut cmd = Command::new("ukify");
98 cmd.current_dir(rootfs);
99 cmd.arg("build")
100 .arg("--linux")
101 .arg(&vmlinuz_path)
102 .arg("--initrd")
103 .arg(&initramfs_path)
104 .arg("--uname")
105 .arg(&kernel.kernel.version)
106 .arg("--cmdline")
107 .arg(&cmdline_str)
108 .arg("--os-release")
109 .arg("@usr/lib/os-release");
110
111 cmd.args(args);
113
114 tracing::debug!("Executing ukify: {:?}", cmd);
115
116 cmd.run_inherited().context("Running ukify")?;
118
119 Ok(())
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::fs;
126
127 #[test]
128 fn test_build_ukify_no_kernel() {
129 let tempdir = tempfile::tempdir().unwrap();
130 let path = Utf8Path::from_path(tempdir.path()).unwrap();
131
132 let result = build_ukify(path, &[], &[]);
133 assert!(result.is_err());
134 let err = format!("{:#}", result.unwrap_err());
135 assert!(
136 err.contains("No kernel found") || err.contains("ukify executable not found"),
137 "Unexpected error message: {err}"
138 );
139 }
140
141 #[test]
142 fn test_build_ukify_already_uki() {
143 let tempdir = tempfile::tempdir().unwrap();
144 let path = Utf8Path::from_path(tempdir.path()).unwrap();
145
146 fs::create_dir_all(tempdir.path().join("boot/EFI/Linux")).unwrap();
148 fs::write(tempdir.path().join("boot/EFI/Linux/test.efi"), b"fake uki").unwrap();
149
150 let result = build_ukify(path, &[], &[]);
151 assert!(result.is_err());
152 let err = format!("{:#}", result.unwrap_err());
153 assert!(
154 err.contains("already contains a UKI") || err.contains("ukify executable not found"),
155 "Unexpected error message: {err}"
156 );
157 }
158}