| // Copyright 2023 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. |
| |
| // These tests verify basic functionality of `vfs::LazyDir`. For more comprehensive tests, see |
| // //src/storage/lib/vfs/cpp and //src/storage/conformance. |
| |
| #include <fcntl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/io.h> |
| #include <lib/vfs/cpp/lazy_dir.h> |
| #include <lib/vfs/cpp/pseudo_dir.h> |
| #include <sys/stat.h> |
| #include <zircon/status.h> |
| |
| #include <map> |
| #include <memory> |
| #include <mutex> |
| #include <string> |
| #include <string_view> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/lib/testing/loop_fixture/real_loop_fixture.h" |
| |
| namespace { |
| |
| // vfs::LazyDir is deprecated. See https://fxbug.dev/309685624 for details. |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| |
| // LazyDir that acts like a pseudo-dir for ease of testing. |
| class TestLazyDir : public vfs::LazyDir { |
| public: |
| struct Entry { |
| uint64_t id; |
| uint32_t mode_type; |
| std::unique_ptr<vfs::Node> node; |
| }; |
| |
| using EntryMap = std::map<std::string_view, Entry, std::less<>>; |
| |
| // Allows modification of the test directory's contents in a thread-safe manner. |
| void SetEntries(EntryMap entries) { |
| std::lock_guard guard(mutex_); |
| entries_ = std::move(entries); |
| } |
| |
| protected: |
| void GetContents(std::vector<LazyEntry>* out_vector) const override { |
| std::lock_guard guard(mutex_); |
| *out_vector = std::vector<LazyEntry>(); |
| out_vector->reserve(entries_.size()); |
| for (const auto& [name, entry] : entries_) { |
| out_vector->push_back(LazyEntry{ |
| .id = entry.id, |
| .name = std::string(name), |
| .type = entry.mode_type, |
| }); |
| } |
| } |
| |
| zx_status_t GetFile(Node** out_node, uint64_t id, std::string name) const override { |
| std::lock_guard guard(mutex_); |
| if (const auto entry = entries_.find(name); entry != entries_.cend()) { |
| if (id != entry->second.id) { |
| // The LazyDir type should guarantee consistency between the entry names and IDs. |
| return ZX_ERR_BAD_STATE; |
| } |
| *out_node = entry->second.node.get(); |
| return ZX_OK; |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| private: |
| mutable std::mutex mutex_; |
| EntryMap entries_ __TA_GUARDED(mutex_); |
| }; |
| |
| class LazyDirTest : public ::gtest::RealLoopFixture { |
| protected: |
| void SetUp() override { |
| root_dir_ = std::make_unique<vfs::PseudoDir>(); |
| test_dir_ = std::make_shared<TestLazyDir>(); |
| root_dir_->AddSharedEntry("test_lazy_dir", test_dir_); |
| zx::channel server; |
| ASSERT_EQ(zx::channel::create(0, &root_client_, &server), ZX_OK); |
| ASSERT_EQ(root_dir_->Serve( |
| fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE, |
| std::move(server)), |
| ZX_OK); |
| } |
| |
| TestLazyDir& test_dir() { return *test_dir_; } |
| |
| // Consumes and opens the test directory as a file descriptor. This must be called from a |
| // different thread than the one serving the connection and can only be called once. |
| fbl::unique_fd open_test_dir_fd() { |
| ZX_ASSERT_MSG(root_client_.is_valid(), "open_test_dir_fd() can only be called once per test!"); |
| fbl::unique_fd root_fd; |
| zx_status_t status = fdio_fd_create(root_client_.release(), root_fd.reset_and_get_address()); |
| ZX_ASSERT_MSG(status == ZX_OK, "Failed to create fd: %s", zx_status_get_string(status)); |
| |
| fbl::unique_fd fd(openat(root_fd.get(), "test_lazy_dir", O_DIRECTORY)); |
| ZX_ASSERT_MSG(fd, "Failed to open entry: %s", strerror(errno)); |
| return fd; |
| } |
| |
| private: |
| std::unique_ptr<vfs::PseudoDir> root_dir_; |
| std::shared_ptr<TestLazyDir> test_dir_; |
| zx::channel root_client_; |
| }; |
| |
| #pragma clang diagnostic pop |
| |
| TEST_F(LazyDirTest, ValidateEntries) { |
| PerformBlockingWork([this] { |
| auto root = open_test_dir_fd(); |
| |
| // Directory should be empty initially. |
| fbl::unique_fd fd(openat(root.get(), "does not exist", O_DIRECTORY)); |
| ASSERT_FALSE(fd) << "Opened non-existing entry!"; |
| ASSERT_EQ(errno, ENOENT) << strerror(errno); |
| |
| // Create some entries in the format: |
| // entry_a/ |
| // entry_b/ |
| // subentry |
| TestLazyDir::EntryMap entries; |
| |
| auto entry_a = std::make_unique<vfs::PseudoDir>(); |
| entries["entry_a"] = TestLazyDir::Entry{ |
| .id = 2u, |
| .mode_type = fuchsia::io::MODE_TYPE_DIRECTORY, |
| .node = std::move(entry_a), |
| }; |
| |
| auto entry_b = std::make_unique<vfs::PseudoDir>(); |
| auto subentry = std::make_unique<vfs::PseudoDir>(); |
| ASSERT_EQ(entry_b->AddEntry("subentry", std::move(subentry)), ZX_OK); |
| entries["entry_b"] = TestLazyDir::Entry{ |
| .id = 5u, |
| .mode_type = fuchsia::io::MODE_TYPE_DIRECTORY, |
| .node = std::move(entry_b), |
| }; |
| |
| test_dir().SetEntries(std::move(entries)); |
| |
| // Verify that we can open all the entries. |
| fd = fbl::unique_fd(openat(root.get(), "entry_a", O_DIRECTORY)); |
| ASSERT_TRUE(fd) << strerror(errno); |
| fd = fbl::unique_fd(openat(root.get(), "entry_b", O_DIRECTORY)); |
| ASSERT_TRUE(fd) << strerror(errno); |
| fd = fbl::unique_fd(openat(root.get(), "entry_b/subentry", O_DIRECTORY)); |
| ASSERT_TRUE(fd) << strerror(errno); |
| |
| // Now clear all entries and ensure we cannot open any. |
| test_dir().SetEntries({}); |
| |
| fd = fbl::unique_fd(openat(root.get(), "entry_a", O_DIRECTORY)); |
| ASSERT_FALSE(fd) << "Opened non-existing entry!"; |
| ASSERT_EQ(errno, ENOENT) << strerror(errno); |
| fd = fbl::unique_fd(openat(root.get(), "entry_b", O_DIRECTORY)); |
| ASSERT_FALSE(fd) << "Opened non-existing entry!"; |
| ASSERT_EQ(errno, ENOENT) << strerror(errno); |
| fd = fbl::unique_fd(openat(root.get(), "entry_b/subentry", O_DIRECTORY)); |
| ASSERT_FALSE(fd) << "Opened non-existing entry!"; |
| ASSERT_EQ(errno, ENOENT) << strerror(errno); |
| }); |
| } |
| |
| } // namespace |