mirror of
https://github.com/edera-dev/sprout.git
synced 2026-11-04 19:02:20 +00:00
feat(bls): one entry for each profile of a unified kernel image
A unified kernel image can have several profiles, each started by a .profile section and made of the sections after it on top of the base. Every profile is an entry. The first has the id of the file, and the others add @ and the identifier or the number of the profile, with a title that names the profile and the profile number as the load options. The profiles share the boot counter of the file, sort in the order they are in the image, and a profile after the first is not picked as the default entry when no default was asked for. The PE reader keeps every section in order, so repeated names and the sections that a profile removes can be told apart, and reads images with more than 96 sections.
This commit is contained in:
1 parent
ac49899294
commit
50f62f5bb6
7 files changed
+628
-86
No files matched your search
@@ -3,6 +3,7 @@ use crate::context::SproutContext;
|
||||
use alloc::format;
|
||||
use alloc::rc::Rc;
|
||||
use alloc::string::{String, ToString};
|
||||
use edera_sprout_bls::profile_entry_id;
|
||||
use edera_sprout_config::entries::EntryDeclaration;
|
||||
use edera_sprout_parsing::{fnmatch_ignore_case, glob_match};
|
||||
|
||||
@@ -18,6 +19,7 @@ pub struct BootableEntry {
|
||||
sort_key: Option<String>,
|
||||
boot_counter: Option<BootCounterTarget>,
|
||||
id_suffix: Option<String>,
|
||||
id_profile: Option<String>,
|
||||
}
|
||||
|
||||
impl BootableEntry {
|
||||
@@ -38,6 +40,7 @@ impl BootableEntry {
|
||||
sort_key: None,
|
||||
boot_counter: None,
|
||||
id_suffix: None,
|
||||
id_profile: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,11 +54,38 @@ impl BootableEntry {
|
||||
/// `fedora.conf`, in lower case. Any other entry has its name as the id.
|
||||
pub fn id(&self) -> String {
|
||||
match self.id_suffix {
|
||||
Some(ref suffix) => format!("{}{}", self.name, suffix).to_lowercase(),
|
||||
Some(ref suffix) => {
|
||||
let file = format!("{}{}", self.sort_name(), suffix);
|
||||
profile_entry_id(&file, self.id_profile.as_deref())
|
||||
}
|
||||
None => self.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the name of the entry without the profile of a unified kernel image, which is
|
||||
/// the same for every profile of the same image.
|
||||
pub fn sort_name(&self) -> &str {
|
||||
self.id_profile
|
||||
.as_deref()
|
||||
.and_then(|profile| {
|
||||
self.name
|
||||
.strip_suffix(profile)
|
||||
.and_then(|name| name.strip_suffix('@'))
|
||||
})
|
||||
.unwrap_or(&self.name)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Set the profile of a unified kernel image that is part of the id of this entry.
|
||||
pub fn set_id_profile(&mut self, profile: &str) {
|
||||
self.id_profile = Some(profile.to_string());
|
||||
}
|
||||
|
||||
/// Set the file extension that is part of the id of this entry, such as `.conf`.
|
||||
pub fn set_id_suffix(&mut self, suffix: &str) {
|
||||
self.id_suffix = Some(suffix.to_string());
|
||||
|
||||
@@ -9,7 +9,9 @@ use alloc::{
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use core::{cmp::Ordering, str::FromStr};
|
||||
use edera_sprout_bls::{BlsEntry, BootCounter, is_reserved_entry_name, sort_bls, strip_extension};
|
||||
use edera_sprout_bls::{
|
||||
BlsEntry, BootCounter, is_reserved_entry_name, profile_id_suffix, sort_bls, strip_extension,
|
||||
};
|
||||
use edera_sprout_config::generators::bls::BlsConfiguration;
|
||||
use log::{info, warn};
|
||||
use uefi::{
|
||||
@@ -54,7 +56,7 @@ fn quirk_initrd_remove_tuned(paths: Vec<String>) -> Vec<String> {
|
||||
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())
|
||||
sort_bls(a_bls, a_boot.sort_name(), b_bls, b_boot.sort_name())
|
||||
}
|
||||
|
||||
/// Produces the bootable entry for the BLS `entry` with the id `name` and the `initrds`.
|
||||
@@ -297,8 +299,7 @@ fn generate_type2(
|
||||
|
||||
let mut found: Vec<(BlsEntry, BootableEntry)> = Vec::new();
|
||||
for image in uki::scan(resolved.filesystem_handle, &directory_name)? {
|
||||
// Only unified kernel images are entries. Images built for another architecture, and
|
||||
// other EFI programs that have no os-release section, are skipped.
|
||||
// Only unified kernel images that are built for this machine are entries.
|
||||
if image.machine != PE_MACHINE {
|
||||
info!(
|
||||
"skipping {} as it is built for another architecture",
|
||||
@@ -306,13 +307,6 @@ fn generate_type2(
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if !image.has_osrel {
|
||||
info!(
|
||||
"skipping {} as it has no .osrel section, so it is not a unified kernel image",
|
||||
image.file_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// The id is the file name without the extension and the boot counter.
|
||||
let file_name = image.file_name;
|
||||
@@ -329,37 +323,71 @@ fn generate_type2(
|
||||
}
|
||||
let id = id.to_string();
|
||||
|
||||
let mut entry = image.entry;
|
||||
entry.boot_counter = boot_counter;
|
||||
// Every profile of the image is an entry. They share the file, and so the boot counter.
|
||||
let mut suffixes: Vec<String> = Vec::new();
|
||||
for profile in image.profiles {
|
||||
// A profile without an os-release section is not a unified kernel image, such as
|
||||
// other EFI programs.
|
||||
if !profile.has_osrel {
|
||||
info!(
|
||||
"skipping {} as it has no .osrel section, so it is not a unified kernel image",
|
||||
file_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut boot = bootable_entry(context, bls, &id, &entry, &[]);
|
||||
boot.set_id_suffix(&extension);
|
||||
// The first profile has the id of the file, and the others name their profile.
|
||||
// A profile that repeats the identifier of an earlier one is told apart by its number.
|
||||
let index = profile.index.unwrap_or(0);
|
||||
let mut suffix = profile_id_suffix(index, &profile.info);
|
||||
if let Some(ref name) = suffix
|
||||
&& suffixes.contains(name)
|
||||
{
|
||||
suffix = Some(index.to_string());
|
||||
}
|
||||
if let Some(ref name) = suffix {
|
||||
suffixes.push(name.clone());
|
||||
}
|
||||
let name = match suffix {
|
||||
Some(ref suffix) => format!("{}@{}", id, suffix),
|
||||
None => id.clone(),
|
||||
};
|
||||
|
||||
// An image with the same id as another entry, such as a leftover copy with another boot
|
||||
// counter, would be ambiguous. Type 1 and type 2 entries have different ids.
|
||||
if entries
|
||||
.iter()
|
||||
.chain(found.iter())
|
||||
.any(|(_, other)| other.id() == boot.id())
|
||||
{
|
||||
warn!(
|
||||
"unified kernel image {} has the same id as another entry, skipping",
|
||||
file_name
|
||||
);
|
||||
continue;
|
||||
let mut entry = profile.entry;
|
||||
entry.boot_counter = boot_counter;
|
||||
|
||||
let mut boot = bootable_entry(context, bls, &name, &entry, &[]);
|
||||
boot.set_id_suffix(&extension);
|
||||
if let Some(ref suffix) = suffix {
|
||||
boot.set_id_profile(suffix);
|
||||
}
|
||||
|
||||
// An image with the same id as another entry, such as a leftover copy with another
|
||||
// boot counter, would be ambiguous. Type 1 and type 2 entries have different ids.
|
||||
if entries
|
||||
.iter()
|
||||
.chain(found.iter())
|
||||
.any(|(_, other)| other.id() == boot.id())
|
||||
{
|
||||
warn!(
|
||||
"unified kernel image {} has the same id as another entry, skipping",
|
||||
file_name
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(counter) = boot_counter {
|
||||
boot.set_boot_counter(BootCounterTarget {
|
||||
counter,
|
||||
filesystem: resolved.filesystem_handle,
|
||||
directory: PathBuf::from(directory.clone()),
|
||||
id: id.clone(),
|
||||
file_name: file_name.clone(),
|
||||
extension: extension.clone(),
|
||||
});
|
||||
}
|
||||
found.push((entry, boot));
|
||||
}
|
||||
|
||||
if let Some(counter) = boot_counter {
|
||||
boot.set_boot_counter(BootCounterTarget {
|
||||
counter,
|
||||
filesystem: resolved.filesystem_handle,
|
||||
directory: PathBuf::from(directory.clone()),
|
||||
id,
|
||||
file_name,
|
||||
extension,
|
||||
});
|
||||
}
|
||||
found.push((entry, boot));
|
||||
}
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use alloc::{
|
||||
vec::Vec,
|
||||
};
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
use edera_sprout_bls::{BlsEntry, PeImage, ReadAt, UKI_SECTIONS, read_pe, strip_extension};
|
||||
use edera_sprout_bls::{
|
||||
BlsEntry, PeImage, ProfileInfo, ReadAt, UKI_SECTIONS, read_pe, strip_extension, uki_profiles,
|
||||
};
|
||||
use log::warn;
|
||||
use uefi::{
|
||||
CString16, Handle, Status,
|
||||
@@ -18,9 +20,19 @@ pub struct UkiFile {
|
||||
pub file_name: String,
|
||||
/// The machine type of the image.
|
||||
pub machine: u16,
|
||||
/// Whether the image has an `.osrel` section, which unified kernel images always have.
|
||||
/// The profiles of the image. An image without profiles has one.
|
||||
pub profiles: Vec<UkiProfileEntry>,
|
||||
}
|
||||
|
||||
/// One profile of a unified kernel image.
|
||||
pub struct UkiProfileEntry {
|
||||
/// The number of the profile, or None if the image has no profiles.
|
||||
pub index: Option<u32>,
|
||||
/// The metadata of the profile.
|
||||
pub info: ProfileInfo,
|
||||
/// Whether the profile has an `.osrel` section, which unified kernel images always have.
|
||||
pub has_osrel: bool,
|
||||
/// The entry made from the sections of the image.
|
||||
/// The entry made from the sections of the profile.
|
||||
pub entry: BlsEntry,
|
||||
}
|
||||
|
||||
@@ -94,9 +106,16 @@ pub fn scan(filesystem: Handle, directory: &str) -> Result<Vec<UkiFile>> {
|
||||
Ok(image) => found.push(UkiFile {
|
||||
file_name,
|
||||
machine: image.machine,
|
||||
has_osrel: image.sections.contains_key(".osrel"),
|
||||
// The sections are dropped here, so only the small entry is kept per image.
|
||||
entry: BlsEntry::from_uki(&image.sections, &path),
|
||||
// The sections are dropped here, so only the small entries are kept per image.
|
||||
profiles: uki_profiles(&image.sections)
|
||||
.into_iter()
|
||||
.map(|profile| UkiProfileEntry {
|
||||
index: profile.index,
|
||||
has_osrel: profile.sections.contains_key(".osrel"),
|
||||
entry: BlsEntry::from_uki_profile(&profile, &path),
|
||||
info: profile.info,
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
Err(error) => warn!("unable to read unified kernel image {}: {:#}", path, error),
|
||||
}
|
||||
|
||||
@@ -547,7 +547,7 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
|
||||
let default_flags = edera_sprout_bls::resolve_default_flags(
|
||||
&entries
|
||||
.iter()
|
||||
.map(|entry| (entry.is_default(), entry.is_bad()))
|
||||
.map(|entry| (entry.is_default(), entry.is_bad(), entry.is_extra_profile()))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
for (entry, default) in entries.iter_mut().zip(default_flags) {
|
||||
|
||||
Reference in new issue
Block a user