| // Copyright 2022 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 "src/storage/fs_test/test_filesystem.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/executor.h> |
| #include <lib/diagnostics/reader/cpp/archive_reader.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl/cpp/wire/channel.h> |
| #include <lib/sys/cpp/component_context.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include "sdk/lib/syslog/cpp/macros.h" |
| #include "src/storage/fs_test/crypt_service.h" |
| |
| namespace fs_test { |
| |
| using diagnostics::reader::InspectData; |
| |
| fs_management::MountOptions TestFilesystem::DefaultMountOptions() const { |
| fs_management::MountOptions options; |
| // Blobfs-specific options: |
| if (options_.blob_compression_algorithm) { |
| options.write_compression_algorithm = |
| blobfs::CompressionAlgorithmToString(*options_.blob_compression_algorithm); |
| } |
| // Other filesystem options: |
| if (GetTraits().uses_crypt) |
| options.crypt_client = [] { return *GetCryptService(); }; |
| return options; |
| } |
| |
| zx::result<TestFilesystem> TestFilesystem::FromInstance( |
| const TestFilesystemOptions& options, std::unique_ptr<FilesystemInstance> instance) { |
| static uint32_t mount_index; |
| TestFilesystem filesystem(options, std::move(instance), |
| std::string("/fs_test." + std::to_string(mount_index++) + "/")); |
| auto status = filesystem.Mount(); |
| if (status.is_error()) { |
| return status.take_error(); |
| } |
| return zx::ok(std::move(filesystem)); |
| } |
| |
| zx::result<TestFilesystem> TestFilesystem::Create(const TestFilesystemOptions& options) { |
| auto instance_or = options.filesystem->Make(options); |
| if (instance_or.is_error()) { |
| return instance_or.take_error(); |
| } |
| return FromInstance(options, std::move(instance_or).value()); |
| } |
| |
| zx::result<TestFilesystem> TestFilesystem::Open(const TestFilesystemOptions& options) { |
| auto instance_or = options.filesystem->Open(options); |
| if (instance_or.is_error()) { |
| return instance_or.take_error(); |
| } |
| return FromInstance(options, std::move(instance_or).value()); |
| } |
| |
| TestFilesystem::~TestFilesystem() { |
| if (filesystem_) { |
| if (mounted_) { |
| auto status = Unmount(); |
| if (status.is_error()) { |
| FX_LOGS(WARNING) << "Failed to unmount: " << status.status_string() << std::endl; |
| } |
| } |
| rmdir(mount_path_.c_str()); |
| } |
| } |
| |
| zx::result<> TestFilesystem::Mount(const fs_management::MountOptions& options) { |
| auto status = filesystem_->Mount(mount_path_, options); |
| if (status.is_ok()) { |
| mounted_ = true; |
| } |
| return status; |
| } |
| |
| zx::result<> TestFilesystem::Unmount() { |
| if (!filesystem_) { |
| return zx::ok(); |
| } |
| auto status = filesystem_->Unmount(mount_path_); |
| if (status.is_ok()) { |
| mounted_ = false; |
| } |
| return status; |
| } |
| |
| zx::result<> TestFilesystem::Fsck() { return filesystem_->Fsck(); } |
| |
| zx::result<std::string> TestFilesystem::DevicePath() const { return filesystem_->DevicePath(); } |
| |
| zx::result<fuchsia_io::wire::FilesystemInfo> TestFilesystem::GetFsInfo() const { |
| fbl::unique_fd root_fd = fbl::unique_fd(open(mount_path().c_str(), O_RDONLY | O_DIRECTORY)); |
| fdio_cpp::UnownedFdioCaller root_connection(root_fd); |
| const auto& result = fidl::WireCall(fidl::UnownedClientEnd<fuchsia_io::Directory>( |
| zx::unowned_channel(root_connection.borrow_channel()))) |
| ->QueryFilesystem(); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| if (result.value().s != ZX_OK) { |
| return zx::error(result.value().s); |
| } |
| return zx::ok(*result.value().info); |
| } |
| |
| void TestFilesystem::TakeSnapshot(std::optional<InspectData>* out) const { |
| ASSERT_NE(nullptr, out) << "out parameter must be non-null"; |
| ASSERT_EQ(std::nullopt, *out) << "out parameter will overwrite value already held"; |
| |
| async::Loop loop = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| loop.StartThread("inspect-snapshot-thread"); |
| async::Executor executor(loop.dispatcher()); |
| |
| std::condition_variable cv; |
| std::mutex m; |
| bool done = false; |
| |
| fpromise::result<std::vector<InspectData>, std::string> data_or_err; |
| auto component_selector = |
| diagnostics::reader::SanitizeMonikerForSelectors(filesystem_->GetMoniker()); |
| { |
| diagnostics::reader::ArchiveReader reader(executor.dispatcher(), |
| {component_selector + ":root"}); |
| auto promise = |
| reader.SnapshotInspectUntilPresent({filesystem_->GetMoniker()}) |
| .then([&](fpromise::result<std::vector<InspectData>, std::string>& inspect_data) { |
| { |
| std::unique_lock<std::mutex> lock(m); |
| data_or_err = std::move(inspect_data); |
| done = true; |
| } |
| cv.notify_all(); |
| }); |
| |
| executor.schedule_task(std::move(promise)); |
| |
| std::unique_lock<std::mutex> lock(m); |
| cv.wait(lock, [&done]() { return done; }); |
| } |
| loop.Quit(); |
| loop.JoinThreads(); |
| |
| ASSERT_TRUE(data_or_err.is_ok()) |
| << "Failed to obtain inspect tree snapshot: " << data_or_err.take_error(); |
| auto all = data_or_err.take_value(); |
| ASSERT_EQ(all.size(), 1ul) << "There should be exactly one matching Inspect hierarchy"; |
| *out = {std::move(all[0])}; |
| } |
| |
| } // namespace fs_test |