1use anyhow::{Context, Result};
6use fidl_fuchsia_storage_block::BlockProxy;
7use fidl_fuchsia_storage_partitions as fpartitions;
8use fs_management::filesystem::BlockConnector;
9use fs_management::format::{DiskFormat, detect_disk_format};
10use fuchsia_component::client::ServiceInstanceStream;
11use futures::TryStreamExt;
12
13pub type Guid = [u8; 16];
14
15pub fn into_guid(guid: Guid) -> fidl_fuchsia_storage_block::Guid {
16 fidl_fuchsia_storage_block::Guid { value: guid }
17}
18
19pub fn create_random_guid() -> Guid {
20 *uuid::Uuid::new_v4().as_bytes()
21}
22
23async fn partition_type_guid_matches(guid: &Guid, partition: &BlockProxy) -> Result<bool> {
24 let (status, type_guid) =
25 partition.get_type_guid().await.context("Failed to get type guid (fidl error")?;
26 zx::ok(status).context("Failed to get type guid")?;
27 let type_guid = if let Some(guid) = type_guid { guid } else { return Ok(false) };
28 let matched = type_guid.value == *guid;
29 log::info!(matched, type_guid:?, target_guid:?=guid; "matching type guid");
30 Ok(matched)
31}
32
33async fn partition_instance_guid_matches(guid: &Guid, partition: &BlockProxy) -> Result<bool> {
34 let (status, instance_guid) =
35 partition.get_instance_guid().await.context("Failed to get instance guid (fidl error")?;
36 zx::ok(status).context("Failed to get instance guid")?;
37 let instance_guid = if let Some(guid) = instance_guid { guid } else { return Ok(false) };
38 let matched = instance_guid.value == *guid;
39 log::info!(matched, instance_guid:?, target_guid:?=guid; "matching instance guid");
40 Ok(matched)
41}
42
43async fn partition_name_matches(name: &str, partition: &BlockProxy) -> Result<bool> {
44 let (status, partition_name) =
45 partition.get_name().await.context("Failed to get partition name (fidl error")?;
46 zx::ok(status).context("Failed to get partition name")?;
47 let partition_name = if let Some(name) = partition_name { name } else { return Ok(false) };
48 let matched = partition_name == name;
49 log::info!(matched, partition_name = partition_name.as_str(), target_name = name; "matching name");
50 Ok(matched)
51}
52
53async fn block_contents_match(format: DiskFormat, block: &BlockProxy) -> Result<bool> {
54 let content_format = detect_disk_format(block).await;
55 Ok(format == content_format)
56}
57
58#[derive(Debug)]
60pub enum BlockDeviceMatcher<'a> {
61 TypeGuid(&'a Guid),
63
64 InstanceGuid(&'a Guid),
66
67 Name(&'a str),
69
70 ContentsMatch(DiskFormat),
72}
73
74impl BlockDeviceMatcher<'_> {
75 async fn matches(&self, partition: &BlockProxy) -> Result<bool> {
76 match self {
77 Self::TypeGuid(guid) => partition_type_guid_matches(guid, partition).await,
78 Self::InstanceGuid(guid) => partition_instance_guid_matches(guid, partition).await,
79 Self::Name(name) => partition_name_matches(name, partition).await,
80 Self::ContentsMatch(format) => block_contents_match(*format, partition).await,
81 }
82 }
83}
84
85async fn matches_all(partition: &BlockProxy, matchers: &[BlockDeviceMatcher<'_>]) -> bool {
86 for matcher in matchers {
87 if !matcher.matches(partition).await.unwrap_or(false) {
88 return false;
89 }
90 }
91 true
92}
93
94pub async fn wait_for_block_device(
97 matchers: &[BlockDeviceMatcher<'_>],
98 mut stream: ServiceInstanceStream<fpartitions::PartitionServiceMarker>,
99) -> Result<fpartitions::PartitionServiceProxy> {
100 while let Some(proxy) = stream.try_next().await? {
101 let partition = proxy.connect_block()?.into_proxy();
102 if matches_all(&partition, matchers).await {
103 return Ok(proxy);
104 }
105 }
106 unreachable!()
107}
108
109pub async fn find_block_device<C, Iter>(
112 matchers: &[BlockDeviceMatcher<'_>],
113 partitions: Iter,
114) -> Result<Option<C>>
115where
116 C: BlockConnector,
117 Iter: Iterator<Item = C>,
118{
119 for connector in partitions {
120 let partition = connector.connect_block()?.into_proxy();
121 if matches_all(&partition, matchers).await {
122 return Ok(Some(connector));
123 }
124 }
125 Ok(None)
126}