Skip to content

Commit 8c9ecf2

Browse files
committed
test: add load_image integration test
1 parent 26d82da commit 8c9ecf2

File tree

4 files changed

+114
-13
lines changed

4 files changed

+114
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
### Added
66
- `DevicePath::to_boxed`, `DevicePath::to_owned`, and `DevicePath::as_bytes`
77
- `DevicePathInstance::to_boxed`, `DevicePathInstance::to_owned`, and `DevicePathInstance::as_bytes`
8-
- `DevicePathNode::data`
8+
- `DevicePathNode::data`, `DevicePathNode::data_u16`, and corresponding `Borrow<T>` implementations
99

1010
### Changed
1111

uefi-test-runner/src/boot/mod.rs

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1+
use alloc::string::ToString;
12
use uefi::proto::console::text::Output;
2-
use uefi::table::boot::{BootServices, SearchType};
3+
use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType, LoadedImageDevicePath};
4+
use uefi::table::boot::{BootServices, LoadImageSource, SearchType};
35
use uefi::table::{Boot, SystemTable};
4-
use uefi::Identify;
6+
use uefi::{CStr16, CString16, Identify};
7+
8+
mod memory;
9+
mod misc;
510

611
pub fn test(st: &SystemTable<Boot>) {
712
let bt = st.boot_services();
813
info!("Testing boot services");
914
memory::test(bt);
1015
misc::test(st);
1116
test_locate_handle_buffer(bt);
17+
test_load_image(bt);
1218
}
1319

14-
mod memory;
15-
mod misc;
16-
1720
fn test_locate_handle_buffer(bt: &BootServices) {
1821
info!("Testing the `locate_handle_buffer` function");
1922

@@ -36,3 +39,72 @@ fn test_locate_handle_buffer(bt: &BootServices) {
3639
);
3740
}
3841
}
42+
43+
/// This test loads the "self image" again into memory using the `load_image`
44+
/// boot service function. The image is not started but just loaded into memory.
45+
///
46+
/// It transitively tests the protocol [`LoadedImageDevicePath`] which is
47+
/// required as helper.
48+
fn test_load_image(bt: &BootServices) {
49+
info!("Testing the `load_image` function");
50+
51+
let image_device_path_protocol = bt
52+
.open_protocol_exclusive::<LoadedImageDevicePath>(bt.image_handle())
53+
.expect("should open LoadedImage protocol");
54+
55+
// Note: This is the full device path. The LoadedImage protocol would only
56+
// provide us with the file-path portion of the device path.
57+
let image_device_path: &DevicePath = &image_device_path_protocol;
58+
59+
// Get the file-path portion of the device path which is typically behind
60+
// device path node (0x4, 0x4).
61+
// TODO we need an abstraction in the UEFI crate for this.
62+
let image_device_path_file_path = image_device_path
63+
.node_iter()
64+
.find(|node| {
65+
node.device_type() == DeviceType::MEDIA /* 0x4 */
66+
&& node.sub_type() == DeviceSubType::HARDWARE_VENDOR /* 0x4 */
67+
})
68+
.map(|node| {
69+
CStr16::from_u16_with_nul(node.data_u16().unwrap()).expect("should be valid CCtr16")
70+
})
71+
// to Rust string
72+
.map(|cstr16| cstr16.to_string().to_uppercase())
73+
.expect("should have file-path portion in device path");
74+
75+
// On x86_64, this will be `\EFI\BOOT\BOOTX64.EFI` for example.
76+
assert!(image_device_path_file_path
77+
.as_str()
78+
.starts_with(r"\EFI\BOOT\BOOT"));
79+
assert!(image_device_path_file_path.as_str().ends_with(".EFI"));
80+
81+
// Variant A: FromBuffer
82+
{
83+
let mut fs = bt
84+
.get_image_file_system(bt.image_handle())
85+
.expect("should open file system");
86+
let path = CString16::try_from(image_device_path_file_path.as_str()).unwrap();
87+
let image_data = fs.read(&*path).expect("should read file content");
88+
let load_source = LoadImageSource::FromBuffer {
89+
buffer: image_data.as_slice(),
90+
file_path: None,
91+
};
92+
let _ = bt
93+
.load_image(bt.image_handle(), load_source)
94+
.expect("should load image");
95+
96+
log::debug!("load_image with FromBuffer strategy works");
97+
}
98+
// Variant B: FromFilePath
99+
{
100+
let load_source = LoadImageSource::FromFilePath {
101+
file_path: image_device_path,
102+
from_boot_manager: false,
103+
};
104+
let _ = bt
105+
.load_image(bt.image_handle(), load_source)
106+
.expect("should load image");
107+
108+
log::debug!("load_image with FromFilePath strategy works");
109+
}
110+
}

