2025-11-02 23:28:31 -05:00
|
|
|
use crate::entries::EntryDeclaration;
|
2026-10-03 19:09:17 -07:00
|
|
|
use alloc::format;
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::string::{String, ToString};
|
2025-11-02 23:28:31 -05:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
/// The default path to the BLS directory.
|
|
|
|
|
const BLS_TEMPLATE_PATH: &str = "\\loader";
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
/// The directory of unified kernel images, relative to the root of the filesystem.
|
|
|
|
|
const BLS_UKI_DIRECTORY: &str = "\\EFI\\Linux";
|
|
|
|
|
|
2025-11-02 23:28:31 -05:00
|
|
|
/// The configuration of the BLS generator.
|
|
|
|
|
/// The BLS uses the Bootloader Specification to produce
|
|
|
|
|
/// entries from an input template.
|
2026-10-03 15:16:05 -07:00
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
2025-11-02 23:28:31 -05:00
|
|
|
pub struct BlsConfiguration {
|
|
|
|
|
/// The entry to use for as a template.
|
|
|
|
|
pub entry: EntryDeclaration,
|
|
|
|
|
/// The path to the BLS directory.
|
|
|
|
|
#[serde(default = "default_bls_path")]
|
|
|
|
|
pub path: String,
|
2026-10-03 19:09:17 -07:00
|
|
|
/// 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>,
|
2026-10-03 20:03:23 -07:00
|
|
|
/// 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
|
2026-10-03 20:19:21 -07:00
|
|
|
/// the `$entry-root` value to find them, such as `$entry-root\\$chainload`. Its unified kernel
|
|
|
|
|
/// images are always in `\\EFI\\Linux`.
|
2026-10-03 20:03:23 -07:00
|
|
|
#[serde(default)]
|
|
|
|
|
pub xbootldr: bool,
|
2026-10-03 15:16:05 -07:00
|
|
|
/// 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
|
|
|
|
|
/// their choice names, so entries of a generator with variants never match the ids
|
|
|
|
|
/// exactly, and are best selected with a pattern like `<id>-*`. Two BLS generators
|
|
|
|
|
/// that read the same BLS directory will produce entries with the same names, so
|
|
|
|
|
/// generators that reuse a BLS directory should disable this.
|
|
|
|
|
#[serde(default = "default_pin_names", rename = "pin-names")]
|
|
|
|
|
pub pin_names: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
impl BlsConfiguration {
|
2026-10-03 20:03:23 -07:00
|
|
|
/// The device of the stamped BLS path `bls_path`, which is everything up to the last slash,
|
|
|
|
|
/// and which is empty for a path on the partition Sprout was loaded from.
|
|
|
|
|
pub fn root_of(bls_path: &str) -> &str {
|
|
|
|
|
bls_path.rfind('/').map_or("", |index| &bls_path[..=index])
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
/// 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.
|
2026-10-03 20:03:23 -07:00
|
|
|
None => Some(format!("{}{}", Self::root_of(bls_path), BLS_UKI_DIRECTORY)),
|
2026-10-03 19:09:17 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 15:16:05 -07:00
|
|
|
/// 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(),
|
2026-10-03 19:09:17 -07:00
|
|
|
uki_path: None,
|
2026-10-03 20:03:23 -07:00
|
|
|
xbootldr: false,
|
2026-10-03 15:16:05 -07:00
|
|
|
pin_names: default_pin_names(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_pin_names() -> bool {
|
|
|
|
|
true
|
2025-11-02 23:28:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_bls_path() -> String {
|
|
|
|
|
BLS_TEMPLATE_PATH.to_string()
|
|
|
|
|
}
|
2026-10-03 19:09:17 -07:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2026-10-03 20:03:23 -07:00
|
|
|
#[test]
|
|
|
|
|
fn root_is_the_device_of_the_bls_path() {
|
|
|
|
|
assert_eq!(BlsConfiguration::root_of("\\loader"), "");
|
|
|
|
|
assert_eq!(
|
|
|
|
|
BlsConfiguration::root_of("PciRoot(0x0)/HD(2,GPT,abc)/\\loader"),
|
|
|
|
|
"PciRoot(0x0)/HD(2,GPT,abc)/"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn xbootldr_is_off_by_default() {
|
|
|
|
|
assert!(!BlsConfiguration::default().xbootldr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
#[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);
|
|
|
|
|
}
|
|
|
|
|
}
|