mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-20 01:00:17 +00:00
document ( by hand :( ) all of the code of sprout
This commit is contained in:
@@ -11,70 +11,109 @@ use uefi::fs::{FileSystem, Path};
|
||||
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
|
||||
use uefi::proto::media::fs::SimpleFileSystem;
|
||||
|
||||
/// BLS entry parser.
|
||||
mod entry;
|
||||
|
||||
/// The default path to the BLS entries directory.
|
||||
const BLS_TEMPLATE_PATH: &str = "\\loader\\entries";
|
||||
|
||||
/// The configuration of the BLS generator.
|
||||
/// The BLS uses the Bootloader Specification to produce
|
||||
/// entries from an input template.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct BlsConfiguration {
|
||||
/// The entry to use for as a template.
|
||||
pub entry: EntryDeclaration,
|
||||
/// The path to the BLS entries directory.
|
||||
#[serde(default = "default_bls_path")]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
fn default_bls_path() -> String {
|
||||
"\\loader\\entries".to_string()
|
||||
BLS_TEMPLATE_PATH.to_string()
|
||||
}
|
||||
|
||||
/// 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<(Rc<SproutContext>, EntryDeclaration)>> {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// Stamp the path to the BLS entries directory.
|
||||
let path = context.stamp(&bls.path);
|
||||
|
||||
// Resolve the path to the BLS entries directory.
|
||||
let resolved = utils::resolve_path(context.root().loaded_image_path()?, &path)
|
||||
.context("unable to resolve bls path")?;
|
||||
|
||||
// Open exclusive access to the BLS filesystem.
|
||||
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(resolved.filesystem_handle)
|
||||
.context("unable to open bls filesystem")?;
|
||||
let mut fs = FileSystem::new(fs);
|
||||
|
||||
// Convert the subpath to the BLS entries directory to a string.
|
||||
let sub_text_path = resolved
|
||||
.sub_path
|
||||
.to_string(DisplayOnly(false), AllowShortcuts(false))
|
||||
.context("unable to convert subpath to string")?;
|
||||
|
||||
// Produce a path to the BLS entries directory.
|
||||
let entries_path = Path::new(&sub_text_path);
|
||||
|
||||
// Read the BLS entries directory.
|
||||
let entries_iter = fs
|
||||
.read_dir(entries_path)
|
||||
.context("unable to read bls entries")?;
|
||||
|
||||
// For each entry in the BLS entries directory, parse the entry and add it to the list.
|
||||
for entry in entries_iter {
|
||||
let entry = entry?;
|
||||
// Unwrap the entry file info.
|
||||
let entry = entry.context("unable to read bls item entry")?;
|
||||
|
||||
// Skip items that are not regular files.
|
||||
if !entry.is_regular_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the file name of the filesystem item.
|
||||
let name = entry.file_name().to_string();
|
||||
|
||||
// Ignore files that are not .conf files.
|
||||
if !name.ends_with(".conf") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Produce the full path to the entry file.
|
||||
let full_entry_path = CString16::try_from(format!("{}\\{}", sub_text_path, name).as_str())
|
||||
.context("unable to construct full entry path")?;
|
||||
let full_entry_path = Path::new(&full_entry_path);
|
||||
|
||||
// Read the entry file.
|
||||
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")?;
|
||||
|
||||
// Ignore entries that are not valid for Sprout.
|
||||
if !entry.is_valid() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Produce a new sprout context for the entry with the extracted values.
|
||||
let mut context = context.fork();
|
||||
context.set("title", entry.title().unwrap_or(name));
|
||||
context.set("chainload", entry.chainload_path().unwrap_or_default());
|
||||
context.set("options", entry.options().unwrap_or_default());
|
||||
context.set("initrd", entry.initrd_path().unwrap_or_default());
|
||||
|
||||
// Add the entry to the list with a frozen context.
|
||||
entries.push((context.freeze(), bls.entry.clone()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +1,82 @@
|
||||
use anyhow::{Error, Result};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Represents a parsed BLS entry.
|
||||
/// Fields unrelated to Sprout are not included.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct BlsEntry {
|
||||
/// The title of the entry.
|
||||
pub title: Option<String>,
|
||||
/// The options to pass to the entry.
|
||||
pub options: Option<String>,
|
||||
/// The path to the linux kernel.
|
||||
pub linux: Option<String>,
|
||||
/// The path to the initrd.
|
||||
pub initrd: Option<String>,
|
||||
/// The path to an EFI image.
|
||||
pub efi: Option<String>,
|
||||
}
|
||||
|
||||
/// Parser for a BLS entry.
|
||||
impl FromStr for BlsEntry {
|
||||
type Err = Error;
|
||||
|
||||
/// Parses the `input` as a BLS entry file.
|
||||
fn from_str(input: &str) -> Result<Self> {
|
||||
// All the fields in a BLS entry we understand.
|
||||
// Set all to None initially.
|
||||
let mut title: Option<String> = None;
|
||||
let mut options: Option<String> = None;
|
||||
let mut linux: Option<String> = None;
|
||||
let mut initrd: Option<String> = None;
|
||||
let mut efi: Option<String> = None;
|
||||
|
||||
// Iterate over each line in the input and parse it.
|
||||
for line in input.lines() {
|
||||
// Trim the line.
|
||||
let line = line.trim();
|
||||
|
||||
// Split the line once by a space.
|
||||
let Some((key, value)) = line.split_once(" ") else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Match the key to a field we understand.
|
||||
match key {
|
||||
// The title of the entry.
|
||||
"title" => {
|
||||
title = Some(value.trim().to_string());
|
||||
}
|
||||
|
||||
// The options to pass to the entry.
|
||||
"options" => {
|
||||
options = Some(value.trim().to_string());
|
||||
}
|
||||
|
||||
// The path to the linux kernel.
|
||||
"linux" => {
|
||||
linux = Some(value.trim().to_string());
|
||||
}
|
||||
|
||||
// The path to the initrd.
|
||||
"initrd" => {
|
||||
initrd = Some(value.trim().to_string());
|
||||
}
|
||||
|
||||
// The path to an EFI image.
|
||||
"efi" => {
|
||||
efi = Some(value.trim().to_string());
|
||||
}
|
||||
|
||||
// Ignore any other key.
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BlsEntry {
|
||||
// Produce a BLS entry from the parsed fields.
|
||||
Ok(Self {
|
||||
title,
|
||||
options,
|
||||
linux,
|
||||
@@ -64,10 +87,14 @@ impl FromStr for BlsEntry {
|
||||
}
|
||||
|
||||
impl BlsEntry {
|
||||
/// Checks if this BLS entry is something we can actually boot in Sprout.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.linux.is_some() || self.efi.is_some()
|
||||
}
|
||||
|
||||
/// Fetches the path to an EFI bootable image to boot, if any.
|
||||
/// This prioritizes the linux field over efi.
|
||||
/// It also converts / to \\ to match EFI path style.
|
||||
pub fn chainload_path(&self) -> Option<String> {
|
||||
self.linux
|
||||
.clone()
|
||||
@@ -75,16 +102,20 @@ impl BlsEntry {
|
||||
.map(|path| path.replace("/", "\\").trim_start_matches("\\").to_string())
|
||||
}
|
||||
|
||||
/// Fetches the path to an initrd to pass to the kernel, if any.
|
||||
/// It also converts / to \\ to match EFI path style.
|
||||
pub fn initrd_path(&self) -> Option<String> {
|
||||
self.initrd
|
||||
.clone()
|
||||
.map(|path| path.replace("/", "\\").trim_start_matches("\\").to_string())
|
||||
}
|
||||
|
||||
/// Fetches the options to pass to the kernel, if any.
|
||||
pub fn options(&self) -> Option<String> {
|
||||
self.options.clone()
|
||||
}
|
||||
|
||||
/// Fetches the title of the entry, if any.
|
||||
pub fn title(&self) -> Option<String> {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
@@ -5,21 +5,37 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Matrix generator configuration.
|
||||
/// The matrix generator produces multiple entries based
|
||||
/// on input values multiplicatively.
|
||||
#[derive(Serialize, Deserialize, Default, Clone)]
|
||||
pub struct MatrixConfiguration {
|
||||
/// The template entry to use for each generated entry.
|
||||
#[serde(default)]
|
||||
pub entry: EntryDeclaration,
|
||||
/// The values to use as the input for the matrix.
|
||||
#[serde(default)]
|
||||
pub values: BTreeMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
/// Builds out multiple generations of `input` based on a matrix style.
|
||||
/// For example, if input is: {"x": ["a", "b"], "y": ["c", "d"]}
|
||||
/// It will produce:
|
||||
/// x: a, y: c
|
||||
/// x: a, y: d
|
||||
/// x: b, y: c
|
||||
/// x: b, y: d
|
||||
fn build_matrix(input: &BTreeMap<String, Vec<String>>) -> Vec<BTreeMap<String, String>> {
|
||||
// Convert the input into a vector of tuples.
|
||||
let items: Vec<(String, Vec<String>)> = input.clone().into_iter().collect();
|
||||
|
||||
// The result is a vector of maps.
|
||||
let mut result: Vec<BTreeMap<String, String>> = vec![BTreeMap::new()];
|
||||
|
||||
for (key, values) in items {
|
||||
let mut new_result = Vec::new();
|
||||
|
||||
// Produce all the combinations of the input values.
|
||||
for combination in &result {
|
||||
for value in &values {
|
||||
let mut new_combination = combination.clone();
|
||||
@@ -34,18 +50,23 @@ fn build_matrix(input: &BTreeMap<String, Vec<String>>) -> Vec<BTreeMap<String, S
|
||||
result.into_iter().filter(|item| !item.is_empty()).collect()
|
||||
}
|
||||
|
||||
/// Generates a set of entries using the specified `matrix` configuration in the `context`.
|
||||
pub fn generate(
|
||||
context: Rc<SproutContext>,
|
||||
matrix: &MatrixConfiguration,
|
||||
) -> Result<Vec<(Rc<SproutContext>, EntryDeclaration)>> {
|
||||
// Produce all the combinations of the input values.
|
||||
let combinations = build_matrix(&matrix.values);
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// For each combination, create a new context and entry.
|
||||
for combination in combinations {
|
||||
let mut context = context.fork();
|
||||
// Insert the combination into the context.
|
||||
context.insert(&combination);
|
||||
let context = context.freeze();
|
||||
|
||||
// Stamp the entry title and actions from the template.
|
||||
let mut entry = matrix.entry.clone();
|
||||
entry.title = context.stamp(&entry.title);
|
||||
entry.actions = entry
|
||||
@@ -53,6 +74,7 @@ pub fn generate(
|
||||
.into_iter()
|
||||
.map(|action| context.stamp(action))
|
||||
.collect();
|
||||
// Push the entry into the list with the new context.
|
||||
entries.push((context, entry));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user