Skip to content

Commit

Permalink
uefi: Implement path
Browse files Browse the repository at this point in the history
UEFI paths can be of 4 types:
1. Absolute Shell Path: Uses shell mappings
2. Absolute Device Path: this is what we want
3: Relative root: path relative to the current root.
4: Relative

Absolute shell path can be identified with `:` and Absolute Device path
can be identified with `/`. Relative root path will start with `\`.

The algorithm is mostly taken from edk2 UEFI shell implementation and is
somewhat simple. Check for the path type in order.

For Absolute Shell path, use `EFI_SHELL->GetDevicePathFromMap` to
get a BorrowedDevicePath for the volume.

For Relative paths, we use the current working directory to construct
the new path.

BorrowedDevicePath abstraction is needed to interact with
`EFI_SHELL->GetDevicePathFromMap` which returns a Device Path Protocol
with the lifetime of UEFI shell.

Absolute Shell paths cannot exist if UEFI shell is missing.

Signed-off-by: Ayush Singh <ayush@beagleboard.org>
  • Loading branch information
Ayush1325 committed Jan 15, 2025
1 parent 35c2908 commit 4549114
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 5 deletions.
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 throught 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
102 changes: 102 additions & 0 deletions library/std/src/sys/path/uefi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#![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. 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.
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 dev_path_text = dev_path.to_text().map_err(|_| unsupported_err())?;

let mut ans = PathBuf::new();
ans.push(&dev_path_text);
// The Device Path Protocol mapping text representation can sometimes miss the final
// seperator (Device path protocol seperator, aka '/').
if *dev_path_text.as_encoded_bytes().last().unwrap() != FORWARD_SLASH {
ans.push("/");
}
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)
}

0 comments on commit 4549114

Please sign in to comment.