Skip to content

Commit 60e4979

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

File tree

4 files changed

+241
-52
lines changed

4 files changed

+241
-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: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use alloc::string::{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,113 @@ 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+
);
43+
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}");
3949

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}");
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+
}
4555

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);
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_components = 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+
let expected_device_path_str_components = &[
84+
"PciRoot(0x0)",
85+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
86+
"Pci(0x1F,0x2)",
87+
#[cfg(target_arch = "aarch64")]
88+
"Pci(0x4,0x0)",
89+
// Sata device only used on x86.
90+
// See xtask utility.
91+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
92+
"Sata(0x0,0xFFFF,0x0)",
93+
"HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)",
94+
"\\efi\\boot\\test_runner.efi",
95+
];
96+
let expected_device_path_str =
97+
expected_device_path_str_components
98+
.iter()
99+
.fold(String::new(), |mut acc, next| {
100+
if !acc.is_empty() {
101+
acc.push('/');
102+
}
103+
acc.push_str(next);
104+
acc
105+
});
106+
107+
assert_eq!(
108+
path_components.as_slice(),
109+
expected_device_path_str_components
110+
);
111+
112+
// Test that to_string works for device_paths
113+
let path = device_path
114+
.to_string(bt, DisplayOnly(false), AllowShortcuts(false))
115+
.unwrap()
116+
.unwrap()
117+
.to_string();
118+
119+
assert_eq!(path, expected_device_path_str);
62120
}
63121
}

uefi/src/proto/device_path/mod.rs

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

xtask/src/qemu.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
598598
None
599599
};
600600

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

603604
cmd.stdin(Stdio::piped());

0 commit comments

Comments
 (0)