use crate::config::BootType;
use crate::core::context::Context;
use crate::core::error::Result;
use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(feature = "limine")]
pub mod limine;
pub mod grub;
pub mod none;
#[cfg(feature = "limine")]
mod fetcher;
#[cfg(feature = "limine")]
pub use fetcher::GitFetcher;
pub trait Bootloader: Send + Sync {
fn prepare(&self, ctx: &Context) -> Result<BootloaderFiles>;
fn config_files(&self, ctx: &Context) -> Result<Vec<ConfigFile>>;
fn process_templates(&self, content: &str, vars: &HashMap<String, String>) -> Result<String> {
let mut result = content.to_string();
for (key, value) in vars {
let placeholder = format!("{{{{{}}}}}", key);
result = result.replace(&placeholder, value);
}
for (key, value) in vars {
let placeholder = format!("${}", key);
result = result.replace(&placeholder, value);
}
Ok(result)
}
fn boot_type(&self) -> BootType;
fn validate_config(&self, ctx: &Context) -> Result<()> {
let required = self.boot_type();
let configured = ctx.config.boot.boot_type;
match (required, configured) {
(BootType::Bios, BootType::Uefi) | (BootType::Uefi, BootType::Bios) => {
return Err(crate::core::error::Error::unsupported(format!(
"Bootloader requires {:?} but boot type is configured as {:?}",
required, configured
)));
}
_ => {}
}
Ok(())
}
fn name(&self) -> &str;
}
#[derive(Debug, Default)]
pub struct BootloaderFiles {
pub bios_files: Vec<FileEntry>,
pub uefi_files: Vec<FileEntry>,
pub system_files: Vec<FileEntry>,
}
impl BootloaderFiles {
pub fn new() -> Self {
Self::default()
}
pub fn add_bios_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
self.bios_files.push(FileEntry { source, dest });
self
}
pub fn add_uefi_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
self.uefi_files.push(FileEntry { source, dest });
self
}
pub fn add_system_file(mut self, source: PathBuf, dest: PathBuf) -> Self {
self.system_files.push(FileEntry { source, dest });
self
}
}
#[derive(Debug, Clone)]
pub struct ConfigFile {
pub source: PathBuf,
pub dest: PathBuf,
pub needs_template_processing: bool,
}
impl ConfigFile {
pub fn new(source: PathBuf, dest: PathBuf) -> Self {
Self {
source,
dest,
needs_template_processing: false,
}
}
pub fn with_template_processing(mut self) -> Self {
self.needs_template_processing = true;
self
}
}
#[derive(Debug, Clone)]
pub struct FileEntry {
pub source: PathBuf,
pub dest: PathBuf,
}
impl FileEntry {
pub fn new(source: PathBuf, dest: PathBuf) -> Self {
Self { source, dest }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bootloader_files_new_is_empty() {
let files = BootloaderFiles::new();
assert!(files.bios_files.is_empty());
assert!(files.uefi_files.is_empty());
assert!(files.system_files.is_empty());
}
#[test]
fn test_bootloader_files_builder_chain() {
let files = BootloaderFiles::new()
.add_bios_file(PathBuf::from("bios.sys"), PathBuf::from("boot/bios.sys"))
.add_uefi_file(PathBuf::from("bootx64.efi"), PathBuf::from("efi/boot/bootx64.efi"))
.add_system_file(PathBuf::from("kernel.elf"), PathBuf::from("boot/kernel.elf"));
assert_eq!(files.bios_files.len(), 1);
assert_eq!(files.uefi_files.len(), 1);
assert_eq!(files.system_files.len(), 1);
assert_eq!(files.bios_files[0].source, PathBuf::from("bios.sys"));
assert_eq!(files.uefi_files[0].dest, PathBuf::from("efi/boot/bootx64.efi"));
assert_eq!(files.system_files[0].source, PathBuf::from("kernel.elf"));
}
#[test]
fn test_config_file_template_processing() {
let cf = ConfigFile::new(PathBuf::from("limine.conf"), PathBuf::from("boot/limine.conf"));
assert!(!cf.needs_template_processing);
let cf = cf.with_template_processing();
assert!(cf.needs_template_processing);
assert_eq!(cf.source, PathBuf::from("limine.conf"));
assert_eq!(cf.dest, PathBuf::from("boot/limine.conf"));
}
#[test]
fn test_file_entry_construction() {
let entry = FileEntry::new(PathBuf::from("/src/kernel"), PathBuf::from("boot/kernel"));
assert_eq!(entry.source, PathBuf::from("/src/kernel"));
assert_eq!(entry.dest, PathBuf::from("boot/kernel"));
}
#[test]
fn test_process_templates_default_impl() {
let bootloader = none::NoneBootloader::new();
let mut vars = HashMap::new();
vars.insert("KEY".to_string(), "value".to_string());
let result = bootloader.process_templates("Hello {{KEY}}", &vars).unwrap();
assert_eq!(result, "Hello value");
}
}