Skip to content

Commit 9f328b1

Browse files
committed
fs/path: finalize implementation + integration test
Now, only remove_dir_all is missing. The reason for this is not technical. Someone just needs to put a little more effort into it.
1 parent e0dd674 commit 9f328b1

File tree

8 files changed

+134
-488
lines changed

8 files changed

+134
-488
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
### Added
66

77
- There is a new `fs` module that provides a high-level API for file-system
8-
access. The API is close to the `std::fs` module.
8+
access. The API is close to the `std::fs` module. The module also provides a
9+
`Path` and a `PathBuf` abstraction that is similar to the ones from
10+
`std::path`. However, they are adapted for UEFI.
911
- Multiple convenience methods for `CString16` and `CStr16`, including:
1012
- `CStr16::as_slice()`
1113
- `CStr16::num_chars()`

uefi-test-runner/src/fs/mod.rs

+41-37
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
33
use alloc::string::{String, ToString};
44
use alloc::vec::Vec;
5-
use uefi::fs::{FileSystem, FileSystemError};
5+
use uefi::cstr16;
6+
use uefi::fs::{FileSystem, FileSystemError, PathBuf};
67
use uefi::proto::media::fs::SimpleFileSystem;
78
use uefi::table::boot::ScopedProtocol;
89

@@ -11,47 +12,50 @@ use uefi::table::boot::ScopedProtocol;
1112
pub fn test(sfs: ScopedProtocol<SimpleFileSystem>) -> Result<(), FileSystemError> {
1213
let mut fs = FileSystem::new(sfs);
1314

14-
fs.create_dir("test_file_system_abs")?;
15+
// test create dir
16+
fs.create_dir(cstr16!("foo_dir"))?;
1517

16-
// slash is transparently transformed to backslash
17-
fs.write("test_file_system_abs/foo", "hello")?;
18-
// absolute or relative paths are supported; ./ is ignored
19-
fs.copy("\\test_file_system_abs/foo", "\\test_file_system_abs/./bar")?;
20-
let read = fs.read("\\test_file_system_abs\\bar")?;
18+
// test write, copy, and read
19+
let data_to_write = "hello world";
20+
fs.write(cstr16!("foo_dir\\foo"), data_to_write)?;
21+
// Here, we additionally check that absolute paths work.
22+
fs.copy(cstr16!("\\foo_dir\\foo"), cstr16!("\\foo_dir\\foo_cpy"))?;
23+
let read = fs.read(cstr16!("foo_dir\\foo_cpy"))?;
2124
let read = String::from_utf8(read).expect("Should be valid utf8");
22-
assert_eq!(read, "hello");
23-
24-
assert_eq!(
25-
fs.try_exists("test_file_system_abs\\barfoo"),
26-
Err(FileSystemError::OpenError(
27-
"\\test_file_system_abs\\barfoo".to_string()
28-
))
29-
);
30-
fs.rename("test_file_system_abs\\bar", "test_file_system_abs\\barfoo")?;
31-
assert!(fs.try_exists("test_file_system_abs\\barfoo").is_ok());
32-
25+
assert_eq!(read.as_str(), data_to_write);
26+
27+
// test copy from non-existent file
28+
let err = fs.copy(cstr16!("not_found"), cstr16!("abc"));
29+
assert!(matches!(err, Err(FileSystemError::OpenError { .. })));
30+
31+
// test rename file + path buf replaces / with \
32+
fs.rename(
33+
PathBuf::from(cstr16!("/foo_dir/foo_cpy")),
34+
cstr16!("foo_dir\\foo_cpy2"),
35+
)?;
36+
// file should not be available after rename
37+
let err = fs.read(cstr16!("foo_dir\\foo_cpy"));
38+
assert!(matches!(err, Err(FileSystemError::OpenError { .. })));
39+
40+
// test read dir on a sub dir
3341
let entries = fs
34-
.read_dir("test_file_system_abs")?
35-
.map(|e| {
36-
e.expect("Should return boxed file info")
37-
.file_name()
38-
.to_string()
39-
})
42+
.read_dir(cstr16!("foo_dir"))?
43+
.map(|entry| entry.expect("Should be valid").file_name().to_string())
4044
.collect::<Vec<_>>();
41-
assert_eq!(&[".", "..", "foo", "barfoo"], entries.as_slice());
42-
43-
fs.create_dir("/deeply_nested_test")?;
44-
fs.create_dir("/deeply_nested_test/1")?;
45-
fs.create_dir("/deeply_nested_test/1/2")?;
46-
fs.create_dir("/deeply_nested_test/1/2/3")?;
47-
fs.create_dir("/deeply_nested_test/1/2/3/4")?;
48-
fs.create_dir_all("/deeply_nested_test/1/2/3/4/5/6/7")?;
49-
fs.try_exists("/deeply_nested_test/1/2/3/4/5/6/7")?;
45+
assert_eq!(&[".", "..", "foo", "foo_cpy2"], entries.as_slice());
46+
47+
// test create dir recursively
48+
fs.create_dir_all(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7"))?;
49+
fs.create_dir_all(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8"))?;
50+
fs.write(
51+
cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8\\foobar"),
52+
data_to_write,
53+
)?;
54+
let boxinfo = fs.metadata(cstr16!("foo_dir\\1\\2\\3\\4\\5\\6\\7\\8\\foobar"))?;
55+
assert_eq!(boxinfo.file_size(), data_to_write.len() as u64);
56+
57+
// test remove dir all
5058
// TODO
51-
// fs.remove_dir_all("/deeply_nested_test/1/2/3/4/5/6/7")?;
52-
fs.remove_dir("/deeply_nested_test/1/2/3/4/5/6/7")?;
53-
let exists = matches!(fs.try_exists("/deeply_nested_test/1/2/3/4/5/6/7"), Ok(_));
54-
assert!(!exists);
5559

5660
Ok(())
5761
}

uefi/src/data_types/strs.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
175175
}
176176
}
177177

