| // Copyright 2020 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::io::Directory, |
| fidl_fuchsia_device::{ControllerMarker, ControllerProxy}, |
| fidl_fuchsia_hardware_block_partition::Guid, |
| fidl_fuchsia_hardware_block_volume::{ |
| VolumeManagerMarker, VolumeManagerProxy, VolumeMarker, VolumeProxy, |
| }, |
| fidl_fuchsia_io::OPEN_RIGHT_READABLE, |
| fuchsia_component::client::connect_to_service_at_path, |
| fuchsia_zircon::{sys::zx_status_t, AsHandleRef, Rights, Status, Vmo}, |
| isolated_driver_manager::{bind_fvm, rebind_fvm}, |
| ramdevice_client::{RamdiskClient, VmoRamdiskClientBuilder}, |
| rand::{rngs::SmallRng, FromEntropy, Rng}, |
| std::{ |
| fs::OpenOptions, |
| os::{raw::c_int, unix::io::AsRawFd}, |
| path::PathBuf, |
| time::Duration, |
| }, |
| test_utils_lib::{ |
| events::{Event, EventMode, EventSubscription, Started}, |
| matcher::EventMatcher, |
| opaque_test::OpaqueTest, |
| }, |
| }; |
| |
| #[link(name = "fs-management")] |
| extern "C" { |
| // This function initializes FVM on a fuchsia.hardware.block.Block device |
| // with a given slice size. |
| pub fn fvm_init(fd: c_int, slice_size: usize) -> zx_status_t; |
| } |
| |
| async fn start_test() -> OpaqueTest { |
| let test: OpaqueTest = |
| OpaqueTest::default("fuchsia-pkg://fuchsia.com/isolated-devmgr#meta/isolated-devmgr.cm") |
| .await |
| .unwrap(); |
| |
| // Wait for the root component to start |
| let event_source = test.connect_to_event_source().await.unwrap(); |
| let mut started_event_stream = event_source |
| .subscribe(vec![EventSubscription::new(vec![Started::NAME], EventMode::Sync)]) |
| .await |
| .unwrap(); |
| event_source.start_component_tree().await; |
| EventMatcher::ok().moniker(".").expect_match::<Started>(&mut started_event_stream).await; |
| |
| test |
| } |
| |
| fn create_ramdisk(test: &OpaqueTest, vmo: &Vmo, ramdisk_block_size: u64) -> RamdiskClient { |
| // Wait until the ramctl driver is available |
| let dev_path = test.get_hub_v2_path().join("exec/expose/dev"); |
| let ramctl_path = dev_path.join("misc/ramctl"); |
| let ramctl_path = ramctl_path.to_str().unwrap(); |
| ramdevice_client::wait_for_device(ramctl_path, Duration::from_secs(20)).unwrap(); |
| |
| let duplicated_handle = vmo.as_handle_ref().duplicate(Rights::SAME_RIGHTS).unwrap(); |
| let duplicated_vmo = Vmo::from(duplicated_handle); |
| |
| // Create the ramdisks |
| let dev_root = OpenOptions::new().read(true).write(true).open(&dev_path).unwrap(); |
| VmoRamdiskClientBuilder::new(duplicated_vmo) |
| .block_size(ramdisk_block_size) |
| .dev_root(dev_root) |
| .build() |
| .unwrap() |
| } |
| |
| fn init_fvm(ramdisk_path: &str, fvm_slice_size: u64) { |
| // Create the FVM filesystem |
| let ramdisk_file = OpenOptions::new().read(true).write(true).open(ramdisk_path).unwrap(); |
| let ramdisk_fd = ramdisk_file.as_raw_fd(); |
| let status = unsafe { fvm_init(ramdisk_fd, fvm_slice_size as usize) }; |
| Status::ok(status).unwrap(); |
| } |
| |
| async fn start_fvm_driver(ramdisk_path: &str) -> (ControllerProxy, VolumeManagerProxy) { |
| let controller = connect_to_service_at_path::<ControllerMarker>(ramdisk_path).unwrap(); |
| bind_fvm(&controller).await.unwrap(); |
| |
| // Wait until the FVM driver is available |
| let fvm_path = PathBuf::from(ramdisk_path).join("fvm"); |
| let fvm_path = fvm_path.to_str().unwrap(); |
| ramdevice_client::wait_for_device(fvm_path, Duration::from_secs(20)).unwrap(); |
| |
| // Connect to the Volume Manager |
| let proxy = connect_to_service_at_path::<VolumeManagerMarker>(fvm_path).unwrap(); |
| (controller, proxy) |
| } |
| |
| async fn does_guid_match(volume_proxy: &VolumeProxy, expected_instance_guid: &Guid) -> bool { |
| // The GUIDs must match |
| let (status, actual_guid_instance) = volume_proxy.get_instance_guid().await.unwrap(); |
| |
| // The ramdisk is also a block device, but does not support the Volume protocol |
| if let Err(Status::NOT_SUPPORTED) = Status::ok(status) { |
| return false; |
| } |
| |
| let actual_guid_instance = actual_guid_instance.unwrap(); |
| *actual_guid_instance == *expected_instance_guid |
| } |
| |
| /// This structs holds processes of component manager, isolated-devmgr |
| /// and the fvm driver. |
| /// |
| /// NOTE: The order of fields in this struct is important. |
| /// Destruction happens top-down. Test must be destroyed last. |
| pub struct FvmInstance { |
| /// A proxy to fuchsia.hardware.block.VolumeManager protocol |
| /// Used to create new FVM volumes |
| volume_manager: VolumeManagerProxy, |
| |
| /// A proxy to fuchsia.device.Controller protocol |
| /// Used to bind/rebind the FVM driver to the ramdisk device |
| controller: ControllerProxy, |
| |
| /// Manages the ramdisk device that is backed by a VMO |
| ramdisk: RamdiskClient, |
| |
| /// The component manager process that runs isolated-devmgr |
| test: OpaqueTest, |
| } |
| |
| impl FvmInstance { |
| /// Kill the test's component manager process. |
| /// This should take down the entire test's component tree with it, |
| /// including the driver manager and ramdisk + fvm drivers. |
| pub fn kill_component_manager(mut self) { |
| self.test.component_manager_app.kill().unwrap(); |
| |
| // We do not want the ramdisk to be destroyed cleanly. |
| // Forget the ramdisk struct. |
| std::mem::forget(self.ramdisk); |
| } |
| |
| /// Force rebind the FVM driver. This is similar to a device disconnect/reconnect. |
| pub async fn rebind_fvm_driver(&mut self) { |
| rebind_fvm(&self.controller).await.unwrap(); |
| } |
| |
| /// Start an isolated FVM driver against the given VMO. |
| /// If `init` is true, initialize the VMO with FVM layout first. |
| pub async fn new(init: bool, vmo: &Vmo, fvm_slice_size: u64, ramdisk_block_size: u64) -> Self { |
| let test = start_test().await; |
| let ramdisk = create_ramdisk(&test, &vmo, ramdisk_block_size); |
| |
| let dev_path = test.get_hub_v2_path().join("exec/expose/dev"); |
| let ramdisk_path = dev_path.join(ramdisk.get_path()); |
| let ramdisk_path = ramdisk_path.to_str().unwrap(); |
| |
| if init { |
| init_fvm(ramdisk_path, fvm_slice_size); |
| } |
| |
| let (controller, volume_manager) = start_fvm_driver(ramdisk_path).await; |
| |
| Self { test, controller, ramdisk, volume_manager } |
| } |
| |
| /// Get the full path to /dev/class/block from the devmgr running in this test |
| pub fn block_path(&self) -> PathBuf { |
| self.test.get_hub_v2_path().join("exec/expose/dev/class/block") |
| } |
| |
| /// Create a new FVM volume with the given name and type GUID. This volume will consume |
| /// exactly 1 slice. Returns the instance GUID used to uniquely identify this volume. |
| pub async fn new_volume(&mut self, name: &str, mut type_guid: Guid) -> Guid { |
| // Generate a random instance GUID |
| let mut rng = SmallRng::from_entropy(); |
| let mut instance_guid = Guid { value: rng.gen() }; |
| |
| // Create the new volume |
| let status = self |
| .volume_manager |
| .allocate_partition(1, &mut type_guid, &mut instance_guid, name, 0) |
| .await |
| .unwrap(); |
| Status::ok(status).unwrap(); |
| |
| instance_guid |
| } |
| |
| /// Get the full path to a volume in this test that matches the given instance GUID. |
| /// This function will wait until a matching volume is found. |
| pub async fn get_volume_path(&self, instance_guid: &Guid) -> PathBuf { |
| get_volume_path(self.block_path(), instance_guid).await |
| } |
| } |
| |
| /// Gets the full path to a volume matching the given instance GUID at the given |
| /// /dev/class/block path. This function will wait until a matching volume is found. |
| pub async fn get_volume_path(block_path: PathBuf, instance_guid: &Guid) -> PathBuf { |
| let dir = Directory::from_namespace(block_path.clone(), OPEN_RIGHT_READABLE).unwrap(); |
| loop { |
| // TODO(xbhatnag): Find a better way to wait for the volume to appear |
| for entry in dir.entries().await.unwrap() { |
| let volume_path = block_path.join(entry); |
| let volume_path_str = volume_path.to_str().unwrap(); |
| |
| // Connect to the Volume FIDL protocol |
| let volume_proxy = connect_to_service_at_path::<VolumeMarker>(volume_path_str).unwrap(); |
| if does_guid_match(&volume_proxy, &instance_guid).await { |
| return volume_path; |
| } |
| } |
| } |
| } |