use alloc::string::{String, ToString}; use anyhow::{Error, Result}; use core::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, /// The options to pass to the entry. pub options: Option, /// The path to the linux kernel. pub linux: Option, /// The path to the initrd. pub initrd: Option, /// The path to an EFI image. pub efi: Option, /// The sort key for the entry. pub sort_key: Option, /// The version of the entry. pub version: Option, /// The machine id of the entry. pub machine_id: Option, } /// 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 { // All the fields in a BLS entry we understand. // Set all to None initially. let mut title: Option = None; let mut options: Option = None; let mut linux: Option = None; let mut initrd: Option = None; let mut efi: Option = None; let mut sort_key: Option = None; let mut version: Option = None; let mut machine_id: Option = None; // Iterate over each line in the input and parse it. for line in input.lines() { // Trim the line. let line = line.trim(); // Skip over empty lines and comments. if line.is_empty() || line.starts_with('#') { continue; } // Split the line once by whitespace. This technically includes newlines but since // the lines iterator is used, there should never be a newline here. let Some((key, value)) = line.split_once(char::is_whitespace) 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()); } "sort-key" => { sort_key = Some(value.trim().to_string()); } "version" => { version = Some(value.trim().to_string()); } "machine-id" => { machine_id = Some(value.trim().to_string()); } // Ignore any other key. _ => { continue; } } } // Produce a BLS entry from the parsed fields. Ok(Self { title, options, linux, initrd, efi, sort_key, version, machine_id, }) } } 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 { self.linux .clone() .or(self.efi.clone()) .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 { 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 { self.options.clone() } /// Fetches the title of the entry, if any. pub fn title(&self) -> Option { self.title.clone() } /// Fetches the sort key of the entry, if any. pub fn sort_key(&self) -> Option { self.sort_key.clone() } /// Fetches the version of the entry, if any. pub fn version(&self) -> Option { self.version.clone() } /// Fetches the machine id of the entry, if any. pub fn machine_id(&self) -> Option { self.machine_id.clone() } }