feat(bls): boot unified kernel images (Type #2 entries) (#111)

* feat(bls): read PE sections without loading the whole image

* feat(bls): produce entries from unified kernel images

* feat(bls): boot unified kernel images as type 2 entries

Scan the EFI/Linux directory, or the new uki-path option of the BLS
generator, for .efi files and turn each one into an entry using the
metadata in its PE sections. Autoconfigure now also detects a disk that
only has unified kernel images. An image with the id of a type 1 entry is
skipped, and boot counting works on the .efi file name.

* fix(boot): rename boot counter files in place instead of copying them

* fix(bls): skip hidden files and keep unified kernel images when the type 1 scan fails

Check file name suffixes without slicing by byte offset, drop the PE
section bytes once an image has been parsed, and only fail the generator
on a type 1 error when unified kernel images are disabled.
This commit is contained in:
alex authored and GitHub committed 2026-10-03 19:09:17 -07:00
1 parent 98e8c9ad44
commit f4221131f1
9 files changed
+948 -95

No files matched your search

+73
View File
@@ -1,10 +1,14 @@
use crate::entries::EntryDeclaration;
use alloc::format;
use alloc::string::{String, ToString};
use serde::{Deserialize, Serialize};
/// The default path to the BLS directory.
const BLS_TEMPLATE_PATH: &str = "\\loader";
/// The directory of unified kernel images, relative to the root of the filesystem.
const BLS_UKI_DIRECTORY: &str = "\\EFI\\Linux";
/// The configuration of the BLS generator.
/// The BLS uses the Bootloader Specification to produce
/// entries from an input template.
@@ -15,6 +19,13 @@ pub struct BlsConfiguration {
/// The path to the BLS directory.
#[serde(default = "default_bls_path")]
pub path: String,
/// The path to the directory of unified kernel images, which are BLS Type #2 entries.
/// Every `.efi` file in the directory becomes an entry. When this is not set, the
/// directory is `\EFI\Linux` on the same device as `path`. An empty path disables
/// unified kernel images. Two generators that read the same directory produce entries
/// with the same names, so they should not both scan it.
#[serde(default, rename = "uki-path")]
pub uki_path: Option<String>,
/// Whether generated entries keep the name of the BLS entry file as-is.
/// When enabled, the generator name is not prepended, so the entry names match
/// the BLS entry ids used by tools like `bootctl set-default`. Variants still append
@@ -26,12 +37,29 @@ pub struct BlsConfiguration {
pub pin_names: bool,
}
impl BlsConfiguration {
/// The directory of unified kernel images for the stamped BLS path `bls_path`,
/// or None if unified kernel images are disabled.
pub fn uki_path_for(&self, bls_path: &str) -> Option<String> {
match self.uki_path.as_deref() {
Some("") => None,
Some(path) => Some(path.to_string()),
// Keep the device of the BLS path, which is everything up to the last slash.
None => {
let device = bls_path.rfind('/').map_or("", |index| &bls_path[..=index]);
Some(format!("{}{}", device, BLS_UKI_DIRECTORY))
}
}
}
}
/// The default configuration, which matches an empty BLS generator section.
impl Default for BlsConfiguration {
fn default() -> Self {
Self {
entry: Default::default(),
path: default_bls_path(),
uki_path: None,
pin_names: default_pin_names(),
}
}
@@ -44,3 +72,48 @@ fn default_pin_names() -> bool {
fn default_bls_path() -> String {
BLS_TEMPLATE_PATH.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uki_path_is_next_to_the_default_bls_path() {
let bls = BlsConfiguration::default();
assert_eq!(
bls.uki_path_for("\\loader").as_deref(),
Some("\\EFI\\Linux")
);
}
#[test]
fn uki_path_follows_the_device_of_the_bls_path() {
let bls = BlsConfiguration::default();
assert_eq!(
bls.uki_path_for("PciRoot(0x0)/HD(2,GPT,abc)/\\loader")
.as_deref(),
Some("PciRoot(0x0)/HD(2,GPT,abc)/\\EFI\\Linux")
);
}
#[test]
fn explicit_uki_path_is_used_as_is() {
let bls = BlsConfiguration {
uki_path: Some("\\uki".to_string()),
..Default::default()
};
assert_eq!(
bls.uki_path_for("HD(2,GPT,abc)/\\loader").as_deref(),
Some("\\uki")
);
}
#[test]
fn empty_uki_path_disables_unified_kernel_images() {
let bls = BlsConfiguration {
uki_path: Some(String::new()),
..Default::default()
};
assert_eq!(bls.uki_path_for("\\loader"), None);
}
}