Files
sprout/crates/boot/src/generators/bls.rs
T

174 lines
6.5 KiB
Rust
Raw Normal View History

2025-10-13 01:54:03 -07:00
use crate::context::SproutContext;
use crate::entries::BootableEntry;
use alloc::{
format,
rc::Rc,
string::{String, ToString},
vec::Vec,
};
2025-10-13 01:54:03 -07:00
use anyhow::{Context, Result};
use core::{cmp::Ordering, str::FromStr};
use edera_sprout_bls::{BlsEntry, sort_bls};
use edera_sprout_config::generators::bls::BlsConfiguration;
use uefi::{
cstr16,
fs::{FileSystem, PathBuf},
proto::device_path::text::{AllowShortcuts, DisplayOnly},
proto::media::fs::SimpleFileSystem,
};
// TODO(azenla): remove this once variable substitution is implemented.
/// This function is used to remove the `tuned_initrd` variable from entry values.
/// Fedora uses tuned which adds an initrd that shouldn't be used.
fn quirk_initrd_remove_tuned(input: String) -> String {
input.replace("$tuned_initrd", "").trim().to_string()
}
/// Sorts two entries according to the BLS sort system.
2025-12-21 14:11:10 -08:00
/// Reference: <https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting>
fn sort_entries(a: &(BlsEntry, BootableEntry), b: &(BlsEntry, BootableEntry)) -> Ordering {
let (a_bls, a_boot) = a;
let (b_bls, b_boot) = b;
sort_bls(a_bls, a_boot.name(), b_bls, b_boot.name())
}
/// Generates entries from the BLS entries directory using the specified `bls` configuration and
/// `context`. The BLS conversion is best-effort and will ignore any unsupported entries.
pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Vec<BootableEntry>> {
2025-10-13 01:54:03 -07:00
let mut entries = Vec::new();
// Stamp the path to the BLS directory.
2025-10-13 01:54:03 -07:00
let path = context.stamp(&bls.path);
// Resolve the path to the BLS directory.
let bls_resolved =
eficore::path::resolve_path(Some(context.root().loaded_image_path()?), &path)
.context("unable to resolve bls path")?;
// Construct a filesystem path to the BLS entries directory.
let mut entries_path = PathBuf::from(
bls_resolved
.sub_path
.to_string(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert bls path to string")?,
);
entries_path.push(cstr16!("entries"));
// Open exclusive access to the BLS filesystem.
let fs =
uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(bls_resolved.filesystem_handle)
.context("unable to open bls filesystem")?;
2025-10-13 01:54:03 -07:00
let mut fs = FileSystem::new(fs);
// Read the BLS entries directory.
2025-10-13 01:54:03 -07:00
let entries_iter = fs
.read_dir(&entries_path)
.context("unable to read bls entries")?;
2025-10-13 01:54:03 -07:00
// For each entry in the BLS entries directory, parse the entry and add it to the list.
2025-10-13 01:54:03 -07:00
for entry in entries_iter {
// Unwrap the entry file info.
let entry = entry.context("unable to read bls item entry")?;
// Skip items that are not regular files.
2025-10-13 01:54:03 -07:00
if !entry.is_regular_file() {
continue;
}
// Get the file name of the filesystem item.
let mut name = entry.file_name().to_string();
// Ignore files that are not .conf files.
if !name.to_lowercase().ends_with(".conf") {
2025-10-13 01:54:03 -07:00
continue;
}
// Remove the .conf extension.
name.truncate(name.len() - 5);
// Skip over files that are named just ".conf" as they are not valid entry files.
if name.is_empty() {
continue;
}
// Create a mutable path so we can append the file name to produce the full path.
let mut full_entry_path = entries_path.to_path_buf();
full_entry_path.push(entry.file_name());
// Read the entry file.
2025-10-13 01:54:03 -07:00
let content = fs
.read(full_entry_path)
.context("unable to read bls file")?;
// Parse the entry file as a UTF-8 string.
let content = String::from_utf8(content).context("unable to read bls entry as utf8")?;
// Parse the entry file as a BLS entry.
let entry = BlsEntry::from_str(&content).context("unable to parse bls entry")?;
2025-10-13 01:54:03 -07:00
// Ignore entries that are not valid for Sprout.
2025-10-13 01:54:03 -07:00
if !entry.is_valid() {
continue;
}
// Produce a new sprout context for the entry with the extracted values.
2025-10-13 01:54:03 -07:00
let mut context = context.fork();
let title_base = entry.title().unwrap_or_else(|| name.clone());
let chainload = entry.chainload_path().unwrap_or_default();
let options = entry.options().unwrap_or_default();
let version = entry.version().unwrap_or_default();
let machine_id = entry.machine_id().unwrap_or_default();
// Put the initrd through a quirk modifier to support Fedora.
let initrd = quirk_initrd_remove_tuned(entry.initrd_path().unwrap_or_default());
// Combine the title with the version if a version is present, except if it already contains it.
// Sometimes BLS will have a version in the title already, and this makes it unique.
let title_full = if !version.is_empty() && !title_base.contains(&version) {
format!("{} {}", title_base, version)
} else {
title_base.clone()
};
context.set("title-base", title_base);
context.set("title", title_full);
context.set("chainload", chainload);
context.set("options", options);
context.set("initrd", initrd);
context.set("version", version);
context.set("machine-id", machine_id);
2025-10-13 01:54:03 -07:00
// Produce a new bootable entry.
let mut boot = BootableEntry::new(
name,
bls.entry.title.clone(),
context.freeze(),
bls.entry.clone(),
);
// Pin the entry name to prevent prefixing.
// This is needed as the bootloader interface requires the name to be
// the same as the entry file name, minus the .conf extension.
boot.mark_pin_name();
// Add the BLS entry to the list, along with the bootable entry.
entries.push((entry, boot));
2025-10-13 01:54:03 -07:00
}
// Sort all the entries according to the BLS sort system.
entries.sort_by(sort_entries);
// Grab the number of entries that we have, so we can calculate a reverse index.
let entry_count = entries.len();
// Set the sort keys of all the bootable entries to a semi-unique prefix + the BLS sort order.
// The final comparison happens using version comparison, so this will sort
// things properly.
for (idx, (_bls, boot)) in entries.iter_mut().enumerate() {
boot.set_sort_key(format!("bls-{}-{}", path, entry_count - idx - 1));
}
// Collect all the bootable entries and return them.
Ok(entries.into_iter().map(|(_, boot)| boot).collect())
2025-10-13 01:54:03 -07:00
}