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