mod dir;
mod info;
mod regular;
use crate::{CStr16, Result, Status, StatusExt};
use core::ffi::c_void;
use core::fmt::Debug;
use core::{mem, ptr};
use uefi_raw::protocol::file_system::FileProtocolV1;
#[cfg(feature = "alloc")]
use {crate::mem::make_boxed, alloc::boxed::Box};
pub use dir::Directory;
pub use info::{
FileInfo, FileInfoCreationError, FileProtocolInfo, FileSystemInfo, FileSystemVolumeLabel,
FromUefi,
};
pub use regular::RegularFile;
pub use uefi_raw::protocol::file_system::FileAttribute;
pub trait File: Sized {
#[doc(hidden)]
fn handle(&mut self) -> &mut FileHandle;
fn open(
&mut self,
filename: &CStr16,
open_mode: FileMode,
attributes: FileAttribute,
) -> Result<FileHandle> {
let mut ptr = ptr::null_mut();
unsafe {
(self.imp().open)(
self.imp(),
&mut ptr,
filename.as_ptr().cast(),
uefi_raw::protocol::file_system::FileMode::from_bits_truncate(open_mode as u64),
attributes,
)
}
.to_result_with_val(|| unsafe { FileHandle::new(ptr) })
}
fn close(self) {}
fn delete(mut self) -> Result {
let result = unsafe { (self.imp().delete)(self.imp()) }.to_result();
mem::forget(self);
result
}
fn get_info<'buf, Info: FileProtocolInfo + ?Sized>(
&mut self,
buffer: &'buf mut [u8],
) -> Result<&'buf mut Info, Option<usize>> {
let mut buffer_size = buffer.len();
Info::assert_aligned(buffer);
unsafe {
(self.imp().get_info)(
self.imp(),
&Info::GUID,
&mut buffer_size,
buffer.as_mut_ptr().cast(),
)
}
.to_result_with(
|| unsafe { Info::from_uefi(buffer.as_mut_ptr().cast::<c_void>()) },
|s| {
if s == Status::BUFFER_TOO_SMALL {
Some(buffer_size)
} else {
None
}
},
)
}
fn set_info<Info: FileProtocolInfo + ?Sized>(&mut self, info: &Info) -> Result {
let info_ptr = ptr::from_ref(info).cast::<c_void>();
let info_size = size_of_val(info);
unsafe { (self.imp().set_info)(self.imp(), &Info::GUID, info_size, info_ptr).to_result() }
}
fn flush(&mut self) -> Result {
unsafe { (self.imp().flush)(self.imp()) }.to_result()
}
#[cfg(feature = "alloc")]
fn get_boxed_info<Info: FileProtocolInfo + ?Sized + Debug>(&mut self) -> Result<Box<Info>> {
let fetch_data_fn = |buf| self.get_info::<Info>(buf);
let file_info = make_boxed::<Info, _>(fetch_data_fn)?;
Ok(file_info)
}
fn is_regular_file(&self) -> Result<bool>;
fn is_directory(&self) -> Result<bool>;
}
trait FileInternal: File {
fn imp(&mut self) -> &mut FileProtocolV1 {
unsafe { &mut *self.handle().0 }
}
}
impl<T: File> FileInternal for T {}
#[repr(transparent)]
#[derive(Debug)]
pub struct FileHandle(*mut FileProtocolV1);
impl FileHandle {
pub(super) const unsafe fn new(ptr: *mut FileProtocolV1) -> Self {
Self(ptr)
}
pub fn into_type(self) -> Result<FileType> {
use FileType::*;
self.is_regular_file().map(|is_file| {
if is_file {
unsafe { Regular(RegularFile::new(self)) }
} else {
unsafe { Dir(Directory::new(self)) }
}
})
}
#[must_use]
pub fn into_directory(self) -> Option<Directory> {
if let Ok(FileType::Dir(dir)) = self.into_type() {
Some(dir)
} else {
None
}
}
#[must_use]
pub fn into_regular_file(self) -> Option<RegularFile> {
if let Ok(FileType::Regular(regular)) = self.into_type() {
Some(regular)
} else {
None
}
}
}
impl File for FileHandle {
#[inline]
fn handle(&mut self) -> &mut FileHandle {
self
}
fn is_regular_file(&self) -> Result<bool> {
let this = unsafe { self.0.as_mut().unwrap() };
let mut pos = 0;
match unsafe { (this.get_position)(this, &mut pos) } {
Status::SUCCESS => Ok(true),
Status::UNSUPPORTED => Ok(false),
s => Err(s.into()),
}
}
fn is_directory(&self) -> Result<bool> {
self.is_regular_file().map(|b| !b)
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
let result: Result = unsafe { (self.imp().close)(self.imp()) }.to_result();
result.expect("Failed to close file");
}
}
#[derive(Debug)]
pub enum FileType {
Regular(RegularFile),
Dir(Directory),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(u64)]
pub enum FileMode {
Read = 1,
ReadWrite = 2 | 1,
CreateReadWrite = (1 << 63) | 2 | 1,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::Time;
use crate::{CString16, Guid, Identify};
use ::alloc::vec;
use uefi_raw::protocol::file_system::FileProtocolRevision;
#[test]
fn test_get_boxed_info() {
let mut file_impl = FileProtocolV1 {
revision: FileProtocolRevision::REVISION_1,
open: stub_open,
close: stub_close,
delete: stub_delete,
read: stub_read,
write: stub_write,
get_position: stub_get_position,
set_position: stub_set_position,
get_info: stub_get_info,
set_info: stub_set_info,
flush: stub_flush,
};
let file_handle = FileHandle(&mut file_impl);
let mut file = unsafe { RegularFile::new(file_handle) };
let info = file.get_boxed_info::<FileInfo>().unwrap();
assert_eq!(info.file_size(), 123);
assert_eq!(info.file_name(), CString16::try_from("test_file").unwrap());
}
unsafe extern "efiapi" fn stub_get_info(
_this: *mut FileProtocolV1,
information_type: *const Guid,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> Status {
assert_eq!(unsafe { *information_type }, FileInfo::GUID);
let mut tmp = vec![0; 128];
let file_size = 123;
let physical_size = 456;
let time = Time::invalid();
let info = FileInfo::new(
&mut tmp,
file_size,
physical_size,
time,
time,
time,
FileAttribute::empty(),
&CString16::try_from("test_file").unwrap(),
)
.unwrap();
let required_size = size_of_val(info);
if unsafe { *buffer_size } < required_size {
unsafe {
*buffer_size = required_size;
}
Status::BUFFER_TOO_SMALL
} else {
unsafe {
ptr::copy_nonoverlapping(
core::ptr::from_ref::<FileInfo>(info).cast(),
buffer,
required_size,
);
}
unsafe {
*buffer_size = required_size;
}
Status::SUCCESS
}
}
const extern "efiapi" fn stub_open(
_this: *mut FileProtocolV1,
_new_handle: *mut *mut FileProtocolV1,
_filename: *const uefi_raw::Char16,
_open_mode: uefi_raw::protocol::file_system::FileMode,
_attributes: FileAttribute,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_close(_this: *mut FileProtocolV1) -> Status {
Status::SUCCESS
}
const extern "efiapi" fn stub_delete(_this: *mut FileProtocolV1) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_read(
_this: *mut FileProtocolV1,
_buffer_size: *mut usize,
_buffer: *mut c_void,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_write(
_this: *mut FileProtocolV1,
_buffer_size: *mut usize,
_buffer: *const c_void,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_get_position(
_this: *const FileProtocolV1,
_position: *mut u64,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_set_position(
_this: *mut FileProtocolV1,
_position: u64,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_set_info(
_this: *mut FileProtocolV1,
_information_type: *const Guid,
_buffer_size: usize,
_buffer: *const c_void,
) -> Status {
Status::UNSUPPORTED
}
const extern "efiapi" fn stub_flush(_this: *mut FileProtocolV1) -> Status {
Status::UNSUPPORTED
}
}