about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAyush Singh <ayush@beagleboard.org>2025-03-14 22:48:38 +0530
committerAyush Singh <ayush@beagleboard.org>2025-03-18 08:11:16 +0530
commit2e70cfc04d29ec499e076746d2588d8e7a4e0fe6 (patch)
treec6748f740bf73a9b2c619aa1fcf56d89cda4e33d
parentcb50d4d8566b1ee97e9a5ef95a37a40936a62c30 (diff)
downloadrust-2e70cfc04d29ec499e076746d2588d8e7a4e0fe6.tar.gz
rust-2e70cfc04d29ec499e076746d2588d8e7a4e0fe6.zip
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 <ayush@beagleboard.org>
-rw-r--r--library/std/src/sys/fs/uefi.rs140
-rw-r--r--library/std/src/sys/pal/uefi/helpers.rs3
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 56aed7dfd8e..defc1681d38 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<bool> {
-    unsupported()
+pub fn exists(path: &Path) -> io::Result<bool> {
+    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<PathBuf> {
@@ -317,3 +322,134 @@ pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
 pub fn copy(_from: &Path, _to: &Path) -> io::Result<u64> {
     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<file::Protocol>);
+
+    impl File {
+        pub(crate) fn from_path(path: &Path, open_mode: u64, attr: u64) -> io::Result<Self> {
+            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<device_path::Protocol> =
+                    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<crate::ffi::c_void>) -> io::Result<Self> {
+            let simple_file_system_protocol = helpers::open_protocol::<simple_file_system::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<Self> {
+            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<Box<[u16]>> {
+        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 60c33c637d7..2faa733f23f 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)
     }