use r_efi::protocols::file; use crate::ffi::OsString; use crate::fmt; use crate::fs::TryLockError; use crate::hash::Hash; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut, SeekFrom}; use crate::path::{Path, PathBuf}; use crate::sys::time::SystemTime; use crate::sys::unsupported; #[expect(dead_code)] const FILE_PERMISSIONS_MASK: u64 = r_efi::protocols::file::READ_ONLY; pub struct File(!); #[derive(Clone)] pub struct FileAttr { attr: u64, size: u64, } pub struct ReadDir(!); pub struct DirEntry(!); #[derive(Clone, Debug)] pub struct OpenOptions { mode: u64, append: bool, truncate: bool, create_new: bool, } #[derive(Copy, Clone, Debug, Default)] pub struct FileTimes {} #[derive(Clone, PartialEq, Eq, Debug)] // Bool indicates if file is readonly pub struct FilePermissions(bool); #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] // Bool indicates if directory pub struct FileType(bool); #[derive(Debug)] pub struct DirBuilder; impl FileAttr { pub fn size(&self) -> u64 { self.size } pub fn perm(&self) -> FilePermissions { FilePermissions::from_attr(self.attr) } pub fn file_type(&self) -> FileType { FileType::from_attr(self.attr) } pub fn modified(&self) -> io::Result { unsupported() } pub fn accessed(&self) -> io::Result { unsupported() } pub fn created(&self) -> io::Result { unsupported() } } impl FilePermissions { pub fn readonly(&self) -> bool { self.0 } pub fn set_readonly(&mut self, readonly: bool) { self.0 = readonly } const fn from_attr(attr: u64) -> Self { Self(attr & r_efi::protocols::file::READ_ONLY != 0) } #[expect(dead_code)] const fn to_attr(&self) -> u64 { if self.0 { r_efi::protocols::file::READ_ONLY } else { 0 } } } impl FileTimes { pub fn set_accessed(&mut self, _t: SystemTime) {} pub fn set_modified(&mut self, _t: SystemTime) {} } impl FileType { pub fn is_dir(&self) -> bool { self.0 } pub fn is_file(&self) -> bool { !self.is_dir() } // Symlinks are not supported in UEFI pub fn is_symlink(&self) -> bool { false } const fn from_attr(attr: u64) -> Self { Self(attr & r_efi::protocols::file::DIRECTORY != 0) } } 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: 0, append: false, create_new: false, truncate: 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 { // Valid Combinations: Read, Read/Write, Read/Write/Create self.read(true); self.mode |= file::MODE_WRITE; } else { self.mode &= !file::MODE_WRITE; } } pub fn append(&mut self, append: bool) { // Docs state that `.write(true).append(true)` has the same effect as `.append(true)` if append { 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.mode |= file::MODE_CREATE; } else { self.mode &= !file::MODE_CREATE; } } pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; } #[expect(dead_code)] const fn is_mode_valid(&self) -> bool { // Valid Combinations: Read, Read/Write, Read/Write/Create self.mode == file::MODE_READ || self.mode == (file::MODE_READ | file::MODE_WRITE) || self.mode == (file::MODE_READ | file::MODE_WRITE | file::MODE_CREATE) } } impl File { pub fn open(_path: &Path, _opts: &OpenOptions) -> io::Result { unsupported() } pub fn file_attr(&self) -> io::Result { self.0 } pub fn fsync(&self) -> io::Result<()> { self.0 } pub fn datasync(&self) -> io::Result<()> { self.0 } pub fn lock(&self) -> io::Result<()> { self.0 } pub fn lock_shared(&self) -> io::Result<()> { self.0 } pub fn try_lock(&self) -> Result<(), TryLockError> { self.0 } pub fn try_lock_shared(&self) -> Result<(), TryLockError> { self.0 } pub fn unlock(&self) -> io::Result<()> { self.0 } pub fn truncate(&self, _size: u64) -> io::Result<()> { self.0 } pub fn read(&self, _buf: &mut [u8]) -> io::Result { self.0 } pub fn read_vectored(&self, _bufs: &mut [IoSliceMut<'_>]) -> io::Result { self.0 } pub fn is_read_vectored(&self) -> bool { self.0 } pub fn read_buf(&self, _cursor: BorrowedCursor<'_>) -> io::Result<()> { self.0 } pub fn write(&self, _buf: &[u8]) -> io::Result { self.0 } pub fn write_vectored(&self, _bufs: &[IoSlice<'_>]) -> io::Result { self.0 } pub fn is_write_vectored(&self) -> bool { self.0 } pub fn flush(&self) -> io::Result<()> { self.0 } pub fn seek(&self, _pos: SeekFrom) -> io::Result { self.0 } pub fn tell(&self) -> io::Result { self.0 } pub fn duplicate(&self) -> io::Result { self.0 } pub fn set_permissions(&self, _perm: FilePermissions) -> io::Result<()> { self.0 } pub fn set_times(&self, _times: FileTimes) -> io::Result<()> { self.0 } } impl DirBuilder { pub fn new() -> DirBuilder { DirBuilder } pub fn mkdir(&self, p: &Path) -> io::Result<()> { uefi_fs::mkdir(p) } } impl fmt::Debug for File { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0 } } 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<()> { unsupported() } pub fn rmdir(_p: &Path) -> io::Result<()> { unsupported() } pub fn remove_dir_all(_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 { 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 { unsupported() } pub fn lstat(p: &Path) -> io::Result { stat(p) } pub fn canonicalize(p: &Path) -> io::Result { crate::path::absolute(p) } 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, } } } /// An implementation of mkdir to allow creating new directory without having to open the /// volume twice (once for checking and once for creating) pub(crate) fn mkdir(path: &Path) -> io::Result<()> { let absolute = crate::path::absolute(path)?; let p = helpers::OwnedDevicePath::from_text(absolute.as_os_str())?; let (vol, mut path_remaining) = File::open_volume_from_device_path(p.borrow())?; // Check if file exists match vol.open(&mut path_remaining, file::MODE_READ, 0) { Ok(_) => { return Err(io::Error::new(io::ErrorKind::AlreadyExists, "Path already exists")); } Err(e) if e.kind() == io::ErrorKind::NotFound => {} Err(e) => return Err(e), } let _ = vol.open( &mut path_remaining, file::MODE_READ | file::MODE_WRITE | file::MODE_CREATE, file::DIRECTORY, )?; Ok(()) } }