Skip to content

Commit 3a70961

Browse files
authored
Merge pull request #559 from phip1611/dir-info-boxed
`Directory::read_entry_boxed` plus common abstraction `make_boxed`
2 parents 8c070c7 + 5fdccbd commit 3a70961

File tree

7 files changed

+269
-65
lines changed

7 files changed

+269
-65
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
you can write `some_cstr16.eq_str_until_nul("test")` instead of
66
`some_cstr16.eq_str_until_nul(&"test")` now.
77
- Added `TryFrom<core::ffi::CStr>` implementation for `CStr8`.
8+
- Added `Directory::read_entry_boxed` which works similar to `File::get_boxed_info`. This allows
9+
easier iteration over the entries in a directory.
810

911
## uefi-macros - [Unreleased]
1012

uefi-test-runner/src/proto/media/known_disk.rs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::string::ToString;
2+
use core::cell::RefCell;
23
use core::ptr::NonNull;
34
use uefi::prelude::*;
45
use uefi::proto::media::block::BlockIO;
@@ -21,20 +22,61 @@ fn test_existing_dir(directory: &mut Directory) {
2122

2223
assert!(dir.is_directory().unwrap());
2324

24-
let mut dir = dir.into_directory().expect("not a directory");
25-
26-
// Collect and validate the directory entries.
27-
let mut entry_names = vec![];
28-
let mut buf = vec![0; 200];
29-
loop {
30-
let entry = dir.read_entry(&mut buf).expect("failed to read directory");
31-
if let Some(entry) = entry {
32-
entry_names.push(entry.file_name().to_string());
33-
} else {
34-
break;
25+
let dir = dir.into_directory().expect("Should be a directory");
26+
27+
let dir = RefCell::new(dir);
28+
29+
// Backing memory to read the file info data into.
30+
let mut stack_buf = [0; 200];
31+
32+
// The file names that the test read from the directory.
33+
let entry_names = RefCell::new(vec![]);
34+
35+
// Expected file names in the directory.
36+
const EXPECTED: &[&str] = &[".", "..", "test_input.txt"];
37+
38+
// Reads the whole directory with provided backing memory.
39+
let mut test_read_dir_stack_mem = || {
40+
let mut dir = dir.borrow_mut();
41+
let mut entry_names = entry_names.borrow_mut();
42+
loop {
43+
let entry = dir
44+
.read_entry(&mut stack_buf)
45+
.expect("failed to read directory");
46+
if let Some(entry) = entry {
47+
entry_names.push(entry.file_name().to_string());
48+
} else {
49+
break;
50+
}
3551
}
52+
assert_eq!(&*entry_names, EXPECTED);
53+
};
54+
55+
// Reads the whole directory but returns owned memory on the heap.
56+
let test_read_dir_heap_mem = || {
57+
let mut dir = dir.borrow_mut();
58+
let mut entry_names = entry_names.borrow_mut();
59+
loop {
60+
let entry = dir.read_entry_boxed().expect("failed to read directory");
61+
if let Some(entry) = entry {
62+
entry_names.push(entry.file_name().to_string());
63+
} else {
64+
break;
65+
}
66+
}
67+
assert_eq!(&*entry_names, EXPECTED);
68+
};
69+
70+
// Tests all read dir test functions three times.
71+
for _ in 0..3 {
72+
entry_names.borrow_mut().clear();
73+
dir.borrow_mut().reset_entry_readout().unwrap();
74+
test_read_dir_stack_mem();
75+
76+
entry_names.borrow_mut().clear();
77+
dir.borrow_mut().reset_entry_readout().unwrap();
78+
test_read_dir_heap_mem();
3679
}
37-
assert_eq!(entry_names, [".", "..", "test_input.txt"]);
3880
}
3981

4082
/// Test that deleting a file opened in read-only mode fails with a

uefi/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,6 @@ pub mod global_allocator;
7979

8080
#[cfg(feature = "logger")]
8181
pub mod logger;
82+
83+
#[cfg(feature = "alloc")]
84+
pub(crate) mod mem;

uefi/src/mem.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//! This is a utility module with helper methods for allocations/memory.
2+
3+
use crate::ResultExt;
4+
use crate::{Result, Status};
5+
use ::alloc::{alloc, boxed::Box};
6+
use core::alloc::Layout;
7+
use core::fmt::Debug;
8+
use core::slice;
9+
use uefi::data_types::Align;
10+
use uefi::Error;
11+
12+
/// Helper to return owned versions of certain UEFI data structures on the heap in a [`Box`]. This
13+
/// function is intended to wrap low-level UEFI functions of this crate that
14+
/// - can consume an empty buffer without a panic to get the required buffer size in the errors
15+
/// payload,
16+
/// - consume a mutable reference to a buffer that will be filled with some data if the provided
17+
/// buffer size is sufficient, and
18+
/// - return a mutable typed reference that points to the same memory as the input buffer on
19+
/// success.
20+
pub fn make_boxed<
21+
'a,
22+
Data: Align + ?Sized + Debug + 'a,
23+
F: FnMut(&'a mut [u8]) -> Result<&'a mut Data, Option<usize>>,
24+
>(
25+
mut fetch_data_fn: F,
26+
) -> Result<Box<Data>> {
27+
let required_size = match fetch_data_fn(&mut []).map_err(Error::split) {
28+
// This is the expected case: the empty buffer passed in is too
29+
// small, so we get the required size.
30+
Err((Status::BUFFER_TOO_SMALL, Some(required_size))) => Ok(required_size),
31+
// Propagate any other error.
32+
Err((status, _)) => Err(Error::from(status)),
33+
// Success is unexpected, return an error.
34+
Ok(_) => Err(Error::from(Status::UNSUPPORTED)),
35+
}?;
36+
37+
// We add trailing padding because the size of a rust structure must
38+
// always be a multiple of alignment.
39+
let layout = Layout::from_size_align(required_size, Data::alignment())
40+
.unwrap()
41+
.pad_to_align();
42+
43+
// Allocate the buffer.
44+
let heap_buf: *mut u8 = unsafe {
45+
let ptr = alloc::alloc(layout);
46+
if ptr.is_null() {
47+
return Err(Status::OUT_OF_RESOURCES.into());
48+
}
49+
ptr
50+
};
51+
52+
// Read the data into the provided buffer.
53+
let data: Result<&mut Data> = {
54+
let buffer = unsafe { slice::from_raw_parts_mut(heap_buf, required_size) };
55+
fetch_data_fn(buffer).discard_errdata()
56+
};
57+
58+
// If an error occurred, deallocate the memory before returning.
59+
let data: &mut Data = match data {
60+
Ok(data) => data,
61+
Err(err) => {
62+
unsafe { alloc::dealloc(heap_buf, layout) };
63+
return Err(err);
64+
}
65+
};
66+
67+
let data = unsafe { Box::from_raw(data) };
68+
69+
Ok(data)
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::*;
75+
use crate::ResultExt;
76+
use core::mem::{align_of, size_of};
77+
78+
/// Some simple dummy type to test [`make_boxed`].
79+
#[derive(Debug)]
80+
#[repr(C)]
81+
struct SomeData([u8; 4]);
82+
83+
impl Align for SomeData {
84+
fn alignment() -> usize {
85+
align_of::<Self>()
86+
}
87+
}
88+
89+
/// Type wrapper that ensures an alignment of 16 for the underlying data.
90+
#[derive(Debug)]
91+
#[repr(C, align(16))]
92+
struct Align16<T>(T);
93+
94+
/// Version of [`SomeData`] that has an alignment of 16.
95+
type SomeDataAlign16 = Align16<SomeData>;
96+
97+
impl Align for SomeDataAlign16 {
98+
fn alignment() -> usize {
99+
align_of::<Self>()
100+
}
101+
}
102+
103+
/// Function that behaves like the other UEFI functions. It takes a
104+
/// mutable reference to a buffer memory that represents a [`SomeData`]
105+
/// instance.
106+
fn uefi_function_stub_read<Data: Align>(buf: &mut [u8]) -> Result<&mut Data, Option<usize>> {
107+
let required_size = size_of::<Data>();
108+
109+
if buf.len() < required_size {
110+
// We can use an zero-length buffer to find the required size.
111+
return Status::BUFFER_TOO_SMALL.into_with(|| panic!(), |_| Some(required_size));
112+
};
113+
114+
// assert alignment
115+
assert_eq!(
116+
buf.as_ptr() as usize % Data::alignment(),
117+
0,
118+
"The buffer must be correctly aligned!"
119+
);
120+
121+
buf[0] = 1;
122+
buf[1] = 2;
123+
buf[2] = 3;
124+
buf[3] = 4;
125+
126+
let data = unsafe { &mut *buf.as_mut_ptr().cast::<Data>() };
127+
128+
Ok(data)
129+
}
130+
131+
// Some basic sanity checks so that we can catch problems early that miri would detect
132+
// otherwise.
133+
#[test]
134+
fn test_some_data_type_size_constraints() {
135+
assert_eq!(size_of::<SomeData>(), 4);
136+
assert_eq!(SomeData::alignment(), 1);
137+
assert_eq!(
138+
size_of::<SomeDataAlign16>(),
139+
16,
140+
"The size must be 16 instead of 4, as in Rust the runtime size is a multiple of the alignment."
141+
);
142+
assert_eq!(SomeDataAlign16::alignment(), 16);
143+
}
144+
145+
// Tests `uefi_function_stub_read` which is the foundation for the `test_make_boxed_utility`
146+
// test.
147+
#[test]
148+
fn test_basic_stub_read() {
149+
assert_eq!(
150+
uefi_function_stub_read::<SomeData>(&mut []).status(),
151+
Status::BUFFER_TOO_SMALL
152+
);
153+
assert_eq!(
154+
*uefi_function_stub_read::<SomeData>(&mut [])
155+
.unwrap_err()
156+
.data(),
157+
Some(4)
158+
);
159+
160+
let mut buf: [u8; 4] = [0; 4];
161+
let data: &mut SomeData = uefi_function_stub_read(&mut buf).unwrap();
162+
assert_eq!(&data.0, &[1, 2, 3, 4]);
163+
164+
let mut buf: Align16<[u8; 16]> = Align16([0; 16]);
165+
let data: &mut SomeDataAlign16 = uefi_function_stub_read(&mut buf.0).unwrap();
166+
assert_eq!(&data.0 .0, &[1, 2, 3, 4]);
167+
}
168+
169+
#[test]
170+
fn test_make_boxed_utility() {
171+
let fetch_data_fn = |buf| uefi_function_stub_read(buf);
172+
let data: Box<SomeData> = make_boxed(fetch_data_fn).unwrap();
173+
assert_eq!(&data.0, &[1, 2, 3, 4]);
174+
175+
let fetch_data_fn = |buf| uefi_function_stub_read(buf);
176+
let data: Box<SomeDataAlign16> = make_boxed(fetch_data_fn).unwrap();
177+
assert_eq!(&data.0 .0, &[1, 2, 3, 4]);
178+
}
179+
}

uefi/src/proto/media/file/dir.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use crate::data_types::Align;
33
use crate::Result;
44
use core::ffi::c_void;
55

6+
#[cfg(feature = "alloc")]
7+
use {crate::mem::make_boxed, alloc::boxed::Box};
8+
69
/// A `FileHandle` that is also a directory.
710
///
811
/// Use `File::into_type` or `Directory::new` to create a `Directory`. In
@@ -20,7 +23,7 @@ impl Directory {
2023
Self(RegularFile::new(handle))
2124
}
2225

23-
/// Read the next directory entry
26+
/// Read the next directory entry.
2427
///
2528
/// Try to read the next directory entry into `buffer`. If the buffer is too small, report the
2629
/// required buffer size as part of the error. If there are no more directory entries, return
@@ -56,6 +59,28 @@ impl Directory {
5659
})
5760
}
5861

62+
/// Wrapper around [`Self::read_entry`] that returns an owned copy of the data. It has the same
63+
/// implications and requirements. On failure, the payload of `Err` is `()´.
64+
#[cfg(feature = "alloc")]
65+
pub fn read_entry_boxed(&mut self) -> Result<Option<Box<FileInfo>>> {
66+
let read_entry_res = self.read_entry(&mut []);
67+
68+
// If no more entries are available, return early.
69+
if let Ok(None) = read_entry_res {
70+
return Ok(None);
71+
}
72+
73+
let fetch_data_fn = |buf| {
74+
self.read_entry(buf)
75+
// this is safe, as above, we checked that there are more entries
76+
.map(|maybe_info: Option<&mut FileInfo>| {
77+
maybe_info.expect("Should have more entries")
78+
})
79+
};
80+
let file_info = make_boxed::<FileInfo, _>(fetch_data_fn)?;
81+
Ok(Some(file_info))
82+
}
83+
5984
/// Start over the process of enumerating directory entries
6085
pub fn reset_entry_readout(&mut self) -> Result {
6186
self.0.set_position(0)

uefi/src/proto/media/file/mod.rs

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ use core::fmt::Debug;
1717
use core::mem;
1818
use core::ptr;
1919
#[cfg(feature = "alloc")]
20-
use {
21-
crate::ResultExt,
22-
::alloc::{alloc, alloc::Layout, boxed::Box},
23-
core::slice,
24-
};
20+
use {alloc::boxed::Box, uefi::mem::make_boxed};
2521

2622
pub use self::info::{FileInfo, FileProtocolInfo, FileSystemInfo, FileSystemVolumeLabel, FromUefi};
2723
pub use self::{dir::Directory, regular::RegularFile};
@@ -166,53 +162,11 @@ pub trait File: Sized {
166162
}
167163

168164
#[cfg(feature = "alloc")]
169-
/// Get the dynamically allocated info for a file
165+
/// Read the dynamically allocated info for a file.
170166
fn get_boxed_info<Info: FileProtocolInfo + ?Sized + Debug>(&mut self) -> Result<Box<Info>> {
171-
// Initially try get_info with an empty array, this should always fail
172-
// as all Info types at least need room for a null-terminator.
173-
let size = match self
174-
.get_info::<Info>(&mut [])
175-
.expect_err("zero sized get_info unexpectedly succeeded")
176-
.split()
177-
{
178-
(s, None) => return Err(s.into()),
179-
(_, Some(size)) => size,
180-
};
181-
182-
// We add trailing padding because the size of a rust structure must
183-
// always be a multiple of alignment.
184-
let layout = Layout::from_size_align(size, Info::alignment())
185-
.unwrap()
186-
.pad_to_align();
187-
188-
// Allocate the buffer.
189-
let data: *mut u8 = unsafe {
190-
let data = alloc::alloc(layout);
191-
if data.is_null() {
192-
return Err(Status::OUT_OF_RESOURCES.into());
193-
}
194-
data
195-
};
196-
197-
// Get the file info using the allocated buffer for storage.
198-
let info = {
199-
let buffer = unsafe { slice::from_raw_parts_mut(data, layout.size()) };
200-
self.get_info::<Info>(buffer).discard_errdata()
201-
};
202-
203-
// If an error occurred, deallocate the memory before returning.
204-
let info = match info {
205-
Ok(info) => info,
206-
Err(err) => {
207-
unsafe { alloc::dealloc(data, layout) };
208-
return Err(err);
209-
}
210-
};
211-
212-
// Wrap the file info in a box so that it will be deallocated on
213-
// drop. This is valid because the memory was allocated with the
214-
// global allocator.
215-
unsafe { Ok(Box::from_raw(info)) }
167+
let fetch_data_fn = |buf| self.get_info::<Info>(buf);
168+
let file_info = make_boxed::<Info, _>(fetch_data_fn)?;
169+
Ok(file_info)
216170
}
217171

218172
/// Returns if the underlying file is a regular file.

0 commit comments

Comments
 (0)