blob: 23cbe32d1c00206357281b18ccc20dcfa002017a [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 <fs-management/admin.h>
#include <fs-management/format.h>
#include <fs-management/mount.h>
#include <ramdevice-client/ramnand.h>
#include "src/storage/blobfs/blob_layout.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;
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;
// 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);
// 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 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 storage::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 {
public:
struct Traits {
std::string name;
zx::duration timestamp_granularity = zx::nsec(1);
bool supports_hard_links = true;
bool supports_mmap = false;
bool supports_resize = false;
off_t max_file_size = std::numeric_limits<off_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_fs_query = true;
bool supports_watch_event_deleted = true;
};
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, disk_format_t format,
const mkfs_options_t& options);
zx::status<> FsMount(const std::string& device_path, const std::string& mount_path,
disk_format_t format, const mount_options_t& mount_options,
zx::channel* outgoing_directory = nullptr);
// Unmounts using fs/Admin.Shutdown.
zx::status<> FsAdminUnmount(const std::string& mount_path, const zx::channel& outgoing_directory);
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_