Skip to content

Commit e0dd674

Browse files
committed
fs/path: Add Path and PathBuf abstractions
1 parent 96fa66a commit e0dd674

File tree

4 files changed

+554
-0
lines changed

4 files changed

+554
-0
lines changed

uefi/src/fs/path/mod.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! This module offers the [`Path`] and [`PathBuf`] abstractions.
2+
//!
3+
//! # Interoperability with Rust strings
4+
//!
5+
//! For the interoperability with Rust strings, i.e., `String` and `str` from
6+
//! the standard library, the API is intended to transform these types first to
7+
//! `CString16` respectively `CStr16`. They do not directly translate to
8+
//! [`Path`] and [`PathBuf`].
9+
//!
10+
//! # Path Structure
11+
//!
12+
//! Paths use the [`SEPARATOR`] character as separator. Paths are absolute and
13+
//! do not contain `.` or `..` components. However, this can be implemented in
14+
//! the future.
15+
16+
mod path;
17+
mod pathbuf;
18+
mod validation;
19+
20+
pub use path::Path;
21+
pub use pathbuf::PathBuf;
22+
23+
use uefi::{CStr16, Char16};
24+
pub(super) use validation::validate_path;
25+
pub use validation::PathError;
26+
27+
/// The default separator for paths.
28+
pub const SEPARATOR: char = '\\';
29+
/// [`SEPARATOR`] but as useful UEFI type.
30+
pub const SEPARATOR_C16: Char16 = unsafe { Char16::from_u16_unchecked('\\' as u16) };
31+
32+
/// Stringified version of [`SEPARATOR`].
33+
pub const SEPARATOR_STR: &str = "\\";
34+
/// [`SEPARATOR_STR`] but as useful UEFI type.
35+
pub const SEPARATOR_CSTR16: &CStr16 = uefi_macros::cstr16!("\\");
36+
37+
/// Deny list of characters for path components. UEFI supports FAT-like file
38+
/// systems. According to <https://en.wikipedia.org/wiki/Comparison_of_file_systems>,
39+
/// paths should not contain the following symbols.
40+
pub const CHARACTER_DENY_LIST: [Char16; 10] = unsafe {
41+
[
42+
Char16::from_u16_unchecked('\0' as u16),
43+
Char16::from_u16_unchecked('"' as u16),
44+
Char16::from_u16_unchecked('*' as u16),
45+
Char16::from_u16_unchecked('/' as u16),
46+
Char16::from_u16_unchecked(':' as u16),
47+
Char16::from_u16_unchecked('<' as u16),
48+
Char16::from_u16_unchecked('>' as u16),
49+
Char16::from_u16_unchecked('?' as u16),
50+
Char16::from_u16_unchecked('\\' as u16),
51+
Char16::from_u16_unchecked('|' as u16),
52+
]
53+
};

