Skip to content

Commit 0a8e624

Browse files
committed
fs/path: Add Path and PathBuf abstractions
1 parent 17e5aa0 commit 0a8e624

File tree

6 files changed

+607
-4
lines changed

6 files changed

+607
-4
lines changed

uefi/src/data_types/owned_strs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ impl core::error::Error for FromStrError {}
3636

3737
/// An owned UCS-2 null-terminated string.
3838
///
39-
/// For convenience, a [CString16] is comparable with `&str` and `String` from the standard library
40-
/// through the trait [EqStrUntilNul].
39+
/// For convenience, a [`CString16`] is comparable with `&str` and `String` from
40+
/// the standard library through the trait [`EqStrUntilNul`].
4141
///
4242
/// # Examples
4343
///

uefi/src/data_types/strs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub enum FromStrWithBufError {
6767
/// For convenience, a [`CStr8`] is comparable with [`core::str`] and
6868
/// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`].
6969
#[repr(transparent)]
70-
#[derive(Eq, PartialEq)]
70+
#[derive(Eq, PartialEq, Ord, PartialOrd)]
7171
pub struct CStr8([Char8]);
7272

7373
impl CStr8 {
@@ -182,7 +182,7 @@ impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
182182
///
183183
/// For convenience, a [`CStr16`] is comparable with [`core::str`] and
184184
/// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`].
185-
#[derive(Eq, PartialEq)]
185+
#[derive(Eq, PartialEq, Ord, PartialOrd)]
186186
#[repr(transparent)]
187187
pub struct CStr16([Char16]);
188188

uefi/src/fs/path/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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::{Components, Path};
21+
pub use pathbuf::PathBuf;
22+
23+
use uefi::data_types::chars::NUL_16;
24+
use uefi::{CStr16, Char16};
25+
pub(super) use validation::validate_path;
26+
pub use validation::PathError;
27+
28+
/// The default separator for paths.
29+
pub const SEPARATOR: Char16 = unsafe { Char16::from_u16_unchecked('\\' as u16) };
30+
31+
/// Stringified version of [`SEPARATOR`].
32+
pub const SEPARATOR_STR: &CStr16 = uefi_macros::cstr16!("\\");
33+
34+
/// Deny list of characters for path components. UEFI supports FAT-like file
35+
/// systems. According to <https://en.wikipedia.org/wiki/Comparison_of_file_systems>,
36+
/// paths should not contain the following symbols.
37+
pub const CHARACTER_DENY_LIST: [Char16; 10] = unsafe {
38+
[
39+
NUL_16,
40+
Char16::from_u16_unchecked('"' as u16),
41+
Char16::from_u16_unchecked('*' as u16),
42+
Char16::from_u16_unchecked('/' 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+
SEPARATOR,
48+
Char16::from_u16_unchecked('|' as u16),
49+
]
50+
};

uefi/src/fs/path/path.rs

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// allow "path.rs" in "path"
2+
#![allow(clippy::module_inception)]
3+
4+
use crate::fs::path::{PathBuf, SEPARATOR};
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, Eq, PartialOrd, Ord)]
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)
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 {
72+
acc.push(SEPARATOR);
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+
impl PartialEq for Path {
95+
fn eq(&self, other: &Self) -> bool {
96+
self.components().count() == other.components().count()
97+
&& !self
98+
.components()
99+
.zip(other.components())
100+
.any(|(c1, c2)| c1 != c2)
101+
}
102+
}
103+
104+
/// Iterator over the components of a path. For example, the path `\\a\\b\\c`
105+
/// has the components `[a, b, c]`. This is a more basic approach than the
106+
/// components type of the standard library.
107+
#[derive(Debug)]
108+
pub struct Components<'a> {
109+
path: &'a CStr16,
110+
i: usize,
111+
}
112+
113+
impl<'a> Iterator for Components<'a> {
114+
// Attention. We can't iterate over &'Ctr16, as we would break any guarantee
115+
// made for the terminating null character.
116+
type Item = CString16;
117+
118+
fn next(&mut self) -> Option<Self::Item> {
119+
if self.path.is_empty() {
120+
return None;
121+
}
122+
if self.path.num_chars() == 1 && self.path.as_slice()[0] == SEPARATOR {
123+
// The current implementation does not handle the root dir as
124+
// dedicated component so far. We just return nothing.
125+
return None;
126+
}
127+
128+
// If the path is not empty and starts with a separator, skip it.
129+
if self.i == 0 && *self.path.as_slice().first().unwrap() == SEPARATOR {
130+
self.i = 1;
131+
}
132+
133+
// Count how many characters are there until the next separator is
134+
// found.
135+
let len = self
136+
.path
137+
.iter()
138+
.skip(self.i)
139+
.take_while(|c| **c != SEPARATOR)
140+
.count();
141+
142+
let progress = self.i + len;
143+
if progress > self.path.num_chars() {
144+
None
145+
} else {
146+
// select the next component and build an owned string
147+
let part = &self.path.as_slice()[self.i..self.i + len];
148+
let mut string = CString16::new();
149+
part.iter().for_each(|c| string.push(*c));
150+
151+
// +1: skip the separator
152+
self.i = progress + 1;
153+
Some(string)
154+
}
155+
}
156+
}
157+
158+
mod convenience_impls {
159+
use super::*;
160+
use core::borrow::Borrow;
161+
162+
impl AsRef<Path> for &Path {
163+
fn as_ref(&self) -> &Path {
164+
self
165+
}
166+
}
167+
168+
impl<'a> From<&'a CStr16> for &'a Path {
169+
fn from(value: &'a CStr16) -> Self {
170+
Path::new(value)
171+
}
172+
}
173+
174+
impl AsRef<CStr16> for Path {
175+
fn as_ref(&self) -> &CStr16 {
176+
&self.0
177+
}
178+
}
179+
180+
impl Borrow<CStr16> for Path {
181+
fn borrow(&self) -> &CStr16 {
182+
&self.0
183+
}
184+
}
185+
186+
impl AsRef<Path> for CStr16 {
187+
fn as_ref(&self) -> &Path {
188+
Path::new(self)
189+
}
190+
}
191+
192+
impl Borrow<Path> for CStr16 {
193+
fn borrow(&self) -> &Path {
194+
Path::new(self)
195+
}
196+
}
197+
}
198+
199+
#[cfg(test)]
200+
mod tests {
201+
use super::*;
202+
use alloc::vec::Vec;
203+
use uefi_macros::cstr16;
204+
205+
#[test]
206+
fn from_cstr16() {
207+
let source: &CStr16 = cstr16!("\\hello\\foo\\bar");
208+
let _path: &Path = source.into();
209+
let _path: &Path = Path::new(source);
210+
}
211+
212+
#[test]
213+
fn from_cstring16() {
214+
let source = CString16::try_from("\\hello\\foo\\bar").unwrap();
215+
let _path: &Path = source.as_ref().into();
216+
let _path: &Path = Path::new(source.as_ref());
217+
}
218+
219+
#[test]
220+
fn components_iter() {
221+
let path = Path::new(cstr16!("foo\\bar\\hello"));
222+
let components = path.components().collect::<Vec<_>>();
223+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
224+
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
225+
assert_eq!(components.as_slice(), expected);
226+
227+
// In case there is a leading slash, it should be ignored.
228+
let path = Path::new(cstr16!("\\foo\\bar\\hello"));
229+
let components = path.components().collect::<Vec<_>>();
230+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
231+
let expected: &[&CStr16] = &[cstr16!("foo"), cstr16!("bar"), cstr16!("hello")];
232+
assert_eq!(components.as_slice(), expected);
233+
234+
// empty path iteration should be just fine
235+
let empty_cstring16 = CString16::try_from("").unwrap();
236+
let path = Path::new(empty_cstring16.as_ref());
237+
let components = path.components().collect::<Vec<_>>();
238+
let expected: &[CString16] = &[];
239+
assert_eq!(components.as_slice(), expected);
240+
241+
// test empty path
242+
let _path = Path::new(cstr16!());
243+
let path = Path::new(cstr16!(""));
244+
let components = path.components().collect::<Vec<_>>();
245+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
246+
let expected: &[&CStr16] = &[];
247+
assert_eq!(components.as_slice(), expected);
248+
249+
// test path that has only root component. Treated as empty path by
250+
// the components iterator.
251+
let path = Path::new(cstr16!("\\"));
252+
let components = path.components().collect::<Vec<_>>();
253+
let components: Vec<&CStr16> = components.iter().map(|x| x.as_ref()).collect::<Vec<_>>();
254+
let expected: &[&CStr16] = &[];
255+
assert_eq!(components.as_slice(), expected);
256+
}
257+
258+
#[test]
259+
fn test_parent() {
260+
assert_eq!(None, Path::new(cstr16!("")).parent());
261+
assert_eq!(None, Path::new(cstr16!("\\")).parent());
262+
assert_eq!(
263+
Path::new(cstr16!("a\\b")).parent(),
264+
Some(PathBuf::from(cstr16!("a"))),
265+
);
266+
assert_eq!(
267+
Path::new(cstr16!("\\a\\b")).parent(),
268+
Some(PathBuf::from(cstr16!("a"))),
269+
);
270+
assert_eq!(
271+
Path::new(cstr16!("a\\b\\c\\d")).parent(),
272+
Some(PathBuf::from(cstr16!("a\\b\\c"))),
273+
);
274+
assert_eq!(Path::new(cstr16!("abc")).parent(), None,);
275+
}
276+
277+
#[test]
278+
fn partial_eq() {
279+
let path1 = Path::new(cstr16!(r"a\b"));
280+
let path2 = Path::new(cstr16!(r"\a\b"));
281+
let path3 = Path::new(cstr16!(r"a\b\c"));
282+
283+
assert_eq!(path1, path1);
284+
assert_eq!(path2, path2);
285+
assert_eq!(path3, path3);
286+
287+
// Equal as currently, we only support absolute paths, so the leading
288+
// separator is obligatory.
289+
assert_eq!(path1, path2);
290+
assert_eq!(path2, path1);
291+
292+
assert_ne!(path1, path3);
293+
assert_ne!(path3, path1);
294+
}
295+
}

0 commit comments

Comments
 (0)