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 3050d50

Browse files
committed
high-level fs abstraction
1 parent f93f089 commit 3050d50

File tree

7 files changed

+678
-0
lines changed

7 files changed

+678
-0
lines changed

‎src/fs/dir_entry_iter.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::fs::file_info::{BoxedUefiFileInfo, UEFI_FILE_INFO_MAX_SIZE};
2+
use super::*;
3+
use alloc_api::vec::Vec;
4+
5+
/// Iterator over the low-level entries of a UEFI directory handle.
6+
/// Returns owning [BoxedUefiFileInfo].
7+
pub struct UefiDirectoryEntryIterator {
8+
handle: UefiDirectoryHandle,
9+
buffer: Vec<u8>,
10+
}
11+
12+
impl UefiDirectoryEntryIterator {
13+
pub fn new(handle: UefiDirectoryHandle) -> Self {
14+
// May contain 256 characters with an fixed size UTF-16 format. Null-byte included.
15+
let mut buffer = Vec::with_capacity(UEFI_FILE_INFO_MAX_SIZE);
16+
(0..buffer.capacity()).for_each(|_| buffer.push(0));
17+
Self { handle, buffer }
18+
}
19+
}
20+
21+
impl Iterator for UefiDirectoryEntryIterator {
22+
type Item = BoxedUefiFileInfo;
23+
24+
fn next(&mut self) -> Option<Self::Item> {
25+
// reset as the buffer gets used multiple times
26+
self.buffer.fill(0);
27+
28+
self.handle
29+
.read_entry(&mut self.buffer)
30+
// it is extremly ugly to marry the results from uefi with the option from the iterator..
31+
// thus, just panic =(
32+
.expect("Uefi Error")
33+
.map(|fileinfo| BoxedUefiFileInfo::new(fileinfo))
34+
}
35+
}

‎src/fs/file_info.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use crate::fs::path::MAX_COMPONENT_LENGTH;
2+
use alloc_api::vec::Vec;
3+
use core::ops::Deref;
4+
use uefi::proto::media::file::FileInfo as UefiFileInfo;
5+
6+
// Ugly but we can't use size_of() as the type isn't sized.
7+
// This is constant, as it is specified.
8+
/// Size of a [UefiFileInfo] without the filename and without a NULL byte.
9+
pub const UEFI_FILE_INFO_MAX_SIZE: usize = 80 + 2 * (1 + MAX_COMPONENT_LENGTH);
10+
11+
/// A UEFI file info that lives on the heap. Owns the data.
12+
#[derive(Debug)]
13+
pub struct BoxedUefiFileInfo {
14+
// owning buffer
15+
buffer: Vec<u8>,
16+
}
17+
18+
impl BoxedUefiFileInfo {
19+
pub fn new(info: &UefiFileInfo) -> Self {
20+
let mut buffer = Vec::<u8>::with_capacity(UEFI_FILE_INFO_MAX_SIZE);
21+
(0..buffer.capacity()).for_each(|_| buffer.push(0));
22+
23+
unsafe {
24+
let src_ptr = core::ptr::addr_of!(*info).cast::<u8>();
25+
let dest_ptr = buffer.as_mut_ptr().cast::<u8>();
26+
core::ptr::copy_nonoverlapping(src_ptr, dest_ptr, UEFI_FILE_INFO_MAX_SIZE);
27+
}
28+
Self { buffer }
29+
}
30+
}
31+
32+
impl Deref for BoxedUefiFileInfo {
33+
type Target = UefiFileInfo;
34+
fn deref(&self) -> &Self::Target {
35+
unsafe {
36+
let addr = self.buffer.as_ptr().cast::<()>();
37+
let ptr: *const UefiFileInfo =
38+
// todo metadata is here probably unused?!
39+
core::ptr::from_raw_parts(addr, self.buffer.capacity());
40+
ptr.as_ref().unwrap()
41+
}
42+
}
43+
}
44+
45+
#[cfg(test)]
46+
mod tests {
47+
use super::*;
48+
use uefi::proto::media::file::FromUefi;
49+
50+
#[test]
51+
fn test_put_into_box() {
52+
let mut buffer = [0_u8; UEFI_FILE_INFO_MAX_SIZE];
53+
54+
let boxed_fileinfo = unsafe {
55+
let ptr = buffer.as_mut_ptr();
56+
57+
// set some values to the "file_size" property
58+
*ptr.cast::<u64>().add(1) = 0x7733_0909_2345_1337;
59+
60+
let uefi_fileinfo = UefiFileInfo::from_uefi(ptr.cast());
61+
assert_eq!(uefi_fileinfo.file_size(), 0x7733_0909_2345_1337);
62+
BoxedUefiFileInfo::new(uefi_fileinfo)
63+
};
64+
65+
assert_eq!(boxed_fileinfo.file_size(), 0x7733_0909_2345_1337);
66+
}
67+
}

