cargo_image_runner/bootloader/
mod.rs1use crate::config::BootType;
4use crate::core::context::Context;
5use crate::core::error::Result;
6use std::collections::HashMap;
7use std::path::PathBuf;
8
9#[cfg(feature = "limine")]
11pub mod limine;
12
13pub mod grub;
14pub mod none;
15
16#[cfg(feature = "limine")]
17mod fetcher;
18
19#[cfg(feature = "limine")]
20pub use fetcher::GitFetcher;
21
22pub trait Bootloader: Send + Sync {
24 fn prepare(&self, ctx: &Context) -> Result<BootloaderFiles>;
28
29 fn config_files(&self, ctx: &Context) -> Result<Vec<ConfigFile>>;
33
34 fn process_templates(&self, content: &str, vars: &HashMap<String, String>) -> Result<String> {
38 let mut result = content.to_string();
39
40 for (key, value) in vars {
42 let placeholder = format!("{{{{{}}}}}", key);
43 result = result.replace(&placeholder, value);
44 }
45
46 for (key, value) in vars {
48 let placeholder = format!("${}", key);
49 result = result.replace(&placeholder, value);
50 }
51
52 Ok(result)
53 }
54
55 fn boot_type(&self) -> BootType;
57
58 fn validate_config(&self, ctx: &Context) -> Result<()> {
60 let required = self.boot_type();
62 let configured = ctx.config.boot.boot_type;
63
64 match (required, configured) {
65 (BootType::Bios, BootType::Uefi) | (BootType::Uefi, BootType::Bios) => {
66 return Err(crate::core::error::Error::unsupported(format!(
67 "Bootloader requires {:?} but boot type is configured as {:?}",
68 required, configured
69 )));
70 }
71 _ => {}
72 }
73
74 Ok(())
75 }
76
77 fn name(&self) -> &str;
79}
80
81#[derive(Debug, Default)]
83pub struct BootloaderFiles {
84 pub bios_files: Vec<FileEntry>,
86
87 pub uefi_files: Vec<FileEntry>,
89
90 pub system_files: Vec<FileEntry>,
92}
93
94impl BootloaderFiles {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn add_bios_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
102 self.bios_files.push(FileEntry { source, dest });
103 self
104 }
105
106 pub fn add_uefi_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
108 self.uefi_files.push(FileEntry { source, dest });
109 self
110 }
111
112 pub fn add_system_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
114 self.system_files.push(FileEntry { source, dest });
115 self
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct ConfigFile {
122 pub source: PathBuf,
124
125 pub dest: PathBuf,
127
128 pub needs_template_processing: bool,
130}
131
132impl ConfigFile {
133 pub fn new(source: PathBuf, dest: PathBuf) -> Self {
135 Self {
136 source,
137 dest,
138 needs_template_processing: false,
139 }
140 }
141
142 pub fn with_template_processing(mut self) -> Self {
144 self.needs_template_processing = true;
145 self
146 }
147}
148
149#[derive(Debug, Clone)]
151pub struct FileEntry {
152 pub source: PathBuf,
154
155 pub dest: PathBuf,
157}
158
159impl FileEntry {
160 pub fn new(source: PathBuf, dest: PathBuf) -> Self {
162 Self { source, dest }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_bootloader_files_new_is_empty() {
172 let files = BootloaderFiles::new();
173 assert!(files.bios_files.is_empty());
174 assert!(files.uefi_files.is_empty());
175 assert!(files.system_files.is_empty());
176 }
177
178 #[test]
179 fn test_bootloader_files_builder_chain() {
180 let files = BootloaderFiles::new()
181 .add_bios_file(PathBuf::from("bios.sys"), PathBuf::from("boot/bios.sys"))
182 .add_uefi_file(PathBuf::from("bootx64.efi"), PathBuf::from("efi/boot/bootx64.efi"))
183 .add_system_file(PathBuf::from("kernel.elf"), PathBuf::from("boot/kernel.elf"));
184
185 assert_eq!(files.bios_files.len(), 1);
186 assert_eq!(files.uefi_files.len(), 1);
187 assert_eq!(files.system_files.len(), 1);
188 assert_eq!(files.bios_files[0].source, PathBuf::from("bios.sys"));
189 assert_eq!(files.uefi_files[0].dest, PathBuf::from("efi/boot/bootx64.efi"));
190 assert_eq!(files.system_files[0].source, PathBuf::from("kernel.elf"));
191 }
192
193 #[test]
194 fn test_config_file_template_processing() {
195 let cf = ConfigFile::new(PathBuf::from("limine.conf"), PathBuf::from("boot/limine.conf"));
196 assert!(!cf.needs_template_processing);
197
198 let cf = cf.with_template_processing();
199 assert!(cf.needs_template_processing);
200 assert_eq!(cf.source, PathBuf::from("limine.conf"));
201 assert_eq!(cf.dest, PathBuf::from("boot/limine.conf"));
202 }
203
204 #[test]
205 fn test_file_entry_construction() {
206 let entry = FileEntry::new(PathBuf::from("/src/kernel"), PathBuf::from("boot/kernel"));
207 assert_eq!(entry.source, PathBuf::from("/src/kernel"));
208 assert_eq!(entry.dest, PathBuf::from("boot/kernel"));
209 }
210
211 #[test]
212 fn test_process_templates_default_impl() {
213 let bootloader = none::NoneBootloader::new();
214 let mut vars = HashMap::new();
215 vars.insert("KEY".to_string(), "value".to_string());
216
217 let result = bootloader.process_templates("Hello {{KEY}}", &vars).unwrap();
218 assert_eq!(result, "Hello value");
219 }
220}