2026-09-26 19:45:47 -07:00
|
|
|
use crate::generators::bls::BLS_INITRD_SLOTS;
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::string::ToString;
|
|
|
|
|
use alloc::{format, vec};
|
2025-10-27 12:25:22 -04:00
|
|
|
use anyhow::{Context, Result};
|
2025-11-02 23:28:31 -05:00
|
|
|
use edera_sprout_config::RootConfiguration;
|
|
|
|
|
use edera_sprout_config::actions::ActionDeclaration;
|
|
|
|
|
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
|
|
|
|
|
use edera_sprout_config::entries::EntryDeclaration;
|
|
|
|
|
use edera_sprout_config::generators::GeneratorDeclaration;
|
|
|
|
|
use edera_sprout_config::generators::bls::BlsConfiguration;
|
2026-03-25 15:56:02 -04:00
|
|
|
use edera_sprout_parsing::unique_hash;
|
2025-10-27 04:28:25 -04:00
|
|
|
use uefi::cstr16;
|
|
|
|
|
use uefi::fs::{FileSystem, Path};
|
|
|
|
|
use uefi::proto::device_path::DevicePath;
|
|
|
|
|
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
|
|
|
|
|
|
|
|
|
/// The name prefix of the BLS chainload action that will be used
|
|
|
|
|
/// by the BLS generator to chainload entries.
|
|
|
|
|
const BLS_CHAINLOAD_ACTION_PREFIX: &str = "bls-chainload-";
|
|
|
|
|
|
2026-10-04 10:18:54 -07:00
|
|
|
/// Detect whether the `filesystem` has a BLS supported configuration.
|
|
|
|
|
pub fn detect(filesystem: &mut FileSystem) -> Result<bool> {
|
2025-10-27 04:28:25 -04:00
|
|
|
// BLS has a loader.conf file that can specify its own auto-entries mechanism.
|
|
|
|
|
let bls_loader_conf_path = Path::new(cstr16!("\\loader\\loader.conf"));
|
|
|
|
|
// BLS also has an entries directory that can specify explicit entries.
|
|
|
|
|
let bls_entries_path = Path::new(cstr16!("\\loader\\entries"));
|
2026-10-03 19:09:17 -07:00
|
|
|
// BLS Type #2 entries are unified kernel images in the EFI/Linux directory.
|
|
|
|
|
let bls_uki_path = Path::new(cstr16!("\\EFI\\Linux"));
|
2025-10-27 04:28:25 -04:00
|
|
|
|
|
|
|
|
// Whether we have a loader.conf file.
|
|
|
|
|
let has_loader_conf = filesystem
|
|
|
|
|
.try_exists(bls_loader_conf_path)
|
|
|
|
|
.context("unable to check for BLS loader.conf file")?;
|
|
|
|
|
|
|
|
|
|
// Whether we have an entries directory.
|
2026-09-26 20:17:22 -07:00
|
|
|
// We actually iterate the entries to see if there are any entry files, as the
|
|
|
|
|
// directory itself always has the "." and ".." items, even when it is empty.
|
2025-10-27 04:28:25 -04:00
|
|
|
let has_entries_dir = filesystem
|
|
|
|
|
.read_dir(bls_entries_path)
|
2026-09-26 20:17:22 -07:00
|
|
|
.map(|mut iterator| {
|
|
|
|
|
iterator.any(|entry| {
|
|
|
|
|
entry.is_ok_and(|entry| {
|
|
|
|
|
entry.is_regular_file()
|
|
|
|
|
&& entry
|
|
|
|
|
.file_name()
|
|
|
|
|
.to_string()
|
|
|
|
|
.to_lowercase()
|
|
|
|
|
.ends_with(".conf")
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-10-27 04:28:25 -04:00
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
2026-10-03 19:09:17 -07:00
|
|
|
// Whether we have any unified kernel images.
|
|
|
|
|
let has_ukis = filesystem
|
|
|
|
|
.read_dir(bls_uki_path)
|
|
|
|
|
.map(|mut iterator| {
|
|
|
|
|
iterator.any(|entry| {
|
|
|
|
|
entry.is_ok_and(|entry| {
|
|
|
|
|
entry.is_regular_file()
|
|
|
|
|
&& entry
|
|
|
|
|
.file_name()
|
|
|
|
|
.to_string()
|
|
|
|
|
.to_lowercase()
|
|
|
|
|
.ends_with(".efi")
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
2025-10-27 04:28:25 -04:00
|
|
|
// Detect if a BLS supported configuration is on this filesystem.
|
2026-10-03 19:09:17 -07:00
|
|
|
// We check loader.conf, the entries directory and unified kernel images, as only one of
|
|
|
|
|
// them is required.
|
2026-10-04 10:18:54 -07:00
|
|
|
Ok(has_loader_conf || has_entries_dir || has_ukis)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add the BLS generator and its action for the partition with the device path `root`.
|
|
|
|
|
/// In `strict` mode, the generator also reads the extended boot loader partition of the disk.
|
|
|
|
|
pub fn add_generator(
|
|
|
|
|
config: &mut RootConfiguration,
|
|
|
|
|
root: &DevicePath,
|
|
|
|
|
strict: bool,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
// Convert the device path root to a string we can use in the configuration.
|
|
|
|
|
let mut root = root
|
|
|
|
|
.to_string16(DisplayOnly(false), AllowShortcuts(false))
|
|
|
|
|
.context("unable to convert device root to string")?
|
|
|
|
|
.to_string();
|
|
|
|
|
// Add a trailing forward-slash to the root to ensure the device root is completed.
|
|
|
|
|
root.push('/');
|
|
|
|
|
|
|
|
|
|
// Generate a unique hash of the root path.
|
|
|
|
|
let root_unique_hash = unique_hash(&root);
|
2025-10-27 04:28:25 -04:00
|
|
|
|
|
|
|
|
// Generate a unique name for the BLS chainload action.
|
2025-10-27 18:21:28 -04:00
|
|
|
let chainload_action_name = format!("{}{}", BLS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,);
|
2025-10-27 04:28:25 -04:00
|
|
|
|
|
|
|
|
// BLS is now detected, generate a configuration for it.
|
|
|
|
|
let generator = BlsConfiguration {
|
|
|
|
|
entry: EntryDeclaration {
|
|
|
|
|
title: "$title".to_string(),
|
|
|
|
|
actions: vec![chainload_action_name.clone()],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
path: format!("{}\\loader", root),
|
2026-10-03 19:09:17 -07:00
|
|
|
uki_path: None,
|
2026-10-03 20:03:23 -07:00
|
|
|
// Every filesystem is scanned, including the Extended Boot Loader Partition, which
|
2026-10-04 10:18:54 -07:00
|
|
|
// gets a generator of its own, unless strict mode reads it with the generator of the
|
|
|
|
|
// partition that Sprout was loaded from.
|
|
|
|
|
xbootldr: strict,
|
2026-10-03 15:16:05 -07:00
|
|
|
pin_names: true,
|
2025-10-27 04:28:25 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Generate a unique name for the BLS generator and insert the generator into the configuration.
|
|
|
|
|
config.generators.insert(
|
2025-10-30 15:31:26 -04:00
|
|
|
format!("auto-bls-{}", root_unique_hash),
|
2025-10-27 04:28:25 -04:00
|
|
|
GeneratorDeclaration {
|
|
|
|
|
bls: Some(generator),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Generate a chainload configuration for BLS.
|
|
|
|
|
// BLS will provide these values to us.
|
2026-09-26 19:45:47 -07:00
|
|
|
// Every initrd slot is chained in order. Unused slots stamp to the root of the
|
|
|
|
|
// filesystem, which the chainload action skips.
|
2026-10-04 10:18:54 -07:00
|
|
|
// The files of an entry are on the partition of the entry, which is the root of the
|
|
|
|
|
// generator for most entries, and the extended boot loader partition for some in strict mode.
|
|
|
|
|
let entry_root = if strict { "$entry-root" } else { &root };
|
2025-10-27 04:28:25 -04:00
|
|
|
let chainload = ChainloadConfiguration {
|
2026-10-04 10:18:54 -07:00
|
|
|
path: format!("{}\\$chainload", entry_root),
|
2025-10-27 04:28:25 -04:00
|
|
|
options: vec!["$options".to_string()],
|
2026-09-26 19:45:47 -07:00
|
|
|
linux_initrd: None,
|
|
|
|
|
linux_initrd_chain: (0..BLS_INITRD_SLOTS)
|
2026-10-04 10:18:54 -07:00
|
|
|
.map(|slot| format!("{}\\$initrd-{}", entry_root, slot))
|
2026-09-26 19:45:47 -07:00
|
|
|
.collect(),
|
2026-10-03 19:52:21 -07:00
|
|
|
// An unset devicetree stamps to the root of the filesystem, which the action skips.
|
2026-10-04 10:18:54 -07:00
|
|
|
devicetree: Some(format!("{}\\$devicetree", entry_root)),
|
2025-10-27 04:28:25 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Insert the chainload action into the configuration.
|
|
|
|
|
config.actions.insert(
|
|
|
|
|
chainload_action_name,
|
|
|
|
|
ActionDeclaration {
|
|
|
|
|
chainload: Some(chainload),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2026-10-04 10:18:54 -07:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Scan the specified `filesystem` for BLS configurations.
|
|
|
|
|
pub fn scan(
|
|
|
|
|
filesystem: &mut FileSystem,
|
|
|
|
|
root: &DevicePath,
|
|
|
|
|
config: &mut RootConfiguration,
|
|
|
|
|
) -> Result<bool> {
|
|
|
|
|
if !detect(filesystem)? {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
add_generator(config, root, false)?;
|
2025-10-27 04:28:25 -04:00
|
|
|
Ok(true)
|
|
|
|
|
}
|