mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 17:42:20 +00:00
fix(boot): tidy the devicetree, reboot and profile handling after review
Only warn about a devicetree under Secure Boot when the entry has one, skip the fixup if its protocol can't be opened, and reject a devicetree whose size is smaller than its header. Announce a reboot before the delay, advertise the multi-profile UKI feature, and treat a Type 1 entry with a profile after the first as an extra profile that is not picked as the fallback default. A failure to read Sprout's own partition no longer drops the entries of the XBOOTLDR partition, and the README lists every initrd slot and describes the reboot behaviour more exactly.
This commit is contained in:
1 parent
27c2d32900
commit
4e10d57143
8 files changed
+72
-20
No files matched your search
@@ -13,6 +13,7 @@ use eficore::loader::source::ImageSource;
|
||||
use eficore::loader::{ImageLoadRequest, ImageLoader};
|
||||
use eficore::media_loader::MediaLoaderHandle;
|
||||
use eficore::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
|
||||
use eficore::path::ResolvedPath;
|
||||
use eficore::secure::SecureBoot;
|
||||
use log::warn;
|
||||
use uefi::proto::loaded_image::LoadedImage;
|
||||
@@ -31,6 +32,22 @@ fn read_optional_file(
|
||||
path: &str,
|
||||
what: &str,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let Some(resolved) = resolve_optional_file(context, path, what)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let content = resolved
|
||||
.read_file()
|
||||
.with_context(|| format!("unable to read {}", what))?;
|
||||
Ok(Some(content))
|
||||
}
|
||||
|
||||
/// Resolve the `what` at the stamped `path` relative to the sprout image.
|
||||
/// Provides [None] if the path refers to the root of a filesystem rather than a file.
|
||||
fn resolve_optional_file(
|
||||
context: &Rc<SproutContext>,
|
||||
path: &str,
|
||||
what: &str,
|
||||
) -> Result<Option<ResolvedPath>> {
|
||||
let resolved = eficore::path::resolve_path(Some(context.root().loaded_image_path()?), path)
|
||||
.with_context(|| format!("unable to resolve {} path", what))?;
|
||||
|
||||
@@ -42,11 +59,7 @@ fn read_optional_file(
|
||||
if subpath.trim_matches('\\').is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let content = resolved
|
||||
.read_file()
|
||||
.with_context(|| format!("unable to read {}", what))?;
|
||||
Ok(Some(content))
|
||||
Ok(Some(resolved))
|
||||
}
|
||||
|
||||
/// Unloads the image with the contained handle when dropped, unless the handle is taken.
|
||||
@@ -167,10 +180,12 @@ 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 if let Some(content) = read_optional_file(&context, &path, "devicetree")? {
|
||||
} else {
|
||||
let content = resolved.read_file().context("unable to read devicetree")?;
|
||||
devicetree =
|
||||
Some(DeviceTree::install(&content).context("unable to install the devicetree")?);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ pub struct BootableEntry {
|
||||
boot_counter: Option<BootCounterTarget>,
|
||||
id_suffix: Option<String>,
|
||||
id_profile: Option<String>,
|
||||
extra_profile: bool,
|
||||
}
|
||||
|
||||
impl BootableEntry {
|
||||
@@ -41,6 +42,7 @@ impl BootableEntry {
|
||||
boot_counter: None,
|
||||
id_suffix: None,
|
||||
id_profile: None,
|
||||
extra_profile: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +80,12 @@ impl BootableEntry {
|
||||
/// Fetch whether the entry is a profile of a unified kernel image after the first, which
|
||||
/// is never picked as the default entry unless it was asked for.
|
||||
pub fn is_extra_profile(&self) -> bool {
|
||||
self.id_profile.is_some()
|
||||
self.id_profile.is_some() || self.extra_profile
|
||||
}
|
||||
|
||||
/// Mark this entry as booting a profile of a unified kernel image after the first.
|
||||
pub fn mark_extra_profile(&mut self) {
|
||||
self.extra_profile = true;
|
||||
}
|
||||
|
||||
/// Set the profile of a unified kernel image that is part of the id of this entry.
|
||||
|
||||
@@ -266,6 +266,9 @@ fn generate_type1(
|
||||
|
||||
let mut boot = bootable_entry(context, bls, &name, &entry, &initrds, entry_root);
|
||||
boot.set_id_suffix(&extension);
|
||||
if entry.profile_number() > 0 {
|
||||
boot.mark_extra_profile();
|
||||
}
|
||||
|
||||
// Record where the boot counter lives so a try can be consumed when this entry boots.
|
||||
if let Some(counter) = boot_counter {
|
||||
@@ -458,13 +461,21 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
|
||||
let path = context.stamp(&bls.path);
|
||||
|
||||
let uki_path = bls.uki_path_for(&path);
|
||||
let mut entries = generate_partition(
|
||||
// A problem with this partition only stops the generator when there is no other partition.
|
||||
let mut entries = match generate_partition(
|
||||
&context,
|
||||
bls,
|
||||
&path,
|
||||
uki_path.as_deref(),
|
||||
BlsConfiguration::root_of(&path),
|
||||
)?;
|
||||
) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) if bls.xbootldr => {
|
||||
warn!("unable to generate bls entries: {:#}", error);
|
||||
Vec::new()
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -706,12 +706,18 @@ fn efi_main() -> Status {
|
||||
for (index, stack) in error.chain().enumerate() {
|
||||
error!("[{}]: {}", index, stack);
|
||||
}
|
||||
// Sleep to allow the user to read the error.
|
||||
// Sleep to allow the user to read the error. A reboot is announced first, so it is
|
||||
// known why the machine is about to reset.
|
||||
if reboot_on_error {
|
||||
error!(
|
||||
"rebooting in {} seconds after a failure to start the boot entry",
|
||||
DELAY_ON_ERROR.as_secs()
|
||||
);
|
||||
}
|
||||
uefi::boot::stall(DELAY_ON_ERROR);
|
||||
|
||||
// Reboot when asked to, so that the next boot can use the next try or entry.
|
||||
if reboot_on_error {
|
||||
error!("rebooting after a failure to start the boot entry");
|
||||
uefi::runtime::reset(ResetType::COLD, Status::SUCCESS, None);
|
||||
}
|
||||
return Status::ABORTED;
|
||||
|
||||
@@ -29,7 +29,8 @@ pub struct BlsConfiguration {
|
||||
/// Whether to also read the Extended Boot Loader Partition, which is the partition with the
|
||||
/// XBOOTLDR type on the same disk as the partition Sprout was loaded from. Its entries are
|
||||
/// sorted with the others, and their paths are on that partition, so an action has to use
|
||||
/// the `$entry-root` value to find them, such as `$entry-root\\$chainload`.
|
||||
/// the `$entry-root` value to find them, such as `$entry-root\\$chainload`. Its unified kernel
|
||||
/// images are always in `\\EFI\\Linux`.
|
||||
#[serde(default)]
|
||||
pub xbootldr: bool,
|
||||
/// Whether generated entries keep the name of the BLS entry file as-is.
|
||||
|
||||
@@ -68,6 +68,7 @@ impl BootloaderInterface {
|
||||
| LoaderFeatures::EntryPreferred
|
||||
| LoaderFeatures::SortKey
|
||||
| LoaderFeatures::DeviceTree
|
||||
| LoaderFeatures::MultiProfileUki
|
||||
}
|
||||
|
||||
/// Tell the system that Sprout was initialized at the current time.
|
||||
|
||||
@@ -67,8 +67,8 @@ impl DeviceTree {
|
||||
if magic != FDT_MAGIC {
|
||||
bail!("the file is not a flattened devicetree");
|
||||
}
|
||||
if total > dtb.len() {
|
||||
bail!("the devicetree is truncated");
|
||||
if total < FDT_HEADER_SIZE || total > dtb.len() {
|
||||
bail!("the devicetree has an invalid size");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -127,8 +127,11 @@ impl DeviceTree {
|
||||
let Some(handle) = find_handle(&DT_FIXUP_GUID)? else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut protocol = uefi::boot::open_protocol_exclusive::<DtFixupProtocol>(handle)
|
||||
.context("unable to open the devicetree fixup protocol")?;
|
||||
// Without the protocol the devicetree is used as it is, as in systemd-boot.
|
||||
let Ok(mut protocol) = crate::handle::open_shared::<DtFixupProtocol>(handle) else {
|
||||
warn!("unable to open the devicetree fixup protocol, skipping the fixup");
|
||||
return Ok(());
|
||||
};
|
||||
let fixup = protocol.fixup;
|
||||
let this: *mut DtFixupProtocol = &mut *protocol;
|
||||
let flags = DT_APPLY_FIXUPS | DT_RESERVE_MEMORY;
|
||||
|
||||
Reference in new issue
Block a user