Skip to main content

storage_isolated_driver_manager/
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, anyhow};
6use fidl_fuchsia_io as fio;
7use fidl_fuchsia_storage_block::{BlockMarker, BlockProxy};
8use fidl_fuchsia_storage_partitions as fpartitions;
9use fs_management::filesystem::BlockConnector;
10use fs_management::format::{DiskFormat, detect_disk_format};
11use fuchsia_component::client::{ServiceInstanceStream, connect_to_protocol_at_path};
12use fuchsia_fs::directory::{WatchEvent, Watcher};
13use futures::TryStreamExt;
14use std::path::{Path, PathBuf};
15
16pub type Guid = [u8; 16];
17
18pub fn into_guid(guid: Guid) -> fidl_fuchsia_storage_block::Guid {
19    fidl_fuchsia_storage_block::Guid { value: guid }
20}
21
22pub fn create_random_guid() -> Guid {
23    *uuid::Uuid::new_v4().as_bytes()
24}
25
26async fn partition_type_guid_matches(guid: &Guid, partition: &BlockProxy) -> Result<bool> {
27    let (status, type_guid) =
28        partition.get_type_guid().await.context("Failed to get type guid (fidl error")?;
29    zx::ok(status).context("Failed to get type guid")?;
30    let type_guid = if let Some(guid) = type_guid { guid } else { return Ok(false) };
31    let matched = type_guid.value == *guid;
32    log::info!(matched, type_guid:?, target_guid:?=guid; "matching type guid");
33    Ok(matched)
34}
35
36async fn partition_instance_guid_matches(guid: &Guid, partition: &BlockProxy) -> Result<bool> {
37    let (status, instance_guid) =
38        partition.get_instance_guid().await.context("Failed to get instance guid (fidl error")?;
39    zx::ok(status).context("Failed to get instance guid")?;
40    let instance_guid = if let Some(guid) = instance_guid { guid } else { return Ok(false) };
41    let matched = instance_guid.value == *guid;
42    log::info!(matched, instance_guid:?, target_guid:?=guid; "matching instance guid");
43    Ok(matched)
44}
45
46async fn partition_name_matches(name: &str, partition: &BlockProxy) -> Result<bool> {
47    let (status, partition_name) =
48        partition.get_name().await.context("Failed to get partition name (fidl error")?;
49    zx::ok(status).context("Failed to get partition name")?;
50    let partition_name = if let Some(name) = partition_name { name } else { return Ok(false) };
51    let matched = partition_name == name;
52    log::info!(matched, partition_name = partition_name.as_str(), target_name = name; "matching name");
53    Ok(matched)
54}
55
56async fn block_contents_match(format: DiskFormat, block: &BlockProxy) -> Result<bool> {
57    let content_format = detect_disk_format(block).await;
58    Ok(format == content_format)
59}
60
61/// A constraint for the block device being waited for in `wait_for_block_device`.
62#[derive(Debug)]
63pub enum BlockDeviceMatcher<'a> {
64    /// Only matches block devices that have this type Guid.
65    TypeGuid(&'a Guid),
66
67    /// Only matches block devices that have this instance Guid.
68    InstanceGuid(&'a Guid),
69
70    /// Only matches block devices that have this name.
71    Name(&'a str),
72
73    /// Only matches block devices whose contents match the given format.
74    ContentsMatch(DiskFormat),
75}
76
77impl BlockDeviceMatcher<'_> {
78    async fn matches(&self, partition: &BlockProxy) -> Result<bool> {
79        match self {
80            Self::TypeGuid(guid) => partition_type_guid_matches(guid, partition).await,
81            Self::InstanceGuid(guid) => partition_instance_guid_matches(guid, partition).await,
82            Self::Name(name) => partition_name_matches(name, partition).await,
83            Self::ContentsMatch(format) => block_contents_match(*format, partition).await,
84        }
85    }
86}
87
88async fn matches_all(partition: &BlockProxy, matchers: &[BlockDeviceMatcher<'_>]) -> bool {
89    for matcher in matchers {
90        if !matcher.matches(partition).await.unwrap_or(false) {
91            return false;
92        }
93    }
94    true
95}
96
97/// Waits for a block device to appear in `/dev/class/block` that meets all of the requirements of
98/// `matchers`. Returns the path to the matched block device.
99/// TODO(https://fxbug.dev/339491886): Remove when all clients are ported to
100/// `wait_for_block_device`.
101pub async fn wait_for_block_device_devfs(matchers: &[BlockDeviceMatcher<'_>]) -> Result<PathBuf> {
102    const DEV_CLASS_BLOCK: &str = "/dev/class/block";
103    assert!(!matchers.is_empty());
104    let block_dev_dir =
105        fuchsia_fs::directory::open_in_namespace(DEV_CLASS_BLOCK, fio::PERM_READABLE)?;
106    let mut watcher = Watcher::new(&block_dev_dir).await?;
107    while let Some(msg) = watcher.try_next().await? {
108        if msg.event != WatchEvent::ADD_FILE && msg.event != WatchEvent::EXISTING {
109            continue;
110        }
111        if msg.filename.to_str() == Some(".") {
112            continue;
113        }
114        let path = Path::new(DEV_CLASS_BLOCK).join(msg.filename);
115        let partition = connect_to_protocol_at_path::<BlockMarker>(path.to_str().unwrap())?;
116        if matches_all(&partition, matchers).await {
117            return Ok(path);
118        }
119    }
120    Err(anyhow!("Failed to wait for block device"))
121}
122
123/// Waits for the first partition service instance that meets all of the requirements of `matchers`.
124/// Returns the path to the matched block device.
125/// TODO(https://fxbug.dev/339491886): Remove when all clients are ported to
126/// `wait_for_block_device_devfs.
127pub async fn wait_for_block_device(
128    matchers: &[BlockDeviceMatcher<'_>],
129    mut stream: ServiceInstanceStream<fpartitions::PartitionServiceMarker>,
130) -> Result<fpartitions::PartitionServiceProxy> {
131    while let Some(proxy) = stream.try_next().await? {
132        let partition = proxy.connect_block()?.into_proxy();
133        if matches_all(&partition, matchers).await {
134            return Ok(proxy);
135        }
136    }
137    unreachable!()
138}
139
140/// Looks for a block device already in `/dev/class/block` that meets all of the requirements of
141/// `matchers`. Returns the path to the matched block device.
142/// TODO(https://fxbug.dev/339491886): Remove when all clients are ported to `find_block_device`.
143pub async fn find_block_device_devfs(matchers: &[BlockDeviceMatcher<'_>]) -> Result<PathBuf> {
144    const DEV_CLASS_BLOCK: &str = "/dev/class/block";
145    assert!(!matchers.is_empty());
146    let block_dev_dir =
147        fuchsia_fs::directory::open_in_namespace(DEV_CLASS_BLOCK, fio::PERM_READABLE)?;
148    let entries = fuchsia_fs::directory::readdir(&block_dev_dir)
149        .await
150        .context("Failed to readdir /dev/class/block")?;
151    for entry in entries {
152        let path = Path::new(DEV_CLASS_BLOCK).join(entry.name);
153        let partition = connect_to_protocol_at_path::<BlockMarker>(path.to_str().unwrap())?;
154        if matches_all(&partition, matchers).await {
155            return Ok(path);
156        }
157    }
158    Err(anyhow!("Failed to find matching block device"))
159}
160
161/// Returns the first partition in `partitions` matching all of `matchers.`  Ok(None) indicates no
162/// partitions matched.
163pub async fn find_block_device<C, Iter>(
164    matchers: &[BlockDeviceMatcher<'_>],
165    partitions: Iter,
166) -> Result<Option<C>>
167where
168    C: BlockConnector,
169    Iter: Iterator<Item = C>,
170{
171    for connector in partitions {
172        let partition = connector.connect_block()?.into_proxy();
173        if matches_all(&partition, matchers).await {
174            return Ok(Some(connector));
175        }
176    }
177    Ok(None)
178}