blob: 8c70e59301c853871142af7c0881574b58241cb1 [file] [log] [blame]
// Copyright 2022 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 {
crate::{
device::{
constants::{
BLOBFS_PARTITION_LABEL, BOOTPART_DRIVER_PATH, DATA_PARTITION_LABEL,
FVM_DRIVER_PATH, GPT_DRIVER_PATH, LEGACY_DATA_PARTITION_LABEL, MBR_DRIVER_PATH,
NAND_BROKER_DRIVER_PATH,
},
Device,
},
environment::Environment,
},
anyhow::{bail, Context, Error},
async_trait::async_trait,
fs_management::format::DiskFormat,
std::{
collections::BTreeMap,
sync::atomic::{AtomicBool, Ordering},
},
};
#[async_trait]
pub trait Matcher: Send {
/// Tries to match this device against this matcher. Matching should be infallible.
async fn match_device(&self, device: &mut dyn Device) -> bool;
/// Process this device as the format this matcher is for. This is called when this matcher
/// returns true during matching. This step is fallible - if a device matched a matcher, but
/// then this step fails, we stop matching and bubble up the error.
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error>;
}
pub struct Matchers {
matchers: Vec<Box<dyn Matcher>>,
matched: BTreeMap<String, usize>,
}
impl Matchers {
/// Create a new set of matchers. This essentially describes the expected partition layout for
/// a device.
pub fn new(config: &fshost_config::Config, ramdisk_path: Option<String>) -> Self {
let mut matchers = Vec::<Box<dyn Matcher>>::new();
if config.bootpart {
matchers.push(Box::new(BootpartMatcher::new()));
}
if config.nand {
matchers.push(Box::new(NandMatcher::new()));
}
// If ramdisk_image is true but we don't actually have a ramdisk launching, we skip
// making the fvm+blobfs+data or Fxblob matcher entirely.
if !config.ramdisk_image || ramdisk_path.is_some() {
if config.fxfs_blob {
if !config.netboot {
matchers.push(Box::new(FxblobMatcher::new(if config.ramdisk_image {
ramdisk_path
} else {
None
})));
}
} else {
let ramdisk_required = if config.ramdisk_image { ramdisk_path } else { None };
let fvm_matcher = Box::new(FvmMatcher::new(
ramdisk_required,
config.netboot,
config.blobfs,
config.data,
));
if config.fvm || (!config.netboot && (config.blobfs || config.data)) {
matchers.push(fvm_matcher);
}
}
}
if config.fvm && config.ramdisk_image {
// Add another matcher for the non-ramdisk version of fvm.
matchers.push(Box::new(PartitionMapMatcher::new(
DiskFormat::Fvm,
false,
FVM_DRIVER_PATH,
None,
)));
}
let gpt_matcher =
Box::new(PartitionMapMatcher::new(DiskFormat::Gpt, false, GPT_DRIVER_PATH, None));
if config.gpt {
matchers.push(gpt_matcher);
}
if config.gpt_all {
matchers.push(Box::new(PartitionMapMatcher::new(
DiskFormat::Gpt,
true,
GPT_DRIVER_PATH,
None,
)));
}
if config.mbr {
matchers.push(Box::new(PartitionMapMatcher::new(
DiskFormat::Mbr,
true,
MBR_DRIVER_PATH,
None,
)))
}
Matchers { matchers, matched: BTreeMap::new() }
}
/// Using the set of matchers we created, figure out if this block device matches any of our
/// expected partitions. If it does, return the information needed to launch the filesystem,
/// such as the component url or the shared library to pass to the driver binding.
pub async fn match_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<bool, Error> {
for (index, m) in self.matchers.iter_mut().enumerate() {
if m.match_device(device).await {
m.process_device(device, env).await?;
self.matched.insert(device.topological_path().to_string(), index);
return Ok(true);
}
}
Ok(false)
}
}
// Matches Bootpart devices.
struct BootpartMatcher();
impl BootpartMatcher {
fn new() -> Self {
BootpartMatcher()
}
}
#[async_trait]
impl Matcher for BootpartMatcher {
async fn match_device(&self, device: &mut dyn Device) -> bool {
device
.get_block_info()
.await
.map_or(false, |info| info.flags.contains(fidl_fuchsia_hardware_block::Flag::BOOTPART))
}
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error> {
env.attach_driver(device, BOOTPART_DRIVER_PATH).await
}
}
// Matches Nand devices.
struct NandMatcher();
impl NandMatcher {
fn new() -> Self {
NandMatcher()
}
}
#[async_trait]
impl Matcher for NandMatcher {
async fn match_device(&self, device: &mut dyn Device) -> bool {
device.is_nand()
}
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error> {
env.attach_driver(device, NAND_BROKER_DRIVER_PATH).await
}
}
// Matches against a data partition that exists independent of FVM using the DiskFormat.
struct FxblobMatcher {
// If this partition is required to exist on a ramdisk, then this contains the prefix it should
// have.
ramdisk_required: Option<String>,
// Because this matcher binds to the system Fxfs component, we can only match on it once.
// TODO(https://fxbug.dev/42079130): Can we be more precise here, e.g. give the matcher an expected device
// path based on system configuration?
already_matched: AtomicBool,
}
impl FxblobMatcher {
fn new(ramdisk_required: Option<String>) -> Self {
Self { ramdisk_required, already_matched: AtomicBool::new(false) }
}
}
#[async_trait]
impl Matcher for FxblobMatcher {
async fn match_device(&self, device: &mut dyn Device) -> bool {
if self.already_matched.load(Ordering::Relaxed) {
return false;
}
if let Some(ramdisk_prefix) = &self.ramdisk_required {
if !device.topological_path().starts_with(ramdisk_prefix) {
return false;
}
}
device.content_format().await.ok() == Some(DiskFormat::Fxfs)
}
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error> {
self.already_matched.store(true, Ordering::Relaxed);
env.mount_fxblob(device).await?;
env.mount_blob_volume().await?;
env.mount_data_volume().await?;
Ok(())
}
}
// Matches against the fvm partition and explicitly mounts the data and blob partitions.
// Fails if the blob partition doesn't exist. Creates the data partition if it doesn't
// already exist.
struct FvmMatcher {
// If this partition is required to exist on a ramdisk, then this contains the prefix it should
// have.
ramdisk_required: Option<String>,
netboot: bool,
// Set if we want to mount the blob partition.
blobfs: bool,
// Set if we want to mount the data partition.
data: bool,
// Set to true if we already matched a partition. It doesn't make sense to try and match
// multiple main system partitions.
already_matched: bool,
}
impl FvmMatcher {
fn new(ramdisk_required: Option<String>, netboot: bool, blobfs: bool, data: bool) -> Self {
Self { ramdisk_required, netboot, blobfs, data, already_matched: false }
}
}
#[async_trait]
impl Matcher for FvmMatcher {
async fn match_device(&self, device: &mut dyn Device) -> bool {
if self.already_matched {
return false;
}
if let Some(ramdisk_prefix) = &self.ramdisk_required {
if !device.topological_path().starts_with(ramdisk_prefix) {
return false;
}
}
device.content_format().await.ok() == Some(DiskFormat::Fvm)
}
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error> {
// volume names have the format {label}-p-{index}, e.g. blobfs-p-1
let volume_names = env.bind_and_enumerate_fvm(device).await?;
if !self.netboot {
if self.blobfs {
if let Some(blob_name) =
volume_names.iter().find(|name| name.starts_with(BLOBFS_PARTITION_LABEL))
{
env.mount_blobfs_on(blob_name).await?;
} else {
tracing::error!(?volume_names, "Couldn't find blobfs partition!");
bail!("Unable to find blobfs within FVM.");
}
}
if self.data {
if let Some(data_name) = volume_names.iter().find(|name| {
name.starts_with(DATA_PARTITION_LABEL)
|| name.starts_with(LEGACY_DATA_PARTITION_LABEL)
}) {
env.mount_data_on(data_name).await?;
} else {
let fvm_driver_path = format!("{}/fvm", device.topological_path());
tracing::warn!(%fvm_driver_path, ?volume_names,
"No existing data partition. Calling format_data().",
);
let fs =
env.format_data(&fvm_driver_path).await.context("failed to format data")?;
env.bind_data(fs)?;
}
}
}
// Once we have matched and processed the main system partitions, fuse this matcher so we
// don't match any other partitions.
self.already_matched = true;
Ok(())
}
}
// Matches partition maps. Matching is done using content sniffing.
struct PartitionMapMatcher {
// The content format expected.
content_format: DiskFormat,
// If true, match against multiple devices. Otherwise, only the first is matched.
allow_multiple: bool,
// When matched, this driver is attached to the device.
driver_path: &'static str,
ramdisk_path: Option<String>,
// The topological paths of all devices matched so far.
device_paths: Vec<String>,
}
impl PartitionMapMatcher {
fn new(
content_format: DiskFormat,
allow_multiple: bool,
driver_path: &'static str,
ramdisk_path: Option<String>,
) -> Self {
Self { content_format, allow_multiple, driver_path, ramdisk_path, device_paths: Vec::new() }
}
}
#[async_trait]
impl Matcher for PartitionMapMatcher {
async fn match_device(&self, device: &mut dyn Device) -> bool {
if !self.allow_multiple && !self.device_paths.is_empty() {
return false;
}
if let Some(ramdisk_prefix) = &self.ramdisk_path {
if device.topological_path().starts_with(ramdisk_prefix) {
tracing::info!(path = %device.path(), %ramdisk_prefix, "Found the fvm ramdisk");
} else {
return false;
}
}
device.content_format().await.ok() == Some(self.content_format)
}
async fn process_device(
&mut self,
device: &mut dyn Device,
env: &mut dyn Environment,
) -> Result<(), Error> {
env.attach_driver(device, self.driver_path).await?;
self.device_paths.push(device.topological_path().to_string());
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::{Device, DiskFormat, Environment, Matchers},
crate::{
config::default_config,
device::constants::{
BLOBFS_PARTITION_LABEL, BOOTPART_DRIVER_PATH, DATA_PARTITION_LABEL, DATA_TYPE_GUID,
FVM_DRIVER_PATH, GPT_DRIVER_PATH, LEGACY_DATA_PARTITION_LABEL,
NAND_BROKER_DRIVER_PATH,
},
environment::Filesystem,
},
anyhow::{anyhow, Error},
async_trait::async_trait,
fidl_fuchsia_device::ControllerProxy,
fidl_fuchsia_hardware_block::{BlockInfo, BlockProxy, Flag},
fidl_fuchsia_hardware_block_volume::VolumeProxy,
std::sync::Mutex,
};
#[derive(Clone)]
struct MockDevice {
block_flags: Flag,
is_nand: bool,
content_format: DiskFormat,
topological_path: String,
partition_label: Option<String>,
partition_type: Option<[u8; 16]>,
}
impl MockDevice {
fn new() -> Self {
MockDevice {
block_flags: Flag::empty(),
is_nand: false,
content_format: DiskFormat::Unknown,
topological_path: "mock_device".to_string(),
partition_label: None,
partition_type: None,
}
}
fn set_block_flags(mut self, flags: Flag) -> Self {
self.block_flags = flags;
self
}
fn set_nand(mut self, v: bool) -> Self {
self.is_nand = v;
self
}
fn set_content_format(mut self, format: DiskFormat) -> Self {
self.content_format = format;
self
}
fn set_topological_path(mut self, path: impl ToString) -> Self {
self.topological_path = path.to_string().into();
self
}
fn set_partition_label(mut self, label: impl ToString) -> Self {
self.partition_label = Some(label.to_string());
self
}
fn set_partition_type(mut self, partition_type: &[u8; 16]) -> Self {
self.partition_type = Some(partition_type.clone());
self
}
}
#[async_trait]
impl Device for MockDevice {
async fn get_block_info(&self) -> Result<fidl_fuchsia_hardware_block::BlockInfo, Error> {
if self.is_nand {
Err(anyhow!("not supported by nand device"))
} else {
Ok(BlockInfo {
block_count: 0,
block_size: 0,
max_transfer_size: 0,
flags: self.block_flags,
})
}
}
fn is_nand(&self) -> bool {
self.is_nand
}
async fn content_format(&mut self) -> Result<DiskFormat, Error> {
Ok(self.content_format)
}
fn topological_path(&self) -> &str {
&self.topological_path
}
fn path(&self) -> &str {
&self.topological_path
}
async fn partition_label(&mut self) -> Result<&str, Error> {
Ok(self
.partition_label
.as_ref()
.unwrap_or_else(|| panic!("Unexpected call to partition_label")))
}
async fn partition_type(&mut self) -> Result<&[u8; 16], Error> {
Ok(self
.partition_type
.as_ref()
.unwrap_or_else(|| panic!("Unexpected call to partition_type")))
}
async fn partition_instance(&mut self) -> Result<&[u8; 16], Error> {
unreachable!()
}
fn controller(&self) -> &ControllerProxy {
unreachable!()
}
fn reopen_controller(&self) -> Result<ControllerProxy, Error> {
unreachable!()
}
fn block_proxy(&self) -> Result<BlockProxy, Error> {
unreachable!()
}
fn volume_proxy(&self) -> Result<VolumeProxy, Error> {
unreachable!()
}
async fn get_child(&self, _suffix: &str) -> Result<Box<dyn Device>, Error> {
unreachable!()
}
}
struct MockEnv {
expected_driver_path: Mutex<Option<String>>,
expect_bind_and_enumerate_fvm: Mutex<bool>,
expect_mount_blobfs_on: Mutex<bool>,
expect_mount_fxblob: Mutex<bool>,
expect_mount_blob_volume: Mutex<bool>,
expect_mount_data_volume: Mutex<bool>,
expect_mount_data_on: Mutex<bool>,
expect_format_data: Mutex<bool>,
expect_bind_data: Mutex<bool>,
legacy_data_format: bool,
create_data_partition: bool,
}
impl MockEnv {
fn new() -> Self {
MockEnv {
expected_driver_path: Mutex::new(None),
expect_bind_and_enumerate_fvm: Mutex::new(false),
expect_mount_blobfs_on: Mutex::new(false),
expect_mount_fxblob: Mutex::new(false),
expect_mount_blob_volume: Mutex::new(false),
expect_mount_data_volume: Mutex::new(false),
expect_mount_data_on: Mutex::new(false),
expect_format_data: Mutex::new(false),
expect_bind_data: Mutex::new(false),
legacy_data_format: false,
create_data_partition: true,
}
}
fn expect_attach_driver(mut self, path: impl ToString) -> Self {
*self.expected_driver_path.get_mut().unwrap() = Some(path.to_string());
self
}
fn expect_bind_and_enumerate_fvm(mut self) -> Self {
*self.expect_bind_and_enumerate_fvm.get_mut().unwrap() = true;
self
}
fn expect_mount_blobfs_on(mut self) -> Self {
*self.expect_mount_blobfs_on.get_mut().unwrap() = true;
self
}
fn expect_format_data(mut self) -> Self {
*self.expect_format_data.get_mut().unwrap() = true;
self
}
fn expect_bind_data(mut self) -> Self {
*self.expect_bind_data.get_mut().unwrap() = true;
self
}
fn expect_mount_fxblob(mut self) -> Self {
*self.expect_mount_fxblob.get_mut().unwrap() = true;
self
}
fn expect_mount_blob_volume(mut self) -> Self {
*self.expect_mount_blob_volume.get_mut().unwrap() = true;
self
}
fn expect_mount_data_volume(mut self) -> Self {
*self.expect_mount_data_volume.get_mut().unwrap() = true;
self
}
fn expect_mount_data_on(mut self) -> Self {
*self.expect_mount_data_on.get_mut().unwrap() = true;
self
}
fn legacy_data_format(mut self) -> Self {
self.legacy_data_format = true;
self
}
fn without_data_partition(mut self) -> Self {
self.create_data_partition = false;
self
}
}
#[async_trait]
impl Environment for MockEnv {
async fn attach_driver(
&self,
_device: &mut dyn Device,
driver_path: &str,
) -> Result<(), Error> {
assert_eq!(
driver_path,
self.expected_driver_path
.lock()
.unwrap()
.take()
.expect("Unexpected call to attach_driver")
);
Ok(())
}
async fn bind_and_enumerate_fvm(
&mut self,
_device: &mut dyn Device,
) -> Result<Vec<String>, Error> {
assert_eq!(
std::mem::take(&mut *self.expect_bind_and_enumerate_fvm.lock().unwrap()),
true,
"Unexpected call to bind_and_enumerate_fvm"
);
let mut volume_names = vec![BLOBFS_PARTITION_LABEL.to_string()];
if self.create_data_partition {
if self.legacy_data_format {
volume_names.push(LEGACY_DATA_PARTITION_LABEL.to_string())
} else {
volume_names.push(DATA_PARTITION_LABEL.to_string())
};
}
Ok(volume_names)
}
async fn mount_blobfs_on(&mut self, _blobfs_partition_name: &str) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_mount_blobfs_on.lock().unwrap()),
true,
"Unexpected call to mount_blobfs_on"
);
Ok(())
}
async fn mount_data_on(&mut self, _data_partition_name: &str) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_mount_data_on.lock().unwrap()),
true,
"Unexpected call to mount_data_on"
);
Ok(())
}
async fn format_data(&mut self, _fvm_topo_path: &str) -> Result<Filesystem, Error> {
assert_eq!(
std::mem::take(&mut *self.expect_format_data.lock().unwrap()),
true,
"Unexpected call to format_data"
);
Ok(Filesystem::Queue(vec![]))
}
fn bind_data(&mut self, mut _fs: Filesystem) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_bind_data.lock().unwrap()),
true,
"Unexpected call to bind_data"
);
Ok(())
}
async fn mount_fxblob(&mut self, _device: &mut dyn Device) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_mount_fxblob.lock().unwrap()),
true,
"Unexpected call to mount_fxblob"
);
Ok(())
}
async fn mount_blob_volume(&mut self) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_mount_blob_volume.lock().unwrap()),
true,
"Unexpected call to mount_blob_volume"
);
Ok(())
}
async fn mount_data_volume(&mut self) -> Result<(), Error> {
assert_eq!(
std::mem::take(&mut *self.expect_mount_data_volume.lock().unwrap()),
true,
"Unexpected call to mount_data_volume"
);
Ok(())
}
async fn shred_data(&mut self) -> Result<(), Error> {
unreachable!();
}
async fn shutdown(&mut self) -> Result<(), Error> {
unreachable!();
}
}
impl Drop for MockEnv {
fn drop(&mut self) {
assert!(self.expected_driver_path.get_mut().unwrap().is_none());
assert!(!*self.expect_mount_blobfs_on.lock().unwrap());
assert!(!*self.expect_mount_data_on.lock().unwrap());
assert!(!*self.expect_bind_and_enumerate_fvm.lock().unwrap());
assert!(!*self.expect_bind_data.lock().unwrap());
}
}
#[fuchsia::test]
async fn test_bootpart_matcher() {
let mut mock_device = MockDevice::new().set_block_flags(Flag::BOOTPART);
// Check no match when disabled in config.
assert!(!Matchers::new(
&fshost_config::Config { bootpart: false, ..default_config() },
None
)
.match_device(&mut mock_device, &mut MockEnv::new())
.await
.expect("match_device failed"));
assert!(Matchers::new(&default_config(), None)
.match_device(
&mut mock_device,
&mut MockEnv::new().expect_attach_driver(BOOTPART_DRIVER_PATH)
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_nand_matcher() {
let mut device = MockDevice::new().set_nand(true);
let mut env = MockEnv::new().expect_attach_driver(NAND_BROKER_DRIVER_PATH);
// Default shouldn't match.
assert!(!Matchers::new(&default_config(), None)
.match_device(&mut device, &mut env)
.await
.expect("match_device failed"));
assert!(Matchers::new(&fshost_config::Config { nand: true, ..default_config() }, None)
.match_device(&mut device, &mut env)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_partition_map_matcher() {
let mut env = MockEnv::new().expect_attach_driver(GPT_DRIVER_PATH);
// Check no match when disabled in config.
let mut device = MockDevice::new().set_content_format(DiskFormat::Gpt);
assert!(!Matchers::new(
&fshost_config::Config { blobfs: false, data: false, gpt: false, ..default_config() },
None
)
.match_device(&mut device, &mut env)
.await
.expect("match_device failed"));
let mut matchers = Matchers::new(&default_config(), None);
assert!(matchers.match_device(&mut device, &mut env).await.expect("match_device failed"));
// More GPT devices should not get matched.
assert!(!matchers.match_device(&mut device, &mut env).await.expect("match_device failed"));
// The gpt_all config should allow multiple GPT devices to be matched.
let mut matchers =
Matchers::new(&fshost_config::Config { gpt_all: true, ..default_config() }, None);
let mut env = MockEnv::new().expect_attach_driver(GPT_DRIVER_PATH);
assert!(matchers.match_device(&mut device, &mut env).await.expect("match_device failed"));
let mut env = MockEnv::new().expect_attach_driver(GPT_DRIVER_PATH);
assert!(matchers.match_device(&mut device, &mut env).await.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_partition_map_matcher_ramdisk_prefix() {
// If ramdisk_image is true and one of the devices matches the ramdisk prefix, we will match
// two fvm devices, and the third one will fail.
let mut matchers = Matchers::new(
&fshost_config::Config {
ramdisk_image: true,
data_filesystem_format: "minfs".to_string(),
..default_config()
},
Some("second_prefix".to_string()),
);
let mut fvm_device = MockDevice::new()
.set_content_format(DiskFormat::Fvm)
.set_topological_path("first_prefix");
let mut env = MockEnv::new().expect_attach_driver(FVM_DRIVER_PATH);
assert!(matchers
.match_device(&mut fvm_device, &mut env)
.await
.expect("match_device failed"));
let mut fvm_device = fvm_device.set_topological_path("second_prefix");
let mut env = MockEnv::new()
.expect_bind_and_enumerate_fvm()
.expect_mount_blobfs_on()
.expect_mount_data_on();
assert!(matchers
.match_device(&mut fvm_device, &mut env)
.await
.expect("match_device failed"));
let mut fvm_device = fvm_device.set_topological_path("third_prefix");
assert!(!matchers
.match_device(&mut fvm_device, &mut MockEnv::new())
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn partition_map_matcher_wrong_prefix_match() {
// If ramdisk_image is true but no devices match the prefix, only the first device will
// match.
let mut matchers = Matchers::new(
&fshost_config::Config {
ramdisk_image: true,
data_filesystem_format: "fxfs".to_string(),
..default_config()
},
Some("wrong_prefix".to_string()),
);
let mut fvm_device = MockDevice::new()
.set_content_format(DiskFormat::Fvm)
.set_topological_path("first_prefix");
let mut env = MockEnv::new().expect_attach_driver(FVM_DRIVER_PATH);
assert!(matchers
.match_device(&mut fvm_device, &mut env)
.await
.expect("match_device failed"));
let mut fvm_device = fvm_device.set_topological_path("second_prefix");
assert!(!matchers
.match_device(&mut fvm_device, &mut MockEnv::new())
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn partition_map_matcher_no_prefix_match() {
// If ramdisk_image is true but no ramdisk path is provided, only the first device will
// match.
let mut matchers = Matchers::new(
&fshost_config::Config {
ramdisk_image: true,
data_filesystem_format: "fxfs".to_string(),
..default_config()
},
None,
);
let mut fvm_device = MockDevice::new()
.set_content_format(DiskFormat::Fvm)
.set_topological_path("first_prefix");
let mut env = MockEnv::new().expect_attach_driver(FVM_DRIVER_PATH);
assert!(matchers
.match_device(&mut fvm_device, &mut env)
.await
.expect("match_device failed"));
let mut fvm_device = fvm_device.set_topological_path("second_prefix");
assert!(!matchers
.match_device(&mut fvm_device, &mut MockEnv::new())
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_blobfs_matcher() {
let mut fvm_device = MockDevice::new().set_content_format(DiskFormat::Fvm);
let mut env = MockEnv::new().expect_bind_and_enumerate_fvm().expect_mount_blobfs_on();
let mut matchers =
Matchers::new(&fshost_config::Config { data: false, ..default_config() }, None);
assert!(matchers
.match_device(&mut fvm_device, &mut env)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_data_matcher() {
let mut matchers =
Matchers::new(&fshost_config::Config { blobfs: false, ..default_config() }, None);
assert!(matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new().expect_bind_and_enumerate_fvm().expect_mount_data_on()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_legacy_data_matcher() {
let mut matchers = Matchers::new(&default_config(), None);
assert!(matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new()
.legacy_data_format()
.expect_bind_and_enumerate_fvm()
.expect_mount_blobfs_on()
.expect_mount_data_on()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_matcher_without_data_partition() {
let mut matchers = Matchers::new(&default_config(), None);
assert!(matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new()
.without_data_partition()
.expect_bind_and_enumerate_fvm()
.expect_mount_blobfs_on()
.expect_format_data()
.expect_bind_data()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_multiple_fvm_partitions_second_fails() {
let mut matchers = Matchers::new(&default_config(), None);
assert!(matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new()
.expect_bind_and_enumerate_fvm()
.expect_mount_data_on()
.expect_mount_blobfs_on()
)
.await
.expect("match_device failed"));
assert!(!matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_netboot_flag_true() {
let mut matchers =
Matchers::new(&fshost_config::Config { netboot: true, ..default_config() }, None);
assert!(matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new().expect_bind_and_enumerate_fvm()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_netboot_flag_true_fxblob() {
let mut matchers = Matchers::new(
&fshost_config::Config {
data_filesystem_format: "fxfs".to_string(),
netboot: true,
fxfs_blob: true,
..default_config()
},
None,
);
// FVM shouldn't match...
assert!(!matchers
.match_device(
&mut MockDevice::new().set_content_format(DiskFormat::Fvm),
&mut MockEnv::new()
)
.await
.expect("match_device failed"));
// And neither should Fxblob.
assert!(!matchers
.match_device(
&mut MockDevice::new()
.set_content_format(DiskFormat::Fxfs)
.set_partition_label(DATA_PARTITION_LABEL)
.set_partition_type(&DATA_TYPE_GUID),
&mut MockEnv::new()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
async fn test_fxblob_matcher() {
let mut matchers = Matchers::new(
&fshost_config::Config {
fxfs_blob: true,
data_filesystem_format: "fxfs".to_string(),
..default_config()
},
None,
);
assert!(matchers
.match_device(
&mut MockDevice::new()
.set_content_format(DiskFormat::Fxfs)
.set_partition_label(DATA_PARTITION_LABEL)
.set_partition_type(&DATA_TYPE_GUID),
&mut MockEnv::new()
.expect_mount_fxblob()
.expect_mount_blob_volume()
.expect_mount_data_volume()
)
.await
.expect("match_device failed"));
// We should only be able to match Fxblob once.
assert!(!matchers
.match_device(
&mut MockDevice::new()
.set_content_format(DiskFormat::Fxfs)
.set_partition_label(DATA_PARTITION_LABEL)
.set_partition_type(&DATA_TYPE_GUID),
&mut MockEnv::new()
.expect_mount_fxblob()
.expect_mount_blob_volume()
.expect_mount_data_volume()
)
.await
.expect("match_device failed"));
}
#[fuchsia::test]
fn test_device_fvm_path() {
let device =
MockDevice::new().set_topological_path("/some/fvm/path/with/another/fvm/inside");
assert_eq!(device.fvm_path(), Some("/some/fvm/path/with/another/fvm".to_string()));
}
}