blob: 07d44e86cd43f2fee95a44ced7224de31a051183 [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 <fcntl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/zx/status.h>
#include <lib/zx/time.h>
#include <stdint.h>
#include <zircon/compiler.h>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <fbl/unique_fd.h>
#include <fs-management/admin.h>
#include <fs-management/format.h>
#include <fs-management/mount.h>
#include <ramdevice-client/ramnand.h>
#include "src/lib/isolated_devmgr/v2_component/ram_disk.h"
#include "src/storage/blobfs/blob-layout.h"
namespace fs_test {
class Filesystem;
struct TestFilesystemOptions {
static TestFilesystemOptions DefaultMinfs();
static TestFilesystemOptions MinfsWithoutFvm();
static TestFilesystemOptions DefaultMemfs();
static TestFilesystemOptions DefaultFatfs();
static TestFilesystemOptions DefaultBlobfs();
static TestFilesystemOptions BlobfsWithoutFvm();
std::string description;
bool use_ram_nand = false;
// If use_ram_nand is true, specifies a VMO to be used to back the device. If supplied, its size
// must match the device size (if device_block_count is non-zero), including the extra required
// for OOB.
zx::unowned_vmo ram_nand_vmo;
bool use_fvm = false;
uint64_t device_block_size = 0;
uint64_t device_block_count = 0;
uint64_t fvm_slice_size = 0;
const Filesystem* filesystem = nullptr;
// By default the ram-disk we create is filled with a non-zero value (so that we don't
// inadvertently depend on it), but that won't work for very large ram-disks (they will trigger
// OOMs), in which case they can be zero filled.
bool zero_fill = false;
// The format blobfs should store blobs in.
std::optional<blobfs::BlobLayoutFormat> blob_layout_format;
std::ostream& operator<<(std::ostream& out, const TestFilesystemOptions& options);
std::vector<TestFilesystemOptions> AllTestFilesystems();
// Provides the ability to map and filter all test file systems, using the supplied function.
std::vector<TestFilesystemOptions> MapAndFilterAllTestFilesystems(
std::function<std::optional<TestFilesystemOptions>(const TestFilesystemOptions&)>);
// A file system instance is a specific instance created for test purposes.
class FilesystemInstance {
FilesystemInstance() = default;
FilesystemInstance(const FilesystemInstance&) = delete;
FilesystemInstance& operator=(const FilesystemInstance&) = delete;
virtual ~FilesystemInstance() = default;
virtual zx::status<> Format(const TestFilesystemOptions&) = 0;
virtual zx::status<> Mount(const std::string& mount_path, const mount_options_t& options) = 0;
virtual zx::status<> Unmount(const std::string& mount_path);
virtual zx::status<> Fsck() = 0;
// Returns path of the device on which the filesystem is created. For filesystem that are not
// block device based, like memfs, the function returns an error.
virtual zx::status<std::string> DevicePath() const = 0;
virtual isolated_devmgr::RamDisk* GetRamDisk() { return nullptr; }
virtual ramdevice_client::RamNand* GetRamNand() { return nullptr; }
virtual zx::unowned_channel GetOutgoingDirectory() const { return {}; }
// Base class for all supported file systems. It is a factory class that generates
// instances of FilesystemInstance subclasses.
class Filesystem {
struct Traits {
std::string_view name;
bool can_unmount = false;
zx::duration timestamp_granularity = zx::nsec(1);
bool supports_hard_links = true;
bool supports_mmap = false;
bool supports_resize = false;
uint64_t max_file_size = std::numeric_limits<uint64_t>::max();
bool in_memory = false;
bool is_case_sensitive = true;
bool supports_sparse_files = true;
bool is_fat = false;
bool supports_fsck_after_every_transaction = false;
virtual zx::status<std::unique_ptr<FilesystemInstance>> Make(
const TestFilesystemOptions& options) const = 0;
virtual zx::status<std::unique_ptr<FilesystemInstance>> Open(
const TestFilesystemOptions& options) const {
return zx::error(ZX_ERR_NOT_SUPPORTED);
virtual const Traits& GetTraits() const = 0;
// Template that implementations can use to gain the SharedInstance method.
template <typename T>
class FilesystemImpl : public Filesystem {
static const T& SharedInstance() {
static const auto* const kInstance = new T();
return *kInstance;
template <typename T, typename Instance>
class FilesystemImplWithDefaultMake : public FilesystemImpl<T> {
zx::status<std::unique_ptr<FilesystemInstance>> Make(
const TestFilesystemOptions& options) const override;
// Support for Memfs.
class MemfsFilesystem : public FilesystemImpl<MemfsFilesystem> {
zx::status<std::unique_ptr<FilesystemInstance>> Make(
const TestFilesystemOptions& options) const override;
const Traits& GetTraits() const override {
static Traits traits{
.name = "memfs",
.can_unmount = false,
.timestamp_granularity = zx::nsec(1),
.supports_hard_links = true,
.supports_mmap = true,
.supports_resize = false,
.max_file_size = 512 * 1024 * 1024,
.in_memory = true,
.is_case_sensitive = true,
.supports_sparse_files = true,
return traits;
// Support for Fatfs.
class FatFilesystem : public FilesystemImpl<FatFilesystem> {
zx::status<std::unique_ptr<FilesystemInstance>> Make(
const TestFilesystemOptions& options) const override;
const Traits& GetTraits() const override {
static Traits traits{
.name = "fatfs",
.can_unmount = true,
.timestamp_granularity = zx::sec(2),
.supports_hard_links = false,
.supports_mmap = false,
.supports_resize = false,
.max_file_size = 4'294'967'295,
.in_memory = false,
.is_case_sensitive = false,
.supports_sparse_files = false,
.is_fat = true,
return traits;
// Helper that creates a test file system with the given options and will clean-up upon destruction.
class TestFilesystem {
// Creates and returns a mounted test file system.
static zx::status<TestFilesystem> Create(const TestFilesystemOptions& options);
// Opens an existing instance of a file system.
static zx::status<TestFilesystem> Open(const TestFilesystemOptions& options);
TestFilesystem(TestFilesystem&&) = default;
TestFilesystem& operator=(TestFilesystem&&) = default;
const TestFilesystemOptions& options() const { return options_; }
const std::string& mount_path() const { return mount_path_; }
bool is_mounted() const { return mounted_; }
// Mounts the file system (only necessary after calling Unmount).
zx::status<> Mount() { return MountWithOptions(default_mount_options); }
zx::status<> MountWithOptions(const mount_options_t&);
// Unmounts a mounted file system.
zx::status<> Unmount();
// Runs fsck on the file system. Does not automatically unmount, so Unmount should be
// called first if that is required.
zx::status<> Fsck();
// Formats a file system instance.
zx::status<> Format() { return filesystem_->Format(options_); }
zx::status<std::string> DevicePath() const;
const Filesystem::Traits& GetTraits() const { return options_.filesystem->GetTraits(); }
fbl::unique_fd GetRootFd() const {
return fbl::unique_fd(open(mount_path_.c_str(), O_RDONLY | O_DIRECTORY));
// Returns the ramdisk, or nullptr if one isn't being used.
isolated_devmgr::RamDisk* GetRamDisk() const { return filesystem_->GetRamDisk(); }
// Returns the ram-nand device, or nullptr if one isn't being used.
ramdevice_client::RamNand* GetRamNand() const { return filesystem_->GetRamNand(); }
zx::status<::llcpp::fuchsia::io::FilesystemInfo> GetFsInfo();
zx::unowned_channel GetOutgoingDirectory() const { return filesystem_->GetOutgoingDirectory(); };
// Creates a mount point for the instance, mounts it and returns a TestFilesystem.
static zx::status<TestFilesystem> FromInstance(const TestFilesystemOptions& options,
std::unique_ptr<FilesystemInstance> instance);
TestFilesystem(TestFilesystemOptions options, std::unique_ptr<FilesystemInstance> filesystem,
std::string mount_path)
: options_(std::move(options)),
mount_path_(std::move(mount_path)) {}
TestFilesystemOptions options_;
std::unique_ptr<FilesystemInstance> filesystem_;
std::string mount_path_;
bool mounted_ = false;
} // namespace fs_test