blob: 5fd2ef1c7280d10fa2547797db44f280844b182b [file] [log] [blame]
// Copyright 2018 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/fdio/directory.h>
#include <lib/fdio/namespace.h>
#include <lib/zx/channel.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <utility>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
namespace {
namespace fio = fuchsia_io;
void FidlOpenValidator(const fidl::ClientEnd<fio::Directory>& directory, const char* path,
zx::status<fio::wire::NodeInfo::Tag> expected) {
zx::status endpoints = fidl::CreateEndpoints<fio::Node>();
ASSERT_OK(endpoints.status_value());
const fidl::WireResult result = fidl::WireCall(directory)->Open(
fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kDescribe, 0,
fidl::StringView::FromExternal(path), std::move(endpoints->server));
ASSERT_OK(result.status());
class EventHandler : public fidl::testing::WireSyncEventHandlerTestBase<fio::Node> {
public:
std::optional<zx_status_t> status() const { return status_; }
std::optional<fio::wire::NodeInfo::Tag> tag() const { return tag_; }
void OnOpen(fidl::WireEvent<fio::Node::OnOpen>* event) override {
status_ = event->s;
if (!event->info.has_invalid_tag()) {
tag_ = event->info.Which();
}
}
void NotImplemented_(const std::string& name) override {
ADD_FAILURE("Unexpected %s", name.c_str());
}
private:
std::optional<zx_status_t> status_;
std::optional<fio::wire::NodeInfo::Tag> tag_;
};
EventHandler event_handler;
ASSERT_OK(event_handler.HandleOneEvent(endpoints->client));
ASSERT_TRUE(event_handler.status().has_value());
if (expected.is_ok()) {
ASSERT_OK(event_handler.status().value());
ASSERT_TRUE(event_handler.tag().has_value());
ASSERT_EQ(event_handler.tag().value(), expected.value());
}
if (expected.is_error()) {
ASSERT_STATUS(event_handler.status().value(), expected.status_value());
ASSERT_EQ(event_handler.tag(), std::nullopt);
}
}
// Ensure that our hand-rolled FIDL messages within devfs and memfs are acting correctly
// for open event messages (on both success and error).
TEST(FidlTestCase, Open) {
{
zx::status endpoints = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(endpoints.status_value());
fdio_ns_t* ns;
ASSERT_OK(fdio_ns_get_installed(&ns));
ASSERT_OK(fdio_ns_service_connect(ns, "/dev", endpoints->server.channel().release()));
ASSERT_NO_FAILURES(
FidlOpenValidator(endpoints->client, "zero", zx::ok(fio::wire::NodeInfo::Tag::kDevice)));
ASSERT_NO_FAILURES(FidlOpenValidator(endpoints->client, "class/platform-bus/000",
zx::ok(fio::wire::NodeInfo::Tag::kDevice)));
ASSERT_NO_FAILURES(FidlOpenValidator(endpoints->client, "this-path-better-not-actually-exist",
zx::error(ZX_ERR_NOT_FOUND)));
ASSERT_NO_FAILURES(FidlOpenValidator(endpoints->client,
"zero/this-path-better-not-actually-exist",
zx::error(ZX_ERR_NOT_FOUND)));
}
{
zx::status endpoints = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(endpoints.status_value());
fdio_ns_t* ns;
ASSERT_OK(fdio_ns_get_installed(&ns));
ASSERT_OK(fdio_ns_service_connect(ns, "/boot", endpoints->server.channel().release()));
ASSERT_NO_FAILURES(
FidlOpenValidator(endpoints->client, "lib", zx::ok(fio::wire::NodeInfo::Tag::kDirectory)));
ASSERT_NO_FAILURES(FidlOpenValidator(endpoints->client, "this-path-better-not-actually-exist",
zx::error(ZX_ERR_NOT_FOUND)));
}
}
TEST(FidlTestCase, Basic) {
{
zx::status endpoints = fidl::CreateEndpoints<fio::Node>();
ASSERT_OK(endpoints.status_value());
ASSERT_OK(fdio_service_connect("/dev/class", endpoints->server.channel().release()));
const fidl::WireResult result = fidl::WireCall(endpoints->client)->Describe();
ASSERT_OK(result.status());
const auto& response = result.value();
ASSERT_TRUE(response.info.is_directory());
}
{
zx::status endpoints = fidl::CreateEndpoints<fio::Node>();
ASSERT_OK(endpoints.status_value());
ASSERT_OK(fdio_service_connect("/dev/zero", endpoints->server.channel().release()));
const fidl::WireResult result = fidl::WireCall(endpoints->client)->Describe();
ASSERT_OK(result.status());
const auto& response = result.value();
ASSERT_TRUE(response.info.is_device());
}
}
using watch_buffer_t = struct {
// Buffer containing cached messages
uint8_t buf[fio::wire::kMaxBuf];
uint8_t name_buf[fio::wire::kMaxFilename + 1];
// Offset into 'buf' of next message
uint8_t* ptr;
// Maximum size of buffer
size_t size;
};
void CheckLocalEvent(watch_buffer_t* wb, const char** name, fio::WatchEvent* event) {
ASSERT_NOT_NULL(wb->ptr);
// Used a cached event
*event = static_cast<fio::WatchEvent>(wb->ptr[0]);
ASSERT_LT(static_cast<size_t>(wb->ptr[1]), sizeof(wb->name_buf));
memcpy(wb->name_buf, wb->ptr + 2, wb->ptr[1]);
wb->name_buf[wb->ptr[1]] = 0;
*name = reinterpret_cast<const char*>(wb->name_buf);
wb->ptr += wb->ptr[1] + 2;
ASSERT_LE((uintptr_t)wb->ptr, (uintptr_t)wb->buf + wb->size);
if (wb->ptr == wb->buf + wb->size) {
wb->ptr = nullptr;
}
}
// Read the next event off the channel. Storage for |*name| will be reused
// between calls.
void ReadEvent(watch_buffer_t* wb, const fidl::ClientEnd<fio::DirectoryWatcher>& client_end,
const char** name, fio::WatchEvent* event) {
if (wb->ptr == nullptr) {
zx_signals_t observed;
ASSERT_OK(client_end.channel().wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), &observed));
ASSERT_EQ(observed & ZX_CHANNEL_READABLE, ZX_CHANNEL_READABLE);
uint32_t actual;
ASSERT_OK(client_end.channel().read(0, wb->buf, nullptr, sizeof(wb->buf), 0, &actual, nullptr));
wb->size = actual;
wb->ptr = wb->buf;
}
ASSERT_NO_FAILURES(CheckLocalEvent(wb, name, event));
}
TEST(FidlTestCase, DirectoryWatcherExisting) {
zx::status endpoints = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(endpoints.status_value());
zx::status watcher_endpoints = fidl::CreateEndpoints<fio::DirectoryWatcher>();
ASSERT_OK(watcher_endpoints.status_value());
ASSERT_OK(fdio_service_connect("/dev/class", endpoints->server.channel().release()));
const fidl::WireResult result =
fidl::WireCall(endpoints->client)
->Watch(fio::wire::WatchMask::kMask, 0, std::move(watcher_endpoints->server));
ASSERT_OK(result.status());
const auto& response = result.value();
ASSERT_OK(response.s);
watch_buffer_t wb = {};
// We should see nothing but EXISTING events until we see an IDLE event
while (true) {
const char* name = nullptr;
fio::wire::WatchEvent event;
ASSERT_NO_FAILURES(ReadEvent(&wb, watcher_endpoints->client, &name, &event));
if (event == fio::wire::WatchEvent::kIdle) {
ASSERT_STREQ(name, "");
break;
}
ASSERT_EQ(event, fio::wire::WatchEvent::kExisting);
ASSERT_STRNE(name, "");
}
}
TEST(FidlTestCase, DirectoryWatcherWithClosedHalf) {
zx::status endpoints = fidl::CreateEndpoints<fio::Directory>();
ASSERT_OK(endpoints.status_value());
ASSERT_OK(fdio_service_connect("/dev/class", endpoints->server.channel().release()));
{
zx::status watcher_endpoints = fidl::CreateEndpoints<fio::DirectoryWatcher>();
ASSERT_OK(watcher_endpoints.status_value());
// Close our end of the watcher before devmgr gets its end.
watcher_endpoints->client.reset();
const fidl::WireResult result =
fidl::WireCall(endpoints->client)
->Watch(fio::wire::WatchMask::kMask, 0, std::move(watcher_endpoints->server));
ASSERT_OK(result.status());
const auto& response = result.value();
ASSERT_OK(response.s);
// If we're here and usermode didn't crash, we didn't hit the bug.
}
{
// Create a new watcher, and see if it's functional at all
zx::status watcher_endpoints = fidl::CreateEndpoints<fio::DirectoryWatcher>();
ASSERT_OK(watcher_endpoints.status_value());
const fidl::WireResult result =
fidl::WireCall(endpoints->client)
->Watch(fio::wire::WatchMask::kMask, 0, std::move(watcher_endpoints->server));
ASSERT_OK(result.status());
const auto& response = result.value();
ASSERT_OK(response.s);
watch_buffer_t wb = {};
const char* name = nullptr;
fio::wire::WatchEvent event;
ASSERT_NO_FAILURES(ReadEvent(&wb, watcher_endpoints->client, &name, &event));
ASSERT_EQ(event, fio::wire::WatchEvent::kExisting);
}
}
} // namespace