mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 19:52:22 +00:00
feat(bls): parse loader.conf
This commit is contained in:
1 parent
1207f1d4e9
commit
5c517473e3
2 files changed
+164
No files matched your search
@@ -7,6 +7,10 @@ use alloc::vec::Vec;
|
||||
use anyhow::{Error, Result};
|
||||
use core::{cmp::Ordering, iter::Peekable, str::FromStr};
|
||||
|
||||
mod loader_conf;
|
||||
|
||||
pub use loader_conf::{LoaderConf, LoaderTimeout};
|
||||
|
||||
/// Represents a parsed BLS entry.
|
||||
/// Fields unrelated to Sprout are not included.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -579,6 +583,81 @@ mod tests {
|
||||
assert!(!entry.is_bad());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_empty_has_no_settings() {
|
||||
let conf = LoaderConf::parse("");
|
||||
assert_eq!(conf, LoaderConf::default());
|
||||
assert!(conf.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_reads_default_and_timeout() {
|
||||
let conf = LoaderConf::parse("default fedora-*\ntimeout 7\n");
|
||||
assert_eq!(conf.default.as_deref(), Some("fedora-*"));
|
||||
assert_eq!(conf.timeout, Some(LoaderTimeout::Seconds(7)));
|
||||
assert!(conf.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_skips_comments_blank_lines_and_bom() {
|
||||
let conf = LoaderConf::parse("\u{feff}# a comment\n\n \n default arch.conf \n");
|
||||
assert_eq!(conf.default.as_deref(), Some("arch.conf"));
|
||||
assert!(conf.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_last_value_wins() {
|
||||
let conf = LoaderConf::parse("default a\ndefault b\ntimeout 1\ntimeout 2\n");
|
||||
assert_eq!(conf.default.as_deref(), Some("b"));
|
||||
assert_eq!(conf.timeout, Some(LoaderTimeout::Seconds(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_timeout_words() {
|
||||
for (value, expected) in [
|
||||
("0", LoaderTimeout::Seconds(0)),
|
||||
("menu-hidden", LoaderTimeout::Hidden),
|
||||
("menu-disabled", LoaderTimeout::Disabled),
|
||||
("menu-force", LoaderTimeout::Force),
|
||||
] {
|
||||
let conf = LoaderConf::parse(&format!("timeout {value}\n"));
|
||||
assert_eq!(conf.timeout, Some(expected), "{value}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_invalid_timeout_is_ignored_with_a_warning() {
|
||||
for value in ["soon", "-1", "99999999999999999999999"] {
|
||||
let conf = LoaderConf::parse(&format!("timeout {value}\n"));
|
||||
assert_eq!(conf.timeout, None, "{value}");
|
||||
assert_eq!(conf.warnings.len(), 1, "{value}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_keys_without_values_are_ignored_with_a_warning() {
|
||||
let conf = LoaderConf::parse("default\ntimeout\n");
|
||||
assert_eq!(conf.default, None);
|
||||
assert_eq!(conf.timeout, None);
|
||||
assert_eq!(conf.warnings.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_warns_about_unsupported_keys() {
|
||||
let conf = LoaderConf::parse("editor no\nentry-token machine-id\nconsole-mode max\n");
|
||||
assert_eq!(conf.warnings.len(), 3);
|
||||
assert!(
|
||||
conf.warnings
|
||||
.iter()
|
||||
.any(|warning| warning.contains("editor"))
|
||||
);
|
||||
assert!(
|
||||
conf.warnings
|
||||
.iter()
|
||||
.any(|warning| warning.contains("entry-token"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_flags_keep_a_good_default() {
|
||||
// The second entry is bad and also matched the default pattern.
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
use alloc::format;
|
||||
use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// The menu timeout of a `loader.conf` file.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LoaderTimeout {
|
||||
/// Show the menu for this many seconds. Zero hides the menu.
|
||||
Seconds(u64),
|
||||
/// Hide the menu unless a key is held.
|
||||
Hidden,
|
||||
/// Never show the menu.
|
||||
Disabled,
|
||||
/// Always show the menu and wait for the user.
|
||||
Force,
|
||||
}
|
||||
|
||||
impl LoaderTimeout {
|
||||
/// Parses a `timeout` value, which is a number of seconds or one of the menu words.
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
"menu-hidden" => Some(Self::Hidden),
|
||||
"menu-disabled" => Some(Self::Disabled),
|
||||
"menu-force" => Some(Self::Force),
|
||||
_ if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) => {
|
||||
value.parse().ok().map(Self::Seconds)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The settings Sprout understands from a `loader.conf` file.
|
||||
/// Reference: <https://www.freedesktop.org/software/systemd/man/latest/loader.conf.html>
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct LoaderConf {
|
||||
/// A glob pattern that selects the default entry, or `@saved`.
|
||||
pub default: Option<String>,
|
||||
/// How long to show the boot menu.
|
||||
pub timeout: Option<LoaderTimeout>,
|
||||
/// Problems found while parsing, such as keys that are not supported.
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
impl LoaderConf {
|
||||
/// Parses the `input` as a `loader.conf` file. Parsing never fails, as a bad setting
|
||||
/// should not prevent booting. Anything that is ignored is described in `warnings`.
|
||||
/// When a key appears more than once, the last value is used.
|
||||
pub fn parse(input: &str) -> Self {
|
||||
let mut conf = Self::default();
|
||||
|
||||
// Some editors write a UTF-8 byte order mark at the start of the file.
|
||||
let input = input.strip_prefix('\u{feff}').unwrap_or(input);
|
||||
|
||||
for line in input.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (key, value) = match line.split_once(char::is_whitespace) {
|
||||
Some((key, value)) => (key, value.trim()),
|
||||
None => (line, ""),
|
||||
};
|
||||
|
||||
match key {
|
||||
"default" if !value.is_empty() => conf.default = Some(value.to_string()),
|
||||
"timeout" => match LoaderTimeout::parse(value) {
|
||||
Some(timeout) => conf.timeout = Some(timeout),
|
||||
None => conf
|
||||
.warnings
|
||||
.push(format!("ignoring invalid loader.conf timeout '{}'", value)),
|
||||
},
|
||||
"default" => conf
|
||||
.warnings
|
||||
.push("ignoring loader.conf default without a value".to_string()),
|
||||
_ => conf
|
||||
.warnings
|
||||
.push(format!("ignoring unsupported loader.conf key '{}'", key)),
|
||||
}
|
||||
}
|
||||
|
||||
conf
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user