diff --git a/Cargo.lock b/Cargo.lock index 7d43dbc9e0640..17475dcf5969d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2943,6 +2943,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7345c622833c6745e7b027a28aa95618813dc1f3c3de396206410267dce6f3" +dependencies = [ + "compiler_builtins", + "rustc-std-workspace-core", +] + +[[package]] +name = "r-efi-alloc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d6f09fe2b6ad044bc3d2c34ce4979796581afd2f1ebc185837e02421e02fd7" +dependencies = [ + "compiler_builtins", + "r-efi", + "rustc-std-workspace-core", +] + [[package]] name = "rand" version = "0.7.3" @@ -4898,6 +4919,8 @@ dependencies = [ "panic_abort", "panic_unwind", "profiler_builtins", + "r-efi", + "r-efi-alloc", "rand 0.7.3", "rustc-demangle", "std_detect", diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 4f396e970ad70..543d0463372b6 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -432,7 +432,10 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( ) -> Bx::Function { // The entry function is either `int main(void)` or `int main(int argc, char **argv)`, // depending on whether the target needs `argc` and `argv` to be passed in. - let llfty = if cx.sess().target.main_needs_argc_argv { + + let llfty = if cx.sess().target.os.contains("uefi") { + cx.type_func(&[cx.type_i8p(), cx.type_i8p()], cx.type_isize()) + } else if cx.sess().target.main_needs_argc_argv { cx.type_func(&[cx.type_int(), cx.type_ptr_to(cx.type_i8p())], cx.type_int()) } else { cx.type_func(&[], cx.type_int()) @@ -499,8 +502,12 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( }; let result = bx.call(start_ty, None, start_fn, &args, None); - let cast = bx.intcast(result, cx.type_int(), true); - bx.ret(cast); + if cx.sess().target.os.contains("uefi") { + bx.ret(result); + } else { + let cast = bx.intcast(result, cx.type_int(), true); + bx.ret(cast); + } llfn } @@ -511,7 +518,17 @@ fn get_argc_argv<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx: &'a Bx::CodegenCx, bx: &mut Bx, ) -> (Bx::Value, Bx::Value) { - if cx.sess().target.main_needs_argc_argv { + if cx.sess().target.os.contains("uefi") { + let param_handle = bx.get_param(0); + let param_system_table = bx.get_param(1); + let arg_argc = bx.const_int(cx.type_isize(), 2); + let arg_argv = bx.alloca(cx.type_array(cx.type_i8p(), 2), Align::ONE); + bx.store(param_handle, arg_argv, Align::ONE); + let arg_argv_el1 = + bx.gep(cx.type_ptr_to(cx.type_i8()), arg_argv, &[bx.const_int(cx.type_int(), 1)]); + bx.store(param_system_table, arg_argv_el1, Align::ONE); + (arg_argc, arg_argv) + } else if cx.sess().target.main_needs_argc_argv { // Params from native `main()` used as args for rust start function let param_argc = bx.get_param(0); let param_argv = bx.get_param(1); diff --git a/compiler/rustc_target/src/spec/uefi_msvc_base.rs b/compiler/rustc_target/src/spec/uefi_msvc_base.rs index 8968d3c8fc100..a50a55ad7e028 100644 --- a/compiler/rustc_target/src/spec/uefi_msvc_base.rs +++ b/compiler/rustc_target/src/spec/uefi_msvc_base.rs @@ -46,6 +46,7 @@ pub fn opts() -> TargetOptions { stack_probes: StackProbeType::Call, singlethread: true, linker: Some("rust-lld".into()), + entry_name: "efi_main".into(), ..base } } diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs b/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs index a7ae17839da8c..9bc74ad299976 100644 --- a/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs +++ b/compiler/rustc_target/src/spec/x86_64_unknown_uefi.rs @@ -5,12 +5,13 @@ // The win64 ABI is used. It differs from the sysv64 ABI, so we must use a windows target with // LLVM. "x86_64-unknown-windows" is used to get the minimal subset of windows-specific features. -use crate::spec::Target; +use crate::{abi::call::Conv, spec::Target}; pub fn target() -> Target { let mut base = super::uefi_msvc_base::opts(); base.cpu = "x86-64".into(); base.max_atomic_width = Some(64); + base.entry_abi = Conv::X86_64Win64; // We disable MMX and SSE for now, even though UEFI allows using them. Problem is, you have to // enable these CPU features explicitly before their first use, otherwise their instructions diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index cba8ef25db626..748c0655ee2a5 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -42,6 +42,7 @@ pub unsafe fn __rust_start_panic(_payload: *mut &mut dyn BoxMeUp) -> u32 { libc::abort(); } } else if #[cfg(any(target_os = "hermit", + target_os = "uefi", all(target_vendor = "fortanix", target_env = "sgx") ))] { unsafe fn abort() -> ! { diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml index c10bfde4ddf79..78481627f2024 100644 --- a/library/std/Cargo.toml +++ b/library/std/Cargo.toml @@ -47,6 +47,10 @@ hermit-abi = { version = "0.2.6", features = ['rustc-dep-of-std'] } [target.wasm32-wasi.dependencies] wasi = { version = "0.11.0", features = ['rustc-dep-of-std'], default-features = false } +[target.'cfg(target_os = "uefi")'.dependencies] +r-efi = { version = "4.1.0", features = ['rustc-dep-of-std', 'efiapi']} +r-efi-alloc = { version = "1.0.0", features = ['rustc-dep-of-std']} + [features] backtrace = [ "gimli-symbolize", diff --git a/library/std/build.rs b/library/std/build.rs index 8b1a06ee750fb..daf68fedb9471 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -31,6 +31,7 @@ fn main() { || target.contains("espidf") || target.contains("solid") || target.contains("nintendo-3ds") + || target.contains("uefi") { // These platforms don't have any special requirements. } else { @@ -42,7 +43,6 @@ fn main() { // - nvptx64-nvidia-cuda // - arch=avr // - tvos (aarch64-apple-tvos, x86_64-apple-tvos) - // - uefi (x86_64-unknown-uefi, i686-unknown-uefi) // - JSON targets // - Any new targets that have not been explicitly added above. println!("cargo:rustc-cfg=feature=\"restricted-std\""); diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 65d4c3c891ea6..126813167afd0 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -234,6 +234,7 @@ )] // // Language features: +#![feature(abi_efiapi)] #![feature(alloc_error_handler)] #![feature(allocator_internals)] #![feature(allow_internal_unsafe)] diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs index 42773805cdb6d..f9e3caa0857dd 100644 --- a/library/std/src/os/mod.rs +++ b/library/std/src/os/mod.rs @@ -143,6 +143,8 @@ pub mod redox; pub mod solaris; #[cfg(target_os = "solid_asp3")] pub mod solid; +#[cfg(target_os = "uefi")] +pub mod uefi; #[cfg(target_os = "vxworks")] pub mod vxworks; #[cfg(target_os = "watchos")] diff --git a/library/std/src/os/uefi/env.rs b/library/std/src/os/uefi/env.rs new file mode 100644 index 0000000000000..9fc1e58bebf5e --- /dev/null +++ b/library/std/src/os/uefi/env.rs @@ -0,0 +1,57 @@ +//! UEFI-specific extensions to the primitives in `std::env` module + +use crate::ffi::c_void; +use crate::ptr::NonNull; +use crate::sync::atomic::{AtomicPtr, Ordering}; +use crate::sync::Once; + +static GLOBAL_SYSTEM_TABLE: AtomicPtr = AtomicPtr::new(crate::ptr::null_mut()); +static GLOBAL_IMAGE_HANDLE: AtomicPtr = AtomicPtr::new(crate::ptr::null_mut()); +pub(crate) static GLOBALS: Once = Once::new(); + +/// Initializes the global System Table and Image Handle pointers. +/// +/// The standard library requires access to the UEFI System Table and the Application Image Handle +/// to operate. Those are provided to UEFI Applications via their application entry point. By +/// calling `init_globals()`, those pointers are retained by the standard library for future use. +/// The pointers are never exposed to any entity outside of this application and it is guaranteed +/// that, once the application exited, these pointers are never dereferenced again. +/// +/// Callers are required to ensure the pointers are valid for the entire lifetime of this +/// application. In particular, UEFI Boot Services must not be exited while an application with the +/// standard library is loaded. +/// +/// This function must not be called more than once. +#[unstable(feature = "uefi_std", issue = "100499")] +pub unsafe fn init_globals(handle: NonNull, system_table: NonNull) { + GLOBALS.call_once(|| { + GLOBAL_SYSTEM_TABLE.store(system_table.as_ptr(), Ordering::Release); + GLOBAL_IMAGE_HANDLE.store(handle.as_ptr(), Ordering::Release); + }) +} + +/// Get the SystemTable Pointer. +/// Note: This function panics if the System Table and Image Handle is Not initialized +#[unstable(feature = "uefi_std", issue = "100499")] +pub fn system_table() -> NonNull { + try_system_table().unwrap() +} + +/// Get the SystemHandle Pointer. +/// Note: This function panics if the System Table and Image Handle is Not initialized +#[unstable(feature = "uefi_std", issue = "100499")] +pub fn image_handle() -> NonNull { + try_image_handle().unwrap() +} + +/// Get the SystemTable Pointer. +/// This function is mostly intended for places where panic is not an option +pub(crate) fn try_system_table() -> Option> { + NonNull::new(GLOBAL_SYSTEM_TABLE.load(Ordering::Acquire)) +} + +/// Get the SystemHandle Pointer. +/// This function is mostly intended for places where panic is not an option +pub(crate) fn try_image_handle() -> Option> { + NonNull::new(GLOBAL_IMAGE_HANDLE.load(Ordering::Acquire)) +} diff --git a/library/std/src/os/uefi/mod.rs b/library/std/src/os/uefi/mod.rs new file mode 100644 index 0000000000000..fc0468f25fc8e --- /dev/null +++ b/library/std/src/os/uefi/mod.rs @@ -0,0 +1,7 @@ +//! Platform-specific extensions to `std` for UEFI. + +#![unstable(feature = "uefi_std", issue = "100499")] + +pub mod env; +#[path = "../windows/ffi.rs"] +pub mod ffi; diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index c080c176a2ace..bde49bb295ef6 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -43,6 +43,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_family = "wasm")] { mod wasm; pub use self::wasm::*; + } else if #[cfg(target_os = "uefi")] { + mod uefi; + pub use self::uefi::*; } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { mod sgx; pub use self::sgx::*; diff --git a/library/std/src/sys/uefi/alloc.rs b/library/std/src/sys/uefi/alloc.rs new file mode 100644 index 0000000000000..4ac657a39b5a3 --- /dev/null +++ b/library/std/src/sys/uefi/alloc.rs @@ -0,0 +1,34 @@ +//! Global Allocator for UEFI. +//! Uses [r-efi-alloc](https://crates.io/crates/r-efi-alloc) + +use crate::alloc::{handle_alloc_error, GlobalAlloc, Layout, System}; + +pub(crate) const POOL_ALIGNMENT: usize = 8; + +const MEMORY_TYPE: u32 = r_efi::efi::LOADER_DATA; + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let system_table = match crate::os::uefi::env::try_system_table() { + None => return crate::ptr::null_mut(), + Some(x) => x.as_ptr() as *mut _, + }; + + if layout.size() > 0 { + unsafe { r_efi_alloc::raw::alloc(system_table, layout, MEMORY_TYPE) } + } else { + layout.dangling().as_ptr() + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let system_table = match crate::os::uefi::env::try_system_table() { + None => handle_alloc_error(layout), + Some(x) => x.as_ptr() as *mut _, + }; + if layout.size() > 0 { + unsafe { r_efi_alloc::raw::dealloc(system_table, ptr, layout) } + } + } +} diff --git a/library/std/src/sys/uefi/args.rs b/library/std/src/sys/uefi/args.rs new file mode 100644 index 0000000000000..ddc179653c6fe --- /dev/null +++ b/library/std/src/sys/uefi/args.rs @@ -0,0 +1,172 @@ +//! Args related functionality for UEFI. Takes a lot of inspiration of Windows args + +use super::common; +use crate::env::current_exe; +use crate::ffi::OsString; +use crate::fmt; +use crate::num::NonZeroU16; +use crate::os::uefi::ffi::OsStringExt; +use crate::path::PathBuf; +use crate::sync::OnceLock; +use crate::sys_common::wstr::WStrUnits; +use crate::vec; +use r_efi::efi::protocols::loaded_image; + +/// This is the const equivalent to `NonZeroU16::new(n).unwrap()` +/// +/// FIXME: This can be removed once `Option::unwrap` is stably const. +/// See the `const_option` feature (#67441). +const fn non_zero_u16(n: u16) -> NonZeroU16 { + match NonZeroU16::new(n) { + Some(n) => n, + None => panic!("called `unwrap` on a `None` value"), + } +} + +pub struct Args { + parsed_args_list: vec::IntoIter, +} + +// Get the Supplied arguments for loaded image. +// Uses EFI_LOADED_IMAGE_PROTOCOL +pub fn args() -> Args { + static ARGUMENTS: OnceLock> = OnceLock::new(); + // Caching the arguments the first time they are parsed. + let vec_args = ARGUMENTS.get_or_init(|| { + match common::get_current_handle_protocol::( + loaded_image::PROTOCOL_GUID, + ) { + Some(x) => { + let lp_cmd_line = unsafe { (*x.as_ptr()).load_options as *const u16 }; + parse_lp_cmd_line(unsafe { WStrUnits::new(lp_cmd_line) }, || { + current_exe().map(PathBuf::into_os_string).unwrap_or_else(|_| OsString::new()) + }) + } + None => Vec::new(), + } + }); + Args { parsed_args_list: vec_args.clone().into_iter() } +} + +/// Implements the UEFI command-line argument parsing algorithm. +/// +/// While this sounds good in theory, I have not really found any concrete implementation of +/// argument parsing in UEFI. Thus I have created thisimplementation based on what is defined in +/// Section 3.2 of [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf) +pub(crate) fn parse_lp_cmd_line<'a, F: Fn() -> OsString>( + lp_cmd_line: Option>, + exe_name: F, +) -> Vec { + const QUOTE: NonZeroU16 = non_zero_u16(b'"' as u16); + const SPACE: NonZeroU16 = non_zero_u16(b' ' as u16); + const CARET: NonZeroU16 = non_zero_u16(b'^' as u16); + + let mut ret_val = Vec::new(); + // If the cmd line pointer is null or it points to an empty string then + // return the name of the executable as argv[0]. + if lp_cmd_line.as_ref().and_then(|cmd| cmd.peek()).is_none() { + ret_val.push(exe_name()); + return ret_val; + } + let mut code_units = lp_cmd_line.unwrap(); + + // The executable name at the beginning is special. + let mut in_quotes = false; + let mut cur = Vec::new(); + for w in &mut code_units { + match w { + // A quote mark always toggles `in_quotes` no matter what because + // there are no escape characters when parsing the executable name. + QUOTE => in_quotes = !in_quotes, + // If not `in_quotes` then whitespace ends argv[0]. + SPACE if !in_quotes => break, + // In all other cases the code unit is taken literally. + _ => cur.push(w.get()), + } + } + + // Skip whitespace. + code_units.advance_while(|w| w == SPACE); + ret_val.push(OsString::from_wide(&cur)); + + // Parse the arguments according to these rules: + // * All code units are taken literally except space, quote and caret. + // * When not `in_quotes`, space separate arguments. Consecutive spaces are + // treated as a single separator. + // * A space `in_quotes` is taken literally. + // * A quote toggles `in_quotes` mode unless it's escaped. An escaped quote is taken literally. + // * A quote can be escaped if preceded by caret. + // * A caret can be escaped if preceded by caret. + let mut cur = Vec::new(); + let mut in_quotes = false; + while let Some(w) = code_units.next() { + match w { + // If not `in_quotes`, a space or tab ends the argument. + SPACE if !in_quotes => { + ret_val.push(OsString::from_wide(&cur[..])); + cur.truncate(0); + + // Skip whitespace. + code_units.advance_while(|w| w == SPACE); + } + // Caret can escape quotes or carets + CARET if in_quotes => { + if let Some(x) = code_units.next() { + cur.push(x.get()) + } + } + // If `in_quotes` and not backslash escaped (see above) then a quote either + // unsets `in_quote` or is escaped by another quote. + QUOTE if in_quotes => match code_units.peek() { + // Otherwise set `in_quotes`. + Some(_) => in_quotes = false, + // The end of the command line. + // Push `cur` even if empty, which we do by breaking while `in_quotes` is still set. + None => break, + }, + // If not `in_quotes` and not BACKSLASH escaped (see above) then a quote sets `in_quote`. + QUOTE => in_quotes = true, + // Everything else is always taken literally. + _ => cur.push(w.get()), + } + } + // Push the final argument, if any. + if !cur.is_empty() || in_quotes { + ret_val.push(OsString::from_wide(&cur[..])); + } + ret_val +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.parsed_args_list.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + + #[inline] + fn next(&mut self) -> Option { + self.parsed_args_list.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.parsed_args_list.size_hint() + } +} + +impl DoubleEndedIterator for Args { + #[inline] + fn next_back(&mut self) -> Option { + self.parsed_args_list.next_back() + } +} + +impl ExactSizeIterator for Args { + #[inline] + fn len(&self) -> usize { + self.parsed_args_list.len() + } +} diff --git a/library/std/src/sys/uefi/common.rs b/library/std/src/sys/uefi/common.rs new file mode 100644 index 0000000000000..8c9de91101186 --- /dev/null +++ b/library/std/src/sys/uefi/common.rs @@ -0,0 +1,577 @@ +//! Contains most of the shared UEFI specific stuff. Some of this might be moved to `std::os::uefi` +//! if needed but no point in adding extra public API when there is not Std support for UEFI in the +//! first place + +use r_efi::efi::{EventNotify, Guid, Tpl}; + +use crate::alloc::{AllocError, Allocator, Global, Layout}; +use crate::ffi::{OsStr, OsString}; +use crate::io::{self, const_io_error}; +use crate::mem::{self, MaybeUninit}; +use crate::os::uefi; +use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; +use crate::ptr::NonNull; + +/// Get the Protocol for current system handle. +/// Note: Some protocols need to be manually freed. It is the callers responsibility to do so. +pub(crate) fn get_current_handle_protocol(protocol_guid: Guid) -> Option> { + let system_handle = uefi::env::image_handle(); + open_protocol(system_handle, protocol_guid).ok() +} + +#[repr(transparent)] +pub(crate) struct Event { + inner: r_efi::efi::Event, +} + +impl Event { + pub(crate) fn create( + event_type: u32, + event_tpl: Tpl, + notify_function: Option, + notify_context: Option>, + ) -> io::Result { + let boot_services = boot_services(); + + let mut event: r_efi::efi::Event = crate::ptr::null_mut(); + let notify_context = match notify_context { + None => crate::ptr::null_mut(), + Some(x) => x.as_ptr(), + }; + + let r = unsafe { + ((*boot_services.as_ptr()).create_event)( + event_type, + event_tpl, + notify_function, + notify_context, + &mut event, + ) + }; + + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(Self { inner: event }) } + } + + pub(crate) fn create_timer() -> io::Result { + Self::create( + r_efi::efi::EVT_TIMER | r_efi::efi::EVT_NOTIFY_WAIT, + r_efi::efi::TPL_CALLBACK, + Some(empty_notify), + None, + ) + } + + fn set_timer(&self, timeout: u64) -> io::Result<()> { + let boot_services = boot_services(); + let r = unsafe { + ((*boot_services.as_ptr()).set_timer)( + self.as_raw_event(), + r_efi::efi::TIMER_RELATIVE, + timeout, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + // timeout is the number of 100ns + pub(crate) fn wait_with_timer(&self, timer_event: &Event, timeout: u64) -> io::Result<()> { + timer_event.set_timer(timeout)?; + let index = Self::wait_raw(&mut [self.as_raw_event(), timer_event.as_raw_event()])?; + match index { + 0 => Ok(()), + 1 => Err(io::const_io_error!(io::ErrorKind::TimedOut, "Event Timout")), + _ => unreachable!(), + } + } + + pub(crate) fn wait(&self) -> io::Result<()> { + Self::wait_raw(&mut [self.as_raw_event()])?; + Ok(()) + } + + fn wait_raw(events: &mut [*mut crate::ffi::c_void]) -> io::Result { + let boot_services = boot_services(); + + let mut index = 0usize; + let r = unsafe { + ((*boot_services.as_ptr()).wait_for_event)( + events.len(), + events.as_mut_ptr(), + &mut index, + ) + }; + + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(index) } + } + + #[inline] + pub(crate) fn as_raw_event(&self) -> r_efi::efi::Event { + self.inner + } +} + +pub(crate) extern "efiapi" fn empty_notify(_: r_efi::efi::Event, _: *mut crate::ffi::c_void) {} + +impl Drop for Event { + fn drop(&mut self) { + let boot_services = boot_services(); + // Always returns EFI_SUCCESS + let _ = unsafe { ((*boot_services.as_ptr()).close_event)(self.inner) }; + } +} + +// A type to make working with UEFI DSTs easier +// The Layout of this type has to be explicitly supplied +// Inspiered by Box +pub(crate) struct VariableBox { + inner: NonNull, + layout: Layout, +} + +impl VariableBox { + pub(crate) unsafe fn from_raw(inner: *mut T, layout: Layout) -> VariableBox { + unsafe { VariableBox::new(NonNull::new_unchecked(inner), layout) } + } + + #[inline] + pub(crate) unsafe fn new(inner: NonNull, layout: Layout) -> VariableBox { + VariableBox { inner, layout } + } + + #[inline] + pub(crate) fn into_raw_with_layout(b: VariableBox) -> (*mut T, Layout) { + let (leaked, layout) = VariableBox::into_non_null(b); + (leaked.as_ptr(), layout) + } + + pub(crate) fn new_uninit(layout: Layout) -> VariableBox> { + match Self::try_new_uninit(layout) { + Ok(x) => x, + Err(_) => crate::alloc::handle_alloc_error(layout), + } + } + + fn try_new_uninit(layout: Layout) -> Result>, AllocError> { + let inner = Global.allocate(layout)?.cast(); + unsafe { Ok(VariableBox::new(inner, layout)) } + } + + #[inline] + pub(crate) fn leak<'a>(b: Self) -> &'a mut T { + unsafe { &mut *mem::ManuallyDrop::new(b).inner.as_ptr() } + } + + #[inline] + pub(crate) fn into_non_null(b: Self) -> (NonNull, Layout) { + let layout = b.layout; + (NonNull::from(VariableBox::leak(b)), layout) + } + + #[inline] + pub(crate) fn layout(&self) -> Layout { + self.layout + } + + #[inline] + pub(crate) fn as_mut_ptr(&mut self) -> *mut T { + self.inner.as_ptr() + } + + #[inline] + pub(crate) fn as_ptr(&self) -> *const T { + self.inner.as_ptr() + } +} + +impl VariableBox> { + #[inline] + pub(crate) unsafe fn assume_init(self) -> VariableBox { + let (raw, layout) = VariableBox::into_raw_with_layout(self); + unsafe { VariableBox::from_raw(raw.cast(), layout) } + } + + #[inline] + pub(crate) fn as_uninit_mut_ptr(&mut self) -> *mut T { + unsafe { (*self.inner.as_ptr()).as_mut_ptr() } + } +} + +impl Drop for VariableBox { + #[inline] + fn drop(&mut self) { + unsafe { Global.deallocate(self.inner.cast(), self.layout) } + } +} + +// Locate handles with a particular protocol GUID +/// Implemented using `EFI_BOOT_SERVICES.LocateHandles()` +pub(crate) fn locate_handles(mut guid: Guid) -> io::Result>> { + fn inner( + guid: &mut Guid, + boot_services: NonNull, + buf_size: &mut usize, + buf: *mut r_efi::efi::Handle, + ) -> io::Result<()> { + let r = unsafe { + ((*boot_services.as_ptr()).locate_handle)( + r_efi::efi::BY_PROTOCOL, + guid, + crate::ptr::null_mut(), + buf_size, + buf, + ) + }; + + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + let boot_services = boot_services(); + let mut buf_len = 0usize; + + match inner(&mut guid, boot_services, &mut buf_len, crate::ptr::null_mut()) { + Ok(()) => unreachable!(), + Err(e) => match e.kind() { + io::ErrorKind::FileTooLarge => {} + _ => return Err(e), + }, + } + + // The returned buf_len is in bytes + let mut buf: Vec = + Vec::with_capacity(buf_len / crate::mem::size_of::()); + match inner(&mut guid, boot_services, &mut buf_len, buf.as_mut_ptr()) { + Ok(()) => { + // SAFETY: This is safe because the call will succeed only if buf_len >= required + // length. Also, on success, the `buf_len` is updated with the size of bufferv (in + // bytes) written + unsafe { buf.set_len(buf_len / crate::mem::size_of::()) }; + Ok(buf.iter().filter_map(|x| NonNull::new(*x)).collect()) + } + Err(e) => Err(e), + } +} + +/// Open Protocol on a handle +/// Implemented using `EFI_BOOT_SERVICES.OpenProtocol()` +pub(crate) fn open_protocol( + handle: NonNull, + mut protocol_guid: Guid, +) -> io::Result> { + let boot_services = boot_services(); + let system_handle = uefi::env::image_handle(); + let mut protocol: MaybeUninit<*mut T> = MaybeUninit::uninit(); + + let r = unsafe { + ((*boot_services.as_ptr()).open_protocol)( + handle.as_ptr(), + &mut protocol_guid, + protocol.as_mut_ptr().cast(), + system_handle.as_ptr(), + crate::ptr::null_mut(), + r_efi::system::OPEN_PROTOCOL_GET_PROTOCOL, + ) + }; + + if r.is_error() { + Err(status_to_io_error(r)) + } else { + NonNull::new(unsafe { protocol.assume_init() }) + .ok_or(const_io_error!(io::ErrorKind::Other, "null protocol")) + } +} + +pub(crate) fn status_to_io_error(s: r_efi::efi::Status) -> io::Error { + use io::ErrorKind; + use r_efi::efi::Status; + + // Keep the List in Alphabetical Order + // The Messages are taken from UEFI Specification Appendix D - Status Codes + match s { + Status::ABORTED => { + const_io_error!(ErrorKind::ConnectionAborted, "The operation was aborted.") + } + Status::ACCESS_DENIED => { + const_io_error!(ErrorKind::PermissionDenied, "Access was denied.") + } + Status::ALREADY_STARTED => { + const_io_error!(ErrorKind::Other, "The protocol has already been started.") + } + Status::BAD_BUFFER_SIZE => { + const_io_error!( + ErrorKind::InvalidData, + "The buffer was not the proper size for the request." + ) + } + Status::BUFFER_TOO_SMALL => { + const_io_error!( + ErrorKind::FileTooLarge, + "The buffer is not large enough to hold the requested data. The required buffer size is returned in the appropriate parameter when this error occurs." + ) + } + Status::COMPROMISED_DATA => { + const_io_error!( + ErrorKind::Other, + "The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status." + ) + } + Status::CONNECTION_FIN => { + const_io_error!( + ErrorKind::Other, + "The receiving operation fails because the communication peer has closed the connection and there is no more data in the receive buffer of the instance." + ) + } + Status::CONNECTION_REFUSED => { + const_io_error!( + ErrorKind::ConnectionRefused, + "The receiving or transmission operation fails because this connection is refused." + ) + } + Status::CONNECTION_RESET => { + const_io_error!( + ErrorKind::ConnectionReset, + "The connect fails because the connection is reset either by instance itself or the communication peer." + ) + } + Status::CRC_ERROR => const_io_error!(ErrorKind::Other, "A CRC error was detected."), + Status::DEVICE_ERROR => const_io_error!( + ErrorKind::Other, + "The physical device reported an error while attempting the operation." + ), + Status::END_OF_FILE => { + const_io_error!(ErrorKind::UnexpectedEof, "The end of the file was reached.") + } + Status::END_OF_MEDIA => { + const_io_error!(ErrorKind::Other, "Beginning or end of media was reached") + } + Status::HOST_UNREACHABLE => { + const_io_error!(ErrorKind::HostUnreachable, "The remote host is not reachable.") + } + Status::HTTP_ERROR => { + const_io_error!(ErrorKind::Other, "A HTTP error occurred during the network operation.") + } + Status::ICMP_ERROR => { + const_io_error!( + ErrorKind::Other, + "An ICMP error occurred during the network operation." + ) + } + Status::INCOMPATIBLE_VERSION => { + const_io_error!( + ErrorKind::Other, + "The function encountered an internal version that was incompatible with a version requested by the caller." + ) + } + Status::INVALID_LANGUAGE => { + const_io_error!(ErrorKind::InvalidData, "The language specified was invalid.") + } + Status::INVALID_PARAMETER => { + const_io_error!(ErrorKind::InvalidInput, "A parameter was incorrect.") + } + Status::IP_ADDRESS_CONFLICT => { + const_io_error!(ErrorKind::AddrInUse, "There is an address conflict address allocation") + } + Status::LOAD_ERROR => { + const_io_error!(ErrorKind::Other, "The image failed to load.") + } + Status::MEDIA_CHANGED => { + const_io_error!( + ErrorKind::Other, + "The medium in the device has changed since the last access." + ) + } + Status::NETWORK_UNREACHABLE => { + const_io_error!( + ErrorKind::NetworkUnreachable, + "The network containing the remote host is not reachable." + ) + } + Status::NO_MAPPING => { + const_io_error!(ErrorKind::Other, "A mapping to a device does not exist.") + } + Status::NO_MEDIA => { + const_io_error!( + ErrorKind::Other, + "The device does not contain any medium to perform the operation." + ) + } + Status::NO_RESPONSE => { + const_io_error!( + ErrorKind::HostUnreachable, + "The server was not found or did not respond to the request." + ) + } + Status::NOT_FOUND => const_io_error!(ErrorKind::NotFound, "The item was not found."), + Status::NOT_READY => { + const_io_error!(ErrorKind::ResourceBusy, "There is no data pending upon return.") + } + Status::NOT_STARTED => { + const_io_error!(ErrorKind::Other, "The protocol has not been started.") + } + Status::OUT_OF_RESOURCES => { + const_io_error!(ErrorKind::OutOfMemory, "A resource has run out.") + } + Status::PROTOCOL_ERROR => { + const_io_error!( + ErrorKind::Other, + "A protocol error occurred during the network operation." + ) + } + Status::PROTOCOL_UNREACHABLE => { + const_io_error!(ErrorKind::Other, "An ICMP protocol unreachable error is received.") + } + Status::SECURITY_VIOLATION => { + const_io_error!( + ErrorKind::PermissionDenied, + "The function was not performed due to a security violation." + ) + } + Status::TFTP_ERROR => { + const_io_error!(ErrorKind::Other, "A TFTP error occurred during the network operation.") + } + Status::TIMEOUT => const_io_error!(ErrorKind::TimedOut, "The timeout time expired."), + Status::UNSUPPORTED => { + const_io_error!(ErrorKind::Unsupported, "The operation is not supported.") + } + Status::VOLUME_FULL => { + const_io_error!(ErrorKind::StorageFull, "There is no more space on the file system.") + } + Status::VOLUME_CORRUPTED => { + const_io_error!( + ErrorKind::Other, + "An inconstancy was detected on the file system causing the operating to fail." + ) + } + Status::WRITE_PROTECTED => { + const_io_error!(ErrorKind::ReadOnlyFilesystem, "The device cannot be written to.") + } + _ => io::Error::new(ErrorKind::Uncategorized, format!("Status: {}", s.as_usize())), + } +} + +pub(crate) fn install_protocol( + handle: &mut r_efi::efi::Handle, + mut guid: r_efi::efi::Guid, + interface: &mut T, +) -> io::Result<()> { + let boot_services = boot_services(); + let r = unsafe { + ((*boot_services.as_ptr()).install_protocol_interface)( + handle, + &mut guid, + r_efi::efi::NATIVE_INTERFACE, + interface as *mut T as *mut crate::ffi::c_void, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } +} + +pub(crate) fn uninstall_protocol( + handle: r_efi::efi::Handle, + mut guid: r_efi::efi::Guid, + interface: &mut T, +) -> io::Result<()> { + let boot_services = boot_services(); + let r = unsafe { + ((*boot_services.as_ptr()).uninstall_protocol_interface)( + handle, + &mut guid, + interface as *mut T as *mut crate::ffi::c_void, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } +} + +// A Helper trait for `ProtocolWrapper

