feat(bls): boot counting (#107)

* feat(bls): parse and render boot counters

* feat(bls): sort entries with no boot tries left last

* feat(boot): strip boot counters from bls entry ids

* feat(boot): consume bls boot counter tries before booting

* fix(boot): never default to a bad boot counter entry while a good one exists
This commit is contained in:
alex authored and GitHub committed 2026-10-03 17:19:27 -07:00
1 parent 5e8fe7c783
commit 1207f1d4e9
5 files changed
+398 -7

No files matched your search

+32 -5
View File
@@ -36,6 +36,9 @@ pub mod actions;
/// autoconfigure: Autoconfigure Sprout based on the detected environment.
pub mod autoconfigure;
/// boot_counter: Consume the boot counter tries of BLS entries.
pub mod boot_counter;
/// config: Sprout configuration mechanism.
pub mod config;
@@ -407,11 +410,20 @@ fn run() -> Result<()> {
force_boot_entry = Some(bootloader_interface_oneshot_entry.clone());
}
// If no entries were the default, pick the first entry as the default entry.
if entries.iter().all(|entry| !entry.is_default())
&& let Some(entry) = entries.first_mut()
{
entry.mark_default();
// Entries that have no boot counter tries left are never picked automatically.
// If no entries are the default, pick the first usable entry as the default entry.
let default_flags = edera_sprout_bls::resolve_default_flags(
&entries
.iter()
.map(|entry| (entry.is_default(), entry.is_bad()))
.collect::<Vec<_>>(),
);
for (entry, default) in entries.iter_mut().zip(default_flags) {
if default {
entry.mark_default();
} else {
entry.unmark_default();
}
}
// Convert the menu timeout to a duration.
@@ -465,6 +477,21 @@ fn run() -> Result<()> {
// Execute the late phase, now that the entry is chosen but before its actions are executed.
phase(context.clone(), &config.phases.late).context("unable to execute late phase")?;
// Consume a try from the boot counter of the selected entry.
// Failing to do so must not prevent the entry from booting.
if let Some(target) = entry.boot_counter()
&& !target.counter.is_bad()
{
match target.consume() {
Ok(path) => info!("updated boot counter of entry {}: {}", entry.name(), path),
Err(error) => warn!(
"unable to update boot counter of entry {}: {:#}",
entry.name(),
error
),
}
}
// Execute all the actions for the selected entry.
for action in &entry.declaration().actions {
let action = entry.context().stamp(action);