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}
127
128pub const DEFAULT_BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
129
130/// Finds a partition reserved for testing/benchmarks in GPT, or creates it if absent.
131pub async fn find_or_create_test_partition(
132    service: fuchsia_component::client::Service<fpartitions::PartitionServiceMarker>,
133    manager: fpartitions::PartitionsManagerProxy,
134    partition_size_bytes: u64,
135) -> Result<fpartitions::PartitionServiceProxy> {
136    if let Some(connector) = find_block_device(
137        &[
138            BlockDeviceMatcher::Name(fs_management::format::constants::BENCHMARK_FVM_VOLUME_NAME),
139            BlockDeviceMatcher::TypeGuid(
140                &fs_management::format::constants::BENCHMARK_FVM_TYPE_GUID,
141            ),
142        ],
143        service.clone().enumerate().await.context("Failed to enumerate partitions")?.into_iter(),
144    )
145    .await
146    .context("Error while searching for benchmark-fvm")?
147    {
148        return Ok(connector);
149    }
150
151    if let Some(connector) = find_block_device(
152        &[BlockDeviceMatcher::Name(fs_management::format::constants::PAD_RW_PARTITION_LABEL)],
153        service.clone().enumerate().await.context("Failed to enumerate partitions")?.into_iter(),
154    )
155    .await
156    .context("Error while searching for pad_rw")?
157    {
158        return Ok(connector);
159    }
160
161    // Otherwise, create the test partition in the GPT.
162    let info = manager
163        .get_block_info()
164        .await
165        .context("FIDL error on get_block_info")?
166        .map_err(zx::Status::err_from_raw)
167        .context("get_block_info failed")?;
168    let transaction = manager
169        .create_transaction()
170        .await
171        .context("FIDL error on create_transaction")?
172        .map_err(zx::Status::err_from_raw)
173        .context("create_transaction failed")?;
174    let request = fpartitions::PartitionsManagerAddPartitionRequest {
175        transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS)?),
176        name: Some(fs_management::format::constants::BENCHMARK_FVM_VOLUME_NAME.to_string()),
177        type_guid: Some(fidl_fuchsia_storage_block::Guid {
178            value: fs_management::format::constants::BENCHMARK_FVM_TYPE_GUID,
179        }),
180        num_blocks: Some(partition_size_bytes / info.1 as u64),
181        ..Default::default()
182    };
183    manager
184        .add_partition(request)
185        .await
186        .context("FIDL error on add_partition")?
187        .map_err(zx::Status::err_from_raw)
188        .context("add_partition failed")?;
189    manager
190        .commit_transaction(transaction)
191        .await
192        .context("FIDL error on commit_transaction")?
193        .map_err(zx::Status::err_from_raw)
194        .context("commit_transaction failed")?;
195
196    let service_instances = service.enumerate().await.context("Failed to enumerate partitions")?;
197    find_block_device(
198        &[
199            BlockDeviceMatcher::Name(fs_management::format::constants::BENCHMARK_FVM_VOLUME_NAME),
200            BlockDeviceMatcher::TypeGuid(
201                &fs_management::format::constants::BENCHMARK_FVM_TYPE_GUID,
202            ),
203        ],
204        service_instances.into_iter(),
205    )
206    .await?
207    .ok_or_else(|| anyhow::anyhow!("Failed to find newly created test partition"))
208}