fix(path): tell device and file path nodes apart by their type

Device path nodes were treated as device nodes when their text
contained "(", and as file path nodes otherwise. A file name with
parentheses, like \EFI\Linux\vmlinuz(1).efi, was then treated as a
device node, so it couldn't be resolved or loaded. Check whether each
node is a media file path node instead.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 20:52:15 -07:00
1 parent 8447861985
commit 079bf0c3e8
1 file changed
+23 -39
+23 -39
View File
@@ -6,7 +6,9 @@ use anyhow::{Context, Result};
use core::ops::Deref; use core::ops::Deref;
use uefi::fs::{FileSystem, Path}; use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::device_path::{
DevicePath, DevicePathNode, DeviceSubType, DeviceType, PoolDevicePath,
};
use uefi::proto::media::fs::SimpleFileSystem; use uefi::proto::media::fs::SimpleFileSystem;
use uefi::{CString16, Handle}; use uefi::{CString16, Handle};
@@ -40,10 +42,17 @@ impl ResolvedPath {
} }
} }
/// Checks if a [CString16] contains a char `c`. /// Checks if the device path `node` is a file path node, rather than a device node.
/// We need to call to_string() because CString16 doesn't support `contains` with a char. fn is_file_path_node(node: &DevicePathNode) -> bool {
fn cstring16_contains_char(string: &CString16, c: char) -> bool { node.device_type() == DeviceType::MEDIA && node.sub_type() == DeviceSubType::MEDIA_FILE_PATH
string.to_string().contains(c) }
/// Converts the device path `node` to its text form.
fn node_to_string(node: &DevicePathNode) -> Result<String> {
Ok(node
.to_string16(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert device path node to string")?
.to_string())
} }
/// Parses the input `path` as a [DevicePath]. /// Parses the input `path` as a [DevicePath].
@@ -67,20 +76,9 @@ pub fn text_to_device_path(path: impl AsRef<str>) -> Result<PoolDevicePath> {
pub fn device_path_root(path: &DevicePath) -> Result<String> { pub fn device_path_root(path: &DevicePath) -> Result<String> {
let mut path = path let mut path = path
.node_iter() .node_iter()
.filter_map(|item| { .filter(|node| !is_file_path_node(node))
let item = item.to_string16(DisplayOnly(false), AllowShortcuts(false)); .map(node_to_string)
if item .collect::<Result<Vec<_>>>()?
.as_ref()
.map(|item| cstring16_contains_char(item, '('))
.unwrap_or(false)
{
Some(item.unwrap_or_default())
} else {
None
}
})
.map(|item| item.to_string())
.collect::<Vec<_>>()
.join("/"); .join("/");
path.push('/'); path.push('/');
Ok(path) Ok(path)
@@ -92,20 +90,9 @@ pub fn device_path_root(path: &DevicePath) -> Result<String> {
pub fn device_path_subpath(path: &DevicePath) -> Result<String> { pub fn device_path_subpath(path: &DevicePath) -> Result<String> {
let path = path let path = path
.node_iter() .node_iter()
.filter_map(|item| { .filter(|node| is_file_path_node(node))
let item = item.to_string16(DisplayOnly(false), AllowShortcuts(false)); .map(node_to_string)
if item .collect::<Result<Vec<_>>>()?
.as_ref()
.map(|item| cstring16_contains_char(item, '('))
.unwrap_or(false)
{
None
} else {
Some(item.unwrap_or_default())
}
})
.map(|item| item.to_string())
.collect::<Vec<_>>()
.join("\\"); .join("\\");
Ok(path) Ok(path)
} }
@@ -120,15 +107,12 @@ pub fn resolve_path(
let mut input = input.to_string(); let mut input = input.to_string();
let mut path = text_to_device_path(&input).context("unable to convert text to path")?; let mut path = text_to_device_path(&input).context("unable to convert text to path")?;
// A path that starts with a device node has a device, while a path that starts
// with a file path node is relative to the default root path.
let path_has_device = path let path_has_device = path
.node_iter() .node_iter()
.next() .next()
.map(|it| { .is_some_and(|node| !is_file_path_node(node));
it.to_string16(DisplayOnly(false), AllowShortcuts(false))
.unwrap_or_default()
})
.map(|it| it.to_string().contains('('))
.unwrap_or(false);
if !path_has_device { if !path_has_device {
if !input.starts_with('\\') { if !input.starts_with('\\') {
input.insert(0, '\\'); input.insert(0, '\\');