blob: d304337eac17e953ae006c4dfef54d527eb6034e [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.
#ifndef SRC_STORAGE_FS_TEST_FS_TEST_H_
#define SRC_STORAGE_FS_TEST_FS_TEST_H_
#include <fcntl.h>
#include <lib/zx/status.h>
#include <lib/zx/time.h>
#include <stdint.h>
#include <zircon/compiler.h>
#include <functional>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <fbl/unique_fd.h>
#include <ramdevice-client/ramnand.h>
#include "src/lib/storage/fs_management/cpp/admin.h"
#include "src/lib/storage/fs_management/cpp/format.h"
#include "src/lib/storage/fs_management/cpp/mount.h"
#include "src/storage/blobfs/blob_layout.h"
#include "src/storage/blobfs/compression_settings.h"
#include "src/storage/testing/ram_disk.h"
namespace fs_test {
class Filesystem;
using RamDevice = std::variant<storage::RamDisk, ramdevice_client::RamNand>;
struct TestFilesystemOptions {
static TestFilesystemOptions DefaultBlobfs();
static TestFilesystemOptions BlobfsWithoutFvm();
std::string description;
bool use_ram_nand = false;
// If set specifies a VMO to be used to back the device. If used for ram-nand, Its size must
// match the device size (if device_block_count is non-zero), including the extra required for
// OOB.
zx::unowned_vmo vmo;
bool use_fvm = false;
// If non-zero, create a dummy FVM partition which has the effect of moving the location of the
// partition under test to be at a different offset on the underlying device.
uint64_t dummy_fvm_partition_size = 0;
// If true, tests will avoid creating volumes smaller than the size given by
// device_block_size * device_block_count.
bool has_min_volume_size = false;
uint64_t device_block_size = 0;
uint64_t device_block_count = 0;
uint64_t fvm_slice_size = 0;
uint64_t initial_fvm_slice_count = 1;
// Only supported for blobfs for now.
uint64_t num_inodes = 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.
blobfs::BlobLayoutFormat blob_layout_format = blobfs::BlobLayoutFormat::kCompactMerkleTreeAtEnd;
// The compression algorithm blobfs should use for new files.
std::optional<blobfs::CompressionAlgorithm> blob_compression_algorithm = std::nullopt;
// If using ram_nand, the number of writes after which writes should fail.
uint32_t fail_after;
// If true, when the ram-disk is disconnected it will discard random writes performed since the
// last flush (which is all that any device will guarantee).
bool ram_disk_discard_random_after_last_flush = false;
};
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&)>);
TestFilesystemOptions OptionsWithDescription(std::string_view description);
// Returns device and device path.
zx::status<std::pair<RamDevice, std::string>> CreateRamDevice(const TestFilesystemOptions& options);
// Returns a handle to a test crypt service.
zx::status<zx::channel> GetCryptService();
// A file system instance is a specific instance created for test purposes.
class FilesystemInstance {
public:
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 fs_management::MountOptions& 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 storage::RamDisk* GetRamDisk() { return nullptr; }
virtual ramdevice_client::RamNand* GetRamNand() { return nullptr; }
virtual fidl::UnownedClientEnd<fuchsia_io::Directory> GetOutgoingDirectory() const {
return fidl::ClientEnd<fuchsia_io::Directory>();
}
virtual void ResetOutgoingDirectory() {}
};
// Base class for all supported file systems. It is a factory class that generates
// instances of FilesystemInstance subclasses.
class Filesystem {
public:
struct Traits {
std::string name;
zx::duration timestamp_granularity = zx::nsec(1);
bool supports_hard_links = true;
bool supports_mmap = false;
bool supports_mmap_shared_write = false;
bool supports_resize = false;
int64_t max_file_size = std::numeric_limits<int64_t>::max();
int64_t max_block_size = std::numeric_limits<int64_t>::max();
bool in_memory = false;
bool is_case_sensitive = true;
bool supports_sparse_files = true;
bool is_slow = false;
bool supports_fsck_after_every_transaction = false;
bool has_directory_size_limit = false;
bool is_journaled = true;
bool supports_watch_event_deleted = true;
bool supports_inspect = false;
bool supports_shutdown_on_no_connections = false;
bool uses_crypt = false;
};
virtual ~Filesystem() = default;
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 {
public:
static const T& SharedInstance() {
static const auto* const kInstance = new T();
return *kInstance;
}
};
template <typename T>
class FilesystemImplWithDefaultMake : public FilesystemImpl<T> {
public:
virtual std::unique_ptr<FilesystemInstance> Create(RamDevice device,
std::string device_path) const = 0;
zx::status<std::unique_ptr<FilesystemInstance>> Make(
const TestFilesystemOptions& options) const override {
auto result = CreateRamDevice(options);
if (result.is_error()) {
return result.take_error();
}
auto [device, device_path] = std::move(result).value();
auto instance = Create(std::move(device), std::move(device_path));
zx::status<> status = instance->Format(options);
if (status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(instance));
}
};
// -- Default implementations that use fs-management --
zx::status<> FsFormat(const std::string& device_path, fs_management::DiskFormat format,
const fs_management::MkfsOptions& options);
zx::status<fidl::ClientEnd<fuchsia_io::Directory>> FsMount(
const std::string& device_path, const std::string& mount_path, fs_management::DiskFormat format,
const fs_management::MountOptions& mount_options);
zx::status<std::pair<RamDevice, std::string>> OpenRamDevice(const TestFilesystemOptions& options);
std::string StripTrailingSlash(const std::string& in);
// Removes `mount_path` from the namespace.
zx::status<> FsUnbind(const std::string& mount_path);
} // namespace fs_test
#endif // SRC_STORAGE_FS_TEST_FS_TEST_H_