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 3a70961

Browse files
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)

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