uefi/src/fs/path/path.rs

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// allow "path.rs" in "path"
2+
#![allow(clippy::module_inception)]
3+
4+
use crate::fs::path::{PathBuf, SEPARATOR_C16};
5+
use crate::CStr16;
6+
use core::fmt::{Display, Formatter};
7+
use uefi::CString16;
8+
9+
/// A path similar to the `Path` of the standard library, but based on
10+
/// [`CStr16`] strings and [`SEPARATOR`] as separator.
11+
///
12+
/// [`SEPARATOR`]: super::SEPARATOR
13+
#[derive(Debug, PartialEq, Eq)]
14+
pub struct Path(CStr16);
15+
16+
impl Path {
17+
/// Constructor.
18+
#[must_use]
19+
pub fn new<S: AsRef<CStr16> + ?Sized>(s: &S) -> &Self {
20+
unsafe { &*(s.as_ref() as *const CStr16 as *const Self) }
21+
}
22+
23+
/// Returns the underlying string.
24+
#[must_use]
25+
pub fn to_cstr16(&self) -> &CStr16 {
26+
&self.0
27+
}
28+
29+
/// Returns a path buf from that type.
30+
#[must_use]
31+
pub fn to_path_buf(&self) -> PathBuf {
32+
let cstring = CString16::from(&self.0);
33+
PathBuf::from(cstring)
34+
}
35+
36+
/// Iterator over the components of a path.
37+
#[must_use]
38+
pub fn components(&self) -> Components {
39+
Components {
40+
path: self.as_ref(),
41+
i: 0,
42+
}
43+
}
44+
45+
/// Returns the parent directory as [`PathBuf`].
46+
///
47+
/// If the path is a top-level component, this returns None.
48+
#[must_use]
49+
pub fn parent(&self) -> Option<PathBuf> {
50+
let components_count = self.components().count();
51+
if components_count == 0 {
52+
return None;
53+
}
54+
55+
// Return None, as we do not treat "\\" as dedicated component.
56+
let sep_count = self
57+
.0
58+
.as_slice()
59+
.iter()
60+
.filter(|char| **char == SEPARATOR_C16)
61+
.count();
62+
if sep_count == 0 {
63+
return None;
64+
}
65+
66+
let path =
67+
self.components()
68+
.take(components_count - 1)
69+
.fold(CString16::new(), |mut acc, next| {
70+
// Add separator, as needed.
71+
if !acc.is_empty() && *acc.as_slice().last().unwrap() != SEPARATOR_C16 {
72+
acc.push(SEPARATOR_C16);
73+
}
74+
acc.push_str(next.as_ref());
75+
acc
76+
});
77+
let path = PathBuf::from(path);
78+
Some(path)
79+
}
80+
81+
/// Returns of the path is empty.
82+
#[must_use]
83+
pub fn is_empty(&self) -> bool {
84+
self.to_cstr16().is_empty()
85+
}
86+
}
87+
88+
impl Display for Path {
89+
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
90+
Display::fmt(self.to_cstr16(), f)
91+
}
92+
}
93+
94+
/// Iterator over the components of a path. For example, the path `\\a\\b\\c`
95+
/// has the components `[a, b, c]`. This is a more basic approach than the
96+
/// components type of the standard library.
97+
#[derive(Debug)]
98+
pub struct Components<'a> {
99+
path: &'a CStr16,
100+
i: usize,
101+
}
102+
103+
impl<'a> Iterator for Components<'a> {
104+
// Attention. We can't iterate over &'Ctr16, as we would break any guarantee
105+
// made for the terminating null character.
106+
type Item = CString16;
107+
108+
fn next(&mut self) -> Option<Self::Item> {
109+
if self.path.is_empty() {
110+
return None;
111+
}
112+
if self.path.num_chars() == 1 && self.path.as_slice()[0] == SEPARATOR_C16 {
113+
// The current implementation does not handle the root dir as
114+
// dedicated component so far. We just return nothing.
115+
return None;
116+
}
117+
118+
// If the path is not empty and starts with a separator, skip it.
119+
if self.i == 0 && *self.path.as_slice().first().unwrap() == SEPARATOR_C16 {
120+
self.i = 1;
121+
}
122+
123+
// Count how many characters are there until the next separator is
124+
// found.
125+
let len = self
126+
.path
127+
.iter()
128+
.skip(self.i)
129+
.take_while(|c| **c != SEPARATOR_C16)
130+
.count();
131+
132+
let progress = self.i + len;
133+
if progress > self.path.num_chars() {
134+
None
135+
} else {
136+
// select the next component and build an owned string
137+
let part = &self.path.as_slice()[self.i..self.i + len];
138+
let mut string = CString16::new();
139+
part.iter().for_each(|c| string.push(*c));
140+
141+
// +1: skip the separator
142+
self.i = progress + 1;
143+
Some(string)
144+
}
145+
}
146+
}
147+
148+
mod convenience_impls {
149+
use super::*;
150+
use core::borrow::Borrow;
151+
152+
impl AsRef<Path> for &Path {
153+
fn as_ref(&self) -> &Path {
154+
self
155+
}
156+
}
157+
158+
impl<'a> From<&'a CStr16> for &'a Path {
159+
fn from(value: &'a CStr16) -> Self {
160+
Path::new(value)
161+
}
162+
}
163+
164+
impl AsRef<CStr16> for Path {
165+
fn as_ref(&self) -> &CStr16 {
166+
&self.0
167+
}
168+
}
169+
170+
impl Borrow<CStr16> for Path {
171+
fn borrow(&self) -> &CStr16 {
172+
&self.0
173+
}
174+
}
175+
176+
impl AsRef<Path> for CStr16 {
177+
fn as_ref(&self) -> &Path {
178+
Path::new(self)
179+
}
180+
}
181+
182+
impl Borrow<Path> for CStr16 {
183+
fn borrow(&self) -> &Path {
184+
Path::new(self)
185+
}
186+
}
187+
}
188+
189+
#[cfg(test)]
190+
mod tests {
191+
use super::*;
192+
use alloc::vec::Vec;
193+
use uefi_macros::cstr16;
194+
195+
#[test]
196+
fn from_cstr16() {
197+
let source: &CStr16 = cstr16!("\\hello\\foo\\bar");
198+
let _path: &Path = source.into();
199+
let _path: &Path = Path::new(source);
200+
}
201+
202+
#[test]
203+
fn from_cstring16() {
204+
let source = CString16::try_from("\\hello\\foo\\bar").unwrap();
205+
let _path: &Path = source.as_ref().into();
206+
let _path: &Path = Path::new(source.as_ref());
207+
}
208+
209+
#[test]
210+
fn components_iter() {
211+
let path = Path::new(cstr16!("foo\\bar\\hello"));
212+
let components = path.components().collect::<Vec<_>>();
213+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
214+
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
215+
assert_eq!(components.as_slice(), expected);
216+
217+
// In case there is a leading slash, it should be ignored.
218+
let path = Path::new(cstr16!("\\foo\\bar\\hello"));
219+
let components = path.components().collect::<Vec<_>>();
220+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
221+
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
222+
assert_eq!(components.as_slice(), expected);
223+
224+
// empty path iteration should be just fine
225+
let empty_cstring16 = CString16::try_from("").unwrap();
226+
let path = Path::new(empty_cstring16.as_ref());
227+
let components = path.components().collect::<Vec<_>>();
228+
let expected: &[CString16] = &[];
229+
assert_eq!(components.as_slice(), expected);
230+
231+
// test empty path
232+
let _path = Path::new(cstr16!());
233+
let path = Path::new(cstr16!(""));
234+
let components = path.components().collect::<Vec<_>>();
235+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
236+
let expected: &[&CStr16] = &[];
237+
assert_eq!(components.as_slice(), expected);
238+
239+
// test path that has only root component. Treated as empty path by
240+
// the components iterator.
241+
let path = Path::new(cstr16!("\\"));
242+
let components = path.components().collect::<Vec<_>>();
243+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
244+
let expected: &[&CStr16] = &[];
245+
assert_eq!(components.as_slice(), expected);
246+
}
247+
248+
#[test]
249+
fn test_parent() {
250+
assert_eq!(None, Path::new(cstr16!("")).parent());
251+
assert_eq!(None, Path::new(cstr16!("\\")).parent());
252+
assert_eq!(
253+
Path::new(cstr16!("a\\b")).parent(),
254+
Some(PathBuf::from(cstr16!("a"))),
255+
);
256+
assert_eq!(
257+
Path::new(cstr16!("\\a\\b")).parent(),
258+
Some(PathBuf::from(cstr16!("a"))),
259+
);
260+
assert_eq!(
261+
Path::new(cstr16!("a\\b\\c\\d")).parent(),
262+
Some(PathBuf::from(cstr16!("a\\b\\c"))),
263+
);
264+
assert_eq!(Path::new(cstr16!("abc")).parent(), None,);
265+
}
266+
}

0 commit comments

Comments
 (0)