blob: 83fcc97984a54b30623f114cdf545a4b2c7a0a50 [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 <fidl/fuchsia.io/cpp/wire_test_base.h>
#include <lib/fdio/namespace.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/syslog/global.h>
#include <lib/syslog/logger.h>
#include <zircon/assert.h>
#include <zircon/hw/gpt.h>
#include <sstream>
#include <string>
#include <gtest/gtest.h>
#include "src/storage/fshost/block-device-manager.h"
#include "src/storage/fshost/block-watcher.h"
#include "src/storage/fshost/config.h"
#include "src/storage/fshost/extract-metadata.h"
#include "src/storage/fshost/filesystem-mounter.h"
#include "src/storage/fshost/fs-manager.h"
#include "src/storage/minfs/format.h"
#include "src/storage/testing/ram_disk.h"
namespace fshost {
namespace {
constexpr uint64_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
class BlockDeviceTest : public testing::Test {
public:
BlockDeviceTest() : manager_(nullptr), config_(EmptyConfig()), watcher_(manager_, &config_) {}
void SetUp() override {
// Initialize FilesystemMounter.
fidl::ServerEnd<fuchsia_io::Directory> dir_request;
fidl::ServerEnd<fuchsia_process_lifecycle::Lifecycle> lifecycle_request;
ASSERT_EQ(manager_.Initialize(std::move(dir_request), std::move(lifecycle_request), config_,
watcher_),
ZX_OK);
manager_.DisableCrashReporting();
// Fshost really likes mounting filesystems at "/fs".
// Let's make that available in our namespace.
auto fs_dir_or = manager_.GetFsDir();
ASSERT_EQ(fs_dir_or.status_value(), ZX_OK);
fdio_ns_t* ns;
ASSERT_EQ(fdio_ns_get_installed(&ns), ZX_OK);
ASSERT_EQ(fdio_ns_bind(ns, "/fs", fs_dir_or->TakeChannel().release()), ZX_OK);
// 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_EQ(fdio_ns_bind_fd(ns, "/boot", pkg_fd), ZX_OK);
manager_.ReadyForShutdown();
}
void TearDown() override {
fdio_ns_t* ns;
ASSERT_EQ(fdio_ns_get_installed(&ns), ZX_OK);
fdio_ns_unbind(ns, "/fs");
fdio_ns_unbind(ns, "/boot");
}
void CreateRamdisk(bool use_guid = false) {
storage::RamDisk::Options options;
if (use_guid)
options.type_guid = std::array<uint8_t, GPT_GUID_LEN>(GUID_DATA_VALUE);
ramdisk_ = storage::RamDisk::Create(kBlockSize, kBlockCount, options).value();
ASSERT_EQ(wait_for_device(ramdisk_->path().c_str(), zx::sec(10).get()), ZX_OK);
}
fbl::unique_fd GetRamdiskFd() { return fbl::unique_fd(open(ramdisk_->path().c_str(), O_RDWR)); }
fbl::unique_fd devfs_root() { return fbl::unique_fd(open("/dev", O_RDWR)); }
protected:
FsManager manager_;
fshost_config::Config config_;
private:
std::optional<storage::RamDisk> ramdisk_;
BlockWatcher watcher_;
};
TEST_F(BlockDeviceTest, TestBadHandleDevice) {
FilesystemMounter mounter(manager_, &config_);
fbl::unique_fd fd;
BlockDevice device(&mounter, {}, &config_);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatUnknown);
fuchsia_hardware_block_BlockInfo info;
EXPECT_EQ(device.GetInfo(&info), ZX_ERR_BAD_HANDLE);
fuchsia_hardware_block_partition::wire::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_EQ(device.UnsealZxcrypt(), ZX_OK);
// Returns ZX_OK because filesystem checks are disabled.
EXPECT_EQ(device.CheckFilesystem(), ZX_OK);
EXPECT_EQ(device.FormatFilesystem(), ZX_ERR_BAD_HANDLE);
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_BAD_HANDLE);
}
TEST_F(BlockDeviceTest, TestEmptyDevice) {
FilesystemMounter mounter(manager_, &config_);
// Initialize Ramdisk.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(/*use_guid=*/true));
BlockDevice device(&mounter, GetRamdiskFd(), &config_);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatUnknown);
fuchsia_hardware_block_BlockInfo info;
EXPECT_EQ(device.GetInfo(&info), ZX_OK);
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_EQ(device.GetInfo(&info), ZX_OK);
EXPECT_EQ(info.block_count, kBlockCount);
EXPECT_EQ(info.block_size, kBlockSize);
static constexpr fuchsia_hardware_block_partition::wire::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);
}
class TestMinfsMounter : public FilesystemMounter {
public:
TestMinfsMounter(FsManager& fshost, const fshost_config::Config* config)
: FilesystemMounter(fshost, config) {}
zx::status<> LaunchFsComponent(zx::channel block_device,
fuchsia_fs_startup::wire::StartOptions options,
const std::string& fs_name) final {
EXPECT_EQ(fs_name, "data");
return zx::ok();
}
zx_status_t LaunchFs(int argc, const char** argv, zx_handle_t* hnd, uint32_t* ids,
size_t len) final {
ADD_FAILURE() << "Unexpected call to LaunchFs";
return ZX_ERR_NOT_SUPPORTED;
}
};
TEST_F(BlockDeviceTest, TestMinfsBadGUID) {
TestMinfsMounter mounter(manager_, &config_);
// Initialize Ramdisk with an empty GUID.
ASSERT_NO_FATAL_FAILURE(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(), &config_);
device.SetFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.FormatFilesystem(), ZX_OK);
// 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(BlockDeviceTest, TestMinfsGoodGUID) {
TestMinfsMounter mounter(manager_, &config_);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd(), &config_);
device.SetFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.FormatFilesystem(), ZX_OK);
EXPECT_EQ(device.MountFilesystem(), ZX_OK);
EXPECT_EQ(device.MountFilesystem(), ZX_ERR_ALREADY_BOUND);
}
TEST_F(BlockDeviceTest, TestMinfsReformat) {
auto config = EmptyConfig();
config.check_filesystems() = true;
TestMinfsMounter mounter(manager_, &config);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd(), &config);
device.SetFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatMinfs);
// Before formatting the device, this isn't a valid minfs partition.
EXPECT_NE(device.CheckFilesystem(), ZX_OK);
// After formatting the device, it is a valid partition. We can check the device,
// and also mount it.
EXPECT_EQ(device.FormatFilesystem(), ZX_OK);
EXPECT_EQ(device.CheckFilesystem(), ZX_OK);
EXPECT_EQ(device.MountFilesystem(), ZX_OK);
}
TEST_F(BlockDeviceTest, TestBlobfs) {
auto config = EmptyConfig();
config.check_filesystems() = true;
FilesystemMounter mounter(manager_, &config);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd(), &config);
device.SetFormat(fs_management::kDiskFormatBlobfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatBlobfs);
// Before formatting the device, this isn't a valid blobfs partition.
// However, as implemented, we always validate the consistency of the filesystem.
EXPECT_EQ(device.CheckFilesystem(), ZX_OK);
// Additionally, blobfs does not yet support reformatting within fshost.
EXPECT_NE(device.FormatFilesystem(), ZX_OK);
EXPECT_EQ(device.CheckFilesystem(), ZX_OK);
EXPECT_NE(device.MountFilesystem(), ZX_OK);
}
TEST_F(BlockDeviceTest, TestMinfsCorruptionEventLogged) {
auto config = EmptyConfig();
config.check_filesystems() = true;
FilesystemMounter mounter(manager_, &config);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd(), &config);
device.SetFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatMinfs);
// Format minfs.
EXPECT_EQ(device.FormatFilesystem(), ZX_OK);
// Corrupt minfs.
uint64_t buffer_size = static_cast<uint64_t>(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(GetRamdiskFd().get(), zeroed_buffer.get(), buffer_size),
static_cast<ssize_t>(buffer_size));
ASSERT_NE(device.CheckFilesystem(), ZX_OK);
// Verify that we logged a Minfs corruption event to the InspectManager.
inspect::Hierarchy hierarchy =
fpromise::run_single_threaded(
inspect::ReadFromInspector(mounter.inspect_manager().inspector()))
.take_value();
const inspect::Hierarchy* corruption_events = hierarchy.GetByPath({"corruption_events"});
ASSERT_NE(corruption_events, nullptr);
const auto* property =
corruption_events->node().get_property<inspect::UintPropertyValue>("minfs");
ASSERT_NE(property, nullptr);
ASSERT_EQ(property->value(), 1u);
}
std::unique_ptr<std::string> GetData(int fd) {
constexpr size_t kBufferSize = 10 * 1024 * 1024;
auto buffer = std::make_unique<char[]>(kBufferSize);
memset(buffer.get(), 0, kBufferSize);
ssize_t read_length;
size_t offset = 0;
while ((read_length = read(fd, &buffer.get()[offset], kBufferSize - offset - 1)) >= 0) {
EXPECT_GE(read_length, 0);
offset += read_length;
if (offset >= kBufferSize - 1 || read_length == 0) {
buffer.get()[kBufferSize - 1] = '\0';
return std::make_unique<std::string>(std::string(buffer.get()));
}
}
EXPECT_GE(read_length, 0);
return nullptr;
}
std::pair<fbl::unique_fd, fbl::unique_fd> SetupLog() {
int pipefd[2];
EXPECT_EQ(pipe2(pipefd, O_NONBLOCK), 0);
fbl::unique_fd fd_to_close1(pipefd[0]);
fbl::unique_fd fd_to_close2(pipefd[1]);
fx_logger_activate_fallback(fx_log_get_logger(), pipefd[0]);
return {std::move(fd_to_close1), std::move(fd_to_close2)};
}
TEST_F(BlockDeviceTest, ExtractMinfsOnCorruptionToLog) {
auto fd_pair = SetupLog();
auto config = EmptyConfig();
config.check_filesystems() = true;
FilesystemMounter mounter(manager_, &config);
// Initialize Ramdisk with a data GUID.
ASSERT_NO_FATAL_FAILURE(CreateRamdisk(true));
BlockDevice device(&mounter, GetRamdiskFd(), &config);
device.SetFormat(fs_management::kDiskFormatMinfs);
EXPECT_EQ(device.GetFormat(), fs_management::kDiskFormatMinfs);
// Format minfs.
EXPECT_EQ(device.FormatFilesystem(), ZX_OK);
// Corrupt minfs.
uint64_t buffer_size = minfs::kMinfsBlockSize * 8;
std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
memset(buffer.get(), 0, buffer_size);
ASSERT_EQ(pread(GetRamdiskFd().get(), buffer.get(), buffer_size, 0),
static_cast<ssize_t>(buffer_size));
// Corrupt checksum of both copies of superblocks.
buffer.get()[offsetof(minfs::Superblock, checksum)] += 1;
buffer.get()[(minfs::kMinfsBlockSize * 7) + offsetof(minfs::Superblock, checksum)] += 1;
ASSERT_EQ(pwrite(GetRamdiskFd().get(), buffer.get(), buffer_size, 0),
static_cast<ssize_t>(buffer_size));
EXPECT_NE(device.CheckFilesystem(), ZX_OK);
fd_pair.first.reset();
auto logs = GetData(fd_pair.second.get());
auto header_line = logs->find("EIL: Extracting minfs to serial.");
auto helper_line1 =
logs->find("EIL: Following lines that start with \"EIL\" are from extractor.");
auto helper_line2 =
logs->find("EIL: Successful extraction ends with \"EIL: Done extracting minfs to serial.\"");
auto dump_option_line =
logs->find("EIL: Compression:off Checksum:on Offset:on bytes_per_line:64");
auto offsets_string = logs->find("EIL 0-63:");
auto checksum_line = logs->find(":checksum: ");
if (ExtractMetadataEnabled()) {
ASSERT_NE(header_line, std::string::npos);
ASSERT_NE(helper_line1, std::string::npos);
ASSERT_NE(helper_line2, std::string::npos);
ASSERT_NE(dump_option_line, std::string::npos);
ASSERT_NE(offsets_string, std::string::npos);
ASSERT_NE(checksum_line, std::string::npos);
ASSERT_NE(logs->find("EIL: Done extracting minfs to serial", checksum_line), std::string::npos);
} else {
ASSERT_EQ(header_line, std::string::npos);
ASSERT_EQ(helper_line1, std::string::npos);
ASSERT_EQ(helper_line2, std::string::npos);
ASSERT_EQ(dump_option_line, std::string::npos);
ASSERT_EQ(offsets_string, std::string::npos);
ASSERT_EQ(checksum_line, std::string::npos);
ASSERT_EQ(logs->find("EIL: Done extracting minfs to serial"), std::string::npos);
}
}
// TODO(unknown): Add tests for Zxcrypt binding.
} // namespace
} // namespace fshost