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 a0ea3db

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 a3521ab commit a0ea3db

File tree

8 files changed

+134
-488
lines changed

8 files changed

+134
-488
lines changed

‎CHANGELOG.md

Lines changed: 3 additions & 1 deletion
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

Lines changed: 41 additions & 37 deletions
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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
182182
}
183183
}
184184

185-
/// An UCS-2 null-terminated string.
185+
/// An UCS-2 null-terminated string slice.
186186
///
187187
/// This type is largely inspired by [`core::ffi::CStr`] with the exception that all characters are
188188
/// guaranteed to be 16 bit long.
@@ -449,6 +449,12 @@ impl<StrType: AsRef<str> + ?Sized> EqStrUntilNul<StrType> for CStr16 {
449449
}
450450
}
451451

452+
impl AsRef<CStr16> for CStr16 {
453+
fn as_ref(&self) -> &CStr16 {
454+
self
455+
}
456+
}
457+
452458
/// An iterator over the [`Char16`]s in a [`CStr16`].
453459
#[derive(Debug)]
454460
pub struct CStr16Iter<'a> {

‎uefi/src/fs/file_system.rs

Lines changed: 74 additions & 45 deletions
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

Lines changed: 8 additions & 10 deletions
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)

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