| // 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; |
| } |