blob: 8f710a9c3e066513dc26557cbf48b2bca0b9878f [file] [log] [blame]
// Copyright 2021 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 "src/devices/bin/driver_manager/devfs/devfs.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/ddk/driver.h>
#include <functional>
#include <zxtest/zxtest.h>
#include "src/storage/lib/vfs/cpp/synchronous_vfs.h"
namespace {
using driver_manager::Devfs;
using driver_manager::DevfsDevice;
using driver_manager::Devnode;
class Connecter : public fidl::WireServer<fuchsia_device_fs::Connector> {
public:
private:
void Connect(ConnectRequestView request, ConnectCompleter::Sync& completer) override {
ASSERT_EQ(channel_.get(), ZX_HANDLE_INVALID);
channel_ = std::move(request->server);
}
zx::channel channel_;
};
std::optional<std::reference_wrapper<const Devnode>> lookup(const Devnode& parent,
std::string_view name) {
{
fbl::RefPtr<fs::Vnode> out;
switch (const zx_status_t status = parent.children().Lookup(name, &out); status) {
case ZX_OK:
return std::reference_wrapper(fbl::RefPtr<Devnode::VnodeImpl>::Downcast(out)->holder_);
case ZX_ERR_NOT_FOUND:
break;
default:
ADD_FAILURE("%s", zx_status_get_string(status));
return {};
}
}
const auto it = parent.children().unpublished.find(name);
if (it != parent.children().unpublished.end()) {
return it->second.get();
}
return {};
}
TEST(Devfs, Export) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one/two", {}, out));
std::optional node_one = lookup(root_node, "one");
ASSERT_TRUE(node_one.has_value());
EXPECT_EQ("one", node_one->get().name());
std::optional node_two = lookup(node_one->get(), "two");
ASSERT_TRUE(node_two.has_value());
EXPECT_EQ("two", node_two->get().name());
}
TEST(Devfs, Export_ExcessSeparators) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_STATUS(root_node.export_dir(Devnode::Target(), "one//two", {}, out), ZX_ERR_INVALID_ARGS);
ASSERT_FALSE(lookup(root_node, "one").has_value());
ASSERT_FALSE(lookup(root_node, "two").has_value());
}
TEST(Devfs, Export_OneByOne) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one", {}, out));
std::optional node_one = lookup(root_node, "one");
ASSERT_TRUE(node_one.has_value());
EXPECT_EQ("one", node_one->get().name());
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one/two", {}, out));
std::optional node_two = lookup(node_one->get(), "two");
ASSERT_TRUE(node_two.has_value());
EXPECT_EQ("two", node_two->get().name());
}
TEST(Devfs, Export_InvalidPath) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, root_node.export_dir(Devnode::Target(), "", {}, out));
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, root_node.export_dir(Devnode::Target(), "/one/two", {}, out));
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, root_node.export_dir(Devnode::Target(), "one/two/", {}, out));
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, root_node.export_dir(Devnode::Target(), "/one/two/", {}, out));
}
TEST(Devfs, Export_WithProtocol) {
std::optional<Devnode> root_slot;
Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::optional proto_node = devfs.proto_node(ZX_PROTOCOL_BLOCK);
ASSERT_TRUE(proto_node.has_value());
EXPECT_EQ("block", proto_node.value().get().name());
{
fbl::RefPtr<fs::Vnode> node_000;
EXPECT_STATUS(proto_node.value().get().children().Lookup("000", &node_000), ZX_ERR_NOT_FOUND);
ASSERT_EQ(node_000, nullptr);
}
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one/two", "block", out));
std::optional node_one = lookup(root_node, "one");
ASSERT_TRUE(node_one.has_value());
EXPECT_EQ("one", node_one->get().name());
std::optional node_two = lookup(node_one->get(), "two");
ASSERT_TRUE(node_two.has_value());
EXPECT_EQ("two", node_two->get().name());
fbl::RefPtr<fs::Vnode> node_000;
ASSERT_OK(proto_node.value().get().children().Lookup("000", &node_000));
ASSERT_NE(node_000, nullptr);
}
TEST(Devfs, Export_AlreadyExists) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one/two", {}, out));
ASSERT_STATUS(ZX_ERR_ALREADY_EXISTS, root_node.export_dir(Devnode::Target(), "one/two", {}, out));
}
TEST(Devfs, Export_DropDevfs) {
std::optional<Devnode> root_slot;
const Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
Devnode& root_node = root_slot.value();
std::vector<std::unique_ptr<Devnode>> out;
ASSERT_OK(root_node.export_dir(Devnode::Target(), "one/two", {}, out));
{
std::optional node_one = lookup(root_node, "one");
ASSERT_TRUE(node_one.has_value());
EXPECT_EQ("one", node_one->get().name());
std::optional node_two = lookup(node_one->get(), "two");
ASSERT_TRUE(node_two.has_value());
EXPECT_EQ("two", node_two->get().name());
}
out.clear();
ASSERT_FALSE(lookup(root_node, "one").has_value());
}
TEST(Devfs, PassthroughTarget) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
fs::SynchronousVfs vfs(loop.dispatcher());
std::optional<Devnode> root_slot;
Devfs devfs(root_slot);
ASSERT_TRUE(root_slot.has_value());
fuchsia_device_fs::ConnectionType connection_type;
Devnode::PassThrough passthrough(
{
[&loop, &connection_type](zx::channel server) {
connection_type = fuchsia_device_fs::ConnectionType::kDevice;
loop.Quit();
return ZX_OK;
},
},
{
[&loop, &connection_type](fidl::ServerEnd<fuchsia_device::Controller> server_end) {
connection_type = fuchsia_device_fs::ConnectionType::kController;
loop.Quit();
return ZX_OK;
},
});
DevfsDevice device;
ASSERT_OK(root_slot.value().add_child("test", std::nullopt, passthrough.Clone(), device));
device.publish();
zx::result devfs_client = devfs.Connect(vfs);
ASSERT_OK(devfs_client);
struct TestRun {
const char* file_name;
fuchsia_device_fs::ConnectionType expected;
};
const TestRun tests[] = {
{
.file_name = "test",
.expected = fuchsia_device_fs::ConnectionType::kDevice,
},
{
.file_name = "test/device_controller",
.expected = fuchsia_device_fs::ConnectionType::kController,
},
{
.file_name = "test/device_protocol",
.expected = fuchsia_device_fs::ConnectionType::kDevice,
},
};
for (const TestRun& test : tests) {
SCOPED_TRACE(test.file_name);
auto [_, server_end] = fidl::Endpoints<fuchsia_io::Node>::Create();
ASSERT_OK(fidl::WireCall(devfs_client.value())
->Open(fuchsia_io::wire::OpenFlags(), fuchsia_io::wire::ModeType(),
fidl::StringView::FromExternal(test.file_name), std::move(server_end))
.status());
loop.Run();
loop.ResetQuit();
ASSERT_EQ(connection_type, test.expected);
}
}
} // namespace