mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 19:52:22 +00:00
feat(bls): reboot when an entry with boot counting fails to start
Add the reboot-on-error loader.conf setting. With auto, which is the default, the machine resets after a failed start only if a boot counter try was just used up and there were tries left, so the entry eventually runs out of tries and the next boot picks another one. yes always resets and no never does.
This commit is contained in:
1 parent
14bcfc2653
commit
d41bcaa292
3 files changed
+135
-4
No files matched your search
+70
-1
@@ -11,7 +11,7 @@ mod loader_conf;
|
||||
mod pe;
|
||||
mod uki;
|
||||
|
||||
pub use loader_conf::{LoaderConf, LoaderTimeout};
|
||||
pub use loader_conf::{LoaderConf, LoaderTimeout, RebootOnError};
|
||||
pub use pe::{
|
||||
MAX_SECTION_SIZE, PE_MACHINE_AARCH64, PE_MACHINE_X86_64, PeImage, ReadAt, read_pe,
|
||||
read_sections,
|
||||
@@ -958,6 +958,75 @@ mod tests {
|
||||
assert_eq!(conf.default.as_deref(), Some("\"foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reboot_on_error_defaults_to_auto() {
|
||||
assert_eq!(LoaderConf::parse("").reboot_on_error, RebootOnError::Auto);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reboot_on_error_reads_booleans_and_auto() {
|
||||
for (value, expected) in [
|
||||
("auto", RebootOnError::Auto),
|
||||
("yes", RebootOnError::Yes),
|
||||
("true", RebootOnError::Yes),
|
||||
("1", RebootOnError::Yes),
|
||||
("on", RebootOnError::Yes),
|
||||
("y", RebootOnError::Yes),
|
||||
("t", RebootOnError::Yes),
|
||||
("no", RebootOnError::No),
|
||||
("off", RebootOnError::No),
|
||||
("0", RebootOnError::No),
|
||||
("f", RebootOnError::No),
|
||||
("false", RebootOnError::No),
|
||||
("n", RebootOnError::No),
|
||||
("\"no\"", RebootOnError::No),
|
||||
] {
|
||||
let conf = LoaderConf::parse(&format!("reboot-on-error {value}\n"));
|
||||
assert_eq!(conf.reboot_on_error, expected, "{value}");
|
||||
assert!(conf.warnings.is_empty(), "{value}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reboot_on_error_ignores_invalid_values_with_a_warning() {
|
||||
// The values are case sensitive, as they are in systemd-boot.
|
||||
for value in ["YES", "Auto", "maybe", ""] {
|
||||
let conf = LoaderConf::parse(&format!("reboot-on-error {value}\n"));
|
||||
assert_eq!(conf.reboot_on_error, RebootOnError::Auto, "{value}");
|
||||
assert_eq!(conf.warnings.len(), 1, "{value}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reboot_on_error_last_value_wins() {
|
||||
let conf = LoaderConf::parse("reboot-on-error no\nreboot-on-error yes\n");
|
||||
assert_eq!(conf.reboot_on_error, RebootOnError::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reboot_on_error_decides_from_the_tries_that_were_left() {
|
||||
assert!(!RebootOnError::Auto.should_reboot(None));
|
||||
assert!(!RebootOnError::Auto.should_reboot(Some(0)));
|
||||
assert!(RebootOnError::Auto.should_reboot(Some(1)));
|
||||
assert!(RebootOnError::Auto.should_reboot(Some(3)));
|
||||
assert!(RebootOnError::Yes.should_reboot(None));
|
||||
assert!(!RebootOnError::No.should_reboot(Some(3)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_reboots_are_bounded_by_the_boot_counter() {
|
||||
// An entry with two tries that keeps failing is rebooted twice, and then stays put.
|
||||
let mut counter = BootCounter::new(2, 0);
|
||||
let mut reboots = 0;
|
||||
for _ in 0..10 {
|
||||
if RebootOnError::Auto.should_reboot(Some(counter.tries_left)) {
|
||||
reboots += 1;
|
||||
}
|
||||
counter = counter.decremented();
|
||||
}
|
||||
assert_eq!(reboots, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loader_conf_empty_has_no_settings() {
|
||||
let conf = LoaderConf::parse("");
|
||||
|
||||
@@ -30,6 +30,43 @@ impl LoaderTimeout {
|
||||
}
|
||||
}
|
||||
|
||||
/// What to do when the selected entry fails to start, from the `reboot-on-error` setting.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RebootOnError {
|
||||
/// Never reboot.
|
||||
No,
|
||||
/// Always reboot, which can loop forever if the entry never starts.
|
||||
Yes,
|
||||
/// Reboot only if a boot counter try was just used up and tries were left, so that the
|
||||
/// entry eventually runs out of tries and is not booted any more.
|
||||
#[default]
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl RebootOnError {
|
||||
/// Parses a `reboot-on-error` value, which is `auto` or a boolean. The words are case
|
||||
/// sensitive, as in systemd-boot.
|
||||
fn parse(value: &str) -> Option<Self> {
|
||||
match value {
|
||||
"auto" => Some(Self::Auto),
|
||||
"1" | "yes" | "y" | "true" | "t" | "on" => Some(Self::Yes),
|
||||
"0" | "no" | "n" | "false" | "f" | "off" => Some(Self::No),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to reboot after the entry failed to start. `consumed_tries_left` is the number of
|
||||
/// tries that were left before this boot used one up, or None if the entry has no boot
|
||||
/// counter or the try could not be used up, as then a reboot would not make progress.
|
||||
pub fn should_reboot(self, consumed_tries_left: Option<u32>) -> bool {
|
||||
match self {
|
||||
Self::Yes => true,
|
||||
Self::No => false,
|
||||
Self::Auto => consumed_tries_left.is_some_and(|left| left > 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
@@ -41,6 +78,8 @@ pub struct LoaderConf {
|
||||
pub preferred: Option<String>,
|
||||
/// How long to show the boot menu.
|
||||
pub timeout: Option<LoaderTimeout>,
|
||||
/// What to do when the selected entry fails to start.
|
||||
pub reboot_on_error: RebootOnError,
|
||||
/// Problems found while parsing, such as keys that are not supported.
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
@@ -88,6 +127,13 @@ impl LoaderConf {
|
||||
.warnings
|
||||
.push(format!("ignoring invalid loader.conf timeout '{}'", value)),
|
||||
},
|
||||
"reboot-on-error" => match RebootOnError::parse(value) {
|
||||
Some(reboot_on_error) => conf.reboot_on_error = reboot_on_error,
|
||||
None => conf.warnings.push(format!(
|
||||
"ignoring invalid loader.conf reboot-on-error '{}'",
|
||||
value
|
||||
)),
|
||||
},
|
||||
"default" | "preferred" => conf
|
||||
.warnings
|
||||
.push(format!("ignoring loader.conf {} without a value", key)),
|
||||
|
||||
Reference in new issue
Block a user