1use crate::consts::KernelPaths;
2use crate::consts::{CONFIG_FILENAME, KERNEL_IMAGE_PATH, MAKE_COMMAND};
3use crate::discovery::VersionEntry;
4use crate::error::BuilderErr;
5use std::path::{Path, PathBuf};
6use std::process::Output;
7use tracing::info;
8
9#[cfg(feature = "dracut")]
10use crate::consts::DRACUT_COMMAND;
11
12#[derive(Debug)]
13pub struct BootManager {
14 paths: KernelPaths,
15 keep_last_kernel: bool,
16 last_kernel_suffix: String,
17}
18
19impl BootManager {
20 #[must_use]
21 pub fn new(
22 paths: KernelPaths,
23 keep_last_kernel: bool,
24 last_kernel_suffix: Option<String>,
25 ) -> Self {
26 Self {
27 paths,
28 keep_last_kernel,
29 last_kernel_suffix: last_kernel_suffix.unwrap_or_else(|| "prev".to_string()),
30 }
31 }
32
33 pub fn link_kernel_config(&self, kernel_path: &Path) -> Result<(), BuilderErr> {
39 let link = kernel_path.join(CONFIG_FILENAME);
40 let dot_config = &self.paths.kernel_config;
41
42 if link.exists() {
43 if link.is_symlink() {
44 std::fs::remove_file(&link)
45 .map_err(|e| BuilderErr::linking_file_error(e, link.clone()))?;
46 } else {
47 let mut old_file = link.clone();
48 old_file.set_file_name(format!("{CONFIG_FILENAME}.old"));
49 std::fs::copy(&link, &old_file)
50 .map_err(|e| BuilderErr::linking_file_error(e, link.clone()))?;
51 }
52 }
53
54 std::os::unix::fs::symlink(dot_config, &link)
55 .map_err(|e| BuilderErr::linking_file_error(e, link.clone()))?;
56
57 Ok(())
58 }
59
60 pub fn update_linux_symlink(&self, version_entry: &VersionEntry) -> Result<(), BuilderErr> {
66 let linux = &self.paths.linux_symlink;
67
68 if linux.exists() || linux.is_symlink() {
69 if let Ok(target) = linux.read_link() {
70 if target == version_entry.path {
71 info!(
72 "Linux symlink already points to {}",
73 version_entry.version_string
74 );
75 return Ok(());
76 }
77 }
78 std::fs::remove_file(linux)
79 .map_err(|e| BuilderErr::linking_file_error(e, linux.clone()))?;
80 }
81
82 std::os::unix::fs::symlink(&version_entry.path, linux)
83 .map_err(|e| BuilderErr::linking_file_error(e, linux.clone()))?;
84
85 info!("Updated linux symlink to {}", version_entry.version_string);
86 Ok(())
87 }
88
89 pub fn check_new_config_options(&self, kernel_path: &Path) -> Result<bool, BuilderErr> {
95 let output = duct::cmd(MAKE_COMMAND, &["listnewconfig"])
96 .dir(kernel_path)
97 .stdout_capture()
98 .run()
99 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
100
101 let stdout = String::from_utf8_lossy(&output.stdout);
102 Ok(!stdout.trim().is_empty())
103 }
104
105 pub fn run_olddefconfig(&self, kernel_path: &Path) -> Result<(), BuilderErr> {
111 info!("Running make olddefconfig to integrate new kernel options");
112
113 let output = duct::cmd(MAKE_COMMAND, &["olddefconfig"])
114 .dir(kernel_path)
115 .stdout_capture()
116 .stderr_capture()
117 .run()
118 .map_err(|e| {
119 BuilderErr::CommandError(format!("Failed to run make olddefconfig: {e}"))
120 })?;
121
122 if !output.status.success() {
123 let stderr = String::from_utf8_lossy(&output.stderr);
124 return Err(BuilderErr::kernel_config_update_error(stderr.to_string()));
125 }
126
127 let mut old_config = self.paths.kernel_config.clone();
128 old_config.pop();
129 old_config.push(format!("{CONFIG_FILENAME}.old"));
130 std::fs::copy(&self.paths.kernel_config, &old_config)
131 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
132
133 std::fs::copy(kernel_path.join(CONFIG_FILENAME), &self.paths.kernel_config)
134 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
135
136 std::fs::remove_file(kernel_path.join(format!("{CONFIG_FILENAME}.old"))).ok();
137
138 let dot_config = kernel_path.join(CONFIG_FILENAME);
139 std::fs::remove_file(&dot_config).ok();
140 std::os::unix::fs::symlink(&self.paths.kernel_config, &dot_config)
141 .map_err(|e| BuilderErr::linking_file_error(e, dot_config))?;
142
143 Ok(())
144 }
145
146 pub fn build_kernel(&self, kernel_path: &Path, threads: usize) -> Result<Output, BuilderErr> {
152 info!("Building kernel with {threads} threads");
153
154 let output = duct::cmd(MAKE_COMMAND, &["-j".to_string(), threads.to_string()])
155 .dir(kernel_path)
156 .stdout_capture()
157 .stderr_capture()
158 .run()
159 .map_err(|e| BuilderErr::CommandError(format!("Kernel build failed: {e}")))?;
160
161 if !output.status.success() {
162 let stderr = String::from_utf8_lossy(&output.stderr);
163 return Err(BuilderErr::KernelBuildFail(stderr.to_string()));
164 }
165
166 Ok(output)
167 }
168
169 pub fn install_kernel(&self, kernel_path: &Path, replace: bool) -> Result<(), BuilderErr> {
175 let kernel_image = kernel_path.join(KERNEL_IMAGE_PATH);
176
177 if !kernel_image.exists() {
178 return Err(BuilderErr::KernelBuildFail(format!(
179 "Kernel image not found at {}",
180 kernel_image.display()
181 )));
182 }
183
184 if self.keep_last_kernel && !replace && self.paths.kernel_image.exists() {
185 let backup_path = self.paths.backup_path(
186 &self.paths.kernel_image,
187 &format!("-{}", self.last_kernel_suffix),
188 );
189 info!("Backing up current kernel to {}", backup_path.display());
190 std::fs::copy(&self.paths.kernel_image, &backup_path)
191 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
192 }
193
194 info!("Installing kernel to {}", self.paths.kernel_image.display());
195 std::fs::copy(&kernel_image, &self.paths.kernel_image)
196 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
197
198 Ok(())
199 }
200
201 pub fn install_modules(&self, kernel_path: &Path) -> Result<Output, BuilderErr> {
207 info!("Installing kernel modules");
208
209 let output = duct::cmd(MAKE_COMMAND, &["modules_install"])
210 .dir(kernel_path)
211 .stdout_capture()
212 .stderr_capture()
213 .run()
214 .map_err(|e| BuilderErr::CommandError(format!("Failed to install modules: {e}")))?;
215
216 if !output.status.success() {
217 let stderr = String::from_utf8_lossy(&output.stderr);
218 return Err(BuilderErr::KernelBuildFail(stderr.to_string()));
219 }
220
221 Ok(output)
222 }
223
224 #[cfg(feature = "dracut")]
225 pub fn generate_initramfs(
231 &self,
232 version_entry: &VersionEntry,
233 replace: bool,
234 ) -> Result<Output, BuilderErr> {
235 let initramfs_path = self.paths.initramfs.as_ref().ok_or_else(|| {
236 BuilderErr::KernelConfigMissingOption("initramfs path not configured".to_string())
237 })?;
238
239 if self.keep_last_kernel && !replace && initramfs_path.exists() {
240 let backup_path = self
241 .paths
242 .backup_path(initramfs_path, &format!("-{}.img", self.last_kernel_suffix));
243 info!("Backing up current initramfs to {}", backup_path.display());
244 std::fs::copy(initramfs_path, &backup_path)
245 .map_err(|e| BuilderErr::CommandError(e.to_string()))?;
246 }
247
248 info!("Generating initramfs for {}", version_entry.version_string);
249
250 let kver = version_entry
251 .version_string
252 .strip_prefix("linux-")
253 .unwrap_or(&version_entry.version_string);
254
255 let output = duct::cmd(
256 DRACUT_COMMAND,
257 &[
258 "--hostonly",
259 "--kver",
260 kver,
261 "--force",
262 initramfs_path.to_string_lossy().as_ref(),
263 ],
264 )
265 .dir(&version_entry.path)
266 .stdout_capture()
267 .stderr_capture()
268 .run()
269 .map_err(|e| BuilderErr::CommandError(format!("Failed to generate initramfs: {e}")))?;
270
271 if !output.status.success() {
272 let stderr = String::from_utf8_lossy(&output.stderr);
273 return Err(BuilderErr::KernelBuildFail(stderr.to_string()));
274 }
275
276 Ok(output)
277 }
278
279 pub fn run_menuconfig(&self, kernel_path: &Path) -> Result<Output, BuilderErr> {
285 info!("Running menuconfig");
286
287 let output = duct::cmd(MAKE_COMMAND, &["menuconfig"])
288 .dir(kernel_path)
289 .stdout_capture()
290 .stderr_capture()
291 .run()
292 .map_err(|e| BuilderErr::CommandError(format!("Failed to run menuconfig: {e}")))?;
293
294 Ok(output)
295 }
296
297 #[must_use]
299 pub fn get_current_kernel(&self) -> Option<PathBuf> {
300 if self.paths.linux_symlink.exists() || self.paths.linux_symlink.is_symlink() {
301 self.paths.linux_symlink.read_link().ok()
302 } else {
303 None
304 }
305 }
306
307 pub fn remove_kernel(&self, kernel_path: &Path) -> Result<(), BuilderErr> {
313 info!("Removing kernel at {}", kernel_path.display());
314 std::fs::remove_dir_all(kernel_path)
315 .map_err(|e| BuilderErr::CommandError(format!("Failed to remove kernel: {e}")))?;
316 Ok(())
317 }
318}