| // 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. |
| |
| #include "src/storage/fshost/block-device-manager.h" |
| |
| #include <fcntl.h> |
| #include <fidl/fuchsia.hardware.block.volume/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/service/llcpp/service.h> |
| #include <sys/statfs.h> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/storage/block_client/cpp/remote_block_device.h" |
| #include "src/storage/blobfs/fsck.h" |
| #include "src/storage/blobfs/mkfs.h" |
| #include "src/storage/fshost/config.h" |
| #include "src/storage/fshost/constants.h" |
| #include "src/storage/fshost/filesystem-mounter.h" |
| #include "src/storage/fshost/fs-manager.h" |
| #include "src/storage/fshost/testing/fshost_integration_test.h" |
| #include "src/storage/fshost/testing/mock-block-device.h" |
| #include "src/storage/testing/fvm.h" |
| #include "src/storage/testing/ram_disk.h" |
| |
| namespace fshost { |
| namespace { |
| |
| namespace volume = fuchsia_hardware_block_volume; |
| |
| using ::fshost::testing::MockBlobfsDevice; |
| using ::fshost::testing::MockBlockDevice; |
| using ::fshost::testing::MockMinfsDevice; |
| using ::fshost::testing::MockZxcryptDevice; |
| using ::testing::ContainerEq; |
| |
| // For tests that want the full integration test suite. |
| using BlockDeviceManagerIntegration = testing::FshostIntegrationTest; |
| |
| TEST(BlockDeviceManager, BlobfsLimit) { |
| auto config = DefaultConfig(); |
| config.blobfs_max_bytes() = 7654321; |
| BlockDeviceManager manager(&config); |
| |
| // When there's no FVM we expect no match and no max size call. |
| MockBlobfsDevice blobfs_device; |
| manager.AddDevice(blobfs_device); |
| ASSERT_FALSE(blobfs_device.max_size()); |
| |
| // Add FVM and re-try. This should call the limit set function. |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_EQ(manager.AddDevice(fvm_device), ZX_OK); |
| manager.AddDevice(blobfs_device); |
| ASSERT_TRUE(blobfs_device.max_size()); |
| EXPECT_EQ(7654321u, *blobfs_device.max_size()); |
| |
| // Make a blobfs that looks like it's in a ramdisk, the limit should not be set. |
| MockBlockDevice::Options ramdisk_opts = MockBlobfsDevice::BlobfsOptions(); |
| ramdisk_opts.topological_path = |
| "/dev/sys/platform/00:00:2d/ramctl" + ramdisk_opts.topological_path; |
| MockBlockDevice ramdisk_blobfs(ramdisk_opts); |
| manager.AddDevice(ramdisk_blobfs); |
| ASSERT_FALSE(ramdisk_blobfs.max_size()); |
| } |
| |
| TEST(BlockDeviceManager, MinfsLimit) { |
| auto config = DefaultConfig(); |
| config.data_max_bytes() = 7654321; |
| BlockDeviceManager manager(&config); |
| |
| MockBlockDevice fvm_device(MockBlockDevice::FvmOptions()); |
| EXPECT_EQ(manager.AddDevice(fvm_device), ZX_OK); |
| |
| MockBlockDevice::Options device_options = MockZxcryptDevice::ZxcryptOptions(); |
| device_options.content_format = fs_management::kDiskFormatUnknown; |
| MockZxcryptDevice zxcrypt_device(device_options); |
| EXPECT_EQ(manager.AddDevice(zxcrypt_device), ZX_OK); |
| |
| MockMinfsDevice minfs_device; |
| EXPECT_EQ(manager.AddDevice(minfs_device), ZX_OK); |
| ASSERT_TRUE(minfs_device.max_size()); |
| EXPECT_EQ(7654321u, *minfs_device.max_size()); |
| } |
| |
| // The component for the fshost integration test sets the fshost config: |
| // minfs_maximum_runtime_bytes = 32768 |
| // which in turn sets the fshost variable kMinfsMaxBytes. This test is checking that this setting |
| // actually was sent to fshost and applies to FVM. |
| TEST_F(BlockDeviceManagerIntegration, MaxSize) { |
| constexpr uint32_t kBlockCount = 9 * 1024 * 256; |
| constexpr uint32_t kBlockSize = 512; |
| constexpr uint32_t kSliceSize = 32'768; |
| constexpr size_t kDeviceSize = kBlockCount * kBlockSize; |
| |
| PauseWatcher(); // Pause whilst we create a ramdisk. |
| |
| // Create a ramdisk with an unformatted minfs partitition. |
| zx::vmo vmo; |
| ASSERT_EQ(zx::vmo::create(kDeviceSize, 0, &vmo), ZX_OK); |
| |
| // Create a child VMO so that we can keep hold of the original. |
| zx::vmo child_vmo; |
| ASSERT_EQ(vmo.create_child(ZX_VMO_CHILD_SLICE, 0, kDeviceSize, &child_vmo), ZX_OK); |
| |
| // Now create the ram-disk with a single FVM partition. |
| { |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(child_vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| storage::FvmOptions options{ |
| .name = kDataPartitionLabel, |
| .type = std::array<uint8_t, BLOCK_GUID_LEN>{GUID_DATA_VALUE}, |
| }; |
| auto fvm_partition_or = storage::CreateFvmPartition(ramdisk_or->path(), kSliceSize, options); |
| ASSERT_EQ(fvm_partition_or.status_value(), ZX_OK); |
| } |
| |
| ResumeWatcher(); |
| |
| // Now reattach the ram-disk and fshost should format it. |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| auto [fd, fs_type] = WaitForMount("minfs"); |
| ASSERT_TRUE(fd); |
| EXPECT_TRUE(fs_type == VFS_TYPE_MINFS || fs_type == VFS_TYPE_FXFS); |
| |
| // FVM will be at something like "/dev/sys/platform/00:00:2d/ramctl/ramdisk-1/block/fvm" |
| std::string fvm_path = ramdisk_or.value().path() + "/fvm"; |
| fbl::unique_fd fvm_fd(open(fvm_path.c_str(), O_RDONLY)); |
| ASSERT_TRUE(fvm_fd); |
| |
| // The minfs partition will be the only one inside FVM. |
| std::string partition_path = fvm_path + "/"; |
| partition_path.append(kDataPartitionLabel); |
| partition_path.append("-p-1/block"); |
| fbl::unique_fd partition_fd(open(partition_path.c_str(), O_RDONLY)); |
| ASSERT_TRUE(partition_fd); |
| |
| // Query the minfs partition instance guid. This is needed to query the limit later on. |
| fdio_cpp::UnownedFdioCaller partition_caller(partition_fd.get()); |
| auto guid_result = |
| fidl::WireCall(partition_caller.borrow_as<volume::Volume>())->GetInstanceGuid(); |
| ASSERT_EQ(ZX_OK, guid_result.status()); |
| ASSERT_EQ(ZX_OK, guid_result.value().status); |
| |
| // Query the partition limit for the minfs partition. |
| fdio_cpp::UnownedFdioCaller fvm_caller(fvm_fd.get()); |
| auto limit_result = fidl::WireCall(fvm_caller.borrow_as<volume::VolumeManager>()) |
| ->GetPartitionLimit(*guid_result.value().guid); |
| ASSERT_EQ(ZX_OK, limit_result.status()); |
| ASSERT_EQ(ZX_OK, limit_result.value().status); |
| |
| // The partition limit should match the value set in the integration test fshost configuration |
| // (see the BUILD.gn file). |
| constexpr uint64_t kMaxRuntimeBytes = 117440512u; |
| EXPECT_EQ(limit_result.value().slice_count, kMaxRuntimeBytes / kSliceSize); |
| } |
| |
| TEST_F(BlockDeviceManagerIntegration, MinfsPartitionsRenamedToPreferredName) { |
| constexpr uint32_t kBlockCount = 9 * 1024 * 256; |
| constexpr uint32_t kBlockSize = 512; |
| constexpr uint32_t kSliceSize = 32'768; |
| constexpr size_t kDeviceSize = kBlockCount * kBlockSize; |
| |
| if (DataFilesystemFormat() == "fxfs") { |
| // Fxfs partitions use a new matcher which does not have the logic to migrate legacy names. |
| return; |
| } |
| |
| PauseWatcher(); // Pause whilst we create a ramdisk. |
| |
| // Create a ramdisk with an unformatted minfs partitition. |
| zx::vmo vmo; |
| ASSERT_EQ(zx::vmo::create(kDeviceSize, 0, &vmo), ZX_OK); |
| |
| // Create a child VMO so that we can keep hold of the original. |
| zx::vmo child_vmo; |
| ASSERT_EQ(vmo.create_child(ZX_VMO_CHILD_SLICE, 0, kDeviceSize, &child_vmo), ZX_OK); |
| |
| // Now create the ram-disk with a single FVM partition. |
| { |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(child_vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| storage::FvmOptions options{ |
| .name = "minfs", // Use a legacy name |
| .type = std::array<uint8_t, BLOCK_GUID_LEN>{GUID_DATA_VALUE}, |
| }; |
| auto fvm_partition_or = storage::CreateFvmPartition(ramdisk_or->path(), kSliceSize, options); |
| ASSERT_EQ(fvm_partition_or.status_value(), ZX_OK); |
| } |
| |
| ResumeWatcher(); |
| |
| // Now reattach the ram-disk and fshost should format it. |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| auto [fd, fs_type] = WaitForMount("minfs"); |
| ASSERT_TRUE(fd); |
| EXPECT_TRUE(fs_type == VFS_TYPE_MINFS || fs_type == VFS_TYPE_FXFS); |
| |
| // FVM will be at something like "/dev/sys/platform/00:00:2d/ramctl/ramdisk-1/block/fvm" |
| std::string fvm_path = ramdisk_or.value().path() + "/fvm"; |
| fbl::unique_fd fvm_fd(open(fvm_path.c_str(), O_RDONLY)); |
| ASSERT_TRUE(fvm_fd); |
| |
| // The minfs partition will be the only one inside FVM. |
| std::string partition_path = fvm_path + "/minfs-p-1/block"; |
| fbl::unique_fd partition_fd(open(partition_path.c_str(), O_RDONLY)); |
| ASSERT_TRUE(partition_fd); |
| |
| // Query the partition name. |
| fdio_cpp::UnownedFdioCaller partition_caller(partition_fd.get()); |
| auto result = fidl::WireCall(partition_caller.borrow_as<volume::Volume>())->GetName(); |
| ASSERT_EQ(result.status(), ZX_OK); |
| ASSERT_EQ(result.value().status, ZX_OK); |
| |
| // It should be the preferred name. |
| ASSERT_EQ(result.value().name.get(), kDataPartitionLabel); |
| } |
| |
| TEST_F(BlockDeviceManagerIntegration, StartBlobfsComponent) { |
| constexpr uint32_t kBlockCount = 9 * 1024 * 256; |
| constexpr uint32_t kBlockSize = 512; |
| constexpr uint32_t kSliceSize = 32'768; |
| constexpr size_t kDeviceSize = kBlockCount * kBlockSize; |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread("blobfs test caller thread"), ZX_OK); |
| |
| // Call query on the blob directory. We expect that the request will be queued, but not responed |
| // to until blobfs is mounted by fshost later. |
| auto node_client_end = service::ConnectAt<fuchsia_io::Node>(exposed_dir().client_end(), "blob"); |
| ASSERT_EQ(node_client_end.status_value(), ZX_OK); |
| fidl::WireSharedClient<fuchsia_io::Node> node(std::move(*node_client_end), loop.dispatcher()); |
| sync_completion_t query_completion; |
| node->QueryFilesystem().ThenExactlyOnce( |
| [query_completion = |
| &query_completion](fidl::WireUnownedResult<fuchsia_io::Node::QueryFilesystem>& res) { |
| EXPECT_EQ(res.status(), ZX_OK); |
| EXPECT_EQ(res.value().s, ZX_OK); |
| EXPECT_EQ(res.value().info->fs_type, VFS_TYPE_BLOBFS); |
| sync_completion_signal(query_completion); |
| }); |
| |
| ASSERT_FALSE(sync_completion_signaled(&query_completion)); |
| PauseWatcher(); // Pause whilst we create a ramdisk. |
| |
| // Create a ramdisk with an unformatted blobfs partitition. |
| zx::vmo vmo; |
| ASSERT_EQ(zx::vmo::create(kDeviceSize, 0, &vmo), ZX_OK); |
| |
| // Create a child VMO so that we can keep hold of the original. |
| zx::vmo child_vmo; |
| ASSERT_EQ(vmo.create_child(ZX_VMO_CHILD_SLICE, 0, kDeviceSize, &child_vmo), ZX_OK); |
| |
| // Now create the ram-disk with a single FVM partition. |
| { |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(child_vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| storage::FvmOptions options{ |
| .name = "blobfs", |
| .type = std::array<uint8_t, BLOCK_GUID_LEN>{GUID_BLOB_VALUE}, |
| }; |
| auto fvm_partition_or = storage::CreateFvmPartition(ramdisk_or->path(), kSliceSize, options); |
| ASSERT_EQ(fvm_partition_or.status_value(), ZX_OK); |
| |
| // Format the new fvm partition with blobfs. |
| fbl::unique_fd fvm_fd(open(fvm_partition_or->c_str(), O_RDONLY)); |
| ASSERT_TRUE(fvm_fd); |
| zx::channel blobfs_device_chan; |
| ASSERT_EQ(fdio_fd_transfer(fvm_fd.release(), blobfs_device_chan.reset_and_get_address()), |
| ZX_OK); |
| |
| std::unique_ptr<block_client::RemoteBlockDevice> blobfs_device; |
| ASSERT_EQ( |
| block_client::RemoteBlockDevice::Create(std::move(blobfs_device_chan), &blobfs_device), |
| ZX_OK); |
| ASSERT_EQ(blobfs::FormatFilesystem(blobfs_device.get(), blobfs::FilesystemOptions{}), ZX_OK); |
| |
| // Check the newly formatted blobfs for good measure. |
| ASSERT_EQ(blobfs::Fsck(std::move(blobfs_device), blobfs::MountOptions{}), ZX_OK); |
| } |
| |
| ResumeWatcher(); |
| |
| // At this point, the query request should still be pending. |
| ASSERT_FALSE(sync_completion_signaled(&query_completion)); |
| |
| // Now reattach the ram-disk and fshost should find it and start blobfs. |
| auto ramdisk_or = storage::RamDisk::CreateWithVmo(std::move(vmo), kBlockSize); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| // Now the query request should be responded to. |
| // Query should get a response now. |
| ASSERT_EQ(sync_completion_wait(&query_completion, ZX_TIME_INFINITE), ZX_OK); |
| } |
| |
| } // namespace |
| } // namespace fshost |