Skip to content

Commit

Permalink
uefi: Implement getcwd and chdir
Browse files Browse the repository at this point in the history
- Using EFI Shell Protocol. These functions do not make much sense
  unless a shell is present.
- Return the exe dir in case shell protocol is missing.

Signed-off-by: Ayush Singh <[email protected]>
  • Loading branch information
Ayush1325 committed Sep 23, 2024
1 parent 66b0b29 commit 1f718f9
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 14 deletions.
24 changes: 14 additions & 10 deletions library/std/src/sys/pal/uefi/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,16 +177,8 @@ pub(crate) fn device_path_to_text(path: NonNull<device_path::Protocol>) -> io::R
)
};

// SAFETY: `convert_device_path_to_text` returns a pointer to a null-terminated UTF-16
// string, and that string cannot be deallocated prior to dropping the `WStrUnits`, so
// it's safe for `WStrUnits` to use.
let path_len = unsafe {
WStrUnits::new(path_ptr)
.ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path"))?
.count()
};

let path = OsString::from_wide(unsafe { slice::from_raw_parts(path_ptr.cast(), path_len) });
let path = os_string_from_raw(path_ptr)
.ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path"))?;

if let Some(boot_services) = crate::os::uefi::env::boot_services() {
let boot_services: NonNull<r_efi::efi::BootServices> = boot_services.cast();
Expand Down Expand Up @@ -420,3 +412,15 @@ impl<T> Drop for OwnedTable<T> {
unsafe { crate::alloc::dealloc(self.ptr as *mut u8, self.layout) };
}
}

/// Create OsString from a pointer to NULL terminated UTF-16 string
pub(crate) fn os_string_from_raw(ptr: *mut r_efi::efi::Char16) -> Option<OsString> {
let path_len = unsafe { WStrUnits::new(ptr)?.count() };
Some(OsString::from_wide(unsafe { slice::from_raw_parts(ptr.cast(), path_len) }))
}

/// Create NULL terminated UTF-16 string
pub(crate) fn os_string_to_raw(s: &OsStr) -> Option<Box<[r_efi::efi::Char16]>> {
let temp = s.encode_wide().chain(Some(0)).collect::<Box<[r_efi::efi::Char16]>>();
if temp[..temp.len() - 1].contains(&0) { None } else { Some(temp) }
}
63 changes: 59 additions & 4 deletions library/std/src/sys/pal/uefi/os.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use r_efi::efi::Status;
use r_efi::efi::protocols::{device_path, loaded_image_device_path};

use super::{RawOsError, helpers, unsupported};
use super::{RawOsError, helpers, unsupported, unsupported_err};
use crate::error::Error as StdError;
use crate::ffi::{OsStr, OsString};
use crate::marker::PhantomData;
Expand Down Expand Up @@ -125,11 +125,32 @@ pub fn error_string(errno: RawOsError) -> String {
}

pub fn getcwd() -> io::Result<PathBuf> {
unsupported()
match uefi_shell::open_shell() {
Some(shell) => {
// SAFETY: path_ptr is managed by UEFI shell and should not be deallocated
let path_ptr = unsafe { ((*shell.as_ptr()).get_cur_dir)(crate::ptr::null_mut()) };
helpers::os_string_from_raw(path_ptr)
.map(PathBuf::from)
.ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path"))
}
None => {
let mut t = current_exe()?;
// SAFETY: This should never fail since the disk prefix will be present even for root
// executables
assert!(t.pop());
Ok(t)
}
}
}

pub fn chdir(_: &path::Path) -> io::Result<()> {
unsupported()
pub fn chdir(p: &path::Path) -> io::Result<()> {
let shell = uefi_shell::open_shell().ok_or(unsupported_err())?;

let mut p = helpers::os_string_to_raw(p.as_os_str())
.ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path"))?;

let r = unsafe { ((*shell.as_ptr()).set_cur_dir)(crate::ptr::null_mut(), p.as_mut_ptr()) };
if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}

pub struct SplitPaths<'a>(!, PhantomData<&'a ()>);
Expand Down Expand Up @@ -239,3 +260,37 @@ pub fn exit(code: i32) -> ! {
pub fn getpid() -> u32 {
panic!("no pids on this platform")
}

mod uefi_shell {
use r_efi::protocols::shell;

use super::super::helpers;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicPtr, Ordering};

pub fn open_shell() -> Option<NonNull<shell::Protocol>> {
static LAST_VALID_HANDLE: AtomicPtr<crate::ffi::c_void> =
AtomicPtr::new(crate::ptr::null_mut());

if let Some(handle) = NonNull::new(LAST_VALID_HANDLE.load(Ordering::Acquire)) {
if let Ok(protocol) = helpers::open_protocol::<shell::Protocol>(
handle,
r_efi::protocols::shell::PROTOCOL_GUID,
) {
return Some(protocol);
}
}

let handles = helpers::locate_handles(shell::PROTOCOL_GUID).ok()?;
for handle in handles {
if let Ok(protocol) =
helpers::open_protocol::<shell::Protocol>(handle, shell::PROTOCOL_GUID)
{
LAST_VALID_HANDLE.store(handle.as_ptr(), Ordering::Release);
return Some(protocol);
}
}

None
}
}

0 comments on commit 1f718f9

Please sign in to comment.