|
| 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 | +} |
0 commit comments