uefi/src/data_types/strs.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,8 @@ impl CStr16 {
211211
Self::from_u16_with_nul_unchecked(slice::from_raw_parts(ptr, len + 1))
212212
}
213213

214-
/// Creates a C string wrapper from a u16 slice
215-
///
216-
/// Since not every u16 value is a valid UCS-2 code point, this function
217-
/// must do a bit more validity checking than CStr::from_bytes_with_nul
214+
/// Creates a `&CStr16` from a u16 slice, if the slice contains exactly
215+
/// one terminating null-byte and all chars are valid UCS-2 chars.
218216
pub fn from_u16_with_nul(codes: &[u16]) -> Result<&Self, FromSliceWithNulError> {
219217
for (pos, &code) in codes.iter().enumerate() {
220218
match code.try_into() {
@@ -234,7 +232,7 @@ impl CStr16 {
234232
Err(FromSliceWithNulError::NotNulTerminated)
235233
}
236234

237-
/// Unsafely creates a C string wrapper from a u16 slice.
235+
/// Unsafely creates a `&CStr16` from a u16 slice.
238236
///
239237
/// # Safety
240238
///
@@ -287,11 +285,13 @@ impl CStr16 {
287285
Self::from_u16_with_nul(&buf[..index + 1]).map_err(|err| match err {
288286
FromSliceWithNulError::InvalidChar(p) => FromStrWithBufError::InvalidChar(p),
289287
FromSliceWithNulError::InteriorNul(p) => FromStrWithBufError::InteriorNul(p),
290-
FromSliceWithNulError::NotNulTerminated => unreachable!(),
288+
FromSliceWithNulError::NotNulTerminated => {
289+
unreachable!()
290+
}
291291
})
292292
}
293293

294-
/// Create a [`CStr16`] from an [`UnalignedSlice`] using an aligned
294+
/// Create a `&CStr16` from an [`UnalignedSlice`] using an aligned
295295
/// buffer for storage. The lifetime of the output is tied to `buf`,
296296
/// not `src`.
297297
pub fn from_unaligned_slice<'buf>(

uefi/src/proto/device_path/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub use device_path_gen::{
8484
use {alloc::borrow::ToOwned, alloc::boxed::Box};
8585

8686
use crate::proto::{unsafe_protocol, ProtocolPointer};
87+
use core::borrow::Borrow;
8788
use core::ffi::c_void;
8889
use core::fmt::{self, Debug, Formatter};
8990
use core::mem;
@@ -184,6 +185,22 @@ impl DevicePathNode {
184185
&self.data
185186
}
186187

188+
/// Returns the payload data of this node as u16 slice,
189+
/// if the slice can be mapped to a `u16` slice..
190+
#[must_use]
191+
pub fn data_u16(&self) -> Option<&[u16]> {
192+
let multiple_of_u16 = self.data.len() % 2 == 0;
193+
let aligned = self.data.as_ptr() as u64 & 0xf == 0;
194+
if !multiple_of_u16 || !aligned {
195+
log::debug!("multiple_of_16={multiple_of_u16}, aligned={aligned}");
196+
None
197+
} else {
198+
Some(unsafe {
199+
core::slice::from_raw_parts(self.data.as_ptr().cast(), self.data.len() / 2)
200+
})
201+
}
202+
}
203+
187204
/// Convert from a generic [`DevicePathNode`] reference to an enum
188205
/// of more specific node types.
189206
pub fn as_enum(&self) -> Result<DevicePathNodeEnum, NodeConversionError> {
@@ -206,6 +223,18 @@ impl PartialEq for DevicePathNode {
206223
}
207224
}
208225

226+
impl Borrow<[u8]> for DevicePathNode {
227+
fn borrow(&self) -> &[u8] {
228+
self.data()
229+
}
230+
}
231+
232+
impl Borrow<[u16]> for DevicePathNode {
233+
fn borrow(&self) -> &[u16] {
234+
self.data_u16().expect("should be convertible to u16 slice")
235+
}
236+
}
237+
209238
/// A single device path instance that ends with either an [`END_INSTANCE`]
210239
/// or [`END_ENTIRE`] node. Use [`DevicePath::instance_iter`] to get the
211240
/// path instances in a [`DevicePath`].

0 commit comments

Comments
 (0)