bootmgr_rs_core/boot/
config.rs1use alloc::{borrow::ToOwned, string::String};
45use uefi::{CStr16, Status, cstr16, proto::console::text::Color};
46
47use crate::{
48 BootResult,
49 system::{
50 fs::{FsError, UefiFileSystem},
51 helper::normalize_path,
52 },
53};
54
55const CONFIG_PATH: &CStr16 = cstr16!("\\loader\\bootmgr-rs.conf");
57
58pub struct BootConfig {
60 pub timeout: i64,
62
63 pub default: Option<usize>,
65
66 pub drivers: bool,
68
69 pub driver_path: String,
71
72 pub editor: bool,
74
75 pub pxe: bool,
77
78 pub bg: Color,
80
81 pub fg: Color,
83
84 pub highlight_bg: Color,
86
87 pub highlight_fg: Color,
89}
90
91impl BootConfig {
92 pub(super) fn new() -> BootResult<Self> {
100 let mut fs = UefiFileSystem::from_image_fs()?;
101
102 let mut buf = [0; 4096]; let bytes = match fs.read_into(CONFIG_PATH, &mut buf) {
104 Ok(bytes) => bytes,
105 Err(FsError::OpenErr(Status::NOT_FOUND)) => return Ok(Self::default()),
106 Err(e) => return Err(e.into()),
107 };
108
109 Ok(Self::get_boot_config(&buf, Some(bytes)))
110 }
111
112 #[must_use = "Has no effect if the result is unused"]
114 pub fn get_boot_config(content: &[u8], bytes: Option<usize>) -> Self {
115 let mut config = Self::default();
116 let slice = &content[0..bytes.unwrap_or(content.len()).min(content.len())];
117
118 #[cfg(not(test))]
119 if let Some(timeout) = super::bli::get_timeout_var() {
120 config.timeout = timeout;
121 }
122
123 if let Ok(content) = str::from_utf8(slice) {
124 for line in content.lines() {
125 let line = line.trim();
126 if line.is_empty() || line.starts_with('#') {
127 continue;
128 }
129
130 config.assign_to_field(line);
131 }
132 }
133
134 config
135 }
136
137 fn assign_to_field(&mut self, line: &str) {
139 if let Some((key, value)) = line.split_once(' ') {
140 let value = value.trim().to_owned();
141 match &*key.to_ascii_lowercase() {
142 "timeout" => {
143 if let Ok(value) = value.parse() {
144 self.timeout = value;
145
146 #[cfg(not(test))]
147 let _ = super::bli::set_timeout_var(value);
148 }
149 }
150 "default" => {
151 if let Ok(value) = value.parse() {
152 self.default = Some(value);
153 }
154 }
155 "drivers" => {
156 if let Ok(value) = value.parse() {
157 self.drivers = value;
158 }
159 }
160 "driver_path" => {
161 let value = normalize_path(&value);
162 self.driver_path = value;
163 }
164 "editor" => {
165 if let Ok(value) = value.parse() {
166 self.editor = value;
167 }
168 }
169 "pxe" => {
170 if let Ok(value) = value.parse() {
171 self.pxe = value;
172 }
173 }
174 "background" => self.bg = match_str_color_bg(&value),
175 "foreground" => self.fg = match_str_color_fg(&value),
176 "highlight_background" => self.highlight_bg = match_str_color_bg(&value),
177 "highlight_foreground" => self.highlight_fg = match_str_color_fg(&value),
178 _ => (),
179 }
180 }
181 }
182}
183
184impl Default for BootConfig {
185 fn default() -> Self {
186 Self {
187 timeout: 5,
188 default: None,
189 drivers: false,
190 driver_path: "\\EFI\\BOOT\\drivers".to_owned(),
191 editor: false,
192 pxe: false,
193 bg: Color::Black,
194 fg: Color::White,
195 highlight_bg: Color::LightGray,
196 highlight_fg: Color::Black,
197 }
198 }
199}
200
201fn match_str_color_fg(color: &str) -> Color {
205 match color {
206 "red" => Color::Red,
207 "green" => Color::Green,
208 "yellow" => Color::Yellow,
209 "blue" => Color::Blue,
210 "magenta" => Color::Magenta,
211 "cyan" => Color::Cyan,
212 "gray" => Color::LightGray,
213 "dark_gray" => Color::DarkGray,
214 "light_red" => Color::LightRed,
215 "light_green" => Color::LightGreen,
216 "light_blue" => Color::LightBlue,
217 "light_magenta" => Color::LightMagenta,
218 "light_cyan" => Color::LightCyan,
219 "white" => Color::White,
220 _ => Color::Black,
221 }
222}
223
224fn match_str_color_bg(color: &str) -> Color {
229 match color {
230 "blue" => Color::Blue,
231 "green" => Color::Green,
232 "cyan" => Color::Cyan,
233 "red" => Color::Red,
234 "magenta" => Color::Magenta,
235 "gray" | "white" => Color::LightGray, _ => Color::Black,
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use proptest::prelude::*;
244
245 #[test]
249 fn test_full_config() {
250 let config = b"
251 timeout 100
252 default 2
253 driver_path /efi/drivers
254 editor true
255 pxe false
256 background gray
257 foreground white
258 highlight_background black
259 highlight_foreground white
260 ";
261
262 let config = BootConfig::get_boot_config(config, None);
263 assert_eq!(config.timeout, 100);
264 assert_eq!(config.default, Some(2));
265 assert_eq!(config.driver_path, "\\efi\\drivers".to_owned());
266 assert!(config.editor);
267 assert!(!config.pxe);
268 assert!(matches!(config.bg, Color::LightGray));
269 assert!(matches!(config.fg, Color::White));
270 assert!(matches!(config.highlight_bg, Color::Black));
271 assert!(matches!(config.highlight_fg, Color::White));
272 }
273
274 proptest! {
275 #[test]
276 fn doesnt_panic(x in any::<Vec<u8>>(), y in any::<usize>()) {
277 let _ = BootConfig::get_boot_config(&x, Some(y));
278 }
279 }
280}