diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b0c51e1..f0588e5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ ### Added - There is a new `fs` module that provides a high-level API for file-system - access. The API is close to the `std::fs` module. + access. The API is close to the `std::fs` module. The module also provides a + `Path` and a `PathBuf` abstraction that is similar to the ones from + `std::path`. However, they are adapted for UEFI. - Multiple convenience methods for `CString16` and `CStr16`, including: - `CStr16::as_slice()` - `CStr16::num_chars()` diff --git a/uefi-test-runner/src/fs/mod.rs b/uefi-test-runner/src/fs/mod.rs index 8ff38ac64..daf895e32 100644 --- a/uefi-test-runner/src/fs/mod.rs +++ b/uefi-test-runner/src/fs/mod.rs @@ -2,7 +2,8 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use uefi::fs::{FileSystem, FileSystemError}; +use uefi::cstr16; +use uefi::fs::{FileSystem, FileSystemError, PathBuf}; use uefi::proto::media::fs::SimpleFileSystem; use uefi::table::boot::ScopedProtocol; @@ -11,47 +12,50 @@ use uefi::table::boot::ScopedProtocol; pub fn test(sfs: ScopedProtocol) -> Result<(), FileSystemError> { let mut fs = FileSystem::new(sfs); - fs.create_dir("test_file_system_abs")?; + // test create dir + fs.create_dir(cstr16!("foo_dir"))?; - // slash is transparently transformed to backslash - fs.write("test_file_system_abs/foo", "hello")?; - // absolute or relative paths are supported; ./ is ignored - fs.copy("\\test_file_system_abs/foo", "\\test_file_system_abs/./bar")?; - let read = fs.read("\\test_file_system_abs\\bar")?; + // test write, copy, and read + let data_to_write = "hello world"; + fs.write(cstr16!("foo_dir\\foo"), data_to_write)?; + // Here, we additionally check that absolute paths work. + fs.copy(cstr16!("\\foo_dir\\foo"), cstr16!("\\foo_dir\\foo_cpy"))?; + let read = fs.read(cstr16!("foo_dir\\foo_cpy"))?; let read = String::from_utf8(read).expect("Should be valid utf8"); - assert_eq!(read, "hello"); - - assert_eq!( - fs.try_exists("test_file_system_abs\\barfoo"), - Err(FileSystemError::OpenError( - "\\test_file_system_abs\\barfoo".to_string() - )) - ); - fs.rename("test_file_system_abs\\bar", "test_file_system_abs\\barfoo")?; - assert!(fs.try_exists("test_file_system_abs\\barfoo").is_ok()); - + assert_eq!(read.as_str(), data_to_write); + + // test copy from non-existent file + let err = fs.copy(cstr16!("not_found"), cstr16!("abc")); + assert!(matches!(err, Err(FileSystemError::OpenError { .. }))); + + // test rename file + path buf replaces / with \ + fs.rename( + PathBuf::from(cstr16!("/foo_dir/foo_cpy")), + cstr16!("foo_dir\\foo_cpy2"), + )?; + // file should not be available after rename + let err = fs.read(cstr16!("foo_dir\\foo_cpy")); + assert!(matches!(err, Err(FileSystemError::OpenError { .. }))); + + // test read dir on a sub dir let entries = fs - .read_dir("test_file_system_abs")? - .map(|e| { - e.expect("Should return boxed file info") - .file_name() - .to_string() - }) + .read_dir(cstr16!("foo_dir"))? + .map(|entry| entry.expect("Should be valid").file_name().to_string()) .collect::>(); - assert_eq!(&[".", "..", "foo", "barfoo"], entries.as_slice()); - - fs.create_dir("/deeply_nested_test")?; - fs.create_dir("/deeply_nested_test/1")?; - fs.create_dir("/deeply_nested_test/1/2")?; - fs.create_dir("/deeply_nested_test/1/2/3")?; - fs.create_dir("/deeply_nested_test/1/2/3/4")?; - fs.create_dir_all("/deeply_nested_test/1/2/3/4/5/6/7")?; - fs.try_exists("/deeply_nested_test/1/2/3/4/5/6/7")?; + assert_eq!(&[".", "..", "foo", "foo_cpy2"], entries.as_slice()); + + // test create dir recursively + fs.create_dir_all(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7"))?; + fs.create_dir_all(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8"))?; + fs.write( + cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8\\foobar"), + data_to_write, + )?; + let boxinfo = fs.metadata(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8\\foobar"))?; + assert_eq!(boxinfo.file_size(), data_to_write.len() as u64); + + // test remove dir all // TODO - // fs.remove_dir_all("/deeply_nested_test/1/2/3/4/5/6/7")?; - fs.remove_dir("/deeply_nested_test/1/2/3/4/5/6/7")?; - let exists = matches!(fs.try_exists("/deeply_nested_test/1/2/3/4/5/6/7"), Ok(_)); - assert!(!exists); Ok(()) } diff --git a/uefi/src/data_types/strs.rs b/uefi/src/data_types/strs.rs index 38644976e..d43dcc3bd 100644 --- a/uefi/src/data_types/strs.rs +++ b/uefi/src/data_types/strs.rs @@ -175,7 +175,7 @@ impl<'a> TryFrom<&'a CStr> for &'a CStr8 { } } -/// An UCS-2 null-terminated string. +/// An UCS-2 null-terminated string slice. /// /// This type is largely inspired by [`core::ffi::CStr`] with the exception that all characters are /// guaranteed to be 16 bit long. @@ -423,6 +423,12 @@ impl + ?Sized> EqStrUntilNul for CStr16 { } } +impl AsRef for CStr16 { + fn as_ref(&self) -> &CStr16 { + self + } +} + /// An iterator over the [`Char16`]s in a [`CStr16`]. #[derive(Debug)] pub struct CStr16Iter<'a> { diff --git a/uefi/src/fs/file_system.rs b/uefi/src/fs/file_system.rs index 68f800b42..41dd78c15 100644 --- a/uefi/src/fs/file_system.rs +++ b/uefi/src/fs/file_system.rs @@ -1,17 +1,18 @@ //! Module for [`FileSystem`]. use super::*; +use crate::fs::path::{validate_path, PathError}; use crate::proto::media::file::{FileAttribute, FileInfo, FileType}; use crate::table::boot::ScopedProtocol; use alloc::boxed::Box; use alloc::string::{FromUtf8Error, String, ToString}; +use alloc::vec; use alloc::vec::Vec; -use alloc::{format, vec}; use core::fmt; use core::fmt::{Debug, Formatter}; use core::ops::Deref; use derive_more::Display; -use log::info; +use log::debug; /// All errors that can happen when working with the [`FileSystem`]. #[derive(Debug, Clone, Display, PartialEq, Eq)] @@ -19,6 +20,8 @@ pub enum FileSystemError { /// Can't open the root directory of the underlying volume. CantOpenVolume, /// The path is invalid because of the underlying [`PathError`]. + /// + /// [`PathError`]: path::PathError IllegalPath(PathError), /// The file or directory was not found in the underlying volume. FileNotFound(String), @@ -40,12 +43,28 @@ pub enum FileSystemError { ReadFailure, /// Can't parse file content as UTF-8. Utf8Error(FromUtf8Error), - /// Could not open the given path. - OpenError(String), + /// Could not open the given path. Carries the path that could not be opened + /// and the underlying UEFI error. + #[display(fmt = "{path:?}")] + OpenError { + /// Path that caused the failure. + path: String, + /// More detailed failure description. + error: crate::Error, + }, } #[cfg(feature = "unstable")] -impl core::error::Error for FileSystemError {} +impl core::error::Error for FileSystemError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + match self { + FileSystemError::IllegalPath(e) => Some(e), + FileSystemError::Utf8Error(e) => Some(e), + FileSystemError::OpenError { path: _path, error } => Some(error), + _ => None, + } + } +} impl From for FileSystemError { fn from(err: PathError) -> Self { @@ -90,10 +109,6 @@ impl<'a> FileSystem<'a> { let path = path.as_ref(); self.open(path, UefiFileMode::CreateReadWrite, true) .map(|_| ()) - .map_err(|err| { - log::debug!("failed to fetch file info: {err:#?}"); - FileSystemError::OpenError(path.to_string()) - }) } /// Recursively create a directory and all of its parent components if they @@ -101,33 +116,38 @@ impl<'a> FileSystem<'a> { pub fn create_dir_all(&mut self, path: impl AsRef) -> FileSystemResult<()> { let path = path.as_ref(); - let normalized_path = NormalizedPath::new("\\", path)?; - let normalized_path_string = normalized_path.to_string(); - let normalized_path_pathref = Path::new(&normalized_path_string); + // Collect all relevant sub paths in a vector. + let mut dirs_to_create = vec![path.to_path_buf()]; + while let Some(parent) = dirs_to_create.last().unwrap().parent() { + debug!("parent={parent}"); + dirs_to_create.push(parent) + } + // Now reverse, so that we have something like this: + // - a + // - a\\b + // - a\\b\\c + dirs_to_create.reverse(); + + for parent in dirs_to_create { + if self.try_exists(&parent).is_err() { + self.create_dir(parent)?; + } + } - let iter = || normalized_path_pathref.components(SEPARATOR); - iter() - .scan(String::new(), |path_acc, component| { - if component != Component::RootDir { - *path_acc += SEPARATOR_STR; - *path_acc += format!("{component}").as_str(); - } - info!("path_acc: {path_acc}, component: {component}"); - Some((component, path_acc.clone())) - }) - .try_for_each(|(_component, full_path)| self.create_dir(full_path.as_str())) + Ok(()) } /// Given a path, query the file system to get information about a file, /// directory, etc. Returns [`UefiFileInfo`]. pub fn metadata(&mut self, path: impl AsRef) -> FileSystemResult> { let path = path.as_ref(); - let file = self.open(path, UefiFileMode::Read, false)?; - log::debug!("{:#?}", &file.into_type().unwrap()); let mut file = self.open(path, UefiFileMode::Read, false)?; file.get_boxed_info().map_err(|err| { - log::debug!("failed to fetch file info: {err:#?}"); - FileSystemError::OpenError(path.to_string()) + log::trace!("failed to fetch file info: {err:#?}"); + FileSystemError::OpenError { + path: path.to_cstr16().to_string(), + error: err, + } }) } @@ -138,11 +158,13 @@ impl<'a> FileSystem<'a> { let mut file = self .open(path, UefiFileMode::Read, false)? .into_regular_file() - .ok_or(FileSystemError::NotAFile(path.as_str().to_string()))?; - let info = file.get_boxed_info::().map_err(|e| { - log::error!("get info failed: {e:?}"); - FileSystemError::OpenError(path.as_str().to_string()) - })?; + .ok_or(FileSystemError::NotAFile(path.to_cstr16().to_string()))?; + let info = file + .get_boxed_info::() + .map_err(|err| FileSystemError::OpenError { + path: path.to_cstr16().to_string(), + error: err, + })?; let mut vec = vec![0; info.file_size() as usize]; let read_bytes = file.read(vec.as_mut_slice()).map_err(|e| { @@ -164,7 +186,7 @@ impl<'a> FileSystem<'a> { let dir = self .open(path, UefiFileMode::Read, false)? .into_directory() - .ok_or(FileSystemError::NotADirectory(path.as_str().to_string()))?; + .ok_or(FileSystemError::NotADirectory(path.to_cstr16().to_string()))?; Ok(UefiDirectoryIter::new(dir)) } @@ -185,16 +207,18 @@ impl<'a> FileSystem<'a> { match file { FileType::Dir(dir) => dir.delete().map_err(|e| { log::error!("error removing dir: {e:?}"); - FileSystemError::CantDeleteDirectory(path.as_str().to_string()) + FileSystemError::CantDeleteDirectory(path.to_cstr16().to_string()) }), - FileType::Regular(_) => Err(FileSystemError::NotADirectory(path.as_str().to_string())), + FileType::Regular(_) => { + Err(FileSystemError::NotADirectory(path.to_cstr16().to_string())) + } } } /*/// Removes a directory at this path, after removing all its contents. Use /// carefully! - pub fn remove_dir_all(&mut self, _path: impl AsRef) -> FileSystemResult<()> { - todo!() + pub fn remove_dir_all(&mut self, path: impl AsRef) -> FileSystemResult<()> { + let path = path.as_ref(); }*/ /// Removes a file from the filesystem. @@ -209,9 +233,9 @@ impl<'a> FileSystem<'a> { match file { FileType::Regular(file) => file.delete().map_err(|e| { log::error!("error removing file: {e:?}"); - FileSystemError::CantDeleteFile(path.as_str().to_string()) + FileSystemError::CantDeleteFile(path.to_cstr16().to_string()) }), - FileType::Dir(_) => Err(FileSystemError::NotAFile(path.as_str().to_string())), + FileType::Dir(_) => Err(FileSystemError::NotAFile(path.to_cstr16().to_string())), } } @@ -278,8 +302,8 @@ impl<'a> FileSystem<'a> { mode: UefiFileMode, is_dir: bool, ) -> FileSystemResult { - let path = NormalizedPath::new("\\", path)?; - log::debug!("normalized path: {path}"); + validate_path(path)?; + log::trace!("open validated path: {path}"); let attr = if mode == UefiFileMode::CreateReadWrite && is_dir { FileAttribute::DIRECTORY @@ -287,10 +311,15 @@ impl<'a> FileSystem<'a> { FileAttribute::empty() }; - self.open_root()?.open(&path, mode, attr).map_err(|x| { - log::trace!("Can't open file {path}: {x:?}"); - FileSystemError::OpenError(path.to_string()) - }) + self.open_root()? + .open(path.to_cstr16(), mode, attr) + .map_err(|err| { + log::trace!("Can't open file {path}: {err:?}"); + FileSystemError::OpenError { + path: path.to_cstr16().to_string(), + error: err, + } + }) } } diff --git a/uefi/src/fs/mod.rs b/uefi/src/fs/mod.rs index 44d8618b4..18253ebce 100644 --- a/uefi/src/fs/mod.rs +++ b/uefi/src/fs/mod.rs @@ -1,14 +1,14 @@ -//! A high-level file system API for UEFI applications close to the `fs` module -//! from Rust's standard library. +//! A high-level file system API for UEFI applications close to the `std::fs` +//! module from Rust's standard library. The main type by this module is +//! [`FileSystem`]. //! //! # Difference to typical File System Abstractions //! Users perform actions on dedicated volumes: For example, the boot volume, //! such as a CD-rom, USB-stick, or any other storage device. //! //! Unlike in the API of typical UNIX file system abstractions, there is -//! no virtual file system. -//! -//! Unlike Windows, there is no way to access volumes by a dedicated name. +//! no virtual file system. Unlike in Windows, there is no way to access volumes +//! by a dedicated name. //! //! # Paths //! All paths are absolute and follow the FAT-like file system conventions for @@ -17,7 +17,8 @@ //! directory is always `/`, i.e., the root, of the opened volume. //! //! Symlinks or hard-links are not supported but only directories and regular -//! files with plain linear paths to them. +//! files with plain linear paths to them. For more information, see +//! [`Path`] and [`PathBuf`]. //! //! # API Hints //! There are no `File` and `Path` abstractions similar to those from `std` that @@ -31,14 +32,11 @@ mod dir_entry_iter; mod file_system; -mod normalized_path; mod path; mod uefi_types; pub use file_system::{FileSystem, FileSystemError, FileSystemResult}; -pub use normalized_path::{PathError, SEPARATOR, SEPARATOR_STR}; +pub use path::*; use dir_entry_iter::*; -use normalized_path::*; -use path::*; use uefi_types::*; diff --git a/uefi/src/fs/normalized_path.rs b/uefi/src/fs/normalized_path.rs deleted file mode 100644 index 2a93237f5..000000000 --- a/uefi/src/fs/normalized_path.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! Module for path normalization. See [`NormalizedPath`]. - -use super::*; -use crate::data_types::FromStrError; -use crate::CString16; -use alloc::format; -use alloc::string::{String, ToString}; -use alloc::vec::Vec; -use core::ops::Deref; -use derive_more::Display; - -/// The default separator for paths. -pub const SEPARATOR: char = '\\'; - -/// Stringifyed version of [`SEPARATOR`]. -pub const SEPARATOR_STR: &str = "\\"; - -/// Errors that may happen during path normalization.. -#[derive(Debug, Clone, Eq, PartialEq, Display)] -pub enum PathError { - /// The specified present working directory is not absolute. - PwdNotAbsolute, - /// The path is empty. - Empty, - /// There are illegal characters in the path. - IllegalCharacters(CharactersError), -} - -#[cfg(feature = "unstable")] -impl core::error::Error for PathError {} - -#[derive(Debug, Clone, Eq, PartialEq, Display)] -pub enum CharactersError { - ProhibitedSymbols, - NonUCS2Compatible(FromStrError), -} - -#[cfg(feature = "unstable")] -impl core::error::Error for CharactersError {} - -/// **Internal API (so far).** -/// -/// Unlike a [`Path`], which is close to the implementation of the Rust -/// standard library, this abstraction is an absolute path that is valid in -/// FAT-like file systems (which are supported by UEFI and can be accessed via -/// the file system protocol). -/// -/// Hence, it is called normalized path. Another term might be canonicalized -/// path. -/// -/// For compatibility with the UEFI file-system protocol, this is a -/// [`CString16`]. The separator is `\`. For convenience, all occurrences of `/` -/// are transparently replaced by `\`. -/// -/// A normalized path is always absolute, i.e., starts at the root directory. -#[derive(Debug, Eq, PartialEq, Display)] -pub struct NormalizedPath(CString16); - -impl NormalizedPath { - /// Deny list of characters for path components. UEFI supports FAT-like file - /// systems. According to , - /// paths should not contain the following symbols. - pub const CHARACTER_DENY_LIST: [char; 10] = - ['\0', '"', '*', '/', ':', '<', '>', '?', '\\', '|']; - - /// Constructor. Combines the path with the present working directory (pwd) - /// if the `path` is relative. The resulting path is technically valid so - /// that it can be passed to the underlying file-system protocol. The - /// resulting path doesn't contain `.` or `..`. - /// - /// `pwd` is expected to be valid. - pub fn new(pwd: impl AsRef, path: impl AsRef) -> Result { - let pwd = pwd.as_ref(); - let path = path.as_ref(); - - let path = Self::normalize_separator(path); - let path = Path::new(path.as_str()); - - Self::check_pwd_absolute(pwd)?; - Self::check_prohibited_chars(path)?; - - let path = Self::combine_path_with_pwd(pwd, path); - - Self::build_normalized_path(path.as_str().as_ref()) - } - - /// Checks if the pwd is an absolute path. - fn check_pwd_absolute(pwd: &Path) -> Result<(), PathError> { - if !pwd.as_str().starts_with(SEPARATOR) { - return Err(PathError::PwdNotAbsolute); - } - Ok(()) - } - - /// Replaces all occurrences of `/` with [`SEPARATOR`]. - fn normalize_separator(path: &Path) -> String { - path.as_str().replace('/', SEPARATOR_STR) - } - - /// Checks that each component of type [`Component::Normal`] doesn't contain - /// any of the prohibited characters specified in - /// [`Self::CHARACTER_DENY_LIST`]. - fn check_prohibited_chars(path: &Path) -> Result<(), PathError> { - let prohibited_character_found = path - .components(SEPARATOR) - .filter_map(|c| match c { - Component::Normal(n) => Some(n), - _ => None, - }) - .flat_map(|c| c.chars()) - .any(|c| Self::CHARACTER_DENY_LIST.contains(&c)); - - (!prohibited_character_found) - .then_some(()) - .ok_or(PathError::IllegalCharacters( - CharactersError::ProhibitedSymbols, - )) - } - - /// Merges `pwd` and `path`, if `path` is not absolute. - fn combine_path_with_pwd(pwd: &Path, path: &Path) -> String { - let path_is_absolute = path.as_str().starts_with(SEPARATOR); - if path_is_absolute { - path.as_str().to_string() - } else { - // This concatenation is fine as pwd is an absolute path. - if pwd.as_str() == SEPARATOR_STR { - format!("{separator}{path}", separator = SEPARATOR) - } else { - format!("{pwd}{separator}{path}", separator = SEPARATOR) - } - } - } - - /// Consumes an absolute path and builds a `Self` from it. At this point, - /// the path is expected to have passed all sanity checks. The last step - /// is only relevant to resolve `.` and `..`. - fn build_normalized_path(path: &Path) -> Result { - let component_count = path.components(SEPARATOR).count(); - let mut normalized_components = Vec::with_capacity(component_count); - - for component in path.components(SEPARATOR) { - match component { - Component::RootDir => { - normalized_components.push(SEPARATOR_STR); - } - Component::CurDir => continue, - Component::ParentDir => { - normalized_components.remove(normalized_components.len() - 1); - } - Component::Normal(n) => { - let prev_has_sep = normalized_components - .last() - .map(|x| x.eq(&SEPARATOR_STR)) - .unwrap_or(false); - if !prev_has_sep { - normalized_components.push(SEPARATOR_STR); - } - normalized_components.push(n); - } - } - } - - let normalized_string: String = normalized_components.concat(); - CString16::try_from(normalized_string.as_str()) - .map(Self) - .map_err(|x| PathError::IllegalCharacters(CharactersError::NonUCS2Compatible(x))) - } -} - -impl Deref for NormalizedPath { - type Target = CString16; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn pwd_must_be_absolute() { - let path = NormalizedPath::new("", ""); - assert_eq!(Err(PathError::PwdNotAbsolute), path); - - let path = NormalizedPath::new(".", ""); - assert_eq!(Err(PathError::PwdNotAbsolute), path); - - let path = NormalizedPath::new("/", ""); - assert_eq!(Err(PathError::PwdNotAbsolute), path); - } - - #[test] - fn normalized_path() { - let path = NormalizedPath::new("\\foo", "/bar/barfoo").map(|x| x.0); - assert_eq!(path, Ok(CString16::try_from("\\bar\\barfoo").unwrap())); - - let path = NormalizedPath::new("\\foo", "bar/barfoo").map(|x| x.0); - assert_eq!(path, Ok(CString16::try_from("\\foo\\bar\\barfoo").unwrap())); - - let path = NormalizedPath::new("\\foo", "./bar/barfoo").map(|x| x.0); - assert_eq!(path, Ok(CString16::try_from("\\foo\\bar\\barfoo").unwrap())); - - let path = NormalizedPath::new("\\foo", "./bar/.././././barfoo").map(|x| x.0); - assert_eq!(path, Ok(CString16::try_from("\\foo\\barfoo").unwrap())); - - let path = NormalizedPath::new("\\", "foo").map(|x| x.0); - assert_eq!(path, Ok(CString16::try_from("\\foo").unwrap())); - } - - #[test] - fn check_components_for_allowed_chars() { - fn check_fail(path: impl AsRef) { - assert_eq!( - NormalizedPath::check_prohibited_chars(path.as_ref()), - Err(PathError::IllegalCharacters( - CharactersError::ProhibitedSymbols - )) - ); - } - - assert_eq!( - NormalizedPath::check_prohibited_chars("\\foo".as_ref()), - Ok(()) - ); - - check_fail("\\foo\0"); - check_fail("\\foo:"); - check_fail("\\foo*"); - check_fail("\\foo/"); - check_fail("\\foo<"); - check_fail("\\foo>"); - check_fail("\\foo?"); - check_fail("\\foo|"); - check_fail("\\foo\""); - } -} diff --git a/uefi/src/fs/path.rs b/uefi/src/fs/path.rs deleted file mode 100644 index 36f850241..000000000 --- a/uefi/src/fs/path.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Module for handling file-system paths in [`super::FileSystem`]. -//! See [`Path`]. - -use alloc::string::String; -use core::fmt::{Display, Formatter}; - -/// Path abstraction similar to `std::path::Path` but adapted to the platform- -/// agnostic `no_std` use case. It is up to the file-system implementation to -/// verify if a path is valid. -#[repr(transparent)] -#[derive(Debug)] -pub struct Path(str); - -impl Path { - /// Directly wraps a string slice as a `Path` slice. - pub fn new + ?Sized>(str: &S) -> &Self { - unsafe { &*(str.as_ref() as *const str as *const Path) } - } - - /// Returns the underlying `str`. - pub fn as_str(&self) -> &str { - self.as_ref() - } - - /// Returns an Iterator of type [`Components`]. - pub fn components(&self, separator: char) -> Components<'_> { - let split = self.0.split(separator); - Components::new(self, split) - } -} - -impl AsRef for str { - fn as_ref(&self) -> &Path { - Path::new(self) - } -} - -impl AsRef for Path { - fn as_ref(&self) -> &Path { - self - } -} - -impl AsRef for Path { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl AsRef for String { - fn as_ref(&self) -> &Path { - self.as_str().as_ref() - } -} - -impl Display for Path { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", &self.0) - } -} - -/// [`Iterator`] over the [`Component`]s of a [`Path`]. -#[derive(Debug)] -pub struct Components<'a> { - path: &'a Path, - split: core::str::Split<'a, char>, - i: usize, -} - -impl<'a> Components<'a> { - fn new(path: &'a Path, split: core::str::Split<'a, char>) -> Self { - Self { path, split, i: 0 } - } -} - -impl<'a> Iterator for Components<'a> { - type Item = Component<'a>; - - fn next(&mut self) -> Option { - if self.path.0.is_empty() { - return None; - }; - - self.split.next().map(|str| match str { - "." => Component::CurDir, - ".." => Component::ParentDir, - "" if self.i == 0 => Component::RootDir, - normal => Component::Normal(normal), - }) - } -} - -/// Components of a [`Path`]. -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum Component<'a> { - /// Current dir: `.` - CurDir, - /// Parent dir: `..` - ParentDir, - /// Root directory: `/` - RootDir, - /// Normal directory or filename. - Normal(&'a str), -} - -impl<'a> Display for Component<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self { - Component::CurDir => f.write_str(".\\"), - Component::ParentDir => f.write_str("..\\"), - Component::RootDir => f.write_str("\\"), - Component::Normal(normal) => f.write_str(normal), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec::Vec; - - #[test] - fn path_creation() { - let path_str = "/foo/bar/foobar"; - let _path = Path::new(path_str); - let path: &Path = path_str.as_ref(); - let _path: &Path = path.as_ref(); - } - - #[test] - fn path_components() { - let path_str = "/foo/./../bar/foobar"; - let path = Path::new(path_str); - assert_eq!(path_str, path.as_str()); - let components = path.components('/').collect::>(); - let expected = [ - Component::RootDir, - Component::Normal("foo"), - Component::CurDir, - Component::ParentDir, - Component::Normal("bar"), - Component::Normal("foobar"), - ]; - assert_eq!(components.as_slice(), expected.as_slice()); - - let path = Path::new("./foo"); - let components = path.components('/').collect::>(); - let expected = [Component::CurDir, Component::Normal("foo")]; - assert_eq!(components.as_slice(), expected.as_slice()); - - let path = Path::new(""); - assert_eq!(path.components('/').count(), 0); - } -} diff --git a/uefi/src/result/error.rs b/uefi/src/result/error.rs index 4cd657d83..8a88cc1b0 100644 --- a/uefi/src/result/error.rs +++ b/uefi/src/result/error.rs @@ -6,7 +6,7 @@ use core::fmt::{Debug, Display}; /// An UEFI-related error with optionally additional payload data. The error /// kind is encoded in the `status` field (see [`Status`]). Additional payload /// may be inside the `data` field. -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { status: Status, data: Data,