Skip to content

uefi: Implement path #135475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion library/std/src/sys/pal/uefi/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ use r_efi::protocols::{device_path, device_path_to_text, shell};

use crate::ffi::{OsStr, OsString};
use crate::io::{self, const_error};
use crate::marker::PhantomData;
use crate::mem::{MaybeUninit, size_of};
use crate::os::uefi::env::boot_services;
use crate::os::uefi::ffi::{OsStrExt, OsStringExt};
use crate::os::uefi::{self};
use crate::path::Path;
use crate::ptr::NonNull;
use crate::slice;
use crate::sync::atomic::{AtomicPtr, Ordering};
Expand Down Expand Up @@ -278,6 +280,10 @@ impl OwnedDevicePath {
pub(crate) const fn as_ptr(&self) -> *mut r_efi::protocols::device_path::Protocol {
self.0.as_ptr()
}

pub(crate) const fn borrow<'a>(&'a self) -> BorrowedDevicePath<'a> {
BorrowedDevicePath::new(self.0)
}
}

impl Drop for OwnedDevicePath {
Expand All @@ -293,13 +299,37 @@ impl Drop for OwnedDevicePath {

impl crate::fmt::Debug for OwnedDevicePath {
fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result {
match device_path_to_text(self.0) {
match self.borrow().to_text() {
Ok(p) => p.fmt(f),
Err(_) => f.debug_struct("OwnedDevicePath").finish_non_exhaustive(),
}
}
}

pub(crate) struct BorrowedDevicePath<'a> {
protocol: NonNull<r_efi::protocols::device_path::Protocol>,
phantom: PhantomData<&'a r_efi::protocols::device_path::Protocol>,
}

impl<'a> BorrowedDevicePath<'a> {
pub(crate) const fn new(protocol: NonNull<r_efi::protocols::device_path::Protocol>) -> Self {
Self { protocol, phantom: PhantomData }
}

pub(crate) fn to_text(&self) -> io::Result<OsString> {
device_path_to_text(self.protocol)
}
}

impl<'a> crate::fmt::Debug for BorrowedDevicePath<'a> {
fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result {
match self.to_text() {
Ok(p) => p.fmt(f),
Err(_) => f.debug_struct("BorrowedDevicePath").finish_non_exhaustive(),
}
}
}

pub(crate) struct OwnedProtocol<T> {
guid: r_efi::efi::Guid,
handle: NonNull<crate::ffi::c_void>,
Expand Down Expand Up @@ -452,3 +482,21 @@ pub(crate) fn open_shell() -> Option<NonNull<shell::Protocol>> {

None
}

/// Get device path protocol associated with shell mapping.
///
/// returns None in case no such mapping is exists
pub(crate) fn get_device_path_from_map(map: &Path) -> io::Result<BorrowedDevicePath<'static>> {
let shell =
open_shell().ok_or(io::const_error!(io::ErrorKind::NotFound, "UEFI Shell not found"))?;
let mut path = os_string_to_raw(map.as_os_str())
.ok_or(io::const_error!(io::ErrorKind::InvalidFilename, "Invalid UEFI shell mapping"))?;

// The Device Path Protocol pointer returned by UEFI shell is owned by the shell and is not
// freed throughout it's lifetime. So it has a 'static lifetime.
let protocol = unsafe { ((*shell.as_ptr()).get_device_path_from_map)(path.as_mut_ptr()) };
let protocol = NonNull::new(protocol)
.ok_or(io::const_error!(io::ErrorKind::NotFound, "UEFI Shell mapping not found"))?;

Ok(BorrowedDevicePath::new(protocol))
}
8 changes: 4 additions & 4 deletions library/std/src/sys/path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ cfg_if::cfg_if! {
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
mod sgx;
pub use sgx::*;
} else if #[cfg(any(
target_os = "uefi",
target_os = "solid_asp3",
))] {
} else if #[cfg(target_os = "solid_asp3")] {
mod unsupported_backslash;
pub use unsupported_backslash::*;
} else if #[cfg(target_os = "uefi")] {
mod uefi;
pub use uefi::*;
} else {
mod unix;
pub use unix::*;
Expand Down
105 changes: 105 additions & 0 deletions library/std/src/sys/path/uefi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#![forbid(unsafe_op_in_unsafe_fn)]
use crate::ffi::OsStr;
use crate::io;
use crate::path::{Path, PathBuf, Prefix};
use crate::sys::{helpers, unsupported_err};

const FORWARD_SLASH: u8 = b'/';
const COLON: u8 = b':';

#[inline]
pub fn is_sep_byte(b: u8) -> bool {
b == b'\\'
}

#[inline]
pub fn is_verbatim_sep(b: u8) -> bool {
b == b'\\'
}

pub fn parse_prefix(_: &OsStr) -> Option<Prefix<'_>> {
None
}

