| // Copyright 2019 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 "lib/vfs/cpp/pseudo_dir.h" |
| |
| #include <lib/fdio/vfs.h> |
| #include <lib/gtest/real_loop_fixture.h> |
| #include <lib/vfs/cpp/pseudo_file.h> |
| |
| namespace { |
| |
| class TestNode : public vfs::Node { |
| public: |
| TestNode(std::function<void()> death_callback = nullptr) |
| : death_callback_(death_callback) {} |
| ~TestNode() override { |
| if (death_callback_) { |
| death_callback_(); |
| } |
| } |
| |
| private: |
| bool IsDirectory() const override { return false; } |
| |
| void Describe(fuchsia::io::NodeInfo* out_info) override {} |
| |
| zx_status_t CreateConnection( |
| uint32_t flags, std::unique_ptr<vfs::Connection>* connection) override { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| std::function<void()> death_callback_; |
| }; |
| |
| class PseudoDirUnit : public ::testing::Test { |
| protected: |
| void Init(int number_of_nodes) { |
| nodes_.resize(number_of_nodes); |
| node_names_.resize(number_of_nodes); |
| |
| for (int i = 0; i < number_of_nodes; i++) { |
| node_names_[i] = "node" + std::to_string(i); |
| nodes_[i] = std::make_shared<TestNode>(); |
| ASSERT_EQ(ZX_OK, dir_.AddSharedEntry(node_names_[i], nodes_[i])); |
| } |
| } |
| |
| vfs::PseudoDir dir_; |
| std::vector<std::string> node_names_; |
| std::vector<std::shared_ptr<TestNode>> nodes_; |
| }; |
| |
| TEST_F(PseudoDirUnit, NotEmpty) { |
| Init(1); |
| ASSERT_FALSE(dir_.IsEmpty()); |
| } |
| |
| TEST_F(PseudoDirUnit, Empty) { |
| Init(0); |
| ASSERT_TRUE(dir_.IsEmpty()); |
| } |
| |
| TEST_F(PseudoDirUnit, Lookup) { |
| Init(10); |
| for (int i = 0; i < 10; i++) { |
| vfs::Node* n; |
| ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[i], &n)) |
| << "for " << node_names_[i]; |
| ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i]; |
| } |
| } |
| |
| TEST_F(PseudoDirUnit, LookupUniqueNode) { |
| Init(1); |
| |
| auto node = std::make_unique<TestNode>(); |
| vfs::Node* node_ptr = node.get(); |
| ASSERT_EQ(ZX_OK, dir_.AddEntry("un", std::move(node))); |
| vfs::Node* n; |
| ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[0], &n)); |
| ASSERT_EQ(nodes_[0].get(), n); |
| |
| ASSERT_EQ(ZX_OK, dir_.Lookup("un", &n)); |
| ASSERT_EQ(node_ptr, n); |
| } |
| |
| TEST_F(PseudoDirUnit, InvalidLookup) { |
| Init(3); |
| vfs::Node* n; |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup("invalid", &n)); |
| } |
| |
| TEST_F(PseudoDirUnit, RemoveEntry) { |
| Init(5); |
| for (int i = 0; i < 5; i++) { |
| ASSERT_EQ(2, nodes_[i].use_count()); |
| ASSERT_EQ(ZX_OK, dir_.RemoveEntry(node_names_[i])) |
| << "for " << node_names_[i]; |
| |
| // cannot access |
| vfs::Node* n; |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup(node_names_[i], &n)) |
| << "for " << node_names_[i]; |
| // check that use count went doen by 1 |
| ASSERT_EQ(1, nodes_[i].use_count()); |
| } |
| ASSERT_TRUE(dir_.IsEmpty()); |
| } |
| |
| TEST_F(PseudoDirUnit, RemoveUniqueNode) { |
| Init(0); |
| |
| bool node_died = false; |
| auto node = std::make_unique<TestNode>([&]() { node_died = true; }); |
| EXPECT_FALSE(node_died); |
| ASSERT_EQ(ZX_OK, dir_.AddEntry("un", std::move(node))); |
| ASSERT_EQ(ZX_OK, dir_.RemoveEntry("un")); |
| EXPECT_TRUE(node_died); |
| |
| vfs::Node* n; |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.Lookup("un", &n)); |
| } |
| |
| TEST_F(PseudoDirUnit, RemoveInvalidEntry) { |
| Init(5); |
| ASSERT_EQ(ZX_ERR_NOT_FOUND, dir_.RemoveEntry("invalid")); |
| |
| // make sure nothing was removed |
| for (int i = 0; i < 5; i++) { |
| vfs::Node* n; |
| ASSERT_EQ(ZX_OK, dir_.Lookup(node_names_[i], &n)) |
| << "for " << node_names_[i]; |
| ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i]; |
| } |
| } |
| |
| TEST_F(PseudoDirUnit, AddAfterRemove) { |
| Init(5); |
| ASSERT_EQ(ZX_OK, dir_.RemoveEntry(node_names_[2])); |
| |
| auto new_node = std::make_shared<TestNode>(); |
| ASSERT_EQ(ZX_OK, dir_.AddSharedEntry("new_node", new_node)); |
| |
| for (int i = 0; i < 5; i++) { |
| zx_status_t expected_status = ZX_OK; |
| if (i == 2) { |
| expected_status = ZX_ERR_NOT_FOUND; |
| } |
| vfs::Node* n; |
| ASSERT_EQ(expected_status, dir_.Lookup(node_names_[i], &n)) |
| << "for " << node_names_[i]; |
| if (expected_status == ZX_OK) { |
| ASSERT_EQ(nodes_[i].get(), n) << "for " << node_names_[i]; |
| } |
| } |
| |
| vfs::Node* n; |
| ASSERT_EQ(ZX_OK, dir_.Lookup("new_node", &n)); |
| ASSERT_EQ(new_node.get(), n); |
| } |
| |
| class Dirent { |
| public: |
| uint64_t ino_; |
| uint8_t type_; |
| uint8_t size_; |
| std::string name_; |
| |
| uint64_t size_in_bytes_; |
| |
| static Dirent DirentForDot() { return DirentForDirectory("."); } |
| |
| static Dirent DirentForDirectory(const std::string& name) { |
| return Dirent(fuchsia::io::INO_UNKNOWN, fuchsia::io::DIRENT_TYPE_DIRECTORY, |
| name); |
| } |
| |
| static Dirent DirentForFile(const std::string& name) { |
| return Dirent(fuchsia::io::INO_UNKNOWN, fuchsia::io::DIRENT_TYPE_FILE, |
| name); |
| } |
| |
| std::string String() { |
| return "Dirent:\nino: " + std ::to_string(ino_) + |
| "\ntype: " + std ::to_string(type_) + |
| "\nsize: " + std ::to_string(size_) + "\nname: " + name_; |
| } |
| |
| private: |
| Dirent(uint64_t ino, uint8_t type, const std::string& name) |
| : ino_(ino), |
| type_(type), |
| size_(static_cast<uint8_t>(name.length())), |
| name_(name) { |
| ZX_DEBUG_ASSERT(name.length() <= static_cast<uint64_t>(NAME_MAX)); |
| size_in_bytes_ = sizeof(vdirent_t) + size_; |
| } |
| }; |
| |
| class DirectoryWrapper { |
| public: |
| DirectoryWrapper(bool start_loop = true) |
| : dir_(std::make_shared<vfs::PseudoDir>()), |
| loop_(&kAsyncLoopConfigNoAttachToThread) { |
| if (start_loop) { |
| loop_.StartThread("vfs test thread"); |
| } |
| } |
| |
| void AddEntry(const std::string& name, std::unique_ptr<vfs::Node> node, |
| zx_status_t expected_status = ZX_OK) { |
| ASSERT_EQ(expected_status, dir_->AddEntry(name, std::move(node))); |
| } |
| |
| void AddSharedEntry(const std::string& name, std::shared_ptr<vfs::Node> node, |
| zx_status_t expected_status = ZX_OK) { |
| ASSERT_EQ(expected_status, dir_->AddSharedEntry(name, std::move(node))); |
| } |
| |
| fuchsia::io::DirectorySyncPtr Serve( |
| int flags = fuchsia::io::OPEN_RIGHT_READABLE) { |
| fuchsia::io::DirectorySyncPtr ptr; |
| dir_->Serve(flags, ptr.NewRequest().TakeChannel(), loop_.dispatcher()); |
| return ptr; |
| } |
| |
| void AddReadOnlyFile(const std::string& file_name, |
| const std::string& file_content, |
| zx_status_t expected_status = ZX_OK) { |
| auto read_fn = [file_content](std::vector<uint8_t>* output) { |
| output->resize(file_content.length()); |
| std::copy(file_content.begin(), file_content.end(), output->begin()); |
| return ZX_OK; |
| }; |
| |
| auto file = |
| std::make_unique<vfs::BufferedPseudoFile>(std::move(read_fn), nullptr); |
| |
| AddEntry(file_name, std::move(file)); |
| } |
| |
| std::shared_ptr<vfs::PseudoDir>& dir() { return dir_; }; |
| |
| private: |
| std::shared_ptr<vfs::PseudoDir> dir_; |
| async::Loop loop_; |
| }; |
| |
| class PseudoDirConnection : public gtest::RealLoopFixture { |
| protected: |
| void AssertReadDirents(fuchsia::io::DirectorySyncPtr& ptr, uint64_t max_bytes, |
| std::vector<Dirent>& expected_dirents, |
| zx_status_t expected_status = ZX_OK) { |
| std::vector<uint8_t> out_dirents; |
| zx_status_t status; |
| ptr->ReadDirents(max_bytes, &status, &out_dirents); |
| ASSERT_EQ(expected_status, status); |
| if (status != ZX_OK) { |
| return; |
| } |
| uint64_t expected_size = 0; |
| for (auto& d : expected_dirents) { |
| expected_size += d.size_in_bytes_; |
| } |
| EXPECT_EQ(expected_size, out_dirents.size()); |
| uint64_t offset = 0; |
| auto data_ptr = out_dirents.data(); |
| for (auto& d : expected_dirents) { |
| SCOPED_TRACE(d.String()); |
| ASSERT_LE(sizeof(vdirent_t), out_dirents.size() - offset); |
| vdirent_t* de = reinterpret_cast<vdirent_t*>(data_ptr + offset); |
| EXPECT_EQ(d.ino_, de->ino); |
| EXPECT_EQ(d.size_, de->size); |
| EXPECT_EQ(d.type_, de->type); |
| ASSERT_LE(d.size_in_bytes_, out_dirents.size() - offset); |
| EXPECT_EQ(d.name_, std::string(de->name, de->size)); |
| |
| offset += sizeof(vdirent_t) + de->size; |
| } |
| } |
| |
| void AssertRewind(fuchsia::io::DirectorySyncPtr& ptr, |
| zx_status_t expected_status = ZX_OK) { |
| zx_status_t status; |
| ptr->Rewind(&status); |
| ASSERT_EQ(expected_status, status); |
| } |
| |
| void AssertOpen(async_dispatcher_t* dispatcher, uint32_t flags, |
| zx_status_t expected_status, bool test_on_open_event = true) { |
| fuchsia::io::NodePtr node_ptr; |
| if (test_on_open_event) { |
| flags |= fuchsia::io::OPEN_FLAG_DESCRIBE; |
| } |
| EXPECT_EQ(expected_status, |
| dir_.dir()->Serve(flags, node_ptr.NewRequest().TakeChannel(), |
| dispatcher)); |
| |
| if (test_on_open_event) { |
| bool on_open_called = false; |
| node_ptr.events().OnOpen = |
| [&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) { |
| EXPECT_FALSE(on_open_called); // should be called only once |
| on_open_called = true; |
| EXPECT_EQ(expected_status, status); |
| if (expected_status == ZX_OK) { |
| ASSERT_NE(info.get(), nullptr); |
| EXPECT_TRUE(info->is_directory()); |
| } else { |
| EXPECT_EQ(info.get(), nullptr); |
| } |
| }; |
| |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&]() { return on_open_called; }, |
| zx::sec(1), zx::msec(1))); |
| } |
| } |
| |
| template <typename T> |
| void AssertOpenPath(fuchsia::io::DirectorySyncPtr& dir_ptr, |
| const std::string& path, |
| ::fidl::SynchronousInterfacePtr<T>& out_sync_ptr, |
| uint32_t flags = 0, uint32_t mode = 0, |
| zx_status_t expected_status = ZX_OK) { |
| ::fidl::InterfacePtr<fuchsia::io::Node> node_ptr; |
| dir_ptr->Open(flags | fuchsia::io::OPEN_FLAG_DESCRIBE, mode, path, |
| node_ptr.NewRequest()); |
| bool on_open_called = false; |
| node_ptr.events().OnOpen = |
| [&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> unused) { |
| EXPECT_FALSE(on_open_called); // should be called only once |
| on_open_called = true; |
| EXPECT_EQ(expected_status, status); |
| }; |
| |
| ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&]() { return on_open_called; }, |
| zx::sec(1), zx::msec(1))); |
| |
| // Bind channel to sync_ptr |
| out_sync_ptr.Bind(node_ptr.Unbind().TakeChannel()); |
| } |
| |
| void AssertRead(fuchsia::io::FileSyncPtr& file, int count, |
| const std::string& expected_str, |
| zx_status_t expected_status = ZX_OK) { |
| zx_status_t status; |
| std::vector<uint8_t> buffer; |
| file->Read(count, &status, &buffer); |
| ASSERT_EQ(expected_status, status); |
| std::string str(buffer.size(), 0); |
| std::copy(buffer.begin(), buffer.end(), str.begin()); |
| ASSERT_EQ(expected_str, str); |
| } |
| |
| DirectoryWrapper dir_; |
| }; |
| |
| TEST_F(PseudoDirConnection, ReadDirSimple) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("file3", "file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = { |
| Dirent::DirentForDot(), Dirent::DirentForDirectory("subdir"), |
| Dirent::DirentForFile("file1"), Dirent::DirentForFile("file2"), |
| Dirent::DirentForFile("file3"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirOnEmptyDirectory) { |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = { |
| Dirent::DirentForDot(), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirSizeLessThanFirstEntry) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents; |
| AssertReadDirents(ptr, sizeof(vdirent_t), expected_dirents, |
| ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirSizeLessThanEntry) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = {Dirent::DirentForDot()}; |
| AssertReadDirents(ptr, sizeof(vdirent_t) + 1, expected_dirents); |
| std::vector<Dirent> empty_dirents; |
| AssertReadDirents(ptr, sizeof(vdirent_t), empty_dirents, ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirInParts) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("file3", "file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForFile("file1"), |
| Dirent::DirentForFile("file2"), |
| Dirent::DirentForFile("file3"), |
| }; |
| AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1); |
| AssertReadDirents(ptr, 3 * sizeof(vdirent_t) + 20, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirWithExactBytes) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("file3", "file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = { |
| Dirent::DirentForDot(), Dirent::DirentForDirectory("subdir"), |
| Dirent::DirentForFile("file1"), Dirent::DirentForFile("file2"), |
| Dirent::DirentForFile("file3"), |
| }; |
| uint64_t exact_size = 0; |
| for (auto& d : expected_dirents) { |
| exact_size += d.size_in_bytes_; |
| } |
| |
| AssertReadDirents(ptr, exact_size, expected_dirents); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirInPartsWithExactBytes) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("file3", "file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForFile("file1"), |
| Dirent::DirentForFile("file2"), |
| Dirent::DirentForFile("file3"), |
| }; |
| uint64_t exact_size1 = 0; |
| for (auto& d : expected_dirents1) { |
| exact_size1 += d.size_in_bytes_; |
| } |
| |
| uint64_t exact_size2 = 0; |
| for (auto& d : expected_dirents2) { |
| exact_size2 += d.size_in_bytes_; |
| } |
| |
| AssertReadDirents(ptr, exact_size1, expected_dirents1); |
| AssertReadDirents(ptr, exact_size2, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirAfterFullRead) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| |
| std::vector<Dirent> empty_dirents; |
| |
| AssertReadDirents(ptr, 1024, expected_dirents); |
| AssertReadDirents(ptr, 1024, empty_dirents); |
| } |
| |
| TEST_F(PseudoDirConnection, RewindWorksAfterFullRead) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| |
| std::vector<Dirent> empty_dirents; |
| |
| AssertReadDirents(ptr, 1024, expected_dirents); |
| AssertReadDirents(ptr, 1024, empty_dirents); |
| |
| AssertRewind(ptr); |
| |
| AssertReadDirents(ptr, 1024, expected_dirents); |
| } |
| |
| TEST_F(PseudoDirConnection, RewindWorksAfterPartialRead) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("file3", "file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForFile("file1"), |
| Dirent::DirentForFile("file2"), |
| Dirent::DirentForFile("file3"), |
| }; |
| AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1); |
| AssertRewind(ptr); |
| AssertReadDirents(ptr, 2 * sizeof(vdirent_t) + 10, expected_dirents1); |
| AssertReadDirents(ptr, 3 * sizeof(vdirent_t) + 20, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirAfterAddingEntry) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents1); |
| |
| dir_.AddReadOnlyFile("file1", "file1"); |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForFile("file1"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirAndRewindAfterAddingEntry) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents1); |
| |
| dir_.AddReadOnlyFile("file1", "file1"); |
| AssertRewind(ptr); |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| Dirent::DirentForFile("file1"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, ReadDirAfterRemovingEntry) { |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<Dirent> expected_dirents1 = { |
| Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir"), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents1); |
| std::vector<Dirent> empty_dirents; |
| ASSERT_EQ(ZX_OK, dir_.dir()->RemoveEntry("subdir")); |
| AssertReadDirents(ptr, 1024, empty_dirents); |
| |
| // rewind and check again |
| AssertRewind(ptr); |
| |
| std::vector<Dirent> expected_dirents2 = { |
| Dirent::DirentForDot(), |
| }; |
| AssertReadDirents(ptr, 1024, expected_dirents2); |
| } |
| |
| TEST_F(PseudoDirConnection, CantReadNodeReferenceDir) { |
| auto ptr = dir_.Serve(fuchsia::io::OPEN_FLAG_NODE_REFERENCE); |
| // make sure node reference was opened |
| zx_status_t status; |
| fuchsia::io::NodeAttributes attr; |
| ASSERT_EQ(ZX_OK, ptr->GetAttr(&status, &attr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_DIRECTORY); |
| |
| std::vector<uint8_t> out_dirents; |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, ptr->ReadDirents(100, &status, &out_dirents)); |
| } |
| |
| TEST_F(PseudoDirConnection, ServeOnInValidFlags) { |
| uint32_t prohibitive_flags[] = {fuchsia::io::OPEN_RIGHT_ADMIN, |
| fuchsia::io::OPEN_FLAG_NO_REMOTE}; |
| uint32_t not_allowed_flags[] = { |
| fuchsia::io::OPEN_FLAG_CREATE, fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT, |
| fuchsia::io::OPEN_FLAG_TRUNCATE, fuchsia::io::OPEN_FLAG_APPEND}; |
| |
| for (auto not_allowed_flag : not_allowed_flags) { |
| SCOPED_TRACE(std::to_string(not_allowed_flag)); |
| AssertOpen(dispatcher(), not_allowed_flag, ZX_ERR_INVALID_ARGS); |
| } |
| |
| for (auto prohibitive_flag : prohibitive_flags) { |
| SCOPED_TRACE(std::to_string(prohibitive_flag)); |
| AssertOpen(dispatcher(), prohibitive_flag, ZX_ERR_NOT_SUPPORTED); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, ServeOnValidFlags) { |
| uint32_t allowed_flags[] = { |
| fuchsia::io::OPEN_RIGHT_READABLE, fuchsia::io::OPEN_RIGHT_WRITABLE, |
| fuchsia::io::OPEN_FLAG_NODE_REFERENCE, fuchsia::io::OPEN_FLAG_DIRECTORY}; |
| for (auto allowed_flag : allowed_flags) { |
| SCOPED_TRACE(std::to_string(allowed_flag)); |
| AssertOpen(dispatcher(), allowed_flag, ZX_OK); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenSelf) { |
| std::string paths[] = { |
| "", ".", "./", |
| ".//", "././", "././/.", |
| "././//", "././/", "././././/././././////./././//././/./././/././."}; |
| auto subdir = std::make_shared<vfs::PseudoDir>(); |
| dir_.AddSharedEntry("subdir", subdir); |
| auto ptr = dir_.Serve(); |
| std::vector<Dirent> expected_dirents = {Dirent::DirentForDot(), |
| Dirent::DirentForDirectory("subdir")}; |
| for (auto& path : paths) { |
| SCOPED_TRACE("path: " + path); |
| |
| fuchsia::io::DirectorySyncPtr new_ptr; |
| AssertOpenPath(ptr, path, new_ptr); |
| |
| // assert correct directory was opened |
| AssertReadDirents(new_ptr, 1024, expected_dirents); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenSubDir) { |
| DirectoryWrapper subdir1(false); |
| DirectoryWrapper subdir2(false); |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| dir_.AddSharedEntry("subdir2", subdir2.dir()); |
| subdir1.AddReadOnlyFile("file1", "file1"); |
| subdir2.AddReadOnlyFile("file2", "file2"); |
| |
| auto ptr = dir_.Serve(); |
| std::vector<Dirent> expected_dirents_sub1 = {Dirent::DirentForDot(), |
| Dirent::DirentForFile("file1")}; |
| std::vector<Dirent> expected_dirents_sub2 = {Dirent::DirentForDot(), |
| Dirent::DirentForFile("file2")}; |
| |
| std::string paths1[] = {"./subdir1", |
| "././subdir1", |
| ".//./././././/./subdir1", |
| "subdir1", |
| "subdir1/", |
| "subdir1/.", |
| "subdir1//", |
| "subdir1///", |
| "subdir1/./", |
| "subdir1/.//", |
| "subdir1/.//.", |
| "subdir1/.//././//./", |
| "subdir1/.//././/./."}; |
| for (auto& path : paths1) { |
| SCOPED_TRACE("path: " + path); |
| |
| fuchsia::io::DirectorySyncPtr new_ptr; |
| AssertOpenPath(ptr, path, new_ptr); |
| |
| // assert correct directory was opened |
| AssertReadDirents(new_ptr, 1024, expected_dirents_sub1); |
| } |
| |
| // test with other directory |
| std::string paths2[] = {"./subdir2", |
| "././subdir2", |
| ".//./././././/./subdir2", |
| "subdir2", |
| "subdir2/", |
| "subdir2/.", |
| "subdir2//", |
| "subdir2///", |
| "subdir2/./", |
| "subdir2/.//", |
| "subdir2/.//.", |
| "subdir2/.//././//./", |
| "subdir2/.//././/./."}; |
| for (auto& path : paths2) { |
| SCOPED_TRACE("path: " + path); |
| |
| fuchsia::io::DirectorySyncPtr new_ptr; |
| AssertOpenPath(ptr, path, new_ptr); |
| |
| // assert correct directory was opened |
| AssertReadDirents(new_ptr, 1024, expected_dirents_sub2); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenFile) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file2", "file2"); |
| dir_.AddReadOnlyFile("..foo", "..foo"); |
| dir_.AddReadOnlyFile("...foo", "...foo"); |
| dir_.AddReadOnlyFile(".foo", ".foo"); |
| |
| DirectoryWrapper subdir1(false); |
| DirectoryWrapper subdir2(false); |
| |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| dir_.AddSharedEntry("subdir2", subdir2.dir()); |
| subdir1.AddReadOnlyFile("file2", "subdir1/file2"); |
| subdir1.AddReadOnlyFile("file1", "subdir1/file1"); |
| subdir1.AddReadOnlyFile("..foo", "subdir1/..foo"); |
| subdir1.AddReadOnlyFile("...foo", "subdir1/...foo"); |
| subdir1.AddReadOnlyFile(".foo", "subdir1/.foo"); |
| subdir2.AddReadOnlyFile("file3", "subdir2/file3"); |
| subdir2.AddReadOnlyFile("file4", "subdir2/file4"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::string files[] = {"file1", "file2", ".foo", |
| "..foo", "...foo", "subdir1/file1", |
| "subdir1/file2", "subdir2/file3", "subdir2/file4", |
| "subdir1/.foo", "subdir1/..foo", "subdir1/...foo"}; |
| for (auto& file : files) { |
| SCOPED_TRACE("file: " + file); |
| fuchsia::io::FileSyncPtr file_ptr; |
| AssertOpenPath(ptr, file, file_ptr, fuchsia::io::OPEN_RIGHT_READABLE); |
| |
| AssertRead(file_ptr, 100, file); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenFileWithMultipleSlashesAndDotsInPath) { |
| DirectoryWrapper subdir1(false); |
| |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| subdir1.AddReadOnlyFile("file1", "file1"); |
| dir_.AddReadOnlyFile("file1", "file1"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::string files[] = {"./file1", |
| ".//file1", |
| "././/././///././file1", |
| "subdir1//file1", |
| "subdir1///file1", |
| "subdir1////file1", |
| "subdir1/./file1", |
| "subdir1/.//./file1", |
| "subdir1/././file1", |
| "subdir1/././///file1"}; |
| for (auto& file : files) { |
| SCOPED_TRACE("file: " + file); |
| fuchsia::io::FileSyncPtr file_ptr; |
| AssertOpenPath(ptr, file, file_ptr, fuchsia::io::OPEN_RIGHT_READABLE); |
| |
| AssertRead(file_ptr, 100, "file1"); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenWithInValidPaths) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| |
| DirectoryWrapper subdir1(false); |
| DirectoryWrapper subdir2(false); |
| |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| dir_.AddSharedEntry("subdir2", subdir2.dir()); |
| subdir1.AddReadOnlyFile("file1", "subdir1/file1"); |
| subdir2.AddReadOnlyFile("file3", "subdir2/file3"); |
| |
| auto ptr = dir_.Serve(); |
| |
| std::vector<std::string> not_found_paths = {"file", "subdir", "subdir1/file", |
| "subdir2/file1"}; |
| |
| std::string big_path(NAME_MAX + 1, 'a'); |
| std::vector<std::string> invalid_args_paths = {"..", |
| "../", |
| "subdir1/..", |
| "subdir1/../", |
| "subdir1/../file1", |
| "file1/../file1", |
| std::move(big_path)}; |
| |
| std::vector<std::string> not_dir_paths = { |
| "subdir1/file1/", "subdir1/file1//", "subdir1/file1///", |
| "subdir1/file1/.", "subdir1/file1/file2", "./file1/", |
| "./file1/.", "./file1/file2"}; |
| |
| std::vector<zx_status_t> expected_errors = { |
| ZX_ERR_NOT_FOUND, ZX_ERR_INVALID_ARGS, ZX_ERR_NOT_DIR}; |
| std::vector<std::vector<std::string>> invalid_paths = { |
| not_found_paths, invalid_args_paths, not_dir_paths}; |
| |
| // sanity check |
| ASSERT_EQ(expected_errors.size(), invalid_paths.size()); |
| |
| for (size_t i = 0; i < expected_errors.size(); i++) { |
| auto expected_status = expected_errors[i]; |
| auto& paths = invalid_paths[i]; |
| for (auto& path : paths) { |
| SCOPED_TRACE("path: " + path); |
| fuchsia::io::NodeSyncPtr file_ptr; |
| AssertOpenPath(ptr, path, file_ptr, 0, 0, expected_status); |
| } |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, CannotOpenFileWithDirectoryFlag) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| auto ptr = dir_.Serve(); |
| fuchsia::io::FileSyncPtr file_ptr; |
| AssertOpenPath(ptr, "file1", file_ptr, fuchsia::io::OPEN_FLAG_DIRECTORY, 0, |
| ZX_ERR_NOT_DIR); |
| } |
| |
| TEST_F(PseudoDirConnection, CannotOpenDirectoryWithInvalidFlags) { |
| uint32_t invalid_flags[] = { |
| fuchsia::io::OPEN_FLAG_CREATE, fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT, |
| fuchsia::io::OPEN_FLAG_TRUNCATE, fuchsia::io::OPEN_FLAG_APPEND}; |
| DirectoryWrapper subdir1(false); |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| |
| auto ptr = dir_.Serve(); |
| std::string paths[] = {".", "subdir1"}; |
| |
| for (auto& path : paths) { |
| for (auto flag : invalid_flags) { |
| SCOPED_TRACE("path: " + path + ", flag: " + std::to_string(flag)); |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(ptr, path, node_ptr, flag, 0, ZX_ERR_INVALID_ARGS); |
| } |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenDirWithCorrectMode) { |
| DirectoryWrapper subdir1(false); |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| |
| auto ptr = dir_.Serve(); |
| std::string paths[] = {".", "subdir1"}; |
| |
| uint32_t modes[] = {fuchsia::io::MODE_TYPE_DIRECTORY, V_IXUSR, V_IWUSR, |
| V_IRUSR}; |
| |
| for (auto& path : paths) { |
| for (auto mode : modes) { |
| SCOPED_TRACE("path: " + path + ", mode: " + std::to_string(mode)); |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(ptr, path, node_ptr, 0, mode); |
| } |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenDirWithInCorrectMode) { |
| DirectoryWrapper subdir1(false); |
| dir_.AddSharedEntry("subdir1", subdir1.dir()); |
| |
| auto ptr = dir_.Serve(); |
| std::string paths[] = {".", "subdir1"}; |
| |
| uint32_t modes[] = { |
| fuchsia::io::MODE_TYPE_FILE, fuchsia::io::MODE_TYPE_BLOCK_DEVICE, |
| fuchsia::io::MODE_TYPE_SOCKET, fuchsia::io::MODE_TYPE_SERVICE}; |
| |
| for (auto& path : paths) { |
| for (auto mode : modes) { |
| SCOPED_TRACE("path: " + path + ", mode: " + std::to_string(mode)); |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(ptr, path, node_ptr, 0, mode, ZX_ERR_INVALID_ARGS); |
| } |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenFileWithCorrectMode) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| auto ptr = dir_.Serve(); |
| |
| uint32_t modes[] = {fuchsia::io::MODE_TYPE_FILE, V_IXUSR, V_IWUSR, V_IRUSR}; |
| |
| for (auto mode : modes) { |
| SCOPED_TRACE("mode: " + std::to_string(mode)); |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(ptr, "file1", node_ptr, 0, mode); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, OpenFileWithInCorrectMode) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| auto ptr = dir_.Serve(); |
| |
| uint32_t modes[] = { |
| fuchsia::io::MODE_TYPE_DIRECTORY, fuchsia::io::MODE_TYPE_BLOCK_DEVICE, |
| fuchsia::io::MODE_TYPE_SOCKET, fuchsia::io::MODE_TYPE_SERVICE}; |
| |
| for (auto mode : modes) { |
| SCOPED_TRACE("mode: " + std::to_string(mode)); |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(ptr, "file1", node_ptr, 0, mode, ZX_ERR_INVALID_ARGS); |
| } |
| } |
| |
| TEST_F(PseudoDirConnection, CanCloneDirectoryConnection) { |
| dir_.AddReadOnlyFile("file1", "file1"); |
| auto ptr = dir_.Serve(); |
| fuchsia::io::DirectorySyncPtr cloned_ptr; |
| ptr->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>( |
| cloned_ptr.NewRequest().TakeChannel())); |
| |
| fuchsia::io::NodeSyncPtr node_ptr; |
| AssertOpenPath(cloned_ptr, "file1", node_ptr, 0, 0); |
| } |
| |
| TEST_F(PseudoDirConnection, NodeReferenceIsClonedAsNodeReference) { |
| fuchsia::io::DirectorySyncPtr cloned_ptr; |
| { |
| auto ptr = dir_.Serve(fuchsia::io::OPEN_FLAG_NODE_REFERENCE); |
| |
| ptr->Clone(0, fidl::InterfaceRequest<fuchsia::io::Node>( |
| cloned_ptr.NewRequest().TakeChannel())); |
| } |
| // make sure node reference was cloned |
| zx_status_t status; |
| fuchsia::io::NodeAttributes attr; |
| ASSERT_EQ(ZX_OK, cloned_ptr->GetAttr(&status, &attr)); |
| ASSERT_EQ(ZX_OK, status); |
| ASSERT_NE(0u, attr.mode | fuchsia::io::MODE_TYPE_DIRECTORY); |
| |
| std::vector<uint8_t> out_dirents; |
| ASSERT_EQ(ZX_ERR_PEER_CLOSED, |
| cloned_ptr->ReadDirents(100, &status, &out_dirents)); |
| } |
| |
| } // namespace |