use async_trait::async_trait;
use fidl::endpoints::{create_proxy, Proxy};
use fidl_fuchsia_device::{ControllerMarker, ControllerProxy};
use fidl_fuchsia_hardware_block::BlockMarker;
use fidl_fuchsia_hardware_block_volume::{
VolumeManagerMarker, VolumeManagerProxy, VolumeSynchronousProxy,
ALLOCATE_PARTITION_FLAG_INACTIVE,
};
use fidl_fuchsia_io as fio;
use fs_management::format::DiskFormat;
use fs_management::BLOBFS_TYPE_GUID;
use fuchsia_component::client::{
connect_to_named_protocol_at_dir_root, connect_to_protocol_at_path,
};
use ramdevice_client::RamdiskClient;
use std::path::PathBuf;
use storage_benchmarks::block_device::BlockDevice;
use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
use storage_isolated_driver_manager::{
create_random_guid, find_block_device, fvm, into_guid, wait_for_block_device, zxcrypt,
BlockDeviceMatcher, Guid,
};
const RAMDISK_FVM_SLICE_SIZE: usize = 1024 * 1024;
const BLOBFS_VOLUME_NAME: &str = "blobfs";
const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
const BENCHMARK_FVM_SLICE_SIZE_BYTES: usize = 8 * 1024 * 1024;
const BENCHMARK_FVM_TYPE_GUID: &Guid = &[
0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
];
const BENCHMARK_FVM_VOLUME_NAME: &str = "benchmark-fvm";
const BENCHMARK_TYPE_GUID: &Guid = &[
0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
];
const BENCHMARK_VOLUME_NAME: &str = "benchmark";
pub struct RamdiskFactory {
block_size: u64,
block_count: u64,
}
impl RamdiskFactory {
#[allow(dead_code)]
pub async fn new(block_size: u64, block_count: u64) -> Self {
Self { block_size, block_count }
}
}
#[async_trait]
impl BlockDeviceFactory for RamdiskFactory {
async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
Box::new(Ramdisk::new(self.block_size, self.block_count, config).await)
}
}
pub struct Ramdisk {
_ramdisk: RamdiskClient,
volume_dir: fio::DirectoryProxy,
volume_controller: ControllerProxy,
}
impl Ramdisk {
async fn new(block_size: u64, block_count: u64, config: &BlockDeviceConfig) -> Self {
let ramdisk = RamdiskClient::create(block_size, block_count)
.await
.expect("Failed to create RamdiskClient");
let volume_manager = fvm::set_up_fvm(
ramdisk.as_controller().expect("invalid controller"),
ramdisk.as_dir().expect("invalid directory proxy"),
RAMDISK_FVM_SLICE_SIZE,
)
.await
.expect("Failed to set up FVM");
let volume_dir = set_up_fvm_volume(&volume_manager, config.fvm_volume_size).await;
let volume_dir = if config.use_zxcrypt {
zxcrypt::set_up_insecure_zxcrypt(&volume_dir).await.expect("Failed to set up zxcrypt")
} else {
volume_dir
};
let volume_controller = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
&volume_dir,
"device_controller",
)
.expect("failed to connect to the device controller");
Self { _ramdisk: ramdisk, volume_dir, volume_controller: volume_controller }
}
}
impl BlockDevice for Ramdisk {
fn dir(&self) -> &fio::DirectoryProxy {
&self.volume_dir
}
fn controller(&self) -> &ControllerProxy {
&self.volume_controller
}
}
pub struct FvmVolumeFactory {
fvm: VolumeManagerProxy,
}
async fn connect_to_system_fvm() -> Option<VolumeManagerProxy> {
let blobfs_dev_path = find_block_device(&[
BlockDeviceMatcher::Name(BLOBFS_VOLUME_NAME),
BlockDeviceMatcher::TypeGuid(&BLOBFS_TYPE_GUID),
])
.await
.ok()?;
let controller_path = format!("{}/device_controller", blobfs_dev_path.to_str().unwrap());
let blobfs_controller = connect_to_protocol_at_path::<ControllerMarker>(&controller_path)
.unwrap_or_else(|_| panic!("Failed to connect to Controller at {:?}", controller_path));
let path = blobfs_controller
.get_topological_path()
.await
.expect("FIDL error")
.expect("get_topological_path failed");
let mut path = PathBuf::from(path);
if !path.pop() || !path.pop() {
panic!("Unexpected topological path for Blobfs {}", path.display());
}
match path.file_name() {
Some(p) => assert!(p == "fvm", "Unexpected FVM path: {}", path.display()),
None => panic!("Unexpected FVM path: {}", path.display()),
}
Some(
connect_to_protocol_at_path::<VolumeManagerMarker>(path.to_str().unwrap())
.unwrap_or_else(|_| panic!("Failed to connect to VolumeManager at {:?}", path)),
)
}
async fn connect_to_test_fvm() -> Option<VolumeManagerProxy> {
let mut fvm_path = if let Ok(path) = find_block_device(&[
BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
])
.await
{
path
} else {
let mut gpt_block_path =
find_block_device(&[BlockDeviceMatcher::ContentsMatch(DiskFormat::Gpt)]).await.ok()?;
gpt_block_path.push("device_controller");
let gpt_block_controller =
connect_to_protocol_at_path::<ControllerMarker>(gpt_block_path.to_str().unwrap())
.expect("Failed to connect to GPT controller");
let mut gpt_path = gpt_block_controller
.get_topological_path()
.await
.expect("FIDL error")
.expect("get_topological_path failed");
gpt_path.push_str("/gpt/device_controller");
let gpt_controller = connect_to_protocol_at_path::<ControllerMarker>(&gpt_path)
.expect("Failed to connect to GPT controller");
let (volume_manager, server) = create_proxy::<VolumeManagerMarker>();
gpt_controller
.connect_to_device_fidl(server.into_channel())
.expect("Failed to connect to device FIDL");
let slice_size = {
let (status, info) = volume_manager.get_info().await.expect("FIDL error");
zx::ok(status).expect("Failed to get VolumeManager info");
info.unwrap().slice_size
};
let slice_count = BENCHMARK_FVM_SIZE_BYTES / slice_size;
let instance_guid = into_guid(create_random_guid());
let status = volume_manager
.allocate_partition(
slice_count,
&into_guid(BENCHMARK_FVM_TYPE_GUID.clone()),
&instance_guid,
BENCHMARK_FVM_VOLUME_NAME,
0,
)
.await
.expect("FIDL error");
zx::ok(status).expect("Failed to allocate benchmark FVM");
wait_for_block_device(&[
BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
])
.await
.expect("Failed to wait for newly created benchmark FVM to appear")
};
fvm_path.push("device_controller");
let fvm_controller =
connect_to_protocol_at_path::<ControllerMarker>(fvm_path.to_str().unwrap())
.expect("failed to connect to controller");
fvm_controller.unbind_children().await.expect("FIDL error").expect("failed to unbind children");
let topo_path = fvm_controller
.get_topological_path()
.await
.expect("FIDL error")
.expect("get_topological_path failed");
let dir = fuchsia_fs::directory::open_in_namespace(&topo_path, fio::Flags::empty())
.expect("failed to open device");
fvm::format_for_fvm(&dir, BENCHMARK_FVM_SLICE_SIZE_BYTES).expect("Failed to format FVM");
let fvm = fvm::start_fvm_driver(&fvm_controller, &dir).await.expect("Failed to start FVM");
Some(fvm)
}
impl FvmVolumeFactory {
pub async fn new() -> Option<Self> {
let fvm = if let Some(fvm) = connect_to_system_fvm().await {
tracing::info!("Using system FVM");
fvm
} else if let Some(fvm) = connect_to_test_fvm().await {
tracing::info!("Using test FVM");
fvm
} else {
return None;
};
Some(Self { fvm })
}
}
#[async_trait]
impl BlockDeviceFactory for FvmVolumeFactory {
async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
Box::new(FvmVolume::new(&self.fvm, config).await)
}
}
pub struct FvmVolume {
volume: VolumeSynchronousProxy,
volume_dir: fio::DirectoryProxy,
volume_controller: ControllerProxy,
}
impl FvmVolume {
async fn new(fvm: &VolumeManagerProxy, config: &BlockDeviceConfig) -> Self {
let volume_dir = set_up_fvm_volume(fvm, config.fvm_volume_size).await;
let block = connect_to_named_protocol_at_dir_root::<BlockMarker>(&volume_dir, ".").unwrap();
let volume = VolumeSynchronousProxy::new(block.into_channel().unwrap().into());
let volume_dir = if config.use_zxcrypt {
zxcrypt::set_up_insecure_zxcrypt(&volume_dir).await.expect("Failed to set up zxcrypt")
} else {
volume_dir
};
let volume_controller = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
&volume_dir,
"device_controller",
)
.expect("failed to connect to the device controller");
Self { volume, volume_dir, volume_controller: volume_controller }
}
}
impl BlockDevice for FvmVolume {
fn dir(&self) -> &fio::DirectoryProxy {
&self.volume_dir
}
fn controller(&self) -> &ControllerProxy {
&self.volume_controller
}
}
impl Drop for FvmVolume {
fn drop(&mut self) {
let status = self
.volume
.destroy(zx::MonotonicInstant::INFINITE)
.expect("Failed to destroy the FVM volume");
zx::ok(status).expect("Failed to destroy the FVM volume");
}
}
async fn set_up_fvm_volume(
volume_manager: &VolumeManagerProxy,
volume_size: Option<u64>,
) -> fio::DirectoryProxy {
let instance_guid = create_random_guid();
fvm::create_fvm_volume(
volume_manager,
BENCHMARK_VOLUME_NAME,
BENCHMARK_TYPE_GUID,
&instance_guid,
volume_size,
ALLOCATE_PARTITION_FLAG_INACTIVE,
)
.await
.expect("Failed to create FVM volume");
let device_path = wait_for_block_device(&[
BlockDeviceMatcher::TypeGuid(BENCHMARK_TYPE_GUID),
BlockDeviceMatcher::InstanceGuid(&instance_guid),
BlockDeviceMatcher::Name(BENCHMARK_VOLUME_NAME),
])
.await
.expect("Failed to find the FVM volume");
let controller = connect_to_protocol_at_path::<ControllerMarker>(&format!(
"{}/device_controller",
device_path.to_str().unwrap()
))
.expect("failed to connect to controller");
let topo_path = controller
.get_topological_path()
.await
.expect("transport error on get_topological_path")
.expect("get_topological_path failed");
fuchsia_fs::directory::open_in_namespace(&topo_path, fuchsia_fs::Flags::empty())
.expect("failed to open device")
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_hardware_block_volume::VolumeMarker;
use fuchsia_runtime::vmar_root_self;
use ramdevice_client::RamdiskClientBuilder;
use test_util::assert_gt;
const BLOCK_SIZE: u64 = 4 * 1024;
const BLOCK_COUNT: u64 = 1024;
const GPT_BLOCK_COUNT: u64 = 49152;
#[fuchsia::test]
async fn ramdisk_create_block_device_with_zxcrypt() {
let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT).await;
let ramdisk = ramdisk_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: true, fvm_volume_size: None })
.await;
let path = ramdisk
.controller()
.get_topological_path()
.await
.expect("Failed to get topological path")
.map_err(zx::Status::from_raw)
.expect("Failed to get topological path");
assert!(path.contains("/zxcrypt/"), "block device path does not contain zxcrypt: {}", path);
}
#[fuchsia::test]
async fn ramdisk_create_block_device_without_zxcrypt() {
let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT).await;
let ramdisk = ramdisk_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: false, fvm_volume_size: None })
.await;
let path = ramdisk
.controller()
.get_topological_path()
.await
.expect("Failed to get topological path")
.map_err(zx::Status::from_raw)
.expect("Failed to get topological path");
assert!(
!path.contains("/zxcrypt/"),
"block device path should not contain zxcrypt: {}",
path
);
}
#[fuchsia::test]
async fn ramdisk_create_block_device_without_volume_size() {
let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT).await;
let ramdisk = ramdisk_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: false, fvm_volume_size: None })
.await;
let (volume, server) = fidl::endpoints::create_proxy::<VolumeMarker>();
let () = ramdisk
.controller()
.connect_to_device_fidl(server.into_channel())
.expect("failed to connect to device fidl");
let volume_info = volume.get_volume_info().await.unwrap();
zx::ok(volume_info.0).unwrap();
let volume_info = volume_info.2.unwrap();
assert_eq!(volume_info.partition_slice_count, 1);
}
#[fuchsia::test]
async fn ramdisk_create_block_device_with_volume_size() {
let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT).await;
let ramdisk = ramdisk_factory
.create_block_device(&BlockDeviceConfig {
use_zxcrypt: false,
fvm_volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
})
.await;
let (volume, server) = fidl::endpoints::create_proxy::<VolumeMarker>();
let () = ramdisk
.controller()
.connect_to_device_fidl(server.into_channel())
.expect("failed to connect to device fidl");
let volume_info = volume.get_volume_info().await.unwrap();
zx::ok(volume_info.0).unwrap();
let volume_info = volume_info.2.unwrap();
assert_eq!(volume_info.partition_slice_count, 3);
}
#[derive(PartialEq)]
enum TestMode {
Gpt,
Fvm,
}
async fn init_ramdisk_for_fvm_volume_factory(mode: TestMode) -> RamdiskClient {
if mode == TestMode::Gpt {
let vmo = {
let size = (BLOCK_SIZE * GPT_BLOCK_COUNT) as usize;
let vmo = zx::Vmo::create(size as u64).unwrap();
let flags = zx::VmarFlags::PERM_READ
| zx::VmarFlags::PERM_WRITE
| zx::VmarFlags::REQUIRE_NON_RESIZABLE;
let addr = vmar_root_self().map(0, &vmo, 0, size, flags).unwrap();
assert!(flags.contains(zx::VmarFlags::REQUIRE_NON_RESIZABLE));
let buffer = unsafe { std::slice::from_raw_parts_mut(addr as *mut u8, size) };
let vmo_wrapper = Box::new(std::io::Cursor::new(buffer));
let mut disk = gpt::GptConfig::new()
.initialized(false)
.writable(true)
.logical_block_size(
BLOCK_SIZE.try_into().expect("Unsupported logical block size"),
)
.create_from_device(vmo_wrapper, None)
.unwrap();
disk.update_partitions(std::collections::BTreeMap::new())
.expect("Failed to init partitions");
disk.write().expect("Failed to write GPT");
vmo
};
let ramdisk_client = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE))
.build()
.await
.expect("Failed to create ramdisk");
ramdisk_client
.as_controller()
.expect("invalid controller")
.bind("gpt.cm")
.await
.expect("FIDL error calling bind()")
.map_err(zx::Status::from_raw)
.expect("bind() returned non-Ok status");
wait_for_block_device(&[BlockDeviceMatcher::ContentsMatch(DiskFormat::Gpt)])
.await
.expect("Failed to wait for GPT to appear");
ramdisk_client
} else {
let ramdisk_client = RamdiskClient::create(BLOCK_SIZE, BLOCK_COUNT)
.await
.expect("Failed to create RamdiskClient");
let volume_manager = fvm::set_up_fvm(
ramdisk_client.as_controller().expect("invalid controller"),
ramdisk_client.as_dir().expect("invalid directory proxy"),
RAMDISK_FVM_SLICE_SIZE,
)
.await
.expect("Failed to set up FVM");
fvm::create_fvm_volume(
&volume_manager,
BLOBFS_VOLUME_NAME,
&BLOBFS_TYPE_GUID,
&create_random_guid(),
None,
ALLOCATE_PARTITION_FLAG_INACTIVE,
)
.await
.expect("Failed to create blobfs");
wait_for_block_device(&[
BlockDeviceMatcher::Name(BLOBFS_VOLUME_NAME),
BlockDeviceMatcher::TypeGuid(&BLOBFS_TYPE_GUID),
])
.await
.expect("Failed to wait for blobfs to appear");
ramdisk_client
}
}
async fn fvm_volume_factory_can_find_fvm_instance(mode: TestMode) {
let _ramdisk = init_ramdisk_for_fvm_volume_factory(mode).await;
let volume_factory = FvmVolumeFactory::new().await.unwrap();
volume_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: false, fvm_volume_size: None })
.await;
}
#[fuchsia::test]
async fn fvm_volume_factory_can_find_fvm_instance_with_system_fvm() {
fvm_volume_factory_can_find_fvm_instance(TestMode::Fvm).await;
}
#[fuchsia::test]
async fn fvm_volume_factory_can_find_fvm_instance_without_system_fvm() {
fvm_volume_factory_can_find_fvm_instance(TestMode::Gpt).await;
}
async fn get_fvm_used_slices(fvm: &VolumeManagerProxy) -> u64 {
let info = fvm.get_info().await.unwrap();
zx::ok(info.0).unwrap();
let info = info.1.unwrap();
info.assigned_slice_count
}
async fn dropping_an_fvm_volume_removes_the_volume(mode: TestMode) {
let _ramdisk = init_ramdisk_for_fvm_volume_factory(mode).await;
let volume_factory = FvmVolumeFactory::new().await.unwrap();
let used_slices = get_fvm_used_slices(&volume_factory.fvm).await;
let volume = volume_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: false, fvm_volume_size: None })
.await;
assert_gt!(get_fvm_used_slices(&volume_factory.fvm).await, used_slices);
std::mem::drop(volume);
assert_eq!(get_fvm_used_slices(&volume_factory.fvm).await, used_slices);
}
#[fuchsia::test]
async fn dropping_an_fvm_volume_removes_the_volume_with_system_fvm() {
dropping_an_fvm_volume_removes_the_volume(TestMode::Fvm).await;
}
#[fuchsia::test]
async fn dropping_an_fvm_volume_removes_the_volume_without_system_fvm() {
dropping_an_fvm_volume_removes_the_volume(TestMode::Gpt).await;
}
async fn fvm_volume_factory_create_block_device_with_zxcrypt(mode: TestMode) {
let _ramdisk = init_ramdisk_for_fvm_volume_factory(mode).await;
let volume_factory = FvmVolumeFactory::new().await.unwrap();
let volume = volume_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: true, fvm_volume_size: None })
.await;
let path = volume
.controller()
.get_topological_path()
.await
.expect("Failed to get topological path")
.map_err(zx::Status::from_raw)
.expect("Failed to get topological path");
assert!(path.contains("/zxcrypt/"), "block device path does not contain zxcrypt: {}", path);
}
#[fuchsia::test]
async fn fvm_volume_factory_create_block_device_with_zxcrypt_with_fvm() {
fvm_volume_factory_create_block_device_with_zxcrypt(TestMode::Fvm).await;
}
#[fuchsia::test]
async fn fvm_volume_factory_create_block_device_with_zxcrypt_without_fvm() {
fvm_volume_factory_create_block_device_with_zxcrypt(TestMode::Gpt).await;
}
async fn fvm_volume_factory_create_block_device_without_zxcrypt(mode: TestMode) {
let _ramdisk = init_ramdisk_for_fvm_volume_factory(mode).await;
let volume_factory = FvmVolumeFactory::new().await.unwrap();
let volume = volume_factory
.create_block_device(&BlockDeviceConfig { use_zxcrypt: false, fvm_volume_size: None })
.await;
let path = volume
.controller()
.get_topological_path()
.await
.expect("Failed to get topological path")
.map_err(zx::Status::from_raw)
.expect("Failed to get topological path");
assert!(
!path.contains("/zxcrypt/"),
"block device path should not contain zxcrypt: {}",
path
);
}
#[fuchsia::test]
async fn fvm_volume_factory_create_block_device_without_zxcrypt_with_system_fvm() {
fvm_volume_factory_create_block_device_without_zxcrypt(TestMode::Fvm).await;
}
#[fuchsia::test]
async fn fvm_volume_factory_create_block_device_without_zxcrypt_without_system_fvm() {
fvm_volume_factory_create_block_device_without_zxcrypt(TestMode::Gpt).await;
}
}