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.