From aed990318093fb9312d2b85d798abfe8c5fcd3ab Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 26 Sep 2026 20:18:23 -0700 Subject: [PATCH] fix(extractors): skip filesystems that can't be checked for a match filesystem-device-match failed as soon as any filesystem couldn't be checked, such as a removable device without media or a filesystem that couldn't be opened exclusively. The fallback value was never used and boot was aborted. Log a warning and check the next filesystem instead. The filesystem protocol is now only opened when the label or item criteria need it, and the partition UUIDs are parsed once up front. --- .../src/extractors/filesystem_device_match.rs | 219 ++++++++++-------- 1 file changed, 125 insertions(+), 94 deletions(-) diff --git a/crates/boot/src/extractors/filesystem_device_match.rs b/crates/boot/src/extractors/filesystem_device_match.rs index 87909c4..7dd6877 100644 --- a/crates/boot/src/extractors/filesystem_device_match.rs +++ b/crates/boot/src/extractors/filesystem_device_match.rs @@ -6,11 +6,106 @@ use core::ops::Deref; use core::str::FromStr; use edera_sprout_config::extractors::filesystem_device_match::FilesystemDeviceMatchExtractor; use eficore::partition::PartitionGuidForm; +use log::warn; use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::DevicePath; use uefi::proto::media::file::{File, FileSystemVolumeLabel}; use uefi::proto::media::fs::SimpleFileSystem; -use uefi::{CString16, Guid}; +use uefi::{CString16, Guid, Handle}; + +/// Check whether the filesystem at `handle` matches all the criteria of the `extractor` +/// configuration inside the `context`. The partition UUIDs are parsed ahead of time into +/// `partition_uuid` and `partition_type_uuid`. +fn is_match( + context: &Rc, + extractor: &FilesystemDeviceMatchExtractor, + handle: Handle, + partition_uuid: Option, + partition_type_uuid: Option, +) -> Result { + // Check if the partition info matches partition uuid criteria. + if let Some(parsed_uuid) = partition_uuid { + // Fetch the root of the device. + let root = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to fetch the device path of the filesystem")? + .deref() + .to_boxed(); + + // Fetch the partition uuid for this filesystem. + let partition_uuid = + eficore::partition::partition_guid(&root, PartitionGuidForm::Partition) + .context("unable to fetch the partition uuid of the filesystem")?; + + // Compare the partition uuid to the parsed uuid. + if partition_uuid != Some(parsed_uuid) { + return Ok(false); + } + } + + // Check if the partition info matches partition type uuid criteria. + if let Some(parsed_uuid) = partition_type_uuid { + // Fetch the root of the device. + let root = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to fetch the device path of the filesystem")? + .deref() + .to_boxed(); + + // Fetch the partition type uuid for this filesystem. + let partition_type_uuid = + eficore::partition::partition_guid(&root, PartitionGuidForm::PartitionType) + .context("unable to fetch the partition uuid of the filesystem")?; + + // Compare the partition type uuid to the parsed uuid. + if partition_type_uuid != Some(parsed_uuid) { + return Ok(false); + } + } + + // The remaining criteria need the filesystem itself. + if extractor.has_label.is_none() && extractor.has_item.is_none() { + return Ok(true); + } + + // Open the filesystem protocol for this handle. + let mut filesystem = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to open filesystem protocol")?; + + // Check if the filesystem matches label criteria. + if let Some(ref label) = extractor.has_label { + let want_label = CString16::try_from(context.stamp(label).as_str()) + .context("unable to convert label to CString16")?; + let mut root = filesystem + .open_volume() + .context("unable to open filesystem volume")?; + let label = root + .get_boxed_info::() + .context("unable to get filesystem volume label")?; + + if label.volume_label() != want_label { + return Ok(false); + } + } + + // Check if the filesystem matches item criteria. + if let Some(ref item) = extractor.has_item { + let want_item = CString16::try_from(context.stamp(item).as_str()) + .context("unable to convert item to CString16")?; + let mut filesystem = FileSystem::new(filesystem); + + // Check the metadata of the item. + // Ignore filesystem errors as we can't do anything useful with the error. + let Some(metadata) = filesystem.metadata(Path::new(&want_item)).ok() else { + return Ok(false); + }; + + // Only check directories and files. + if !(metadata.is_directory() || metadata.is_regular_file()) { + return Ok(false); + } + } + + Ok(true) +} /// Extract a filesystem device path using the specified `context` and `extractor` configuration. pub fn extract( @@ -26,107 +121,43 @@ pub fn extract( bail!("at least one criteria is required for filesystem-device-match"); } + // Parse the partition uuid from the extractor. + let partition_uuid = extractor + .has_partition_uuid + .as_deref() + .map(Guid::from_str) + .transpose() + .map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?; + + // Parse the partition type uuid from the extractor. + let partition_type_uuid = extractor + .has_partition_type_uuid + .as_deref() + .map(Guid::from_str) + .transpose() + .map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?; + // Find all the filesystems inside the UEFI stack. let handles = uefi::boot::find_handles::() .context("unable to find filesystem handles")?; // Iterate over all the filesystems and check if they match the criteria. for handle in handles { - // This defines whether a match has been found. - let mut has_match = false; - - // Check if the partition info matches partition uuid criteria. - if let Some(ref has_partition_uuid) = extractor.has_partition_uuid { - // Parse the partition uuid from the extractor. - let parsed_uuid = Guid::from_str(has_partition_uuid) - .map_err(|e| anyhow!("unable to parse has-partition-uuid: {}", e))?; - - // Fetch the root of the device. - let root = uefi::boot::open_protocol_exclusive::(handle) - .context("unable to fetch the device path of the filesystem")? - .deref() - .to_boxed(); - - // Fetch the partition uuid for this filesystem. - let partition_uuid = - eficore::partition::partition_guid(&root, PartitionGuidForm::Partition) - .context("unable to fetch the partition uuid of the filesystem")?; - - // Compare the partition uuid to the parsed uuid. - // If it does not match, continue to the next filesystem. - if partition_uuid != Some(parsed_uuid) { + // A filesystem that can't be checked is skipped, as it should not prevent + // finding a match on the other filesystems or using the fallback value. + match is_match( + &context, + extractor, + handle, + partition_uuid, + partition_type_uuid, + ) { + Ok(true) => {} + Ok(false) => continue, + Err(error) => { + warn!("skipping filesystem, unable to check it: {:#}", error); continue; } - has_match = true; - } - - // Check if the partition info matches partition type uuid criteria. - if let Some(ref has_partition_type_uuid) = extractor.has_partition_type_uuid { - // Parse the partition type uuid from the extractor. - let parsed_uuid = Guid::from_str(has_partition_type_uuid) - .map_err(|e| anyhow!("unable to parse has-partition-type-uuid: {}", e))?; - - // Fetch the root of the device. - let root = uefi::boot::open_protocol_exclusive::(handle) - .context("unable to fetch the device path of the filesystem")? - .deref() - .to_boxed(); - - // Fetch the partition type uuid for this filesystem. - let partition_type_uuid = - eficore::partition::partition_guid(&root, PartitionGuidForm::PartitionType) - .context("unable to fetch the partition uuid of the filesystem")?; - // Compare the partition type uuid to the parsed uuid. - // If it does not match, continue to the next filesystem. - if partition_type_uuid != Some(parsed_uuid) { - continue; - } - has_match = true; - } - - // Open the filesystem protocol for this handle. - let mut filesystem = uefi::boot::open_protocol_exclusive::(handle) - .context("unable to open filesystem protocol")?; - - // Check if the filesystem matches label criteria. - if let Some(ref label) = extractor.has_label { - let want_label = CString16::try_from(context.stamp(label).as_str()) - .context("unable to convert label to CString16")?; - let mut root = filesystem - .open_volume() - .context("unable to open filesystem volume")?; - let label = root - .get_boxed_info::() - .context("unable to get filesystem volume label")?; - - if label.volume_label() != want_label { - continue; - } - has_match = true; - } - - // Check if the filesystem matches item criteria. - if let Some(ref item) = extractor.has_item { - let want_item = CString16::try_from(context.stamp(item).as_str()) - .context("unable to convert item to CString16")?; - let mut filesystem = FileSystem::new(filesystem); - - // Check the metadata of the item. - // Ignore filesystem errors as we can't do anything useful with the error. - let Some(metadata) = filesystem.metadata(Path::new(&want_item)).ok() else { - continue; - }; - - // Only check directories and files. - if !(metadata.is_directory() || metadata.is_regular_file()) { - continue; - } - has_match = true; - } - - // If there is no match, continue to the next filesystem. - if !has_match { - continue; } // If we have a match, return the device root path.