From cd49b27ccbbe0db1bb2f24029e269678cdd87d78 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 3 Oct 2026 19:19:36 -0700 Subject: [PATCH] feat(bls): read the loader.conf preferred key and strip quotes --- crates/bls/src/lib.rs | 21 +++++++++++++++++++++ crates/bls/src/loader_conf.rs | 23 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/bls/src/lib.rs b/crates/bls/src/lib.rs index 579cf15..8c08f73 100644 --- a/crates/bls/src/lib.rs +++ b/crates/bls/src/lib.rs @@ -846,6 +846,27 @@ mod tests { assert_eq!(boot_path(""), "/"); } + #[test] + fn loader_conf_reads_preferred() { + let conf = LoaderConf::parse("preferred fedora-*\n"); + assert_eq!(conf.preferred.as_deref(), Some("fedora-*")); + assert!(conf.warnings.is_empty()); + } + + #[test] + fn loader_conf_strips_matching_quotes() { + let conf = LoaderConf::parse("default \"foo*\"\npreferred 'bar'\ntimeout \"3\"\n"); + assert_eq!(conf.default.as_deref(), Some("foo*")); + assert_eq!(conf.preferred.as_deref(), Some("bar")); + assert_eq!(conf.timeout, Some(LoaderTimeout::Seconds(3))); + } + + #[test] + fn loader_conf_keeps_unmatched_quotes() { + let conf = LoaderConf::parse("default \"foo\n"); + assert_eq!(conf.default.as_deref(), Some("\"foo")); + } + #[test] fn loader_conf_empty_has_no_settings() { let conf = LoaderConf::parse(""); diff --git a/crates/bls/src/loader_conf.rs b/crates/bls/src/loader_conf.rs index baa106d..27a5001 100644 --- a/crates/bls/src/loader_conf.rs +++ b/crates/bls/src/loader_conf.rs @@ -36,12 +36,28 @@ impl LoaderTimeout { pub struct LoaderConf { /// A glob pattern that selects the default entry, or `@saved`. pub default: Option, + /// A glob pattern that selects the preferred entry, or `@saved`. Unlike `default`, an entry + /// with no boot counter tries left is not used. + pub preferred: Option, /// How long to show the boot menu. pub timeout: Option, /// Problems found while parsing, such as keys that are not supported. pub warnings: Vec, } +/// Removes one pair of matching single or double quotes around `value`. +fn unquote(value: &str) -> &str { + for quote in ['"', '\''] { + if let Some(inner) = value + .strip_prefix(quote) + .and_then(|rest| rest.strip_suffix(quote)) + { + return inner; + } + } + value +} + 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`. @@ -59,21 +75,22 @@ impl LoaderConf { } let (key, value) = match line.split_once(char::is_whitespace) { - Some((key, value)) => (key, value.trim()), + Some((key, value)) => (key, unquote(value.trim())), None => (line, ""), }; match key { "default" if !value.is_empty() => conf.default = Some(value.to_string()), + "preferred" if !value.is_empty() => conf.preferred = 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 + "default" | "preferred" => conf .warnings - .push("ignoring loader.conf default without a value".to_string()), + .push(format!("ignoring loader.conf {} without a value", key)), _ => conf .warnings .push(format!("ignoring unsupported loader.conf key '{}'", key)),