Skip to content

Commit 75530e9

Browse files
committed
Auto merge of #135368 - Ayush1325:uefi-fs-2, r=jhpratt,nicholasbishop
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. This is an effort to break the original PR (#129700) into much smaller chunks for faster upstreaming.
2 parents 259fdb5 + 2e70cfc commit 75530e9

File tree

2 files changed

+138
-5
lines changed

2 files changed

+138
-5
lines changed

library/std/src/sys/fs/uefi.rs

+138-2
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,13 @@ pub fn remove_dir_all(_path: &Path) -> io::Result<()> {
286286
unsupported()
287287
}
288288

289-
pub fn exists(_path: &Path) -> io::Result<bool> {
290-
unsupported()
289+
pub fn exists(path: &Path) -> io::Result<bool> {
290+
let f = uefi_fs::File::from_path(path, r_efi::protocols::file::MODE_READ, 0);
291+
match f {
292+
Ok(_) => Ok(true),
293+
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
294+
Err(e) => Err(e),
295+
}
291296
}
292297

293298
pub fn readlink(_p: &Path) -> io::Result<PathBuf> {
@@ -317,3 +322,134 @@ pub fn canonicalize(_p: &Path) -> io::Result<PathBuf> {
317322
pub fn copy(_from: &Path, _to: &Path) -> io::Result<u64> {
318323
unsupported()
319324
}
325+
326+
mod uefi_fs {
327+
use r_efi::protocols::{device_path, file, simple_file_system};
328+
329+
use crate::boxed::Box;
330+
use crate::io;
331+
use crate::path::Path;
332+
use crate::ptr::NonNull;
333+
use crate::sys::helpers;
334+
335+
pub(crate) struct File(NonNull<file::Protocol>);
336+
337+
impl File {
338+
pub(crate) fn from_path(path: &Path, open_mode: u64, attr: u64) -> io::Result<Self> {
339+
let absolute = crate::path::absolute(path)?;
340+
341+
let p = helpers::OwnedDevicePath::from_text(absolute.as_os_str())?;
342+
let (vol, mut path_remaining) = Self::open_volume_from_device_path(p.borrow())?;
343+
344+
vol.open(&mut path_remaining, open_mode, attr)
345+
}
346+
347+
/// Open Filesystem volume given a devicepath to the volume, or a file/directory in the
348+
/// volume. The path provided should be absolute UEFI device path, without any UEFI shell
349+
/// mappings.
350+
///
351+
/// Returns
352+
/// 1. The volume as a UEFI File
353+
/// 2. Path relative to the volume.
354+
///
355+
/// For example, given "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi",
356+
/// this will open the volume "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)"
357+
/// and return the remaining file path "\abc\run.efi".
358+
fn open_volume_from_device_path(
359+
path: helpers::BorrowedDevicePath<'_>,
360+
) -> io::Result<(Self, Box<[u16]>)> {
361+
let handles = match helpers::locate_handles(simple_file_system::PROTOCOL_GUID) {
362+
Ok(x) => x,
363+
Err(e) => return Err(e),
364+
};
365+
for handle in handles {
366+
let volume_device_path: NonNull<device_path::Protocol> =
367+
match helpers::open_protocol(handle, device_path::PROTOCOL_GUID) {
368+
Ok(x) => x,
369+
Err(_) => continue,
370+
};
371+
let volume_device_path = helpers::BorrowedDevicePath::new(volume_device_path);
372+
373+
if let Some(left_path) = path_best_match(&volume_device_path, &path) {
374+
return Ok((Self::open_volume(handle)?, left_path));
375+
}
376+
}
377+
378+
Err(io::const_error!(io::ErrorKind::NotFound, "Volume Not Found"))
379+
}
380+
381+
// Open volume on device_handle using SIMPLE_FILE_SYSTEM_PROTOCOL
382+
fn open_volume(device_handle: NonNull<crate::ffi::c_void>) -> io::Result<Self> {
383+
let simple_file_system_protocol = helpers::open_protocol::<simple_file_system::Protocol>(
384+
device_handle,
385+
simple_file_system::PROTOCOL_GUID,
386+
)?;
387+
388+
let mut file_protocol = crate::ptr::null_mut();
389+
let r = unsafe {
390+
((*simple_file_system_protocol.as_ptr()).open_volume)(
391+
simple_file_system_protocol.as_ptr(),
392+
&mut file_protocol,
393+
)
394+
};
395+
if r.is_error() {
396+
return Err(io::Error::from_raw_os_error(r.as_usize()));
397+
}
398+
399+
// Since no error was returned, file protocol should be non-NULL.
400+
let p = NonNull::new(file_protocol).unwrap();
401+
Ok(Self(p))
402+
}
403+
404+
fn open(&self, path: &mut [u16], open_mode: u64, attr: u64) -> io::Result<Self> {
405+
let file_ptr = self.0.as_ptr();
406+
let mut file_opened = crate::ptr::null_mut();
407+
408+
let r = unsafe {
409+
((*file_ptr).open)(file_ptr, &mut file_opened, path.as_mut_ptr(), open_mode, attr)
410+
};
411+
412+
if r.is_error() {
413+
return Err(io::Error::from_raw_os_error(r.as_usize()));
414+
}
415+
416+
// Since no error was returned, file protocol should be non-NULL.
417+
let p = NonNull::new(file_opened).unwrap();
418+
Ok(File(p))
419+
}
420+
}
421+
422+
impl Drop for File {
423+
fn drop(&mut self) {
424+
let file_ptr = self.0.as_ptr();
425+
let _ = unsafe { ((*self.0.as_ptr()).close)(file_ptr) };
426+
}
427+
}
428+
429+
/// A helper to check that target path is a descendent of source. It is expected to be used with
430+
/// absolute UEFI device paths without any UEFI shell mappings.
431+
///
432+
/// Returns the path relative to source
433+
///
434+
/// For example, given "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/" and
435+
/// "PciRoot(0x0)/Pci(0x1,0x1)/Ata(Secondary,Slave,0x0)/\abc\run.efi", this will return
436+
/// "\abc\run.efi"
437+
fn path_best_match(
438+
source: &helpers::BorrowedDevicePath<'_>,
439+
target: &helpers::BorrowedDevicePath<'_>,
440+
) -> Option<Box<[u16]>> {
441+
let mut source_iter = source.iter().take_while(|x| !x.is_end_instance());
442+
let mut target_iter = target.iter().take_while(|x| !x.is_end_instance());
443+
444+
loop {
445+
match (source_iter.next(), target_iter.next()) {
446+
(Some(x), Some(y)) if x == y => continue,
447+
(None, Some(y)) => {
448+
let p = y.to_path().to_text().ok()?;
449+
return helpers::os_string_to_raw(&p);
450+
}
451+
_ => return None,
452+
}
453+
}
454+
}
455+
}

library/std/src/sys/pal/uefi/helpers.rs

-3
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ impl<'a> BorrowedDevicePath<'a> {
374374
device_path_to_text(self.protocol)
375375
}
376376

377-
#[expect(dead_code)]
378377
pub(crate) const fn iter(&'a self) -> DevicePathIterator<'a> {
379378
DevicePathIterator::new(DevicePathNode::new(self.protocol))
380379
}
@@ -452,7 +451,6 @@ impl<'a> DevicePathNode<'a> {
452451
&& self.sub_type() == r_efi::protocols::device_path::End::SUBTYPE_ENTIRE
453452
}
454453

455-
#[expect(dead_code)]
456454
pub(crate) const fn is_end_instance(&self) -> bool {
457455
self.node_type() == r_efi::protocols::device_path::TYPE_END
458456
&& self.sub_type() == r_efi::protocols::device_path::End::SUBTYPE_INSTANCE
@@ -468,7 +466,6 @@ impl<'a> DevicePathNode<'a> {
468466
Self::new(node)
469467
}
470468

471-
#[expect(dead_code)]
472469
pub(crate) fn to_path(&'a self) -> BorrowedDevicePath<'a> {
473470
BorrowedDevicePath::new(self.protocol)
474471
}

0 commit comments

Comments
 (0)