Skip to content

Commit 68efc82

Browse files
committed
add memory util mem::make_boxed
1 parent 4e8e09e commit 68efc82

File tree

4 files changed

+148
-102
lines changed

4 files changed

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

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)