use crate::{
boot_services::BootServices,
device_path::{
node_defs::DevicePathType,
paths::{DevicePath, DevicePathBuf},
},
error::{EfiError, Result},
};
use r_efi::efi;
pub fn is_partial_device_path(device_path: &DevicePath) -> bool {
let Some(first_node) = device_path.iter().next() else {
return false;
};
let node_type = first_node.header.r#type;
node_type != DevicePathType::Hardware as u8
&& node_type != DevicePathType::Acpi as u8
&& node_type != DevicePathType::End as u8
}
pub fn expand_device_path<B: BootServices>(boot_services: &B, partial_path: &mut DevicePath) -> Result<DevicePathBuf> {
if !is_partial_device_path(partial_path) {
return Ok((&*partial_path).into());
}
let mut device_path_ptr = partial_path as *mut DevicePath as *mut u8 as *mut efi::protocols::device_path::Protocol;
let handle =
unsafe { boot_services.locate_device_path(&efi::protocols::device_path::PROTOCOL_GUID, &mut device_path_ptr) }
.map_err(EfiError::from)?;
let full_dp_ptr = unsafe { boot_services.handle_protocol::<efi::protocols::device_path::Protocol>(handle) }
.map_err(EfiError::from)?;
let full_path =
unsafe { DevicePath::try_from_ptr(full_dp_ptr as *const _ as *const u8) }.map_err(|_| EfiError::DeviceError)?;
let mut result = DevicePathBuf::from(full_path);
let remaining_path = unsafe { DevicePath::try_from_ptr(device_path_ptr as *const u8) };
if let Ok(remaining) = remaining_path {
if remaining.iter().any(|node| node.header.r#type != DevicePathType::End as u8) {
result.append_device_path(&DevicePathBuf::from(remaining));
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
extern crate alloc;
extern crate std;
use super::*;
use crate::{
boot_services::MockBootServices,
device_path::node_defs::{Acpi, EndEntire, FilePath, HardDrive, Pci},
};
use alloc::boxed::Box;
fn build_partial_hd_path(guid: [u8; 16]) -> DevicePathBuf {
DevicePathBuf::from_device_path_node_iter([HardDrive::new_gpt(1, 2048, 1000000, guid)].into_iter())
}
fn build_full_path_with_hd(guid: [u8; 16]) -> DevicePathBuf {
let mut path = DevicePathBuf::from_device_path_node_iter([Acpi::new_pci_root(0)].into_iter());
let pci_path = DevicePathBuf::from_device_path_node_iter([Pci { function: 0, device: 0x1D }].into_iter());
path.append_device_path(&pci_path);
let hd_path =
DevicePathBuf::from_device_path_node_iter([HardDrive::new_gpt(1, 2048, 1000000, guid)].into_iter());
path.append_device_path(&hd_path);
path
}
#[test]
fn test_is_partial_with_hd_node() {
let partial = build_partial_hd_path([0xAA; 16]);
assert!(is_partial_device_path(&partial));
}
#[test]
fn test_is_partial_with_full_path_acpi() {
let full = build_full_path_with_hd([0xAA; 16]);
assert!(!is_partial_device_path(&full));
}
#[test]
fn test_is_partial_empty_path() {
let empty = DevicePathBuf::from_device_path_node_iter([EndEntire].into_iter());
assert!(!is_partial_device_path(&empty));
}
#[test]
fn test_expand_already_full_returns_unchanged() {
let mut full = build_full_path_with_hd([0xAA; 16]);
let expected = full.clone();
let mock = MockBootServices::new();
let result = expand_device_path(&mock, &mut full);
assert!(result.is_ok());
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_expand_partial_path_success() {
let guid = [0xAA; 16];
let mut partial = build_partial_hd_path(guid);
let file_path =
DevicePathBuf::from_device_path_node_iter([FilePath::new("\\EFI\\BOOT\\BOOTX64.EFI")].into_iter());
partial.append_device_path(&file_path);
let full_handle_path = build_full_path_with_hd(guid);
let mut expected = full_handle_path.clone();
expected.append_device_path(&file_path);
let path_ref: &DevicePath = full_handle_path.as_ref();
let bytes: alloc::vec::Vec<u8> = unsafe {
alloc::vec::Vec::from(core::slice::from_raw_parts(path_ref as *const _ as *const u8, path_ref.size()))
};
let leaked_bytes = Box::leak(bytes.into_boxed_slice());
let full_path_ptr: usize = leaked_bytes.as_ptr() as usize;
let fake_handle_addr: usize = 0x12345678;
let mut mock = MockBootServices::new();
mock.expect_locate_device_path().returning(move |_protocol, device_path_ptr| {
unsafe {
let current_ptr = *device_path_ptr as *const u8;
let header = current_ptr as *const efi::protocols::device_path::Protocol;
let hd_node_size = u16::from_le_bytes([(*header).length[0], (*header).length[1]]) as usize;
*device_path_ptr = current_ptr.add(hd_node_size) as *mut efi::protocols::device_path::Protocol;
}
Ok(fake_handle_addr as *mut core::ffi::c_void)
});
mock.expect_handle_protocol::<efi::protocols::device_path::Protocol>().returning(move |_handle| {
Ok(unsafe { &mut *(full_path_ptr as *mut efi::protocols::device_path::Protocol) })
});
let result = expand_device_path(&mock, &mut partial);
assert!(result.is_ok(), "expand_device_path should succeed");
let expanded = result.unwrap();
assert_eq!(expanded, expected, "Expanded path should match expected full path with file");
}
#[test]
fn test_expand_partial_path_not_found() {
let mut partial = build_partial_hd_path([0xBB; 16]);
let mut mock = MockBootServices::new();
mock.expect_locate_device_path().returning(|_protocol, _device_path_ptr| Err(efi::Status::NOT_FOUND));
let result = expand_device_path(&mock, &mut partial);
assert!(result.is_err(), "expand_device_path should fail when device not found");
}
#[test]
fn test_expand_partial_path_handle_protocol_fails() {
let mut partial = build_partial_hd_path([0xCC; 16]);
let fake_handle_addr: usize = 0x87654321;
let mut mock = MockBootServices::new();
mock.expect_locate_device_path()
.returning(move |_protocol, _device_path_ptr| Ok(fake_handle_addr as *mut core::ffi::c_void));
mock.expect_handle_protocol::<efi::protocols::device_path::Protocol>()
.returning(|_handle| Err(efi::Status::UNSUPPORTED));
let result = expand_device_path(&mock, &mut partial);
assert!(result.is_err(), "expand_device_path should fail when handle_protocol fails");
}
}