1use std::path::Path;
8
9use anyhow::{Context, Result};
10use bootc_kernel_cmdline::utf8::Cmdline;
11use camino::Utf8PathBuf;
12use cap_std_ext::cap_std::fs::Dir;
13use cap_std_ext::dirext::CapStdExtDirExt;
14use cfsctl::composefs_boot;
15use serde::Serialize;
16
17use crate::bootc_composefs::boot::EFI_LINUX;
18
19#[derive(Debug, Serialize)]
21#[serde(rename_all = "kebab-case")]
22pub(crate) struct Kernel {
23 pub(crate) version: String,
27 pub(crate) unified: bool,
29}
30
31pub(crate) enum KernelType {
37 Uki {
38 path: Utf8PathBuf,
39 cmdline: Option<Cmdline<'static>>,
42 },
43 Vmlinuz {
44 path: Utf8PathBuf,
45 initramfs: Utf8PathBuf,
46 },
47}
48
49pub(crate) struct KernelInternal {
56 pub(crate) kernel: Kernel,
57 pub(crate) k_type: KernelType,
58}
59
60impl From<KernelInternal> for Kernel {
61 fn from(kernel_internal: KernelInternal) -> Self {
62 kernel_internal.kernel
63 }
64}
65
66pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
74 if let Some(uki_path) = find_uki_path(root)? {
76 let version = uki_path.file_stem().unwrap_or(uki_path.as_str()).to_owned();
77
78 let uki = root.read(&uki_path).context("Reading UKI")?;
79
80 let cmdline = composefs_boot::uki::get_section(&uki, ".cmdline");
82
83 let cmdline = match cmdline {
84 Some(Ok(cmdline)) => {
85 let cmdline_str = std::str::from_utf8(cmdline)?;
86 Some(Cmdline::from(cmdline_str.to_owned()))
87 }
88
89 Some(Err(uki_error)) => match uki_error {
90 composefs_boot::uki::UkiError::MissingSection(_) => {
91 None
94 }
95
96 e => anyhow::bail!("Failed to read UKI cmdline: {e:?}"),
97 },
98
99 None => None,
100 };
101
102 return Ok(Some(KernelInternal {
103 kernel: Kernel {
104 version,
105 unified: true,
106 },
107 k_type: KernelType::Uki {
108 path: uki_path,
109 cmdline,
110 },
111 }));
112 }
113
114 if let Some(modules_dir) = ostree_ext::bootabletree::find_kernel_dir_fs(root)? {
116 let version = modules_dir
117 .file_name()
118 .ok_or_else(|| anyhow::anyhow!("kernel dir should have a file name: {modules_dir}"))?
119 .to_owned();
120 let vmlinuz = modules_dir.join("vmlinuz");
121 let initramfs = modules_dir.join("initramfs.img");
122 return Ok(Some(KernelInternal {
123 kernel: Kernel {
124 version,
125 unified: false,
126 },
127 k_type: KernelType::Vmlinuz {
128 path: vmlinuz,
129 initramfs,
130 },
131 }));
132 }
133
134 Ok(None)
135}
136
137fn find_uki_path(root: &Dir) -> Result<Option<Utf8PathBuf>> {
142 let Some(boot) = root.open_dir_optional(crate::install::BOOT)? else {
143 return Ok(None);
144 };
145 let Some(efi_linux) = boot.open_dir_optional(EFI_LINUX)? else {
146 return Ok(None);
147 };
148
149 let mut uki_files = Vec::new();
150 for entry in efi_linux.entries()? {
151 let entry = entry?;
152 let name = entry.file_name();
153 let name_path = Path::new(&name);
154 let extension = name_path.extension().and_then(|v| v.to_str());
155 if extension == Some("efi") {
156 if let Some(name_str) = name.to_str() {
157 uki_files.push(name_str.to_owned());
158 }
159 }
160 }
161
162 uki_files.sort();
164 Ok(uki_files
165 .into_iter()
166 .next()
167 .map(|filename| Utf8PathBuf::from(format!("boot/{EFI_LINUX}/{filename}"))))
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use cap_std_ext::{cap_std, cap_tempfile, dirext::CapStdExtDirExt};
174
175 #[test]
176 fn test_find_kernel_none() -> Result<()> {
177 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
178 assert!(find_kernel(&tempdir)?.is_none());
179 Ok(())
180 }
181
182 #[test]
183 fn test_find_kernel_traditional() -> Result<()> {
184 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
185 tempdir.create_dir_all("usr/lib/modules/6.12.0-100.fc41.x86_64")?;
186 tempdir.atomic_write(
187 "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz",
188 b"fake kernel",
189 )?;
190
191 let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
192 assert_eq!(kernel_internal.kernel.version, "6.12.0-100.fc41.x86_64");
193 assert!(!kernel_internal.kernel.unified);
194 match &kernel_internal.k_type {
195 KernelType::Vmlinuz { path, initramfs } => {
196 assert_eq!(
197 path.as_str(),
198 "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
199 );
200 assert_eq!(
201 initramfs.as_str(),
202 "usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
203 );
204 }
205 KernelType::Uki { .. } => panic!("Expected Vmlinuz, got Uki"),
206 }
207 Ok(())
208 }
209
210 #[test]
211 fn test_find_kernel_uki() -> Result<()> {
212 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
213 tempdir.create_dir_all("boot/EFI/Linux")?;
214 tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?;
215
216 let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
217 assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0");
218 assert!(kernel_internal.kernel.unified);
219 match &kernel_internal.k_type {
220 KernelType::Uki { path, .. } => {
221 assert_eq!(path.as_str(), "boot/EFI/Linux/fedora-6.12.0.efi");
222 }
223 KernelType::Vmlinuz { .. } => panic!("Expected Uki, got Vmlinuz"),
224 }
225 Ok(())
226 }
227
228 #[test]
229 fn test_find_kernel_uki_takes_precedence() -> Result<()> {
230 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
231 tempdir.create_dir_all("usr/lib/modules/6.12.0-100.fc41.x86_64")?;
233 tempdir.atomic_write(
234 "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz",
235 b"fake kernel",
236 )?;
237 tempdir.create_dir_all("boot/EFI/Linux")?;
238 tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?;
239
240 let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
241 assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0");
243 assert!(kernel_internal.kernel.unified);
244 Ok(())
245 }
246
247 #[test]
248 fn test_find_uki_path_sorted() -> Result<()> {
249 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
250 tempdir.create_dir_all("boot/EFI/Linux")?;
251 tempdir.atomic_write("boot/EFI/Linux/zzz.efi", b"fake uki")?;
252 tempdir.atomic_write("boot/EFI/Linux/aaa.efi", b"fake uki")?;
253 tempdir.atomic_write("boot/EFI/Linux/mmm.efi", b"fake uki")?;
254
255 let path = find_uki_path(&tempdir)?.expect("should find uki");
257 assert_eq!(path.as_str(), "boot/EFI/Linux/aaa.efi");
258 Ok(())
259 }
260}