Skip to content

Commit b5abddc

Browse files
committed
device_path: add to_string() for more convenience
1 parent 38f2c3a commit b5abddc

File tree

3 files changed

+222
-52
lines changed

3 files changed

+222
-52
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added `Event::from_ptr`, `Event::as_ptr`, and `Handle::as_ptr`.
1010
- Added `ScopedProtocol::get` and `ScopedProtocol::get_mut` to access
1111
potentially-null interfaces without panicking.
12+
- `DevicePath::to_string` and `DevicePathNode::to_string`,
1213

1314
### Changed
1415
- Renamed `LoadImageSource::FromFilePath` to `LoadImageSource::FromDevicePath`
Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use alloc::string::ToString;
2+
use alloc::vec::Vec;
13
use uefi::prelude::*;
24
use uefi::proto::device_path::text::*;
35
use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath};
@@ -7,57 +9,94 @@ use uefi::table::boot::BootServices;
79
pub fn test(image: Handle, bt: &BootServices) {
810
info!("Running device path protocol test");
911

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-
);
12+
// test 1/2: test low-level API by directly opening all protocols
13+
{
14+
let loaded_image = bt
15+
.open_protocol_exclusive::<LoadedImage>(image)
16+
.expect("Failed to open LoadedImage protocol");
17+
18+
let device_path = bt
19+
.open_protocol_exclusive::<DevicePath>(loaded_image.device())
20+
.expect("Failed to open DevicePath protocol");
21+
22+
let device_path_to_text = bt
23+
.open_protocol_exclusive::<DevicePathToText>(
24+
bt.get_handle_for_protocol::<DevicePathToText>()
25+
.expect("Failed to get DevicePathToText handle"),
26+
)
27+
.expect("Failed to open DevicePathToText protocol");
28+
29+
let device_path_from_text = bt
30+
.open_protocol_exclusive::<DevicePathFromText>(
31+
bt.get_handle_for_protocol::<DevicePathFromText>()
32+
.expect("Failed to get DevicePathFromText handle"),
33+
)
34+
.expect("Failed to open DevicePathFromText protocol");
35+
36+
for path in device_path.node_iter() {
37+
info!(
38+
"path: type={:?}, subtype={:?}, length={}",
39+
path.device_type(),
40+
path.sub_type(),
41+
path.length(),
42+
);
3943

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}");
44+
let text = device_path_to_text
45+
.convert_device_node_to_text(bt, path, DisplayOnly(true), AllowShortcuts(false))
46+
.expect("Failed to convert device path to text");
47+
let text = &*text;
48+
info!("path name: {text}");
4549

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);
50+
let convert = device_path_from_text
51+
.convert_text_to_device_node(text)
52+
.expect("Failed to convert text to device path");
53+
assert_eq!(path, convert);
54+
}
55+
56+
// Get the `LoadedImageDevicePath`. Verify it start with the same nodes as
57+
// `device_path`.
58+
let loaded_image_device_path = bt
59+
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
60+
.expect("Failed to open LoadedImageDevicePath protocol");
61+
62+
for (n1, n2) in device_path
63+
.node_iter()
64+
.zip(loaded_image_device_path.node_iter())
65+
{
66+
assert_eq!(n1, n2);
67+
}
5068
}
5169

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())
70+
// test 2/2: test high-level to-string api
6071
{
61-
assert_eq!(n1, n2);
72+
let loaded_image_device_path = bt
73+
.open_protocol_exclusive::<LoadedImageDevicePath>(image)
74+
.expect("Failed to open LoadedImageDevicePath protocol");
75+
let device_path: &DevicePath = &loaded_image_device_path;
76+
77+
let path = device_path
78+
.node_iter()
79+
.map(|node| node.to_string(bt, DisplayOnly(false), AllowShortcuts(false)))
80+
.map(|str| str.unwrap().unwrap().to_string())
81+
.collect::<Vec<_>>();
82+
83+
assert_eq!(
84+
path.as_slice(),
85+
&[
86+
"PciRoot(0x0)",
87+
"Pci(0x1F,0x2)",
88+
"Sata(0x0,0xFFFF,0x0)",
89+
"HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)",
90+
"\\efi\\boot\\test_runner.efi"
91+
]
92+
);
93+
94+
// Test that to_string works for device_paths
95+
let path = device_path
96+
.to_string(bt, DisplayOnly(false), AllowShortcuts(false))
97+
.unwrap()
98+
.unwrap()
99+
.to_string();
100+
assert_eq!(path, "PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x0,0xFFFF,0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\\efi\\boot\\test_runner.efi");
62101
}
63102
}

