pub mod build;
pub mod text;
pub mod util;
mod device_path_gen;
pub use device_path_gen::{
DevicePathNodeEnum, acpi, bios_boot_spec, end, hardware, media, messaging,
};
pub use uefi_raw::protocol::device_path::{DeviceSubType, DeviceType};
use crate::mem::PoolAllocation;
use crate::proto::{ProtocolPointer, unsafe_protocol};
use core::ffi::c_void;
use core::fmt::{self, Debug, Display, Formatter};
use core::ops::Deref;
use core::ptr;
use ptr_meta::Pointee;
use uefi_raw::protocol::device_path::DevicePathProtocol;
#[cfg(feature = "alloc")]
use {
crate::boot::{self, OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, SearchType},
crate::proto::device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly},
crate::proto::device_path::util::DevicePathUtilities,
crate::{CString16, Identify},
alloc::borrow::ToOwned,
alloc::boxed::Box,
core::mem,
};
opaque_type! {
pub struct FfiDevicePath;
}
#[derive(Debug)]
pub struct PoolDevicePath(pub(crate) PoolAllocation);
impl Deref for PoolDevicePath {
type Target = DevicePath;
fn deref(&self) -> &Self::Target {
unsafe { DevicePath::from_ffi_ptr(self.0.as_ptr().as_ptr().cast()) }
}
}
#[derive(Debug)]
pub struct PoolDevicePathNode(pub(crate) PoolAllocation);
impl Deref for PoolDevicePathNode {
type Target = DevicePathNode;
fn deref(&self) -> &Self::Target {
unsafe { DevicePathNode::from_ffi_ptr(self.0.as_ptr().as_ptr().cast()) }
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct DevicePathHeader(DevicePathProtocol);
impl DevicePathHeader {
#[must_use]
pub const fn new(major_type: DeviceType, sub_type: DeviceSubType, length: u16) -> Self {
Self(DevicePathProtocol {
major_type,
sub_type,
length: length.to_le_bytes(),
})
}
#[must_use]
pub const fn device_type(&self) -> DeviceType {
self.0.major_type
}
#[must_use]
pub const fn sub_type(&self) -> DeviceSubType {
self.0.sub_type
}
#[must_use]
pub const fn length(&self) -> u16 {
self.0.length()
}
}
impl<'a> TryFrom<&'a [u8]> for &'a DevicePathHeader {
type Error = ByteConversionError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if size_of::<DevicePathHeader>() <= bytes.len() {
unsafe { Ok(&*bytes.as_ptr().cast::<DevicePathHeader>()) }
} else {
Err(ByteConversionError::InvalidLength)
}
}
}
#[derive(Eq, Pointee)]
#[repr(C, packed)]
pub struct DevicePathNode {
header: DevicePathHeader,
data: [u8],
}
impl DevicePathNode {
#[must_use]
pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a Self {
let header = unsafe { *ptr.cast::<DevicePathHeader>() };
let data_len = usize::from(header.length()) - size_of::<DevicePathHeader>();
unsafe { &*ptr_meta::from_raw_parts(ptr.cast(), data_len) }
}
#[must_use]
pub const fn as_ffi_ptr(&self) -> *const FfiDevicePath {
let ptr: *const Self = self;
ptr.cast::<FfiDevicePath>()
}
#[must_use]
pub const fn device_type(&self) -> DeviceType {
self.header.device_type()
}
#[must_use]
pub const fn sub_type(&self) -> DeviceSubType {
self.header.sub_type()
}
#[must_use]
pub const fn full_type(&self) -> (DeviceType, DeviceSubType) {
(self.device_type(), self.sub_type())
}
#[must_use]
pub const fn length(&self) -> u16 {
self.header.length()
}
#[must_use]
pub fn is_end_entire(&self) -> bool {
self.full_type() == (DeviceType::END, DeviceSubType::END_ENTIRE)
}
#[must_use]
pub const fn data(&self) -> &[u8] {
&self.data
}
pub fn as_enum(&self) -> Result<DevicePathNodeEnum<'_>, NodeConversionError> {
DevicePathNodeEnum::try_from(self)
}
#[cfg(feature = "alloc")]
pub fn to_string16(
&self,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Result<CString16, DevicePathToTextError> {
let to_text_protocol = open_text_protocol()?;
to_text_protocol
.convert_device_node_to_text(self, display_only, allow_shortcuts)
.map(|pool_string| {
let cstr16 = &*pool_string;
CString16::from(cstr16)
})
.map_err(|_| DevicePathToTextError::OutOfMemory)
}
}
#[cfg(feature = "alloc")]
impl Display for DevicePathNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if boot::are_boot_services_active() {
let cstring16 = self
.to_string16(DisplayOnly(true), AllowShortcuts(true))
.unwrap();
write!(f, "{}", cstring16)
} else {
write!(f, "<device path node: {} bytes>", self.data.len())
}
}
}
impl Debug for DevicePathNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("DevicePathNode")
.field("header", &self.header)
.field("data", &&self.data)
.finish()
}
}
impl PartialEq for DevicePathNode {
fn eq(&self, other: &Self) -> bool {
self.header == other.header && self.data == other.data
}
}
impl<'a> TryFrom<&'a [u8]> for &'a DevicePathNode {
type Error = ByteConversionError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let dp = <&DevicePathHeader>::try_from(bytes)?;
if usize::from(dp.length()) <= bytes.len() {
unsafe { Ok(DevicePathNode::from_ffi_ptr(bytes.as_ptr().cast())) }
} else {
Err(ByteConversionError::InvalidLength)
}
}
}
#[repr(C, packed)]
#[derive(Eq, Pointee)]
pub struct DevicePathInstance {
data: [u8],
}
impl DevicePathInstance {
#[must_use]
pub const fn node_iter(&self) -> DevicePathNodeIterator<'_> {
DevicePathNodeIterator {
nodes: &self.data,
stop_condition: StopCondition::AnyEndNode,
}
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
&self.data
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_boxed(&self) -> Box<Self> {
let data = self.data.to_owned();
let data = data.into_boxed_slice();
unsafe { mem::transmute(data) }
}
}
impl Debug for DevicePathInstance {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("DevicePathInstance")
.field("data", &&self.data)
.finish()
}
}
impl PartialEq for DevicePathInstance {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
#[cfg(feature = "alloc")]
impl ToOwned for DevicePathInstance {
type Owned = Box<Self>;
fn to_owned(&self) -> Self::Owned {
self.to_boxed()
}
}
#[repr(C, packed)]
#[unsafe_protocol(DevicePathProtocol::GUID)]
#[derive(Eq, Pointee)]
pub struct DevicePath {
data: [u8],
}
impl ProtocolPointer for DevicePath {
unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self {
ptr_meta::from_raw_parts(ptr.cast(), unsafe { Self::size_in_bytes_from_ptr(ptr) })
}
unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self {
ptr_meta::from_raw_parts_mut(ptr.cast(), unsafe { Self::size_in_bytes_from_ptr(ptr) })
}
}
impl DevicePath {
unsafe fn size_in_bytes_from_ptr(ptr: *const c_void) -> usize {
let mut ptr = ptr.cast::<u8>();
let mut total_size_in_bytes: usize = 0;
loop {
let node = unsafe { DevicePathNode::from_ffi_ptr(ptr.cast::<FfiDevicePath>()) };
let node_size_in_bytes = usize::from(node.length());
total_size_in_bytes += node_size_in_bytes;
if node.is_end_entire() {
break;
}
ptr = unsafe { ptr.add(node_size_in_bytes) };
}
total_size_in_bytes
}
fn size_in_bytes_from_slice(mut bytes: &[u8]) -> Result<usize, ByteConversionError> {
let max_size_in_bytes = bytes.len();
let mut total_size_in_bytes: usize = 0;
loop {
let node = <&DevicePathNode>::try_from(bytes)?;
let node_size_in_bytes = usize::from(node.length());
total_size_in_bytes += node_size_in_bytes;
if total_size_in_bytes > max_size_in_bytes {
return Err(ByteConversionError::InvalidLength);
}
if node.is_end_entire() {
break;
}
bytes = &bytes[node_size_in_bytes..];
}
Ok(total_size_in_bytes)
}
#[must_use]
pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a Self {
unsafe { &*Self::ptr_from_ffi(ptr.cast::<c_void>()) }
}
#[must_use]
pub const fn as_ffi_ptr(&self) -> *const FfiDevicePath {
ptr::from_ref(self).cast()
}
#[must_use]
pub const fn instance_iter(&self) -> DevicePathInstanceIterator<'_> {
DevicePathInstanceIterator {
remaining_path: Some(self),
}
}
#[must_use]
pub const fn node_iter(&self) -> DevicePathNodeIterator<'_> {
DevicePathNodeIterator {
nodes: &self.data,
stop_condition: StopCondition::EndEntireNode,
}
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
&self.data
}
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_boxed(&self) -> Box<Self> {
let data = self.data.to_owned();
let data = data.into_boxed_slice();
unsafe { mem::transmute(data) }
}
#[cfg(feature = "alloc")]
pub fn to_pool(&self) -> Result<PoolDevicePath, DevicePathUtilitiesError> {
open_utility_protocol()?
.duplicate_path(self)
.map_err(|_| DevicePathUtilitiesError::OutOfMemory)
}
#[cfg(feature = "alloc")]
pub fn to_string16(
&self,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Result<CString16, DevicePathToTextError> {
let to_text_protocol = open_text_protocol()?;
to_text_protocol
.convert_device_path_to_text(self, display_only, allow_shortcuts)
.map(|pool_string| {
let cstr16 = &*pool_string;
CString16::from(cstr16)
})
.map_err(|_| DevicePathToTextError::OutOfMemory)
}
#[cfg(feature = "alloc")]
pub fn append_path(&self, right: &Self) -> Result<PoolDevicePath, DevicePathUtilitiesError> {
open_utility_protocol()?
.append_path(self, right)
.map_err(|_| DevicePathUtilitiesError::OutOfMemory)
}
#[cfg(feature = "alloc")]
pub fn append_node(
&self,
right: &DevicePathNode,
) -> Result<PoolDevicePath, DevicePathUtilitiesError> {
open_utility_protocol()?
.append_node(self, right)
.map_err(|_| DevicePathUtilitiesError::OutOfMemory)
}
}
#[cfg(feature = "alloc")]
impl Display for DevicePath {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if boot::are_boot_services_active() {
let cstring16 = self
.to_string16(DisplayOnly(true), AllowShortcuts(true))
.unwrap();
write!(f, "{}", cstring16)
} else {
write!(f, "<device path: {} bytes>", self.data.len())
}
}
}
impl Debug for DevicePath {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("DevicePath")
.field("data", &&self.data)
.finish()
}
}
impl PartialEq for DevicePath {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl<'a> TryFrom<&'a [u8]> for &'a DevicePath {
type Error = ByteConversionError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let len = DevicePath::size_in_bytes_from_slice(bytes)?;
unsafe { Ok(&*ptr_meta::from_raw_parts(bytes.as_ptr().cast(), len)) }
}
}
#[cfg(feature = "alloc")]
impl ToOwned for DevicePath {
type Owned = Box<Self>;
fn to_owned(&self) -> Self::Owned {
self.to_boxed()
}
}
#[derive(Debug)]
pub struct DevicePathInstanceIterator<'a> {
remaining_path: Option<&'a DevicePath>,
}
impl<'a> Iterator for DevicePathInstanceIterator<'a> {
type Item = &'a DevicePathInstance;
fn next(&mut self) -> Option<Self::Item> {
let remaining_path = self.remaining_path?;
let mut instance_size: usize = 0;
let node_iter = DevicePathNodeIterator {
nodes: &remaining_path.data,
stop_condition: StopCondition::NoMoreNodes,
};
for node in node_iter {
instance_size += usize::from(node.length());
if node.device_type() == DeviceType::END {
break;
}
}
let (head, rest) = remaining_path.data.split_at(instance_size);
if rest.is_empty() {
self.remaining_path = None;
} else {
self.remaining_path = unsafe {
Some(&*ptr_meta::from_raw_parts(
rest.as_ptr().cast::<()>(),
rest.len(),
))
};
}
unsafe {
Some(&*ptr_meta::from_raw_parts(
head.as_ptr().cast::<()>(),
head.len(),
))
}
}
}
#[derive(Debug)]
enum StopCondition {
AnyEndNode,
EndEntireNode,
NoMoreNodes,
}
#[derive(Debug)]
pub struct DevicePathNodeIterator<'a> {
nodes: &'a [u8],
stop_condition: StopCondition,
}
impl<'a> Iterator for DevicePathNodeIterator<'a> {
type Item = &'a DevicePathNode;
fn next(&mut self) -> Option<Self::Item> {
if self.nodes.is_empty() {
return None;
}
let node =
unsafe { DevicePathNode::from_ffi_ptr(self.nodes.as_ptr().cast::<FfiDevicePath>()) };
let stop = match self.stop_condition {
StopCondition::AnyEndNode => node.device_type() == DeviceType::END,
StopCondition::EndEntireNode => node.is_end_entire(),
StopCondition::NoMoreNodes => false,
};
if stop {
self.nodes = &[];
None
} else {
let node_size = usize::from(node.length());
self.nodes = &self.nodes[node_size..];
Some(node)
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ByteConversionError {
InvalidLength,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NodeConversionError {
DifferentType,
InvalidLength,
UnsupportedType,
}
#[repr(transparent)]
#[unsafe_protocol("bc62157e-3e33-4fec-9920-2d3b36d750df")]
#[derive(Debug, Pointee)]
pub struct LoadedImageDevicePath(DevicePath);
impl ProtocolPointer for LoadedImageDevicePath {
unsafe fn ptr_from_ffi(ptr: *const c_void) -> *const Self {
ptr_meta::from_raw_parts(ptr.cast(), unsafe {
DevicePath::size_in_bytes_from_ptr(ptr)
})
}
unsafe fn mut_ptr_from_ffi(ptr: *mut c_void) -> *mut Self {
ptr_meta::from_raw_parts_mut(ptr.cast(), unsafe {
DevicePath::size_in_bytes_from_ptr(ptr)
})
}
}
impl Deref for LoadedImageDevicePath {
type Target = DevicePath;
fn deref(&self) -> &DevicePath {
&self.0
}
}
#[derive(Debug)]
pub enum DevicePathToTextError {
CantLocateHandleBuffer(crate::Error),
NoHandle,
CantOpenProtocol(crate::Error),
OutOfMemory,
}
impl Display for DevicePathToTextError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl core::error::Error for DevicePathToTextError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::CantLocateHandleBuffer(e) => Some(e),
Self::CantOpenProtocol(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "alloc")]
fn open_text_protocol() -> Result<ScopedProtocol<DevicePathToText>, DevicePathToTextError> {
let &handle = boot::locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
.map_err(DevicePathToTextError::CantLocateHandleBuffer)?
.first()
.ok_or(DevicePathToTextError::NoHandle)?;
unsafe {
boot::open_protocol::<DevicePathToText>(
OpenProtocolParams {
handle,
agent: boot::image_handle(),
controller: None,
},
OpenProtocolAttributes::GetProtocol,
)
}
.map_err(DevicePathToTextError::CantOpenProtocol)
}
#[derive(Debug)]
pub enum DevicePathUtilitiesError {
CantLocateHandleBuffer(crate::Error),
NoHandle,
CantOpenProtocol(crate::Error),
OutOfMemory,
}
impl Display for DevicePathUtilitiesError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl core::error::Error for DevicePathUtilitiesError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::CantLocateHandleBuffer(e) => Some(e),
Self::CantOpenProtocol(e) => Some(e),
_ => None,
}
}
}
#[cfg(feature = "alloc")]
fn open_utility_protocol() -> Result<ScopedProtocol<DevicePathUtilities>, DevicePathUtilitiesError>
{
let &handle = boot::locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
.map_err(DevicePathUtilitiesError::CantLocateHandleBuffer)?
.first()
.ok_or(DevicePathUtilitiesError::NoHandle)?;
unsafe {
boot::open_protocol::<DevicePathUtilities>(
OpenProtocolParams {
handle,
agent: boot::image_handle(),
controller: None,
},
OpenProtocolAttributes::GetProtocol,
)
}
.map_err(DevicePathUtilitiesError::CantOpenProtocol)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
fn add_node(path: &mut Vec<u8>, device_type: u8, sub_type: u8, node_data: &[u8]) {
path.push(device_type);
path.push(sub_type);
path.extend(
u16::try_from(size_of::<DevicePathHeader>() + node_data.len())
.unwrap()
.to_le_bytes(),
);
path.extend(node_data);
}
fn create_raw_device_path() -> Vec<u8> {
let mut raw_data = Vec::new();
add_node(&mut raw_data, 0xa0, 0xb0, &[10, 11]);
add_node(&mut raw_data, 0xa1, 0xb1, &[20, 21, 22, 23]);
add_node(
&mut raw_data,
DeviceType::END.0,
DeviceSubType::END_INSTANCE.0,
&[],
);
add_node(&mut raw_data, 0xa2, 0xb2, &[30, 31]);
add_node(&mut raw_data, 0xa3, 0xb3, &[40, 41, 42, 43]);
add_node(
&mut raw_data,
DeviceType::END.0,
DeviceSubType::END_ENTIRE.0,
&[],
);
raw_data
}
fn check_node(node: &DevicePathNode, device_type: u8, sub_type: u8, node_data: &[u8]) {
assert_eq!(node.device_type().0, device_type);
assert_eq!(node.sub_type().0, sub_type);
assert_eq!(
node.length(),
u16::try_from(size_of::<DevicePathHeader>() + node_data.len()).unwrap()
);
assert_eq!(&node.data, node_data);
}
#[test]
fn test_device_path_nodes() {
let raw_data = create_raw_device_path();
let dp = unsafe { DevicePath::from_ffi_ptr(raw_data.as_ptr().cast()) };
assert_eq!(size_of_val(dp), 6 + 8 + 4 + 6 + 8 + 4);
let nodes: Vec<_> = dp.node_iter().collect();
check_node(nodes[0], 0xa0, 0xb0, &[10, 11]);
check_node(nodes[1], 0xa1, 0xb1, &[20, 21, 22, 23]);
check_node(
nodes[2],
DeviceType::END.0,
DeviceSubType::END_INSTANCE.0,
&[],
);
check_node(nodes[3], 0xa2, 0xb2, &[30, 31]);
check_node(nodes[4], 0xa3, 0xb3, &[40, 41, 42, 43]);
assert_eq!(nodes.len(), 5);
}
#[test]
fn test_device_path_instances() {
let raw_data = create_raw_device_path();
let dp = unsafe { DevicePath::from_ffi_ptr(raw_data.as_ptr().cast()) };
let mut iter = dp.instance_iter();
let mut instance = iter.next().unwrap();
assert_eq!(size_of_val(instance), 6 + 8 + 4);
let nodes: Vec<_> = instance.node_iter().collect();
check_node(nodes[0], 0xa0, 0xb0, &[10, 11]);
check_node(nodes[1], 0xa1, 0xb1, &[20, 21, 22, 23]);
assert_eq!(nodes.len(), 2);
instance = iter.next().unwrap();
assert_eq!(size_of_val(instance), 6 + 8 + 4);
let nodes: Vec<_> = instance.node_iter().collect();
check_node(nodes[0], 0xa2, 0xb2, &[30, 31]);
check_node(nodes[1], 0xa3, 0xb3, &[40, 41, 42, 43]);
assert_eq!(nodes.len(), 2);
assert!(iter.next().is_none());
}
#[test]
fn test_to_owned() {
assert_eq!(size_of::<&DevicePath>(), size_of::<&[u8]>());
let raw_data = create_raw_device_path();
let dp = unsafe { DevicePath::from_ffi_ptr(raw_data.as_ptr().cast()) };
assert_eq!(size_of_val(dp), size_of_val(&dp.data));
let owned_dp = dp.to_owned();
let owned_dp_ref = &*owned_dp;
assert_eq!(owned_dp_ref, dp)
}
#[test]
fn test_device_path_node_from_bytes() {
let mut raw_data = Vec::new();
let node = [0xa0, 0xb0];
let node_data = &[10, 11];
raw_data.push(node[0]);
assert!(<&DevicePathNode>::try_from(raw_data.as_slice()).is_err());
raw_data.push(node[1]);
raw_data.extend(
u16::try_from(size_of::<DevicePathHeader>() + node_data.len())
.unwrap()
.to_le_bytes(),
);
raw_data.extend(node_data);
let dp = <&DevicePathNode>::try_from(raw_data.as_slice()).unwrap();
assert_eq!(size_of_val(dp), 6);
check_node(dp, 0xa0, 0xb0, &[10, 11]);
raw_data[2] += 1;
assert!(<&DevicePathNode>::try_from(raw_data.as_slice()).is_err());
}
#[test]
fn test_device_path_nodes_from_bytes() {
let raw_data = create_raw_device_path();
let dp = <&DevicePath>::try_from(raw_data.as_slice()).unwrap();
assert_eq!(size_of_val(dp), 6 + 8 + 4 + 6 + 8 + 4);
let nodes: Vec<_> = dp.node_iter().collect();
check_node(nodes[0], 0xa0, 0xb0, &[10, 11]);
check_node(nodes[1], 0xa1, 0xb1, &[20, 21, 22, 23]);
check_node(
nodes[2],
DeviceType::END.0,
DeviceSubType::END_INSTANCE.0,
&[],
);
check_node(nodes[3], 0xa2, 0xb2, &[30, 31]);
check_node(nodes[4], 0xa3, 0xb3, &[40, 41, 42, 43]);
assert_eq!(nodes.len(), 5);
}
#[test]
fn test_specific_node_from_device_path_node() {
let mut raw_data = Vec::new();
add_node(
&mut raw_data,
DeviceType::END.0,
DeviceSubType::END_INSTANCE.0,
&[],
);
let node = <&DevicePathNode>::try_from(raw_data.as_slice()).unwrap();
assert!(<&end::Instance>::try_from(node).is_ok());
assert_eq!(
<&end::Entire>::try_from(node).unwrap_err(),
NodeConversionError::DifferentType
);
}
}