Skip to content

Commit

Permalink
device_path: add to_string() for more convenience
Browse files Browse the repository at this point in the history
  • Loading branch information
phip1611 authored and nicholasbishop committed Jun 20, 2023
1 parent 38f2c3a commit ea968e1
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added `Event::from_ptr`, `Event::as_ptr`, and `Handle::as_ptr`.
- Added `ScopedProtocol::get` and `ScopedProtocol::get_mut` to access
potentially-null interfaces without panicking.
- `DevicePath::to_string` and `DevicePathNode::to_string`

### Changed
- Renamed `LoadImageSource::FromFilePath` to `LoadImageSource::FromDevicePath`
Expand Down
152 changes: 105 additions & 47 deletions uefi-test-runner/src/proto/device_path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use uefi::prelude::*;
use uefi::proto::device_path::text::*;
use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath};
Expand All @@ -7,57 +9,113 @@ use uefi::table::boot::BootServices;
pub fn test(image: Handle, bt: &BootServices) {
info!("Running device path protocol test");

let loaded_image = bt
.open_protocol_exclusive::<LoadedImage>(image)
.expect("Failed to open LoadedImage protocol");

let device_path = bt
.open_protocol_exclusive::<DevicePath>(loaded_image.device())
.expect("Failed to open DevicePath protocol");

let device_path_to_text = bt
.open_protocol_exclusive::<DevicePathToText>(
bt.get_handle_for_protocol::<DevicePathToText>()
.expect("Failed to get DevicePathToText handle"),
)
.expect("Failed to open DevicePathToText protocol");

let device_path_from_text = bt
.open_protocol_exclusive::<DevicePathFromText>(
bt.get_handle_for_protocol::<DevicePathFromText>()
.expect("Failed to get DevicePathFromText handle"),
)
.expect("Failed to open DevicePathFromText protocol");

for path in device_path.node_iter() {
info!(
"path: type={:?}, subtype={:?}, length={}",
path.device_type(),
path.sub_type(),
path.length(),
);
// test 1/2: test low-level API by directly opening all protocols
{
let loaded_image = bt
.open_protocol_exclusive::<LoadedImage>(image)
.expect("Failed to open LoadedImage protocol");

let device_path = bt
.open_protocol_exclusive::<DevicePath>(loaded_image.device())
.expect("Failed to open DevicePath protocol");

let device_path_to_text = bt
.open_protocol_exclusive::<DevicePathToText>(
bt.get_handle_for_protocol::<DevicePathToText>()
.expect("Failed to get DevicePathToText handle"),
)
.expect("Failed to open DevicePathToText protocol");

let device_path_from_text = bt
.open_protocol_exclusive::<DevicePathFromText>(
bt.get_handle_for_protocol::<DevicePathFromText>()
.expect("Failed to get DevicePathFromText handle"),
)
.expect("Failed to open DevicePathFromText protocol");

for path in device_path.node_iter() {
info!(
"path: type={:?}, subtype={:?}, length={}",
path.device_type(),
path.sub_type(),
path.length(),
);

let text = device_path_to_text
.convert_device_node_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
.expect("Failed to convert device path to text");
let text = &*text;
info!("path name: {text}");

let text = device_path_to_text
.convert_device_node_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
.expect("Failed to convert device path to text");
let text = &*text;
info!("path name: {text}");
let convert = device_path_from_text
.convert_text_to_device_node(text)
.expect("Failed to convert text to device path");
assert_eq!(path, convert);
}

let convert = device_path_from_text
.convert_text_to_device_node(text)
.expect("Failed to convert text to device path");
assert_eq!(path, convert);
// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
// `device_path`.
let loaded_image_device_path = bt
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
.expect("Failed to open LoadedImageDevicePath protocol");

for (n1, n2) in device_path
.node_iter()
.zip(loaded_image_device_path.node_iter())
{
assert_eq!(n1, n2);
}
}

// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
// `device_path`.
let loaded_image_device_path = bt
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
.expect("Failed to open LoadedImageDevicePath protocol");
for (n1, n2) in device_path
.node_iter()
.zip(loaded_image_device_path.node_iter())
// test 2/2: test high-level to-string api
{
assert_eq!(n1, n2);
let loaded_image_device_path = bt
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
.expect("Failed to open LoadedImageDevicePath protocol");
let device_path: &DevicePath = &loaded_image_device_path;

let path_components = device_path
.node_iter()
.map(|node| node.to_string(bt, DisplayOnly(false), AllowShortcuts(false)))
.map(|str| str.unwrap().unwrap().to_string())
.collect::<Vec<_>>();

let expected_device_path_str_components = &[
"PciRoot(0x0)",
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
"Pci(0x1F,0x2)",
#[cfg(target_arch = "aarch64")]
"Pci(0x4,0x0)",
// Sata device only used on x86.
// See xtask utility.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
"Sata(0x0,0xFFFF,0x0)",
"HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)",
"\\efi\\boot\\test_runner.efi",
];
let expected_device_path_str =
expected_device_path_str_components
.iter()
.fold(String::new(), |mut acc, next| {
if !acc.is_empty() {
acc.push('/');
}
acc.push_str(next);
acc
});

assert_eq!(
path_components.as_slice(),
expected_device_path_str_components
);

// Test that to_string works for device_paths
let path = device_path
.to_string(bt, DisplayOnly(false), AllowShortcuts(false))
.unwrap()
.unwrap()
.to_string();

assert_eq!(path, expected_device_path_str);
}
}
139 changes: 134 additions & 5 deletions uefi/src/proto/device_path/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,21 @@ mod device_path_gen;
pub use device_path_gen::{
acpi, bios_boot_spec, end, hardware, media, messaging, DevicePathNodeEnum,
};
#[cfg(feature = "alloc")]
use {alloc::borrow::ToOwned, alloc::boxed::Box};

use crate::proto::{unsafe_protocol, ProtocolPointer};
use core::ffi::c_void;
use core::fmt::{self, Debug, Formatter};
use core::fmt::{self, Debug, Display, Formatter};
use core::mem;
use core::ops::Deref;
use ptr_meta::Pointee;
use uefi::table::boot::ScopedProtocol;
#[cfg(feature = "alloc")]
use {alloc::borrow::ToOwned, alloc::boxed::Box, uefi::CString16};

use crate::prelude::BootServices;
use crate::proto::device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly};
use crate::proto::{unsafe_protocol, ProtocolPointer};
use crate::table::boot::{OpenProtocolAttributes, OpenProtocolParams, SearchType};
use crate::Identify;

