diff --git a/CHANGELOG.md b/CHANGELOG.md index dd04cbd5a..39d60c95d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/uefi-test-runner/src/proto/device_path.rs b/uefi-test-runner/src/proto/device_path.rs index a8f5f7acb..84a8157a8 100644 --- a/uefi-test-runner/src/proto/device_path.rs +++ b/uefi-test-runner/src/proto/device_path.rs @@ -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}; @@ -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::(image) - .expect("Failed to open LoadedImage protocol"); - - let device_path = bt - .open_protocol_exclusive::(loaded_image.device()) - .expect("Failed to open DevicePath protocol"); - - let device_path_to_text = bt - .open_protocol_exclusive::( - bt.get_handle_for_protocol::() - .expect("Failed to get DevicePathToText handle"), - ) - .expect("Failed to open DevicePathToText protocol"); - - let device_path_from_text = bt - .open_protocol_exclusive::( - bt.get_handle_for_protocol::() - .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::(image) + .expect("Failed to open LoadedImage protocol"); + + let device_path = bt + .open_protocol_exclusive::(loaded_image.device()) + .expect("Failed to open DevicePath protocol"); + + let device_path_to_text = bt + .open_protocol_exclusive::( + bt.get_handle_for_protocol::() + .expect("Failed to get DevicePathToText handle"), + ) + .expect("Failed to open DevicePathToText protocol"); + + let device_path_from_text = bt + .open_protocol_exclusive::( + bt.get_handle_for_protocol::() + .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::(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::(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::(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::>(); + + 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); } } diff --git a/uefi/src/proto/device_path/mod.rs b/uefi/src/proto/device_path/mod.rs index 9ec1097fa..0bb0da9e8 100644 --- a/uefi/src/proto/device_path/mod.rs +++ b/uefi/src/proto/device_path/mod.rs @@ -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 @@ -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. /// @@ -189,6 +211,31 @@ impl DevicePathNode { pub fn as_enum(&self) -> Result { 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, 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 { @@ -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, 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 { @@ -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, DevicePathToTextError> { + let &handle = bs + .locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID)) + .map_err(DevicePathToTextError::CantLocateHandleBuffer)? + .first() + .ok_or(DevicePathToTextError::NoHandle)?; + + unsafe { + bs.open_protocol::( + OpenProtocolParams { + handle, + agent: bs.image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + } + .map_err(DevicePathToTextError::CantOpenProtocol) +} + #[cfg(test)] mod tests { use super::*; diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index 6ad04f97a..dbdaf699e 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -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());