1use std::path::Path;
8
9use anyhow::Result;
10use cap_std_ext::cap_std::fs::Dir;
11use cap_std_ext::dirext::CapStdExtDirExt;
12use serde::Serialize;
13
14use crate::bootc_composefs::boot::EFI_LINUX;
15
16#[derive(Debug, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub(crate) struct Kernel {
20 pub(crate) version: String,
24 pub(crate) unified: bool,
26}
27
28pub(crate) fn find_kernel(root: &Dir) -> Result<Option<Kernel>> {
36 if let Some(uki_filename) = find_uki_filename(root)? {
38 let version = uki_filename
39 .strip_suffix(".efi")
40 .unwrap_or(&uki_filename)
41 .to_owned();
42 return Ok(Some(Kernel {
43 version,
44 unified: true,
45 }));
46 }
47
48 if let Some(kernel_dir) = ostree_ext::bootabletree::find_kernel_dir_fs(root)? {
50 let version = kernel_dir
51 .file_name()
52 .ok_or_else(|| anyhow::anyhow!("kernel dir should have a file name: {kernel_dir}"))?
53 .to_owned();
54 return Ok(Some(Kernel {
55 version,
56 unified: false,
57 }));
58 }
59
60 Ok(None)
61}
62
63fn find_uki_filename(root: &Dir) -> Result<Option<String>> {
68 let Some(boot) = root.open_dir_optional(crate::install::BOOT)? else {
69 return Ok(None);
70 };
71 let Some(efi_linux) = boot.open_dir_optional(EFI_LINUX)? else {
72 return Ok(None);
73 };
74
75 let mut uki_files = Vec::new();
76 for entry in efi_linux.entries()? {
77 let entry = entry?;
78 let name = entry.file_name();
79 let name_path = Path::new(&name);
80 let extension = name_path.extension().and_then(|v| v.to_str());
81 if extension == Some("efi") {
82 if let Some(name_str) = name.to_str() {
83 uki_files.push(name_str.to_owned());
84 }
85 }
86 }
87
88 uki_files.sort();
90 Ok(uki_files.into_iter().next())
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use cap_std_ext::{cap_std, cap_tempfile, dirext::CapStdExtDirExt};
97
98 #[test]
99 fn test_find_kernel_none() -> Result<()> {
100 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
101 assert!(find_kernel(&tempdir)?.is_none());
102 Ok(())
103 }
104
105 #[test]
106 fn test_find_kernel_traditional() -> Result<()> {
107 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
108 tempdir.create_dir_all("usr/lib/modules/6.12.0-100.fc41.x86_64")?;
109 tempdir.atomic_write(
110 "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz",
111 b"fake kernel",
112 )?;
113
114 let kernel = find_kernel(&tempdir)?.expect("should find kernel");
115 assert_eq!(kernel.version, "6.12.0-100.fc41.x86_64");
116 assert!(!kernel.unified);
117 Ok(())
118 }
119
120 #[test]
121 fn test_find_kernel_uki() -> Result<()> {
122 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
123 tempdir.create_dir_all("boot/EFI/Linux")?;
124 tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?;
125
126 let kernel = find_kernel(&tempdir)?.expect("should find kernel");
127 assert_eq!(kernel.version, "fedora-6.12.0");
128 assert!(kernel.unified);
129 Ok(())
130 }
131
132 #[test]
133 fn test_find_kernel_uki_takes_precedence() -> Result<()> {
134 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
135 tempdir.create_dir_all("usr/lib/modules/6.12.0-100.fc41.x86_64")?;
137 tempdir.atomic_write(
138 "usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz",
139 b"fake kernel",
140 )?;
141 tempdir.create_dir_all("boot/EFI/Linux")?;
142 tempdir.atomic_write("boot/EFI/Linux/fedora-6.12.0.efi", b"fake uki")?;
143
144 let kernel = find_kernel(&tempdir)?.expect("should find kernel");
145 assert_eq!(kernel.version, "fedora-6.12.0");
147 assert!(kernel.unified);
148 Ok(())
149 }
150
151 #[test]
152 fn test_find_uki_filename_sorted() -> Result<()> {
153 let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
154 tempdir.create_dir_all("boot/EFI/Linux")?;
155 tempdir.atomic_write("boot/EFI/Linux/zzz.efi", b"fake uki")?;
156 tempdir.atomic_write("boot/EFI/Linux/aaa.efi", b"fake uki")?;
157 tempdir.atomic_write("boot/EFI/Linux/mmm.efi", b"fake uki")?;
158
159 let filename = find_uki_filename(&tempdir)?.expect("should find uki");
161 assert_eq!(filename, "aaa.efi");
162 Ok(())
163 }
164}