178-
/// An UCS-2 null-terminated string.
178+
/// An UCS-2 null-terminated string slice.
179179
///
180180
/// This type is largely inspired by [`core::ffi::CStr`] with the exception that all characters are
181181
/// guaranteed to be 16 bit long.
@@ -423,6 +423,12 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
423423
}
424424
}
425425

426+
impl AsRef<CStr16> for CStr16 {
427+
fn as_ref(&self) -> &CStr16 {
428+
self
429+
}
430+
}
431+
426432
/// An iterator over the [`Char16`]s in a [`CStr16`].
427433
#[derive(Debug)]
428434
pub struct CStr16Iter<'a> {

uefi/src/fs/file_system.rs

+74-45
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
//! Module for [`FileSystem`].
22
33
use super::*;
4+
use crate::fs::path::{validate_path, PathError};
45
use crate::proto::media::file::{FileAttribute, FileInfo, FileType};
56
use crate::table::boot::ScopedProtocol;
67
use alloc::boxed::Box;
78
use alloc::string::{FromUtf8Error, String, ToString};
9+
use alloc::vec;
810
use alloc::vec::Vec;
9-
use alloc::{format, vec};
1011
use core::fmt;
1112
use core::fmt::{Debug, Formatter};
1213
use core::ops::Deref;
1314
use derive_more::Display;
14-
use log::info;
15+
use log::debug;
1516

1617
/// All errors that can happen when working with the [`FileSystem`].
1718
#[derive(Debug, Clone, Display, PartialEq, Eq)]
1819
pub enum FileSystemError {
1920
/// Can't open the root directory of the underlying volume.
2021
CantOpenVolume,
2122
/// The path is invalid because of the underlying [`PathError`].
23+
///
24+
/// [`PathError`]: path::PathError
2225
IllegalPath(PathError),
2326
/// The file or directory was not found in the underlying volume.
2427
FileNotFound(String),
@@ -40,12 +43,28 @@ pub enum FileSystemError {
4043
ReadFailure,
4144
/// Can't parse file content as UTF-8.
4245
Utf8Error(FromUtf8Error),
43-
/// Could not open the given path.
44-
OpenError(String),
46+
/// Could not open the given path. Carries the path that could not be opened
47+
/// and the underlying UEFI error.
48+
#[display(fmt = "{path:?}")]
49+
OpenError {
50+
/// Path that caused the failure.
51+
path: String,
52+
/// More detailed failure description.
53+
error: crate::Error,
54+
},
4555
}
4656

4757
#[cfg(feature = "unstable")]
48-
impl core::error::Error for FileSystemError {}
58+
impl core::error::Error for FileSystemError {
59+
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
60+
match self {
61+
FileSystemError::IllegalPath(e) => Some(e),
62+
FileSystemError::Utf8Error(e) => Some(e),
63+
FileSystemError::OpenError { path: _path, error } => Some(error),
64+
_ => None,
65+
}
66+
}
67+
}
4968