pub const MAIN_SEP_STR: &str = "\\";
pub const MAIN_SEP: char = '\\';

/// UEFI paths can be of 4 types:
///
/// 1. Absolute Shell Path: Uses shell mappings (eg: `FS0:`). Does not exist if UEFI shell not present.
/// It can be identified with `:`.
/// Eg: FS0:\abc\run.efi
///
/// 2. Absolute Device Path: this is what we want
/// It can be identified with `/`.
/// Eg: PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi
///
/// 3: Relative root: path relative to the current volume.
/// It will start with `\`.
/// Eg: \abc\run.efi
///
/// 4: Relative
/// Eg: run.efi
///
/// The algorithm is mostly taken from edk2 UEFI shell implementation and is
/// somewhat simple. Check for the path type in order.
///
/// The volume mapping in Absolute Shell Path (not the rest of the path) can be converted to Device
/// Path Protocol using `EFI_SHELL->GetDevicePathFromMap`. The rest of the path (Relative root
/// path), can just be appended to the remaining path.
///
/// For Relative root, we get the current volume (either in Shell Mapping, or Device Path Protocol
/// form) and join it with the relative root path. We then recurse the function to resolve the Shell
/// Mapping if present.
///
/// For Relative paths, we use the current working directory to construct
/// the new path and recurse the function to resolve the Shell mapping if present.
///
/// Finally, at the end, we get the 2nd form, i.e. Absolute Device Path, which can be used in the
/// normal UEFI APIs such as file, process, etc.
/// Eg: PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi
pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
// Absolute Shell Path
if path.as_os_str().as_encoded_bytes().contains(&COLON) {
let mut path_components = path.components();
// Since path is not empty, it has at least one Component
let prefix = path_components.next().unwrap();

let dev_path = helpers::get_device_path_from_map(prefix.as_ref())?;
let mut dev_path_text = dev_path.to_text().map_err(|_| unsupported_err())?;

// UEFI Shell does not seem to end device path with `/`
if *dev_path_text.as_encoded_bytes().last().unwrap() != FORWARD_SLASH {
dev_path_text.push("/");
}

let mut ans = PathBuf::from(dev_path_text);
ans.push(path_components);

return Ok(ans);
}

// Absolute Device Path
if path.as_os_str().as_encoded_bytes().contains(&FORWARD_SLASH) {
return Ok(path.to_path_buf());
}

// cur_dir() always returns something
let cur_dir = crate::env::current_dir().unwrap();
let mut path_components = path.components();

// Relative Root
if path_components.next().unwrap() == crate::path::Component::RootDir {
let mut ans = PathBuf::new();
ans.push(cur_dir.components().next().unwrap());
ans.push(path_components);
return absolute(&ans);
}

absolute(&cur_dir.join(path))
}

pub(crate) fn is_absolute(path: &Path) -> bool {
let temp = path.as_os_str().as_encoded_bytes();
temp.contains(&COLON) || temp.contains(&FORWARD_SLASH)
}
Loading