blob: 377332be99a6b7257ccd8390b39b07a8196eb987 [file] [log] [blame]
// 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 <fidl/fuchsia.io/cpp/wire.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 <atomic>
#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;
}
};
// 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)->Sync();
EXPECT_OK(result.status());
const fit::result response = result.value();
EXPECT_TRUE(response.is_error());
EXPECT_STATUS(ZX_ERR_NOT_SUPPORTED, response.error_value());
}
return zx::ok(std::move(response.info.value()));
}
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));
}
protected:
void SetUp() override { loop_.StartThread(); }
void TearDown() override { loop_.Shutdown(); }
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
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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::wire::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::wire::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::wire::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::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kAppend,
file_get_result->flags);
}
}
TEST_F(ConnectionTest, NodeGetSetFlagsOnDirectory) {
// Create connection to vfs
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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::wire::OpenFlags::kRightReadable |
fio::wire::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::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable,
dir_get_result->flags);
// Directories do not support setting flags.
auto dir_set_result = fidl::WireCall(dc->client)->SetFlags(fio::wire::OpenFlags::kAppend);
EXPECT_EQ(dir_set_result->s, ZX_ERR_NOT_SUPPORTED);
}
TEST_F(ConnectionTest, PosixFlagDirectoryRightExpansion) {
// Create connection to VFS with all rights.
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
ASSERT_OK(ConnectClient(std::move(root->server)));
// Combinations of POSIX flags to be tested.
const fio::wire::OpenFlags OPEN_FLAG_COMBINATIONS[]{
fio::wire::OpenFlags::kPosixWritable, fio::wire::OpenFlags::kPosixExecutable,
fio::wire::OpenFlags::kPosixWritable | fio::wire::OpenFlags::kPosixExecutable};
for (const fio::wire::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::wire::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::wire::OpenFlags::kRightReadable & dir_flags);
// Each POSIX flag should be expanded to its respective right(s).
if (OPEN_FLAGS & fio::wire::OpenFlags::kPosixWritable)
EXPECT_TRUE(fio::wire::OpenFlags::kRightWritable & dir_flags);
if (OPEN_FLAGS & fio::wire::OpenFlags::kPosixExecutable)
EXPECT_TRUE(fio::wire::OpenFlags::kRightExecutable & dir_flags);
// Repeat test, but for file, which should not have any expanded rights.
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::wire::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::wire::OpenFlags::kRightReadable, file_get_result->flags);
}
}
TEST_F(ConnectionTest, FileGetSetFlagsOnFile) {
// Create connection to vfs
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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::wire::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::wire::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::wire::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::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kAppend,
file_get_result->flags);
}
}
TEST_F(ConnectionTest, FileSeekDirectory) {
// Create connection to vfs
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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::wire::OpenFlags::kRightReadable |
fio::wire::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
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
ASSERT_OK(ConnectClient(std::move(root->server)));
// Connect to polymorphic node as a directory, by passing |kOpenFlagDirectory|.
zx::result dc = fidl::CreateEndpoints<fio::Node>();
ASSERT_OK(dc.status_value());
ASSERT_OK(fidl::WireCall(root->client)
->Open(fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kDescribe |
fio::wire::OpenFlags::kDirectory,
{}, fidl::StringView("file_or_dir"), std::move(dc->server))
.status());
zx::result<fio::wire::NodeInfoDeprecated> dir_info = GetOnOpenResponse(dc->client);
ASSERT_OK(dir_info);
ASSERT_TRUE(dir_info->is_directory());
// Connect to polymorphic node as a file, by passing |kOpenFlagNotDirectory|.
zx::result fc = fidl::CreateEndpoints<fio::Node>();
ASSERT_OK(fc.status_value());
ASSERT_OK(fidl::WireCall(root->client)
->Open(fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kDescribe |
fio::wire::OpenFlags::kNotDirectory,
{}, fidl::StringView("file_or_dir"), std::move(fc->server))
.status());
zx::result<fio::wire::NodeInfoDeprecated> file_info = GetOnOpenResponse(fc->client);
ASSERT_OK(file_info);
ASSERT_TRUE(file_info->is_file());
}
TEST_F(ConnectionTest, PrevalidateFlagsOpenFailure) {
// Create connection to vfs
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
ASSERT_OK(ConnectClient(std::move(root->server)));
// Flag combination which should return INVALID_ARGS (see PrevalidateFlags in connection.cc).
constexpr fio::wire::OpenFlags kInvalidFlagCombo =
fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kDescribe |
fio::wire::OpenFlags::kDirectory | fio::wire::OpenFlags::kNodeReference |
fio::wire::OpenFlags::kAppend;
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);
}
// 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.
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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::wire::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.
zx::result root = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(root.status_value());
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