5069
impl From<PathError> for FileSystemError {
5170
fn from(err: PathError) -> Self {
@@ -90,44 +109,45 @@ impl<'a> FileSystem<'a> {
90109
let path = path.as_ref();
91110
self.open(path, UefiFileMode::CreateReadWrite, true)
92111
.map(|_| ())
93-
.map_err(|err| {
94-
log::debug!("failed to fetch file info: {err:#?}");
95-
FileSystemError::OpenError(path.to_string())
96-
})
97112
}
98113

99114
/// Recursively create a directory and all of its parent components if they
100115
/// are missing.
101116
pub fn create_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
102117
let path = path.as_ref();
103118

104-
let normalized_path = NormalizedPath::new("\\", path)?;
105-
let normalized_path_string = normalized_path.to_string();
106-
let normalized_path_pathref = Path::new(&normalized_path_string);
119+
// Collect all relevant sub paths in a vector.
120+
let mut dirs_to_create = vec![path.to_path_buf()];
121+
while let Some(parent) = dirs_to_create.last().unwrap().parent() {
122+
debug!("parent={parent}");
123+
dirs_to_create.push(parent)
124+
}
125+
// Now reverse, so that we have something like this:
126+
// - a
127+
// - a\\b
128+
// - a\\b\\c
129+
dirs_to_create.reverse();
130+
131+
for parent in dirs_to_create {
132+
if self.try_exists(&parent).is_err() {
133+
self.create_dir(parent)?;
134+
}
135+
}
107136

108-
let iter = || normalized_path_pathref.components(SEPARATOR);
109-
iter()
110-
.scan(String::new(), |path_acc, component| {
111-
if component != Component::RootDir {
112-
*path_acc += SEPARATOR_STR;
113-
*path_acc += format!("{component}").as_str();
114-
}
115-
info!("path_acc: {path_acc}, component: {component}");
116-
Some((component, path_acc.clone()))
117-
})
118-
.try_for_each(|(_component, full_path)| self.create_dir(full_path.as_str()))
137+
Ok(())
119138
}
120139

121140
/// Given a path, query the file system to get information about a file,
122141
/// directory, etc. Returns [`UefiFileInfo`].
123142
pub fn metadata(&mut self, path: impl AsRef<Path>) -> FileSystemResult<Box<UefiFileInfo>> {
124143
let path = path.as_ref();
125-
let file = self.open(path, UefiFileMode::Read, false)?;
126-
log::debug!("{:#?}", &file.into_type().unwrap());
127144
let mut file = self.open(path, UefiFileMode::Read, false)?;
128145
file.get_boxed_info().map_err(|err| {
129-
log::debug!("failed to fetch file info: {err:#?}");
130-
FileSystemError::OpenError(path.to_string())
146+
log::trace!("failed to fetch file info: {err:#?}");
147+
FileSystemError::OpenError {
148+
path: path.to_cstr16().to_string(),
149+
error: err,
150+
}
131151
})
132152
}
133153

@@ -138,11 +158,13 @@ impl<'a> FileSystem<'a> {
138158
let mut file = self
139159
.open(path, UefiFileMode::Read, false)?
140160
.into_regular_file()
141-
.ok_or(FileSystemError::NotAFile(path.as_str().to_string()))?;
142-
let info = file.get_boxed_info::<FileInfo>().map_err(|e| {
143-
log::error!("get info failed: {e:?}");
144-
FileSystemError::OpenError(path.as_str().to_string())
145-
})?;
161+
.ok_or(FileSystemError::NotAFile(path.to_cstr16().to_string()))?;
162+
let info = file
163+
.get_boxed_info::<FileInfo>()
164+
.map_err(|err| FileSystemError::OpenError {
165+
path: path.to_cstr16().to_string(),
166+
error: err,
167+
})?;
146168

147169
let mut vec = vec![0; info.file_size() as usize];
148170
let read_bytes = file.read(vec.as_mut_slice()).map_err(|e| {
@@ -164,7 +186,7 @@ impl<'a> FileSystem<'a> {
164186
let dir = self
165187
.open(path, UefiFileMode::Read, false)?
166188
.into_directory()
167-
.ok_or(FileSystemError::NotADirectory(path.as_str().to_string()))?;
189+
.ok_or(FileSystemError::NotADirectory(path.to_cstr16().to_string()))?;
168190
Ok(UefiDirectoryIter::new(dir))
169191
}
170192

@@ -185,16 +207,18 @@ impl<'a> FileSystem<'a> {
185207
match file {
186208
FileType::Dir(dir) => dir.delete().map_err(|e| {
187209
log::error!("error removing dir: {e:?}");
188-
FileSystemError::CantDeleteDirectory(path.as_str().to_string())
210+
FileSystemError::CantDeleteDirectory(path.to_cstr16().to_string())
189211
}),
190-
FileType::Regular(_) => Err(FileSystemError::NotADirectory(path.as_str().to_string())),
212+
FileType::Regular(_) => {
213+
Err(FileSystemError::NotADirectory(path.to_cstr16().to_string()))
214+
}
191215
}
192216
}
193217

194218
/*/// Removes a directory at this path, after removing all its contents. Use
195219
/// carefully!
196-
pub fn remove_dir_all(&mut self, _path: impl AsRef<Path>) -> FileSystemResult<()> {
197-
todo!()
220+
pub fn remove_dir_all(&mut self, path: impl AsRef<Path>) -> FileSystemResult<()> {
221+
let path = path.as_ref();
198222
}*/
199223

200224
/// Removes a file from the filesystem.
@@ -209,9 +233,9 @@ impl<'a> FileSystem<'a> {
209233
match file {
210234
FileType::Regular(file) => file.delete().map_err(|e| {
211235
log::error!("error removing file: {e:?}");
212-
FileSystemError::CantDeleteFile(path.as_str().to_string())
236+
FileSystemError::CantDeleteFile(path.to_cstr16().to_string())
213237
}),
214-
FileType::Dir(_) => Err(FileSystemError::NotAFile(path.as_str().to_string())),
238+
FileType::Dir(_) => Err(FileSystemError::NotAFile(path.to_cstr16().to_string())),
215239
}
216240
}
217241

@@ -278,19 +302,24 @@ impl<'a> FileSystem<'a> {
278302
mode: UefiFileMode,
279303
is_dir: bool,
280304
) -> FileSystemResult<UefiFileHandle> {
281-
let path = NormalizedPath::new("\\", path)?;
282-
log::debug!("normalized path: {path}");
305+
validate_path(path)?;
306+
log::trace!("open validated path: {path}");
283307

284308
let attr = if mode == UefiFileMode::CreateReadWrite && is_dir {
285309
FileAttribute::DIRECTORY
286310
} else {
287311
FileAttribute::empty()
288312
};
289313

290-
self.open_root()?.open(&path, mode, attr).map_err(|x| {
291-
log::trace!("Can't open file {path}: {x:?}");
292-
FileSystemError::OpenError(path.to_string())
293-
})
314+
self.open_root()?
315+
.open(path.to_cstr16(), mode, attr)
316+
.map_err(|err| {
317+
log::trace!("Can't open file {path}: {err:?}");
318+
FileSystemError::OpenError {
319+
path: path.to_cstr16().to_string(),
320+
error: err,
321+
}
322+
})
294323
}
295324
}
296325

uefi/src/fs/mod.rs

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
//! A high-level file system API for UEFI applications close to the `fs` module
2-
//! from Rust's standard library.
1+
//! A high-level file system API for UEFI applications close to the `std::fs`
2+
//! module from Rust's standard library. The main type by this module is
3+
//! [`FileSystem`].
34
//!
45
//! # Difference to typical File System Abstractions
56
//! Users perform actions on dedicated volumes: For example, the boot volume,
67
//! such as a CD-rom, USB-stick, or any other storage device.
78
//!
89
//! Unlike in the API of typical UNIX file system abstractions, there is
9-
//! no virtual file system.
10-
//!
11-
//! Unlike Windows, there is no way to access volumes by a dedicated name.
10+
//! no virtual file system. Unlike in Windows, there is no way to access volumes
11+
//! by a dedicated name.
1212
//!
1313
//! # Paths
1414
//! All paths are absolute and follow the FAT-like file system conventions for
@@ -17,7 +17,8 @@
1717
//! directory is always `/`, i.e., the root, of the opened volume.
1818
//!
1919
//! Symlinks or hard-links are not supported but only directories and regular
20-
//! files with plain linear paths to them.
20+
//! files with plain linear paths to them. For more information, see
21+
//! [`Path`] and [`PathBuf`].
2122
//!
2223
//! # API Hints
2324
//! There are no `File` and `Path` abstractions similar to those from `std` that
@@ -31,14 +32,11 @@
3132
3233
mod dir_entry_iter;
3334
mod file_system;
34-
mod normalized_path;
3535
mod path;
3636
mod uefi_types;
3737

3838
pub use file_system::{FileSystem, FileSystemError, FileSystemResult};
39-
pub use normalized_path::{PathError, SEPARATOR, SEPARATOR_STR};
39+
pub use path::*;
4040

4141
use dir_entry_iter::*;
42-
use normalized_path::*;
43-
use path::*;
4442
use uefi_types::*;

0 commit comments

Comments
 (0)