use crate::Status;
use crate::fs::*;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use core::fmt::{Debug, Formatter};
use uefi::boot::ScopedProtocol;
pub type FileSystemResult<T> = Result<T, Error>;
pub struct FileSystem(ScopedProtocol<SimpleFileSystemProtocol>);
impl FileSystem {
#[must_use]
pub fn new(proto: impl Into<Self>) -> Self {
proto.into()
}
pub fn try_exists(&mut self, path: impl AsRef<Path>) -> FileSystemResult<bool> {
match self.open(path.as_ref(), UefiFileMode::Read, false) {
Ok(_) => Ok(true),
Err(Error::Io(err)) => {
if err.uefi_error.status() == Status::NOT_FOUND {
Ok(false)
} else {
Err(Error::Io(err))
}
}
Err(err) => Err(err),
}
}
pub fn copy(
&mut self,
src_path: impl AsRef<Path>,
dest_path: impl AsRef<Path>,
) -> FileSystemResult<()> {
let src_path = src_path.as_ref();
let dest_path = dest_path.as_ref();
let mut src = self
.open(src_path, UefiFileMode::Read, false)?
.into_regular_file()
.ok_or_else(|| {
Error::Io(IoError {
path: src_path.to_path_buf(),
context: IoErrorContext::NotAFile,
uefi_error: Status::INVALID_PARAMETER.into(),
})
})?;
let src_size = {
let src_info = src.get_boxed_info::<UefiFileInfo>().map_err(|err| {
Error::Io(IoError {
path: src_path.to_path_buf(),
context: IoErrorContext::Metadata,
uefi_error: err,
})
})?;
src_info.file_size()
};
let _ = self.remove_file(dest_path);
let mut dest = self
.open(dest_path, UefiFileMode::CreateReadWrite, false)?
.into_regular_file()
.ok_or_else(|| {
Error::Io(IoError {
path: dest_path.to_path_buf(),
context: IoErrorContext::OpenError,
uefi_error: Status::INVALID_PARAMETER.into(),
})
})?;
let mut chunk = vec![0; 1024 * 1024];
let mut remaining_size = src_size;
while remaining_size > 0 {
let num_bytes_read = src.read(&mut chunk).map_err(|err| {
Error::Io(IoError {
path: src_path.to_path_buf(),
context: IoErrorContext::ReadFailure,
uefi_error: err.to_err_without_payload(),
})
})?;
if num_bytes_read == 0 {
return Err(Error::Io(IoError {
path: src_path.to_path_buf(),
context: IoErrorContext::ReadFailure,
uefi_error: Status::ABORTED.into(),
}));
}
dest.write(&chunk[..num_bytes_read]).map_err(|err| {
Error::Io(IoError {
path: dest_path.to_path_buf(),
context: IoErrorContext::WriteFailure,
uefi_error: err.to_err_without_payload(),
})
})?;
remaining_size -= u64::try_from(num_bytes_read).unwrap();
}
dest.flush().map_err(|err| {
Error::Io(IoError {
path: dest_path.to_path_buf(),
context: IoErrorContext::FlushFailure,
uefi_error: err,
})
})?;
Ok(())
}
pub fn create_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
let path = path.as_ref();
self.open(path, UefiFileMode::CreateReadWrite, true)
.map(|_| ())
}
pub fn create_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
let path = path.as_ref();
let mut dirs_to_create = vec![path.to_path_buf()];
while let Some(parent) = dirs_to_create.last().unwrap().parent() {
dirs_to_create.push(parent)
}
dirs_to_create.reverse();
for parent in dirs_to_create {
if !self.try_exists(&parent)? {
self.create_dir(parent)?;
}
}
Ok(())
}
pub fn metadata(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Box<UefiFileInfo>> {
let path = path.as_ref();
let mut file = self.open(path, UefiFileMode::Read, false)?;
file.get_boxed_info().map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::Metadata,
uefi_error: err,
})
})
}
pub fn read(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Vec<u8>> {
let path = path.as_ref();
let mut file = self
.open(path, UefiFileMode::Read, false)?
.into_regular_file()
.ok_or_else(|| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::NotAFile,
uefi_error: Status::INVALID_PARAMETER.into(),
})
})?;
let info = file.get_boxed_info::<UefiFileInfo>().map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::Metadata,
uefi_error: err,
})
})?;
let mut vec = vec![0; info.file_size() as usize];
let read_bytes = file.read(vec.as_mut_slice()).map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::ReadFailure,
uefi_error: err.to_err_without_payload(),
})
})?;
if read_bytes != info.file_size() as usize {
log::error!("Did only read {}/{} bytes", info.file_size(), read_bytes);
}
Ok(vec)
}
pub fn read_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<UefiDirectoryIter> {
let path = path.as_ref();
let dir = self
.open(path, UefiFileMode::Read, false)?
.into_directory()
.ok_or_else(|| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::NotADirectory,
uefi_error: Status::INVALID_PARAMETER.into(),
})
})?;
Ok(UefiDirectoryIter::new(dir))
}
pub fn read_to_string(&mut self, path: impl AsRef<Path>) -> FileSystemResult<String> {
String::from_utf8(self.read(path)?).map_err(Error::Utf8Encoding)
}
pub fn remove_dir(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
let path = path.as_ref();
let file = self
.open(path, UefiFileMode::ReadWrite, false)?
.into_type()
.unwrap();
match file {
UefiFileType::Dir(dir) => dir.delete().map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::CantDeleteDirectory,
uefi_error: err,
})
}),
UefiFileType::Regular(_) => {
Err(Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::NotADirectory,
uefi_error: Status::INVALID_PARAMETER.into(),
}))
}
}
}
pub fn remove_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
let path = path.as_ref();
for file_info in self
.read_dir(path)?
.filter_map(|file_info_result| file_info_result.ok())
{
if COMMON_SKIP_DIRS.contains(&file_info.file_name()) {
continue;
}
let mut abs_entry_path = PathBuf::new();
abs_entry_path.push(path);
abs_entry_path.push(file_info.file_name());
if file_info.is_directory() {
self.remove_dir_all(&abs_entry_path)?;
} else {
self.remove_file(abs_entry_path)?;
}
}
self.remove_dir(path)?;
Ok(())
}
pub fn remove_file(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
let path = path.as_ref();
let file = self
.open(path, UefiFileMode::ReadWrite, false)?
.into_type()
.unwrap();
match file {
UefiFileType::Regular(file) => file.delete().map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::CantDeleteFile,
uefi_error: err,
})
}),
UefiFileType::Dir(_) => Err(Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::NotAFile,
uefi_error: Status::INVALID_PARAMETER.into(),
})),
}
}
pub fn rename(
&mut self,
src_path: impl AsRef<Path>,
dest_path: impl AsRef<Path>,
) -> FileSystemResult<()> {
self.copy(&src_path, dest_path)?;
self.remove_file(src_path)
}
pub fn write(
&mut self,
path: impl AsRef<Path>,
content: impl AsRef<[u8]>,
) -> FileSystemResult<()> {
let path = path.as_ref();
if self.try_exists(path)? {
self.remove_file(path)?;
}
let mut handle = self
.open(path, UefiFileMode::CreateReadWrite, false)?
.into_regular_file()
.unwrap();
handle.write(content.as_ref()).map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::WriteFailure,
uefi_error: err.to_err_without_payload(),
})
})?;
handle.flush().map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::FlushFailure,
uefi_error: err,
})
})?;
Ok(())
}
fn open_root(&mut self) -> FileSystemResult<UefiDirectoryHandle> {
self.0.open_volume().map_err(|err| {
Error::Io(IoError {
path: {
let mut path = PathBuf::new();
path.push(SEPARATOR_STR);
path
},
context: IoErrorContext::CantOpenVolume,
uefi_error: err,
})
})
}
fn open(
&mut self,
path: &Path,
mode: UefiFileMode,
create_dir: bool,
) -> FileSystemResult<UefiFileHandle> {
validate_path(path)?;
let attr = if mode == UefiFileMode::CreateReadWrite && create_dir {
UefiFileAttribute::DIRECTORY
} else {
UefiFileAttribute::empty()
};
self.open_root()?
.open(path.to_cstr16(), mode, attr)
.map_err(|err| {
Error::Io(IoError {
path: path.to_path_buf(),
context: IoErrorContext::OpenError,
uefi_error: err,
})
})
}
}
impl Debug for FileSystem {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let ptr: *const _ = &self.0;
f.debug_tuple("FileSystem").field(&ptr).finish()
}
}
impl From<uefi::boot::ScopedProtocol<SimpleFileSystemProtocol>> for FileSystem {
fn from(proto: uefi::boot::ScopedProtocol<SimpleFileSystemProtocol>) -> Self {
Self(proto)
}
}