mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 17:52:20 +00:00
feat(boot): make strict mode follow systemd-boot's selection and menu rules
In strict mode a one-shot entry only becomes the default, the default entry may be one with no boot counter tries left, no menu timeout means a hidden menu, the menu is shown again when a started image returns, and entries that mix linux, efi and uki, entries whose file is missing, and unified kernel images with no name are hidden. Plain EFI entries are not counted, the XBOOTLDR partition is always read, and a devicetree that can't be installed fails the entry. Outside of strict mode these keep working as they did, and each one is now logged where it applies. The initrd limit of BLS entries is 32. A devicetree that can't be installed is skipped with a warning.
This commit is contained in:
1 parent
bd37e9058c
commit
ca01353df4
4 files changed
+266
-128
No files matched your search
@@ -62,6 +62,22 @@ fn resolve_optional_file(
|
||||
Ok(Some(resolved))
|
||||
}
|
||||
|
||||
/// Install the devicetree at the stamped `path` for the image that is about to be started.
|
||||
/// Provides [None] if there is none to install, such as when the path refers to the root of a
|
||||
/// filesystem, or when Secure Boot is enabled, as a devicetree can't be verified.
|
||||
fn install_devicetree(context: &Rc<SproutContext>, path: &str) -> Result<Option<DeviceTree>> {
|
||||
let Some(resolved) = resolve_optional_file(context, path, "devicetree")? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if SecureBoot::enabled().unwrap_or(true) {
|
||||
warn!("ignoring the devicetree, as Secure Boot is enabled");
|
||||
return Ok(None);
|
||||
}
|
||||
let content = resolved.read_file().context("unable to read devicetree")?;
|
||||
let devicetree = DeviceTree::install(&content).context("unable to install the devicetree")?;
|
||||
Ok(Some(devicetree))
|
||||
}
|
||||
|
||||
/// Unloads the image with the contained handle when dropped, unless the handle is taken.
|
||||
/// This ensures that an image that is loaded but never started is not left in memory.
|
||||
struct UnloadGuard(Option<Handle>);
|
||||
@@ -180,14 +196,17 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
|
||||
.devicetree
|
||||
.as_ref()
|
||||
.map(|path| context.stamp(path)),
|
||||
) && let Some(resolved) = resolve_optional_file(&context, &path, "devicetree")?
|
||||
{
|
||||
if SecureBoot::enabled().unwrap_or(true) {
|
||||
warn!("ignoring the devicetree, as Secure Boot is enabled");
|
||||
} else {
|
||||
let content = resolved.read_file().context("unable to read devicetree")?;
|
||||
devicetree =
|
||||
Some(DeviceTree::install(&content).context("unable to install the devicetree")?);
|
||||
) {
|
||||
match install_devicetree(&context, &path) {
|
||||
Ok(installed) => devicetree = installed,
|
||||
// systemd-boot does not boot an entry whose devicetree can't be installed. Outside
|
||||
// of strict mode it is booted without it, as before the devicetree was supported.
|
||||
Err(error) if !context.root().options().bls_strict_mode => warn!(
|
||||
"booting without the devicetree {}, as it can't be installed: {:#} \
|
||||
(systemd-boot and strict mode do not boot the entry)",
|
||||
path, error
|
||||
),
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use edera_sprout_bls::{
|
||||
use edera_sprout_config::generators::bls::BlsConfiguration;
|
||||
use log::{info, warn};
|
||||
use uefi::{
|
||||
cstr16,
|
||||
CString16, cstr16,
|
||||
fs::{FileSystem, PathBuf},
|
||||
proto::device_path::text::{AllowShortcuts, DisplayOnly},
|
||||
proto::media::fs::SimpleFileSystem,
|
||||
@@ -38,8 +38,8 @@ const ARCHITECTURE: &str = "x64";
|
||||
const ARCHITECTURE: &str = "aa64";
|
||||
|
||||
/// The number of initrd slots set on each BLS entry.
|
||||
/// Entries set `initrd-0` through `initrd-7`, and slots without an initrd are empty.
|
||||
pub const BLS_INITRD_SLOTS: usize = 8;
|
||||
/// Entries set `initrd-0` through `initrd-31`, and slots without an initrd are empty.
|
||||
pub const BLS_INITRD_SLOTS: usize = 32;
|
||||
|
||||
// TODO(azenla): remove this once variable substitution is implemented.
|
||||
/// This function is used to remove the `tuned_initrd` variable from the initrd paths.
|
||||
@@ -138,6 +138,7 @@ fn generate_type1(
|
||||
path: &str,
|
||||
entry_root: &str,
|
||||
) -> Result<Vec<(BlsEntry, BootableEntry)>> {
|
||||
let strict = context.root().options().bls_strict_mode;
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// Resolve the path to the BLS directory.
|
||||
@@ -252,6 +253,42 @@ fn generate_type1(
|
||||
continue;
|
||||
}
|
||||
|
||||
// An entry that names more than one of linux, efi and uki is broken, and systemd-boot
|
||||
// does not show it. Outside of strict mode, the first of them in that order is booted.
|
||||
if entry.mixes_boot_targets() {
|
||||
if strict {
|
||||
warn!(
|
||||
"hiding bls entry {} as it has more than one of linux, efi and uki",
|
||||
name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
warn!(
|
||||
"bls entry {} has more than one of linux, efi and uki, which systemd-boot hides; \
|
||||
using the first of linux, efi and uki (strict mode hides it)",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
// The file that the entry boots has to exist. systemd-boot hides an entry that has
|
||||
// none, while outside of strict mode it is shown and fails when it is booted.
|
||||
if let Some(target) = entry.chainload_path()
|
||||
&& let Ok(target_path) = CString16::try_from(format!("\\{}", target).as_str())
|
||||
&& let Ok(false) = fs.try_exists(PathBuf::from(target_path))
|
||||
{
|
||||
if strict {
|
||||
warn!(
|
||||
"hiding bls entry {} as its file {} does not exist",
|
||||
name, target
|
||||
);
|
||||
continue;
|
||||
}
|
||||
warn!(
|
||||
"bls entry {} refers to the file {}, which does not exist (strict mode hides it)",
|
||||
name, target
|
||||
);
|
||||
}
|
||||
|
||||
// Put the initrds through a quirk modifier to support Fedora.
|
||||
let initrds = quirk_initrd_remove_tuned(entry.initrd_paths());
|
||||
|
||||
@@ -271,7 +308,12 @@ fn generate_type1(
|
||||
}
|
||||
|
||||
// Record where the boot counter lives so a try can be consumed when this entry boots.
|
||||
if let Some(counter) = boot_counter {
|
||||
// systemd-boot only counts the tries of the entries it boots as a kernel or as a unified
|
||||
// kernel image, and not those of a plain EFI program.
|
||||
let counted = !(strict && entry.linux.is_none() && entry.uki.is_none());
|
||||
if let Some(counter) = boot_counter
|
||||
&& counted
|
||||
{
|
||||
boot.set_boot_counter(BootCounterTarget {
|
||||
counter,
|
||||
filesystem: bls_resolved.filesystem_handle,
|
||||
@@ -307,7 +349,8 @@ fn generate_type2(
|
||||
let directory_name = directory.to_string();
|
||||
|
||||
let mut found: Vec<(BlsEntry, BootableEntry)> = Vec::new();
|
||||
for image in uki::scan(resolved.filesystem_handle, &directory_name)? {
|
||||
let strict = context.root().options().bls_strict_mode;
|
||||
for image in uki::scan(resolved.filesystem_handle, &directory_name, strict)? {
|
||||
// Only unified kernel images that are built for this machine are entries.
|
||||
if image.machine != PE_MACHINE {
|
||||
info!(
|
||||
@@ -362,6 +405,16 @@ fn generate_type2(
|
||||
None => id.clone(),
|
||||
};
|
||||
|
||||
// systemd-boot skips an image that has no name, while outside of strict mode the
|
||||
// name of the file is used.
|
||||
if strict && profile.entry.title.is_none() {
|
||||
warn!(
|
||||
"hiding unified kernel image {} as it has no name in its os-release",
|
||||
file_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut entry = profile.entry;
|
||||
entry.boot_counter = boot_counter;
|
||||
|
||||
@@ -470,7 +523,7 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
|
||||
BlsConfiguration::root_of(&path),
|
||||
) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) if bls.xbootldr => {
|
||||
Err(error) if bls.xbootldr || context.root().options().bls_strict_mode => {
|
||||
warn!("unable to generate bls entries: {:#}", error);
|
||||
Vec::new()
|
||||
}
|
||||
@@ -479,7 +532,7 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
|
||||
|
||||
// Add the entries of the Extended Boot Loader Partition, which are sorted together with the
|
||||
// others. A problem with it should not prevent booting from the entries that are found.
|
||||
if bls.xbootldr {
|
||||
if bls.xbootldr || context.root().options().bls_strict_mode {
|
||||
match xbootldr_root(&context) {
|
||||
Ok(Some(root)) => {
|
||||
let xbootldr_uki = uki_path.as_ref().map(|_| format!("{}\\EFI\\Linux", root));
|
||||
|
||||
@@ -62,7 +62,7 @@ impl ReadAt for FileReader {
|
||||
/// Finds the `.efi` files in `directory` on `filesystem` and reads their sections.
|
||||
/// A missing directory has no images. Files that can't be read as a PE image are skipped,
|
||||
/// as one broken file should not prevent booting any of the other entries.
|
||||
pub fn scan(filesystem: Handle, directory: &str) -> Result<Vec<UkiFile>> {
|
||||
pub fn scan(filesystem: Handle, directory: &str, strict: bool) -> Result<Vec<UkiFile>> {
|
||||
let mut fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(filesystem)
|
||||
.context("unable to open the unified kernel image filesystem")?;
|
||||
let mut root = fs
|
||||
@@ -112,7 +112,7 @@ pub fn scan(filesystem: Handle, directory: &str) -> Result<Vec<UkiFile>> {
|
||||
.map(|profile| UkiProfileEntry {
|
||||
index: profile.index,
|
||||
has_osrel: profile.sections.contains_key(".osrel"),
|
||||
entry: BlsEntry::from_uki_profile(&profile, &path),
|
||||
entry: BlsEntry::from_uki_profile_with(&profile, &path, strict),
|
||||
info: profile.info,
|
||||
})
|
||||
.collect(),
|
||||
|
||||
+177
-111
@@ -372,6 +372,9 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
.context("unable to get last booted entry from bootloader interface"),
|
||||
);
|
||||
|
||||
// Whether to follow the specification and systemd-boot exactly.
|
||||
let strict = context.root().options().bls_strict_mode;
|
||||
|
||||
// Acquire the oneshot entry from the bootloader interface.
|
||||
let bootloader_interface_oneshot_entry = advisory(
|
||||
BootloaderInterface::get_oneshot_entry()
|
||||
@@ -412,8 +415,13 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
.find(|timeout| !matches!(timeout, BootloaderInterfaceTimeout::Unspecified))
|
||||
.unwrap_or_default();
|
||||
|
||||
// The menu timeout in seconds, if no source specified one.
|
||||
let mut menu_timeout = DEFAULT_MENU_TIMEOUT_SECONDS;
|
||||
// The menu timeout in seconds, if no source specified one. systemd-boot does not show the
|
||||
// menu by default, while outside of strict mode it is shown for a while.
|
||||
let mut menu_timeout = if strict {
|
||||
0
|
||||
} else {
|
||||
DEFAULT_MENU_TIMEOUT_SECONDS
|
||||
};
|
||||
|
||||
// Whether the boot menu should wait for the user, instead of counting down.
|
||||
let mut wait_for_user = false;
|
||||
@@ -452,7 +460,13 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
}
|
||||
|
||||
BootloaderInterfaceTimeout::Unspecified => {
|
||||
// Do nothing.
|
||||
if !strict {
|
||||
info!(
|
||||
"no menu timeout is set, so the menu is shown for {} seconds \
|
||||
(systemd-boot and strict mode do not show it)",
|
||||
DEFAULT_MENU_TIMEOUT_SECONDS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +500,15 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
// no entry, such as when the entry was removed, is ignored so that the next source is used.
|
||||
// The preferred entry sources never use an entry with no boot counter tries left.
|
||||
// Each source has whether it matches by id, and whether it skips bad entries.
|
||||
// In strict mode, the one-shot entry is the default entry for this boot, like in
|
||||
// systemd-boot, and the menu or its timeout still decides what is booted.
|
||||
let oneshot_source = if strict {
|
||||
bootloader_interface_oneshot_entry.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for (source, value, by_id, skip_bad) in [
|
||||
("bootloader interface one-shot", oneshot_source, true, false),
|
||||
(
|
||||
"bootloader interface preferred",
|
||||
bootloader_interface_preferred_entry.clone(),
|
||||
@@ -523,20 +545,22 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
let Some(pattern) = pattern else {
|
||||
continue;
|
||||
};
|
||||
let matches = |entry: &BootableEntry| {
|
||||
let matched = if by_id {
|
||||
let pattern_matches = |entry: &BootableEntry| {
|
||||
if by_id {
|
||||
entry.is_match_id(&pattern)
|
||||
} else {
|
||||
entry.is_match(&pattern)
|
||||
};
|
||||
matched && !(skip_bad && entry.is_bad())
|
||||
}
|
||||
};
|
||||
let matches =
|
||||
|entry: &BootableEntry| pattern_matches(entry) && !(skip_bad && entry.is_bad());
|
||||
// A source that only matches entries with no boot counter tries left is skipped while
|
||||
// another source could pick a usable entry, unless every entry is bad.
|
||||
// another source could pick a usable entry, unless every entry is bad. systemd-boot
|
||||
// does not look at the tries for the default entry, so strict mode does not either.
|
||||
let any_usable = entries.iter().any(|entry| !entry.is_bad());
|
||||
if entries
|
||||
.iter()
|
||||
.any(|entry| matches(entry) && (!any_usable || !entry.is_bad()))
|
||||
.any(|entry| matches(entry) && (strict || !any_usable || !entry.is_bad()))
|
||||
{
|
||||
// Mark the matching entries as the default and unmark all the others.
|
||||
for entry in &mut entries {
|
||||
@@ -548,6 +572,18 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if !skip_bad
|
||||
&& entries
|
||||
.iter()
|
||||
.any(|entry| pattern_matches(entry) && entry.is_bad())
|
||||
{
|
||||
warn!(
|
||||
"the {} entry '{}' has no boot counter tries left, so it is not the default \
|
||||
(systemd-boot and strict mode use it)",
|
||||
source, pattern
|
||||
);
|
||||
continue;
|
||||
}
|
||||
warn!("ignoring {} entry '{}': no matching entry", source, pattern);
|
||||
}
|
||||
|
||||
@@ -556,7 +592,11 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
let default_flags = edera_sprout_bls::resolve_default_flags(
|
||||
&entries
|
||||
.iter()
|
||||
.map(|entry| (entry.is_default(), entry.is_bad(), entry.is_extra_profile()))
|
||||
.map(|entry| {
|
||||
// In strict mode, an entry that was asked for stays the default even without tries.
|
||||
let bad = entry.is_bad() && !(strict && entry.is_default());
|
||||
(entry.is_default(), bad, entry.is_extra_profile())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
for (entry, default) in entries.iter_mut().zip(default_flags) {
|
||||
@@ -567,125 +607,151 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the menu timeout to a duration.
|
||||
// A zero timeout boots without showing the menu, so a forced menu with a zero timeout
|
||||
// waits for the user instead.
|
||||
let menu_timeout = if wait_for_user || (force_boot_menu && menu_timeout == 0) {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(menu_timeout))
|
||||
};
|
||||
loop {
|
||||
// Convert the menu timeout to a duration.
|
||||
// A zero timeout boots without showing the menu, so a forced menu with a zero timeout
|
||||
// waits for the user instead.
|
||||
let menu_timeout = if wait_for_user || (force_boot_menu && menu_timeout == 0) {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_secs(menu_timeout))
|
||||
};
|
||||
|
||||
// Determine the menu style based on the options or configuration.
|
||||
// We prefer the options over the configuration to allow for overriding.
|
||||
let menu_style = context
|
||||
.root()
|
||||
.options()
|
||||
.menu_style
|
||||
.unwrap_or(config.options.menu_style);
|
||||
// Determine the menu style based on the options or configuration.
|
||||
// We prefer the options over the configuration to allow for overriding.
|
||||
let menu_style = context
|
||||
.root()
|
||||
.options()
|
||||
.menu_style
|
||||
.unwrap_or(config.options.menu_style);
|
||||
|
||||
// Find the forced boot entry, unless the boot menu is forced.
|
||||
// The oneshot entry from the bootloader interface is forced by id, and it is used instead of
|
||||
// --boot. If the forced boot entry can't be found, such as when it was removed,
|
||||
// the boot menu is used instead.
|
||||
let forced_entry = if force_boot_menu {
|
||||
None
|
||||
} else if let Some(ref oneshot) = bootloader_interface_oneshot_entry {
|
||||
let entry = BootableEntry::find_id(oneshot, entries.iter());
|
||||
if entry.is_none() {
|
||||
warn!("unable to find entry '{}', using the boot menu", oneshot);
|
||||
}
|
||||
entry
|
||||
} else {
|
||||
force_boot_entry.as_ref().and_then(|force_boot_entry| {
|
||||
let entry = BootableEntry::find(force_boot_entry, entries.iter());
|
||||
if entry.is_none() {
|
||||
warn!(
|
||||
"unable to find entry '{}', using the boot menu",
|
||||
force_boot_entry
|
||||
);
|
||||
// Find the forced boot entry, unless the boot menu is forced.
|
||||
// The oneshot entry from the bootloader interface is forced by id, and it is used instead of
|
||||
// --boot. If the forced boot entry can't be found, such as when it was removed,
|
||||
// the boot menu is used instead.
|
||||
let forced_entry = if force_boot_menu {
|
||||
None
|
||||
} else if !strict && let Some(ref oneshot) = bootloader_interface_oneshot_entry {
|
||||
let entry = BootableEntry::find_id(oneshot, entries.iter());
|
||||
match entry {
|
||||
Some(entry) => info!(
|
||||
"booting the one-shot entry {} without showing the menu \
|
||||
(systemd-boot and strict mode only make it the default)",
|
||||
entry.id()
|
||||
),
|
||||
None => warn!("unable to find entry '{}', using the boot menu", oneshot),
|
||||
}
|
||||
entry
|
||||
})
|
||||
};
|
||||
} else {
|
||||
force_boot_entry.as_ref().and_then(|force_boot_entry| {
|
||||
let entry = BootableEntry::find(force_boot_entry, entries.iter());
|
||||
if entry.is_none() {
|
||||
warn!(
|
||||
"unable to find entry '{}', using the boot menu",
|
||||
force_boot_entry
|
||||
);
|
||||
}
|
||||
entry
|
||||
})
|
||||
};
|
||||
|
||||
// Use the forced boot entry if possible, otherwise pick an entry using a boot menu.
|
||||
let entry = match forced_entry {
|
||||
Some(entry) => entry,
|
||||
// Delegate to the menu to select an entry to boot.
|
||||
None => menu::select(&timer, menu_timeout, menu_disabled, menu_style, &entries)
|
||||
.context("unable to select entry via boot menu")?,
|
||||
};
|
||||
// Use the forced boot entry if possible, otherwise pick an entry using a boot menu.
|
||||
let entry = match forced_entry {
|
||||
Some(entry) => entry,
|
||||
// Delegate to the menu to select an entry to boot.
|
||||
None => menu::select(&timer, menu_timeout, menu_disabled, menu_style, &entries)
|
||||
.context("unable to select entry via boot menu")?,
|
||||
};
|
||||
|
||||
// Tell the bootloader interface what the selected entry is.
|
||||
advisory(
|
||||
BootloaderInterface::set_selected_entry(entry.id())
|
||||
.context("unable to set selected entry in bootloader interface"),
|
||||
);
|
||||
// Tell the bootloader interface what the selected entry is.
|
||||
advisory(
|
||||
BootloaderInterface::set_selected_entry(entry.id())
|
||||
.context("unable to set selected entry in bootloader interface"),
|
||||
);
|
||||
|
||||
// Save the selected entry for the next boot when `@saved` asks for it, and forget a saved
|
||||
// entry that is no longer used. A forced boot, such as a one-shot entry, changes nothing,
|
||||
// and neither does an unchanged value.
|
||||
if forced_entry.is_none() {
|
||||
let id = entry.id();
|
||||
if use_saved_entry {
|
||||
if !last_booted_entry
|
||||
// Save the selected entry for the next boot when `@saved` asks for it, and forget a saved
|
||||
// entry that is no longer used. A forced boot, such as a one-shot entry, changes nothing,
|
||||
// and neither does an unchanged value.
|
||||
let oneshot_selected = strict
|
||||
&& bootloader_interface_oneshot_entry
|
||||
.as_deref()
|
||||
.is_some_and(|saved| saved.eq_ignore_ascii_case(&id))
|
||||
{
|
||||
.is_some_and(|oneshot| entry.is_match_id(oneshot));
|
||||
if forced_entry.is_none() && !oneshot_selected {
|
||||
let id = entry.id();
|
||||
if use_saved_entry {
|
||||
if !last_booted_entry
|
||||
.as_deref()
|
||||
.is_some_and(|saved| saved.eq_ignore_ascii_case(&id))
|
||||
{
|
||||
advisory(
|
||||
BootloaderInterface::set_last_booted_entry(&id)
|
||||
.context("unable to save last booted entry in bootloader interface"),
|
||||
);
|
||||
}
|
||||
} else if last_booted_entry.is_some() {
|
||||
advisory(
|
||||
BootloaderInterface::set_last_booted_entry(&id)
|
||||
.context("unable to save last booted entry in bootloader interface"),
|
||||
BootloaderInterface::remove_last_booted_entry()
|
||||
.context("unable to remove last booted entry in bootloader interface"),
|
||||
);
|
||||
}
|
||||
} else if last_booted_entry.is_some() {
|
||||
advisory(
|
||||
BootloaderInterface::remove_last_booted_entry()
|
||||
.context("unable to remove last booted entry in bootloader interface"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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")?;
|
||||
// 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.
|
||||
// An entry with no tries left, which was picked by hand, is counted too, so that it can
|
||||
// still be marked as good.
|
||||
// The tries that were left before this boot used one up, if it did.
|
||||
let mut consumed_tries_left = None;
|
||||
if let Some(target) = entry.boot_counter() {
|
||||
match target.consume() {
|
||||
Ok(path) => {
|
||||
info!("updated boot counter of entry {}: {}", entry.name(), path);
|
||||
consumed_tries_left = Some(target.counter.tries_left);
|
||||
// Tell the system where the counter is, so it can mark the boot as good.
|
||||
let path = edera_sprout_bls::boot_path(&path.to_string());
|
||||
advisory(
|
||||
BootloaderInterface::set_boot_count_path(&path)
|
||||
.context("unable to set boot count path in bootloader interface"),
|
||||
);
|
||||
// Consume a try from the boot counter of the selected entry.
|
||||
// Failing to do so must not prevent the entry from booting.
|
||||
// An entry with no tries left, which was picked by hand, is counted too, so that it can
|
||||
// still be marked as good.
|
||||
// The tries that were left before this boot used one up, if it did.
|
||||
let mut consumed_tries_left = None;
|
||||
if let Some(target) = entry.boot_counter() {
|
||||
match target.consume() {
|
||||
Ok(path) => {
|
||||
info!("updated boot counter of entry {}: {}", entry.name(), path);
|
||||
consumed_tries_left = Some(target.counter.tries_left);
|
||||
// Tell the system where the counter is, so it can mark the boot as good.
|
||||
let path = edera_sprout_bls::boot_path(&path.to_string());
|
||||
advisory(
|
||||
BootloaderInterface::set_boot_count_path(&path)
|
||||
.context("unable to set boot count path in bootloader interface"),
|
||||
);
|
||||
}
|
||||
Err(error) => warn!(
|
||||
"unable to update boot counter of entry {}: {:#}",
|
||||
entry.name(),
|
||||
error
|
||||
),
|
||||
}
|
||||
Err(error) => warn!(
|
||||
"unable to update boot counter of entry {}: {:#}",
|
||||
entry.name(),
|
||||
error
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Decide whether a failure to start the entry reboots the machine. This is only decided now,
|
||||
// as an earlier failure did not use up a try, so a reboot would not make any progress.
|
||||
*reboot_on_error = loader_conf
|
||||
.reboot_on_error
|
||||
.should_reboot(consumed_tries_left);
|
||||
// Decide whether a failure to start the entry reboots the machine. This is only decided now,
|
||||
// as an earlier failure did not use up a try, so a reboot would not make any progress.
|
||||
*reboot_on_error = loader_conf
|
||||
.reboot_on_error
|
||||
.should_reboot(consumed_tries_left);
|
||||
|
||||
// Execute all the actions for the selected entry.
|
||||
for action in &entry.declaration().actions {
|
||||
let action = entry.context().stamp(action);
|
||||
actions::execute(entry.context().clone(), &action)
|
||||
.context(format!("unable to execute action '{}'", action))?;
|
||||
// Execute all the actions for the selected entry.
|
||||
for action in &entry.declaration().actions {
|
||||
let action = entry.context().stamp(action);
|
||||
actions::execute(entry.context().clone(), &action)
|
||||
.context(format!("unable to execute action '{}'", action))?;
|
||||
}
|
||||
|
||||
// Once the image returns, which is not usual, systemd-boot shows the menu again and waits,
|
||||
// while outside of strict mode Sprout goes on to exit to the firmware.
|
||||
if strict {
|
||||
info!("the boot entry returned, showing the menu again");
|
||||
force_boot_menu = true;
|
||||
wait_for_user = true;
|
||||
menu_disabled = false;
|
||||
continue;
|
||||
}
|
||||
info!(
|
||||
"the boot entry returned, so Sprout exits to the firmware \
|
||||
(systemd-boot and strict mode show the menu again)"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in new issue
Block a user