From be7732b5cf4a340cc4e224a565b5e2049b56447f Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Sat, 27 Jan 2024 01:06:40 +0530 Subject: [PATCH] Initial fs implementation for UEFI - Only implementing basic file stuff for now. Signed-off-by: Ayush Singh --- library/std/src/lib.rs | 1 + library/std/src/sys/pal/uefi/fs.rs | 736 ++++++++++++++++++++++++ library/std/src/sys/pal/uefi/helpers.rs | 256 ++++++++- library/std/src/sys/pal/uefi/mod.rs | 1 - library/std/src/sys/pal/uefi/process.rs | 2 +- library/std/src/sys/pal/uefi/time.rs | 10 +- 6 files changed, 992 insertions(+), 14 deletions(-) create mode 100644 library/std/src/sys/pal/uefi/fs.rs diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 3ab652383689c..f1998ba9cff44 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -356,6 +356,7 @@ #![feature(prelude_2024)] #![feature(ptr_as_uninit)] #![feature(ptr_mask)] +#![feature(ptr_metadata)] #![feature(random)] #![feature(slice_internals)] #![feature(slice_ptr_get)] diff --git a/library/std/src/sys/pal/uefi/fs.rs b/library/std/src/sys/pal/uefi/fs.rs new file mode 100644 index 0000000000000..5cddba7cf92b1 --- /dev/null +++ b/library/std/src/sys/pal/uefi/fs.rs @@ -0,0 +1,736 @@ +use r_efi::protocols::file; + +use super::helpers; +use crate::ffi::OsString; +use crate::fmt; +use crate::hash::Hash; +use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; +use crate::os::uefi::ffi::OsStringExt; +use crate::path::{Path, PathBuf}; +use crate::sys::time::SystemTime; +use crate::sys::unsupported; + +const EOF_POS: u64 = u64::MAX; + +pub struct File(uefi_fs::File); + +#[derive(Clone)] +pub struct FileAttr { + size: u64, + attr: u64, + times: FileTimes, +} + +impl From<&uefi_fs::Info> for FileAttr { + fn from(v: &uefi_fs::Info) -> Self { + Self { + size: v.file_size, + attr: v.attribute, + times: FileTimes { + modified: SystemTime::new(v.modification_time), + accessed: SystemTime::new(v.last_access_time), + created: SystemTime::new(v.create_time), + }, + } + } +} + +pub struct ReadDir(!); + +pub struct DirEntry(!); + +#[derive(Clone, Debug)] +pub struct OpenOptions { + mode: u64, + attr: u64, + append: bool, + truncate: bool, + create_new: bool, +} + +#[derive(Copy, Clone, Debug)] +pub struct FileTimes { + modified: SystemTime, + accessed: SystemTime, + created: SystemTime, +} + +impl Default for FileTimes { + fn default() -> Self { + Self { modified: SystemTime::ZERO, accessed: SystemTime::ZERO, created: SystemTime::ZERO } + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FilePermissions(u64); + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct FileType(u64); + +#[derive(Debug)] +pub struct DirBuilder {} + +impl FileAttr { + pub fn size(&self) -> u64 { + self.size + } + + pub fn perm(&self) -> FilePermissions { + FilePermissions(self.attr) + } + + pub fn file_type(&self) -> FileType { + FileType(self.attr) + } + + pub fn modified(&self) -> io::Result { + Ok(self.times.modified) + } + + pub fn accessed(&self) -> io::Result { + Ok(self.times.accessed) + } + + pub fn created(&self) -> io::Result { + Ok(self.times.created) + } +} + +impl FilePermissions { + pub fn readonly(&self) -> bool { + self.0 & r_efi::protocols::file::READ_ONLY != 0 + } + + pub fn set_readonly(&mut self, readonly: bool) { + if readonly { + self.0 |= r_efi::protocols::file::READ_ONLY; + } else { + self.0 &= !r_efi::protocols::file::READ_ONLY; + } + } +} + +impl FileTimes { + pub fn set_accessed(&mut self, t: SystemTime) { + self.accessed = t; + } + pub fn set_modified(&mut self, t: SystemTime) { + self.modified = t; + } +} + +impl FileType { + pub fn is_dir(&self) -> bool { + self.0 & r_efi::protocols::file::DIRECTORY != 0 + } + + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + // Symlinks are not supported + pub fn is_symlink(&self) -> bool { + false + } +} + +impl fmt::Debug for ReadDir { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0 + } +} + +impl Iterator for ReadDir { + type Item = io::Result; + + fn next(&mut self) -> Option> { + self.0 + } +} + +impl DirEntry { + pub fn path(&self) -> PathBuf { + self.0 + } + + pub fn file_name(&self) -> OsString { + self.0 + } + + pub fn metadata(&self) -> io::Result { + self.0 + } + + pub fn file_type(&self) -> io::Result { + self.0 + } +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + mode: file::MODE_READ, + attr: 0, + append: false, + truncate: false, + create_new: false, + } + } + + pub fn read(&mut self, read: bool) { + if read { + self.mode |= file::MODE_READ; + } else { + self.mode &= !file::MODE_READ; + } + } + + pub fn write(&mut self, write: bool) { + if write { + self.mode |= file::MODE_WRITE; + } else { + self.mode &= !file::MODE_WRITE; + } + } + + pub fn append(&mut self, append: bool) { + self.append = append; + } + + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + + pub fn create(&mut self, create: bool) { + if create { + self.mode |= file::MODE_CREATE; + } else { + self.mode &= !file::MODE_CREATE; + } + } + + pub fn create_new(&mut self, create_new: bool) { + self.create(true); + self.create_new = create_new; + } +} + +impl File { + const fn new(file: uefi_fs::File) -> Self { + Self(file) + } + + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + if opts.create_new { + // Check if file already exists by trying to open in readonly + let opts = OpenOptions::new(); + let temp = uefi_fs::File::from_path(path, opts.mode, opts.attr); + + if temp.is_ok() { + return Err(io::const_io_error!(io::ErrorKind::AlreadyExists, "File exists")); + } + } + + let file = uefi_fs::File::from_path(path, opts.mode, opts.attr)?; + let file = Self::new(file); + + if opts.truncate { + file.truncate(0)?; + } + + if opts.append { + file.seek(SeekFrom::Start(EOF_POS))?; + } + + Ok(file) + } + + pub fn file_attr(&self) -> io::Result { + self.0.get_info().map(|info| FileAttr::from(info.as_ref())) + } + + pub fn fsync(&self) -> io::Result<()> { + self.flush() + } + + pub fn datasync(&self) -> io::Result<()> { + self.fsync() + } + + pub fn truncate(&self, size: u64) -> io::Result<()> { + let mut info: Box = self.0.get_info()?; + info.file_size = size; + self.0.set_info(info.as_mut()) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + self.0.read(buf) + } + + pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { + crate::io::default_read_vectored(|buf| self.read(buf), bufs) + } + + pub fn is_read_vectored(&self) -> bool { + false + } + + pub fn read_buf(&self, cursor: BorrowedCursor<'_>) -> io::Result<()> { + crate::io::default_read_buf(|buf| self.read(buf), cursor) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result { + crate::io::default_write_vectored(|buf| self.write(buf), bufs) + } + + pub fn is_write_vectored(&self) -> bool { + false + } + + pub fn flush(&self) -> io::Result<()> { + self.0.flush() + } + + pub fn seek(&self, pos: SeekFrom) -> io::Result { + let position: u64 = match pos { + SeekFrom::Start(x) => x, + SeekFrom::Current(x) => ((self.0.get_position()? as i64) + x) as u64, + SeekFrom::End(x) => ((self.file_attr()?.size() as i64) + x) as u64, + }; + + self.0.set_position(position)?; + Ok(position) + } + + pub fn duplicate(&self) -> io::Result { + unsupported() + } + + pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> { + let mut info = self.0.get_info()?; + info.attribute = perm.0; + self.0.set_info(&mut info) + } + + pub fn set_times(&self, times: FileTimes) -> io::Result<()> { + let mut info = self.0.get_info()?; + + if times.accessed != SystemTime::ZERO { + info.last_access_time = helpers::uefi_time_from_duration( + times.accessed.0, + info.last_access_time.daylight, + info.last_access_time.timezone, + ); + } + + if times.created != SystemTime::ZERO { + info.create_time = helpers::uefi_time_from_duration( + times.created.0, + info.create_time.daylight, + info.create_time.timezone, + ); + } + + if times.modified != SystemTime::ZERO { + info.modification_time = helpers::uefi_time_from_duration( + times.modified.0, + info.modification_time.daylight, + info.modification_time.timezone, + ); + } + + self.0.set_info(&mut info) + } +} + +impl DirBuilder { + pub fn new() -> DirBuilder { + DirBuilder {} + } + + pub fn mkdir(&self, _p: &Path) -> io::Result<()> { + unsupported() + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut b = f.debug_struct("File"); + + if let Ok(info) = self.0.get_info() { + let flen = info.file_name.len(); + let fname = OsString::from_wide(&info.file_name[..(flen - 1)]); + b.field("file_name", &fname); + } + + b.finish() + } +} + +pub fn readdir(_p: &Path) -> io::Result { + unsupported() +} + +pub fn unlink(_p: &Path) -> io::Result<()> { + unsupported() +} + +pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + +pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { + let f = uefi_fs::File::from_path(p, file::MODE_READ | file::MODE_WRITE, 0)?; + + let mut info = f.get_info()?; + info.attribute = perm.0; + f.set_info(&mut info) +} + +pub fn rmdir(p: &Path) -> io::Result<()> { + let f = uefi_fs::File::from_path(p, file::MODE_READ | file::MODE_WRITE, file::DIRECTORY)?; + f.delete() +} + +pub fn remove_dir_all(_path: &Path) -> io::Result<()> { + unsupported() +} + +pub fn readlink(_p: &Path) -> io::Result { + unsupported() +} + +pub fn symlink(_original: &Path, _link: &Path) -> io::Result<()> { + unsupported() +} + +pub fn link(_src: &Path, _dst: &Path) -> io::Result<()> { + unsupported() +} + +pub fn stat(p: &Path) -> io::Result { + let opts = OpenOptions::new(); + File::open(p, &opts)?.file_attr() +} + +/// Same as stat since symlinks are not supported +pub fn lstat(p: &Path) -> io::Result { + stat(p) +} + +pub fn canonicalize(_p: &Path) -> io::Result { + unsupported() +} + +pub fn copy(from: &Path, to: &Path) -> io::Result { + let mut src = crate::fs::File::open(from)?; + let mut dst = crate::fs::File::create(to)?; + + crate::io::copy(&mut src, &mut dst) +} + +pub fn exists(path: &Path) -> io::Result { + let f = crate::fs::File::open(path); + match f { + Ok(_) => Ok(true), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e), + } +} + +mod uefi_fs { + #![allow(dead_code)] + + use r_efi::protocols::{device_path, file, loaded_image, simple_file_system}; + + use super::super::helpers; + use crate::boxed::Box; + use crate::io; + use crate::mem::MaybeUninit; + use crate::path::Path; + use crate::ptr::NonNull; + use crate::sys::helpers::OwnedDevicePath; + + const BACKSLASH: u16 = 0x005c; + const DOT: u16 = 0x002e; + + pub struct File(NonNull); + + impl File { + pub const fn new(file: NonNull) -> Self { + Self(file) + } + + pub fn from_path(path: &Path, open_mode: u64, attr: u64) -> io::Result { + let p = OwnedDevicePath::from_text(path.as_os_str())?; + let (vol, mut path_remaining) = Self::open_volume_from_device_path(p.borrow())?; + vol.open(&mut path_remaining, open_mode, attr) + } + + fn open_volume_from_device_path( + path: helpers::BorrowedDevicePath<'_>, + ) -> io::Result<(Self, Box<[u16]>)> { + let handles = match helpers::locate_handles(simple_file_system::PROTOCOL_GUID) { + Ok(x) => x, + Err(e) => return Err(e), + }; + for handle in handles { + let volume_device_path: NonNull = + match helpers::open_protocol(handle, device_path::PROTOCOL_GUID) { + Ok(x) => x, + Err(_) => continue, + }; + let volume_device_path = helpers::BorrowedDevicePath::new(volume_device_path); + + if let Some(left_path) = path_best_match(&volume_device_path, &path) { + return Ok((Self::open_volume(handle)?, left_path)); + } + } + + Err(io::const_io_error!(io::ErrorKind::NotFound, "Volume Not Found")) + } + + // Open volume on device_handle using SIMPLE_FILE_SYSTEM_PROTOCOL + fn open_volume(device_handle: NonNull) -> io::Result { + let simple_file_system_protocol = helpers::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() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + let p = NonNull::new(unsafe { file_protocol.assume_init() }) + .unwrap_or_else(|| unreachable!()); + Ok(Self::new(p)) + } + + fn open(&self, path: &mut [u16], open_mode: u64, attr: u64) -> io::Result { + let file_ptr = self.0.as_ptr(); + let mut file_opened: MaybeUninit<*mut file::Protocol> = MaybeUninit::uninit(); + + let r = unsafe { + ((*file_ptr).open)( + file_ptr, + file_opened.as_mut_ptr(), + path.as_mut_ptr(), + open_mode, + attr, + ) + }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + // SAFETY: file_opened is initialized successfully + let p = NonNull::new(unsafe { file_opened.assume_init() }) + .unwrap_or_else(|| unreachable!()); + Ok(File::new(p)) + } + + pub fn delete(self) -> io::Result<()> { + let file_ptr = self.0.as_ptr(); + let r = unsafe { ((*file_ptr).delete)(file_ptr) }; + + // SAFETY: Spec says that this function will always return EFI_SUCCESS or + // EFI_WARN_DELETE_FAILURE. + // + // Also, the file is closed even in case of warning + if r.is_error() { + unreachable!() + } + crate::mem::forget(self); + + Ok(()) + } + + pub fn read(&self, buf: &mut [u8]) -> io::Result { + let file_ptr = self.0.as_ptr(); + let mut buf_size = buf.len(); + let r = + unsafe { ((*file_ptr).read)(file_ptr, &mut buf_size, buf.as_mut_ptr() as *mut _) }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + Ok(buf_size) + } + + pub fn write(&self, buf: &[u8]) -> io::Result { + let file_ptr = self.0.as_ptr(); + let mut buf_size = buf.len(); + let r = unsafe { ((*file_ptr).write)(file_ptr, &mut buf_size, buf.as_ptr() as *mut _) }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + Ok(buf_size) + } + + pub fn set_position(&self, position: u64) -> io::Result<()> { + let file_ptr = self.0.as_ptr(); + let r = unsafe { ((*file_ptr).set_position)(file_ptr, position) }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + Ok(()) + } + + pub fn get_position(&self) -> io::Result { + let file_ptr = self.0.as_ptr(); + let mut position = 0; + let r = unsafe { ((*file_ptr).get_position)(file_ptr, &mut position) }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + Ok(position) + } + + pub fn get_info(&self) -> io::Result> { + let mut buf_size = 0usize; + match unsafe { + Self::get_info_raw( + self.0.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 mut info: Box = Info::alloc(buf_size); + + unsafe { + Self::get_info_raw( + self.0.as_ptr(), + file::INFO_ID, + &mut buf_size, + info.as_mut() as *mut Info as *mut _, + ) + }?; + + Ok(info) + } + + pub fn set_info(&self, info: &mut Info) -> io::Result<()> { + let file_ptr = self.0.as_ptr(); + let mut info_id = file::INFO_ID; + + let r = unsafe { + ((*file_ptr).set_info)( + self.0.as_ptr(), + &mut info_id, + info.size as usize, + info as *mut Info as *mut _, + ) + }; + + if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } + } + + pub fn flush(&self) -> io::Result<()> { + let file_ptr = self.0.as_ptr(); + let r = unsafe { ((*file_ptr).flush)(file_ptr) }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + Ok(()) + } + + 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(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) } + } + } + + impl Drop for File { + fn drop(&mut self) { + let file_ptr = self.0.as_ptr(); + let _ = unsafe { ((*self.0.as_ptr()).close)(file_ptr) }; + } + } + + // Open the volume on the device_handle the image was loaded from. + fn rootfs() -> io::Result { + let loaded_image_protocol: NonNull = + helpers::image_handle_protocol(loaded_image::PROTOCOL_GUID)?; + + 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"))?; + + File::open_volume(device_handle) + } + + #[repr(C)] + #[derive(Debug)] + pub struct Info { + pub size: u64, + pub file_size: u64, + pub physical_size: u64, + pub create_time: r_efi::system::Time, + pub last_access_time: r_efi::system::Time, + pub modification_time: r_efi::system::Time, + pub attribute: u64, + pub file_name: [r_efi::base::Char16], + } + + impl Info { + pub fn alloc(buf_size: usize) -> Box { + unsafe { + let buf_layout = crate::alloc::Layout::from_size_align(buf_size, 8).unwrap(); + let temp = crate::alloc::alloc(buf_layout); + let name_size = (buf_size - crate::mem::size_of::>()) / 2; + let temp_ptr: *mut Info = crate::ptr::from_raw_parts_mut(temp as *mut _, name_size); + Box::from_raw(temp_ptr) + } + } + } + + fn path_best_match<'a>( + source: &helpers::BorrowedDevicePath<'a>, + target: &helpers::BorrowedDevicePath<'a>, + ) -> Option> { + let mut source_iter = source.iter().take_while(|x| !x.is_end_instance()); + let mut target_iter = target.iter().take_while(|x| !x.is_end_instance()); + + loop { + match (source_iter.next(), target_iter.next()) { + (Some(x), Some(y)) if x == y => continue, + (None, Some(y)) => return Some(y.to_path().to_text_raw().unwrap()), + _ => return None, + } + } + } +} diff --git a/library/std/src/sys/pal/uefi/helpers.rs b/library/std/src/sys/pal/uefi/helpers.rs index abc8e69a285f3..72fe5cb62e63d 100644 --- a/library/std/src/sys/pal/uefi/helpers.rs +++ b/library/std/src/sys/pal/uefi/helpers.rs @@ -14,6 +14,8 @@ use r_efi::protocols::{device_path, device_path_to_text, shell}; use crate::ffi::{OsStr, OsString}; use crate::io::{self, const_io_error}; +use crate::iter::Iterator; +use crate::marker::PhantomData; use crate::mem::{MaybeUninit, size_of}; use crate::os::uefi::env::boot_services; use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; @@ -22,6 +24,7 @@ use crate::ptr::NonNull; use crate::slice; use crate::sync::atomic::{AtomicPtr, Ordering}; use crate::sys_common::wstr::WStrUnits; +use crate::time::Duration; type BootInstallMultipleProtocolInterfaces = unsafe extern "efiapi" fn(_: *mut r_efi::efi::Handle, _: ...) -> r_efi::efi::Status; @@ -162,11 +165,11 @@ pub(crate) fn image_handle_protocol(protocol_guid: Guid) -> io::Result) -> io::Result { +fn device_path_to_text_raw(path: NonNull) -> io::Result> { fn path_to_text( protocol: NonNull, path: NonNull, - ) -> io::Result { + ) -> io::Result> { let path_ptr: *mut r_efi::efi::Char16 = unsafe { ((*protocol.as_ptr()).convert_device_path_to_text)( path.as_ptr(), @@ -177,6 +180,56 @@ pub(crate) fn device_path_to_text(path: NonNull) -> io::R ) }; + owned_uefi_string_from_raw(path_ptr) + .ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path")) + } + + 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 Ok(protocol) = open_protocol::( + handle, + device_path_to_text::PROTOCOL_GUID, + ) { + return path_to_text(protocol, path); + } + } + + let device_path_to_text_handles = locate_handles(device_path_to_text::PROTOCOL_GUID)?; + for handle in device_path_to_text_handles { + if let Ok(protocol) = open_protocol::( + handle, + device_path_to_text::PROTOCOL_GUID, + ) { + LAST_VALID_HANDLE.store(handle.as_ptr(), Ordering::Release); + return path_to_text(protocol, path); + } + } + + Err(io::const_io_error!(io::ErrorKind::NotFound, "No device path to text protocol found")) +} + +pub(crate) fn device_path_to_text(path: NonNull) -> io::Result { + let p = device_path_to_text_raw(path)?; + Ok(OsString::from_wide(&p)) +} + +fn device_node_to_text(path: NonNull) -> io::Result { + fn node_to_text( + protocol: NonNull, + path: NonNull, + ) -> io::Result { + let path_ptr: *mut r_efi::efi::Char16 = unsafe { + ((*protocol.as_ptr()).convert_device_node_to_text)( + path.as_ptr(), + // DisplayOnly + r_efi::efi::Boolean::FALSE, + // AllowShortcuts + r_efi::efi::Boolean::FALSE, + ) + }; + let path = os_string_from_raw(path_ptr) .ok_or(io::const_io_error!(io::ErrorKind::InvalidData, "Invalid path"))?; @@ -198,7 +251,7 @@ pub(crate) fn device_path_to_text(path: NonNull) -> io::R handle, device_path_to_text::PROTOCOL_GUID, ) { - return path_to_text(protocol, path); + return node_to_text(protocol, path); } } @@ -209,7 +262,7 @@ pub(crate) fn device_path_to_text(path: NonNull) -> io::R device_path_to_text::PROTOCOL_GUID, ) { LAST_VALID_HANDLE.store(handle.as_ptr(), Ordering::Release); - return path_to_text(protocol, path); + return node_to_text(protocol, path); } } @@ -224,14 +277,14 @@ pub(crate) fn runtime_services() -> Option> NonNull::new(runtime_services) } -pub(crate) struct DevicePath(NonNull); +pub(crate) struct OwnedDevicePath(pub(crate) NonNull); -impl DevicePath { +impl OwnedDevicePath { pub(crate) fn from_text(p: &OsStr) -> io::Result { fn inner( p: &OsStr, protocol: NonNull, - ) -> io::Result { + ) -> io::Result { let path_vec = p.encode_wide().chain(Some(0)).collect::>(); if path_vec[..path_vec.len() - 1].contains(&0) { return Err(const_io_error!( @@ -243,7 +296,7 @@ impl DevicePath { let path = unsafe { ((*protocol.as_ptr()).convert_text_to_device_path)(path_vec.as_ptr()) }; - NonNull::new(path).map(DevicePath).ok_or_else(|| { + NonNull::new(path).map(OwnedDevicePath).ok_or_else(|| { const_io_error!(io::ErrorKind::InvalidFilename, "Invalid Device Path") }) } @@ -277,12 +330,16 @@ impl DevicePath { )) } - pub(crate) fn as_ptr(&self) -> *mut r_efi::protocols::device_path::Protocol { + pub(crate) const fn as_ptr(&self) -> *mut r_efi::protocols::device_path::Protocol { self.0.as_ptr() } + + pub(crate) const fn borrow<'a>(&'a self) -> BorrowedDevicePath<'a> { + BorrowedDevicePath::new(self.0) + } } -impl Drop for DevicePath { +impl Drop for OwnedDevicePath { fn drop(&mut self) { if let Some(bt) = boot_services() { let bt: NonNull = bt.cast(); @@ -293,6 +350,132 @@ impl Drop for DevicePath { } } +impl crate::fmt::Debug for OwnedDevicePath { + fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result { + let p = device_path_to_text(self.0).unwrap(); + p.fmt(f) + } +} + +pub(crate) struct BorrowedDevicePath<'a> { + protocol: NonNull, + phantom: PhantomData<&'a r_efi::protocols::device_path::Protocol>, +} + +impl<'a> BorrowedDevicePath<'a> { + pub(crate) const fn new(protocol: NonNull) -> Self { + Self { protocol, phantom: PhantomData } + } + + pub(crate) const fn iter(&'a self) -> DevicePathIterator<'a> { + DevicePathIterator::new(DevicePathNode::new(self.protocol)) + } + + pub(crate) fn to_text_raw(&self) -> io::Result> { + device_path_to_text_raw(self.protocol) + } +} + +impl<'a> crate::fmt::Debug for BorrowedDevicePath<'a> { + fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result { + let p = device_path_to_text(self.protocol).unwrap(); + p.fmt(f) + } +} + +pub(crate) struct DevicePathIterator<'a>(Option>); + +impl<'a> DevicePathIterator<'a> { + const fn new(node: DevicePathNode<'a>) -> Self { + if node.is_end() { Self(None) } else { Self(Some(node)) } + } +} + +impl<'a> Iterator for DevicePathIterator<'a> { + type Item = DevicePathNode<'a>; + + fn next(&mut self) -> Option { + let cur_node = self.0?; + + let next_node = unsafe { cur_node.next_node() }; + self.0 = if next_node.is_end() { None } else { Some(next_node) }; + + Some(cur_node) + } +} + +#[derive(Copy, Clone)] +pub(crate) struct DevicePathNode<'a> { + protocol: NonNull, + phantom: PhantomData<&'a r_efi::protocols::device_path::Protocol>, +} + +impl<'a> DevicePathNode<'a> { + pub(crate) const fn new(protocol: NonNull) -> Self { + Self { protocol, phantom: PhantomData } + } + + pub(crate) const fn length(&self) -> u16 { + let len = unsafe { (*self.protocol.as_ptr()).length }; + u16::from_le_bytes(len) + } + + pub(crate) const fn node_type(&self) -> u8 { + unsafe { (*self.protocol.as_ptr()).r#type } + } + + pub(crate) const fn sub_type(&self) -> u8 { + unsafe { (*self.protocol.as_ptr()).sub_type } + } + + pub(crate) const fn is_end(&self) -> bool { + self.node_type() == r_efi::protocols::device_path::TYPE_END + && self.sub_type() == r_efi::protocols::device_path::End::SUBTYPE_ENTIRE + } + + pub(crate) const fn is_end_instance(&self) -> bool { + self.node_type() == r_efi::protocols::device_path::TYPE_END + && self.sub_type() == r_efi::protocols::device_path::End::SUBTYPE_INSTANCE + } + + pub(crate) unsafe fn next_node(&self) -> Self { + let node = unsafe { + self.protocol + .cast::() + .add(self.length().into()) + .cast::() + }; + Self::new(node) + } + + pub(crate) fn to_path(&'a self) -> BorrowedDevicePath<'a> { + BorrowedDevicePath::new(self.protocol) + } +} + +impl<'a> PartialEq for DevicePathNode<'a> { + fn eq(&self, other: &Self) -> bool { + let self_len = self.length(); + let other_len = other.length(); + + self_len == other_len + && unsafe { + compiler_builtins::mem::memcmp( + self.protocol.as_ptr().cast(), + other.protocol.as_ptr().cast(), + usize::from(self_len), + ) == 0 + } + } +} + +impl<'a> crate::fmt::Debug for DevicePathNode<'a> { + fn fmt(&self, f: &mut crate::fmt::Formatter<'_>) -> crate::fmt::Result { + let p = device_node_to_text(self.protocol).unwrap(); + p.fmt(f) + } +} + pub(crate) struct OwnedProtocol { guid: r_efi::efi::Guid, handle: NonNull, @@ -413,6 +596,15 @@ impl Drop for OwnedTable { } } +/// Create an Owned UEFI string from raw pointer. Allows string allocations and conversions +/// +/// SAFETY: This function assumes that Rust has ownership over this string +fn owned_uefi_string_from_raw(ptr: *mut r_efi::efi::Char16) -> Option> { + let str_len = unsafe { WStrUnits::new(ptr)?.count() }; + let str_slice = crate::ptr::slice_from_raw_parts_mut(ptr.cast(), str_len); + Some(unsafe { Box::from_raw(str_slice) }) +} + /// Create OsString from a pointer to NULL terminated UTF-16 string pub(crate) fn os_string_from_raw(ptr: *mut r_efi::efi::Char16) -> Option { let path_len = unsafe { WStrUnits::new(ptr)?.count() }; @@ -445,3 +637,47 @@ pub(crate) fn open_shell() -> Option> { None } + +// This algorithm is taken from: http://howardhinnant.github.io/date_algorithms.html +pub const fn uefi_time_from_duration( + dur: Duration, + daylight: u8, + timezone: i16, +) -> r_efi::system::Time { + const SECS_IN_MINUTE: u64 = 60; + const SECS_IN_HOUR: u64 = SECS_IN_MINUTE * 60; + const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; + + let secs = dur.as_secs(); + + let days = secs / SECS_IN_DAY; + let remaining_secs = secs % SECS_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 / SECS_IN_HOUR) as u8, + minute: ((remaining_secs % SECS_IN_HOUR) / SECS_IN_MINUTE) as u8, + second: ((remaining_secs % SECS_IN_HOUR) % SECS_IN_MINUTE) as u8, + pad1: 0, + nanosecond: dur.subsec_nanos(), + timezone, + daylight, + pad2: 0, + } +} diff --git a/library/std/src/sys/pal/uefi/mod.rs b/library/std/src/sys/pal/uefi/mod.rs index c0ab52f650aa5..aa795bcdbd2d3 100644 --- a/library/std/src/sys/pal/uefi/mod.rs +++ b/library/std/src/sys/pal/uefi/mod.rs @@ -15,7 +15,6 @@ pub mod args; pub mod env; -#[path = "../unsupported/fs.rs"] pub mod fs; pub mod helpers; #[path = "../unsupported/io.rs"] diff --git a/library/std/src/sys/pal/uefi/process.rs b/library/std/src/sys/pal/uefi/process.rs index 0cc9cecb89db0..2ca2e08539b95 100644 --- a/library/std/src/sys/pal/uefi/process.rs +++ b/library/std/src/sys/pal/uefi/process.rs @@ -320,7 +320,7 @@ mod uefi_command_internal { impl Image { pub fn load_image(p: &OsStr) -> io::Result { - let path = helpers::DevicePath::from_text(p)?; + let path = helpers::OwnedDevicePath::from_text(p)?; let boot_services: NonNull = boot_services() .ok_or_else(|| const_io_error!(io::ErrorKind::NotFound, "Boot Services not found"))? .cast(); diff --git a/library/std/src/sys/pal/uefi/time.rs b/library/std/src/sys/pal/uefi/time.rs index 495ff2dc930ed..afbe8d3324c49 100644 --- a/library/std/src/sys/pal/uefi/time.rs +++ b/library/std/src/sys/pal/uefi/time.rs @@ -8,7 +8,7 @@ const SECS_IN_DAY: u64 = SECS_IN_HOUR * 24; pub struct Instant(Duration); #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct SystemTime(Duration); +pub struct SystemTime(pub(crate) Duration); pub const UNIX_EPOCH: SystemTime = SystemTime(Duration::from_secs(0)); @@ -40,6 +40,12 @@ impl Instant { } impl SystemTime { + pub(crate) const ZERO: SystemTime = SystemTime(Duration::ZERO); + + pub(crate) const fn new(t: r_efi::efi::Time) -> Self { + Self(system_time_internal::uefi_time_to_duration(t)) + } + pub fn now() -> SystemTime { system_time_internal::now() .unwrap_or_else(|| panic!("time not implemented on this platform")) @@ -79,7 +85,7 @@ pub(crate) mod system_time_internal { let t = unsafe { t.assume_init() }; - Some(SystemTime(uefi_time_to_duration(t))) + Some(SystemTime::new(t)) } // This algorithm is based on the one described in the post