`. +pub(crate) trait Protocol { + const PROTOCOL_GUID: Guid; +} + +// A wrapper for creating protocols from Rust in Local scope. Uninstalls the protocol on Drop. +pub(crate) struct ProtocolWrapper

+where + P: Protocol, +{ + handle: NonNull, + protocol: Box

, +} + +impl

ProtocolWrapper

+where + P: Protocol, +{ + #[inline] + pub(crate) fn new(handle: NonNull, protocol: Box

) -> Self { + Self { handle, protocol } + } + + pub(crate) fn install_protocol(protocol: P) -> io::Result> { + let mut handle: r_efi::efi::Handle = crate::ptr::null_mut(); + let mut protocol = Box::new(protocol); + install_protocol::

(&mut handle, P::PROTOCOL_GUID, &mut protocol)?; + let handle = NonNull::new(handle) + .ok_or(io::const_io_error!(io::ErrorKind::Uncategorized, "found null handle"))?; + Ok(Self::new(handle, protocol)) + } + + pub(crate) fn install_protocol_in( + protocol: P, + mut handle: r_efi::efi::Handle, + ) -> io::Result> { + let mut protocol = Box::new(protocol); + install_protocol::

(&mut handle, P::PROTOCOL_GUID, &mut protocol)?; + let handle = NonNull::new(handle) + .ok_or(io::const_io_error!(io::ErrorKind::Uncategorized, "found null handle"))?; + Ok(Self::new(handle, protocol)) + } + + #[inline] + pub(crate) fn handle(&self) -> NonNull { + self.handle + } +} + +impl

Drop for ProtocolWrapper

+where + P: Protocol, +{ + #[inline] + fn drop(&mut self) { + let _ = uninstall_protocol::

(self.handle.as_ptr(), P::PROTOCOL_GUID, &mut self.protocol); + } +} + +/// Get the BootServices Pointer. +pub(crate) fn boot_services() -> NonNull { + let system_table: NonNull = uefi::env::system_table().cast(); + let boot_services = unsafe { (*system_table.as_ptr()).boot_services }; + NonNull::new(boot_services).unwrap() +} +/// Get the BootServices Pointer. +/// This function is mostly intended for places where panic is not an option +pub(crate) fn try_boot_services() -> Option> { + let system_table: NonNull = uefi::env::try_system_table()?.cast(); + let boot_services = unsafe { (*system_table.as_ptr()).boot_services }; + NonNull::new(boot_services) +} + +/// Get the RuntimeServices Pointer. +pub(crate) fn runtime_services() -> NonNull { + let system_table: NonNull = uefi::env::system_table().cast(); + let runtime_services = unsafe { (*system_table.as_ptr()).runtime_services }; + NonNull::new(runtime_services).unwrap() +} + +// Create UCS-2 Vector from OsStr +pub(crate) fn to_ffi_string(s: &OsStr) -> Vec { + let mut v: Vec = s.encode_wide().collect(); + v.push(0); + v +} + +// Create OsString from UEFI UCS-2 String +pub(crate) fn from_ffi_string(ucs: *mut u16, bytes: usize) -> OsString { + // Convert len in bytes to string length and do not count the null character + let len = bytes / crate::mem::size_of::() - 1; + let s = unsafe { crate::slice::from_raw_parts(ucs, len) }; + OsString::from_wide(s) +} diff --git a/library/std/src/sys/uefi/env.rs b/library/std/src/sys/uefi/env.rs new file mode 100644 index 0000000000000..c106d5fed3e1d --- /dev/null +++ b/library/std/src/sys/uefi/env.rs @@ -0,0 +1,9 @@ +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "uefi"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ""; + pub const DLL_EXTENSION: &str = ""; + pub const EXE_SUFFIX: &str = ".efi"; + pub const EXE_EXTENSION: &str = "efi"; +} diff --git a/library/std/src/sys/uefi/fs.rs b/library/std/src/sys/uefi/fs.rs new file mode 100644 index 0000000000000..4bf65d1dc87bc --- /dev/null +++ b/library/std/src/sys/uefi/fs.rs @@ -0,0 +1,1035 @@ +//! File System functionality for UEFI + +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::hash::Hash; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::path::{Path, PathBuf}; +use crate::ptr::addr_of; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; +use r_efi::protocols::file; + +pub struct File { + ptr: uefi_fs::FileProtocol, +} + +#[derive(Clone, Copy, Debug)] +pub struct FileAttr { + size: u64, + perm: FilePermissions, + file_type: FileType, + created_time: SystemTime, + last_accessed_time: SystemTime, + modification_time: SystemTime, +} + +pub struct ReadDir { + inner: uefi_fs::FileProtocol, + path: PathBuf, +} + +pub struct DirEntry { + pub(crate) attr: FileAttr, + pub(crate) name: OsString, + path: PathBuf, +} + +#[derive(Clone, Debug)] +pub struct OpenOptions { + open_mode: u64, + attr: u64, + append: bool, + truncate: bool, + create_new: bool, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct FilePermissions { + attr: u64, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub struct FileType { + attr: u64, +} + +#[derive(Copy, Clone, Debug)] +pub struct FileTimes { + last_accessed_time: Option, + modification_time: Option, +} + +#[derive(Debug)] +pub struct DirBuilder { + attr: u64, + open_mode: u64, +} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.size + } + + pub fn perm(&self) -> FilePermissions { + self.perm + } + + pub fn file_type(&self) -> FileType { + self.file_type + } + + pub fn modified(&self) -> io::Result { + Ok(self.modification_time) + } + + pub fn accessed(&self) -> io::Result { + Ok(self.last_accessed_time) + } + + pub fn created(&self) -> io::Result { + Ok(self.created_time) + } +} + +impl From<&super::common::VariableBox> for FileAttr { + fn from(info: &super::common::VariableBox) -> Self { + unsafe { + FileAttr { + size: addr_of!((*info.as_ptr()).file_size).read(), + perm: FilePermissions { attr: addr_of!((*info.as_ptr()).attribute).read() }, + file_type: FileType { attr: addr_of!((*info.as_ptr()).attribute).read() }, + created_time: SystemTime::from(addr_of!((*info.as_ptr()).create_time).read()), + last_accessed_time: SystemTime::from( + addr_of!((*info.as_ptr()).last_access_time).read(), + ), + modification_time: SystemTime::from( + addr_of!((*info.as_ptr()).modification_time).read(), + ), + } + } + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + self.attr & file::READ_ONLY != 0 + } + + pub fn set_readonly(&mut self, readonly: bool) { + if readonly { + self.attr |= file::READ_ONLY; + } else { + self.attr &= !file::READ_ONLY; + } + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.attr & file::DIRECTORY != 0 + } + + // Not sure if Archive is a file + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + // Doesn't seem like symlink can be detected/supported. + pub fn is_symlink(&self) -> bool { + false + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.path, f) + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + let dir_entry = self.inner.read_dir_entry(self.path.as_path()); + if let Some(Ok(ref x)) = dir_entry { + // Ignore `.` and `..` + if x.file_name().as_os_str() == OsStr::new(".") + || x.file_name().as_os_str() == OsStr::new("..") + { + self.next() + } else { + dir_entry + } + } else { + dir_entry + } + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.last_accessed_time = Some(t); + } + + pub fn set_modified(&mut self, t: SystemTime) { + self.modification_time = Some(t); + } +} + +impl Default for FileTimes { + fn default() -> Self { + Self { last_accessed_time: None, modification_time: None } + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + pub fn file_name(&self) -> OsString { + self.name.clone() + } + + pub fn metadata(&self) -> io::Result { + Ok(self.attr) + } + + pub fn file_type(&self) -> io::Result { + Ok(self.attr.file_type()) + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + // These options open file in readonly mode + OpenOptions { + open_mode: file::MODE_READ, + attr: 0, + append: false, + truncate: false, + create_new: false, + } + } + + pub fn read(&mut self, read: bool) { + if read { + self.open_mode |= file::MODE_READ; + } else { + self.open_mode &= !file::MODE_READ; + } + } + + pub fn write(&mut self, write: bool) { + if write { + self.open_mode |= file::MODE_WRITE; + } else { + self.open_mode &= !file::MODE_WRITE; + } + } + + pub fn append(&mut self, append: bool) { + self.write(true); + self.append = append; + } + + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + + pub fn create(&mut self, create: bool) { + if create { + self.open_mode |= file::MODE_CREATE; + } else { + self.open_mode &= !file::MODE_CREATE; + } + } + + // FIXME: Should be atomic, so not sure if this is correct + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + self.create(true); + self.write(true); + } +} + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + if opts.create_new && uefi_fs::FileProtocol::file_exists(path)? { + return Err(io::const_io_error!(io::ErrorKind::AlreadyExists, "File already exists")); + } + let file_opened = uefi_fs::FileProtocol::from_path(path, opts.open_mode, opts.attr)?; + let file = File { ptr: file_opened }; + if opts.truncate { + file.truncate(0)?; + } else if opts.append { + // If you truncate a file, no need to seek to end + file.seek(SeekFrom::End(0))?; + } + Ok(file) + } + + pub fn file_attr(&self) -> io::Result { + let info = self.ptr.get_file_info()?; + Ok(FileAttr::from(&info)) + } + + pub fn fsync(&self) -> io::Result<()> { + self.ptr.flush() + } + + pub fn datasync(&self) -> io::Result<()> { + self.fsync() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + self.ptr.set_file_size(size) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + let mut buf_len = buf.len(); + self.ptr.read(buf, &mut buf_len)?; + Ok(buf_len) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|buf| self.read(buf), bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + let mut buffer_size = buf.capacity(); + unsafe { self.ptr.read(buf.as_mut(), &mut buffer_size) }?; + + unsafe { + buf.advance(buffer_size); + } + Ok(()) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.ptr.write(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|buf| self.write(buf), bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn flush(&self) -> io::Result<()> { + Ok(()) + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + let position: u64 = match pos { + SeekFrom::Start(x) => x, + SeekFrom::Current(x) => ((self.ptr.get_position()? as i64) + x) as u64, + SeekFrom::End(x) => ((self.file_attr()?.size as i64) + x) as u64, + }; + + self.ptr.set_position(position) + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { + self.ptr.set_file_attr(perm.attr) + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + self.ptr.set_file_times(times) + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder { + attr: file::DIRECTORY, + open_mode: file::MODE_READ | file::MODE_WRITE | file::MODE_CREATE, + } + } + + pub fn mkdir(&self, p: &Path) -> io::Result<()> { + let _ = uefi_fs::FileProtocol::from_path(p, self.open_mode, self.attr)?; + Ok(()) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("File"); + if let Ok(x) = self.ptr.get_file_name() { + b.field("Name", &x); + } + b.finish() + } +} + +pub fn readdir(p: &Path) -> io::Result { + let abs_path = super::path::absolute(p)?; + let open_mode = file::MODE_READ; + let attr = file::DIRECTORY; + let inner = uefi_fs::FileProtocol::from_path(p, open_mode, attr)?; + Ok(ReadDir { inner, path: abs_path }) +} + +// Just Delete the file since symlinks are not supported +pub fn unlink(p: &Path) -> io::Result<()> { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let attr = 0; + let file = uefi_fs::FileProtocol::from_path(p, open_mode, attr)?; + file.delete() +} + +pub fn rename(old: &Path, new: &Path) -> io::Result<()> { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let file = uefi_fs::FileProtocol::from_path(old, open_mode, 0)?; + + // If tbe device prefix is same or both path are relative (in which case None will be + // returned), then we can just use `set_file_name`. + if super::path::device_prefix(old.as_os_str()) == super::path::device_prefix(new.as_os_str()) { + // Delete if new already exists + if let Ok(f) = uefi_fs::FileProtocol::from_path(new, open_mode, 0) { + f.delete()?; + } + file.set_file_name(new.as_os_str()) + } else { + // Use simple copy if the new path is in a different device. + copy(old, new)?; + file.delete() + } +} + +pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let file = uefi_fs::FileProtocol::from_path(p, open_mode, 0)?; + file.set_file_attr(perm.attr) +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let attr = file::DIRECTORY; + let file = uefi_fs::FileProtocol::from_path(p, open_mode, attr)?; + file.delete() +} + +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let attr = file::DIRECTORY; + let file = uefi_fs::FileProtocol::from_path(path, open_mode, attr)?; + cascade_delete(file, path) +} + +pub fn try_exists(path: &Path) -> io::Result { + match uefi_fs::FileProtocol::from_path(path, file::MODE_READ, 0) { + Ok(_) => Ok(true), + Err(e) => match e.kind() { + io::ErrorKind::NotFound => Ok(false), + _ => Err(e), + }, + } +} + +// Symlink not supported +pub fn readlink(_p: &Path) -> io::Result { + unsupported() +} + +// Symlink not supported +pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> { + unsupported() +} + +// Symlink not supported +pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> { + unsupported() +} + +pub fn stat(p: &Path) -> io::Result { + let opts = OpenOptions { + open_mode: file::MODE_READ, + attr: 0, + append: false, + truncate: false, + create_new: false, + }; + File::open(p, &opts)?.file_attr() +} + +// Shoule be same as stat since symlinks are not implemented anyway +pub fn lstat(p: &Path) -> io::Result { + stat(p) +} + +// Not sure how to implement. Tried doing a round conversion from EFI_DEVICE_PATH protocol but +// that doesn't work either. +pub fn canonicalize(_p: &Path) -> io::Result { + unsupported() +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + let from_file = uefi_fs::FileProtocol::from_path(from, file::MODE_READ, 0)?; + let to_file = uefi_fs::FileProtocol::from_path( + to, + file::MODE_READ | file::MODE_WRITE | file::MODE_CREATE, + 0, + )?; + // Truncate destination file. + to_file.set_file_size(0)?; + + let info = from_file.get_file_info()?; + let file_size = unsafe { (*info.as_ptr()).file_size }; + let mut buffer = Vec::::with_capacity(file_size as usize); + let mut buffer_size = buffer.capacity(); + from_file.read(&mut buffer, &mut buffer_size)?; + unsafe { buffer.set_len(buffer_size) }; + Ok(to_file.write(&buffer)? as u64) +} + +// Liberal Cascade Delete +// The file should not point to root +fn cascade_delete(file: uefi_fs::FileProtocol, path: &Path) -> io::Result<()> { + // Skip "." and ".." + let _ = file.read_dir_entry(path); + let _ = file.read_dir_entry(path); + + while let Some(dir_entry) = file.read_dir_entry(path) { + if let Ok(dir_entry) = dir_entry { + if let Ok(t) = dir_entry.file_type() { + if t.is_dir() { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let attr = file::DIRECTORY; + let new_file = + match file.open(&PathBuf::from(dir_entry.file_name()), open_mode, attr) { + Ok(x) => x, + Err(_) => continue, + }; + let _ = cascade_delete(new_file, &dir_entry.path); + } else { + let open_mode = file::MODE_READ | file::MODE_WRITE; + let attr = 0; + let new_file = + match file.open(&PathBuf::from(dir_entry.file_name()), open_mode, attr) { + Ok(x) => x, + Err(_) => continue, + }; + let _ = new_file.delete(); + } + } + } + } + + file.delete() +} + +mod uefi_fs { + use super::FileTimes; + use super::{DirEntry, FileAttr}; + use crate::default::Default; + use crate::ffi::{OsStr, OsString}; + use crate::io; + use crate::mem::MaybeUninit; + use crate::path::Path; + use crate::ptr::{addr_of, addr_of_mut, NonNull}; + use crate::sys::uefi::alloc::POOL_ALIGNMENT; + use crate::sys::uefi::common::{self, status_to_io_error, VariableBox}; + use r_efi::protocols::file; + + // Wrapper around File Protocol. Automatically closes file/directories on being dropped. + #[derive(Clone)] + pub(crate) struct FileProtocol { + inner: NonNull, + } + + impl FileProtocol { + unsafe fn new(inner: NonNull) -> FileProtocol { + FileProtocol { inner } + } + + // Can open any file as long as it is possible to convert path to EFI_DEVICE_PATH_PROTOCOL + // using `EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL.ConvertTextToDevicePath()`. + // If relative path is provided, then opens the file in the EFI_LOADED_IMAGE_DEVICE_PATH + pub(crate) fn from_path(path: &Path, open_mode: u64, attr: u64) -> io::Result { + match super::super::path::device_prefix(path.as_os_str()) { + None => { + let rootfs = Self::get_rootfs()?; + rootfs.open(path, open_mode, attr) + } + Some(prefix) => { + let vol = Self::get_volume_from_prefix(prefix)?; + let new_path = path.strip_prefix(prefix).map_err(|_| { + io::const_io_error!(io::ErrorKind::NotFound, "ill-formatted path") + })?; + vol.open(&new_path, open_mode, attr) + } + } + } + + // Open the volume on the device_handle the image was loaded from. + fn get_rootfs() -> io::Result { + use r_efi::protocols::loaded_image; + + let loaded_image_protocol = + common::get_current_handle_protocol::( + loaded_image::PROTOCOL_GUID, + ) + .ok_or(io::const_io_error!( + io::ErrorKind::Other, + "Error getting Loaded Image Protocol" + ))?; + + let device_handle = unsafe { (*loaded_image_protocol.as_ptr()).device_handle }; + let device_handle = NonNull::new(device_handle) + .ok_or(io::const_io_error!(io::ErrorKind::Other, "Error getting Device Handle"))?; + + Self::get_volume(device_handle) + } + + // Open a volume having a particular prefix. + fn get_volume_from_prefix(prefix: &OsStr) -> io::Result { + use r_efi::protocols::{device_path, simple_file_system}; + + let handles = match common::locate_handles(simple_file_system::PROTOCOL_GUID) { + Ok(x) => x, + Err(e) => return Err(e), + }; + for handle in handles { + let mut volume_device_path = + match common::open_protocol(handle, device_path::PROTOCOL_GUID) { + Ok(x) => x, + Err(_) => continue, + }; + + let volume_path = match unsafe { + super::super::path::device_path_to_path(volume_device_path.as_mut()) + } { + Ok(x) => x, + Err(_) => continue, + }; + + if volume_path.as_os_str().bytes() == prefix.bytes().split_last().unwrap().1 { + return Self::get_volume(handle); + } + } + Err(io::const_io_error!(io::ErrorKind::NotFound, "Volume Not Found")) + } + + // Open volume on device_handle using SIMPLE_FILE_SYSTEM_PROTOCOL + fn get_volume(device_handle: NonNull) -> io::Result { + use r_efi::protocols::simple_file_system; + + let simple_file_system_protocol = common::open_protocol::( + device_handle, + simple_file_system::PROTOCOL_GUID, + )?; + + let mut file_protocol: MaybeUninit<*mut file::Protocol> = MaybeUninit::uninit(); + let r = unsafe { + ((*simple_file_system_protocol.as_ptr()).open_volume)( + simple_file_system_protocol.as_ptr(), + file_protocol.as_mut_ptr(), + ) + }; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + let p = NonNull::new(unsafe { file_protocol.assume_init() }) + .ok_or(io::const_io_error!(io::ErrorKind::Other, "Null Rootfs"))?; + unsafe { Ok(Self::new(p)) } + } + } + + // Open a file from current EFI_FILE_PATH_PROTOCOL + pub(crate) fn open( + &self, + path: &Path, + open_mode: u64, + attr: u64, + ) -> io::Result { + let mut file_opened: MaybeUninit<*mut file::Protocol> = MaybeUninit::uninit(); + unsafe { + Self::open_raw( + self.inner.as_ptr(), + file_opened.as_mut_ptr(), + common::to_ffi_string(path.as_os_str()).as_mut_ptr(), + open_mode, + attr, + ) + }?; + let p = NonNull::new(unsafe { file_opened.assume_init() }) + .ok_or(io::const_io_error!(io::ErrorKind::Other, "File is Null"))?; + unsafe { Ok(FileProtocol::new(p)) } + } + + // Only Absolute seek is supported in UEFI + pub(crate) fn set_position(&self, pos: u64) -> io::Result { + unsafe { Self::set_position_raw(self.inner.as_ptr(), pos) }?; + Ok(pos) + } + + pub(crate) fn get_position(&self) -> io::Result { + let mut pos: u64 = 0; + unsafe { Self::get_position_raw(self.inner.as_ptr(), &mut pos) }?; + Ok(pos) + } + + pub(crate) fn write(&self, buf: &[u8]) -> io::Result { + let mut buffer_size = buf.len(); + unsafe { + Self::write_raw( + self.inner.as_ptr(), + &mut buffer_size, + buf.as_ptr() as *mut crate::ffi::c_void, + ) + }?; + Ok(buffer_size) + } + + pub(crate) fn read(&self, buf: &mut [T], buffer_size: &mut usize) -> io::Result<()> { + unsafe { Self::read_raw(self.inner.as_ptr(), buffer_size, buf.as_mut_ptr().cast()) } + } + + pub(crate) fn flush(&self) -> io::Result<()> { + unsafe { Self::flush_raw(self.inner.as_ptr()) } + } + + // Read a Directory. + pub(crate) fn read_dir_entry(&self, base_path: &Path) -> Option> { + let mut buf_size = 0usize; + if let Err(e) = unsafe { + Self::read_raw(self.inner.as_ptr(), &mut buf_size, crate::ptr::null_mut()) + } { + match e.kind() { + io::ErrorKind::FileTooLarge => {} + _ => return Some(Err(e)), + } + } + + if buf_size == 0 { + return None; + } + + let layout = unsafe { + crate::alloc::Layout::from_size_align_unchecked(buf_size, POOL_ALIGNMENT) + }; + let mut buf = VariableBox::::new_uninit(layout); + if let Err(e) = unsafe { + Self::read_raw(self.inner.as_ptr(), &mut buf_size, buf.as_mut_ptr().cast()) + } { + return Some(Err(e)); + } + + let mut buf = unsafe { buf.assume_init() }; + let name_bytes: usize = buf_size - crate::mem::size_of::(); + let name = common::from_ffi_string( + unsafe { (*buf.as_mut_ptr()).file_name.as_mut_ptr() }, + name_bytes, + ); + let attr = FileAttr::from(&buf); + + let path = base_path.join(&name); + Some(Ok(DirEntry { attr, name, path })) + } + + // Get current file info + pub(crate) fn get_file_info(&self) -> io::Result> { + let mut buf_size = 0usize; + match unsafe { + Self::get_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + &mut buf_size, + crate::ptr::null_mut(), + ) + } { + Ok(()) => unreachable!(), + Err(e) => match e.kind() { + io::ErrorKind::FileTooLarge => {} + _ => return Err(e), + }, + } + let layout = unsafe { + crate::alloc::Layout::from_size_align_unchecked(buf_size, POOL_ALIGNMENT) + }; + let mut buf = VariableBox::::new_uninit(layout); + match unsafe { + Self::get_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + &mut buf_size, + buf.as_mut_ptr().cast(), + ) + } { + Ok(()) => unsafe { Ok(buf.assume_init()) }, + Err(e) => Err(e), + } + } + + pub(crate) fn get_file_name(&self) -> io::Result { + let mut info = self.get_file_info()?; + let name_bytes: usize = info.layout().size() - crate::mem::size_of::(); + Ok(common::from_ffi_string( + unsafe { (*info.as_mut_ptr()).file_name.as_mut_ptr() }, + name_bytes, + )) + } + + // Set file size. Useful for truncation + pub(crate) fn set_file_size(&self, file_size: u64) -> io::Result<()> { + use r_efi::efi::Time; + + let mut old_info = self.get_file_info()?; + // Update fields with new values + unsafe { + addr_of_mut!((*old_info.as_mut_ptr()).file_size).write(file_size); + // Pass 0 for time values. That means the time stuff will not be updated. + addr_of_mut!((*old_info.as_mut_ptr()).create_time).write(Time::default()); + addr_of_mut!((*old_info.as_mut_ptr()).modification_time).write(Time::default()); + addr_of_mut!((*old_info.as_mut_ptr()).last_access_time).write(Time::default()); + } + unsafe { + Self::set_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + old_info.layout().size(), + old_info.as_mut_ptr().cast(), + ) + } + } + + // Set file attributes + pub(crate) fn set_file_attr(&self, attribute: u64) -> io::Result<()> { + use r_efi::efi::Time; + + let mut old_info = self.get_file_info()?; + + unsafe { + addr_of_mut!((*old_info.as_mut_ptr()).attribute).write(attribute); + // Pass 0 for time values. That means the time stuff will not be updated. + addr_of_mut!((*old_info.as_mut_ptr()).create_time).write(Time::default()); + addr_of_mut!((*old_info.as_mut_ptr()).modification_time).write(Time::default()); + addr_of_mut!((*old_info.as_mut_ptr()).last_access_time).write(Time::default()); + } + + unsafe { + Self::set_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + old_info.layout().size(), + old_info.as_mut_ptr().cast(), + ) + } + } + + // Change file name. It seems possible to provide a relative path as file name. Thus it + // also acts as move + pub(crate) fn set_file_name(&self, file_name: &OsStr) -> io::Result<()> { + use r_efi::efi::Time; + + let file_name = common::to_ffi_string(file_name); + let old_info = self.get_file_info()?; + let new_size = crate::mem::size_of::() + + file_name.len() * crate::mem::size_of::(); + let layout = unsafe { + crate::alloc::Layout::from_size_align_unchecked(new_size, POOL_ALIGNMENT) + }; + let mut new_info = VariableBox::::new_uninit(layout); + unsafe { + addr_of_mut!((*new_info.as_uninit_mut_ptr()).size).write(new_size as u64); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).file_size) + .write((*old_info.as_ptr()).file_size); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).physical_size) + .write((*old_info.as_ptr()).physical_size); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).create_time).write(Time::default()); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).modification_time) + .write(Time::default()); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).last_access_time) + .write(Time::default()); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).attribute) + .write((*old_info.as_ptr()).attribute); + addr_of_mut!((*new_info.as_uninit_mut_ptr()).file_name) + .cast::() + .copy_from_nonoverlapping(file_name.as_ptr(), file_name.len()) + } + + let mut new_info = unsafe { new_info.assume_init() }; + unsafe { + Self::set_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + new_info.layout().size(), + new_info.as_mut_ptr().cast(), + ) + } + } + + pub(crate) fn set_file_times(&self, file_times: FileTimes) -> io::Result<()> { + let mut old_info = self.get_file_info()?; + let last_access_time = + unsafe { addr_of!((*old_info.as_ptr()).last_access_time).read() }; + + if let Some(t) = file_times.last_accessed_time { + unsafe { + addr_of_mut!((*old_info.as_mut_ptr()).last_access_time).write( + super::super::time::uefi_time_from_duration( + t.get_duration(), + last_access_time.daylight, + last_access_time.timezone, + ), + ) + }; + } + if let Some(t) = file_times.modification_time { + unsafe { + addr_of_mut!((*old_info.as_mut_ptr()).last_access_time).write( + super::super::time::uefi_time_from_duration( + t.get_duration(), + last_access_time.daylight, + last_access_time.timezone, + ), + ) + }; + } + + unsafe { + Self::set_info_raw( + self.inner.as_ptr(), + file::INFO_ID, + old_info.layout().size(), + old_info.as_mut_ptr().cast(), + ) + } + } + + pub(crate) fn file_exists(path: &Path) -> io::Result { + match FileProtocol::from_path(path, file::MODE_READ, 0) { + Ok(_) => Ok(true), + Err(e) => { + if e.kind() == io::ErrorKind::NotFound { + Ok(false) + } else { + Err(e) + } + } + } + } + + // Delete a file. + pub(crate) fn delete(self) -> io::Result<()> { + // Deleting the file makes the pointer invalid. Thus calling drop on it later will + // cause UB + let file = crate::mem::ManuallyDrop::new(self); + unsafe { Self::delete_raw(file.inner.as_ptr()) } + } + + #[inline] + unsafe fn open_raw( + rootfs: *mut file::Protocol, + file_opened: *mut *mut file::Protocol, + path: *mut u16, + open_mode: u64, + attr: u64, + ) -> io::Result<()> { + let r = unsafe { ((*rootfs).open)(rootfs, file_opened, path, open_mode, attr) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn set_position_raw(protocol: *mut file::Protocol, pos: u64) -> io::Result<()> { + let r = unsafe { ((*protocol).set_position)(protocol, pos) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn get_position_raw(protocol: *mut file::Protocol, pos: *mut u64) -> io::Result<()> { + let r = unsafe { ((*protocol).get_position)(protocol, pos) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn flush_raw(protocol: *mut file::Protocol) -> io::Result<()> { + let r = unsafe { ((*protocol).flush)(protocol) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn write_raw( + protocol: *mut file::Protocol, + buf_size: *mut usize, + buf: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let r = unsafe { + ((*protocol).write)( + protocol, buf_size, // FIXME: Find if write can modify the buffer + buf, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn read_raw( + protocol: *mut file::Protocol, + buf_size: *mut usize, + buf: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).read)(protocol, buf_size, buf) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn get_info_raw( + protocol: *mut file::Protocol, + mut info_guid: r_efi::efi::Guid, + buf_size: &mut usize, + buf: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).get_info)(protocol, &mut info_guid, buf_size, buf) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn set_info_raw( + protocol: *mut file::Protocol, + mut info_guid: r_efi::efi::Guid, + buf_size: usize, + buf: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).set_info)(protocol, &mut info_guid, buf_size, buf) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + #[inline] + unsafe fn delete_raw(protocol: *mut file::Protocol) -> io::Result<()> { + let r = unsafe { ((*protocol).delete)(protocol) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + } + + impl Drop for FileProtocol { + #[inline] + fn drop(&mut self) { + let protocol = self.inner.as_ptr(); + // Always returns EFI_SUCCESS + let _ = unsafe { ((*protocol).close)(protocol) }; + } + } + + // Safety: No one besides us has the raw pointer (since the volume is opened by us). Also, + // there are no threads to transfer the pointer to. + unsafe impl Send for FileProtocol {} + + // Safety: There are no threads in UEFI + unsafe impl Sync for FileProtocol {} +} diff --git a/library/std/src/sys/uefi/io.rs b/library/std/src/sys/uefi/io.rs new file mode 100644 index 0000000000000..7eb3e3cf59966 --- /dev/null +++ b/library/std/src/sys/uefi/io.rs @@ -0,0 +1,86 @@ +use crate::ffi::c_void; +use crate::marker::PhantomData; +use crate::slice; + +#[derive(Copy, Clone)] +pub struct IoSlice<'a> { + vec: UefiBuf, + _p: PhantomData<&'a [u8]>, +} + +impl<'a> IoSlice<'a> { + #[inline] + pub fn new(buf: &'a [u8]) -> IoSlice<'a> { + let len = buf.len().try_into().unwrap(); + IoSlice { + vec: UefiBuf { len, buf: buf.as_ptr() as *mut u8 as *mut c_void }, + _p: PhantomData, + } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + let n_u32 = n.try_into().unwrap(); + if self.vec.len < n_u32 { + panic!("advancing IoSlice beyond its length"); + } + + unsafe { + self.vec.len -= n_u32; + self.vec.buf = self.vec.buf.add(n); + } + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) } + } +} + +pub struct IoSliceMut<'a> { + vec: UefiBuf, + _p: PhantomData<&'a mut [u8]>, +} + +impl<'a> IoSliceMut<'a> { + #[inline] + pub fn new(buf: &'a mut [u8]) -> IoSliceMut<'a> { + let len = buf.len().try_into().unwrap(); + IoSliceMut { vec: UefiBuf { len, buf: buf.as_mut_ptr().cast() }, _p: PhantomData } + } + + #[inline] + pub fn advance(&mut self, n: usize) { + let n_u32 = n.try_into().unwrap(); + if self.vec.len < n_u32 { + panic!("advancing IoSlice beyond its length"); + } + + unsafe { + self.vec.len -= n_u32; + self.vec.buf = self.vec.buf.add(n); + } + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.vec.buf as *mut u8, self.vec.len as usize) } + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { slice::from_raw_parts_mut(self.vec.buf as *mut u8, self.vec.len as usize) } + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +struct UefiBuf { + pub len: u32, + pub buf: *mut c_void, +} + +// FIXME: Maybe add std::os::io APIs from Windows. +pub fn is_terminal(_: T) -> bool { + false +} diff --git a/library/std/src/sys/uefi/mod.rs b/library/std/src/sys/uefi/mod.rs new file mode 100644 index 0000000000000..c6590e92e0421 --- /dev/null +++ b/library/std/src/sys/uefi/mod.rs @@ -0,0 +1,140 @@ +//! Platform-specific extensions to `std` for UEFI platforms. +//! +//! Provides access to platform-level information on UEFI platforms, and +//! exposes UEFI-specific functions that would otherwise be inappropriate as +//! part of the core `std` library. +//! +//! It exposes more ways to deal with platform-specific strings ([`OsStr`], +//! [`OsString`]), allows to set permissions more granularly, extract low-level +//! file descriptors from files and sockets, and has platform-specific helpers +//! for spawning processes. +//! +//! [`OsStr`]: crate::ffi::OsStr +//! [`OsString`]: crate::ffi::OsString + +#![deny(unsafe_op_in_unsafe_fn)] +pub mod alloc; +pub mod args; +#[path = "../unix/cmath.rs"] +pub mod cmath; +pub mod env; +pub mod fs; +pub mod io; +#[path = "../unsupported/locks/mod.rs"] +pub mod locks; +pub mod net; +pub mod os; +#[path = "../windows/os_str.rs"] +pub mod os_str; +pub mod path; +pub mod pipe; +pub mod process; +pub mod stdio; +pub mod thread; +#[path = "../unsupported/thread_local_key.rs"] +pub mod thread_local_key; +pub mod time; + +pub(crate) mod common; + +#[cfg(test)] +mod tests; + +use crate::io as std_io; +use crate::os::uefi; +use crate::ptr::NonNull; + +pub mod memchr { + pub use core::slice::memchr::{memchr, memrchr}; +} + +// SAFETY: must be called only once during runtime initialization. +// SAFETY: argc must be 2. +// SAFETY: argv must be &[Handle, *mut SystemTable]. +pub unsafe fn init(argc: isize, argv: *const *const u8, _sigpipe: u8) { + assert_eq!(argc, 2); + let image_handle = unsafe { NonNull::new(*argv as *mut crate::ffi::c_void).unwrap() }; + let system_table = unsafe { NonNull::new(*argv.add(1) as *mut crate::ffi::c_void).unwrap() }; + unsafe { crate::os::uefi::env::init_globals(image_handle, system_table) }; +} + +// SAFETY: must be called only once during runtime cleanup. +// NOTE: this is not guaranteed to run, for example when the program aborts. +pub unsafe fn cleanup() {} + +#[inline] +pub const fn unsupported() -> std_io::Result { + Err(unsupported_err()) +} + +#[inline] +pub const fn unsupported_err() -> std_io::Error { + std_io::const_io_error!(std_io::ErrorKind::Unsupported, "operation not supported on UEFI",) +} + +pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind { + use crate::io::ErrorKind; + use r_efi::efi::Status; + + if let Ok(code) = usize::try_from(code) { + common::status_to_io_error(Status::from_usize(code)).kind() + } else { + ErrorKind::Uncategorized + } +} + +pub fn abort_internal() -> ! { + if let (Some(boot_services), Some(handle)) = + (common::try_boot_services(), uefi::env::try_image_handle()) + { + let _ = unsafe { + ((*boot_services.as_ptr()).exit)( + handle.as_ptr(), + r_efi::efi::Status::ABORTED, + 0, + crate::ptr::null_mut(), + ) + }; + } + + // In case SystemTable and ImageHandle cannot be reached, use `core::intrinsics::abort` + core::intrinsics::abort(); +} + +// This function is needed by the panic runtime. The symbol is named in +// pre-link args for the target specification, so keep that in sync. +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn __rust_abort() { + abort_internal(); +} + +#[inline] +pub fn hashmap_random_keys() -> (u64, u64) { + unsafe { (get_random().unwrap_or(1), get_random().unwrap_or(2)) } +} + +fn get_random() -> Option { + use r_efi::protocols::rng; + + let mut buf = [0u8; 8]; + let handles = common::locate_handles(rng::PROTOCOL_GUID).ok()?; + for handle in handles { + if let Ok(protocol) = common::open_protocol::(handle, rng::PROTOCOL_GUID) { + let r = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + crate::ptr::null_mut(), + buf.len(), + buf.as_mut_ptr(), + ) + }; + if r.is_error() { + continue; + } else { + return Some(u64::from_le_bytes(buf)); + } + } + } + None +} diff --git a/library/std/src/sys/uefi/net/implementation.rs b/library/std/src/sys/uefi/net/implementation.rs new file mode 100644 index 0000000000000..b92a3e047ffc9 --- /dev/null +++ b/library/std/src/sys/uefi/net/implementation.rs @@ -0,0 +1,402 @@ +use super::tcp as uefi_tcp; +use crate::fmt; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr}; +use crate::sys::unsupported; +use crate::time::Duration; + +pub struct TcpStream { + inner: uefi_tcp::TcpProtocol, +} + +impl TcpStream { + fn new(inner: uefi_tcp::TcpProtocol) -> Self { + Self { inner } + } + + pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result { + let addr = addr?; + let inner = uefi_tcp::TcpProtocol::connect(addr)?; + Ok(Self { inner }) + } + + pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result { + let timeout = u64::try_from(timeout.as_nanos() / 100) + .map_err(|_| io::const_io_error!(io::ErrorKind::InvalidInput, "timeout is too long"))?; + let inner = uefi_tcp::TcpProtocol::connect_timeout(addr, timeout)?; + Ok(Self { inner }) + } + + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + self.inner.set_read_timeout(timeout) + } + + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + self.inner.set_write_timeout(timeout) + } + + pub fn read_timeout(&self) -> io::Result> { + self.inner.read_timeout() + } + + pub fn write_timeout(&self) -> io::Result> { + self.inner.write_timeout() + } + + pub fn peek(&self, _: &mut [u8]) -> io::Result { + unsupported() + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + self.inner.read_vectored(bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + true + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + self.inner.write_vectored(bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + true + } + + pub fn peer_addr(&self) -> io::Result { + self.inner.peer_addr() + } + + pub fn socket_addr(&self) -> io::Result { + self.inner.local_addr() + } + + pub fn shutdown(&self, how: Shutdown) -> io::Result<()> { + self.inner.shutdown(how) + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_linger(&self, _: Option) -> io::Result<()> { + unsupported() + } + + pub fn linger(&self) -> io::Result> { + unsupported() + } + + // UEFI doesn't seem to allow configure for active connections + pub fn set_nodelay(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn nodelay(&self) -> io::Result { + self.inner.nodelay() + } + + // UEFI doesn't seem to allow configure for active connections + pub fn set_ttl(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn ttl(&self) -> io::Result { + self.inner.ttl() + } + + pub fn take_error(&self) -> io::Result> { + unsupported() + } + + pub fn set_nonblocking(&self, _: bool) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for TcpStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut res = f.debug_struct("TcpStream"); + if let Ok(addr) = self.socket_addr() { + res.field("addr", &addr); + } + if let Ok(peer) = self.peer_addr() { + res.field("peer", &peer); + } + res.finish() + } +} + +pub struct TcpListener { + inner: uefi_tcp::TcpProtocol, +} + +impl TcpListener { + fn new(inner: uefi_tcp::TcpProtocol) -> Self { + Self { inner } + } + + pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result { + let addr = addr?; + Ok(Self::new(uefi_tcp::TcpProtocol::bind(addr)?)) + } + + pub fn socket_addr(&self) -> io::Result { + self.inner.local_addr() + } + + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + let (stream, socket_addr) = self.inner.accept()?; + Ok((TcpStream::new(stream), socket_addr)) + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_ttl(&self, x: u32) -> io::Result<()> { + self.inner.set_ttl(x) + } + + pub fn ttl(&self) -> io::Result { + self.inner.ttl() + } + + pub fn set_only_v6(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn only_v6(&self) -> io::Result { + Ok(false) + } + + pub fn take_error(&self) -> io::Result> { + unsupported() + } + + // Internally TCP Protocol is nonblocking + pub fn set_nonblocking(&self, _: bool) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for TcpListener { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut res = f.debug_struct("TcpListener"); + if let Ok(addr) = self.socket_addr() { + res.field("addr", &addr); + } + res.finish() + } +} + +pub struct UdpSocket {} + +impl UdpSocket { + pub fn bind(_: io::Result<&SocketAddr>) -> io::Result { + unsupported() + } + + pub fn peer_addr(&self) -> io::Result { + unsupported() + } + + pub fn socket_addr(&self) -> io::Result { + unsupported() + } + + pub fn recv_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + unsupported() + } + + pub fn peek_from(&self, _: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + unsupported() + } + + pub fn send_to(&self, _: &[u8], _: &SocketAddr) -> io::Result { + unsupported() + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_read_timeout(&self, _: Option) -> io::Result<()> { + unsupported() + } + + pub fn set_write_timeout(&self, _: Option) -> io::Result<()> { + unsupported() + } + + pub fn read_timeout(&self) -> io::Result> { + unsupported() + } + + pub fn write_timeout(&self) -> io::Result> { + unsupported() + } + + pub fn set_broadcast(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn broadcast(&self) -> io::Result { + unsupported() + } + + pub fn set_multicast_loop_v4(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn multicast_loop_v4(&self) -> io::Result { + unsupported() + } + + pub fn set_multicast_ttl_v4(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn multicast_ttl_v4(&self) -> io::Result { + unsupported() + } + + pub fn set_multicast_loop_v6(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn multicast_loop_v6(&self) -> io::Result { + unsupported() + } + + pub fn join_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unsupported() + } + + pub fn join_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn leave_multicast_v4(&self, _: &Ipv4Addr, _: &Ipv4Addr) -> io::Result<()> { + unsupported() + } + + pub fn leave_multicast_v6(&self, _: &Ipv6Addr, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn set_ttl(&self, _: u32) -> io::Result<()> { + unsupported() + } + + pub fn ttl(&self) -> io::Result { + unsupported() + } + + pub fn take_error(&self) -> io::Result> { + unsupported() + } + + pub fn set_nonblocking(&self, _: bool) -> io::Result<()> { + unsupported() + } + + pub fn recv(&self, _: &mut [u8]) -> io::Result { + unsupported() + } + + pub fn peek(&self, _: &mut [u8]) -> io::Result { + unsupported() + } + + pub fn send(&self, _: &[u8]) -> io::Result { + unsupported() + } + + pub fn connect(&self, _: io::Result<&SocketAddr>) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for UdpSocket { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + unimplemented!() + } +} + +pub struct LookupHost {} + +impl LookupHost { + pub fn port(&self) -> u16 { + unimplemented!() + } +} + +impl Iterator for LookupHost { + type Item = SocketAddr; + fn next(&mut self) -> Option { + unimplemented!() + } +} + +impl TryFrom<&str> for LookupHost { + type Error = io::Error; + + fn try_from(_v: &str) -> io::Result { + unsupported() + } +} + +impl<'a> TryFrom<(&'a str, u16)> for LookupHost { + type Error = io::Error; + + fn try_from(_v: (&'a str, u16)) -> io::Result { + unsupported() + } +} + +#[allow(nonstandard_style)] +pub mod netc { + pub const AF_INET: u8 = 0; + pub const AF_INET6: u8 = 1; + pub type sa_family_t = u8; + + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: u32, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in { + pub sin_family: sa_family_t, + pub sin_port: u16, + pub sin_addr: in_addr, + } + + #[derive(Copy, Clone)] + pub struct sockaddr_in6 { + pub sin6_family: sa_family_t, + pub sin6_port: u16, + pub sin6_addr: in6_addr, + pub sin6_flowinfo: u32, + pub sin6_scope_id: u32, + } + + #[derive(Copy, Clone)] + pub struct in6_addr { + pub s6_addr: [u8; 16], + } + + #[derive(Copy, Clone)] + pub struct sockaddr {} +} diff --git a/library/std/src/sys/uefi/net/mod.rs b/library/std/src/sys/uefi/net/mod.rs new file mode 100644 index 0000000000000..281e8b3fe97bd --- /dev/null +++ b/library/std/src/sys/uefi/net/mod.rs @@ -0,0 +1,74 @@ +mod implementation; +mod tcp; +mod tcp4; + +pub use implementation::*; + +mod uefi_service_binding { + use super::super::common::{self, status_to_io_error}; + use crate::io; + use crate::mem::MaybeUninit; + use crate::ptr::NonNull; + use r_efi::protocols::service_binding; + + #[derive(Clone, Copy)] + pub(crate) struct ServiceBinding { + service_binding_guid: r_efi::efi::Guid, + handle: NonNull, + } + + impl ServiceBinding { + #[inline] + pub(crate) fn new( + service_binding_guid: r_efi::efi::Guid, + handle: NonNull, + ) -> Self { + Self { service_binding_guid, handle } + } + + pub(crate) fn create_child(&self) -> io::Result> { + let service_binding_protocol: NonNull = + common::open_protocol(self.handle, self.service_binding_guid)?; + let mut child_handle: MaybeUninit = MaybeUninit::uninit(); + let r = unsafe { + ((*service_binding_protocol.as_ptr()).create_child)( + service_binding_protocol.as_ptr(), + child_handle.as_mut_ptr(), + ) + }; + + if r.is_error() { + Err(status_to_io_error(r)) + } else { + NonNull::new(unsafe { child_handle.assume_init() }) + .ok_or(io::const_io_error!(io::ErrorKind::Other, "null handle")) + } + } + + pub(crate) fn destroy_child( + &self, + child_handle: NonNull, + ) -> io::Result<()> { + let service_binding_protocol: NonNull = + common::open_protocol(self.handle, self.service_binding_guid)?; + let r = unsafe { + ((*service_binding_protocol.as_ptr()).destroy_child)( + service_binding_protocol.as_ptr(), + child_handle.as_ptr(), + ) + }; + + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + } +} + +#[inline] +pub(crate) fn ipv4_to_r_efi(ip: &crate::net::Ipv4Addr) -> r_efi::efi::Ipv4Address { + r_efi::efi::Ipv4Address { addr: ip.octets() } +} + +#[inline] +pub(crate) fn ipv4_from_r_efi(ip: r_efi::efi::Ipv4Address) -> crate::net::Ipv4Addr { + crate::net::Ipv4Addr::from(ip.addr) +} diff --git a/library/std/src/sys/uefi/net/tcp.rs b/library/std/src/sys/uefi/net/tcp.rs new file mode 100644 index 0000000000000..b75251409d9d7 --- /dev/null +++ b/library/std/src/sys/uefi/net/tcp.rs @@ -0,0 +1,270 @@ +//! Leaving Most of this unimplemented since TCP was mostly implemented to get testing working. +//! In the future, should probably desing networking around SIMPLE_NETWOR_PROTOCOL + +use super::{tcp4, uefi_service_binding}; +use crate::cell::RefCell; +use crate::sys::uefi::common; +use crate::time::Duration; +use crate::{ + io::{self, IoSlice, IoSliceMut}, + net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4}, + sys::unsupported, +}; +use r_efi::protocols; + +pub(crate) struct TcpProtocol { + protocol: TcpProtocolInner, + read_timeout: RefCell>, + write_timeout: RefCell>, +} + +enum TcpProtocolInner { + V4(tcp4::Tcp4Protocol), +} + +impl TcpProtocol { + fn new( + protocol: TcpProtocolInner, + read_timeout: Option, + write_timeout: Option, + ) -> Self { + Self { + protocol, + read_timeout: RefCell::new(read_timeout), + write_timeout: RefCell::new(write_timeout), + } + } + + pub(crate) fn bind(addr: &SocketAddr) -> io::Result { + match addr { + SocketAddr::V4(x) => { + let t = create_tcp4_all_handles( + false, + x, + &Ipv4Addr::new(255, 255, 255, 0), + &SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0), + )?; + Ok(Self::new(TcpProtocolInner::from(t), None, None)) + } + SocketAddr::V6(_x) => unsupported(), + } + } + + pub(crate) fn connect(addr: &SocketAddr) -> io::Result { + match addr { + SocketAddr::V4(addr) => { + let tcp4_protocol = create_tcp4_all_handles( + true, + addr, + &Ipv4Addr::new(255, 255, 255, 0), + &SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0), + )?; + tcp4_protocol.connect(None)?; + Ok(Self::new(TcpProtocolInner::from(tcp4_protocol), None, None)) + } + SocketAddr::V6(_) => unsupported(), + } + } + + pub(crate) fn connect_timeout(addr: &SocketAddr, timeout: u64) -> io::Result { + match addr { + SocketAddr::V4(addr) => { + let tcp4_protocol = create_tcp4_all_handles( + true, + addr, + &Ipv4Addr::new(255, 255, 255, 0), + &SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0), + )?; + tcp4_protocol.connect(Some(timeout))?; + Ok(Self::new(TcpProtocolInner::from(tcp4_protocol), None, None)) + } + SocketAddr::V6(_) => unsupported(), + } + } + + pub(crate) fn accept(&self) -> io::Result<(TcpProtocol, SocketAddr)> { + let stream = match &self.protocol { + TcpProtocolInner::V4(x) => TcpProtocol::new( + TcpProtocolInner::from(x.accept()?), + *self.read_timeout.borrow(), + *self.write_timeout.borrow(), + ), + }; + let socket_addr = stream.peer_addr()?; + Ok((stream, SocketAddr::from(socket_addr))) + } + + #[inline] + pub(crate) fn read(&self, buf: &mut [u8]) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => x.receive(buf, *self.read_timeout.borrow()), + } + } + + #[inline] + pub(crate) fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => x.receive_vectored(bufs, *self.read_timeout.borrow()), + } + } + + #[inline] + pub(crate) fn write(&self, buf: &[u8]) -> io::Result { + if buf.len() == 0 { + // Writing a zero-length buffer (even for a connection closed by client) seems succeed + // in Linux. Thus doing the same here. + return Ok(0); + } + match &self.protocol { + TcpProtocolInner::V4(x) => x.transmit(buf, *self.write_timeout.borrow()), + } + } + + #[inline] + pub(crate) fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => x.transmit_vectored(bufs, *self.write_timeout.borrow()), + } + } + + pub(crate) fn shutdown(&self, how: Shutdown) -> io::Result<()> { + match how { + Shutdown::Read => unsupported(), + Shutdown::Write => unsupported(), + Shutdown::Both => match &self.protocol { + TcpProtocolInner::V4(x) => x.close(false), + }, + } + } + + #[inline] + pub(crate) fn peer_addr(&self) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => Ok(x.remote_socket()?.into()), + } + } + + #[inline] + pub(crate) fn local_addr(&self) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => Ok(x.station_socket()?.into()), + } + } + + pub(crate) fn nodelay(&self) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => { + let config_data = x.get_config_data()?; + let b = unsafe { (*config_data.control_option).enable_nagle }; + Ok(bool::from(b)) + } + } + } + + pub(crate) fn ttl(&self) -> io::Result { + match &self.protocol { + TcpProtocolInner::V4(x) => { + let config_data = x.get_config_data()?; + Ok(u32::from(config_data.time_to_live)) + } + } + } + + pub(crate) fn set_ttl(&self, ttl: u32) -> io::Result<()> { + match &self.protocol { + TcpProtocolInner::V4(x) => { + let mut config_data = x.get_config_data()?; + config_data.time_to_live = ttl as u8; + x.reset()?; + x.configure(&mut config_data) + } + } + } + + pub(crate) fn read_timeout(&self) -> io::Result> { + match self.read_timeout.try_borrow() { + Ok(x) => match *x { + Some(timeout) => Ok(Some(Duration::from_nanos(timeout * 100))), + None => Ok(None), + }, + Err(_) => Err(io::const_io_error!(io::ErrorKind::ResourceBusy, "read timeout is busy")), + } + } + + pub(crate) fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + let timeout = match timeout { + None => None, + Some(x) => Some(u64::try_from(x.as_nanos() / 100).map_err(|_| { + io::const_io_error!(io::ErrorKind::InvalidInput, "Timeout too long") + })?), + }; + *self.read_timeout.borrow_mut() = timeout; + Ok(()) + } + + pub(crate) fn write_timeout(&self) -> io::Result> { + match self.write_timeout.try_borrow() { + Ok(x) => match *x { + Some(timeout) => Ok(Some(Duration::from_nanos(timeout * 100))), + None => Ok(None), + }, + Err(_) => Err(io::const_io_error!(io::ErrorKind::ResourceBusy, "read timeout is busy")), + } + } + + pub(crate) fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + let timeout = match timeout { + None => None, + Some(x) => Some(u64::try_from(x.as_nanos() / 100).map_err(|_| { + io::const_io_error!(io::ErrorKind::InvalidInput, "Timeout too long") + })?), + }; + *self.write_timeout.borrow_mut() = timeout; + Ok(()) + } +} + +impl From for TcpProtocolInner { + #[inline] + fn from(t: tcp4::Tcp4Protocol) -> Self { + TcpProtocolInner::V4(t) + } +} + +fn create_tcp4_all_handles( + active_flag: bool, + station_addr: &crate::net::SocketAddrV4, + subnet_mask: &crate::net::Ipv4Addr, + remote_addr: &crate::net::SocketAddrV4, +) -> io::Result { + let handles = common::locate_handles(protocols::tcp4::SERVICE_BINDING_PROTOCOL_GUID)?; + + // Try all handles + for handle in handles { + let service_binding = uefi_service_binding::ServiceBinding::new( + protocols::tcp4::SERVICE_BINDING_PROTOCOL_GUID, + handle, + ); + let tcp4_protocol = match tcp4::Tcp4Protocol::create(service_binding) { + Ok(x) => x, + Err(_) => { + continue; + } + }; + + match tcp4_protocol.default_config( + true, + active_flag, + station_addr, + subnet_mask, + remote_addr, + ) { + Ok(()) => return Ok(tcp4_protocol), + Err(_) => { + continue; + } + } + } + + Err(io::const_io_error!(io::ErrorKind::Other, "failed to open any EFI_TCP4_PROTOCOL")) +} diff --git a/library/std/src/sys/uefi/net/tcp4.rs b/library/std/src/sys/uefi/net/tcp4.rs new file mode 100644 index 0000000000000..9a8caf2526449 --- /dev/null +++ b/library/std/src/sys/uefi/net/tcp4.rs @@ -0,0 +1,513 @@ +use super::uefi_service_binding::ServiceBinding; +use super::{ipv4_from_r_efi, ipv4_to_r_efi}; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::mem::MaybeUninit; +use crate::net::SocketAddrV4; +use crate::ptr::{addr_of_mut, NonNull}; +use crate::sync::OnceLock; +use crate::sys::uefi::{ + alloc::POOL_ALIGNMENT, + common::{self, status_to_io_error, VariableBox}, +}; +use r_efi::efi::Status; +use r_efi::protocols::{ip4, managed_network, simple_network, tcp4}; + +const TYPE_OF_SERVICE: u8 = 8; +const TIME_TO_LIVE: u8 = 255; + +pub(crate) struct Tcp4Protocol { + protocol: NonNull, + service_binding: ServiceBinding, + child_handle: NonNull, + action_event: OnceLock, + timer_event: OnceLock, +} + +impl Tcp4Protocol { + pub(crate) fn create(service_binding: ServiceBinding) -> io::Result { + let child_handle = service_binding.create_child()?; + Self::with_child_handle(service_binding, child_handle) + } + + pub(crate) fn default_config( + &self, + use_default_address: bool, + active_flag: bool, + station_addr: &crate::net::SocketAddrV4, + subnet_mask: &crate::net::Ipv4Addr, + remote_addr: &crate::net::SocketAddrV4, + ) -> io::Result<()> { + let mut config_data = tcp4::ConfigData { + // FIXME: Check in mailing list what traffic_class should be used + type_of_service: TYPE_OF_SERVICE, + // FIXME: Check in mailing list what hop_limit should be used + time_to_live: TIME_TO_LIVE, + access_point: tcp4::AccessPoint { + use_default_address: r_efi::efi::Boolean::from(use_default_address), + station_address: ipv4_to_r_efi(station_addr.ip()), + station_port: station_addr.port(), + subnet_mask: ipv4_to_r_efi(subnet_mask), + remote_address: ipv4_to_r_efi(remote_addr.ip()), + remote_port: remote_addr.port(), + active_flag: r_efi::efi::Boolean::from(active_flag), + }, + // FIXME: Maybe provide a rust default one at some point + control_option: crate::ptr::null_mut(), + }; + self.configure(&mut config_data) + } + + pub(crate) fn configure(&self, config: &mut tcp4::ConfigData) -> io::Result<()> { + unsafe { Self::config_raw(self.protocol.as_ptr(), config) } + } + + #[inline] + pub(crate) fn reset(&self) -> io::Result<()> { + unsafe { Self::config_raw(self.protocol.as_ptr(), crate::ptr::null_mut()) } + } + + pub(crate) fn accept(&self) -> io::Result { + let accept_event = self.action_event()?; + let completion_token = + tcp4::CompletionToken { event: accept_event.as_raw_event(), status: Status::ABORTED }; + + let mut listen_token = + tcp4::ListenToken { completion_token, new_child_handle: crate::ptr::null_mut() }; + + unsafe { Self::accept_raw(self.protocol.as_ptr(), &mut listen_token) }?; + + self.wait_for_event(None)?; + + let r = listen_token.completion_token.status; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + let child_handle = NonNull::new(listen_token.new_child_handle) + .ok_or(io::const_io_error!(io::ErrorKind::Other, "null child handle"))?; + Self::with_child_handle(self.service_binding, child_handle) + } + } + + pub(crate) fn connect(&self, timeout: Option) -> io::Result<()> { + let connect_event = self.action_event()?; + let completion_token = + tcp4::CompletionToken { event: connect_event.as_raw_event(), status: Status::ABORTED }; + let mut connection_token = tcp4::ConnectionToken { completion_token }; + unsafe { Self::connect_raw(self.protocol.as_ptr(), &mut connection_token) }?; + + self.wait_for_event(timeout)?; + + let r = connection_token.completion_token.status; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + pub(crate) fn transmit(&self, buf: &[u8], timeout: Option) -> io::Result { + let buf_size = crate::cmp::min(buf.len(), u32::MAX as usize) as u32; + let transmit_event = self.action_event()?; + let completion_token = + tcp4::CompletionToken { event: transmit_event.as_raw_event(), status: Status::ABORTED }; + let fragment_table = tcp4::FragmentData { + fragment_length: buf_size, + // FIXME: Probably dangerous + fragment_buffer: buf.as_ptr() as *mut crate::ffi::c_void, + }; + + let mut transmit_data = TransmitData { + push: r_efi::efi::Boolean::TRUE, + urgent: r_efi::efi::Boolean::FALSE, + data_length: buf_size, + fragment_count: 1, + fragment_table: [fragment_table; 1], + }; + + let packet = tcp4::IoTokenPacket { + tx_data: &mut transmit_data as *mut TransmitData<1> as *mut tcp4::TransmitData, + }; + let mut transmit_token = tcp4::IoToken { completion_token, packet }; + unsafe { Self::transmit_raw(self.protocol.as_ptr(), &mut transmit_token) }?; + + self.wait_for_event(timeout)?; + + let r = transmit_token.completion_token.status; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + Ok(unsafe { (*transmit_token.packet.tx_data).data_length } as usize) + } + } + + pub(crate) fn transmit_vectored( + &self, + buf: &[IoSlice<'_>], + timeout: Option, + ) -> io::Result { + let (buf_size, fragment_tables) = { + let mut fragment_tables = Vec::::with_capacity(buf.len()); + let mut current_size = 0; + for b in buf { + if b.len() + current_size > u32::MAX as usize { + break; + } + + fragment_tables.push(tcp4::FragmentData { + // This is safe since UEFI IoSlice can only be created if buf.len() can be + // converted to u32 + fragment_length: b.len() as u32, + fragment_buffer: b.as_ptr() as *mut crate::ffi::c_void, + }); + current_size += b.len(); + } + (current_size, fragment_tables) + }; + + let transmit_event = self.action_event()?; + let completion_token = + tcp4::CompletionToken { event: transmit_event.as_raw_event(), status: Status::ABORTED }; + // At max, this will be equal to buf_size (each fragment length = 1) which is <= u32::MAX + let fragment_tables_len = fragment_tables.len(); + + let layout = unsafe { + crate::alloc::Layout::from_size_align_unchecked( + crate::mem::size_of::() + + fragment_tables_len * crate::mem::size_of::(), + POOL_ALIGNMENT, + ) + }; + let mut transmit_data = VariableBox::>::new_uninit(layout); + + // Initialize TransmitData + unsafe { + addr_of_mut!((*transmit_data.as_uninit_mut_ptr()).push) + .write(r_efi::efi::Boolean::TRUE); + addr_of_mut!((*transmit_data.as_uninit_mut_ptr()).urgent) + .write(r_efi::efi::Boolean::FALSE); + // SAFETY: buf_size is always <= u32::MAX + addr_of_mut!((*transmit_data.as_uninit_mut_ptr()).data_length).write(buf_size as u32); + addr_of_mut!((*transmit_data.as_uninit_mut_ptr()).fragment_count) + .write(fragment_tables_len as u32); + addr_of_mut!((*transmit_data.as_uninit_mut_ptr()).fragment_table) + .cast::() + .copy_from_nonoverlapping(fragment_tables.as_ptr(), fragment_tables_len); + }; + let mut transmit_data = unsafe { transmit_data.assume_init() }; + + let packet = tcp4::IoTokenPacket { tx_data: transmit_data.as_mut_ptr().cast() }; + let mut transmit_token = tcp4::IoToken { completion_token, packet }; + unsafe { Self::transmit_raw(self.protocol.as_ptr(), &mut transmit_token) }?; + + self.wait_for_event(timeout)?; + + let r = transmit_token.completion_token.status; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + Ok(unsafe { (*transmit_token.packet.tx_data).data_length } as usize) + } + } + + pub(crate) fn receive(&self, buf: &mut [u8], timeout: Option) -> io::Result { + let buf_size = crate::cmp::min(buf.len(), u32::MAX as usize) as u32; + let receive_event = self.action_event()?; + let fragment_table = tcp4::FragmentData { + fragment_length: buf_size, + fragment_buffer: buf.as_mut_ptr().cast(), + }; + + let mut receive_data = ReceiveData { + urgent_flag: r_efi::efi::Boolean::FALSE, + data_length: buf_size, + fragment_count: 1, + fragment_table: [fragment_table; 1], + }; + + let packet = tcp4::IoTokenPacket { + rx_data: &mut receive_data as *mut ReceiveData<1> as *mut tcp4::ReceiveData, + }; + let completion_token = + tcp4::CompletionToken { event: receive_event.as_raw_event(), status: Status::ABORTED }; + let mut receive_token = tcp4::IoToken { completion_token, packet }; + + if unsafe { Self::receive_raw(self.protocol.as_ptr(), &mut receive_token) }? + == r_efi::efi::Status::CONNECTION_FIN + { + return Ok(0); + } + + self.wait_for_event(timeout)?; + + let r = receive_token.completion_token.status; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + Ok(unsafe { (*receive_token.packet.rx_data).data_length } as usize) + } + } + + pub(crate) fn receive_vectored( + &self, + buf: &mut [IoSliceMut<'_>], + timeout: Option, + ) -> io::Result { + let (buf_size, fragment_tables) = { + let mut fragment_tables = Vec::::with_capacity(buf.len()); + let mut current_size = 0; + for b in buf { + if b.len() + current_size > u32::MAX as usize { + break; + } + + fragment_tables.push(tcp4::FragmentData { + // This is safe since UEFI IoSlice can only be created if buf.len() can be + // converted to u32 + fragment_length: b.len() as u32, + fragment_buffer: b.as_ptr() as *mut crate::ffi::c_void, + }); + current_size += b.len(); + } + (current_size, fragment_tables) + }; + + let receive_event = self.action_event()?; + // At max, this will be equal to buf_size (each fragment length = 1) which is <= u32::MAX + let fragment_tables_len = fragment_tables.len(); + + let layout = unsafe { + crate::alloc::Layout::from_size_align_unchecked( + crate::mem::size_of::() + + fragment_tables_len * crate::mem::size_of::(), + POOL_ALIGNMENT, + ) + }; + let mut receive_data = VariableBox::>::new_uninit(layout); + unsafe { + addr_of_mut!((*receive_data.as_uninit_mut_ptr()).urgent_flag) + .write(r_efi::efi::Boolean::FALSE); + // SAFETY: buf_size is always <= u32::MAX + addr_of_mut!((*receive_data.as_uninit_mut_ptr()).data_length).write(buf_size as u32); + addr_of_mut!((*receive_data.as_uninit_mut_ptr()).fragment_count) + .write(fragment_tables_len as u32); + addr_of_mut!((*receive_data.as_uninit_mut_ptr()).fragment_table) + .cast::() + .copy_from_nonoverlapping(fragment_tables.as_ptr(), fragment_tables_len); + } + let mut receive_data = unsafe { receive_data.assume_init() }; + + let packet = tcp4::IoTokenPacket { rx_data: receive_data.as_mut_ptr().cast() }; + let completion_token = + tcp4::CompletionToken { event: receive_event.as_raw_event(), status: Status::ABORTED }; + let mut receive_token = tcp4::IoToken { completion_token, packet }; + + if unsafe { Self::receive_raw(self.protocol.as_ptr(), &mut receive_token) }? + == r_efi::efi::Status::CONNECTION_FIN + { + return Ok(0); + } + + self.wait_for_event(timeout)?; + + let r = receive_token.completion_token.status; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + Ok(unsafe { (*receive_token.packet.rx_data).data_length } as usize) + } + } + + pub(crate) fn close(&self, abort_on_close: bool) -> io::Result<()> { + let protocol = self.protocol.as_ptr(); + let close_event = self.action_event()?; + let completion_token = + tcp4::CompletionToken { event: close_event.as_raw_event(), status: Status::ABORTED }; + let mut close_token = tcp4::CloseToken { + abort_on_close: r_efi::efi::Boolean::from(abort_on_close), + completion_token, + }; + let r = unsafe { ((*protocol).close)(protocol, &mut close_token) }; + + if r.is_error() { + return Err(status_to_io_error(r)); + } + + self.wait_for_event(None)?; + let r = close_token.completion_token.status; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + pub(crate) fn wait_for_event(&self, timeout: Option) -> io::Result<()> { + let event = self.action_event()?; + match timeout { + None => event.wait(), + Some(x) => event.wait_with_timer(self.timer_event()?, x), + } + } + + pub(crate) fn remote_socket(&self) -> io::Result { + let config_data = self.get_config_data()?; + Ok(SocketAddrV4::new( + ipv4_from_r_efi(config_data.access_point.remote_address), + config_data.access_point.remote_port, + )) + } + + pub(crate) fn station_socket(&self) -> io::Result { + let config_data = self.get_config_data()?; + Ok(SocketAddrV4::new( + ipv4_from_r_efi(config_data.access_point.station_address), + config_data.access_point.station_port, + )) + } + + fn new( + protocol: NonNull, + service_binding: ServiceBinding, + child_handle: NonNull, + ) -> Self { + Self { + protocol, + service_binding, + child_handle, + action_event: OnceLock::new(), + timer_event: OnceLock::new(), + } + } + + fn with_child_handle( + service_binding: ServiceBinding, + child_handle: NonNull, + ) -> io::Result { + let tcp4_protocol = common::open_protocol(child_handle, tcp4::PROTOCOL_GUID)?; + Ok(Self::new(tcp4_protocol, service_binding, child_handle)) + } + + pub(crate) fn get_config_data(&self) -> io::Result { + // Using MaybeUninit::uninit() generates a Page Fault Here + let mut config_data: MaybeUninit = MaybeUninit::zeroed(); + unsafe { + Self::get_mode_data_raw( + self.protocol.as_ptr(), + crate::ptr::null_mut(), + config_data.as_mut_ptr(), + crate::ptr::null_mut(), + crate::ptr::null_mut(), + crate::ptr::null_mut(), + ) + }?; + Ok(unsafe { config_data.assume_init() }) + } + + unsafe fn receive_raw( + protocol: *mut tcp4::Protocol, + token: *mut tcp4::IoToken, + ) -> io::Result { + let r = unsafe { ((*protocol).receive)(protocol, token) }; + if r.is_error() { + match r { + r_efi::efi::Status::CONNECTION_FIN => Ok(r_efi::efi::Status::CONNECTION_FIN), + _ => Err(status_to_io_error(r)), + } + } else { + Ok(r) + } + } + + unsafe fn transmit_raw( + protocol: *mut tcp4::Protocol, + token: *mut tcp4::IoToken, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).transmit)(protocol, token) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn config_raw( + protocol: *mut tcp4::Protocol, + config_data: *mut tcp4::ConfigData, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).configure)(protocol, config_data) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn accept_raw( + protocol: *mut tcp4::Protocol, + token: *mut tcp4::ListenToken, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).accept)(protocol, token) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn get_mode_data_raw( + protocol: *mut tcp4::Protocol, + tcp4_state: *mut tcp4::ConnectionState, + tcp4_config_data: *mut tcp4::ConfigData, + ip4_mode_data: *mut ip4::ModeData, + mnp_config_data: *mut managed_network::ConfigData, + snp_mode_data: *mut simple_network::Mode, + ) -> io::Result<()> { + let r = unsafe { + ((*protocol).get_mode_data)( + protocol, + tcp4_state, + tcp4_config_data, + ip4_mode_data, + mnp_config_data, + snp_mode_data, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn connect_raw( + protocol: *mut tcp4::Protocol, + token: *mut tcp4::ConnectionToken, + ) -> io::Result<()> { + let r = unsafe { ((*protocol).connect)(protocol, token) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + fn action_event(&self) -> io::Result<&common::Event> { + self.action_event.get_or_try_init(|| { + common::Event::create( + r_efi::efi::EVT_NOTIFY_WAIT, + r_efi::efi::TPL_CALLBACK, + Some(common::empty_notify), + None, + ) + }) + } + + fn timer_event(&self) -> io::Result<&common::Event> { + self.timer_event.get_or_try_init(|| common::Event::create_timer()) + } +} + +impl Drop for Tcp4Protocol { + fn drop(&mut self) { + let _ = self.close(true); + let _ = self.service_binding.destroy_child(self.child_handle); + } +} + +// Safety: No one besides us has the raw pointer (since the handle was created using the Service binding Protocol). +// Also there are no threads to transfer the pointer to. +unsafe impl Send for Tcp4Protocol {} + +// Safety: There are no threads in UEFI +unsafe impl Sync for Tcp4Protocol {} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct TransmitData { + pub push: r_efi::efi::Boolean, + pub urgent: r_efi::efi::Boolean, + pub data_length: u32, + pub fragment_count: u32, + pub fragment_table: [tcp4::FragmentData; N], +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct ReceiveData { + pub urgent_flag: r_efi::efi::Boolean, + pub data_length: u32, + pub fragment_count: u32, + pub fragment_table: [tcp4::FragmentData; N], +} diff --git a/library/std/src/sys/uefi/os.rs b/library/std/src/sys/uefi/os.rs new file mode 100644 index 0000000000000..1727cfa2bafe9 --- /dev/null +++ b/library/std/src/sys/uefi/os.rs @@ -0,0 +1,378 @@ +use super::{ + common::{self, status_to_io_error}, + unsupported, +}; +use crate::error::Error as StdError; +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::marker::PhantomData; +use crate::os::uefi; +use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; +use crate::path::{self, PathBuf}; + +// Return EFI_SUCCESS as Status +pub fn errno() -> i32 { + r_efi::efi::Status::SUCCESS.as_usize() as i32 +} + +pub fn error_string(errno: i32) -> String { + let r = r_efi::efi::Status::from_usize(errno as usize); + status_to_io_error(r).to_string() +} + +// Implemented using EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL +pub fn getcwd() -> io::Result { + let mut p = current_exe()?; + p.pop(); + Ok(p) +} + +pub fn chdir(_: &path::Path) -> io::Result<()> { + unsupported() +} + +pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { + panic!("unsupported") +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option { + self.0 + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +// This is based on windows implementation since the path variable mentioned in Section 3.6.1 +// [UEFI Shell Specification](https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf). +pub fn join_paths(paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + let mut joined = Vec::new(); + let sep = b';' as u16; + + for (i, path) in paths.enumerate() { + let path = path.as_ref(); + if i > 0 { + joined.push(sep) + } + let v = path.encode_wide().collect::>(); + if v.contains(&(b'"' as u16)) { + return Err(JoinPathsError); + } else if v.contains(&sep) { + joined.push(b'"' as u16); + joined.extend_from_slice(&v[..]); + joined.push(b'"' as u16); + } else { + joined.extend_from_slice(&v[..]); + } + } + + Ok(OsStringExt::from_wide(&joined[..])) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "path segment contains `\"`".fmt(f) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "failed to join paths" + } +} + +// Implemented using EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL +pub fn current_exe() -> io::Result { + use r_efi::efi::protocols::{device_path, loaded_image_device_path}; + + match common::get_current_handle_protocol::( + loaded_image_device_path::PROTOCOL_GUID, + ) { + Some(mut x) => unsafe { super::path::device_path_to_path(x.as_mut()) }, + None => Err(io::const_io_error!( + io::ErrorKind::Uncategorized, + "failed to acquire EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL", + )), + } +} + +// FIXME: Implement using Variable Services +pub struct Env { + last_var_name: Vec, + last_var_guid: r_efi::efi::Guid, +} + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + let mut key = self.last_var_name.clone(); + // Only Read Shell and Rust variables. UEFI variables can be random bytes of data and so + // not much point in reading anything else in Env context. + let val = match self.last_var_guid { + uefi_vars::RUST_VARIABLE_GUID => { + uefi_vars::get_variable_utf8(&mut key, uefi_vars::RUST_VARIABLE_GUID) + } + uefi_vars::SHELL_VARIABLE_GUID => { + uefi_vars::get_variable_ucs2(&mut key, self.last_var_guid) + } + _ => None, + }; + let (k, g) = + uefi_vars::get_next_variable_name(&self.last_var_name, &self.last_var_guid).ok()?; + self.last_var_guid = g; + self.last_var_name = k; + match val { + None => self.next(), + Some(x) => Some((OsString::from_wide(&key[..(key.len() - 1)]), x)), + } + } +} + +pub fn env() -> Env { + // The Guid should be ignored, so just passing anything + let (key, guid) = + uefi_vars::get_next_variable_name(&[0], &uefi_vars::RUST_VARIABLE_GUID).unwrap(); + Env { last_var_name: key, last_var_guid: guid } +} + +// Tries to get variable from bot RUST_VARIABLE_GUID and SHELL_VARIABLE_GUID. Precedence: +// RUST_VARIABLE_GUID > SHELL_VARIABLE_GUID +pub fn getenv(key: &OsStr) -> Option { + let mut k = common::to_ffi_string(key); + if let Some(x) = uefi_vars::get_variable_utf8(&mut k, uefi_vars::RUST_VARIABLE_GUID) { + Some(x) + } else { + uefi_vars::get_variable_ucs2(&mut k, uefi_vars::SHELL_VARIABLE_GUID) + } +} + +// Only possible to set variable to RUST_VARIABLE_GUID +pub fn setenv(key: &OsStr, val: &OsStr) -> io::Result<()> { + // Setting a variable with null value is same as unsetting it in UEFI + if val.is_empty() { + unsetenv(key) + } else { + unsafe { + uefi_vars::set_variable_raw( + common::to_ffi_string(key).as_mut_ptr(), + uefi_vars::RUST_VARIABLE_GUID, + val.len(), + val.bytes().as_ptr() as *mut crate::ffi::c_void, + ) + } + } +} + +pub fn unsetenv(key: &OsStr) -> io::Result<()> { + match unsafe { + uefi_vars::set_variable_raw( + common::to_ffi_string(key).as_mut_ptr(), + uefi_vars::RUST_VARIABLE_GUID, + 0, + crate::ptr::null_mut(), + ) + } { + Ok(_) => Ok(()), + Err(e) => match e.kind() { + // Its fine if the key does not exist + io::ErrorKind::NotFound => Ok(()), + _ => Err(e), + }, + } +} + +pub fn temp_dir() -> PathBuf { + panic!("no supported on this platform") +} + +pub fn home_dir() -> Option { + None +} + +pub fn exit(code: i32) -> ! { + let code = match usize::try_from(code) { + Ok(x) => r_efi::efi::Status::from_usize(x), + Err(_) => r_efi::efi::Status::ABORTED, + }; + + // First try to use EFI_BOOT_SERVICES.Exit() + if let (Some(handle), Some(boot_services)) = + (uefi::env::try_image_handle(), common::try_boot_services()) + { + let _ = unsafe { + ((*boot_services.as_ptr()).exit)(handle.as_ptr(), code, 0, crate::ptr::null_mut()) + }; + } + + // If exit is not possible, the call abort + crate::intrinsics::abort() +} + +pub fn getpid() -> u32 { + panic!("no pids on this platform") +} + +// Implement variables using Variable Services in EFI_RUNTIME_SERVICES +pub(crate) mod uefi_vars { + // It is possible to directly store and use UTF-8 data. So no need to convert to and from UCS-2 + use super::super::common::{self, status_to_io_error}; + use crate::ffi::OsString; + use crate::io; + use crate::mem::size_of; + use crate::os::uefi::ffi::OsStringExt; + + // Using Shell Variable Guid from edk2/ShellPkg + pub(crate) const SHELL_VARIABLE_GUID: r_efi::efi::Guid = r_efi::efi::Guid::from_fields( + 0x158def5a, + 0xf656, + 0x419c, + 0xb0, + 0x27, + &[0x7a, 0x31, 0x92, 0xc0, 0x79, 0xd2], + ); + + pub(crate) const RUST_VARIABLE_GUID: r_efi::efi::Guid = r_efi::efi::Guid::from_fields( + 0x49bb4029, + 0x7d2b, + 0x4bf7, + 0xa1, + 0x95, + &[0x0f, 0x18, 0xa1, 0xa8, 0x85, 0xc9], + ); + + pub(crate) fn get_variable_utf8(key: &mut [u16], guid: r_efi::efi::Guid) -> Option { + let mut buf_size = 0; + + if let Err(e) = unsafe { + get_vaiable_raw(key.as_mut_ptr(), guid, &mut buf_size, crate::ptr::null_mut()) + } { + if e.kind() != io::ErrorKind::FileTooLarge { + return None; + } + } + + let mut buf: Vec = Vec::with_capacity(buf_size); + unsafe { + get_vaiable_raw(key.as_mut_ptr(), guid, &mut buf_size, buf.as_mut_ptr().cast()).ok() + }?; + + unsafe { buf.set_len(buf_size) }; + Some(OsString::from(String::from_utf8(buf).ok()?)) + } + + pub(crate) fn get_variable_ucs2(key: &mut [u16], guid: r_efi::efi::Guid) -> Option { + let mut buf_size = 0; + + if let Err(e) = unsafe { + get_vaiable_raw(key.as_mut_ptr(), guid, &mut buf_size, crate::ptr::null_mut()) + } { + if e.kind() != io::ErrorKind::FileTooLarge { + return None; + } + } + + let mut buf: Vec = Vec::with_capacity(buf_size / size_of::()); + unsafe { + get_vaiable_raw(key.as_mut_ptr(), guid, &mut buf_size, buf.as_mut_ptr().cast()).ok() + }?; + + unsafe { buf.set_len(buf_size / size_of::()) }; + Some(OsString::from_wide(&buf)) + } + + pub(crate) fn get_next_variable_name( + last_var_name: &[u16], + last_guid: &r_efi::efi::Guid, + ) -> io::Result<(Vec, r_efi::efi::Guid)> { + #[inline] + fn buf_size(s: usize) -> usize { + s / size_of::() + } + + let mut var_name = Vec::from(last_var_name); + let mut var_size = var_name.capacity() * size_of::(); + let mut guid: r_efi::efi::Guid = *last_guid; + match unsafe { get_next_variable_raw(&mut var_size, var_name.as_mut_ptr(), &mut guid) } { + Ok(_) => { + unsafe { var_name.set_len(buf_size(var_size)) }; + return Ok((var_name, guid)); + } + Err(e) => { + if e.kind() != io::ErrorKind::FileTooLarge { + return Err(e); + } + } + } + + var_name.reserve(buf_size(var_size) - var_name.capacity() + 1); + var_size = var_name.capacity() * size_of::(); + + unsafe { get_next_variable_raw(&mut var_size, var_name.as_mut_ptr(), &mut guid) }?; + unsafe { var_name.set_len(buf_size(var_size)) }; + Ok((var_name, guid)) + } + + pub(crate) unsafe fn set_variable_raw( + variable_name: *mut u16, + mut guid: r_efi::efi::Guid, + data_size: usize, + data: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let runtime_services = common::runtime_services(); + let r = unsafe { + ((*runtime_services.as_ptr()).set_variable)( + variable_name, + &mut guid, + r_efi::efi::VARIABLE_BOOTSERVICE_ACCESS, + data_size, + data, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn get_vaiable_raw( + key: *mut u16, + mut guid: r_efi::efi::Guid, + data_size: &mut usize, + data: *mut crate::ffi::c_void, + ) -> io::Result<()> { + let runtime_services = common::runtime_services(); + let r = unsafe { + ((*runtime_services.as_ptr()).get_variable)( + key, + &mut guid, + crate::ptr::null_mut(), + data_size, + data, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn get_next_variable_raw( + variable_name_size: *mut usize, + variable_name: *mut u16, + vendor_guid: *mut r_efi::efi::Guid, + ) -> io::Result<()> { + let runtime_services = common::runtime_services(); + let r = unsafe { + ((*runtime_services.as_ptr()).get_next_variable_name)( + variable_name_size, + variable_name, + vendor_guid, + ) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } +} diff --git a/library/std/src/sys/uefi/path.rs b/library/std/src/sys/uefi/path.rs new file mode 100644 index 0000000000000..56894bfd6069d --- /dev/null +++ b/library/std/src/sys/uefi/path.rs @@ -0,0 +1,141 @@ +//! Implementation of path from UEFI. Mostly just copying Windows Implementation + +use crate::ffi::OsStr; +use crate::io; +use crate::path::{Path, PathBuf, Prefix}; +use crate::ptr::NonNull; +use crate::sys::uefi::common; + +use r_efi::protocols::{device_path, device_path_from_text, device_path_to_text}; + +pub const MAIN_SEP_STR: &str = "\\"; +pub const MAIN_SEP: char = '\\'; + +#[inline] +pub fn is_sep_byte(b: u8) -> bool { + b == b'\\' +} + +#[inline] +pub fn is_verbatim_sep(b: u8) -> bool { + b == b'\\' +} + +/// # Safety +/// +/// `bytes` must be a valid UTF-8 encoded slice +#[inline] +unsafe fn bytes_as_os_str(bytes: &[u8]) -> &OsStr { + // &OsStr is the same as &Slice for UEFI + unsafe { crate::mem::transmute(bytes) } +} + +pub fn parse_prefix(_p: &OsStr) -> Option> { + None +} + +pub(crate) fn absolute(path: &Path) -> io::Result { + match device_prefix(path.as_os_str()) { + // If no prefix, then use the current prefix + None => match crate::env::current_dir() { + Ok(x) => { + if x.as_os_str().bytes().last() == Some(&b'/') { + Ok(PathBuf::from(format!( + "{}\\{}", + x.to_string_lossy(), + path.to_string_lossy() + ))) + } else { + Ok(x.join(format!("\\{}", path.to_string_lossy()))) + } + } + Err(_) => { + Err(io::const_io_error!(io::ErrorKind::Other, "failed to convert to absolute path")) + } + }, + // If Device Path Prefix present, then path should already be absolute + Some(_) => Ok(path.to_path_buf()), + } +} + +pub(crate) fn device_prefix(p: &OsStr) -> Option<&OsStr> { + let pos = p.bytes().iter().take_while(|b| !is_sep_byte(**b)).count(); + if pos == 0 || pos == p.bytes().len() { + // Relative Path + None + } else { + if p.bytes()[pos - 1] == b'/' { + let prefix = unsafe { bytes_as_os_str(&p.bytes()[0..pos]) }; + Some(prefix) + } else { + // The between UEFI prefix and file-path seems to be `/\` + None + } + } +} + +pub(crate) fn device_path_to_path(path: &mut device_path::Protocol) -> io::Result { + use crate::alloc::{Allocator, Global, Layout}; + + let device_path_to_text_handles = common::locate_handles(device_path_to_text::PROTOCOL_GUID)?; + for handle in device_path_to_text_handles { + let protocol: NonNull = + match common::open_protocol(handle, device_path_to_text::PROTOCOL_GUID) { + Ok(x) => x, + Err(_) => continue, + }; + let path_ucs2 = unsafe { + ((*protocol.as_ptr()).convert_device_path_to_text)( + path, + r_efi::efi::Boolean::FALSE, + r_efi::efi::Boolean::FALSE, + ) + }; + let ucs2_iter = match unsafe { crate::sys_common::wstr::WStrUnits::new(path_ucs2) } { + None => break, + Some(x) => x, + }; + + let mut path: String = String::new(); + for w in ucs2_iter { + // The Returned u16 should be UCS-2 charcters. Thus it should be fine to directly + // convert to char instead of creating `crate::sys_common::wtf8::CodePoint` first + let c = char::from_u32(w.get() as u32).ok_or(io::const_io_error!( + io::ErrorKind::InvalidFilename, + "Invalid Device Path" + ))?; + path.push(c); + } + + let layout = unsafe { + Layout::from_size_align_unchecked(crate::mem::size_of::() * path.len(), 8usize) + }; + // Deallocate returned UCS-2 String + unsafe { Global.deallocate(NonNull::new_unchecked(path_ucs2 as *mut u16).cast(), layout) } + return Ok(PathBuf::from(path)); + } + Err( + io::const_io_error!(io::ErrorKind::InvalidData, "failed to convert to text representation",), + ) +} + +pub(crate) fn device_path_from_os_str(path: &OsStr) -> io::Result> { + let device_path_from_text_handles = + common::locate_handles(device_path_from_text::PROTOCOL_GUID)?; + for handle in device_path_from_text_handles { + let protocol: NonNull = + match common::open_protocol(handle, device_path_from_text::PROTOCOL_GUID) { + Ok(x) => x, + Err(_) => continue, + }; + let device_path = unsafe { + ((*protocol.as_ptr()).convert_text_to_device_path)( + common::to_ffi_string(path).as_mut_ptr(), + ) + }; + return unsafe { Ok(Box::from_raw(device_path)) }; + } + Err( + io::const_io_error!(io::ErrorKind::InvalidData, "failed to convert to text representation",), + ) +} diff --git a/library/std/src/sys/uefi/pipe.rs b/library/std/src/sys/uefi/pipe.rs new file mode 100644 index 0000000000000..4dde3f71452f5 --- /dev/null +++ b/library/std/src/sys/uefi/pipe.rs @@ -0,0 +1,281 @@ +//! An implementation of Pipes for UEFI + +use super::common; +use crate::io::{self, IoSlice, IoSliceMut}; +use crate::ptr::NonNull; + +pub struct AnonPipe { + _pipe_data: Option>, + _protocol: Option>, + handle: NonNull, +} + +unsafe impl Send for AnonPipe {} + +// Safety: There are no threads in UEFI +unsafe impl Sync for AnonPipe {} + +impl AnonPipe { + pub(crate) fn new( + pipe_data: Option>, + protocol: Option>, + handle: NonNull, + ) -> Self { + Self { _pipe_data: pipe_data, _protocol: protocol, handle } + } + + pub(crate) fn null() -> Self { + let pipe = common::ProtocolWrapper::install_protocol(uefi_pipe_protocol::Protocol::null()) + .unwrap(); + let handle = pipe.handle(); + Self::new(None, Some(pipe), handle) + } + + pub(crate) fn make_pipe() -> Self { + const MIN_BUFFER: usize = 1024; + let mut pipe_data = Box::new(uefi_pipe_protocol::Pipedata::with_capacity(MIN_BUFFER)); + let pipe = common::ProtocolWrapper::install_protocol( + uefi_pipe_protocol::Protocol::with_data(&mut pipe_data), + ) + .unwrap(); + let handle = pipe.handle(); + Self::new(Some(pipe_data), Some(pipe), handle) + } + + pub(crate) fn handle(&self) -> NonNull { + self.handle + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + let protocol = common::open_protocol::( + self.handle, + uefi_pipe_protocol::PROTOCOL_GUID, + )?; + let mut buf_size = buf.len(); + let r = unsafe { + ((*protocol.as_ptr()).read)(protocol.as_ptr(), &mut buf_size, buf.as_mut_ptr()) + }; + if r.is_error() { Err(common::status_to_io_error(r)) } else { Ok(buf_size) } + } + + pub(crate) fn read_to_end(&self, buf: &mut Vec) -> io::Result { + let protocol = common::open_protocol::( + self.handle, + uefi_pipe_protocol::PROTOCOL_GUID, + )?; + let buf_size = unsafe { ((*protocol.as_ptr()).size)(protocol.as_ptr()) }; + buf.reserve_exact(buf_size); + let mut buf_size = buf.capacity(); + let r = unsafe { + ((*protocol.as_ptr()).read)(protocol.as_ptr(), &mut buf_size, buf.as_mut_ptr()) + }; + if r.is_error() { + Err(common::status_to_io_error(r)) + } else { + unsafe { + buf.set_len(buf.len() + buf_size); + } + Ok(buf_size) + } + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|buf| self.read(buf), bufs) + } + + #[inline] + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + let protocol = common::open_protocol::( + self.handle, + uefi_pipe_protocol::PROTOCOL_GUID, + )?; + let mut buf_size = buf.len(); + let r = + unsafe { ((*protocol.as_ptr()).write)(protocol.as_ptr(), &mut buf_size, buf.as_ptr()) }; + if r.is_error() { Err(common::status_to_io_error(r)) } else { Ok(buf_size) } + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|buf| self.write(buf), bufs) + } + + #[inline] + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn diverge(&self) -> ! { + unimplemented!() + } +} + +pub fn read2(p1: AnonPipe, v1: &mut Vec, p2: AnonPipe, v2: &mut Vec) -> io::Result<()> { + p1.read_to_end(v1)?; + p2.read_to_end(v2)?; + Ok(()) +} + +pub(crate) mod uefi_pipe_protocol { + use crate::collections::VecDeque; + use crate::io; + use crate::sys::uefi::common; + use io::{Read, Write}; + use r_efi::efi::Guid; + use r_efi::{eficall, eficall_abi}; + + pub(crate) const PROTOCOL_GUID: Guid = Guid::from_fields( + 0x3c4acb49, + 0xfb4c, + 0x45fb, + 0x93, + 0xe4, + &[0x63, 0x5d, 0x71, 0x48, 0x4c, 0x0f], + ); + + // Maybe the internal data needs to be wrapped in a Mutex? + #[repr(C)] + #[derive(Debug)] + pub(crate) struct Pipedata { + data: VecDeque, + } + + impl Pipedata { + #[inline] + pub(crate) fn with_capacity(capacity: usize) -> Pipedata { + Pipedata { data: VecDeque::with_capacity(capacity) } + } + + #[inline] + unsafe fn read(data: *mut Pipedata, buf: &mut [u8]) -> io::Result { + unsafe { (*data).data.read(buf) } + } + + #[inline] + unsafe fn write(data: *mut Pipedata, buf: &[u8]) -> io::Result { + unsafe { (*data).data.write(buf) } + } + + #[inline] + unsafe fn size(data: *mut Pipedata) -> usize { + unsafe { (*data).data.len() } + } + } + + type WriteSignature = eficall! {fn(*mut Protocol, *mut usize, *const u8) -> r_efi::efi::Status}; + type ReadSignature = eficall! {fn(*mut Protocol, *mut usize, *mut u8) -> r_efi::efi::Status}; + type SizeSignature = eficall! {fn(*mut Protocol) -> usize}; + + #[repr(C)] + pub(crate) struct Protocol { + pub read: ReadSignature, + pub write: WriteSignature, + pub size: SizeSignature, + data: *mut Pipedata, + } + + impl common::Protocol for Protocol { + const PROTOCOL_GUID: Guid = PROTOCOL_GUID; + } + + impl Protocol { + #[inline] + pub(crate) fn with_data(data: &mut Pipedata) -> Self { + Self { + data, + read: pipe_protocol_read, + write: pipe_protocol_write, + size: pipe_protocol_size, + } + } + + #[inline] + pub(crate) fn null() -> Self { + Self { + data: crate::ptr::null_mut(), + read: pipe_protocol_null_read, + write: pipe_protocol_null_write, + size: pipe_protocol_null_size, + } + } + + unsafe fn read(protocol: *mut Protocol, buf: &mut [u8]) -> io::Result { + unsafe { + assert!(!(*protocol).data.is_null()); + Pipedata::read((*protocol).data, buf) + } + } + + unsafe fn write(protocol: *mut Protocol, buf: &[u8]) -> io::Result { + unsafe { + assert!(!(*protocol).data.is_null()); + Pipedata::write((*protocol).data, buf) + } + } + + unsafe fn size(protocol: *mut Protocol) -> usize { + unsafe { + assert!(!(*protocol).data.is_null()); + Pipedata::size((*protocol).data) + } + } + } + + extern "efiapi" fn pipe_protocol_read( + protocol: *mut Protocol, + buf_size: *mut usize, + buf: *mut u8, + ) -> r_efi::efi::Status { + let buffer = unsafe { crate::slice::from_raw_parts_mut(buf, buf_size.read()) }; + match unsafe { Protocol::read(protocol, buffer) } { + Ok(x) => { + unsafe { buf_size.write(x) }; + r_efi::efi::Status::SUCCESS + } + Err(_) => r_efi::efi::Status::ABORTED, + } + } + + extern "efiapi" fn pipe_protocol_write( + protocol: *mut Protocol, + buf_size: *mut usize, + buf: *const u8, + ) -> r_efi::efi::Status { + let buffer = unsafe { crate::slice::from_raw_parts(buf, buf_size.read()) }; + match unsafe { Protocol::write(protocol, buffer) } { + Ok(x) => { + unsafe { buf_size.write(x) }; + r_efi::efi::Status::SUCCESS + } + Err(_) => r_efi::efi::Status::ABORTED, + } + } + + extern "efiapi" fn pipe_protocol_size(protocol: *mut Protocol) -> usize { + unsafe { Protocol::size(protocol) } + } + + extern "efiapi" fn pipe_protocol_null_write( + _protocol: *mut Protocol, + _buf_size: *mut usize, + _buf: *const u8, + ) -> r_efi::efi::Status { + r_efi::efi::Status::SUCCESS + } + + extern "efiapi" fn pipe_protocol_null_read( + _protocol: *mut Protocol, + buf_size: *mut usize, + _buf: *mut u8, + ) -> r_efi::efi::Status { + unsafe { buf_size.write(0) }; + r_efi::efi::Status::SUCCESS + } + + extern "efiapi" fn pipe_protocol_null_size(_protocol: *mut Protocol) -> usize { + 0 + } +} diff --git a/library/std/src/sys/uefi/process.rs b/library/std/src/sys/uefi/process.rs new file mode 100644 index 0000000000000..a7ea47bd37c2c --- /dev/null +++ b/library/std/src/sys/uefi/process.rs @@ -0,0 +1,420 @@ +use crate::ffi::OsStr; +use crate::fmt; +use crate::io; +use crate::marker::PhantomData; +use crate::num::NonZeroI32; +use crate::path::Path; +use crate::ptr::NonNull; +use crate::sys::uefi::common; +use crate::sys::uefi::{common::status_to_io_error, fs::File, pipe::AnonPipe, unsupported}; +use crate::sys_common::process::{CommandEnv, CommandEnvs}; + +pub use crate::ffi::OsString as EnvKey; + +//////////////////////////////////////////////////////////////////////////////// +// Command +//////////////////////////////////////////////////////////////////////////////// + +pub struct Command { + env: CommandEnv, + program: crate::ffi::OsString, + args: crate::ffi::OsString, + stdout: Option, + stderr: Option, + stdin: Option, +} +// passed back to std::process with the pipes connected to the child, if any were requested +#[derive(Default)] +pub struct StdioPipes { + pub stdin: Option, + pub stdout: Option, + pub stderr: Option, +} + +#[derive(Clone, Copy)] +pub enum Stdio { + Inherit, + Null, + MakePipe, +} + +impl Command { + pub fn new(program: &OsStr) -> Command { + let program = super::path::absolute(&crate::path::PathBuf::from(program)).unwrap(); + Command { + env: Default::default(), + program: program.clone().into_os_string(), + args: program.into_os_string(), + stdout: None, + stderr: None, + stdin: None, + } + } + + pub fn arg(&mut self, arg: &OsStr) { + self.args.push(" "); + self.args.push(arg); + } + + pub fn env_mut(&mut self) -> &mut CommandEnv { + &mut self.env + } + + pub fn cwd(&mut self, _dir: &OsStr) {} + + pub fn stdin(&mut self, stdin: Stdio) { + self.stdin = Some(stdin) + } + + pub fn stdout(&mut self, stdout: Stdio) { + self.stdout = Some(stdout) + } + + pub fn stderr(&mut self, stderr: Stdio) { + self.stderr = Some(stderr) + } + + pub fn get_program(&self) -> &OsStr { + self.program.as_os_str() + } + + pub fn get_args(&self) -> CommandArgs<'_> { + CommandArgs { _p: PhantomData } + } + + pub fn get_envs(&self) -> CommandEnvs<'_> { + self.env.iter() + } + + pub fn get_current_dir(&self) -> Option<&Path> { + None + } + + fn setup_pipe(output: Stdio, func: F) -> io::Result<(r_efi::efi::Handle, Option)> + where + F: Fn(NonNull) -> r_efi::efi::Handle, + { + match output { + Stdio::Inherit => { + if let Some(command_protocol) = + common::get_current_handle_protocol::( + uefi_command_protocol::PROTOCOL_GUID, + ) + { + Ok((func(command_protocol), None)) + } else { + Ok((crate::ptr::null_mut(), None)) + } + } + Stdio::Null => { + let pipe = AnonPipe::null(); + Ok((pipe.handle().as_ptr(), Some(pipe))) + } + Stdio::MakePipe => { + let pipe = AnonPipe::make_pipe(); + Ok((pipe.handle().as_ptr(), Some(pipe))) + } + } + } + + pub fn spawn( + &mut self, + default: Stdio, + _needs_stdin: bool, + ) -> io::Result<(Process, StdioPipes)> { + let mut cmd = uefi_command::Command::load_image(self.program.as_os_str())?; + let mut command_protocol = uefi_command_protocol::Protocol::default(); + cmd.set_args(self.args.as_os_str())?; + + let mut stdio_pipe = StdioPipes::default(); + + (command_protocol.stdout, stdio_pipe.stdout) = + Self::setup_pipe(self.stdout.unwrap_or(default), |command_protocol| unsafe { + (*command_protocol.as_ptr()).stdout + })?; + (command_protocol.stderr, stdio_pipe.stderr) = + Self::setup_pipe(self.stderr.unwrap_or(default), |command_protocol| unsafe { + (*command_protocol.as_ptr()).stderr + })?; + (command_protocol.stdin, stdio_pipe.stdin) = + Self::setup_pipe(self.stdin.unwrap_or(default), |command_protocol| unsafe { + (*command_protocol.as_ptr()).stdin + })?; + + // Set env varibles + for (key, val) in self.env.iter() { + match val { + None => crate::env::remove_var(key), + Some(x) => crate::env::set_var(key, x), + } + } + + cmd.command_protocol = Some(common::ProtocolWrapper::install_protocol_in( + command_protocol, + cmd.handle.as_ptr(), + )?); + + // Initially thought to implement start at wait. However, it seems like everything expectes + // stdio output to be ready for reading before calling wait, which is not possible at least + // in current implementation. + // Moving this to wait breaks output + let r = cmd.start_image()?; + + // Cleanup env + for (k, _) in self.env.iter() { + let _ = super::os::unsetenv(k); + } + + let proc = Process { status: r }; + + Ok((proc, stdio_pipe)) + } +} + +impl From for Stdio { + fn from(pipe: AnonPipe) -> Stdio { + pipe.diverge() + } +} + +impl From for Stdio { + fn from(_file: File) -> Stdio { + panic!("unsupported") + } +} + +impl fmt::Debug for Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.args.fmt(f) + } +} + +#[derive(Copy, PartialEq, Eq, Clone)] +pub struct ExitStatus(r_efi::efi::Status); + +impl ExitStatus { + pub fn exit_ok(&self) -> Result<(), ExitStatusError> { + if self.0.is_error() { Err(ExitStatusError(*self)) } else { Ok(()) } + } + + pub fn code(&self) -> Option { + Some(self.0.as_usize() as i32) + } +} + +impl fmt::Debug for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&status_to_io_error(self.0), f) + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&status_to_io_error(self.0), f) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitStatusError(ExitStatus); + +impl Into for ExitStatusError { + fn into(self) -> ExitStatus { + self.0 + } +} + +impl ExitStatusError { + pub fn code(self) -> Option { + NonZeroI32::new(self.0.0.as_usize() as i32) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ExitCode(bool); + +impl ExitCode { + pub const SUCCESS: ExitCode = ExitCode(false); + pub const FAILURE: ExitCode = ExitCode(true); + + pub fn as_i32(&self) -> i32 { + self.0 as i32 + } +} + +impl From for ExitCode { + fn from(code: u8) -> Self { + match code { + 0 => Self::SUCCESS, + 1..=255 => Self::FAILURE, + } + } +} + +pub struct Process { + status: r_efi::efi::Status, +} + +impl Process { + // Process does not have an id in UEFI + pub fn id(&self) -> u32 { + unimplemented!() + } + + pub fn kill(&mut self) -> io::Result<()> { + unsupported() + } + + pub fn wait(&mut self) -> io::Result { + Ok(ExitStatus(self.status)) + } + + pub fn try_wait(&mut self) -> io::Result> { + Ok(Some(ExitStatus(self.status))) + } +} + +pub struct CommandArgs<'a> { + _p: PhantomData<&'a ()>, +} + +impl<'a> Iterator for CommandArgs<'a> { + type Item = &'a OsStr; + fn next(&mut self) -> Option<&'a OsStr> { + None + } +} + +impl<'a> ExactSizeIterator for CommandArgs<'a> {} + +impl<'a> fmt::Debug for CommandArgs<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().finish() + } +} + +mod uefi_command { + use super::super::common::{self, status_to_io_error}; + use crate::ffi::OsStr; + use crate::io; + use crate::mem::{ManuallyDrop, MaybeUninit}; + use crate::os::uefi; + use crate::ptr::{addr_of_mut, NonNull}; + use r_efi::protocols::loaded_image; + + pub(crate) struct Command { + pub handle: NonNull, + pub command_protocol: + Option>, + } + + impl Command { + pub(crate) fn load_image(p: &OsStr) -> io::Result { + let boot_services = common::boot_services(); + let system_handle = uefi::env::image_handle(); + let mut path = super::super::path::device_path_from_os_str(p)?; + let mut child_handle: MaybeUninit = MaybeUninit::uninit(); + let r = unsafe { + ((*boot_services.as_ptr()).load_image)( + r_efi::efi::Boolean::FALSE, + system_handle.as_ptr(), + path.as_mut(), + crate::ptr::null_mut(), + 0, + child_handle.as_mut_ptr(), + ) + }; + if r.is_error() { + Err(status_to_io_error(r)) + } else { + let child_handle = unsafe { child_handle.assume_init() }; + match NonNull::new(child_handle) { + None => { + Err(io::const_io_error!(io::ErrorKind::InvalidData, "null handle received")) + } + Some(x) => Ok(Self { handle: x, command_protocol: None }), + } + } + } + + pub(crate) fn start_image(&self) -> io::Result { + let boot_services = common::boot_services(); + let mut exit_data_size: MaybeUninit = MaybeUninit::uninit(); + let mut exit_data: MaybeUninit<*mut u16> = MaybeUninit::uninit(); + let r = unsafe { + ((*boot_services.as_ptr()).start_image)( + self.handle.as_ptr(), + exit_data_size.as_mut_ptr(), + exit_data.as_mut_ptr(), + ) + }; + + // Drop exitdata + unsafe { + exit_data_size.assume_init_drop(); + exit_data.assume_init_drop(); + } + + Ok(r) + } + + pub(crate) fn set_args(&self, args: &OsStr) -> io::Result<()> { + let protocol: NonNull = + common::open_protocol(self.handle, loaded_image::PROTOCOL_GUID)?; + let mut args = ManuallyDrop::new(common::to_ffi_string(args)); + let args_size = (crate::mem::size_of::() * args.len()) as u32; + unsafe { + addr_of_mut!((*protocol.as_ptr()).load_options_size).write(args_size); + addr_of_mut!((*protocol.as_ptr()).load_options).replace(args.as_mut_ptr().cast()); + }; + Ok(()) + } + } + + impl Drop for Command { + // Unload Image + fn drop(&mut self) { + let boot_services = common::boot_services(); + self.command_protocol = None; + let _ = unsafe { ((*boot_services.as_ptr()).unload_image)(self.handle.as_ptr()) }; + } + } +} + +pub(crate) mod uefi_command_protocol { + use super::super::common; + use r_efi::efi::{Guid, Handle}; + + pub(crate) const PROTOCOL_GUID: Guid = Guid::from_fields( + 0xc3cc5ede, + 0xb029, + 0x4daa, + 0xa5, + 0x5f, + &[0x93, 0xf8, 0x82, 0x5b, 0x29, 0xe7], + ); + + #[repr(C)] + pub(crate) struct Protocol { + pub stdout: Handle, + pub stderr: Handle, + pub stdin: Handle, + } + + impl Default for Protocol { + #[inline] + fn default() -> Self { + Self::new(crate::ptr::null_mut(), crate::ptr::null_mut(), crate::ptr::null_mut()) + } + } + + impl Protocol { + #[inline] + pub(crate) fn new(stdout: Handle, stderr: Handle, stdin: Handle) -> Self { + Self { stdout, stderr, stdin } + } + } + + impl common::Protocol for Protocol { + const PROTOCOL_GUID: Guid = PROTOCOL_GUID; + } +} diff --git a/library/std/src/sys/uefi/stdio.rs b/library/std/src/sys/uefi/stdio.rs new file mode 100644 index 0000000000000..0caad0f9fe933 --- /dev/null +++ b/library/std/src/sys/uefi/stdio.rs @@ -0,0 +1,272 @@ +use super::super::process::uefi_command_protocol; +use super::common::{self, status_to_io_error}; +use crate::sys::pipe; +use crate::sys_common::wtf8; +use crate::{io, os::uefi, ptr::NonNull}; +use r_efi::protocols::{simple_text_input, simple_text_output}; +use r_efi::system::BootWaitForEvent; + +pub struct Stdin(()); +pub struct Stdout(()); +pub struct Stderr(()); + +const MAX_BUFFER_SIZE: usize = 8192; + +const CR: u16 = 0x000du16; +const LF: u16 = 0x000au16; + +pub const STDIN_BUF_SIZE: usize = MAX_BUFFER_SIZE / 2 * 3; + +impl Stdin { + #[inline] + pub const fn new() -> Stdin { + Stdin(()) + } + + // Wait for key input + unsafe fn fire_wait_event( + con_in: *mut simple_text_input::Protocol, + wait_for_event: BootWaitForEvent, + ) -> io::Result<()> { + let r = unsafe { + let mut x: usize = 0; + (wait_for_event)(1, [(*con_in).wait_for_key].as_mut_ptr(), &mut x) + }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + unsafe fn read_key_stroke(con_in: *mut simple_text_input::Protocol) -> io::Result { + let mut input_key = simple_text_input::InputKey::default(); + let r = unsafe { ((*con_in).read_key_stroke)(con_in, &mut input_key) }; + + if r.is_error() { + Err(status_to_io_error(r)) + } else if input_key.scan_code != 0 { + Err(io::const_io_error!(io::ErrorKind::InvalidInput, "Invalid Input")) + } else { + Ok(input_key.unicode_char) + } + } + + unsafe fn reset_weak(con_in: *mut simple_text_input::Protocol) -> io::Result<()> { + let r = unsafe { ((*con_in).reset)(con_in, r_efi::efi::Boolean::TRUE) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(()) } + } + + // Write a single Character to Stdout + fn write_character( + con_out: *mut simple_text_output::Protocol, + character: u16, + ) -> io::Result<()> { + let mut buf: [u16; 2] = [character, 0]; + let r = unsafe { ((*con_out).output_string)(con_out, buf.as_mut_ptr()) }; + + if r.is_error() { + Err(status_to_io_error(r)) + } else if character == CR { + // Handle enter key + Self::write_character(con_out, LF) + } else { + Ok(()) + } + } +} + +impl io::Read for Stdin { + // Reads 1 UCS-2 character at a time and returns. + // FIXME: Implement buffered reading. Currently backspace and other characters are read as + // normal characters. Thus it might look like line-editing but it actually isn't + fn read(&mut self, buf: &mut [u8]) -> io::Result { + if let Some(command_protocol) = common::get_current_handle_protocol::< + uefi_command_protocol::Protocol, + >(uefi_command_protocol::PROTOCOL_GUID) + { + if let Some(pipe_protocol) = + NonNull::new(unsafe { (*command_protocol.as_ptr()).stdout }) + { + return pipe::AnonPipe::new(None, None, pipe_protocol).read(buf); + } + } + let global_system_table = uefi::env::system_table().cast(); + let con_in = get_con_in(global_system_table)?; + let con_out = get_con_out(global_system_table)?; + let wait_for_event = get_wait_for_event()?; + + if buf.len() < 3 { + return Ok(0); + } + + let ch = unsafe { + Stdin::reset_weak(con_in.as_ptr())?; + Stdin::fire_wait_event(con_in.as_ptr(), wait_for_event)?; + Stdin::read_key_stroke(con_in.as_ptr())? + }; + Stdin::write_character(con_out.as_ptr(), ch)?; + + // This should be fine since read_key_stroke returns UCS-2 Character + let ch = char::from_u32(ch as u32) + .ok_or(io::const_io_error!(io::ErrorKind::InvalidInput, "Invalid Input"))?; + let bytes_read = ch.len_utf8(); + + // Replace CR with LF + if ch == '\r' { + '\n'.encode_utf8(buf); + } else { + ch.encode_utf8(buf); + } + + Ok(bytes_read) + } +} + +impl Stdout { + #[inline] + pub const fn new() -> Stdout { + Stdout(()) + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + if let Some(command_protocol) = common::get_current_handle_protocol::< + uefi_command_protocol::Protocol, + >(uefi_command_protocol::PROTOCOL_GUID) + { + if let Some(pipe_protocol) = + NonNull::new(unsafe { (*command_protocol.as_ptr()).stdout }) + { + return pipe::AnonPipe::new(None, None, pipe_protocol).write(buf); + } + } + + let global_system_table = uefi::env::system_table().cast(); + let con_out = get_con_out(global_system_table)?; + unsafe { simple_text_output_write(con_out.as_ptr(), buf) } + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + #[inline] + pub const fn new() -> Stderr { + Stderr(()) + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + if let Some(command_protocol) = common::get_current_handle_protocol::< + uefi_command_protocol::Protocol, + >(uefi_command_protocol::PROTOCOL_GUID) + { + if let Some(pipe_protocol) = + NonNull::new(unsafe { (*command_protocol.as_ptr()).stderr }) + { + return pipe::AnonPipe::new(None, None, pipe_protocol).write(buf); + } + } + let global_system_table = uefi::env::system_table().cast(); + let std_err = get_std_err(global_system_table)?; + unsafe { simple_text_output_write(std_err.as_ptr(), buf) } + } + + #[inline] + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[inline] +pub fn is_ebadf(err: &io::Error) -> bool { + err.raw_os_error() == Some(r_efi::efi::Status::DEVICE_ERROR.as_usize() as i32) +} + +#[inline] +pub fn panic_output() -> Option { + if uefi::env::GLOBALS.is_completed() { Some(Stderr::new()) } else { None } +} + +fn utf8_to_ucs2(buf: &[u8], output: &mut [u16]) -> io::Result { + let buffer: &str = crate::str::from_utf8(buf).map_err(|_| { + io::const_io_error!(io::ErrorKind::InvalidData, "Buffer is not valid UTF-8") + })?; + let iter = wtf8::Wtf8::from_str(buffer).code_points(); + let mut count = 0; + let mut bytes_read = 0; + + for ch in iter { + // Convert LF to CRLF + if ch == wtf8::CodePoint::from_u32(LF as u32).unwrap() { + // Will need 2 characters + if count + 2 >= output.len() { + break; + } else { + output[count] = CR; + output[count + 1] = LF; + count += 2; + // UTF-8 character read is only LF + bytes_read += 1; + } + } else { + let c = ch.to_char_lossy(); + bytes_read += c.len_utf8(); + let t = c.encode_utf16(&mut output[count..]); + count += t.len(); + } + + if count + 1 >= output.len() { + break; + } + } + + output[count] = 0; + Ok(bytes_read) +} + +// Write buffer to a Device supporting EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL +unsafe fn simple_text_output_write( + protocol: *mut simple_text_output::Protocol, + buf: &[u8], +) -> io::Result { + let mut output = [0u16; MAX_BUFFER_SIZE / 2]; + let count = utf8_to_ucs2(buf, &mut output)?; + let r = unsafe { ((*protocol).output_string)(protocol, output.as_mut_ptr()) }; + if r.is_error() { Err(status_to_io_error(r)) } else { Ok(count) } +} + +// Returns error if `SystemTable->ConIn` is null. +#[inline] +fn get_con_in( + st: NonNull, +) -> io::Result> { + let con_in = unsafe { (*st.as_ptr()).con_in }; + NonNull::new(con_in).ok_or(io::const_io_error!(io::ErrorKind::NotFound, "ConIn")) +} + +#[inline] +fn get_wait_for_event() -> io::Result { + let boot_services = common::boot_services(); + Ok(unsafe { (*boot_services.as_ptr()).wait_for_event }) +} + +// Returns error if `SystemTable->ConOut` is null. +#[inline] +fn get_con_out( + st: NonNull, +) -> io::Result> { + let con_out = unsafe { (*st.as_ptr()).con_out }; + NonNull::new(con_out).ok_or(io::const_io_error!(io::ErrorKind::NotFound, "ConOut")) +} + +// Returns error if `SystemTable->StdErr` is null. +#[inline] +fn get_std_err( + st: NonNull, +) -> io::Result> { + let std_err = unsafe { (*st.as_ptr()).std_err }; + NonNull::new(std_err).ok_or(io::const_io_error!(io::ErrorKind::NotFound, "StdErr")) +} diff --git a/library/std/src/sys/uefi/tests.rs b/library/std/src/sys/uefi/tests.rs new file mode 100644 index 0000000000000..8806eda3ac0a6 --- /dev/null +++ b/library/std/src/sys/uefi/tests.rs @@ -0,0 +1,21 @@ +use super::alloc::*; + +#[test] +fn align() { + // UEFI ABI specifies that allocation alignment minimum is always 8. So this can be + // statically verified. + assert_eq!(POOL_ALIGNMENT, 8); + + // Loop over allocation-request sizes from 0-256 and alignments from 1-128, and verify + // that in case of overalignment there is at least space for one additional pointer to + // store in the allocation. + for i in 0..256 { + for j in &[1, 2, 4, 8, 16, 32, 64, 128] { + if *j <= 8 { + assert_eq!(align_size(i, *j), i); + } else { + assert!(align_size(i, *j) > i + std::mem::size_of::<*mut ()>()); + } + } + } +} diff --git a/library/std/src/sys/uefi/thread.rs b/library/std/src/sys/uefi/thread.rs new file mode 100644 index 0000000000000..ec31d35a5a899 --- /dev/null +++ b/library/std/src/sys/uefi/thread.rs @@ -0,0 +1,51 @@ +use super::unsupported; +use crate::ffi::CStr; +use crate::io; +use crate::num::NonZeroUsize; +use crate::sys::uefi::common; +use crate::time::Duration; + +pub struct Thread(()); + +pub const DEFAULT_MIN_STACK_SIZE: usize = 4096; + +impl Thread { + // unsafe: see thread::Builder::spawn_unchecked for safety requirements + pub unsafe fn new(_stack: usize, _p: Box) -> io::Result { + unsupported() + } + + pub fn yield_now() { + // do nothing + } + + pub fn set_name(_name: &CStr) { + // nope + } + + pub fn sleep(dur: Duration) { + let boot_services = common::boot_services(); + let _ = unsafe { ((*boot_services.as_ptr()).stall)(dur.as_micros() as usize) }; + } + + pub fn join(self) { + self.0 + } +} + +#[inline] +pub fn available_parallelism() -> io::Result { + // UEFI is single threaded + unsafe { Ok(NonZeroUsize::new_unchecked(1)) } +} + +// FIXME: Should be possible to implement. see https://edk2-docs.gitbook.io/a-tour-beyond-bios-mitigate-buffer-overflow-in-ue/additional_overflow_detection/stack_overflow_detection +pub mod guard { + pub type Guard = !; + pub unsafe fn current() -> Option { + None + } + pub unsafe fn init() -> Option { + None + } +} diff --git a/library/std/src/sys/uefi/time.rs b/library/std/src/sys/uefi/time.rs new file mode 100644 index 0000000000000..c28ffd2778b0f --- /dev/null +++ b/library/std/src/sys/uefi/time.rs @@ -0,0 +1,245 @@ +use super::common; +use crate::mem::MaybeUninit; +use crate::ptr::NonNull; +use crate::sync::atomic::{AtomicPtr, Ordering}; +use crate::sys_common::mul_div_u64; +use crate::time::Duration; + +use r_efi::protocols::timestamp; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct Instant(Duration); + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SystemTime(Duration); + +pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::ZERO); + +const NS_PER_SEC: u64 = 1_000_000_000; +const SEC_IN_MIN: u64 = 60; +const SEC_IN_HOUR: u64 = SEC_IN_MIN * 60; +const SEC_IN_DAY: u64 = SEC_IN_HOUR * 24; +const SEC_IN_YEAR: u64 = SEC_IN_DAY * 365; + +impl Instant { + pub fn now() -> Instant { + // First try using `EFI_TIMESTAMP_PROTOCOL` if present + if let Some(x) = get_timestamp_protocol() { + return x; + } + + // Try using raw CPU Registers + // Currently only implemeted for x86_64 using CPUID (0x15) and TSC register + #[cfg(target_arch = "x86_64")] + if let Some(ns) = get_timestamp() { + return Instant(Duration::from_nanos(ns)); + } + + let runtime_services = common::runtime_services(); + // Finally just use `EFI_RUNTIME_SERVICES.GetTime()` + let mut t = r_efi::efi::Time::default(); + let r = unsafe { ((*runtime_services.as_ptr()).get_time)(&mut t, crate::ptr::null_mut()) }; + + if !r.is_error() { + return Instant(uefi_time_to_duration(t)); + } + + // Panic if nothing works + panic!("Failed to create Instant") + } + + #[inline] + pub fn checked_sub_instant(&self, other: &Instant) -> Option { + self.0.checked_sub(other.0) + } + + #[inline] + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_add(*other)?)) + } + + #[inline] + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(Instant(self.0.checked_sub(*other)?)) + } +} + +// Using Unix representation of Time. +impl SystemTime { + pub fn now() -> SystemTime { + let runtime_services = common::runtime_services(); + let mut t = r_efi::efi::Time::default(); + let r = unsafe { ((*runtime_services.as_ptr()).get_time)(&mut t, crate::ptr::null_mut()) }; + + if !r.is_error() { + return SystemTime::from(t); + } + + panic!("Failed to create SystemTime") + } + + #[inline] + pub fn sub_time(&self, other: &SystemTime) -> Result { + self.0.checked_sub(other.0).ok_or_else(|| other.0 - self.0) + } + + #[inline] + pub fn checked_add_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_add(*other)?)) + } + + #[inline] + pub fn checked_sub_duration(&self, other: &Duration) -> Option { + Some(SystemTime(self.0.checked_sub(*other)?)) + } + + pub(crate) fn get_duration(&self) -> Duration { + self.0 + } +} + +impl From for SystemTime { + #[inline] + fn from(t: r_efi::system::Time) -> Self { + SystemTime(uefi_time_to_duration(t)) + } +} + +// FIXME: Don't know how to use Daylight Saving thing +fn uefi_time_to_duration(t: r_efi::system::Time) -> Duration { + assert!(t.month <= 12); + assert!(t.month != 0); + + const MONTH_DAYS: [u64; 12] = [0, 31, 59, 90, 120, 151, 181, 211, 242, 272, 303, 333]; + + let localtime_epoch: u64 = u64::from(t.year - 1970) * SEC_IN_YEAR + + u64::from((t.year - 1968) / 4) * SEC_IN_DAY + + MONTH_DAYS[usize::from(t.month - 1)] * SEC_IN_DAY + + u64::from(t.day - 1) * SEC_IN_DAY + + u64::from(t.hour) * SEC_IN_HOUR + + u64::from(t.minute) * SEC_IN_MIN + + u64::from(t.second); + let timezone_epoch: i64 = i64::from(t.timezone) * (SEC_IN_MIN as i64); + let utc_epoch: u64 = ((localtime_epoch as i64) + timezone_epoch) as u64; + + Duration::new(utc_epoch, t.nanosecond) +} + +// This algorithm is taken from: http://howardhinnant.github.io/date_algorithms.html +pub(crate) fn uefi_time_from_duration( + dur: Duration, + daylight: u8, + timezone: i16, +) -> r_efi::system::Time { + let secs = dur.as_secs(); + + let days = secs / SEC_IN_DAY; + let remaining_secs = secs % SEC_IN_DAY; + + let z = days + 719468; + let era = z / 146097; + let doe = z - (era * 146097); + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + let mut y = yoe + era * 400; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let d = doy - (153 * mp + 2) / 5 + 1; + let m = if mp < 10 { mp + 3 } else { mp - 9 }; + + if m <= 2 { + y += 1; + } + + r_efi::system::Time { + year: y as u16, + month: m as u8, + day: d as u8, + hour: (remaining_secs / SEC_IN_HOUR) as u8, + minute: ((remaining_secs % SEC_IN_HOUR) / SEC_IN_MIN) as u8, + second: ((remaining_secs % SEC_IN_HOUR) % SEC_IN_MIN) as u8, + pad1: 0, + nanosecond: dur.subsec_nanos(), + timezone, + daylight, + pad2: 0, + } +} + +// Returns the Frequency in Mhz +// Mostly based on [`edk2/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c`](https://github.com/tianocore/edk2/blob/master/UefiCpuPkg/Library/CpuTimerLib/CpuTimerLib.c) +// Currently implemented only for x86_64 but can be extended for other arch if they ever support +// std. +#[cfg(target_arch = "x86_64")] +fn frequency() -> Option { + use crate::sync::atomic::{AtomicU64, Ordering}; + + static FREQUENCY: AtomicU64 = AtomicU64::new(0); + + let cached = FREQUENCY.load(Ordering::Relaxed); + if cached != 0 { + return Some(cached); + } + + if crate::arch::x86_64::has_cpuid() { + let cpuid = unsafe { crate::arch::x86_64::__cpuid(0x15) }; + + if cpuid.eax == 0 || cpuid.ebx == 0 || cpuid.ecx == 0 { + return None; + } + + let freq = mul_div_u64(cpuid.ecx as u64, cpuid.ebx as u64, cpuid.eax as u64); + FREQUENCY.store(freq, Ordering::Relaxed); + return Some(freq); + } + + None +} + +// Currently implemented only for x86_64 but can be extended for other arch if they ever support +// std. +#[cfg(target_arch = "x86_64")] +fn get_timestamp() -> Option { + let freq = frequency()?; + let ts = unsafe { crate::arch::x86_64::_rdtsc() }; + let ns = mul_div_u64(ts, 1000, freq); + Some(ns) +} + +// This function is not tested since OVMF does not contain `EFI_TIMESTAMP_PROTOCOL` +fn get_timestamp_protocol() -> Option { + fn try_handle(handle: NonNull) -> Option { + let protocol: NonNull = + common::open_protocol(handle, timestamp::PROTOCOL_GUID).ok()?; + let mut properties: MaybeUninit = MaybeUninit::uninit(); + let r = unsafe { ((*protocol.as_ptr()).get_properties)(properties.as_mut_ptr()) }; + if r.is_error() { + None + } else { + let properties = unsafe { properties.assume_init() }; + let ts = unsafe { ((*protocol.as_ptr()).get_timestamp)() }; + let frequency = properties.frequency; + let ns = mul_div_u64(ts, NS_PER_SEC, frequency); + Some(ns) + } + } + + static LAST_VALID_HANDLE: AtomicPtr = + AtomicPtr::new(crate::ptr::null_mut()); + + if let Some(handle) = NonNull::new(LAST_VALID_HANDLE.load(Ordering::Acquire)) { + if let Some(ns) = try_handle(handle) { + return Some(Instant(Duration::from_nanos(ns))); + } + } + + if let Ok(handles) = common::locate_handles(timestamp::PROTOCOL_GUID) { + for handle in handles { + if let Some(ns) = try_handle(handle) { + LAST_VALID_HANDLE.store(handle.as_ptr(), Ordering::Release); + return Some(Instant(Duration::from_nanos(ns))); + } + } + } + + None +} diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs index b1987aa0f6286..d01b5ec0f5795 100644 --- a/library/std/src/sys_common/mod.rs +++ b/library/std/src/sys_common/mod.rs @@ -46,6 +46,7 @@ cfg_if::cfg_if! { cfg_if::cfg_if! { if #[cfg(any(target_os = "l4re", target_os = "hermit", + target_os = "uefi", feature = "restricted-std", all(target_family = "wasm", not(target_os = "emscripten")), all(target_vendor = "fortanix", target_env = "sgx")))] { diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index 5d267891bb0ed..8e0d78a2ccebd 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -194,7 +194,7 @@ macro_rules! __thread_local_inner { // // FIXME(#84224) this should come after the `target_thread_local` // block. - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] + #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] { static mut VAL: $t = INIT_EXPR; unsafe { $crate::option::Option::Some(&VAL) } @@ -204,6 +204,7 @@ macro_rules! __thread_local_inner { #[cfg(all( target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics"))), + not(target_os = "uefi") ))] { #[thread_local] @@ -260,6 +261,7 @@ macro_rules! __thread_local_inner { #[cfg(all( not(target_thread_local), not(all(target_family = "wasm", not(target_feature = "atomics"))), + not(target_os = "uefi") ))] { #[inline] @@ -319,7 +321,7 @@ macro_rules! __thread_local_inner { unsafe fn __getit( init: $crate::option::Option<&mut $crate::option::Option<$t>>, ) -> $crate::option::Option<&'static $t> { - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] + #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = $crate::thread::__StaticLocalKeyInner::new(); @@ -327,6 +329,7 @@ macro_rules! __thread_local_inner { #[cfg(all( target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics"))), + not(target_os = "uefi") ))] static __KEY: $crate::thread::__FastLocalKeyInner<$t> = $crate::thread::__FastLocalKeyInner::new(); @@ -334,6 +337,7 @@ macro_rules! __thread_local_inner { #[cfg(all( not(target_thread_local), not(all(target_family = "wasm", not(target_feature = "atomics"))), + not(target_os = "uefi") ))] static __KEY: $crate::thread::__OsLocalKeyInner<$t> = $crate::thread::__OsLocalKeyInner::new(); @@ -861,7 +865,7 @@ mod lazy { /// On some targets like wasm there's no threads, so no need to generate /// thread locals and we can instead just use plain statics! #[doc(hidden)] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] +#[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] pub mod statik { use super::lazy::LazyKeyInner; use crate::fmt; diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 34bdb8bd4612e..58feac59b8ab0 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -228,7 +228,7 @@ pub use realstd::thread::__FastLocalKeyInner; pub use self::local::os::Key as __OsLocalKeyInner; #[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] +#[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] #[doc(hidden)] pub use self::local::statik::Key as __StaticLocalKeyInner; diff --git a/library/std/src/time.rs b/library/std/src/time.rs index ecd06ebf743ab..3f7fe889eef00 100644 --- a/library/std/src/time.rs +++ b/library/std/src/time.rs @@ -114,6 +114,7 @@ pub use core::time::TryFromFloatSecsError; /// | SOLID | `get_tim` | /// | WASI | [__wasi_clock_time_get (Monotonic Clock)] | /// | Windows | [QueryPerformanceCounter] | +/// | UEFI | EFI_TIMESTAMP_PROTOCOL->GetTimestamp() | /// /// [currently]: crate::io#platform-specific-behavior /// [QueryPerformanceCounter]: https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter @@ -218,6 +219,7 @@ pub struct Instant(time::Instant); /// | SOLID | `SOLID_RTC_ReadTime` | /// | WASI | [__wasi_clock_time_get (Realtime Clock)] | /// | Windows | [GetSystemTimePreciseAsFileTime] / [GetSystemTimeAsFileTime] | +/// | UEFI | EFI_RUNTIME_SERVICES->GetTime() | /// /// [currently]: crate::io#platform-specific-behavior /// [`insecure_time` usercall]: https://edp.fortanix.com/docs/api/fortanix_sgx_abi/struct.Usercalls.html#method.insecure_time diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index babf09d2b9334..fb0e2856a62d7 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -449,11 +449,7 @@ pub struct Target { impl Target { pub fn from_triple(triple: &str) -> Self { let mut target: Self = Default::default(); - if triple.contains("-none") - || triple.contains("nvptx") - || triple.contains("switch") - || triple.contains("-uefi") - { + if triple.contains("-none") || triple.contains("nvptx") || triple.contains("switch") { target.no_std = true; } target diff --git a/src/bootstrap/configure.py b/src/bootstrap/configure.py index 6b139decb5551..e16d7e56a86dd 100755 --- a/src/bootstrap/configure.py +++ b/src/bootstrap/configure.py @@ -146,6 +146,10 @@ def v(*args): "rootfs in qemu testing, you probably don't want to use this") v("qemu-riscv64-rootfs", "target.riscv64gc-unknown-linux-gnu.qemu-rootfs", "rootfs in qemu testing, you probably don't want to use this") +v("qemu-x86_64-uefi-rootfs", "target.x86_64-unknown-uefi.qemu-rootfs", + "rootfs in qemu testing, you probably don't want to use this") +v("qemu-i686-uefi-rootfs", "target.i686-unknown-uefi.qemu-rootfs", + "rootfs in qemu testing, you probably don't want to use this") v("experimental-targets", "llvm.experimental-targets", "experimental LLVM targets to build") v("release-channel", "rust.channel", "the name of the release channel to build") diff --git a/src/ci/docker/host-x86_64/x86_64-uefi/Dockerfile b/src/ci/docker/host-x86_64/x86_64-uefi/Dockerfile new file mode 100644 index 0000000000000..c2b114fb3aec4 --- /dev/null +++ b/src/ci/docker/host-x86_64/x86_64-uefi/Dockerfile @@ -0,0 +1,51 @@ +FROM fedora:37 + +RUN dnf update -y && dnf install -y \ + bc \ + bzip2 \ + ca-certificates \ + cmake \ + cpio \ + curl \ + file \ + gcc \ + g++ \ + git \ + make \ + ninja-build \ + python3 \ + qemu \ + edk2-ovmf \ + mtools \ + xz \ + libstdc++-static \ + llvm-devel + +ENV ARCH=x86_64 + +# Create rootfs +WORKDIR /tmp +RUN mkdir rootfs + +# Copy over startup script +RUN echo "@echo -off\n" \ + "fs0:\n" \ + "testd --sequential" > rootfs/startup.nsh + +# This is here in case we want to use some other version/source for OVMF +RUN mkdir OVMF +RUN cp /usr/share/OVMF/* OVMF + +# TODO: What is this?! +# Source of the file: https://github.com/vfdev-5/qemu-rpi2-vexpress/raw/master/vexpress-v2p-ca15-tc1.dtb +RUN curl -O https://ci-mirrors.rust-lang.org/rustc/vexpress-v2p-ca15-tc1.dtb + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +ENV RUST_TEST_THREADS=1 +ENV DEPLOY=1 +ENV RUST_CONFIGURE_ARGS="${RUST_CONFIGURE_ARGS} --qemu-x86_64-uefi-rootfs=/tmp/rootfs --enable-lld --enable-ninja --set target.x86_64-unknown-uefi.linker=rust-lld --set llvm.download-ci-llvm=false" +ENV SCRIPT python3 ../x.py --stage 2 test --host='' --target x86_64-unknown-uefi --warnings warn -v + +ENV NO_CHANGE_USER=1 diff --git a/src/doc/rustc/src/platform-support/unknown-uefi.md b/src/doc/rustc/src/platform-support/unknown-uefi.md index e2bdf73a92990..20947036baab8 100644 --- a/src/doc/rustc/src/platform-support/unknown-uefi.md +++ b/src/doc/rustc/src/platform-support/unknown-uefi.md @@ -19,8 +19,8 @@ Available targets: ## Requirements All UEFI targets can be used as `no-std` environments via cross-compilation. -Support for `std` is missing, but actively worked on. `alloc` is supported if -an allocator is provided by the user. No host tools are supported. +Support for `std` is present, but incomplete and extreamly new. `alloc` is supported if +an allocator is provided by the user or if using std. No host tools are supported. The UEFI environment resembles the environment for Microsoft Windows, with some minor differences. Therefore, cross-compiling for UEFI works with the same @@ -175,7 +175,6 @@ pub extern "C" fn main(_h: *mut core::ffi::c_void, _st: *mut core::ffi::c_void) ``` ## Example: Hello World - This is an example UEFI application that prints "Hello World!", then waits for key input before it exits. It serves as base example how to write UEFI applications without any helper modules other than the standalone UEFI protocol @@ -230,3 +229,136 @@ pub extern "C" fn main(_h: efi::Handle, st: *mut efi::SystemTable) -> efi::Statu efi::Status::SUCCESS } ``` + +## Rust std for UEFI +This section contains information on how to use std on UEFI. It will +continuously evolve, at least untill everything is merged upstream. + +### Build std +The building std part is pretty much the same as the official [docs](https://rustc-dev-guide.rust-lang.org/getting-started.html). +The linker that should be used is `rust-lld`. Here is a sample `config.toml`: +```toml +[rust] +lld = true + +[target.x86_64-unknown-uefi] +linker = "rust-lld" +``` +Then just build using `x.py`: +```sh +./x.py build --target x86_64-unknown-uefi +``` + +### Std Requirements +The current std has a few basic requirements to function: +1. Memory Allocation Services (`EFI_BOOT_SERVICES.AllocatePool()` and + `EFI_BOOT_SERVICES.FreePool()`) are available. +If the above requirement is satisfied, the Rust code will reach `main`. +Now we will discuss what the different modules of std use in UEFI. + +#### alloc +- Implemented using `EFI_BOOT_SERVICES.AllocatePool()` and `EFI_BOOT_SERVICES.FreePool()`. +- Passes all the tests. +- Some Quirks: + - Currently uses `EfiLoaderData` as the `EFI_ALLOCATE_POOL->PoolType`. +#### args +- Implemented using `EFI_LOADED_IMAGE_PROTOCOL`. +#### cmath +- Provided by compiler-builtins. +#### env +- Just some global consants. +#### fs +- Uses `EFI_SIMPLE_FILE SYSTEM_PROTOCOL` and `EFI_FILE_PROTOCOL`. +- Any path that is valid for `EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL.ConvertTextToDevicePath()` can be used. In case of path without prefix, it assumes the prefix of the volume the image was loaded from. +- Unsupported: These functions simply return `Result::Err`. + - `File::duplicate` + - `readlink` + - `symlink` + - `link` + - `canonicalize` +#### locks +- Uses `unsupported/locks`. +- They should work for a platform without threads according to docs. +#### net +- This protocol was mostly implemented for running Tests using `remote-test-server`. Thus, it is kind of incomplete. +- Only works for IPv4. +- Uses `EFI_TCP4_PROTOCOL`. +- Might be best to not use this outside of tests. +#### os +- `current_exe` uses `EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL` and `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText()`. +- `getenv` uses `EFI_RUNTIME_SERVICES->GetVariable`. +- Unsupported: These functions simply return `Result::Err`. + - `chdir` + - `join_paths` +- Unimplemented: These functions panic. + - `split_paths` + - `env` +#### os_str +- Uses WTF-8 from windows. +#### path +- Uses `EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL` and `EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.ConvertDevicePathToText()` +#### pipe +- It uses a custom protocol. Works well but isn't well tested. +#### process +- This protocol was mostly implemented for running Tests using `remote-test-server`. Thus, it is kind of incomplete. +- Any path that is valid for `EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL.ConvertTextToDevicePath()` can be used. In case of path without prefix, it assumes the prefix of the volume the image was loaded from. +- Has a few quirks + - `Command::spawn` is blocking. It calls, `EFI_BOOT_SERVICES.LoadImage()` and `EFI_BOOT_SERVICES.StartImage()`. + - `Process::drop` calls `EFI_BOOT_SERVICES.UnloadImage()`. +#### stdio +- Uses `EFI_SYSTEM_TABLE->ConIn`, `EFI_SYSTEM_TABLE->ConOut` and `EFI_SYSTEM_TABLE->StdErr`. +- Has a few quirks + - Changes `LF` to `CRLF` when printing to Stdout and Stderr. + - Reading from Stdin is not buffered. +#### thread +- Only `thread::sleep` implemented using `EFI_BOOT_SERVICES.Stall()` +#### thread_local_key +- Unimplemented for UEFI. +- `panics` +#### time +- Completely implemented +- Uses `EFI_RUNTIME_SERVICES->GetTime()` for SystemTime. +- Uses `EFI_TIMESTAMP_PROTOCOL` or TSC registor or `EFI_RUNTIME_SERVICES->GetTime()` in that order of availability. +- `panics` if used `SystemTime` or `Instant` are called in environments where `GetTime()` is not available. + +### Example: With std +The following code is a valid UEFI application returning immediately upon +execution with an exit code of 0. In case of panic, `panic=abort` is used. This +will call `EFI_BOOT_SERVICES.Exit()` with status `EFI_ABORTED`. + +Currently, the std support has not been merged upstream. Hence, std must be +built from the source at [`tiano/rust`](https://github.com/tianocore/rust/tree/uefi-master) from the `uefi-master` branch. + +This example can be compiled as binary crate via `cargo` using the toolchain +compiled from the above source (named custom): + +```sh +cargo +custom build --target x86_64-unknown-uefi +``` + +```rust,ignore(platform-specific) +pub fn main() {} +``` + +## Example: Hello World With std +The following code is a valid UEFI application showing stdio in UEFI. It also +uses `alloc` type `String`. + +Currently, the std support has not been merged upstream. Hence, std must be +built from the source at [`tiano/rust`](https://github.com/tianocore/rust/tree/uefi-master) from the `uefi-master` branch. + +This example can be compiled as binary crate via `cargo` using the toolchain +compiled from the above source (named custom): + +```sh +cargo +custom build --target x86_64-unknown-uefi +``` + +```rust,ignore(platform-specific) +pub fn main() { + println!("Enter Name: "); + let mut s = String::new(); + std::io::stdin().read_line(&mut s).expect("Did not enter a correct string"); + println!("Hello World from {}", s); +} +``` diff --git a/src/test/ui/format-no-std.rs b/src/test/ui/format-no-std.rs index c9b7651bfda05..c347875ae4c50 100644 --- a/src/test/ui/format-no-std.rs +++ b/src/test/ui/format-no-std.rs @@ -1,5 +1,7 @@ // run-pass // ignore-emscripten no no_std executables +// ignore-uefi allocation and other std functionality is intialized in `sys::init`. This test +// causes CPU Exception. #![feature(lang_items, start)] #![no_std] diff --git a/src/test/ui/intrinsics/intrinsic-alignment.rs b/src/test/ui/intrinsics/intrinsic-alignment.rs index c8b1ff1dbce3b..f743d6e06391f 100644 --- a/src/test/ui/intrinsics/intrinsic-alignment.rs +++ b/src/test/ui/intrinsics/intrinsic-alignment.rs @@ -52,7 +52,7 @@ mod m { } } -#[cfg(target_os = "windows")] +#[cfg(any(target_os = "windows", target_os = "uefi"))] mod m { pub fn main() { unsafe { diff --git a/src/test/ui/lint/lint-ctypes-fn.rs b/src/test/ui/lint/lint-ctypes-fn.rs index d3b36a9d59c70..4fafbffb0d0ff 100644 --- a/src/test/ui/lint/lint-ctypes-fn.rs +++ b/src/test/ui/lint/lint-ctypes-fn.rs @@ -164,10 +164,10 @@ pub extern "C" fn good17(p: TransparentCustomZst) { } #[allow(improper_ctypes_definitions)] pub extern "C" fn good18(_: &String) { } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", target_os = "uefi")))] pub extern "C" fn good1(size: *const libc::c_int) { } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", target_os = "uefi")))] pub extern "C" fn good2(size: *const libc::c_uint) { } pub extern "C" fn unused_generic1(size: *const Foo) { } diff --git a/src/test/ui/lint/lint-ctypes.rs b/src/test/ui/lint/lint-ctypes.rs index 9165e14b7ffe3..025e359a6c9dc 100644 --- a/src/test/ui/lint/lint-ctypes.rs +++ b/src/test/ui/lint/lint-ctypes.rs @@ -108,7 +108,7 @@ extern "C" { pub fn good19(_: &String); } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(any(target_arch = "wasm32", target_os = "uefi")))] extern "C" { pub fn good1(size: *const libc::c_int); pub fn good2(size: *const libc::c_uint); diff --git a/src/test/ui/process/issue-20091.rs b/src/test/ui/process/issue-20091.rs index 86cc79d6b4244..b89d4ae1272d6 100644 --- a/src/test/ui/process/issue-20091.rs +++ b/src/test/ui/process/issue-20091.rs @@ -20,5 +20,5 @@ fn main() { } } -#[cfg(windows)] +#[cfg(any(windows, target_os = "uefi"))] fn main() {} diff --git a/src/test/ui/process/try-wait.rs b/src/test/ui/process/try-wait.rs index 692197210b15f..4eb0f185f7de1 100644 --- a/src/test/ui/process/try-wait.rs +++ b/src/test/ui/process/try-wait.rs @@ -3,6 +3,7 @@ #![allow(stable_features)] // ignore-emscripten no processes // ignore-sgx no processes +// ignore-uefi Command::spawn is blocking #![feature(process_try_wait)] diff --git a/src/test/ui/runtime/native-print-no-runtime.rs b/src/test/ui/runtime/native-print-no-runtime.rs index f17c9fa6ca937..f032e90495756 100644 --- a/src/test/ui/runtime/native-print-no-runtime.rs +++ b/src/test/ui/runtime/native-print-no-runtime.rs @@ -1,4 +1,6 @@ // run-pass +// ignore-uefi allocation and other std functionality is intialized in `sys::init`. This test +// causes CPU Exception. #![feature(start)] diff --git a/src/test/ui/runtime/out-of-stack.rs b/src/test/ui/runtime/out-of-stack.rs index 6873abc49b244..f3cd734d43592 100644 --- a/src/test/ui/runtime/out-of-stack.rs +++ b/src/test/ui/runtime/out-of-stack.rs @@ -6,6 +6,7 @@ // ignore-emscripten no processes // ignore-sgx no processes // ignore-fuchsia must translate zircon signal to SIGABRT, FIXME (#58590) +// ignore-uefi overflowing stack restarts qemu #![feature(core_intrinsics)] #![feature(rustc_private)] diff --git a/src/test/ui/runtime/running-with-no-runtime.rs b/src/test/ui/runtime/running-with-no-runtime.rs index c575a6bec8e92..19bd7f68a654e 100644 --- a/src/test/ui/runtime/running-with-no-runtime.rs +++ b/src/test/ui/runtime/running-with-no-runtime.rs @@ -1,6 +1,8 @@ // run-pass // ignore-emscripten spawning processes is not supported // ignore-sgx no processes +// ignore-uefi allocation and other std functionality is intialized in `sys::init`. This test +// causes CPU Exception. // revisions: mir thir // [thir]compile-flags: -Zthir-unsafeck diff --git a/src/test/ui/simd/target-feature-mixup.rs b/src/test/ui/simd/target-feature-mixup.rs index 5dd163715eb49..bb366515e7391 100644 --- a/src/test/ui/simd/target-feature-mixup.rs +++ b/src/test/ui/simd/target-feature-mixup.rs @@ -43,7 +43,7 @@ fn is_sigill(status: ExitStatus) -> bool { status.signal() == Some(4) } -#[cfg(windows)] +#[cfg(any(windows, target_os = "uefi"))] fn is_sigill(status: ExitStatus) -> bool { status.code() == Some(0xc000001d) } diff --git a/src/test/ui/sse2.rs b/src/test/ui/sse2.rs index 172f4079821a7..ec67dc7137a47 100644 --- a/src/test/ui/sse2.rs +++ b/src/test/ui/sse2.rs @@ -15,7 +15,8 @@ fn main() { } Err(_) => return, } - if cfg!(any(target_arch = "x86", target_arch = "x86_64")) { + // sse is disabled for UEFI + if cfg!(any(target_arch = "x86", target_arch = "x86_64", not(target_os = "uefi"))) { assert!(cfg!(target_feature = "sse2"), "SSE2 was not detected as available on an x86 platform"); } diff --git a/src/test/ui/structs-enums/rec-align-u64.rs b/src/test/ui/structs-enums/rec-align-u64.rs index 40ede9705f115..651337fc06956 100644 --- a/src/test/ui/structs-enums/rec-align-u64.rs +++ b/src/test/ui/structs-enums/rec-align-u64.rs @@ -67,7 +67,7 @@ mod m { } } -#[cfg(target_os = "windows")] +#[cfg(any(target_os = "windows", target_os = "uefi"))] mod m { pub mod m { pub fn align() -> usize { 8 } diff --git a/src/test/ui/wait-forked-but-failed-child.rs b/src/test/ui/wait-forked-but-failed-child.rs index 674c26a43f208..79470a70bac8d 100644 --- a/src/test/ui/wait-forked-but-failed-child.rs +++ b/src/test/ui/wait-forked-but-failed-child.rs @@ -45,7 +45,7 @@ fn find_zombies() { } } -#[cfg(windows)] +#[cfg(any(windows, target_os = "uefi"))] fn find_zombies() { } fn main() { diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index e07b71a7c4780..4d7e66110c395 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1731,6 +1731,7 @@ impl<'test> TestCx<'test> { || self.is_vxworks_pure_static() || self.config.target.contains("sgx") || self.config.target.contains("bpf") + || self.config.target.contains("uefi") { // We primarily compile all auxiliary libraries as dynamic libraries // to avoid code size bloat and large binaries as much as possible diff --git a/src/tools/remote-test-client/src/main.rs b/src/tools/remote-test-client/src/main.rs index bcda930d093c5..0edfd025918ef 100644 --- a/src/tools/remote-test-client/src/main.rs +++ b/src/tools/remote-test-client/src/main.rs @@ -112,11 +112,44 @@ fn prepare_rootfs(target: &str, rootfs: &Path, server: &Path, rootfs_img: &Path) "arm-unknown-linux-gnueabihf" | "aarch64-unknown-linux-gnu" => { prepare_rootfs_cpio(rootfs, rootfs_img) } + "x86_64-unknown-uefi" | "i686-unknown-uefi" => prepare_rootfs_fat(rootfs, rootfs_img), "riscv64gc-unknown-linux-gnu" => prepare_rootfs_ext4(rootfs, rootfs_img), _ => panic!("{} is not supported", target), } } +fn prepare_rootfs_fat(rootfs: &Path, rootfs_img: &Path) { + // println!("Rootfs: {}", rootfs.to_string_lossy()); + // println!("Rootfs Img: {}", rootfs_img.to_string_lossy()); + let mut dd = Command::new("dd"); + dd.arg("if=/dev/zero") + .arg(&format!("of={}", rootfs_img.to_string_lossy())) + .arg("bs=1M") + .arg("count=1024"); + let mut dd_child = t!(dd.spawn()); + assert!(t!(dd_child.wait()).success()); + + let mut mformat = Command::new("mformat"); + mformat.arg("-i").arg(rootfs_img).arg("::"); + let mut mformat_child = t!(mformat.spawn()); + assert!(t!(mformat_child.wait()).success()); + + let mut mcopy = Command::new("mcopy"); + mcopy.arg("-i").arg(rootfs_img); + add_files(&mut mcopy, rootfs); + mcopy.arg("::"); + let mut mcopy_child = t!(mcopy.spawn()); + assert!(t!(mcopy_child.wait()).success()); + + fn add_files(cmd: &mut Command, cur: &Path) { + for entry in t!(cur.read_dir()) { + let entry = t!(entry); + let path = entry.path(); + cmd.arg(path); + } + } +} + fn prepare_rootfs_cpio(rootfs: &Path, rootfs_img: &Path) { // Generate a new rootfs image now that we've updated the test server // executable. This is the equivalent of: @@ -235,6 +268,30 @@ fn start_qemu_emulator(target: &str, rootfs: &Path, server: &Path, tmpdir: &Path .arg(&format!("file={},format=raw,id=hd0", &rootfs_img.to_string_lossy())); t!(cmd.spawn()); } + "x86_64-unknown-uefi" => { + let mut cmd = Command::new("qemu-system-x86_64"); + cmd.arg("-nographic") + .arg("-m") + .arg("1024") + .arg("-drive") + .arg("if=pflash,format=raw,file=/tmp/OVMF/OVMF_CODE.fd,index=0") + .arg("-drive") + .arg("if=pflash,format=raw,file=/tmp/OVMF/OVMF_VARS.fd,index=1") + .arg("-drive") + .arg("format=raw,file=/tmp/OVMF/UefiShell.iso,index=2") + .arg("-drive") + .arg(&format!( + "file={},format=raw,media=disk,index=3", + &rootfs_img.to_string_lossy() + )) + .arg("-netdev") + .arg("user,id=net0,hostfwd=tcp::12345-:12345") + .arg("-device") + .arg("virtio-net-pci,netdev=net0,mac=00:00:00:00:00:00") + .arg("-device") + .arg("virtio-rng-pci"); + t!(cmd.spawn()); + } _ => panic!("cannot start emulator for: {}", target), } } diff --git a/src/tools/remote-test-server/src/main.rs b/src/tools/remote-test-server/src/main.rs index 8e7c39e72b68b..e6e9d43ed841b 100644 --- a/src/tools/remote-test-server/src/main.rs +++ b/src/tools/remote-test-server/src/main.rs @@ -10,10 +10,10 @@ //! themselves having support libraries. All data over the TCP sockets is in a //! basically custom format suiting our needs. -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "uefi")))] use std::fs::Permissions; use std::net::SocketAddr; -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "uefi")))] use std::os::unix::prelude::*; use std::cmp; @@ -120,6 +120,8 @@ fn main() { let listener = bind_socket(config.bind); let (work, tmp): (PathBuf, PathBuf) = if cfg!(target_os = "android") { ("/data/local/tmp/work".into(), "/data/local/tmp/work/tmp".into()) + } else if cfg!(target_os = "uefi") { + ("tmp\\work".into(), "tmp\\work\\tmp".into()) } else { let mut work_dir = env::temp_dir(); work_dir.push("work"); @@ -266,12 +268,14 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf // On windows, libraries are just searched in the executable directory, // system directories, PWD, and PATH, in that order. PATH is the only one // we can change for this. - let library_path = if cfg!(windows) { "PATH" } else { "LD_LIBRARY_PATH" }; + let library_path = + if cfg!(any(windows, target_os = "uefi")) { "PATH" } else { "LD_LIBRARY_PATH" }; // Support libraries were uploaded to `work` earlier, so make sure that's // in `LD_LIBRARY_PATH`. Also include our own current dir which may have // had some libs uploaded. let mut paths = vec![work.to_owned(), path.clone()]; + if let Some(library_path) = env::var_os(library_path) { paths.extend(env::split_paths(&library_path)); } @@ -285,13 +289,19 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf let mut child = t!(cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()); drop(lock); + let mut stdout = child.stdout.take().unwrap(); let mut stderr = child.stderr.take().unwrap(); let socket = Arc::new(Mutex::new(reader.into_inner())); let socket2 = socket.clone(); - let thread = thread::spawn(move || my_copy(&mut stdout, 0, &*socket2)); - my_copy(&mut stderr, 1, &*socket); - thread.join().unwrap(); + if config.sequential { + my_copy(&mut stdout, 0, &*socket2); + my_copy(&mut stderr, 1, &*socket); + } else { + let thread = thread::spawn(move || my_copy(&mut stdout, 0, &*socket2)); + my_copy(&mut stderr, 1, &*socket); + thread.join().unwrap(); + } // Finally send over the exit status. let status = t!(child.wait()); @@ -307,7 +317,7 @@ fn handle_run(socket: TcpStream, work: &Path, tmp: &Path, lock: &Mutex<()>, conf ])); } -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "uefi")))] fn get_status_code(status: &ExitStatus) -> (u8, i32) { match status.code() { Some(n) => (0, n), @@ -315,7 +325,7 @@ fn get_status_code(status: &ExitStatus) -> (u8, i32) { } } -#[cfg(windows)] +#[cfg(any(target_os = "windows", target_os = "uefi"))] fn get_status_code(status: &ExitStatus) -> (u8, i32) { (0, status.code().unwrap()) } @@ -341,11 +351,11 @@ fn recv(dir: &Path, io: &mut B) -> PathBuf { dst } -#[cfg(not(windows))] +#[cfg(not(any(target_os = "windows", target_os = "uefi")))] fn set_permissions(path: &Path) { t!(fs::set_permissions(&path, Permissions::from_mode(0o755))); } -#[cfg(windows)] +#[cfg(any(target_os = "windows", target_os = "uefi"))] fn set_permissions(_path: &Path) {} fn my_copy(src: &mut dyn Read, which: u8, dst: &Mutex) { diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs index a7f4016728416..4eda77a1069be 100644 --- a/src/tools/tidy/src/deps.rs +++ b/src/tools/tidy/src/deps.rs @@ -24,6 +24,7 @@ const LICENSES: &[&str] = &[ "MIT OR Zlib OR Apache-2.0", // miniz_oxide "(MIT OR Apache-2.0) AND Unicode-DFS-2016", // unicode_ident "Unicode-DFS-2016", // tinystr and icu4x + "MIT OR Apache-2.0 OR LGPL-2.1-or-later", // r-efi ]; /// These are exceptions to Rust's permissive licensing policy, and @@ -271,6 +272,8 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[ "zerofrom-derive", "zerovec", "zerovec-derive", + "r-efi", + "r-efi-alloc", ]; const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[ diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index f4592fdcff9dc..11b03d9b027e4 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -62,6 +62,8 @@ const EXCEPTION_PATHS: &[&str] = &[ "library/std/src/panic.rs", // fuchsia-specific panic backtrace handling "library/std/src/personality.rs", "library/std/src/personality/", + "library/std/src/thread/local.rs", // UEFI special thread_local implementation + "library/std/src/thread/mod.rs", // UEFI specific thread_local implementation ]; pub fn check(path: &Path, bad: &mut bool) {