| // 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. |
| |
| //! Test cases which simulate fshost running in the configuration used in recovery builds (which, |
| //! among other things, sets the ramdisk_image flag to prevent binding of the on-disk filesystems.) |
| |
| use { |
| crate::{blob_fs_type, data_fs_spec, data_fs_type, new_builder, volumes_spec, VolumesSpec}, |
| device_watcher::recursive_wait, |
| fidl::endpoints::{create_proxy, Proxy as _}, |
| fidl_fuchsia_fshost as fshost, |
| fidl_fuchsia_hardware_block::BlockProxy, |
| fidl_fuchsia_hardware_block_partition::PartitionMarker, |
| fidl_fuchsia_io as fio, |
| fs_management::partition::{find_partition_in, PartitionMatcher}, |
| fshost_test_fixture::{write_test_blob, write_test_blob_fxblob}, |
| fuchsia_zircon as zx, |
| remote_block_device::{BlockClient, MutableBufferSlice, RemoteBlockClient}, |
| }; |
| |
| const TEST_BLOB_DATA: [u8; 8192] = [0xFF; 8192]; |
| // TODO(https://fxbug.dev/42072287): Remove hardcoded paths |
| const GPT_PATH: &'static str = "/part-000/block"; |
| const BLOBFS_FVM_PATH: &'static str = "/part-000/block/fvm/blobfs-p-1/block"; |
| const DATA_FVM_PATH: &'static str = "/part-000/block/fvm/data-p-2/block"; |
| |
| // Ensure fuchsia.fshost.Admin/WipeStorage fails if we cannot identify a storage device to wipe. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| #[fuchsia::test] |
| #[cfg_attr(feature = "f2fs", ignore)] |
| async fn no_fvm_device() { |
| let mut builder = new_builder(); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (_, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| let result = admin |
| .wipe_storage(Some(blobfs_server), None) |
| .await |
| .expect("FIDL call to WipeStorage failed") |
| .expect_err("WipeStorage unexpectedly succeeded"); |
| assert_eq!(zx::Status::from_raw(result), zx::Status::INTERNAL); |
| fixture.tear_down().await; |
| } |
| |
| // Demonstrate high level usage of the fuchsia.fshost.Admin/WipeStorage method. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| #[fuchsia::test] |
| #[cfg_attr(feature = "f2fs", ignore)] |
| async fn write_blob() { |
| let mut builder = new_builder(); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| // We need to use a GPT as WipeStorage relies on the reported partition type GUID, rather than |
| // content sniffing of the FVM magic. |
| builder.with_disk().format_volumes(volumes_spec()).format_data(data_fs_spec()).with_gpt(); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| // Wait for the zbi ramdisk filesystems |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| // Also wait for any driver binding on the "on-disk" devices |
| let ramdisk_dir = |
| fixture.ramdisks.first().expect("no ramdisks?").as_dir().expect("invalid dir proxy"); |
| if cfg!(feature = "fxblob") { |
| recursive_wait(ramdisk_dir, GPT_PATH).await.unwrap(); |
| } else { |
| recursive_wait(ramdisk_dir, BLOBFS_FVM_PATH).await.unwrap(); |
| recursive_wait(ramdisk_dir, DATA_FVM_PATH).await.unwrap(); |
| } |
| |
| let (blob_creator_proxy, blob_creator) = if cfg!(feature = "fxblob") { |
| let (proxy, server_end) = fidl::endpoints::create_proxy().unwrap(); |
| (Some(proxy), Some(server_end)) |
| } else { |
| (None, None) |
| }; |
| |
| // Invoke WipeStorage, which will unbind the FVM, reprovision it, and format/mount Blobfs. |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (blobfs_root, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| admin |
| .wipe_storage(Some(blobfs_server), blob_creator) |
| .await |
| .unwrap() |
| .expect("WipeStorage unexpectedly failed"); |
| |
| // Ensure that we can write a blob into the new Blobfs instance. |
| if cfg!(feature = "fxblob") { |
| write_test_blob_fxblob(blob_creator_proxy.unwrap(), &TEST_BLOB_DATA).await; |
| } else { |
| write_test_blob(&blobfs_root, &TEST_BLOB_DATA, false).await; |
| } |
| |
| fixture.tear_down().await; |
| } |
| |
| // Demonstrate high level usage of the fuchsia.fshost.Admin/WipeStorage method when a data |
| // data partition does not already exist. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| #[fuchsia::test] |
| #[cfg_attr(feature = "f2fs", ignore)] |
| async fn write_blob_no_existing_data_partition() { |
| let mut builder = new_builder(); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| // We need to use a GPT as WipeStorage relies on the reported partition type GUID, rather than |
| // content sniffing of the FVM magic. |
| builder |
| .with_disk() |
| .format_volumes(VolumesSpec { create_data_partition: false, ..volumes_spec() }) |
| .with_gpt(); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| // Wait for the zbi ramdisk filesystems |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| // Also wait for any driver binding on the "on-disk" devices |
| let ramdisk_dir = |
| fixture.ramdisks.first().expect("no ramdisks?").as_dir().expect("invalid dir proxy"); |
| if cfg!(feature = "fxblob") { |
| recursive_wait(ramdisk_dir, GPT_PATH).await.unwrap(); |
| } else { |
| recursive_wait(ramdisk_dir, BLOBFS_FVM_PATH).await.unwrap(); |
| } |
| |
| let (blob_creator_proxy, blob_creator) = if cfg!(feature = "fxblob") { |
| let (proxy, server_end) = fidl::endpoints::create_proxy().unwrap(); |
| (Some(proxy), Some(server_end)) |
| } else { |
| (None, None) |
| }; |
| |
| // Invoke WipeStorage, which will unbind the FVM, reprovision it, and format/mount Blobfs. |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (blobfs_root, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| admin |
| .wipe_storage(Some(blobfs_server), blob_creator) |
| .await |
| .unwrap() |
| .expect("WipeStorage unexpectedly failed"); |
| |
| // Ensure that we can write a blob into the new Blobfs instance. |
| if cfg!(feature = "fxblob") { |
| write_test_blob_fxblob(blob_creator_proxy.unwrap(), &TEST_BLOB_DATA).await; |
| } else { |
| write_test_blob(&blobfs_root, &TEST_BLOB_DATA, false).await; |
| } |
| |
| fixture.tear_down().await; |
| } |
| |
| // Verify that all existing blobs are purged after running fuchsia.fshost.Admin/WipeStorage. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| #[fuchsia::test] |
| #[cfg_attr(feature = "f2fs", ignore)] |
| async fn blobfs_formatted() { |
| let mut builder = new_builder(); |
| builder.with_disk().format_volumes(volumes_spec()).format_data(data_fs_spec()).with_gpt(); |
| |
| let fixture = builder.build().await; |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| |
| // The test fixture writes tests blobs to blobfs or fxblob when it is formatted. |
| fixture.check_test_blob(cfg!(feature = "fxblob")).await; |
| |
| let vmo = fixture.into_vmo().await.unwrap(); |
| |
| let mut builder = new_builder().with_disk_from_vmo(vmo); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| // Wait for the zbi ramdisk filesystems |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| // Also wait for any driver binding on the "on-disk" devices |
| let ramdisk_dir = |
| fixture.ramdisks.first().expect("no ramdisks?").as_dir().expect("invalid dir proxy"); |
| if cfg!(feature = "fxblob") { |
| recursive_wait(ramdisk_dir, GPT_PATH).await.unwrap(); |
| } else { |
| recursive_wait(ramdisk_dir, BLOBFS_FVM_PATH).await.unwrap(); |
| recursive_wait(ramdisk_dir, DATA_FVM_PATH).await.unwrap(); |
| } |
| |
| let blob_creator = if cfg!(feature = "fxblob") { |
| let (_, server_end) = fidl::endpoints::create_proxy().unwrap(); |
| Some(server_end) |
| } else { |
| None |
| }; |
| |
| // Invoke the WipeStorage API. |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (blobfs_root, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| admin |
| .wipe_storage(Some(blobfs_server), blob_creator) |
| .await |
| .unwrap() |
| .map_err(zx::Status::from_raw) |
| .expect("WipeStorage unexpectedly failed"); |
| |
| // Verify there are no blobs. |
| assert!(fuchsia_fs::directory::readdir(&blobfs_root).await.unwrap().is_empty()); |
| |
| fixture.tear_down().await; |
| } |
| |
| // Verify that the data partition is wiped and remains unformatted. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| // This test is very specific to fvm, so we don't run it against fxblob. Since both volumes are in |
| // fxfs anyway with fxblob, this test is somewhat redundant with the basic tests. |
| #[fuchsia::test] |
| #[cfg_attr(any(feature = "f2fs", feature = "fxblob"), ignore)] |
| async fn data_unformatted() { |
| const BUFF_LEN: usize = 512; |
| let mut builder = new_builder(); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| builder.with_disk().format_volumes(volumes_spec()).format_data(data_fs_spec()).with_gpt(); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| // Wait for the zbi ramdisk filesystems |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| // Also wait for any driver binding on the "on-disk" devices |
| let ramdisk_dir = |
| fixture.ramdisks.first().expect("no ramdisks?").as_dir().expect("invalid dir proxy"); |
| recursive_wait(ramdisk_dir, BLOBFS_FVM_PATH).await.unwrap(); |
| recursive_wait(ramdisk_dir, DATA_FVM_PATH).await.unwrap(); |
| |
| let test_disk = fixture.ramdisks.first().unwrap(); |
| let test_disk_path = test_disk |
| .as_controller() |
| .expect("ramdisk didn't have controller proxy") |
| .get_topological_path() |
| .await |
| .expect("get topo path fidl failed") |
| .expect("get topo path returned error"); |
| let dev_class = fixture.dir("dev-topological/class/block", fio::OpenFlags::empty()); |
| let matcher = PartitionMatcher { |
| parent_device: Some(test_disk_path), |
| labels: Some(vec!["data".to_string()]), |
| ..Default::default() |
| }; |
| |
| let orig_instance_guid; |
| { |
| let data_controller = |
| find_partition_in(&dev_class, matcher.clone(), zx::Duration::INFINITE).await.unwrap(); |
| let (data_partition, partition_server_end) = |
| fidl::endpoints::create_proxy::<PartitionMarker>().unwrap(); |
| data_controller.connect_to_device_fidl(partition_server_end.into_channel()).unwrap(); |
| |
| let (status, guid) = data_partition.get_instance_guid().await.unwrap(); |
| assert_eq!(zx::Status::from_raw(status), zx::Status::OK); |
| orig_instance_guid = guid.unwrap(); |
| |
| let block_client = RemoteBlockClient::new(BlockProxy::from_channel( |
| data_partition.into_channel().unwrap(), |
| )) |
| .await |
| .unwrap(); |
| let mut buff: [u8; BUFF_LEN] = [0; BUFF_LEN]; |
| block_client.read_at(MutableBufferSlice::Memory(&mut buff), 0).await.unwrap(); |
| // The data partition should have been formatted so there should be some non-zero bytes. |
| assert_ne!(buff, [0; BUFF_LEN]); |
| } |
| |
| // Invoke WipeStorage. |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (_, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| admin |
| .wipe_storage(Some(blobfs_server), None) |
| .await |
| .unwrap() |
| .expect("WipeStorage unexpectedly failed"); |
| |
| // Ensure the data partition was assigned a new instance GUID. |
| let data_controller = |
| find_partition_in(&dev_class, matcher, zx::Duration::INFINITE).await.unwrap(); |
| let (data_partition, partition_server_end) = |
| fidl::endpoints::create_proxy::<PartitionMarker>().unwrap(); |
| data_controller.connect_to_device_fidl(partition_server_end.into_channel()).unwrap(); |
| let (status, guid) = data_partition.get_instance_guid().await.unwrap(); |
| assert_eq!(zx::Status::from_raw(status), zx::Status::OK); |
| assert_ne!(guid.unwrap(), orig_instance_guid); |
| |
| // The data partition should remain unformatted, so the first few bytes should be all zero now. |
| let block_client = |
| RemoteBlockClient::new(BlockProxy::from_channel(data_partition.into_channel().unwrap())) |
| .await |
| .unwrap(); |
| let mut buff: [u8; BUFF_LEN] = [0; BUFF_LEN]; |
| block_client.read_at(MutableBufferSlice::Memory(&mut buff), 0).await.unwrap(); |
| assert_eq!(buff, [0; BUFF_LEN]); |
| |
| fixture.tear_down().await; |
| } |
| |
| // Verify that WipeStorage can handle a completely corrupted FVM. |
| // TODO(https://fxbug.dev/42065222): this test doesn't work on f2fs. |
| #[fuchsia::test] |
| #[cfg_attr(feature = "f2fs", ignore)] |
| async fn handles_corrupt_fvm() { |
| let mut builder = new_builder(); |
| builder.fshost().set_config_value("ramdisk_image", true); |
| // Ensure that, while we allocate an FVM or Fxfs partition inside the GPT, we leave it empty. |
| builder.with_disk().format_volumes(volumes_spec()).with_gpt().with_unformatted_volume_manager(); |
| builder.with_zbi_ramdisk().format_volumes(volumes_spec()); |
| |
| let fixture = builder.build().await; |
| // Wait for the zbi ramdisk filesystems |
| fixture.check_fs_type("blob", blob_fs_type()).await; |
| fixture.check_fs_type("data", data_fs_type()).await; |
| // Also wait for any driver binding on the "on-disk" devices |
| let ramdisk_dir = |
| fixture.ramdisks.first().expect("no ramdisks?").as_dir().expect("invalid dir proxy"); |
| recursive_wait(ramdisk_dir, GPT_PATH).await.unwrap(); |
| |
| let (blob_creator_proxy, blob_creator) = if cfg!(feature = "fxblob") { |
| let (proxy, server_end) = fidl::endpoints::create_proxy().unwrap(); |
| (Some(proxy), Some(server_end)) |
| } else { |
| (None, None) |
| }; |
| |
| // Invoke WipeStorage, which will unbind the FVM, reprovision it, and format/mount Blobfs. |
| let admin = |
| fixture.realm.root.connect_to_protocol_at_exposed_dir::<fshost::AdminMarker>().unwrap(); |
| let (blobfs_root, blobfs_server) = create_proxy::<fio::DirectoryMarker>().unwrap(); |
| admin |
| .wipe_storage(Some(blobfs_server), blob_creator) |
| .await |
| .unwrap() |
| .expect("WipeStorage unexpectedly failed"); |
| |
| // Ensure that we can write a blob into the new Blobfs instance. |
| if cfg!(feature = "fxblob") { |
| write_test_blob_fxblob(blob_creator_proxy.unwrap(), &TEST_BLOB_DATA).await; |
| } else { |
| write_test_blob(&blobfs_root, &TEST_BLOB_DATA, false).await; |
| } |
| |
| fixture.tear_down().await; |
| } |