about summary refs log tree commit diff
path: root/library/std/src/sys
diff options
context:
space:
mode:
authorAyush Singh <ayush@beagleboard.org>2025-01-14 11:40:22 +0530
committerAyush Singh <ayush@beagleboard.org>2025-01-16 10:19:22 +0530
commitc1790b14bcd62df7bccdfa7d7fbe4533dfdcdc8c (patch)
tree0b8411c4a82206d30ccbe8f034f88dd34bbc7313 /library/std/src/sys
parent35c2908177a17ca4e0acbc9013e42ee525ba155c (diff)
downloadrust-c1790b14bcd62df7bccdfa7d7fbe4533dfdcdc8c.tar.gz
rust-c1790b14bcd62df7bccdfa7d7fbe4533dfdcdc8c.zip
uefi: Implement path
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>
Diffstat (limited to 'library/std/src/sys')
-rw-r--r--library/std/src/sys/pal/uefi/helpers.rs50
-rw-r--r--library/std/src/sys/path/mod.rs8
-rw-r--r--library/std/src/sys/path/uefi.rs105
3 files changed, 158 insertions, 5 deletions
diff --git a/library/std/src/sys/pal/uefi/helpers.rs b/library/std/src/sys/pal/uefi/helpers.rs
index 7504a0f7ad7..dccc137d6f5 100644
--- a/library/std/src/sys/pal/uefi/helpers.rs
+++ b/library/std/src/sys/pal/uefi/helpers.rs
@@ -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};
@@ -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 {
@@ -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>,
@@ -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))
+}
diff --git a/library/std/src/sys/path/mod.rs b/library/std/src/sys/path/mod.rs
index 24a94ec7828..1fa4e80d678 100644
--- a/library/std/src/sys/path/mod.rs
+++ b/library/std/src/sys/path/mod.rs
@@ -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::*;
diff --git a/library/std/src/sys/path/uefi.rs b/library/std/src/sys/path/uefi.rs
new file mode 100644
index 00000000000..a3f4a3bfe1b
--- /dev/null
+++ b/library/std/src/sys/path/uefi.rs
@@ -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)
+}