opaque_type! {
/// Opaque type that should be used to represent a pointer to a
Expand All @@ -113,7 +119,23 @@ pub struct DevicePathHeader {
/// A single node within a [`DevicePath`].
///
/// Each node starts with a [`DevicePathHeader`]. The rest of the data
/// in the node depends on the type of node.
/// in the node depends on the type of node. You can "cast" a node to a specific
/// one like this:
/// ```no_run
/// use uefi::proto::device_path::DevicePath;
/// use uefi::proto::device_path::media::FilePath;
///
/// let image_device_path: &DevicePath = unsafe { DevicePath::from_ffi_ptr(0x1337 as *const _) };
/// let file_path = image_device_path
/// .node_iter()
/// .find_map(|node| {
/// let node: &FilePath = node.try_into().ok()?;
/// let path = node.path_name().to_cstring16().ok()?;
/// Some(path.to_string().to_uppercase())
/// });
/// ```
/// More types are available in [`uefi::proto::device_path`]. Builder types
/// can be found in [`uefi::proto::device_path::build`]
///
/// See the [module-level documentation] for more details.
///
Expand Down Expand Up @@ -189,6 +211,31 @@ impl DevicePathNode {
pub fn as_enum(&self) -> Result<DevicePathNodeEnum, NodeConversionError> {
DevicePathNodeEnum::try_from(self)
}

/// Transforms the device path node to its string representation using the
/// [`DevicePathToText`] protocol.
///
/// The resulting string is only None, if there was not enough memory.
#[cfg(feature = "alloc")]
pub fn to_string(
&self,
bs: &BootServices,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Result<Option<CString16>, DevicePathToTextError> {
let to_text_protocol = open_text_protocol(bs)?;

let cstring16 = to_text_protocol
.convert_device_node_to_text(bs, self, display_only, allow_shortcuts)
.ok()
.map(|pool_string| {
let cstr16 = &*pool_string;
// Another allocation; pool string is dropped. This overhead
// is negligible. CString16 is more convenient to use.
CString16::from(cstr16)
});
Ok(cstring16)
}
}

impl Debug for DevicePathNode {
Expand Down Expand Up @@ -377,6 +424,31 @@ impl DevicePath {
let data = data.into_boxed_slice();
unsafe { mem::transmute(data) }
}

/// Transforms the device path to its string representation using the
/// [`DevicePathToText`] protocol.
///
/// The resulting string is only None, if there was not enough memory.
#[cfg(feature = "alloc")]
pub fn to_string(
&self,
bs: &BootServices,
display_only: DisplayOnly,
allow_shortcuts: AllowShortcuts,
) -> Result<Option<CString16>, DevicePathToTextError> {
let to_text_protocol = open_text_protocol(bs)?;

let cstring16 = to_text_protocol
.convert_device_path_to_text(bs, self, display_only, allow_shortcuts)
.ok()
.map(|pool_string| {
let cstr16 = &*pool_string;
// Another allocation; pool string is dropped. This overhead
// is negligible. CString16 is more convenient to use.
CString16::from(cstr16)
});
Ok(cstring16)
}
}

impl Debug for DevicePath {
Expand Down Expand Up @@ -700,6 +772,63 @@ impl Deref for LoadedImageDevicePath {
}
}

/// Errors that may happen when a device path is transformed to a string
/// representation using:
/// - [`DevicePath::to_string`]
/// - [`DevicePathNode::to_string`]
#[derive(Debug)]
pub enum DevicePathToTextError {
/// Can't locate a handle buffer with handles associated with the
/// [`DevicePathToText`] protocol.
CantLocateHandleBuffer(crate::Error),
/// There is no handle supporting the [`DevicePathToText`] protocol.
NoHandle,
/// The handle supporting the [`DevicePathToText`] protocol exists but it
/// could not be opened.
CantOpenProtocol(crate::Error),
}

impl Display for DevicePathToTextError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}

#[cfg(feature = "unstable")]
impl core::error::Error for DevicePathToTextError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
DevicePathToTextError::CantLocateHandleBuffer(e) => Some(e),
DevicePathToTextError::CantOpenProtocol(e) => Some(e),
_ => None,
}
}
}

/// Helper function to open the [`DevicePathToText`] protocol using the boot
/// services.
fn open_text_protocol(
bs: &BootServices,
) -> Result<ScopedProtocol<DevicePathToText>, DevicePathToTextError> {
let &handle = bs
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
.map_err(DevicePathToTextError::CantLocateHandleBuffer)?
.first()
.ok_or(DevicePathToTextError::NoHandle)?;

unsafe {
bs.open_protocol::<DevicePathToText>(
OpenProtocolParams {
handle,
agent: bs.image_handle(),
controller: None,
},
OpenProtocolAttributes::GetProtocol,
)
}
.map_err(DevicePathToTextError::CantOpenProtocol)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions xtask/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
None
};

// Print the actual used QEMU command for running the test.
println!("{}", command_to_string(&cmd));

cmd.stdin(Stdio::piped());
Expand Down

0 comments on commit ea968e1

Please sign in to comment.