Skip to content

Commit 59494c5

Browse files
committed
device_path: add to_string() and to_node_string_pairs()
1 parent a0085d0 commit 59494c5

File tree

3 files changed

+228
-54
lines changed

3 files changed

+228
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- `DevicePath::to_boxed`, `DevicePath::to_owned`, and `DevicePath::as_bytes`
77
- `DevicePathInstance::to_boxed`, `DevicePathInstance::to_owned`, and `DevicePathInstance::as_bytes`
88
- `DevicePathNode::data`
9+
- `DevicePath::to_string`, `DevicePath::to_node_string_pairs`, and `DevicePathNode::to_string`,
910

1011
### Changed
1112
- Renamed `LoadImageSource::FromFilePath` to `LoadImageSource::FromDevicePath`

uefi-test-runner/src/proto/device_path.rs

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,80 @@ use uefi::table::boot::BootServices;
77
pub fn test(image: Handle, bt: &BootServices) {
88
info!("Running device path protocol test");
99

10-
let loaded_image = bt
11-
.open_protocol_exclusive::<LoadedImage>(image)
12-
.expect("Failed to open LoadedImage protocol");
13-
14-
let device_path = bt
15-
.open_protocol_exclusive::<DevicePath>(loaded_image.device())
16-
.expect("Failed to open DevicePath protocol");
17-
18-
let device_path_to_text = bt
19-
.open_protocol_exclusive::<DevicePathToText>(
20-
bt.get_handle_for_protocol::<DevicePathToText>()
21-
.expect("Failed to get DevicePathToText handle"),
22-
)
23-
.expect("Failed to open DevicePathToText protocol");
24-
25-
let device_path_from_text = bt
26-
.open_protocol_exclusive::<DevicePathFromText>(
27-
bt.get_handle_for_protocol::<DevicePathFromText>()
28-
.expect("Failed to get DevicePathFromText handle"),
29-
)
30-
.expect("Failed to open DevicePathFromText protocol");
31-
32-
for path in device_path.node_iter() {
33-
info!(
34-
"path: type={:?}, subtype={:?}, length={}",
35-
path.device_type(),
36-
path.sub_type(),
37-
path.length(),
38-
);
39-
40-
let text = device_path_to_text
41-
.convert_device_node_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
42-
.expect("Failed to convert device path to text");
43-
let text = &*text;
44-
info!("path name: {text}");
45-
46-
let convert = device_path_from_text
47-
.convert_text_to_device_node(text)
48-
.expect("Failed to convert text to device path");
49-
assert_eq!(path, convert);
10+
// test 1/2: test low-level API by directly opening all protocols
11+
{
12+
let loaded_image = bt
13+
.open_protocol_exclusive::<LoadedImage>(image)
14+
.expect("Failed to open LoadedImage protocol");
15+
16+
let device_path = bt
17+
.open_protocol_exclusive::<DevicePath>(loaded_image.device())
18+
.expect("Failed to open DevicePath protocol");
19+
20+
let device_path_to_text = bt
21+
.open_protocol_exclusive::<DevicePathToText>(
22+
bt.get_handle_for_protocol::<DevicePathToText>()
23+
.expect("Failed to get DevicePathToText handle"),
24+
)
25+
.expect("Failed to open DevicePathToText protocol");
26+
27+
let device_path_from_text = bt
28+
.open_protocol_exclusive::<DevicePathFromText>(
29+
bt.get_handle_for_protocol::<DevicePathFromText>()
30+
.expect("Failed to get DevicePathFromText handle"),
31+
)
32+
.expect("Failed to open DevicePathFromText protocol");
33+
34+
for path in device_path.node_iter() {
35+
info!(
36+
"path: type={:?}, subtype={:?}, length={}",
37+
path.device_type(),
38+
path.sub_type(),
39+
path.length(),
40+
);
41+
42+
let text = device_path_to_text
43+
.convert_device_node_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
44+
.expect("Failed to convert device path to text");
45+
let text = &*text;
46+
info!("path name: {text}");
47+
48+
let convert = device_path_from_text
49+
.convert_text_to_device_node(text)
50+
.expect("Failed to convert text to device path");
51+
assert_eq!(path, convert);
52+
}
53+
54+
// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
55+
// `device_path`.
56+
let loaded_image_device_path = bt
57+
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
58+
.expect("Failed to open LoadedImageDevicePath protocol");
59+
60+
for (n1, n2) in device_path
61+
.node_iter()
62+
.zip(loaded_image_device_path.node_iter())
63+
{
64+
assert_eq!(n1, n2);
65+
}
5066
}
5167

52-
// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
53-
// `device_path`.
54-
let loaded_image_device_path = bt
55-
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
56-
.expect("Failed to open LoadedImageDevicePath protocol");
57-
for (n1, n2) in device_path
58-
.node_iter()
59-
.zip(loaded_image_device_path.node_iter())
68+
// test 2/2: test high-level to-string api
6069
{
61-
assert_eq!(n1, n2);
70+
let loaded_image_device_path = bt
71+
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
72+
.expect("Failed to open LoadedImageDevicePath protocol");
73+
let device_path: &DevicePath = &loaded_image_device_path;
74+
75+
// Now also test the higher level interface to the DevicePathToText protocol.
76+
// This invokes to_string() on each single node.
77+
let vec = device_path
78+
.to_node_string_pairs(bt, DisplayOnly(false), AllowShortcuts(false))
79+
.unwrap();
80+
assert_eq!(vec.len(), device_path.node_iter().count());
81+
82+
device_path
83+
.to_string(bt, DisplayOnly(false), AllowShortcuts(false))
84+
.unwrap();
6285
}
6386
}

uefi/src/proto/device_path/mod.rs

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,21 @@ mod device_path_gen;
8080
pub use device_path_gen::{
8181
acpi, bios_boot_spec, end, hardware, media, messaging, DevicePathNodeEnum,
8282
};
83-
#[cfg(feature = "alloc")]
84-
use {alloc::borrow::ToOwned, alloc::boxed::Box};
8583

86-
use crate::proto::{unsafe_protocol, ProtocolPointer};
8784
use core::ffi::c_void;
88-
use core::fmt::{self, Debug, Formatter};
85+
use core::fmt::{self, Debug, Display, Formatter};
8986
use core::mem;
9087
use core::ops::Deref;
9188
use ptr_meta::Pointee;
89+
use uefi::table::boot::ScopedProtocol;
90+
#[cfg(feature = "alloc")]
91+
use {alloc::borrow::ToOwned, alloc::boxed::Box, alloc::vec::Vec, uefi::CString16};
92+
93+
use crate::prelude::BootServices;
94+
use crate::proto::device_path::text::{AllowShortcuts, DevicePathToText, DisplayOnly};
95+
use crate::proto::{unsafe_protocol, ProtocolPointer};
96+
use crate::table::boot::{OpenProtocolAttributes, OpenProtocolParams, SearchType};
97+
use crate::Identify;
9298

9399
opaque_type! {
94100
/// Opaque type that should be used to represent a pointer to a
@@ -113,7 +119,23 @@ pub struct DevicePathHeader {
113119
/// A single node within a [`DevicePath`].
114120
///
115121
/// Each node starts with a [`DevicePathHeader`]. The rest of the data
116-
/// in the node depends on the type of node.
122+
/// in the node depends on the type of node. You can "cast" a node to a specific
123+
/// one like this:
124+
/// ```no_run
125+
/// use uefi::proto::device_path::DevicePath;
126+
/// use uefi::proto::device_path::media::FilePath;
127+
///
128+
/// let image_device_path: &DevicePath = unsafe { DevicePath::from_ffi_ptr(0x1337 as *const _) };
129+
/// let file_path = image_device_path
130+
/// .node_iter()
131+
/// .find_map(|node| {
132+
/// let node: &FilePath = node.try_into().ok()?;
133+
/// let path = node.path_name().to_cstring16().ok()?;
134+
/// Some(path.to_string().to_uppercase())
135+
/// });
136+
/// ```
137+
/// More types are available in [`uefi::proto::device_path`]. Builder types
138+
/// can be found in [`uefi::proto::device_path::builder`]
117139
///
118140
/// See the [module-level documentation] for more details.
119141
///
@@ -189,6 +211,31 @@ impl DevicePathNode {
189211
pub fn as_enum(&self) -> Result<DevicePathNodeEnum, NodeConversionError> {
190212
DevicePathNodeEnum::try_from(self)
191213
}
214+
215+
/// Transforms the device path node to its string representation using the
216+
/// [`DevicePathToText`] protocol.
217+
///
218+
/// The resulting string is only None, if there was not enough memory.
219+
#[cfg(feature = "alloc")]
220+
pub fn to_string(
221+
&self,
222+
bs: &BootServices,
223+
display_only: DisplayOnly,
224+
allow_shortcuts: AllowShortcuts,
225+
) -> Result<Option<CString16>, DevicePathToTextError> {
226+
let to_text_protocol = open_text_protocol(bs)?;
227+
228+
let cstring16 = to_text_protocol
229+
.convert_device_node_to_text(bs, self, display_only, allow_shortcuts)
230+
.ok()
231+
.map(|pool_string| {
232+
let cstr16 = &*pool_string;
233+
// Another allocation; pool string is dropped. This overhead
234+
// is negligible. CString16 is more convenient to use.
235+
CString16::from(cstr16)
236+
});
237+
Ok(cstring16)
238+
}
192239
}
193240

194241
impl Debug for DevicePathNode {
@@ -377,6 +424,51 @@ impl DevicePath {
377424
let data = data.into_boxed_slice();
378425
unsafe { mem::transmute(data) }
379426
}
427+
428+
/// Variant of [`Self::to_string`] that creates a list of tuples of each
429+
/// [`DevicePathNode`] and the corresponding text representation using the
430+
/// [`DevicePathToText`] protocol.
431+
///
432+
/// The resulting string is only None, if there was not enough memory.
433+
#[cfg(feature = "alloc")]
434+
pub fn to_node_string_pairs(
435+
&self,
436+
bs: &BootServices,
437+
display_only: DisplayOnly,
438+
allow_shortcuts: AllowShortcuts,
439+
) -> Result<Vec<(&DevicePathNode, Option<CString16>)>, DevicePathToTextError> {
440+
let mut vec = Vec::new();
441+
for node in self.node_iter() {
442+
let string = node.to_string(bs, display_only, allow_shortcuts)?;
443+
vec.push((node, string))
444+
}
445+
Ok(vec)
446+
}
447+
448+
/// Transforms the device path to its string representation using the
449+
/// [`DevicePathToText`] protocol.
450+
///
451+
/// The resulting string is only None, if there was not enough memory.
452+
#[cfg(feature = "alloc")]
453+
pub fn to_string(
454+
&self,
455+
bs: &BootServices,
456+
display_only: DisplayOnly,
457+
allow_shortcuts: AllowShortcuts,
458+
) -> Result<Option<CString16>, DevicePathToTextError> {
459+
let to_text_protocol = open_text_protocol(bs)?;
460+
461+
let cstring16 = to_text_protocol
462+
.convert_device_path_to_text(bs, self, display_only, allow_shortcuts)
463+
.ok()
464+
.map(|pool_string| {
465+
let cstr16 = &*pool_string;
466+
// Another allocation; pool string is dropped. This overhead
467+
// is negligible. CString16 is more convenient to use.
468+
CString16::from(cstr16)
469+
});
470+
Ok(cstring16)
471+
}
380472
}
381473

382474
impl Debug for DevicePath {
@@ -700,6 +792,64 @@ impl Deref for LoadedImageDevicePath {
700792
}
701793
}
702794

795+
/// Errors that may happen when a device path is transformed to a string
796+
/// representation using:
797+
/// - [`DevicePath::to_node_string_pairs`]
798+
/// - [`DevicePath::to_string`]
799+
/// - [`DevicePathNode::to_string`]
800+
#[derive(Debug)]
801+
pub enum DevicePathToTextError {
802+
/// Can't locate a handle buffer with handles associated with the
803+
/// [`DevicePathToText`] protocol.
804+
CantLocateHandleBuffer(crate::Error),
805+
/// There is no handle supporting the [`DevicePathToText`] protocol.
806+
NoHandle,
807+
/// The handle supporting the [`DevicePathToText`] protocol exists but it
808+
/// could not be opened.
809+
CantOpenProtocol(crate::Error),
810+
}
811+
812+
impl Display for DevicePathToTextError {
813+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
814+
write!(f, "{self:?}")
815+
}
816+
}
817+
818+
#[cfg(feature = "unstable")]
819+
impl core::error::Error for DevicePathToTextError {
820+
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
821+
match self {
822+
DevicePathToTextError::CantLocateHandleBuffer(e) => Some(e),
823+
DevicePathToTextError::CantOpenProtocol(e) => Some(e),
824+
_ => None,
825+
}
826+
}
827+
}
828+
829+
/// Helper function to open the [`DevicePathToText`] protocol using the boot
830+
/// services.
831+
fn open_text_protocol(
832+
bs: &BootServices,
833+
) -> Result<ScopedProtocol<DevicePathToText>, DevicePathToTextError> {
834+
let &handle = bs
835+
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
836+
.map_err(DevicePathToTextError::CantLocateHandleBuffer)?
837+
.first()
838+
.ok_or(DevicePathToTextError::NoHandle)?;
839+
840+
unsafe {
841+
bs.open_protocol::<DevicePathToText>(
842+
OpenProtocolParams {
843+
handle,
844+
agent: bs.image_handle(),
845+
controller: None,
846+
},
847+
OpenProtocolAttributes::GetProtocol,
848+
)
849+
}
850+
.map_err(DevicePathToTextError::CantOpenProtocol)
851+
}
852+
703853
#[cfg(test)]
704854
mod tests {
705855
use super::*;

0 commit comments

Comments
 (0)