use alloc::{borrow::ToOwned, string::String};
use uefi::{CStr16, Status, cstr16, proto::console::text::Color};
use crate::{
BootResult,
system::{
fs::{FsError, UefiFileSystem},
helper::normalize_path,
},
};
const CONFIG_PATH: &CStr16 = cstr16!("\\loader\\bootmgr-rs.conf");
pub struct BootConfig {
pub timeout: i64,
pub default: Option<usize>,
pub drivers: bool,
pub driver_path: String,
pub editor: bool,
pub pxe: bool,
pub bg: Color,
pub fg: Color,
pub highlight_bg: Color,
pub highlight_fg: Color,
}
impl BootConfig {
pub(super) fn new() -> BootResult<Self> {
let mut fs = UefiFileSystem::from_image_fs()?;
let mut buf = [0; 4096]; let bytes = match fs.read_into(CONFIG_PATH, &mut buf) {
Ok(bytes) => bytes,
Err(FsError::OpenErr(Status::NOT_FOUND)) => return Ok(Self::default()),
Err(e) => return Err(e.into()),
};
Ok(Self::get_boot_config(&buf, Some(bytes)))
}
#[must_use = "Has no effect if the result is unused"]
pub fn get_boot_config(content: &[u8], bytes: Option<usize>) -> Self {
let mut config = Self::default();
let slice = &content[0..bytes.unwrap_or(content.len()).min(content.len())];
#[cfg(not(test))]
if let Some(timeout) = super::bli::get_timeout_var() {
config.timeout = timeout;
}
if let Ok(content) = str::from_utf8(slice) {
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
config.assign_to_field(line);
}
}
config
}
fn assign_to_field(&mut self, line: &str) {
if let Some((key, value)) = line.split_once(' ') {
let value = value.trim().to_owned();
match &*key.to_ascii_lowercase() {
"timeout" => {
if let Ok(value) = value.parse() {
self.timeout = value;
#[cfg(not(test))]
let _ = super::bli::set_timeout_var(value);
}
}
"default" => {
if let Ok(value) = value.parse() {
self.default = Some(value);
}
}
"drivers" => {
if let Ok(value) = value.parse() {
self.drivers = value;
}
}
"driver_path" => {
let value = normalize_path(&value);
self.driver_path = value;
}
"editor" => {
if let Ok(value) = value.parse() {
self.editor = value;
}
}
"pxe" => {
if let Ok(value) = value.parse() {
self.pxe = value;
}
}
"background" => self.bg = match_str_color_bg(&value),
"foreground" => self.fg = match_str_color_fg(&value),
"highlight_background" => self.highlight_bg = match_str_color_bg(&value),
"highlight_foreground" => self.highlight_fg = match_str_color_fg(&value),
_ => (),
}
}
}
}
impl Default for BootConfig {
fn default() -> Self {
Self {
timeout: 5,
default: None,
drivers: false,
driver_path: "\\EFI\\BOOT\\drivers".to_owned(),
editor: false,
pxe: false,
bg: Color::Black,
fg: Color::White,
highlight_bg: Color::LightGray,
highlight_fg: Color::Black,
}
}
}
fn match_str_color_fg(color: &str) -> Color {
match color {
"red" => Color::Red,
"green" => Color::Green,
"yellow" => Color::Yellow,
"blue" => Color::Blue,
"magenta" => Color::Magenta,
"cyan" => Color::Cyan,
"gray" => Color::LightGray,
"dark_gray" => Color::DarkGray,
"light_red" => Color::LightRed,
"light_green" => Color::LightGreen,
"light_blue" => Color::LightBlue,
"light_magenta" => Color::LightMagenta,
"light_cyan" => Color::LightCyan,
"white" => Color::White,
_ => Color::Black,
}
}
fn match_str_color_bg(color: &str) -> Color {
match color {
"blue" => Color::Blue,
"green" => Color::Green,
"cyan" => Color::Cyan,
"red" => Color::Red,
"magenta" => Color::Magenta,
"gray" | "white" => Color::LightGray, _ => Color::Black,
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_full_config() {
let config = b"
timeout 100
default 2
driver_path /efi/drivers
editor true
pxe false
background gray
foreground white
highlight_background black
highlight_foreground white
";
let config = BootConfig::get_boot_config(config, None);
assert_eq!(config.timeout, 100);
assert_eq!(config.default, Some(2));
assert_eq!(config.driver_path, "\\efi\\drivers".to_owned());
assert!(config.editor);
assert!(!config.pxe);
assert!(matches!(config.bg, Color::LightGray));
assert!(matches!(config.fg, Color::White));
assert!(matches!(config.highlight_bg, Color::Black));
assert!(matches!(config.highlight_fg, Color::White));
}
proptest! {
#[test]
fn doesnt_panic(x in any::<Vec<u8>>(), y in any::<usize>()) {
let _ = BootConfig::get_boot_config(&x, Some(y));
}
}
}