| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::{anyhow, Context, Result}, |
| fidl_fuchsia_device::ControllerProxy, |
| fidl_fuchsia_hardware_block_partition::{PartitionMarker, PartitionProxy}, |
| fidl_fuchsia_io as fio, |
| fs_management::format::{detect_disk_format, DiskFormat}, |
| fuchsia_component::client::connect_to_protocol_at_path, |
| fuchsia_fs::directory::{WatchEvent, Watcher}, |
| fuchsia_zircon as zx, |
| futures::TryStreamExt, |
| std::path::{Path, PathBuf}, |
| }; |
| |
| pub mod fvm; |
| pub mod zxcrypt; |
| |
| pub type Guid = [u8; 16]; |
| |
| pub fn into_guid(guid: Guid) -> fidl_fuchsia_hardware_block_partition::Guid { |
| fidl_fuchsia_hardware_block_partition::Guid { value: guid } |
| } |
| |
| pub fn create_random_guid() -> Guid { |
| *uuid::Uuid::new_v4().as_bytes() |
| } |
| |
| pub async fn bind_fvm(proxy: &ControllerProxy) -> Result<()> { |
| fvm::bind_fvm_driver(proxy).await |
| } |
| |
| async fn partition_type_guid_matches(guid: &Guid, partition: &PartitionProxy) -> Result<bool> { |
| let (status, type_guid) = partition.get_type_guid().await?; |
| zx::ok(status).context("Failed to get type guid")?; |
| let type_guid = if let Some(guid) = type_guid { guid } else { return Ok(false) }; |
| Ok(type_guid.value == *guid) |
| } |
| |
| async fn partition_instance_guid_matches(guid: &Guid, partition: &PartitionProxy) -> Result<bool> { |
| let (status, instance_guid) = partition.get_instance_guid().await?; |
| zx::ok(status).context("Failed to get instance guid")?; |
| let instance_guid = if let Some(guid) = instance_guid { guid } else { return Ok(false) }; |
| Ok(instance_guid.value == *guid) |
| } |
| |
| async fn partition_name_matches(name: &str, partition: &PartitionProxy) -> Result<bool> { |
| let (status, partition_name) = partition.get_name().await?; |
| zx::ok(status).context("Failed to get partition name")?; |
| let partition_name = if let Some(name) = partition_name { name } else { return Ok(false) }; |
| Ok(partition_name == name) |
| } |
| |
| async fn block_contents_match(format: DiskFormat, block: &PartitionProxy) -> Result<bool> { |
| let content_format = detect_disk_format(block).await; |
| Ok(format == content_format) |
| } |
| |
| /// A constraint for the block device being waited for in `wait_for_block_device`. |
| pub enum BlockDeviceMatcher<'a> { |
| /// Only matches block devices that have this type Guid. |
| TypeGuid(&'a Guid), |
| |
| /// Only matches block devices that have this instance Guid. |
| InstanceGuid(&'a Guid), |
| |
| /// Only matches block devices that have this name. |
| Name(&'a str), |
| |
| /// Only matches block devices whose contents match the given format. |
| ContentsMatch(DiskFormat), |
| } |
| |
| impl BlockDeviceMatcher<'_> { |
| async fn matches(&self, partition: &PartitionProxy) -> Result<bool> { |
| match self { |
| Self::TypeGuid(guid) => partition_type_guid_matches(guid, partition).await, |
| Self::InstanceGuid(guid) => partition_instance_guid_matches(guid, partition).await, |
| Self::Name(name) => partition_name_matches(name, partition).await, |
| Self::ContentsMatch(format) => block_contents_match(*format, partition).await, |
| } |
| } |
| } |
| |
| /// Waits for a block device to appear in `/dev/class/block` that meets all of the requirements of |
| /// `matchers`. Returns the path to the matched block device. |
| pub async fn wait_for_block_device(matchers: &[BlockDeviceMatcher<'_>]) -> Result<PathBuf> { |
| const DEV_CLASS_BLOCK: &str = "/dev/class/block"; |
| assert!(!matchers.is_empty()); |
| let block_dev_dir = |
| fuchsia_fs::directory::open_in_namespace(DEV_CLASS_BLOCK, fio::OpenFlags::RIGHT_READABLE)?; |
| let mut watcher = Watcher::new(&block_dev_dir).await?; |
| while let Some(msg) = watcher.try_next().await? { |
| if msg.event != WatchEvent::ADD_FILE && msg.event != WatchEvent::EXISTING { |
| continue; |
| } |
| if msg.filename.to_str() == Some(".") { |
| continue; |
| } |
| let path = Path::new(DEV_CLASS_BLOCK).join(msg.filename); |
| let partition = connect_to_protocol_at_path::<PartitionMarker>(path.to_str().unwrap())?; |
| let mut matches = true; |
| for matcher in matchers { |
| if !matcher.matches(&partition).await.unwrap_or(false) { |
| matches = false; |
| break; |
| } |
| } |
| if matches { |
| return Ok(path); |
| } |
| } |
| Err(anyhow!("Failed to wait for block device")) |
| } |
| |
| /// Looks for a block device already in `/dev/class/block` that meets all of the requirements of |
| /// `matchers`. Returns the path to the matched block device. |
| pub async fn find_block_device(matchers: &[BlockDeviceMatcher<'_>]) -> Result<PathBuf> { |
| const DEV_CLASS_BLOCK: &str = "/dev/class/block"; |
| assert!(!matchers.is_empty()); |
| let block_dev_dir = |
| fuchsia_fs::directory::open_in_namespace(DEV_CLASS_BLOCK, fio::OpenFlags::RIGHT_READABLE)?; |
| let entries = fuchsia_fs::directory::readdir(&block_dev_dir) |
| .await |
| .context("Failed to readdir /dev/class/block")?; |
| for entry in entries { |
| let path = Path::new(DEV_CLASS_BLOCK).join(entry.name); |
| let partition = connect_to_protocol_at_path::<PartitionMarker>(path.to_str().unwrap())?; |
| let mut matches = true; |
| for matcher in matchers { |
| if !matcher.matches(&partition).await.unwrap_or(false) { |
| matches = false; |
| break; |
| } |
| } |
| if matches { |
| return Ok(path); |
| } |
| } |
| Err(anyhow!("Failed to find matching block device")) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, fidl_fuchsia_hardware_block_volume::ALLOCATE_PARTITION_FLAG_INACTIVE, |
| ramdevice_client::RamdiskClient, |
| }; |
| const BLOCK_SIZE: u64 = 512; |
| const BLOCK_COUNT: u64 = 64 * 1024 * 1024 / BLOCK_SIZE; |
| const FVM_SLICE_SIZE: usize = 1024 * 1024; |
| const INSTANCE_GUID: Guid = [ |
| 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, |
| 0x0f, |
| ]; |
| const TYPE_GUID: Guid = [ |
| 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, |
| 0xf0, |
| ]; |
| const VOLUME_NAME: &str = "volume-name"; |
| |
| #[fuchsia::test] |
| async fn wait_for_block_device_with_all_match_criteria() { |
| let ramdisk = RamdiskClient::create(BLOCK_SIZE, BLOCK_COUNT).await.unwrap(); |
| let fvm = fvm::set_up_fvm( |
| ramdisk.as_controller().expect("invalid controller"), |
| ramdisk.as_dir().expect("invalid directory proxy"), |
| FVM_SLICE_SIZE, |
| ) |
| .await |
| .expect("Failed to format ramdisk with FVM"); |
| fvm::create_fvm_volume( |
| &fvm, |
| VOLUME_NAME, |
| &TYPE_GUID, |
| &INSTANCE_GUID, |
| None, |
| ALLOCATE_PARTITION_FLAG_INACTIVE, |
| ) |
| .await |
| .expect("Failed to create fvm volume"); |
| |
| wait_for_block_device(&[ |
| BlockDeviceMatcher::TypeGuid(&TYPE_GUID), |
| BlockDeviceMatcher::InstanceGuid(&INSTANCE_GUID), |
| BlockDeviceMatcher::Name(VOLUME_NAME), |
| ]) |
| .await |
| .expect("Failed to find block device"); |
| |
| find_block_device(&[ |
| BlockDeviceMatcher::TypeGuid(&TYPE_GUID), |
| BlockDeviceMatcher::InstanceGuid(&INSTANCE_GUID), |
| BlockDeviceMatcher::Name(VOLUME_NAME), |
| ]) |
| .await |
| .expect("Failed to find block device"); |
| |
| find_block_device(&[ |
| BlockDeviceMatcher::TypeGuid(&TYPE_GUID), |
| BlockDeviceMatcher::InstanceGuid(&INSTANCE_GUID), |
| BlockDeviceMatcher::Name("something else"), |
| ]) |
| .await |
| .expect_err("Unexpected match for block device"); |
| } |
| } |