uefi/src/proto/device_path/mod.rs

Lines changed: 135 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, 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::build`]
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,31 @@ impl DevicePath {
377424
let data = data.into_boxed_slice();
378425
unsafe { mem::transmute(data) }
379426
}
427+
428+
/// Transforms the device path to its string representation using the
429+
/// [`DevicePathToText`] protocol.
430+
///
431+
/// The resulting string is only None, if there was not enough memory.
432+
#[cfg(feature = "alloc")]
433+
pub fn to_string(
434+
&self,
435+
bs: &BootServices,
436+
display_only: DisplayOnly,
437+
allow_shortcuts: AllowShortcuts,
438+
) -> Result<Option<CString16>, DevicePathToTextError> {
439+
let to_text_protocol = open_text_protocol(bs)?;
440+
441+
let cstring16 = to_text_protocol
442+
.convert_device_path_to_text(bs, self, display_only, allow_shortcuts)
443+
.ok()
444+
.map(|pool_string| {
445+
let cstr16 = &*pool_string;
446+
// Another allocation; pool string is dropped. This overhead
447+
// is negligible. CString16 is more convenient to use.
448+
CString16::from(cstr16)
449+
});
450+
Ok(cstring16)
451+
}
380452
}
381453

382454
impl Debug for DevicePath {
@@ -700,6 +772,64 @@ impl Deref for LoadedImageDevicePath {
700772
}
701773
}
702774

775+
/// Errors that may happen when a device path is transformed to a string
776+
/// representation using:
777+
/// - [`DevicePath::to_node_string_pairs`]
778+
/// - [`DevicePath::to_string`]
779+
/// - [`DevicePathNode::to_string`]
780+
#[derive(Debug)]
781+
pub enum DevicePathToTextError {
782+
/// Can't locate a handle buffer with handles associated with the
783+
/// [`DevicePathToText`] protocol.
784+
CantLocateHandleBuffer(crate::Error),
785+
/// There is no handle supporting the [`DevicePathToText`] protocol.
786+
NoHandle,
787+
/// The handle supporting the [`DevicePathToText`] protocol exists but it
788+
/// could not be opened.
789+
CantOpenProtocol(crate::Error),
790+
}
791+
792+
impl Display for DevicePathToTextError {
793+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
794+
write!(f, "{self:?}")
795+
}
796+
}
797+
798+
#[cfg(feature = "unstable")]
799+
impl core::error::Error for DevicePathToTextError {
800+
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
801+
match self {
802+
DevicePathToTextError::CantLocateHandleBuffer(e) => Some(e),
803+
DevicePathToTextError::CantOpenProtocol(e) => Some(e),
804+
_ => None,
805+
}
806+
}
807+
}
808+
809+
/// Helper function to open the [`DevicePathToText`] protocol using the boot
810+
/// services.
811+
fn open_text_protocol(
812+
bs: &BootServices,
813+
) -> Result<ScopedProtocol<DevicePathToText>, DevicePathToTextError> {
814+
let &handle = bs
815+
.locate_handle_buffer(SearchType::ByProtocol(&DevicePathToText::GUID))
816+
.map_err(DevicePathToTextError::CantLocateHandleBuffer)?
817+
.first()
818+
.ok_or(DevicePathToTextError::NoHandle)?;
819+
820+
unsafe {
821+
bs.open_protocol::<DevicePathToText>(
822+
OpenProtocolParams {
823+
handle,
824+
agent: bs.image_handle(),
825+
controller: None,
826+
},
827+
OpenProtocolAttributes::GetProtocol,
828+
)
829+
}
830+
.map_err(DevicePathToTextError::CantOpenProtocol)
831+
}
832+
703833
#[cfg(test)]
704834
mod tests {
705835
use super::*;

0 commit comments

Comments
 (0)