blob: 71a89f1b99e34c3837e638e4c3491f0082ee028f [file] [log] [blame]
// 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