blob: c25e340d9242fae4920bf7e414830256aac16a39 [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 <fidl/fuchsia.io.test/cpp/fidl.h>
#include <fidl/fuchsia.io/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <fbl/ref_ptr.h>
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "src/storage/lib/vfs/cpp/vfs_types.h"
#include "src/storage/minfs/bcache.h"
#include "src/storage/minfs/directory.h"
#include "src/storage/minfs/format.h"
#include "src/storage/minfs/minfs.h"
#include "src/storage/minfs/minfs_private.h"
#include "src/storage/minfs/runner.h"
#include "src/storage/minfs/vnode.h"
namespace fio = fuchsia_io;
namespace fio_test = fuchsia_io_test;
namespace minfs {
constexpr uint64_t kBlockCount = 1 << 11;
class MinfsHarness : public fidl::Server<fio_test::Io1Harness> {
public:
explicit MinfsHarness() : vfs_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
vfs_loop_.StartThread("vfs_thread");
auto device = std::make_unique<block_client::FakeBlockDevice>(kBlockCount, kMinfsBlockSize);
auto bcache = Bcache::Create(std::move(device), kBlockCount);
ZX_ASSERT(bcache.is_ok());
ZX_ASSERT(Mkfs(bcache.value().get()).is_ok());
auto runner = Runner::Create(vfs_loop_.dispatcher(), *std::move(bcache), {});
ZX_ASSERT(runner.is_ok());
runner_ = *std::move(runner);
// One connection must be maintained to avoid filesystem termination.
auto [root_client, root_server] = fidl::Endpoints<fio::Directory>::Create();
root_client_ = std::move(root_client);
zx::result status = runner_->ServeRoot(std::move(root_server));
ZX_ASSERT(status.is_ok());
}
~MinfsHarness() override {
// The runner shutdown takes care of shutting everything down in the right order, including the
// async loop.
runner_->Shutdown([](zx_status_t status) { ZX_ASSERT(status == ZX_OK); });
vfs_loop_.JoinThreads();
}
void GetConfig(GetConfigCompleter::Sync& completer) final {
fio_test::Io1Config config;
// Supported options
config.supports_open2(true);
config.supports_get_token(true);
config.supports_append(true);
config.supports_modify_directory(true);
config.supported_attributes(
fio::NodeAttributesQuery::kCreationTime | fio::NodeAttributesQuery::kModificationTime |
fio::NodeAttributesQuery::kId | fio::NodeAttributesQuery::kContentSize |
fio::NodeAttributesQuery::kStorageSize | fio::NodeAttributesQuery::kLinkCount);
completer.Reply(config);
}
void GetDirectory(GetDirectoryRequest& request, GetDirectoryCompleter::Sync& completer) final {
// Create a unique directory within the root of minfs for each request and populate it with the
// requested contents.
auto directory = CreateUniqueDirectory();
PopulateDirectory(request.root().entries(), *directory);
zx::result options = fs::VnodeConnectionOptions::FromOpen1Flags(request.flags());
ZX_ASSERT_MSG(options.is_ok(), "Failed to validate flags: %s", options.status_string());
zx_status_t status =
runner_->Serve(std::move(directory), request.directory_request().TakeChannel(), *options);
ZX_ASSERT_MSG(status == ZX_OK, "Failed to serve test directory: %s",
zx_status_get_string(status));
}
void PopulateDirectory(const std::vector<fidl::Box<fio_test::DirectoryEntry>>& entries,
Directory& dir) {
for (const auto& entry : entries) {
AddEntry(*entry, dir);
}
}
void AddEntry(const fio_test::DirectoryEntry& entry, Directory& parent) {
switch (entry.Which()) {
case fio_test::DirectoryEntry::Tag::kDirectory: {
zx::result vnode = parent.Create(entry.directory()->name(), fs::CreationType::kDirectory);
ZX_ASSERT_MSG(vnode.is_ok(), "Failed to create a directory: %s", vnode.status_string());
auto directory = fbl::RefPtr<Directory>::Downcast(*std::move(vnode));
ZX_ASSERT_MSG(directory != nullptr, "A vnode of the wrong type was created");
PopulateDirectory(entry.directory()->entries(), *directory);
// The directory was opened when it was created.
directory->Close();
break;
}
case fio_test::DirectoryEntry::Tag::kFile: {
zx::result file = parent.Create(entry.file()->name(), fs::CreationType::kFile);
ZX_ASSERT_MSG(file.is_ok(), "Failed to create a file: %s", file.status_string());
const auto& contents = entry.file()->contents();
if (!contents.empty()) {
size_t actual = 0;
zx_status_t status = file->Write(contents.data(), contents.size(),
/*offset=*/0, &actual);
ZX_ASSERT_MSG(status == ZX_OK, "Failed to write to file: %s",
zx_status_get_string(status));
}
// The file was opened when it was created.
file->Close();
break;
}
case fio_test::DirectoryEntry::Tag::kRemoteDirectory:
ZX_PANIC("Remote directories are not supported");
case fio_test::DirectoryEntry::Tag::kExecutableFile:
ZX_PANIC("Executable files are not supported");
}
}
fbl::RefPtr<Directory> GetRootNode() {
auto vn_or = runner_->minfs().VnodeGet(kMinfsRootIno);
ZX_ASSERT(vn_or.is_ok());
auto root = fbl::RefPtr<Directory>::Downcast(std::move(vn_or.value()));
ZX_ASSERT_MSG(root != nullptr, "The root node wasn't a directory");
return root;
}
fbl::RefPtr<Directory> CreateUniqueDirectory() {
++directory_count_;
std::string directory_name = std::to_string(directory_count_);
fbl::RefPtr<Directory> root = GetRootNode();
zx::result vnode = root->Create(directory_name, fs::CreationType::kDirectory);
ZX_ASSERT_MSG(vnode.is_ok(), "Failed to create a unique directory: %s", vnode.status_string());
auto directory = fbl::RefPtr<Directory>::Downcast(*std::move(vnode));
ZX_ASSERT_MSG(directory != nullptr, "A vnode of the wrong type was created");
return directory;
}
private:
async::Loop vfs_loop_;
std::unique_ptr<Runner> runner_;
// Used to create a new unique directory within minfs for every call to |GetDirectory|.
uint32_t directory_count_ = 0;
fidl::ClientEnd<fio::Directory> root_client_;
};
} // namespace minfs
int main(int argc, const char** argv) {
fuchsia_logging::SetTags({"io_conformance_harness_minfs"});
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
component::OutgoingDirectory outgoing = component::OutgoingDirectory(loop.dispatcher());
zx::result result = outgoing.ServeFromStartupInfo();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to serve outgoing directory: " << result.status_string();
return EXIT_FAILURE;
}
result = outgoing.AddProtocol<fio_test::Io1Harness>(std::make_unique<minfs::MinfsHarness>());
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to server test harness: " << result.status_string();
return EXIT_FAILURE;
}
loop.Run();
return EXIT_SUCCESS;
}