From 2e70cfc04d29ec499e076746d2588d8e7a4e0fe6 Mon Sep 17 00:00:00 2001 From: Ayush Singh Date: Fri, 14 Mar 2025 22:48:38 +0530 Subject: [PATCH] uefi: fs: Implement exists Also adds the initial file abstractions. The file opening algorithm is inspired from UEFI shell. It starts by classifying if the Path is Shell mapping, text representation of device path protocol, or a relative path and converts into an absolute text representation of device path protocol. After that, it queries all handles supporting EFI_SIMPLE_FILE_SYSTEM_PROTOCOL and opens the volume that matches the device path protocol prefix (similar to Windows drive). After that, it opens the file in the volume using the remaining pat. It also introduces OwnedDevicePath and BorrowedDevicePath abstractions to allow working with the base UEFI and Shell device paths efficiently. DevicePath in UEFI behaves like an a group of nodes laied out in the memory contiguously and thus can be modeled using iterators. Signed-off-by: Ayush Singh --- library/std/src/sys/fs/uefi.rs | 140 +++++++++++++++++++++++- library/std/src/sys/pal/uefi/helpers.rs | 3 - 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 56aed7dfd8e82..defc1681d38a4 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -286,8 +286,13 @@ pub fn remove_dir_all(_path: &Path) -> io::Result<()> { unsupported() } -pub fn exists(_path: &Path) -> io::Result { - unsupported() +pub fn exists(path: &Path) -> io::Result { + let f = uefi_fs::File::from_path(path, r_efi::protocols::file::MODE_READ, 0); + match f { + Ok(_) => Ok(true), + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e), + } } pub fn readlink(_p: &Path) -> io::Result { @@ -317,3 +322,134 @@ pub fn canonicalize(_p: &Path) -> io::Result { pub fn copy(_from: &Path, _to: &Path) -> io::Result { unsupported() } + +mod uefi_fs { + use r_efi::protocols::{device_path, file, simple_file_system}; + + use crate::boxed::Box; + use crate::io; + use crate::path::Path; + use crate::ptr::NonNull; + use crate::sys::helpers; + + pub(crate) struct File(NonNull); + + impl File { + pub(crate) fn from_path(path: &Path, open_mode: u64, attr: u64) -> io::Result { + let absolute = crate::path::absolute(path)?; + + let p = helpers::OwnedDevicePath::from_text(absolute.as_os_str())?; + let (vol, mut path_remaining) = Self::open_volume_from_device_path(p.borrow())?; + + vol.open(&mut path_remaining, open_mode, attr) + } + + /// Open Filesystem volume given a devicepath to the volume, or a file/directory in the + /// volume. The path provided should be absolute UEFI device path, without any UEFI shell + /// mappings. + /// + /// Returns + /// 1. The volume as a UEFI File + /// 2. Path relative to the volume. + /// + /// For example, given "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi", + /// this will open the volume "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)" + /// and return the remaining file path "\abc\run.efi". + 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_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 = crate::ptr::null_mut(); + let r = unsafe { + ((*simple_file_system_protocol.as_ptr()).open_volume)( + simple_file_system_protocol.as_ptr(), + &mut file_protocol, + ) + }; + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + // Since no error was returned, file protocol should be non-NULL. + let p = NonNull::new(file_protocol).unwrap(); + Ok(Self(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 = crate::ptr::null_mut(); + + let r = unsafe { + ((*file_ptr).open)(file_ptr, &mut file_opened, path.as_mut_ptr(), open_mode, attr) + }; + + if r.is_error() { + return Err(io::Error::from_raw_os_error(r.as_usize())); + } + + // Since no error was returned, file protocol should be non-NULL. + let p = NonNull::new(file_opened).unwrap(); + Ok(File(p)) + } + } + + impl Drop for File { + fn drop(&mut self) { + let file_ptr = self.0.as_ptr(); + let _ = unsafe { ((*self.0.as_ptr()).close)(file_ptr) }; + } + } + + /// A helper to check that target path is a descendent of source. It is expected to be used with + /// absolute UEFI device paths without any UEFI shell mappings. + /// + /// Returns the path relative to source + /// + /// For example, given "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/" and + /// "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi", this will return + /// "\abc\run.efi" + fn path_best_match( + source: &helpers::BorrowedDevicePath<'_>, + target: &helpers::BorrowedDevicePath<'_>, + ) -> 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)) => { + let p = y.to_path().to_text().ok()?; + return helpers::os_string_to_raw(&p); + } + _ => return None, + } + } + } +} diff --git a/library/std/src/sys/pal/uefi/helpers.rs b/library/std/src/sys/pal/uefi/helpers.rs index 60c33c637d762..2faa733f23f41 100644 --- a/library/std/src/sys/pal/uefi/helpers.rs +++ b/library/std/src/sys/pal/uefi/helpers.rs @@ -374,7 +374,6 @@ impl<'a> BorrowedDevicePath<'a> { device_path_to_text(self.protocol) } - #[expect(dead_code)] pub(crate) const fn iter(&'a self) -> DevicePathIterator<'a> { DevicePathIterator::new(DevicePathNode::new(self.protocol)) } @@ -452,7 +451,6 @@ impl<'a> DevicePathNode<'a> { && self.sub_type() == r_efi::protocols::device_path::End::SUBTYPE_ENTIRE } - #[expect(dead_code)] 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 @@ -468,7 +466,6 @@ impl<'a> DevicePathNode<'a> { Self::new(node) } - #[expect(dead_code)] pub(crate) fn to_path(&'a self) -> BorrowedDevicePath<'a> { BorrowedDevicePath::new(self.protocol) }