blob: d3905ee5200be30bc7520a5508249441f878d35a [file] [log] [blame]
// Copyright 2019 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 "block-device.h"
#include <fcntl.h>
#include <lib/devmgr-integration-test/fixture.h>
#include <lib/fdio/namespace.h>
#include <zircon/assert.h>
#include <zircon/hw/gpt.h>
#include <sstream>
#include <cobalt-client/cpp/collector.h>
#include <cobalt-client/cpp/in_memory_logger.h>
#include <cobalt-client/cpp/metric_options.h>
#include <fs/metrics/events.h>
#include <ramdevice-client/ramdisk.h>
#include <zxtest/zxtest.h>
#include "src/storage/fshost/block-device-manager.h"
#include "src/storage/fshost/filesystem-mounter.h"
#include "src/storage/fshost/fs-manager.h"
#include "src/storage/fshost/metrics.h"
#include "src/storage/minfs/format.h"
namespace devmgr {
namespace {
using devmgr_integration_test::IsolatedDevmgr;
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
FsHostMetrics MakeMetrics(cobalt_client::InMemoryLogger** logger) {
std::unique_ptr<cobalt_client::InMemoryLogger> logger_ptr =
std::make_unique<cobalt_client::InMemoryLogger>();
*logger = logger_ptr.get();
return FsHostMetrics(std::make_unique<cobalt_client::Collector>(std::move(logger_ptr)));
}
class BlockDeviceHarness : public zxtest::Test {
public:
void SetUp() override {
// Initialize FilesystemMounter.
zx::channel dir_request, lifecycle_request;
ASSERT_OK(FsManager::Create(nullptr, std::move(dir_request), std::move(lifecycle_request),
MakeMetrics(&logger_), &manager_));
// Fshost really likes mounting filesystems at "/fs".
// Let's make that available in our namespace.
zx::channel client, server;
ASSERT_OK(zx::channel::create(0, &client, &server));
ASSERT_OK(manager_->ServeRoot(std::move(server)));
fdio_ns_t* ns;
ASSERT_OK(fdio_ns_get_installed(&ns));
ASSERT_OK(fdio_ns_bind(ns, "/fs", client.release()));
manager_->WatchExit();
// fshost uses hardcoded /boot/bin paths to launch filesystems, but this test is packaged now.
// Make /boot redirect to /pkg in our namespace, which contains the needed binaries.
int pkg_fd = open("/pkg", O_DIRECTORY | O_RDONLY);
ASSERT_GE(pkg_fd, 0);
ASSERT_OK(fdio_ns_bind_fd(ns, "/boot", pkg_fd));
devmgr_launcher::Args args;
args.disable_block_watcher = true;
args.sys_device_driver = devmgr_integration_test::IsolatedDevmgr::kSysdevDriver;
args.load_drivers.push_back(devmgr_integration_test::IsolatedDevmgr::kSysdevDriver);
args.driver_search_paths.push_back("/boot/driver");
ASSERT_OK(IsolatedDevmgr::Create(std::move(args), &devmgr_));
fbl::unique_fd ctl;
ASSERT_EQ(
devmgr_integration_test::RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &ctl),
ZX_OK);
}
void TearDown() override {
if (ramdisk_) {
ASSERT_OK(ramdisk_destroy(ramdisk_));
}
fdio_ns_t* ns;
ASSERT_OK(fdio_ns_get_installed(&ns));
fdio_ns_unbind(ns, "/fs");
fdio_ns_unbind(ns, "/boot");
}
void CreateRamdisk(bool use_guid = false) {
if (use_guid) {
const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE;
ASSERT_OK(ramdisk_create_at_with_guid(devfs_root().get(), kBlockSize, kBlockCount, data_guid,
sizeof(data_guid), &ramdisk_));
} else {
ASSERT_OK(ramdisk_create_at(devfs_root().get(), kBlockSize, kBlockCount, &ramdisk_));
}
ASSERT_OK(devmgr_integration_test::RecursiveWaitForFile(devfs_root(),
ramdisk_get_path(ramdisk_), &fd_));
ASSERT_TRUE(fd_);
}
fbl::unique_fd GetRamdiskFd() { return std::move(fd_); }
std::unique_ptr<FsManager> TakeManager() { return std::move(manager_); }
fbl::unique_fd devfs_root() { return devmgr_.devfs_root().duplicate(); }
protected:
cobalt_client::InMemoryLogger* logger_ = nullptr;
ramdisk_client_t* ramdisk_ = nullptr;
private:
std::unique_ptr<FsManager> manager_;
IsolatedDevmgr devmgr_;
fbl::unique_fd fd_;
};
TEST_F(BlockDeviceHarness, TestBadHandleDevice) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
FilesystemMounter mounter(std::move(manager), options);
fbl::unique_fd fd;
BlockDevice device(&mounter, GetRamdiskFd());
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_UNKNOWN);
fuchsia_hardware_block_BlockInfo info;
EXPECT_EQ(device.GetInfo(&info), ZX_ERR_BAD_HANDLE);
fuchsia_hardware_block_partition_GUID null_guid{};
EXPECT_EQ(memcmp(&device.GetTypeGuid(), &null_guid, sizeof(null_guid)), 0);
EXPECT_EQ(device.AttachDriver("/foobar"), ZX_ERR_BAD_HANDLE);
// Returns ZX_OK because zxcrypt currently passes the empty fd to a background
// thread without observing the results.
EXPECT_OK(device.UnsealZxcrypt());
// Returns ZX_OK because filesystem checks are disabled.
EXPECT_OK(device.CheckFilesystem());
EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_BAD_HANDLE);
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_BAD_HANDLE);
}
TEST_F(BlockDeviceHarness, TestEmptyDevice) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk.
ASSERT_NO_FAILURES(CreateRamdisk(/*use_ramdisk=*/true));
BlockDevice device(&mounter, GetRamdiskFd());
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_UNKNOWN);
fuchsia_hardware_block_BlockInfo info;
EXPECT_OK(device.GetInfo(&info));
EXPECT_EQ(info.block_count, kBlockCount);
EXPECT_EQ(info.block_size, kBlockSize);
// Black-box: Since we're caching info, double check that re-calling GetInfo
// works correctly.
memset(&info, 0, sizeof(info));
EXPECT_OK(device.GetInfo(&info));
EXPECT_EQ(info.block_count, kBlockCount);
EXPECT_EQ(info.block_size, kBlockSize);
static constexpr fuchsia_hardware_block_partition_GUID expected_guid = GUID_DATA_VALUE;
EXPECT_EQ(memcmp(&device.GetTypeGuid(), &expected_guid, sizeof(expected_guid)), 0);
EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_NOT_SUPPORTED);
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_NOT_SUPPORTED);
}
TEST_F(BlockDeviceHarness, TestMinfsBadGUID) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk with an empty GUID.
ASSERT_NO_FAILURES(CreateRamdisk());
// We started with an empty block device, but let's lie and say it
// should have been a minfs device.
BlockDevice device(&mounter, GetRamdiskFd());
device.SetFormat(DISK_FORMAT_MINFS);
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS);
EXPECT_OK(device.FormatFilesystem());
// Unlike earlier, where we received "ERR_NOT_SUPPORTED", we get "ERR_WRONG_TYPE"
// because the ramdisk doesn't have a data GUID.
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_WRONG_TYPE);
}
TEST_F(BlockDeviceHarness, TestMinfsGoodGUID) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FAILURES(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd());
device.SetFormat(DISK_FORMAT_MINFS);
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS);
EXPECT_OK(device.FormatFilesystem());
EXPECT_OK(device.MountFilesystem());
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_ALREADY_BOUND);
}
TEST_F(BlockDeviceHarness, TestMinfsReformat) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
options.check_filesystems = true;
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FAILURES(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd());
device.SetFormat(DISK_FORMAT_MINFS);
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS);
// Before formatting the device, this isn't a valid minfs partition.
EXPECT_NOT_OK(device.CheckFilesystem());
EXPECT_NOT_OK(device.MountFilesystem());
// After formatting the device, it is a valid partition. We can check the device,
// and also mount it.
EXPECT_OK(device.FormatFilesystem());
EXPECT_OK(device.CheckFilesystem());
EXPECT_OK(device.MountFilesystem());
}
TEST_F(BlockDeviceHarness, TestBlobfs) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
options.check_filesystems = true;
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FAILURES(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd());
device.SetFormat(DISK_FORMAT_BLOBFS);
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_BLOBFS);
// Before formatting the device, this isn't a valid blobfs partition.
// However, as implemented, we always validate the consistency of the filesystem.
EXPECT_OK(device.CheckFilesystem());
EXPECT_NOT_OK(device.MountFilesystem());
// Additionally, blobfs does not yet support reformatting within fshost.
EXPECT_NOT_OK(device.FormatFilesystem());
EXPECT_OK(device.CheckFilesystem());
EXPECT_NOT_OK(device.MountFilesystem());
}
TEST_F(BlockDeviceHarness, TestCorruptionEventLogged) {
std::unique_ptr<FsManager> manager = TakeManager();
BlockWatcherOptions options = {};
options.check_filesystems = true;
FilesystemMounter mounter(std::move(manager), options);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FAILURES(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd());
device.SetFormat(DISK_FORMAT_MINFS);
EXPECT_EQ(device.GetFormat(), DISK_FORMAT_MINFS);
// Format minfs.
EXPECT_OK(device.FormatFilesystem());
// Corrupt minfs.
int ramdisk_fd = ramdisk_get_block_fd(ramdisk_);
uint64_t buffer_size = minfs::kMinfsBlockSize * 8;
std::unique_ptr<uint8_t[]> zeroed_buffer(new uint8_t[buffer_size]);
memset(zeroed_buffer.get(), 0, buffer_size);
ASSERT_EQ(write(ramdisk_fd, zeroed_buffer.get(), buffer_size), buffer_size);
EXPECT_NOT_OK(device.CheckFilesystem());
// Verify a corruption event was logged.
cobalt_client::MetricOptions metric_options;
metric_options.metric_id = static_cast<std::underlying_type<fs_metrics::Event>::type>(
fs_metrics::Event::kDataCorruption);
metric_options.event_codes = {static_cast<uint32_t>(fs_metrics::CorruptionSource::kMinfs),
static_cast<uint32_t>(fs_metrics::CorruptionType::kMetadata)};
metric_options.metric_dimensions = 2;
metric_options.component = {};
ASSERT_NE(logger_->counters().find(metric_options), logger_->counters().end());
ASSERT_EQ(logger_->counters().at(metric_options), 1);
}
TEST(BlockDeviceManager, ReadOptions) {
std::stringstream stream;
stream << "# A comment" << std::endl
<< BlockDeviceManager::Options::kDefault << std::endl
<< BlockDeviceManager::Options::kNoZxcrypt << std::endl
<< "-" << BlockDeviceManager::Options::kBlobfs;
const auto options = BlockDeviceManager::ReadOptions(stream);
auto expected_options = BlockDeviceManager::DefaultOptions();
expected_options.options.emplace(BlockDeviceManager::Options::kDefault);
expected_options.options.emplace(BlockDeviceManager::Options::kNoZxcrypt);
expected_options.options.erase(BlockDeviceManager::Options::kBlobfs);
EXPECT_EQ(expected_options.options, options.options);
}
// TODO: Add tests for Zxcrypt binding.
} // namespace
} // namespace devmgr