Skip to content

Commit

Permalink
fs/path: Add Path and PathBuf abstractions
Browse files Browse the repository at this point in the history
  • Loading branch information
phip1611 committed May 4, 2023
1 parent 17e5aa0 commit 58d1961
Show file tree
Hide file tree
Showing 4 changed files with 553 additions and 0 deletions.
54 changes: 54 additions & 0 deletions uefi/src/fs/path/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! This module offers the [`Path`] and [`PathBuf`] abstractions.
//!
//! # Interoperability with Rust strings
//!
//! For the interoperability with Rust strings, i.e., `String` and `str` from
//! the standard library, the API is intended to transform these types first to
//! `CString16` respectively `CStr16`. They do not directly translate to
//! [`Path`] and [`PathBuf`].
//!
//! # Path Structure
//!
//! Paths use the [`SEPARATOR`] character as separator. Paths are absolute and
//! do not contain `.` or `..` components. However, this can be implemented in
//! the future.
mod path;
mod pathbuf;
mod validation;

pub use path::Path;
pub use path::Components;
pub use pathbuf::PathBuf;

use uefi::{CStr16, Char16};
pub(super) use validation::validate_path;
pub use validation::PathError;

/// The default separator for paths.
pub const SEPARATOR: char = '\\';
/// [`SEPARATOR`] but as useful UEFI type.
pub const SEPARATOR_C16: Char16 = unsafe { Char16::from_u16_unchecked('\\' as u16) };

/// Stringified version of [`SEPARATOR`].
pub const SEPARATOR_STR: &str = "\\";
/// [`SEPARATOR_STR`] but as useful UEFI type.
pub const SEPARATOR_CSTR16: &CStr16 = uefi_macros::cstr16!("\\");

/// Deny list of characters for path components. UEFI supports FAT-like file
/// systems. According to <https://en.wikipedia.org/wiki/Comparison_of_file_systems>,
/// paths should not contain the following symbols.
pub const CHARACTER_DENY_LIST: [Char16; 10] = unsafe {
[
Char16::from_u16_unchecked('\0' as u16),
Char16::from_u16_unchecked('"' as u16),
Char16::from_u16_unchecked('*' as u16),
Char16::from_u16_unchecked('/' as u16),
Char16::from_u16_unchecked(':' as u16),
Char16::from_u16_unchecked('<' as u16),
Char16::from_u16_unchecked('>' as u16),
Char16::from_u16_unchecked('?' as u16),
Char16::from_u16_unchecked('\\' as u16),
Char16::from_u16_unchecked('|' as u16),
]
};
266 changes: 266 additions & 0 deletions uefi/src/fs/path/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// allow "path.rs" in "path"
#![allow(clippy::module_inception)]

use crate::fs::path::{PathBuf, SEPARATOR_C16};
use crate::CStr16;
use core::fmt::{Display, Formatter};
use uefi::CString16;

/// A path similar to the `Path` of the standard library, but based on
/// [`CStr16`] strings and [`SEPARATOR`] as separator.
///
/// [`SEPARATOR`]: super::SEPARATOR
#[derive(Debug, PartialEq, Eq)]
pub struct Path(CStr16);

impl Path {
/// Constructor.
#[must_use]
pub fn new<S: AsRef<CStr16> + ?Sized>(s: &S) -> &Self {
unsafe { &*(s.as_ref() as *const CStr16 as *const Self) }
}

/// Returns the underlying string.
#[must_use]
pub fn to_cstr16(&self) -> &CStr16 {
&self.0
}

/// Returns a path buf from that type.
#[must_use]
pub fn to_path_buf(&self) -> PathBuf {
let cstring = CString16::from(&self.0);
PathBuf::from(cstring)
}

/// Iterator over the components of a path.
#[must_use]
pub fn components(&self) -> Components {
Components {
path: self.as_ref(),
i: 0,
}
}

/// Returns the parent directory as [`PathBuf`].
///
/// If the path is a top-level component, this returns None.
#[must_use]
pub fn parent(&self) -> Option<PathBuf> {
let components_count = self.components().count();
if components_count == 0 {
return None;
}

// Return None, as we do not treat "\\" as dedicated component.
let sep_count = self
.0
.as_slice()
.iter()
.filter(|char| **char == SEPARATOR_C16)
.count();
if sep_count == 0 {
return None;
}

let path =
self.components()
.take(components_count - 1)
.fold(CString16::new(), |mut acc, next| {
// Add separator, as needed.
if !acc.is_empty() && *acc.as_slice().last().unwrap() != SEPARATOR_C16 {
acc.push(SEPARATOR_C16);
}
acc.push_str(next.as_ref());
acc
});
let path = PathBuf::from(path);
Some(path)
}

/// Returns of the path is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
self.to_cstr16().is_empty()
}
}