‎src/fs/file_system.rs

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
//! Module for [FileSystem].
2+
3+
use super::*;
4+
use crate::fs::dir_entry_iter::UefiDirectoryEntryIterator;
5+
use crate::fs::file_info::BoxedUefiFileInfo;
6+
use crate::fs::path::{Path, PathError};
7+
use alloc_api::string::{FromUtf8Error, String, ToString};
8+
use alloc_api::vec::Vec;
9+
use core::fmt;
10+
use core::fmt::{Debug, Formatter};
11+
use uefi::proto::media::file::FileType;
12+
use uefi::table::boot::ScopedProtocol;
13+
use uefi::{CString16};
14+
15+
// #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
16+
#[derive(Debug, Clone)]
17+
pub enum FileSystemError {
18+
/// Can't open the root directory of the underlying volume.
19+
CantOpenVolume,
20+
/// The path is invalid because of the underlying [PathError].
21+
IllegalPath(PathError),
22+
/// The file or directory was not found in the underlying volume.
23+
FileNotFound(String),
24+
/// The path is existend but does not correspond to a directory when a directory was expected.
25+
NotADirectory(String),
26+
/// The path is existend but does not correspond to a file when a file was expected.
27+
NotAFile(String),
28+
/// Can't delete the file.
29+
CantDeleteFile(String),
30+
/// Error writing bytes.
31+
WriteFailure,
32+
/// Error flushing file.
33+
FlushFailure,
34+
/// Error reading file.
35+
ReadFailure,
36+
/// Can't parse file content as UTF-8.
37+
Utf8Error(FromUtf8Error),
38+
/// Could not open the given path.
39+
OpenError(String),
40+
}
41+
42+
impl From<PathError> for FileSystemError {
43+
fn from(err: PathError) -> Self {
44+
Self::IllegalPath(err)
45+
}
46+
}
47+
48+
/// Return type for public [FileSystem] operations.
49+
pub type FileSystemResult<T> = Result<T, FileSystemError>;
50+
51+
/// Entry point into the file system abstraction described by the module description. Accessor to
52+
/// an UEFI volume. Available methods try to be close to the `fs` module of `libstd`.
53+
///
54+
/// # Technical Background
55+
/// Some random interesting information how this abstraction helps.
56+
/// - To get metadata about UEFI files, we need to iterate the parent directory. This abstraction
57+
/// hides this complexity.
58+
pub struct FileSystem<'name, 'boot_services> {
59+
/// Name to identify this file system. For example "root" or "boot". Only a help for users.
60+
name: &'name str,
61+
/// Underlying UEFI
62+
proto: ScopedProtocol<'boot_services, SimpleFileSystemProtocol>,
63+
}
64+
65+
impl<'name, 'boot_services> FileSystem<'name, 'boot_services> {
66+
/// Constructor. The name is only used to help you as a user to identify this file system.
67+
/// This may be "root", "main", or "boot".
68+
pub fn new(
69+
name: &'name str,
70+
proto: ScopedProtocol<'boot_services, SimpleFileSystemProtocol>,
71+
) -> Self {
72+
Self { name, proto }
73+
}
74+
75+
/// Tests if the underlying file exists. If this returns `Ok`, the file exists.
76+
pub fn try_exists(&mut self, path: &str) -> FileSystemResult<()> {
77+
self.metadata(&path).map(|_| ())
78+
}
79+
80+
/// Copies the contents of one file to another. Creates the destination file if it doesn't
81+
/// exist and overwrites any content, if it exists.
82+
pub fn copy(&mut self, src_path: &str, dest_path: &str) -> FileSystemResult<()> {
83+
let read = self.read(src_path)?;
84+
self.write(dest_path, read)
85+
}
86+
87+
/// Creates a new, empty directory at the provided path
88+
pub fn create_dir(&mut self, path: &str) -> FileSystemResult<()> {
89+
let _path = Path::new(path)?;
90+
todo!()
91+
}
92+
93+
/// Recursively create a directory and all of its parent components if they are missing.
94+
pub fn create_dir_all(&mut self, path: &str) -> FileSystemResult<()> {
95+
let _path = Path::new(path)?;
96+
todo!()
97+
}
98+
99+
/// Given a path, query the file system to get information about a file, directory, etc.
100+
pub fn metadata(&mut self, path: &str) -> FileSystemResult<BoxedUefiFileInfo> {
101+
let path = Path::new(path)?;
102+
self.resolve_directory_entry_fileinfo(&path)
103+
}
104+
105+
/// Read the entire contents of a file into a bytes vector.
106+
pub fn read(&mut self, path: &str) -> FileSystemResult<Vec<u8>> {
107+
let path = Path::new(path)?;
108+
109+
let mut file = self
110+
.open_in_root(&path, UefiFileMode::Read)?
111+
.into_regular_file()
112+
.ok_or(FileSystemError::NotAFile(path.as_str().to_string()))?;
113+
let info = self.resolve_directory_entry_fileinfo(&path).unwrap();
114+
115+
let mut vec = Vec::with_capacity(info.file_size() as usize);
116+
vec.resize(vec.capacity(), 0);
117+
let read_bytes = file.read(vec.as_mut_slice()).map_err(|e| {
118+
log::error!("reading failed: {e:?}");
119+
FileSystemError::ReadFailure
120+
})?;
121+
122+
// we read the whole file at once!
123+
if read_bytes != info.file_size() as usize {
124+
log::error!("Did only read {}/{} bytes", info.file_size(), read_bytes);
125+
}
126+
127+
Ok(vec)
128+
}
129+
130+
/// Returns an iterator over the entries within a directory.
131+
pub fn read_dir(&mut self, path: &str) -> FileSystemResult<UefiDirectoryEntryIterator> {
132+
let path = Path::new(path)?;
133+
let parent_dir = self.open_parent_dir_handle_of_path(&path, UefiFileMode::Read)?;
134+
Ok(UefiDirectoryEntryIterator::new(parent_dir))
135+
}
136+
137+
/// Read the entire contents of a file into a string.
138+
pub fn read_to_string(&mut self, path: &str) -> FileSystemResult<String> {
139+
String::from_utf8(self.read(path)?).map_err(|x| FileSystemError::Utf8Error(x))
140+
}
141+
142+
/// Removes an empty directory.
143+
pub fn remove_dir(&mut self, path: &str) -> FileSystemResult<()> {
144+
let _path = Path::new(path)?;
145+
todo!()
146+
}
147+
148+
/// Removes a directory at this path, after removing all its contents. Use carefully!
149+
pub fn remove_dir_all(&mut self, path: &str) -> FileSystemResult<()> {
150+
let _path = Path::new(path)?;
151+
todo!()
152+
}
153+
154+
// Removes a file from the filesystem.
155+
pub fn remove_file(&mut self, path: &str) -> FileSystemResult<()> {
156+
let path = Path::new(path)?;
157+
158+
let file = self
159+
.open_in_root(&path, UefiFileMode::ReadWrite)?
160+
.into_type()
161+
.unwrap();
162+
163+
match file {
164+
FileType::Regular(file) => file.delete().map_err(|e| {
165+
log::error!("error removing file: {e:?}");
166+
FileSystemError::CantDeleteFile(path.as_str().to_string())
167+
}),
168+
FileType::Dir(_) => Err(FileSystemError::NotAFile(path.as_str().to_string())),
169+
}
170+
}
171+
172+
// Rename a file or directory to a new name, replacing the original file if it already exists.
173+
pub fn rename(&mut self, src_path: &str, dest_path: &str) -> FileSystemResult<()> {
174+
self.copy(src_path, dest_path)?;
175+
self.remove_file(src_path)
176+
}
177+
178+
/// Write a slice as the entire contents of a file. This function will create a file if it does
179+
/// not exist, and will entirely replace its contents if it does.
180+
pub fn write<C: AsRef<[u8]>>(&mut self, path: &str, content: C) -> FileSystemResult<()> {
181+
let path = Path::new(path)?;
182+
183+
// since there is no .truncate() in UEFI, we delete the file first it it exists.
184+
if self.try_exists(path.as_str()).is_ok() {
185+
self.remove_file(path.as_str())?;
186+
}
187+
188+
let mut handle = self
189+
.open_in_root(&path, UefiFileMode::CreateReadWrite)?
190+
.into_regular_file()
191+
.unwrap();
192+
193+
handle.write(content.as_ref()).map_err(|e| {
194+
log::error!("only wrote {e:?} bytes");
195+
FileSystemError::WriteFailure
196+
})?;
197+
handle.flush().map_err(|e| {
198+
log::error!("flush failure: {e:?}");
199+
FileSystemError::FlushFailure
200+
})?;
201+
Ok(())
202+
}
203+
204+
/// Resolves the [BoxedUefiFileInfo] of the final [Component].
205+
/// For example, looks up fileinfo of "bar" in "/foo/bar". The returned fileinfo belongs to a
206+
/// directory or a file.
207+
///
208+
/// # Parameters
209+
/// - `path`: whole user input for the path. Only required for error/debug information.
210+
fn resolve_directory_entry_fileinfo(
211+
&mut self,
212+
path: &Path,
213+
) -> FileSystemResult<BoxedUefiFileInfo> {
214+
let ucs2_filename = CString16::try_from(path.final_component().as_str()).unwrap();
215+
216+
let parent_dir = self.open_parent_dir_handle_of_path(path, UefiFileMode::Read)?;
217+
218+
// from my current knowledge, file info can only by obtained by iterating the parent dir
219+
UefiDirectoryEntryIterator::new(parent_dir)
220+
.find(|entry| entry.file_name() == ucs2_filename)
221+
.ok_or(FileSystemError::FileNotFound(path.as_str().to_string()))
222+
}
223+
224+
/// Opens a fresh handle to the root directory of the volume.
225+
fn open_root(&mut self) -> FileSystemResult<UefiDirectoryHandle> {
226+
let proto = unsafe { self.proto.interface.get().as_mut() }.unwrap();
227+
proto.open_volume().map_err(|e| {
228+
log::error!("Can't open root volume: {e:?}");
229+
FileSystemError::CantOpenVolume
230+
})
231+
}
232+
233+
/// Wrapper around [Self::open_root] that opens the specified path. May create a file if
234+
/// a corresonding [UefiFileMode] is set.
235+
fn open_in_root(
236+
&mut self,
237+
path: &Path,
238+
mode: UefiFileMode,
239+
) -> FileSystemResult<UefiFileHandle> {
240+
let ucs2_path = CString16::try_from(path.as_str_without_root()).unwrap();
241+
242+
self.open_root()?
243+
.open(&ucs2_path, mode, UefiFileAttribute::empty())
244+
.map_err(|x| {
245+
log::trace!("Can't open file {path}: {x:?}");
246+
FileSystemError::OpenError(path.as_str().to_string())
247+
})
248+
}
249+
250+
/// Opens a handle to the parent directory of the final component provided by `path`.
251+
fn open_parent_dir_handle_of_path(
252+
&mut self,
253+
path: &Path,
254+
mode: UefiFileMode,
255+
) -> FileSystemResult<UefiDirectoryHandle> {
256+
let mut parent_dir = self.open_root()?;
257+
if let Some(path_to_parent_dir) = path.to_path_of_parent() {
258+
// panic okay because the file must exist at that point (and thus, the parent dir)
259+
260+
let ucs2_path = CString16::try_from(path_to_parent_dir.as_str_without_root()).unwrap();
261+
let filetype = parent_dir
262+
.open(&ucs2_path, mode, UefiFileAttribute::empty())
263+
.map_err(|e| {
264+
log::error!("Can't open directory: {e:?}");
265+
FileSystemError::OpenError(path.as_str().to_string())
266+
})?
267+
.into_type()
268+
.expect("low level uefi error");
269+
assert!(matches!(filetype, UefiFileType::Dir(_)));
270+
}
271+
Ok(parent_dir)
272+
}
273+
}
274+
275+
impl<'name, 'boot_services> Debug for FileSystem<'name, 'boot_services> {
276+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
277+
f.debug_struct("FileSystem")
278+
.field("name", &self.name)
279+
.field("proto_ptr", &self.proto.interface.get())
280+
.finish()
281+
}
282+
}
283+
284+
/* nothing to clean up manually
285+
impl<'name, 'boot_services> Drop for FileSystem<'name, 'boot_services> {
286+
fn drop(&mut self) {
287+
//
288+
}
289+
}*/

0 commit comments

Comments
(0)

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