Skip to content

Commit 04b385d

Browse files
committed
add memory util mem::make_boxed
1 parent a497b64 commit 04b385d

File tree

4 files changed

+149
-102
lines changed

4 files changed

+149
-102
lines changed

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: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 off 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<'a, Data: Align + ?Sized + Debug + 'a>(
21+
mut fetch_data_fn: impl FnMut(&'a mut [u8]) -> Result<&'a mut Data, Option<usize>>,
22+
) -> Result<Box<Data>> {
23+
let required_size = match fetch_data_fn(&mut []).map_err(Error::split) {
24+
// This is the expected case: the empty buffer passed in is too
25+
// small, so we get the required size.
26+
Err((Status::BUFFER_TOO_SMALL, Some(required_size))) => Ok(required_size),
27+
// Propagate any other error.
28+
Err((status, _)) => Err(Error::from(status)),
29+
// Success is unexpected, return an error.
30+
Ok(_) => Err(Error::from(Status::UNSUPPORTED)),
31+
}?;
32+
33+
// We add trailing padding because the size of a rust structure must
34+
// always be a multiple of alignment.
35+
let layout = Layout::from_size_align(required_size, Data::alignment())
36+
.unwrap()
37+
.pad_to_align();
38+
39+
// Allocate the buffer.
40+
let heap_buf: *mut u8 = unsafe {
41+
let ptr = alloc::alloc(layout);
42+
if ptr.is_null() {
43+
return Err(Status::OUT_OF_RESOURCES.into());
44+
}
45+
ptr
46+
};
47+
48+
// Read the data into the provided buffer.
49+
let data: Result<&mut Data> = {
50+
let buffer = unsafe { slice::from_raw_parts_mut(heap_buf, required_size) };
51+
fetch_data_fn(buffer).discard_errdata()
52+
};
53+
54+
// If an error occurred, deallocate the memory before returning.
55+
let data: &mut Data = match data {
56+
Ok(data) => data,
57+
Err(err) => {
58+
unsafe { alloc::dealloc(heap_buf, layout) };
59+
return Err(err);
60+
}
61+
};
62+
63+
let data = unsafe { Box::from_raw(data) };
64+
65+
Ok(data)
66+
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
use crate::ResultExt;
72+
use core::mem::{align_of, size_of};
73+
74+
#[derive(Debug)]
75+
#[repr(C)]
76+
struct SomeData([u8; 4]);
77+
78+
impl Align for SomeData {
79+
fn alignment() -> usize {
80+
align_of::<Self>()
81+
}
82+
}
83+
84+
/// Function that behaves like the other UEFI functions. It takes a
85+
/// mutable reference to a buffer memory that represents a [`SomeData`]
86+
/// instance.
87+
fn uefi_function_stub_read(buf: &mut [u8]) -> Result<&mut SomeData, Option<usize>> {
88+
if buf.len() < 4 {
89+
return Status::BUFFER_TOO_SMALL.into_with(|| panic!(), |_| Some(4));
90+
};
91+
92+
buf[0] = 1;
93+
buf[1] = 2;
94+
buf[2] = 3;
95+
buf[3] = 4;
96+
97+
let data = unsafe { &mut *buf.as_mut_ptr().cast::<SomeData>() };
98+
99+
Ok(data)
100+
}
101+
102+
// Some basic checks so that miri reports everything is fine.
103+
#[test]
104+
fn some_data_type_size_constraints() {
105+
assert_eq!(size_of::<SomeData>(), 4);
106+
assert_eq!(align_of::<SomeData>(), 1);
107+
}
108+
109+
#[test]
110+
fn basic_stub_read() {
111+
assert_eq!(
112+
uefi_function_stub_read(&mut []).status(),
113+
Status::BUFFER_TOO_SMALL
114+
);
115+
assert_eq!(
116+
*uefi_function_stub_read(&mut []).unwrap_err().data(),
117+
Some(4)
118+
);
119+
120+
let mut buf: [u8; 4] = [0; 4];
121+
let data = uefi_function_stub_read(&mut buf).unwrap();
122+
123+
assert_eq!(&data.0, &[1, 2, 3, 4])
124+
}
125+
126+
#[test]
127+
fn make_boxed_utility() {
128+
let fetch_data_fn = |buf| uefi_function_stub_read(buf);
129+
let data: Box<SomeData> = make_boxed(fetch_data_fn).unwrap();
130+
131+
assert_eq!(&data.0, &[1, 2, 3, 4])
132+
}
133+
}

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

Lines changed: 9 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@ use crate::Result;
44
use core::ffi::c_void;
55

66
#[cfg(feature = "alloc")]
7-
use {
8-
crate::{ResultExt, Status},
9-
::alloc::boxed::Box,
10-
alloc::alloc,
11-
core::alloc::Layout,
12-
core::ptr::NonNull,
13-
core::slice,
14-
};
7+
use {crate::mem::make_boxed, alloc::boxed::Box};
158

169
/// A `FileHandle` that is also a directory.
1710
///
@@ -77,51 +70,15 @@ impl Directory {
7770
return Ok(None);
7871
}
7972

80-
let required_size = match read_entry_res
81-
.expect_err("zero sized read unexpectedly succeeded")
82-
.split()
83-
{
84-
// Early return if something has failed.
85-
(s, None) => return Err(s.into()),
86-
(_, Some(required_size)) => required_size,
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+
})
8779
};
88-
89-
// We add trailing padding because the size of a rust structure must
90-
// always be a multiple of alignment.
91-
let layout = Layout::from_size_align(required_size, FileInfo::alignment())
92-
.unwrap()
93-
.pad_to_align();
94-
95-
// Allocate the buffer.
96-
let heap_buf: NonNull<u8> = unsafe {
97-
let ptr = alloc::alloc(layout);
98-
match NonNull::new(ptr) {
99-
None => return Err(Status::OUT_OF_RESOURCES.into()),
100-
Some(ptr) => ptr,
101-
}
102-
};
103-
104-
// Get the file info using the allocated buffer for storage.
105-
let info = {
106-
let buffer = unsafe { slice::from_raw_parts_mut(heap_buf.as_ptr(), layout.size()) };
107-
self.read_entry(buffer).discard_errdata()
108-
};
109-
110-
// If an error occurred, deallocate the memory before returning.
111-
let info = match info {
112-
Ok(info) => info,
113-
Err(err) => {
114-
unsafe { alloc::dealloc(heap_buf.as_ptr(), layout) };
115-
return Err(err);
116-
}
117-
};
118-
119-
// Wrap the file info in a box so that it will be deallocated on
120-
// drop. This is valid because the memory was allocated with the
121-
// global allocator.
122-
let info = info.map(|info| unsafe { Box::from_raw(info) });
123-
124-
Ok(info)
80+
let file_info = make_boxed::<FileInfo>(fetch_data_fn)?;
81+
Ok(Some(file_info))
12582
}
12683

12784
/// Start over the process of enumerating directory entries

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

Lines changed: 4 additions & 50 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};
@@ -168,51 +164,9 @@ pub trait File: Sized {
168164
#[cfg(feature = "alloc")]
169165
/// Get 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)