Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit a3521ab

Browse files
committed
fs/path: Add Path and PathBuf abstractions
1 parent f8d48b9 commit a3521ab

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
@@ -68,7 +68,7 @@ pub enum FromStrWithBufError {
6868
/// For convenience, a [`CStr8`] is comparable with [`core::str`] and
6969
/// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`].
7070
#[repr(transparent)]
71-
#[derive(Eq, PartialEq)]
71+
#[derive(Eq, PartialEq,Ord,PartialOrd)]
7272
pub struct CStr8([Char8]);
7373

7474
impl CStr8 {
@@ -189,7 +189,7 @@ impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
189189
///
190190
/// For convenience, a [`CStr16`] is comparable with [`core::str`] and
191191
/// `alloc::string::String` from the standard library through the trait [`EqStrUntilNul`].
192-
#[derive(Eq, PartialEq)]
192+
#[derive(Eq, PartialEq,Ord,PartialOrd)]
193193
#[repr(transparent)]
194194
pub struct CStr16([Char16]);
195195

‎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)

AltStyle によって変換されたページ (->オリジナル) /