Skip to content

Commit cfe7088

Browse files
authored
Merge pull request #23 from edera-dev/azenla/bootloader-interface
feat(integrations): basic bootloader interface support
2 parents fe714cc + 9d3a022 commit cfe7088

14 files changed

Lines changed: 532 additions & 74 deletions

File tree

src/actions/chainload.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::context::SproutContext;
2+
use crate::integrations::bootloader_interface::BootloaderInterface;
23
use crate::utils;
34
use crate::utils::media_loader::MediaLoaderHandle;
45
use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID;
@@ -102,6 +103,10 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
102103
initrd_handle = Some(handle);
103104
}
104105

106+
// Mark execution of an entry in the bootloader interface.
107+
BootloaderInterface::mark_exec(context.root().timer())
108+
.context("unable to mark execution of boot entry in bootloader interface")?;
109+
105110
// Start the loaded image.
106111
// This call might return, or it may pass full control to another image that will never return.
107112
// Capture the result to ensure we can return an error if the image fails to start, but only

src/context.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::actions::ActionDeclaration;
22
use crate::options::SproutOptions;
3+
use crate::platform::timer::PlatformTimer;
34
use anyhow::anyhow;
45
use anyhow::{Result, bail};
56
use std::cmp::Reverse;
@@ -12,22 +13,29 @@ const CONTEXT_FINALIZE_ITERATION_LIMIT: usize = 100;
1213

1314
/// Declares a root context for Sprout.
1415
/// This contains data that needs to be shared across Sprout.
15-
#[derive(Default)]
1616
pub struct RootContext {
1717
/// The actions that are available in Sprout.
1818
actions: BTreeMap<String, ActionDeclaration>,
1919
/// The device path of the loaded Sprout image.
2020
loaded_image_path: Option<Box<DevicePath>>,
21+
/// Platform timer started at the beginning of the boot process.
22+
timer: PlatformTimer,
2123
/// The global options of Sprout.
2224
options: SproutOptions,
2325
}
2426

