blob: 925bd8b35086472ee6ae2c10d0df3c383558202e [file] [log] [blame]
// Copyright 2018 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 <errno.h>
#include <fcntl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/unsafe.h>
#include <lib/memfs/memfs.h>
#include <lib/sync/completion.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <zircon/device/vfs.h>
#include <zircon/errors.h>
#include <fbl/string_buffer.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include <fs-management/fvm.h>
#include <fs-test-utils/fixture.h>
#include <ramdevice-client/ramdisk.h>
namespace fs_test_utils {
namespace {
constexpr char kRamdiskCtlPath[] = "misc/ramctl";
constexpr char kDevPath[] = "/dev";
// Used as path for referencing devices bound to the isolated devmgr
// in the current test case.
constexpr char kIsolatedDevPath[] = "/isolated-dev";
// Mount point for local MemFs to be mounted.
constexpr char kMemFsPath[] = "/memfs";
// Name for MemFs serving thread.
constexpr char kMemFsThreadName[] = "TestServingMemFsName";
// Path where to mount the filesystem.
constexpr char kFsPath[] = "%s/fs-root";
// Partition name where the filesystem will be mounted when using fvm.
constexpr char kFsPartitionName[] = "fs-test-partition";
// FVM Driver lib
constexpr char kFvmDriverLibPath[] = "/boot/driver/fvm.so";
constexpr uint8_t kTestUniqueGUID[] = {0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
constexpr uint8_t kTestPartGUID[] = {0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
zx_status_t MountMemFs(async::Loop* loop, memfs_filesystem_t** memfs_out) {
zx_status_t result = ZX_OK;
result = loop->StartThread(kMemFsThreadName);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to start serving thread for MemFs.\n");
return result;
}
return memfs_install_at(loop->dispatcher(), kMemFsPath, memfs_out);
}
zx_status_t UnmountMemFs(memfs_filesystem_t* memfs) {
return memfs_uninstall_unsafe(memfs, kMemFsPath);
}
zx_status_t MakeRamdisk(int devfs_root, const FixtureOptions& options,
ramdisk_client_t** out_ramdisk) {
ZX_DEBUG_ASSERT(options.use_ramdisk);
if (options.use_ramdisk) {
zx_status_t result = ramdisk_create_at(devfs_root, options.ramdisk_block_size,
options.ramdisk_block_count, out_ramdisk);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to create ramdisk(block_size=%lu, ramdisk_block_count=%lu)\n",
options.ramdisk_block_size, options.ramdisk_block_count);
return result;
}
}
return ZX_OK;
}
zx_status_t RemoveRamdisk(const FixtureOptions& options, ramdisk_client_t* ramdisk) {
ZX_DEBUG_ASSERT(options.use_ramdisk);
if (options.use_ramdisk && ramdisk != nullptr) {
zx_status_t result = ramdisk_destroy(ramdisk);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to destroy ramdisk.\n");
}
}
return ZX_OK;
}
zx_status_t MakeFvm(int devfs_root, const char* root_path, const fbl::String& block_device_path,
uint64_t fvm_slice_size,
fbl::String* partition_path, bool* fvm_mounted) {
zx_status_t result = ZX_OK;
fbl::unique_fd fd(open(block_device_path.c_str(), O_RDWR));
if (!fd) {
LOG_ERROR(ZX_ERR_IO, "%s.\nblock_device_path: %s\n", strerror(errno),
block_device_path.c_str());
return ZX_ERR_IO;
}
result = fvm_init(fd.get(), fvm_slice_size);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to format device with FVM.\nblock_device_path: %s\n",
block_device_path.c_str());
return result;
}
*fvm_mounted = true;
fbl::String fvm_device_path = fbl::StringPrintf("%s/fvm", block_device_path.c_str());
// Bind FVM Driver.
fdio_t* io = fdio_unsafe_fd_to_io(fd.get());
if (io == NULL) {
return ZX_ERR_INTERNAL;
}
zx_status_t call_status = ZX_OK;
auto resp = fidl::WireCall<fuchsia_device::Controller>(
zx::unowned_channel(fdio_unsafe_borrow_channel(io)))
.Bind(::fidl::StringView(kFvmDriverLibPath));
result = resp.status();
if (resp->result.is_err()) {
call_status = resp->result.err();
}
fdio_unsafe_release(io);
if (result == ZX_OK) {
result = call_status;
}
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to bind fvm driver to block device.\nblock_device:%s\n",
block_device_path.c_str());
return result;
}
result = wait_for_device(fvm_device_path.c_str(), zx::sec(3).get());
if (result != ZX_OK) {
LOG_ERROR(result, "FVM driver failed to start.\nfvm_device_path:%s\n", fvm_device_path.c_str());
return result;
}
fbl::unique_fd fvm_fd(open(fvm_device_path.c_str(), O_RDWR));
if (!fvm_fd) {
LOG_ERROR(ZX_ERR_IO, "%s.\nfvm_device_path:%s\n", strerror(errno), fvm_device_path.c_str());
return ZX_ERR_IO;
}
alloc_req_t request;
memset(&request, 0, sizeof(request));
request.slice_count = 1;
strcpy(request.name, kFsPartitionName);
memcpy(request.type, kTestPartGUID, sizeof(request.type));
memcpy(request.guid, kTestUniqueGUID, sizeof(request.guid));
fbl::unique_fd partition_fd(
fvm_allocate_partition_with_devfs(devfs_root, fvm_fd.get(), &request));
if (!partition_fd) {
LOG_ERROR(ZX_ERR_IO, "Failed to allocate FVM partition. Error: %s \n", strerror(errno));
return ZX_ERR_IO;
}
char buffer[kPathSize];
partition_fd.reset(
open_partition_with_devfs(devfs_root, kTestUniqueGUID, kTestPartGUID, 0, buffer));
*partition_path = fbl::String::Concat({root_path, "/", buffer});
if (!partition_fd) {
LOG_ERROR(ZX_ERR_IO, "Could not locate FVM partition. %s\n", strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
}
} // namespace
bool FixtureOptions::IsValid(fbl::String* err_description) const {
ZX_DEBUG_ASSERT(err_description != nullptr);
fbl::StringBuffer<400> buffer;
err_description->clear();
if (use_ramdisk) {
if (!block_device_path.empty()) {
buffer.Append("use_ramdisk and block_device_path are mutually exclusive.\n");
}
size_t max_size = zx_system_get_physmem();
size_t requested_size = ramdisk_block_count * ramdisk_block_size;
if (max_size < requested_size) {
buffer.AppendPrintf("ramdisk size(%lu) cannot exceed available memory(%lu).\n",
requested_size, requested_size);
}
if (ramdisk_block_count == 0) {
buffer.Append("ramdisk_block_count must be greater than 0.\n");
}
if (ramdisk_block_size == 0) {
buffer.Append("ramdisk_block_size must be greater than 0.\n");
}
} else if (block_device_path.empty()) {
buffer.Append("block_device_path or use_ramdisk must be set\n.");
}
if (use_fvm) {
if (fvm_slice_size == 0 || fvm_slice_size % kFvmBlockSize != 0) {
buffer.AppendPrintf("fvm_slize_size must be a multiple of %lu.\n", kFvmBlockSize);
}
}
*err_description = buffer.ToString();
return err_description->empty();
}
Fixture::Fixture(const FixtureOptions& options) : options_(options) {}
Fixture::~Fixture() {
// Sanity check, teardown any resources if these two were not called yet.
TearDown();
TearDownTestCase();
}
zx_status_t Fixture::Mount() {
fbl::unique_fd fd(open(GetFsBlockDevice().c_str(), O_RDWR));
if (!fd) {
LOG_ERROR(ZX_ERR_IO, "%s.\nblock_device_path:%s\n", strerror(errno),
GetFsBlockDevice().c_str());
return ZX_ERR_IO;
}
// Already mounted.
if (fs_state_ == ResourceState::kAllocated) {
return ZX_OK;
}
mount_options_t mount_options = default_mount_options;
mount_options.create_mountpoint = true;
mount_options.wait_until_ready = true;
mount_options.register_fs = false;
if (options_.write_compression_algorithm) {
mount_options.write_compression_algorithm = options_.write_compression_algorithm;
}
disk_format_t format = detect_disk_format(fd.get());
zx_status_t result =
mount(fd.release(), fs_path_.c_str(), format, &mount_options, launch_stdio_async);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to mount device at %s.\nblock_device_path:%s\n", fs_path_.c_str(),
GetFsBlockDevice().c_str());
return result;
}
fs_state_ = ResourceState::kAllocated;
return ZX_OK;
}
zx_status_t Fixture::Fsck() const {
const fbl::String& block_dev = GetFsBlockDevice().c_str();
if (block_dev.empty()) {
// the block device doesn't exist, in which case there's nothing to
// check. since this is a test fixture, that's probably not what wanted,
// so surface the error.
LOG_ERROR(ZX_ERR_BAD_STATE, "Fsck called on an empty fixture\n");
return ZX_ERR_BAD_STATE;
}
// set up the fsck options
fsck_options_t fsck_options = default_fsck_options;
fsck_options.never_modify = true;
fsck_options.force = true;
// run fsck
zx_status_t result = fsck(block_dev.c_str(), options_.fs_type, &fsck_options, launch_stdio_sync);
if (result != ZX_OK) {
LOG_ERROR(result, "Fsck failed on device at block_device_path:%s\n", block_dev.c_str());
return result;
}
return ZX_OK;
}
zx_status_t Fixture::Umount() {
if (fs_state_ != ResourceState::kAllocated) {
return ZX_OK;
}
if (!fs_path_.empty()) {
zx_status_t result = umount(fs_path_.c_str());
if (result != ZX_OK) {
LOG_ERROR(result,
"Failed to umount device from MemFs.\nblock_device_path:%s\nmount_path:%s\n",
GetFsBlockDevice().c_str(), fs_path_.c_str());
return result;
}
fs_state_ = ResourceState::kFreed;
}
return ZX_OK;
}
zx_status_t Fixture::Format() const {
// Format device.
mkfs_options_t mkfs_options = default_mkfs_options;
fbl::String block_device_path = GetFsBlockDevice();
zx_status_t result =
mkfs(block_device_path.c_str(), options_.fs_type, launch_stdio_sync, &mkfs_options);
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to format block device.\nblock_device_path:%s\n",
block_device_path.c_str());
return result;
}
// Verify format.
fsck_options_t fsck_options = default_fsck_options;
result = fsck(block_device_path.c_str(), options_.fs_type, &fsck_options, launch_stdio_sync);
if (result != ZX_OK) {
LOG_ERROR(result, "Block device format has errors.\nblock_device_path:%s\n",
block_device_path.c_str());
return result;
}
return ZX_OK;
}
zx_status_t Fixture::SetUpTestCase() {
LOG_INFO("Using random seed: %u\n", options_.seed);
zx_status_t result;
seed_ = options_.seed;
// Create the devmgr instance and bind it
if (options_.isolated_devmgr) {
devmgr_launcher::Args args = devmgr_integration_test::IsolatedDevmgr::DefaultArgs();
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");
result = devmgr_integration_test::IsolatedDevmgr::Create(std::move(args), &devmgr_);
if (result != ZX_OK) {
return result;
}
fdio_ns_t* ns;
result = fdio_ns_get_installed(&ns);
if (result != ZX_OK) {
return result;
}
result = fdio_ns_bind_fd(ns, kIsolatedDevPath, devmgr_.devfs_root().get());
if (result != ZX_OK) {
return result;
}
// Wait for RamCtl to appear.
result = wait_for_device_at(devmgr_.devfs_root().get(), kRamdiskCtlPath, zx::sec(5).get());
if (result != ZX_OK) {
return result;
}
devfs_root_.reset(open(kIsolatedDevPath, O_RDWR));
root_path_ = kIsolatedDevPath;
} else {
devfs_root_.reset(open(kDevPath, O_RDWR));
root_path_ = kDevPath;
}
if (options_.use_ramdisk) {
result = MakeRamdisk(devfs_root_.get(), options_, &ramdisk_);
if (result != ZX_OK) {
return result;
}
block_device_path_ =
fbl::StringPrintf("%s/%s", options_.isolated_devmgr ? kIsolatedDevPath : kDevPath,
ramdisk_get_path(ramdisk_));
ramdisk_state_ = ResourceState::kAllocated;
}
if (!options_.block_device_path.empty()) {
block_device_path_ = options_.block_device_path;
}
return ZX_OK;
}
zx_status_t Fixture::SetUp() {
fvm_state_ = ResourceState::kUnallocated;
fs_state_ = ResourceState::kUnallocated;
if (options_.use_fvm) {
bool allocated = false;
zx_status_t result = MakeFvm(devfs_root_.get(), root_path_, block_device_path_,
options_.fvm_slice_size, &partition_path_, &allocated);
if (allocated) {
fvm_state_ = ResourceState::kAllocated;
}
if (result != ZX_OK) {
return result;
}
}
fs_path_ = fbl::StringPrintf(kFsPath, kMemFsPath);
zx_status_t result;
if (options_.fs_format) {
result = Format();
if (result != ZX_OK) {
return result;
}
}
if (options_.fs_mount) {
result = Mount();
if (result != ZX_OK) {
return result;
}
fs_state_ = ResourceState::kAllocated;
}
return ZX_OK;
}
zx_status_t Fixture::TearDown() {
zx_status_t result;
// Umount Fs from MemFs.
if (fs_state_ == ResourceState::kAllocated) {
result = Umount();
if (result != ZX_OK) {
return result;
}
}
// If real device not in FVM, clean it.
if (!block_device_path_.empty() && !options_.use_fvm && fs_state_ == ResourceState::kAllocated) {
result = Format();
if (result != ZX_OK) {
return result;
}
fs_state_ = ResourceState::kFreed;
}
// If using FVM on top of device, just destroy the fvm, this only applies if
// the fvm was created within this process.
if (options_.use_fvm && fvm_state_ == ResourceState::kAllocated) {
result = fvm_destroy(block_device_path_.c_str());
if (result != ZX_OK) {
LOG_ERROR(result, "Failed to destroy fvm in block_device.\nblock_device: %s\n",
block_device_path_.cend());
return result;
}
fs_state_ = ResourceState::kFreed;
fvm_state_ = ResourceState::kFreed;
}
return ZX_OK;
}
zx_status_t Fixture::TearDownTestCase() {
if (options_.isolated_devmgr) {
zx_status_t result;
fdio_ns_t* ns;
result = fdio_ns_get_installed(&ns);
if (result != ZX_OK) {
return result;
}
result = fdio_ns_unbind(ns, kIsolatedDevPath);
// Either successfuly unbind or we already unbound it.
if (result != ZX_OK && result != ZX_ERR_NOT_FOUND) {
return result;
}
}
if (ramdisk_state_ == ResourceState::kAllocated) {
zx_status_t ramdisk_result = RemoveRamdisk(options_, ramdisk_);
ramdisk_ = nullptr;
if (ramdisk_result != ZX_OK) {
return ramdisk_result;
}
}
ramdisk_state_ = ResourceState::kFreed;
return ZX_OK;
}
int RunWithMemFs(const fbl::Function<int()>& main_fn) {
memfs_filesystem_t* fs;
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
zx_status_t status;
if ((status = MountMemFs(&loop, &fs)) != ZX_OK) {
LOG_ERROR(status, "Failed to mount memfs\n");
return -1;
}
int result = main_fn();
loop.Shutdown();
if ((status = UnmountMemFs(fs)) != ZX_OK) {
LOG_ERROR(status, "Failed to unmount memfs\n");
return -1;
}
return result;
}
} // namespace fs_test_utils