| // 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. |
| |
| // This file includes basic VFS file/directory connection tests. For comprehensive behavioral tests, |
| // see the fuchsia.io Conformance Test Suite in //src/storage/conformance. |
| |
| #include <fidl/fuchsia.io/cpp/fidl.h> |
| #include <fidl/fuchsia.io/cpp/wire_test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <stdio.h> |
| #include <zircon/errors.h> |
| |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "src/storage/lib/vfs/cpp/pseudo_dir.h" |
| #include "src/storage/lib/vfs/cpp/pseudo_file.h" |
| #include "src/storage/lib/vfs/cpp/synchronous_vfs.h" |
| |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| |
| zx_status_t DummyReader(fbl::String* output) { return ZX_OK; } |
| |
| zx_status_t DummyWriter(std::string_view input) { return ZX_OK; } |
| |
| // Example vnode that supports protocol negotiation. Here the vnode may be opened as a file or a |
| // directory. |
| class FileOrDirectory : public fs::Vnode { |
| public: |
| FileOrDirectory() = default; |
| |
| fuchsia_io::NodeProtocolKinds GetProtocols() const final { |
| return fuchsia_io::NodeProtocolKinds::kFile | fuchsia_io::NodeProtocolKinds::kDirectory; |
| } |
| |
| fs::VnodeAttributesQuery SupportedMutableAttributes() const final { |
| return fs::VnodeAttributesQuery::kModificationTime; |
| } |
| |
| zx::result<fs::VnodeAttributes> GetAttributes() const final { |
| return zx::ok(fs::VnodeAttributes{ |
| .id = 1234, |
| .modification_time = modification_time_, |
| }); |
| } |
| |
| bool ValidateRights(fio::Rights rights) const final { |
| return (rights & fio::Rights::kExecute) == fio::Rights(); |
| } |
| |
| zx::result<> UpdateAttributes(const fs::VnodeAttributesUpdate& attributes) final { |
| // Attributes not reported by |SupportedMutableAttributes()| should never be set in |
| // |attributes|. |
| ZX_ASSERT(!attributes.creation_time); |
| modification_time_ = *attributes.modification_time; |
| return zx::ok(); |
| } |
| |
| private: |
| uint64_t modification_time_; |
| }; |
| |
| // Helper method to monitor the OnOpen event, used by the tests below when |
| // OPEN_FLAG_DESCRIBE is used. |
| zx::result<fio::wire::NodeInfoDeprecated> GetOnOpenResponse( |
| fidl::UnownedClientEnd<fio::Node> channel) { |
| class EventHandler final : public fidl::testing::WireSyncEventHandlerTestBase<fio::Node> { |
| public: |
| void OnOpen(fidl::WireEvent<fio::Node::OnOpen>* event) override { |
| ASSERT_NE(event, nullptr); |
| response = std::move(*event); |
| } |
| |
| void NotImplemented_(const std::string& name) override { |
| ADD_FAILURE("unexpected %s", name.c_str()); |
| } |
| |
| fidl::WireEvent<fio::Node::OnOpen> response; |
| }; |
| |
| EventHandler event_handler; |
| const fidl::Status result = event_handler.HandleOneEvent(channel); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| fidl::WireEvent<fio::Node::OnOpen>& response = event_handler.response; |
| if (response.s != ZX_OK) { |
| return zx::error(response.s); |
| } |
| if (!response.info.has_value()) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| // In the success case, dispatch a trivial method to synchronize with the VFS; at the time of |
| // writing, the VFS implementation sends the event *before* starting to service the channel. This |
| // can lead to races with test teardown where binding the channel happens after the dispatcher has |
| // been shutdown, which results in a panic in the FIDL runtime. |
| { |
| const fidl::WireResult result = fidl::WireCall(channel)->GetFlags(); |
| zx_status_t status = result.ok() ? zx_status_t{result.value().s} : result.status(); |
| if (status != ZX_OK) { |
| ADD_FAILURE("fuchisa.io/Node.GetFlags failed unexpectedly: %s", zx_status_get_string(status)); |
| return zx::error(status); |
| } |
| } |
| return zx::ok(std::move(response.info.value())); |
| } |
| |
| // Helper method to monitor the OnRepresentation event. Used by the tests below to decode the |
| // fuchsia.io/Node.OnRepresentation event, or to check for the correct epitaph on errors. |
| zx::result<fio::Representation> GetOnRepresentation(fidl::UnownedClientEnd<fio::Node> channel) { |
| struct EventHandler final : public fidl::testing::WireSyncEventHandlerTestBase<fio::Node> { |
| public: |
| void OnRepresentation(fidl::WireEvent<fio::Node::OnRepresentation>* event) override { |
| ZX_ASSERT(event); |
| representation = fidl::ToNatural(std::move(*event)); |
| } |
| |
| void NotImplemented_(const std::string& name) override { |
| ADD_FAILURE("unexpected %s", name.c_str()); |
| } |
| |
| std::optional<fio::Representation> representation = std::nullopt; |
| }; |
| |
| EventHandler event_handler; |
| const fidl::Status result = event_handler.HandleOneEvent(channel); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| ZX_ASSERT(event_handler.representation); |
| return zx::ok(std::move(*event_handler.representation)); |
| } |
| |
| class VfsTestSetup : public zxtest::Test { |
| public: |
| // Setup file structure with one directory and one file. Note: On creation directories and files |
| // have no flags and rights. |
| VfsTestSetup() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { |
| vfs_.SetDispatcher(loop_.dispatcher()); |
| root_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| dir_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| file_ = fbl::MakeRefCounted<fs::BufferedPseudoFile>(&DummyReader, &DummyWriter); |
| file_or_dir_ = fbl::MakeRefCounted<FileOrDirectory>(); |
| root_->AddEntry("dir", dir_); |
| root_->AddEntry("file", file_); |
| root_->AddEntry("file_or_dir", file_or_dir_); |
| } |
| |
| zx_status_t ConnectClient(fidl::ServerEnd<fio::Directory> server_end) { |
| // Serve root directory with maximum rights |
| return vfs_.ServeDirectory(root_, std::move(server_end)); |
| } |
| |
| void SetReadonly() { vfs_.SetReadonly(true); } |
| |
| protected: |
| void SetUp() override { loop_.StartThread(); } |
| |
| void TearDown() override { loop_.RunUntilIdle(); } |
| |
| private: |
| async::Loop loop_; |
| fs::SynchronousVfs vfs_; |
| fbl::RefPtr<fs::PseudoDir> root_; |
| fbl::RefPtr<fs::PseudoDir> dir_; |
| fbl::RefPtr<fs::Vnode> file_; |
| fbl::RefPtr<FileOrDirectory> file_or_dir_; |
| }; |
| |
| using ConnectionTest = VfsTestSetup; |
| |
| TEST_F(ConnectionTest, NodeGetSetFlagsOnFile) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to File |
| zx::result fc = fidl::CreateEndpoints<fio::File>(); |
| ASSERT_OK(fc.status_value()); |
| ASSERT_OK(fdio_open_at(root.client.channel().get(), "file", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable), |
| fc->server.TakeChannel().release())); |
| |
| // Use GetFlags to get current flags and rights |
| auto file_get_result = fidl::WireCall(fc->client)->GetFlags(); |
| EXPECT_OK(file_get_result.status()); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable, file_get_result->flags); |
| { |
| // Make modifications to flags with SetFlags: Note this only works for kOpenFlagAppend |
| // based on posix standard |
| auto file_set_result = fidl::WireCall(fc->client)->SetFlags(fio::OpenFlags::kAppend); |
| EXPECT_OK(file_set_result->s); |
| } |
| { |
| // Check that the new flag is saved |
| auto file_get_result = fidl::WireCall(fc->client)->GetFlags(); |
| EXPECT_OK(file_get_result->s); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable | fio::OpenFlags::kAppend, file_get_result->flags); |
| } |
| } |
| |
| TEST_F(ConnectionTest, NodeGetSetFlagsOnDirectory) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to Directory |
| zx::result dc = fidl::CreateEndpoints<fio::Directory>(); |
| ASSERT_OK(dc.status_value()); |
| ASSERT_OK(fdio_open_at( |
| root.client.channel().get(), "dir", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kRightWritable), |
| dc->server.TakeChannel().release())); |
| |
| // Directories don't have settable flags, only report RIGHT_* flags. |
| auto dir_get_result = fidl::WireCall(dc->client)->GetFlags(); |
| EXPECT_OK(dir_get_result->s); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable | fio::OpenFlags::kRightWritable, dir_get_result->flags); |
| |
| // Directories do not support setting flags. |
| auto dir_set_result = fidl::WireCall(dc->client)->SetFlags(fio::OpenFlags::kAppend); |
| EXPECT_EQ(dir_set_result->s, ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| TEST_F(ConnectionTest, PosixFlagDirectoryRightExpansion) { |
| // Create connection to VFS with all rights. |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Combinations of POSIX flags to be tested. |
| const fio::OpenFlags OPEN_FLAG_COMBINATIONS[]{ |
| fio::OpenFlags::kPosixWritable, fio::OpenFlags::kPosixExecutable, |
| fio::OpenFlags::kPosixWritable | fio::OpenFlags::kPosixExecutable}; |
| |
| for (const fio::OpenFlags OPEN_FLAGS : OPEN_FLAG_COMBINATIONS) { |
| // Connect to drectory specifying the flag combination we want to test. |
| zx::result dc = fidl::CreateEndpoints<fio::Directory>(); |
| ASSERT_OK(dc.status_value()); |
| ASSERT_OK(fdio_open_at(root.client.channel().get(), "dir", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | OPEN_FLAGS), |
| dc->server.TakeChannel().release())); |
| |
| // Ensure flags match those which we expect. |
| auto dir_get_result = fidl::WireCall(dc->client)->GetFlags(); |
| EXPECT_OK(dir_get_result->s); |
| auto dir_flags = dir_get_result->flags; |
| EXPECT_TRUE(fio::OpenFlags::kRightReadable & dir_flags); |
| // Each POSIX flag should be expanded to its respective right(s). |
| if (OPEN_FLAGS & fio::OpenFlags::kPosixWritable) |
| EXPECT_TRUE(fio::OpenFlags::kRightWritable & dir_flags); |
| if (OPEN_FLAGS & fio::OpenFlags::kPosixExecutable) |
| EXPECT_TRUE(fio::OpenFlags::kRightExecutable & dir_flags); |
| |
| // Repeat test, but for file, which should not have any expanded rights. |
| auto fc = fidl::Endpoints<fio::File>::Create(); |
| ASSERT_OK(fdio_open_at(root.client.channel().get(), "file", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | OPEN_FLAGS), |
| fc.server.TakeChannel().release())); |
| auto file_get_result = fidl::WireCall(fc.client)->GetFlags(); |
| EXPECT_OK(file_get_result.status()); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable, file_get_result->flags); |
| } |
| } |
| |
| TEST_F(ConnectionTest, FileGetSetFlagsOnFile) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to File |
| zx::result fc = fidl::CreateEndpoints<fio::File>(); |
| ASSERT_OK(fc.status_value()); |
| ASSERT_OK(fdio_open_at(root.client.channel().get(), "file", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable), |
| fc->server.TakeChannel().release())); |
| |
| { |
| // Use GetFlags to get current flags and rights |
| auto file_get_result = fidl::WireCall(fc->client)->GetFlags(); |
| EXPECT_OK(file_get_result.status()); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable, file_get_result->flags); |
| } |
| { |
| // Make modifications to flags with SetFlags: Note this only works for kOpenFlagAppend |
| // based on posix standard |
| auto file_set_result = fidl::WireCall(fc->client)->SetFlags(fio::OpenFlags::kAppend); |
| EXPECT_OK(file_set_result->s); |
| } |
| { |
| // Check that the new flag is saved |
| auto file_get_result = fidl::WireCall(fc->client)->GetFlags(); |
| EXPECT_OK(file_get_result->s); |
| EXPECT_EQ(fio::OpenFlags::kRightReadable | fio::OpenFlags::kAppend, file_get_result->flags); |
| } |
| } |
| |
| TEST_F(ConnectionTest, GetSetIo1Attrs) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to File |
| zx::result fc = fidl::CreateEndpoints<fio::File>(); |
| ASSERT_OK(fc.status_value()); |
| ASSERT_OK(fdio_open_at( |
| root.client.channel().get(), "file_or_dir", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kRightWritable), |
| fc->server.TakeChannel().release())); |
| { |
| auto io1_attrs = fidl::WireCall(fc->client)->GetAttr(); |
| ASSERT_OK(io1_attrs.status()); |
| EXPECT_EQ(io1_attrs->attributes.modification_time, 0); |
| } |
| |
| // Ensure we can't set creation time. |
| { |
| auto io1_attrs = |
| fidl::WireCall(fc->client)->SetAttr(fio::NodeAttributeFlags::kCreationTime, {}); |
| ASSERT_OK(io1_attrs.status()); |
| // ASSERT_EQ(io1_attrs->s, ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // Update modification time. |
| { |
| auto io1_attrs = fidl::WireCall(fc->client) |
| ->SetAttr(fio::NodeAttributeFlags::kModificationTime, |
| fio::wire::NodeAttributes{.modification_time = 1234}); |
| ASSERT_OK(io1_attrs.status()); |
| ASSERT_EQ(io1_attrs->s, ZX_OK); |
| } |
| |
| // Check modification time was updated. |
| { |
| auto io1_attrs = fidl::WireCall(fc->client)->GetAttr(); |
| ASSERT_OK(io1_attrs.status()); |
| EXPECT_EQ(io1_attrs->attributes.modification_time, 1234); |
| } |
| } |
| |
| // Test that the io2 GetAttributes and UpdateAttributes methods work as expected. |
| TEST_F(ConnectionTest, GetUpdateIo2Attrs) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to File |
| zx::result fc = fidl::CreateEndpoints<fio::File>(); |
| ASSERT_OK(fc.status_value()); |
| ASSERT_OK(fdio_open_at( |
| root.client.channel().get(), "file_or_dir", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kRightWritable), |
| fc->server.TakeChannel().release())); |
| auto client = fidl::SyncClient(std::move(fc->client)); |
| // Our test Vnode only reports a hard-coded ID in addition to protocols/abilities. |
| fio::ImmutableNodeAttributes expected_immutable_attrs = fio::ImmutableNodeAttributes(); |
| expected_immutable_attrs.id() = 1234; |
| expected_immutable_attrs.abilities() = FileOrDirectory().GetAbilities(); |
| expected_immutable_attrs.protocols() = FileOrDirectory().GetProtocols(); |
| // Our test Vnode only supports modification time, and should default-initialize it to zero. |
| fio::MutableNodeAttributes expected_mutable_attrs = fio::MutableNodeAttributes(); |
| expected_mutable_attrs.modification_time() = 0; |
| { |
| auto attrs = client->GetAttributes(fio::NodeAttributesQuery::kMask); |
| ASSERT_TRUE(attrs.is_ok()); |
| EXPECT_EQ(attrs->immutable_attributes(), expected_immutable_attrs); |
| EXPECT_EQ(attrs->mutable_attributes(), expected_mutable_attrs); |
| } |
| |
| // Ensure we can't set creation time. |
| { |
| fio::MutableNodeAttributes update = fio::MutableNodeAttributes(); |
| update.creation_time() = 0; |
| auto result = client->UpdateAttributes(update); |
| ASSERT_TRUE(result.is_error()); |
| EXPECT_EQ(result.error_value().domain_error(), ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| // Update modification time. |
| expected_mutable_attrs.modification_time() = 1234; |
| { |
| auto result = client->UpdateAttributes(expected_mutable_attrs); |
| ASSERT_TRUE(result.is_ok()); |
| } |
| |
| // Check modification time was updated and other attributes remain unchanged. |
| { |
| auto attrs = client->GetAttributes(fio::NodeAttributesQuery::kMask); |
| ASSERT_TRUE(attrs.is_ok()); |
| EXPECT_EQ(attrs->immutable_attributes(), expected_immutable_attrs); |
| EXPECT_EQ(attrs->mutable_attributes(), expected_mutable_attrs); |
| } |
| } |
| |
| TEST_F(ConnectionTest, FileSeekDirectory) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Interacting with a Directory connection using File protocol methods should fail. |
| { |
| zx::result dc = fidl::CreateEndpoints<fio::Directory>(); |
| ASSERT_OK(dc.status_value()); |
| ASSERT_OK(fdio_open_at( |
| root.client.channel().get(), "dir", |
| static_cast<uint32_t>(fio::OpenFlags::kRightReadable | fio::OpenFlags::kRightWritable), |
| dc->server.TakeChannel().release())); |
| |
| // Borrowing directory channel as file channel. |
| auto dir_get_result = |
| fidl::WireCall(fidl::UnownedClientEnd<fio::File>(dc->client.borrow().handle())) |
| ->Seek(fio::wire::SeekOrigin::kStart, 0); |
| EXPECT_NOT_OK(dir_get_result.status()); |
| } |
| } |
| |
| TEST_F(ConnectionTest, NegotiateProtocol) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // Connect to polymorphic node as a directory, by passing |kOpenFlagDirectory|. |
| { |
| auto [dir_client, dir_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open(fio::OpenFlags::kRightReadable | fio::OpenFlags::kDescribe | |
| fio::OpenFlags::kDirectory, |
| {}, fidl::StringView("file_or_dir"), std::move(dir_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> dir_info = GetOnOpenResponse(dir_client); |
| ASSERT_OK(dir_info); |
| ASSERT_TRUE(dir_info->is_directory()); |
| // Check that if we clone the connection, we still get the directory protocol. |
| auto [clone_client, clone_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(dir_client) |
| ->Clone(fio::OpenFlags::kDescribe | fio::OpenFlags::kCloneSameRights, |
| std::move(clone_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> cloned_info = GetOnOpenResponse(clone_client); |
| ASSERT_OK(cloned_info); |
| ASSERT_TRUE(cloned_info->is_directory()); |
| } |
| { |
| // Connect to polymorphic node as a file, by passing |kOpenFlagNotDirectory|. |
| auto [file_client, file_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open(fio::OpenFlags::kRightReadable | fio::OpenFlags::kDescribe | |
| fio::OpenFlags::kNotDirectory, |
| {}, fidl::StringView("file_or_dir"), std::move(file_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> file_info = GetOnOpenResponse(file_client); |
| ASSERT_OK(file_info); |
| ASSERT_TRUE(file_info->is_file()); |
| // Check that if we clone the connection, we still get the file protocol. |
| auto [clone_client, clone_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(file_client) |
| ->Clone(fio::OpenFlags::kDescribe | fio::OpenFlags::kCloneSameRights, |
| std::move(clone_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> cloned_info = GetOnOpenResponse(clone_client); |
| ASSERT_OK(cloned_info); |
| ASSERT_TRUE(cloned_info->is_file()); |
| } |
| { |
| // Connect to polymorphic node as a node reference, by passing |kNodeReference|. |
| auto [node_client, node_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open(fio::OpenFlags::kNodeReference | fio::OpenFlags::kDescribe, {}, |
| fidl::StringView("file_or_dir"), std::move(node_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> node_info = GetOnOpenResponse(node_client); |
| ASSERT_OK(node_info); |
| // In io1, node reference connections map to the service representation in the OnOpen event. |
| ASSERT_TRUE(node_info->is_service()); |
| // Check that if we clone the connection, we still get the node protocol. |
| auto [clone_client, clone_server] = fidl::Endpoints<fio::Node>::Create(); |
| ASSERT_OK(fidl::WireCall(node_client) |
| ->Clone(fio::OpenFlags::kDescribe | fio::OpenFlags::kCloneSameRights, |
| std::move(clone_server)) |
| .status()); |
| zx::result<fio::wire::NodeInfoDeprecated> cloned_info = GetOnOpenResponse(clone_client); |
| ASSERT_OK(cloned_info); |
| ASSERT_TRUE(cloned_info->is_service()); |
| } |
| } |
| |
| TEST_F(ConnectionTest, NegotiateProtocolOpen2) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| { |
| // Connect to polymorphic node as a directory. |
| zx::result dc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(dc.status_value()); |
| fio::NodeProtocols node_protocols; |
| node_protocols.directory() = fio::DirectoryProtocolOptions{}; |
| fio::NodeOptions options; |
| options.flags() = fio::NodeFlags::kGetRepresentation; |
| options.protocols() = std::move(node_protocols); |
| fio::ConnectionProtocols protocols = fio::ConnectionProtocols::WithNode(std::move(options)); |
| fidl::Arena arena; |
| auto wire_obj = fidl::ToWire(arena, std::move(protocols)); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open2(fidl::StringView("file_or_dir"), wire_obj, dc->server.TakeChannel()) |
| .status()); |
| zx::result<fio::Representation> dir_info = GetOnRepresentation(dc->client); |
| ASSERT_OK(dir_info); |
| ASSERT_EQ(dir_info->Which(), fio::Representation::Tag::kDirectory); |
| } |
| |
| { |
| // Connect to polymorphic node as a file. |
| zx::result fc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(fc.status_value()); |
| fio::NodeProtocols node_protocols; |
| node_protocols.file() = fio::FileProtocolFlags{}; |
| fio::NodeOptions options; |
| options.flags() = fio::NodeFlags::kGetRepresentation; |
| options.protocols() = std::move(node_protocols); |
| fio::ConnectionProtocols protocols = fio::ConnectionProtocols::WithNode(std::move(options)); |
| fidl::Arena arena; |
| auto wire_obj = fidl::ToWire(arena, std::move(protocols)); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open2(fidl::StringView("file_or_dir"), wire_obj, fc->server.TakeChannel()) |
| .status()); |
| zx::result<fio::Representation> file_info = GetOnRepresentation(fc->client); |
| ASSERT_OK(file_info); |
| ASSERT_EQ(file_info->Which(), fio::Representation::Tag::kFile); |
| } |
| } |
| |
| TEST_F(ConnectionTest, PrevalidateFlagsOpenFailure) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| // The only invalid flag combination for fuchsia.io/Node.Clone is specifying CLONE_SAME_RIGHTS |
| // with any other rights. |
| constexpr fio::OpenFlags kInvalidFlagCombo = |
| fio::OpenFlags::kCloneSameRights | fio::OpenFlags::kRightReadable | fio::OpenFlags::kDescribe; |
| zx::result dc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(dc.status_value()); |
| // Ensure that invalid flag combination returns INVALID_ARGS. |
| ASSERT_OK( |
| fidl::WireCall(root.client) |
| ->Open(kInvalidFlagCombo, {}, fidl::StringView("file_or_dir"), std::move(dc->server)) |
| .status()); |
| ASSERT_EQ(GetOnOpenResponse(dc->client).status_value(), ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(ConnectionTest, ValidateRights) { |
| // Create connection to vfs |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| // The test Vnode should disallow execute rights. |
| { |
| zx::result fc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(fc.status_value()); |
| fio::NodeProtocols node_protocols; |
| node_protocols.file() = fio::FileProtocolFlags{}; |
| fio::NodeOptions options; |
| options.flags() = fio::NodeFlags::kGetRepresentation; |
| options.protocols() = std::move(node_protocols); |
| options.rights() = fio::Rights::kExecute; |
| fio::ConnectionProtocols protocols = fio::ConnectionProtocols::WithNode(std::move(options)); |
| fidl::Arena arena; |
| auto wire_obj = fidl::ToWire(arena, std::move(protocols)); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open2(fidl::StringView("file_or_dir"), wire_obj, fc->server.TakeChannel()) |
| .status()); |
| zx::result<fio::Representation> file_info = GetOnRepresentation(fc->client); |
| ASSERT_EQ(file_info.status_value(), ZX_ERR_ACCESS_DENIED); |
| } |
| } |
| |
| TEST_F(ConnectionTest, ValidateRightsReadonly) { |
| // Set the filesystem as read-only before creating a root connection. |
| SetReadonly(); |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| { |
| // If the filesystem is read only, we shouldn't be able to open files as writable. |
| zx::result fc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(fc.status_value()); |
| fio::NodeProtocols node_protocols; |
| node_protocols.file() = fio::FileProtocolFlags{}; |
| fio::NodeOptions options; |
| options.flags() = fio::NodeFlags::kGetRepresentation; |
| options.protocols() = std::move(node_protocols); |
| options.rights() = fio::Rights::kWriteBytes; |
| fio::ConnectionProtocols protocols = fio::ConnectionProtocols::WithNode(std::move(options)); |
| fidl::Arena arena; |
| auto wire_obj = fidl::ToWire(arena, std::move(protocols)); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open2(fidl::StringView("file_or_dir"), wire_obj, fc->server.TakeChannel()) |
| .status()); |
| zx::result<fio::Representation> file_info = GetOnRepresentation(fc->client); |
| ASSERT_EQ(file_info.status_value(), ZX_ERR_ACCESS_DENIED); |
| } |
| { |
| // If the filesystem is read only, we shouldn't be granted mutable rights for directories. |
| zx::result fc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(fc.status_value()); |
| fio::NodeProtocols node_protocols; |
| node_protocols.directory() = fio::DirectoryProtocolOptions{}; |
| node_protocols.directory()->optional_rights() = fio::Rights::kModifyDirectory; |
| fio::NodeOptions options; |
| options.flags() = fio::NodeFlags::kGetRepresentation; |
| options.protocols() = std::move(node_protocols); |
| options.rights() = fio::Rights::kGetAttributes; |
| fio::ConnectionProtocols protocols = fio::ConnectionProtocols::WithNode(std::move(options)); |
| fidl::Arena arena; |
| auto wire_obj = fidl::ToWire(arena, std::move(protocols)); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open2(fidl::StringView("file_or_dir"), wire_obj, fc->server.TakeChannel()) |
| .status()); |
| zx::result<fio::Representation> dir_info = GetOnRepresentation(fc->client); |
| ASSERT_OK(dir_info); |
| ASSERT_EQ(dir_info->Which(), fio::Representation::Tag::kDirectory); |
| auto connection_info = fidl::WireCall(fc->client)->GetConnectionInfo(); |
| ASSERT_OK(connection_info); |
| ASSERT_TRUE(connection_info.value().has_rights()); |
| ASSERT_EQ(connection_info.value().rights(), fio::Rights::kGetAttributes); |
| } |
| } |
| |
| // A vnode which maintains a counter of number of |Open| calls that have not been balanced out with |
| // a |Close|. |
| class CountOutstandingOpenVnode : public fs::Vnode { |
| public: |
| CountOutstandingOpenVnode() = default; |
| |
| fuchsia_io::NodeProtocolKinds GetProtocols() const final { |
| return fuchsia_io::NodeProtocolKinds::kFile; |
| } |
| |
| uint64_t GetOpenCount() const { |
| std::lock_guard lock(mutex_); |
| return open_count(); |
| } |
| }; |
| |
| class ConnectionClosingTest : public zxtest::Test { |
| public: |
| // Setup file structure with one directory and one file. Note: On creation directories and files |
| // have no flags and rights. |
| ConnectionClosingTest() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) { |
| vfs_.SetDispatcher(loop_.dispatcher()); |
| root_ = fbl::MakeRefCounted<fs::PseudoDir>(); |
| count_outstanding_open_vnode_ = fbl::MakeRefCounted<CountOutstandingOpenVnode>(); |
| root_->AddEntry("count_outstanding_open_vnode", count_outstanding_open_vnode_); |
| } |
| |
| zx_status_t ConnectClient(fidl::ServerEnd<fuchsia_io::Directory> server_end) { |
| // Serve root directory with maximum rights |
| return vfs_.ServeDirectory(root_, std::move(server_end)); |
| } |
| |
| protected: |
| fbl::RefPtr<CountOutstandingOpenVnode> count_outstanding_open_vnode() const { |
| return count_outstanding_open_vnode_; |
| } |
| |
| async::Loop& loop() { return loop_; } |
| |
| private: |
| async::Loop loop_; |
| fs::SynchronousVfs vfs_; |
| fbl::RefPtr<fs::PseudoDir> root_; |
| fbl::RefPtr<CountOutstandingOpenVnode> count_outstanding_open_vnode_; |
| }; |
| |
| TEST_F(ConnectionClosingTest, ClosingChannelImpliesClosingNode) { |
| // Create connection to vfs. |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| constexpr int kNumActiveClients = 20; |
| |
| ASSERT_EQ(count_outstanding_open_vnode()->GetOpenCount(), 0); |
| |
| // Create a number of active connections to "count_outstanding_open_vnode". |
| std::vector<fidl::ClientEnd<fio::Node>> clients; |
| for (int i = 0; i < kNumActiveClients; i++) { |
| zx::result fc = fidl::CreateEndpoints<fio::Node>(); |
| ASSERT_OK(fc.status_value()); |
| ASSERT_OK(fidl::WireCall(root.client) |
| ->Open(fio::OpenFlags::kRightReadable, {}, |
| fidl::StringView("count_outstanding_open_vnode"), std::move(fc->server)) |
| .status()); |
| clients.push_back(std::move(fc->client)); |
| } |
| |
| ASSERT_OK(loop().RunUntilIdle()); |
| ASSERT_EQ(count_outstanding_open_vnode()->GetOpenCount(), kNumActiveClients); |
| |
| // Drop all the clients, leading to |Close| being invoked on "count_outstanding_open_vnode" |
| // eventually. |
| clients.clear(); |
| |
| ASSERT_OK(loop().RunUntilIdle()); |
| ASSERT_EQ(count_outstanding_open_vnode()->GetOpenCount(), 0); |
| } |
| |
| TEST_F(ConnectionClosingTest, ClosingNodeLeadsToClosingServerEndChannel) { |
| // Create connection to vfs. |
| auto root = fidl::Endpoints<fio::Directory>::Create(); |
| ASSERT_OK(ConnectClient(std::move(root.server))); |
| |
| zx_signals_t observed = ZX_SIGNAL_NONE; |
| ASSERT_STATUS( |
| ZX_ERR_TIMED_OUT, |
| root.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite_past(), &observed)); |
| ASSERT_FALSE(observed & ZX_CHANNEL_PEER_CLOSED); |
| |
| ASSERT_OK(loop().StartThread()); |
| auto result = fidl::WireCall(root.client)->Close(); |
| ASSERT_OK(result.status()); |
| ASSERT_TRUE(result->is_ok(), "%s", zx_status_get_string(result->error_value())); |
| |
| observed = ZX_SIGNAL_NONE; |
| ASSERT_OK( |
| root.client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &observed)); |
| ASSERT_TRUE(observed & ZX_CHANNEL_PEER_CLOSED); |
| |
| loop().Shutdown(); |
| } |
| |
| } // namespace |