2527
impl RootContext {
2628
/// Creates a new root context with the `loaded_image_device_path` which will be stored
27-
/// in the context for easy access.
28-
pub fn new(loaded_image_device_path: Box<DevicePath>, options: SproutOptions) -> Self {
29+
/// in the context for easy access. We also provide a `timer` which is used to measure elapsed
30+
/// time for the bootloader.
31+
pub fn new(
32+
loaded_image_device_path: Box<DevicePath>,
33+
timer: PlatformTimer,
34+
options: SproutOptions,
35+
) -> Self {
2936
Self {
3037
actions: BTreeMap::new(),
38+
timer,
3139
loaded_image_path: Some(loaded_image_device_path),
3240
options,
3341
}
@@ -43,6 +51,11 @@ impl RootContext {
4351
&mut self.actions
4452
}
4553

54+
/// Access the platform timer that is started at the beginning of the boot process.
55+
pub fn timer(&self) -> &PlatformTimer {
56+
&self.timer
57+
}
58+
4659
/// Access the device path of the loaded Sprout image.
4760
pub fn loaded_image_path(&self) -> Result<&DevicePath> {
4861
self.loaded_image_path

src/entries.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct BootableEntry {
2828
context: Rc<SproutContext>,
2929
declaration: EntryDeclaration,
3030
default: bool,
31+
pin_name: bool,
3132
}
3233

3334
impl BootableEntry {
@@ -44,6 +45,7 @@ impl BootableEntry {
4445
context,
4546
declaration,
4647
default: false,
48+
pin_name: false,
4749
}
4850
}
4951

@@ -72,6 +74,11 @@ impl BootableEntry {
7274
self.default
7375
}
7476

77+
/// Fetch whether the entry is pinned, which prevents prefixing.
78+
pub fn is_pin_name(&self) -> bool {
79+
self.pin_name
80+
}
81+
7582
/// Swap out the context of the entry.
7683
pub fn swap_context(&mut self, context: Rc<SproutContext>) {
7784
self.context = context;
@@ -87,6 +94,11 @@ impl BootableEntry {
8794
self.default = true;
8895
}
8996

97+
/// Mark this entry as being pinned, which prevents prefixing.
98+
pub fn mark_pin_name(&mut self) {
99+
self.pin_name = true;
100+
}
101+
90102
/// Prepend the name of the entry with `prefix`.
91103
pub fn prepend_name_prefix(&mut self, prefix: &str) {
92104
self.name.insert_str(0, prefix);

src/extractors/filesystem_device_match.rs

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ use uefi::fs::{FileSystem, Path};
99
use uefi::proto::device_path::DevicePath;
1010
use uefi::proto::media::file::{File, FileSystemVolumeLabel};
1111
use uefi::proto::media::fs::SimpleFileSystem;
12-
use uefi::proto::media::partition::PartitionInfo;
13-
use uefi::{CString16, Guid, Handle};
14-
use uefi_raw::Status;
12+
use uefi::{CString16, Guid};
1513

1614
/// The filesystem device match extractor.
1715
/// This extractor finds a filesystem using some search criteria and returns
@@ -41,48 +39,6 @@ pub struct FilesystemDeviceMatchExtractor {
4139
pub fallback: Option<String>,
4240
}
4341

44-
/// Represents the partition UUIDs for a filesystem.
45-
struct PartitionIds {
46-
/// The UUID of the partition.
47-
partition_uuid: Guid,
48-
/// The type UUID of the partition.
49-
type_uuid: Guid,
50-
}
51-
52-
/// Fetches the partition UUIDs for the specified filesystem handle.
53-
fn fetch_partition_uuids(handle: Handle) -> Result<Option<PartitionIds>> {
54-
// Open the partition info protocol for this handle.
55-
let partition_info = uefi::boot::open_protocol_exclusive::<PartitionInfo>(handle);
56-
57-
match partition_info {
58-
Ok(partition_info) => {
59-
// GPT partitions have a unique partition GUID.
60-
// MBR does not.
61-
if let Some(gpt) = partition_info.gpt_partition_entry() {
62-
let uuid = gpt.unique_partition_guid;
63-
let type_uuid = gpt.partition_type_guid;
64-
Ok(Some(PartitionIds {
65-
partition_uuid: uuid,
66-
type_uuid: type_uuid.0,
67-
}))
68-
} else {
69-
Ok(None)
70-
}
71-
}
72-
73-
Err(error) => {
74-
// If the filesystem does not have a partition, that is okay.
75-
if error.status() == Status::NOT_FOUND || error.status() == Status::UNSUPPORTED {
76-
Ok(None)
77-
} else {
78-
// We should still handle other errors gracefully.
79-
Err(error).context("unable to open filesystem partition info")?;
80-
unreachable!()
81-
}
82-
}
83-
}
84-
}
85-
8642
/// Extract a filesystem device path using the specified `context` and `extractor` configuration.
8743
pub fn extract(
8844
context: Rc<SproutContext>,
@@ -106,30 +62,49 @@ pub fn extract(
10662
// This defines whether a match has been found.
10763
let mut has_match = false;
10864

109-
// Extract the partition info for this filesystem.
110-
// There is no guarantee that the filesystem has a partition.
111-
let partition_info =
112-
fetch_partition_uuids(handle).context("unable to fetch partition info")?;
113-
11465
// Check if the partition info matches partition uuid criteria.
115-
if let Some(ref partition_info) = partition_info
116-
&& let Some(ref has_partition_uuid) = extractor.has_partition_uuid
117-
{
66+
if let Some(ref has_partition_uuid) = extractor.has_partition_uuid {
67+
// Parse the partition uuid from the extractor.
11868
let parsed_uuid = Guid::from_str(has_partition_uuid)
11969
.map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?;
120-
if partition_info.partition_uuid != parsed_uuid {
70+
71+
// Fetch the root of the device.
72+
let root = uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
73+
.context("unable to fetch the device path of the filesystem")?
74+
.deref()
75+
.to_boxed();
76+
77+
// Fetch the partition uuid for this filesystem.
78+
let partition_uuid = utils::partition_guid(&root, utils::PartitionGuidForm::Partition)
79+
.context("unable to fetch the partition uuid of the filesystem")?;
80+
81+
// Compare the partition uuid to the parsed uuid.
82+
// If it does not match, continue to the next filesystem.
83+
if partition_uuid != Some(parsed_uuid) {
12184
continue;
12285
}
12386
has_match = true;
12487
}
12588

12689
// Check if the partition info matches partition type uuid criteria.
127-
if let Some(ref partition_info) = partition_info
128-
&& let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid
129-
{
90+
if let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid {
91+
// Parse the partition type uuid from the extractor.
13092
let parsed_uuid = Guid::from_str(has_partition_type_uuid)
13193
.map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?;
132-
if partition_info.type_uuid != parsed_uuid {
94+
95+
// Fetch the root of the device.
96+
let root = uefi::boot::open_protocol_exclusive::<DevicePath>(handle)
97+
.context("unable to fetch the device path of the filesystem")?
98+
.deref()
99+
.to_boxed();
100+
101+
// Fetch the partition uuid for this filesystem.
102+
let partition_type_uuid =
103+
utils::partition_guid(&root, utils::PartitionGuidForm::Partition)
104+
.context("unable to fetch the partition uuid of the filesystem")?;
105+
// Compare the partition type uuid to the parsed uuid.
106+
// If it does not match, continue to the next filesystem.
107+
if partition_type_uuid != Some(parsed_uuid) {
133108
continue;
134109
}
135110
has_match = true;

src/generators/bls.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,16 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
8383
}
8484

8585
// Get the file name of the filesystem item.
86-
let name = entry.file_name().to_string();
86+
let mut name = entry.file_name().to_string();
8787

8888
// Ignore files that are not .conf files.
8989
if !name.to_lowercase().ends_with(".conf") {
9090
continue;
9191
}
9292

93+
// Remove the .conf extension.
94+
name.truncate(name.len() - 5);
95+
9396
// Create a mutable path so we can append the file name to produce the full path.
9497
let mut full_entry_path = entries_path.to_path_buf();
9598
full_entry_path.push(entry.file_name());
@@ -125,13 +128,21 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
125128
context.set("options", options);
126129
context.set("initrd", initrd);
127130

128-
// Add the entry to the list with a frozen context.
129-
entries.push(BootableEntry::new(
131+
// Produce a new bootable entry.
132+
let mut entry = BootableEntry::new(
130133
name,
131134
bls.entry.title.clone(),
132135
context.freeze(),
133136
bls.entry.clone(),
134-
));
137+
);
138+
139+
// Pin the entry name to prevent prefixing.
140+
// This is needed as the bootloader interface requires the name to be
141+
// the same as the entry file name, minus the .conf extension.
142+
entry.mark_pin_name();
143+
144+
// Add the entry to the list with a frozen context.
145+
entries.push(entry);
135146
}
136147

137148
Ok(entries)

src/integrations.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// Implements support for the bootloader interface specification.
2+
pub mod bootloader_interface;

0 commit comments

Comments
 (0)