Skip to main content

block_matcher/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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/// A constraint for the block device being waited for in `wait_for_block_device`.
59#[derive(Debug)]
60pub enum BlockDeviceMatcher<'a> {
61    /// Only matches block devices that have this type Guid.
62    TypeGuid(&'a Guid),
63
64    /// Only matches block devices that have this instance Guid.
65    InstanceGuid(&'a Guid),
66
67    /// Only matches block devices that have this name.
68    Name(&'a str),
69
70    /// Only matches block devices whose contents match the given format.
71    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
94/// Waits for the first partition service instance that meets all of the requirements of `matchers`.
95/// Returns the path to the matched block device.
96pub 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
109/// Returns the first partition in `partitions` matching all of `matchers.`  Ok(None) indicates no
110/// partitions matched.
111pub 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}