2025-11-03 20:47:21 -05:00
|
|
|
use crate::bootloader_interface::bitflags::LoaderFeatures;
|
2025-10-30 02:36:14 -04:00
|
|
|
use crate::platform::timer::PlatformTimer;
|
2025-11-03 20:47:21 -05:00
|
|
|
use crate::variables::{VariableClass, VariableController};
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::format;
|
|
|
|
|
use alloc::string::{String, ToString};
|
|
|
|
|
use alloc::vec::Vec;
|
2025-10-28 23:23:12 -04:00
|
|
|
use anyhow::{Context, Result};
|
2026-09-26 18:12:35 -07:00
|
|
|
use log::warn;
|
2025-10-30 12:44:07 -04:00
|
|
|
use uefi::proto::device_path::DevicePath;
|
2025-10-30 18:57:26 -04:00
|
|
|
use uefi::{Guid, guid};
|
|
|
|
|
use uefi_raw::table::runtime::VariableVendor;
|
2025-10-28 21:05:22 -04:00
|
|
|
|
2025-11-01 03:24:14 -04:00
|
|
|
/// bitflags: LoaderFeatures bitflags.
|
|
|
|
|
mod bitflags;
|
|
|
|
|
|
2025-10-30 12:50:36 -04:00
|
|
|
/// The name of the bootloader to tell the system.
|
|
|
|
|
const LOADER_NAME: &str = "Sprout";
|
|
|
|
|
|
2025-11-01 17:47:41 -04:00
|
|
|
/// Represents the configured timeout for the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
#[derive(Default)]
|
2025-11-01 17:47:41 -04:00
|
|
|
pub enum BootloaderInterfaceTimeout {
|
2026-09-26 20:13:18 -07:00
|
|
|
/// Force the menu to be shown, and wait for the user.
|
2025-11-01 17:47:41 -04:00
|
|
|
MenuForce,
|
2026-09-26 20:13:18 -07:00
|
|
|
/// Force the menu to be shown, with a timeout.
|
|
|
|
|
MenuForceTimeout(u64),
|
2025-11-01 17:47:41 -04:00
|
|
|
/// Hide the menu.
|
|
|
|
|
MenuHidden,
|
|
|
|
|
/// Disable the menu.
|
|
|
|
|
MenuDisabled,
|
|
|
|
|
/// Set a timeout for the menu.
|
|
|
|
|
Timeout(u64),
|
|
|
|
|
/// Timeout is unspecified.
|
2026-09-26 20:14:57 -07:00
|
|
|
#[default]
|
2025-11-01 17:47:41 -04:00
|
|
|
Unspecified,
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
/// The timeouts set in the bootloader interface.
|
|
|
|
|
/// The one-shot timeout only applies to this boot, so it ranks above everything else.
|
|
|
|
|
#[derive(Default)]
|
|
|
|
|
pub struct BootloaderInterfaceTimeouts {
|
|
|
|
|
/// The timeout from LoaderConfigTimeoutOneShot.
|
|
|
|
|
pub oneshot: BootloaderInterfaceTimeout,
|
|
|
|
|
/// The timeout from LoaderConfigTimeout.
|
|
|
|
|
pub direct: BootloaderInterfaceTimeout,
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
/// Bootloader Interface support.
|
|
|
|
|
pub struct BootloaderInterface;
|
|
|
|
|
|
|
|
|
|
impl BootloaderInterface {
|
2025-10-28 23:23:12 -04:00
|
|
|
/// Bootloader Interface GUID from https://systemd.io/BOOT_LOADER_INTERFACE
|
2025-10-30 18:57:26 -04:00
|
|
|
const VENDOR: VariableController = VariableController::new(VariableVendor(guid!(
|
|
|
|
|
"4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
|
|
|
|
|
)));
|
2025-10-28 23:23:12 -04:00
|
|
|
|
2025-11-01 03:24:14 -04:00
|
|
|
/// The feature we support in Sprout.
|
|
|
|
|
fn features() -> LoaderFeatures {
|
2025-11-01 17:09:49 -04:00
|
|
|
LoaderFeatures::Xbootldr
|
|
|
|
|
| LoaderFeatures::LoadDriver
|
|
|
|
|
| LoaderFeatures::Tpm2ActivePcrBanks
|
|
|
|
|
| LoaderFeatures::RetainShim
|
2025-11-01 17:47:41 -04:00
|
|
|
| LoaderFeatures::ConfigTimeout
|
|
|
|
|
| LoaderFeatures::ConfigTimeoutOneShot
|
|
|
|
|
| LoaderFeatures::MenuDisable
|
2025-11-01 18:04:06 -04:00
|
|
|
| LoaderFeatures::EntryDefault
|
|
|
|
|
| LoaderFeatures::EntryOneShot
|
2026-10-03 17:31:16 -07:00
|
|
|
| LoaderFeatures::BootCounting
|
|
|
|
|
| LoaderFeatures::SavedEntry
|
2025-11-01 03:24:14 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
/// Tell the system that Sprout was initialized at the current time.
|
2025-10-30 02:51:52 -04:00
|
|
|
pub fn mark_init(timer: &PlatformTimer) -> Result<()> {
|
|
|
|
|
Self::mark_time("LoaderTimeInitUSec", timer)
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tell the system that Sprout is about to execute the boot entry.
|
2025-10-30 02:36:14 -04:00
|
|
|
pub fn mark_exec(timer: &PlatformTimer) -> Result<()> {
|
2025-10-30 02:51:52 -04:00
|
|
|
Self::mark_time("LoaderTimeExecUSec", timer)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 13:27:58 -04:00
|
|
|
/// Tell the system that Sprout is about to display the menu.
|
|
|
|
|
pub fn mark_menu(timer: &PlatformTimer) -> Result<()> {
|
2025-11-01 18:35:07 -04:00
|
|
|
Self::mark_time("LoaderTimeMenuUSec", timer)
|
2025-10-30 13:27:58 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 02:51:52 -04:00
|
|
|
/// Tell the system about the current time as measured by the platform timer.
|
|
|
|
|
/// Sets the variable specified by `key` to the number of microseconds.
|
|
|
|
|
fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> {
|
|
|
|
|
// Measure the elapsed time since the hardware timer was started.
|
|
|
|
|
let elapsed = timer.elapsed_since_lifetime();
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
key,
|
|
|
|
|
&elapsed.as_micros().to_string(),
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 03:24:14 -04:00
|
|
|
/// Tell the system what loader is being used and our features.
|
2025-10-30 12:50:36 -04:00
|
|
|
pub fn set_loader_info() -> Result<()> {
|
2025-11-01 03:24:14 -04:00
|
|
|
// Set the LoaderInfo variable with the name of the loader.
|
|
|
|
|
Self::VENDOR
|
|
|
|
|
.set_cstr16(
|
|
|
|
|
"LoaderInfo",
|
|
|
|
|
LOADER_NAME,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
|
|
|
|
.context("unable to set loader info variable")?;
|
|
|
|
|
|
|
|
|
|
// Set the LoaderFeatures variable with the features we support.
|
|
|
|
|
Self::VENDOR
|
|
|
|
|
.set_u64le(
|
|
|
|
|
"LoaderFeatures",
|
|
|
|
|
Self::features().bits(),
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
|
|
|
|
.context("unable to set loader features variable")?;
|
|
|
|
|
Ok(())
|
2025-10-30 12:50:36 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 12:44:07 -04:00
|
|
|
/// Tell the system the relative path to the partition root of the current bootloader.
|
|
|
|
|
pub fn set_loader_path(path: &DevicePath) -> Result<()> {
|
2025-11-03 20:47:21 -05:00
|
|
|
let subpath =
|
|
|
|
|
crate::path::device_path_subpath(path).context("unable to get loader path subpath")?;
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderImageIdentifier",
|
|
|
|
|
&subpath,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-30 12:44:07 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-28 21:05:22 -04:00
|
|
|
/// Tell the system what the partition GUID of the ESP Sprout was booted from is.
|
2025-10-28 23:23:12 -04:00
|
|
|
pub fn set_partition_guid(guid: &Guid) -> Result<()> {
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderDevicePartUUID",
|
|
|
|
|
&guid.to_string(),
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tell the system what boot entries are available.
|
2025-10-28 23:23:12 -04:00
|
|
|
pub fn set_entries<N: AsRef<str>>(entries: impl Iterator<Item = N>) -> Result<()> {
|
|
|
|
|
// Entries are stored as a null-terminated list of CString16 strings back to back.
|
|
|
|
|
// Iterate over the entries and convert them to CString16 placing them into data.
|
|
|
|
|
let mut data = Vec::new();
|
|
|
|
|
for entry in entries {
|
2025-10-30 12:44:07 -04:00
|
|
|
// Convert the entry to CString16 little endian.
|
|
|
|
|
let encoded = entry
|
|
|
|
|
.as_ref()
|
|
|
|
|
.encode_utf16()
|
|
|
|
|
.flat_map(|c| c.to_le_bytes())
|
|
|
|
|
.collect::<Vec<u8>>();
|
2025-10-30 23:15:18 -04:00
|
|
|
// Write the bytes into the data buffer.
|
2025-10-30 12:44:07 -04:00
|
|
|
data.extend_from_slice(&encoded);
|
2025-10-30 23:15:18 -04:00
|
|
|
// Add a null terminator to the end of the entry.
|
2025-10-30 23:25:48 -04:00
|
|
|
data.extend_from_slice(&[0, 0]);
|
2025-10-28 23:23:12 -04:00
|
|
|
}
|
2025-11-02 01:38:07 -05:00
|
|
|
|
|
|
|
|
// If no data was generated, we will do nothing.
|
|
|
|
|
if data.is_empty() {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set(
|
|
|
|
|
"LoaderEntries",
|
|
|
|
|
&data,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tell the system what the selected boot entry is.
|
2025-10-28 23:23:12 -04:00
|
|
|
pub fn set_selected_entry(entry: String) -> Result<()> {
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderEntrySelected",
|
|
|
|
|
&entry,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-28 23:23:12 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-30 11:47:35 -04:00
|
|
|
/// Tell the system about the UEFI firmware we are running on.
|
|
|
|
|
pub fn set_firmware_info() -> Result<()> {
|
2025-10-30 23:25:48 -04:00
|
|
|
// Access the firmware revision.
|
2025-10-30 23:58:07 -04:00
|
|
|
let firmware_revision = uefi::system::firmware_revision();
|
|
|
|
|
|
|
|
|
|
// Access the UEFI revision.
|
|
|
|
|
let uefi_revision = uefi::system::uefi_revision();
|
2025-10-30 23:25:48 -04:00
|
|
|
|
2025-10-30 11:47:35 -04:00
|
|
|
// Format the firmware information string into something human-readable.
|
|
|
|
|
let firmware_info = format!(
|
|
|
|
|
"{} {}.{:02}",
|
|
|
|
|
uefi::system::firmware_vendor(),
|
2025-10-30 23:58:07 -04:00
|
|
|
firmware_revision >> 16,
|
|
|
|
|
firmware_revision & 0xffff,
|
2025-10-30 11:47:35 -04:00
|
|
|
);
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderFirmwareInfo",
|
|
|
|
|
&firmware_info,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)?;
|
2025-10-30 11:47:35 -04:00
|
|
|
|
|
|
|
|
// Format the firmware revision into something human-readable.
|
2025-10-30 23:58:07 -04:00
|
|
|
let firmware_type = format!(
|
|
|
|
|
"UEFI {}.{:02}",
|
|
|
|
|
uefi_revision.major(),
|
|
|
|
|
uefi_revision.minor()
|
|
|
|
|
);
|
2025-10-30 18:57:26 -04:00
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderFirmwareType",
|
|
|
|
|
&firmware_type,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
2025-10-28 23:23:12 -04:00
|
|
|
}
|
2025-10-31 01:30:07 -04:00
|
|
|
|
|
|
|
|
/// Tell the system what the number of active PCR banks is.
|
|
|
|
|
/// If this is zero, that is okay.
|
|
|
|
|
pub fn set_tpm2_active_pcr_banks(value: u32) -> Result<()> {
|
|
|
|
|
// Format the value into the specification format.
|
|
|
|
|
let value = format!("0x{:08x}", value);
|
|
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderTpm2ActivePcrBanks",
|
|
|
|
|
&value,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-11-01 17:47:41 -04:00
|
|
|
|
|
|
|
|
/// Retrieve the timeout value from the bootloader interface, using the specified `key`.
|
|
|
|
|
/// `remove` indicates whether, when found, we remove the variable.
|
|
|
|
|
fn get_timeout_value(key: &str, remove: bool) -> Result<Option<BootloaderInterfaceTimeout>> {
|
|
|
|
|
// Retrieve the timeout value from the bootloader interface.
|
|
|
|
|
let Some(value) = Self::VENDOR
|
|
|
|
|
.get_cstr16(key)
|
|
|
|
|
.context("unable to get timeout value")?
|
|
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If we reach here, we know the value was specified.
|
|
|
|
|
// If `remove` is true, remove the variable.
|
2026-09-26 20:14:57 -07:00
|
|
|
// If removal fails, the value is still used, as it was meant for this boot.
|
|
|
|
|
if remove && let Err(error) = Self::VENDOR.remove(key) {
|
|
|
|
|
warn!("unable to remove {} variable: {:#}", key, error);
|
2025-11-01 17:47:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the value is empty, return Unspecified.
|
|
|
|
|
if value.is_empty() {
|
|
|
|
|
return Ok(Some(BootloaderInterfaceTimeout::Unspecified));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the value is "menu-force", return MenuForce.
|
|
|
|
|
if value == "menu-force" {
|
|
|
|
|
return Ok(Some(BootloaderInterfaceTimeout::MenuForce));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the value is "menu-hidden", return MenuHidden.
|
|
|
|
|
if value == "menu-hidden" {
|
|
|
|
|
return Ok(Some(BootloaderInterfaceTimeout::MenuHidden));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the value is "menu-disabled", return MenuDisabled.
|
|
|
|
|
if value == "menu-disabled" {
|
|
|
|
|
return Ok(Some(BootloaderInterfaceTimeout::MenuDisabled));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the value as a u64 to decode an numeric value.
|
2026-09-26 18:12:35 -07:00
|
|
|
// An invalid value is ignored, as a bad variable should not prevent booting.
|
|
|
|
|
let value = match value.parse::<u64>() {
|
|
|
|
|
Ok(value) => value,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
warn!("ignoring invalid {} value '{}': {}", key, value, error);
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-11-01 17:47:41 -04:00
|
|
|
|
|
|
|
|
// If we reach here, we know it must be a real timeout value.
|
2026-09-26 20:13:18 -07:00
|
|
|
// A value of 0 is left to the caller, as its meaning depends on the variable.
|
2025-11-01 17:47:41 -04:00
|
|
|
Ok(Some(BootloaderInterfaceTimeout::Timeout(value)))
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
/// Get the timeouts from the bootloader interface.
|
2025-11-01 17:47:41 -04:00
|
|
|
/// This indicates how the menu should behave.
|
2026-10-03 17:31:16 -07:00
|
|
|
/// A timeout that is not set is Unspecified.
|
|
|
|
|
pub fn get_timeouts() -> Result<BootloaderInterfaceTimeouts> {
|
2025-11-01 17:47:41 -04:00
|
|
|
// Attempt to acquire the value of the LoaderConfigTimeoutOneShot variable.
|
2026-09-26 20:13:18 -07:00
|
|
|
// The specification says the one-shot timeout shows the menu on this boot,
|
|
|
|
|
// and a value of 0 means the menu waits for the user.
|
2026-10-03 17:31:16 -07:00
|
|
|
let oneshot = Self::get_timeout_value("LoaderConfigTimeoutOneShot", true)
|
|
|
|
|
.context("unable to check for LoaderConfigTimeoutOneShot variable")?
|
|
|
|
|
.map(|oneshot| match oneshot {
|
2026-09-26 20:13:18 -07:00
|
|
|
BootloaderInterfaceTimeout::Timeout(0) => BootloaderInterfaceTimeout::MenuForce,
|
|
|
|
|
BootloaderInterfaceTimeout::Timeout(value) => {
|
|
|
|
|
BootloaderInterfaceTimeout::MenuForceTimeout(value)
|
|
|
|
|
}
|
|
|
|
|
other => other,
|
2026-10-03 17:31:16 -07:00
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
2025-11-01 17:47:41 -04:00
|
|
|
|
|
|
|
|
// Attempt to acquire the value of the LoaderConfigTimeout variable.
|
2026-09-26 20:13:18 -07:00
|
|
|
// The specification says that a value of 0 means that the menu should be hidden.
|
2026-10-03 17:31:16 -07:00
|
|
|
let direct = Self::get_timeout_value("LoaderConfigTimeout", false)
|
|
|
|
|
.context("unable to check for LoaderConfigTimeout variable")?
|
|
|
|
|
.map(|direct| match direct {
|
2026-09-26 20:13:18 -07:00
|
|
|
BootloaderInterfaceTimeout::Timeout(0) => BootloaderInterfaceTimeout::MenuHidden,
|
|
|
|
|
other => other,
|
2026-10-03 17:31:16 -07:00
|
|
|
})
|
|
|
|
|
.unwrap_or_default();
|
2025-11-01 17:47:41 -04:00
|
|
|
|
2026-10-03 17:31:16 -07:00
|
|
|
Ok(BootloaderInterfaceTimeouts { oneshot, direct })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tell the system the path of the entry file whose boot counter was updated, relative
|
|
|
|
|
/// to the partition root, so that it can be marked as good once the boot succeeds.
|
|
|
|
|
pub fn set_boot_count_path(path: &str) -> Result<()> {
|
|
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderBootCountPath",
|
|
|
|
|
path,
|
|
|
|
|
VariableClass::BootAndRuntimeTemporary,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the entry that was saved by the previous boot, if any.
|
|
|
|
|
pub fn get_last_booted_entry() -> Result<Option<String>> {
|
|
|
|
|
Ok(Self::VENDOR
|
|
|
|
|
.get_cstr16("LoaderEntryLastBooted")
|
|
|
|
|
.context("unable to get last booted entry from bootloader interface")?
|
|
|
|
|
.filter(|value| !value.is_empty()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Save the entry that is booting, so a later boot can select it again.
|
|
|
|
|
pub fn set_last_booted_entry(entry: &str) -> Result<()> {
|
|
|
|
|
Self::VENDOR.set_cstr16(
|
|
|
|
|
"LoaderEntryLastBooted",
|
|
|
|
|
entry,
|
|
|
|
|
VariableClass::BootAndRuntimePersistent,
|
|
|
|
|
)
|
2025-11-01 17:47:41 -04:00
|
|
|
}
|
2025-11-01 18:04:06 -04:00
|
|
|
|
|
|
|
|
/// Get the default entry set by the bootloader interface.
|
|
|
|
|
pub fn get_default_entry() -> Result<Option<String>> {
|
2026-09-26 18:12:52 -07:00
|
|
|
// An empty value is treated as if the default entry was not set.
|
|
|
|
|
Ok(Self::VENDOR
|
2025-11-01 18:04:06 -04:00
|
|
|
.get_cstr16("LoaderEntryDefault")
|
2026-09-26 18:12:52 -07:00
|
|
|
.context("unable to get default entry from bootloader interface")?
|
|
|
|
|
.filter(|value| !value.is_empty()))
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the oneshot entry set by the bootloader interface.
|
|
|
|
|
/// This should be the entry we boot.
|
|
|
|
|
pub fn get_oneshot_entry() -> Result<Option<String>> {
|
|
|
|
|
// Acquire the value of the LoaderEntryOneShot variable.
|
|
|
|
|
// If it is not set, return None.
|
|
|
|
|
let Some(value) = Self::VENDOR
|
|
|
|
|
.get_cstr16("LoaderEntryOneShot")
|
|
|
|
|
.context("unable to get oneshot entry from bootloader interface")?
|
|
|
|
|
else {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Remove the oneshot entry from the bootloader interface.
|
2026-09-26 20:14:57 -07:00
|
|
|
// If removal fails, the value is still used, as it was meant for this boot.
|
|
|
|
|
if let Err(error) = Self::VENDOR.remove("LoaderEntryOneShot") {
|
|
|
|
|
warn!("unable to remove LoaderEntryOneShot variable: {:#}", error);
|
|
|
|
|
}
|
2025-11-01 18:04:06 -04:00
|
|
|
|
|
|
|
|
// Return the oneshot value.
|
2026-09-26 18:12:52 -07:00
|
|
|
// An empty value is treated as if the oneshot entry was not set.
|
|
|
|
|
Ok(Some(value).filter(|value| !value.is_empty()))
|
2025-11-01 18:04:06 -04:00
|
|
|
}
|
2025-10-28 21:05:22 -04:00
|
|
|
}
|