impl Display for Path {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
Display::fmt(self.to_cstr16(), f)
}
}

/// Iterator over the components of a path. For example, the path `\\a\\b\\c`
/// has the components `[a, b, c]`. This is a more basic approach than the
/// components type of the standard library.
#[derive(Debug)]
pub struct Components<'a> {
path: &'a CStr16,
i: usize,
}

impl<'a> Iterator for Components<'a> {
// Attention. We can't iterate over &'Ctr16, as we would break any guarantee
// made for the terminating null character.
type Item = CString16;

fn next(&mut self) -> Option<Self::Item> {
if self.path.is_empty() {
return None;
}
if self.path.num_chars() == 1 && self.path.as_slice()[0] == SEPARATOR_C16 {
// The current implementation does not handle the root dir as
// dedicated component so far. We just return nothing.
return None;
}

// If the path is not empty and starts with a separator, skip it.
if self.i == 0 && *self.path.as_slice().first().unwrap() == SEPARATOR_C16 {
self.i = 1;
}

// Count how many characters are there until the next separator is
// found.
let len = self
.path
.iter()
.skip(self.i)
.take_while(|c| **c != SEPARATOR_C16)
.count();

let progress = self.i + len;
if progress > self.path.num_chars() {
None
} else {
// select the next component and build an owned string
let part = &self.path.as_slice()[self.i..self.i + len];
let mut string = CString16::new();
part.iter().for_each(|c| string.push(*c));

// +1: skip the separator
self.i = progress + 1;
Some(string)
}
}
}

mod convenience_impls {
use super::*;
use core::borrow::Borrow;

impl AsRef<Path> for &Path {
fn as_ref(&self) -> &Path {
self
}
}

impl<'a> From<&'a CStr16> for &'a Path {
fn from(value: &'a CStr16) -> Self {
Path::new(value)
}
}

impl AsRef<CStr16> for Path {
fn as_ref(&self) -> &CStr16 {
&self.0
}
}

impl Borrow<CStr16> for Path {
fn borrow(&self) -> &CStr16 {
&self.0
}
}

impl AsRef<Path> for CStr16 {
fn as_ref(&self) -> &Path {
Path::new(self)
}
}

impl Borrow<Path> for CStr16 {
fn borrow(&self) -> &Path {
Path::new(self)
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use uefi_macros::cstr16;

#[test]
fn from_cstr16() {
let source: &CStr16 = cstr16!("\\hello\\foo\\bar");
let _path: &Path = source.into();
let _path: &Path = Path::new(source);
}

#[test]
fn from_cstring16() {
let source = CString16::try_from("\\hello\\foo\\bar").unwrap();
let _path: &Path = source.as_ref().into();
let _path: &Path = Path::new(source.as_ref());
}

#[test]
fn components_iter() {
let path = Path::new(cstr16!("foo\\bar\\hello"));
let components = path.components().collect::<Vec<_>>();
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
assert_eq!(components.as_slice(), expected);

// In case there is a leading slash, it should be ignored.
let path = Path::new(cstr16!("\\foo\\bar\\hello"));
let components = path.components().collect::<Vec<_>>();
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
assert_eq!(components.as_slice(), expected);

// empty path iteration should be just fine
let empty_cstring16 = CString16::try_from("").unwrap();
let path = Path::new(empty_cstring16.as_ref());
let components = path.components().collect::<Vec<_>>();
let expected: &[CString16] = &[];
assert_eq!(components.as_slice(), expected);

// test empty path
let _path = Path::new(cstr16!());
let path = Path::new(cstr16!(""));
let components = path.components().collect::<Vec<_>>();
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
let expected: &[&CStr16] = &[];
assert_eq!(components.as_slice(), expected);

// test path that has only root component. Treated as empty path by
// the components iterator.
let path = Path::new(cstr16!("\\"));
let components = path.components().collect::<Vec<_>>();
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
let expected: &[&CStr16] = &[];
assert_eq!(components.as_slice(), expected);
}

#[test]
fn test_parent() {
assert_eq!(None, Path::new(cstr16!("")).parent());
assert_eq!(None, Path::new(cstr16!("\\")).parent());
assert_eq!(
Path::new(cstr16!("a\\b")).parent(),
Some(PathBuf::from(cstr16!("a"))),
);
assert_eq!(
Path::new(cstr16!("\\a\\b")).parent(),
Some(PathBuf::from(cstr16!("a"))),
);
assert_eq!(
Path::new(cstr16!("a\\b\\c\\d")).parent(),
Some(PathBuf::from(cstr16!("a\\b\\c"))),
);
assert_eq!(Path::new(cstr16!("abc")).parent(), None,);
}
}
Loading

0 comments on commit 58d1961

Please sign in to comment.