| // Copyright 2017 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 <fuchsia/io/llcpp/fidl.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 <utility> |
| |
| #include <fs/pseudo_dir.h> |
| #include <fs/service.h> |
| #include <fs/synchronous_vfs.h> |
| #include <unittest/unittest.h> |
| |
| namespace { |
| |
| namespace fio = ::llcpp::fuchsia::io; |
| |
| bool test_service() { |
| BEGIN_TEST; |
| |
| // set up a service which can only be bound once (to make it easy to |
| // simulate an error to test error reporting behavior from the connector) |
| zx::channel bound_channel; |
| auto svc = fbl::AdoptRef<fs::Service>(new fs::Service([&bound_channel](zx::channel channel) { |
| if (bound_channel) |
| return ZX_ERR_IO; |
| bound_channel = std::move(channel); |
| return ZX_OK; |
| })); |
| |
| fs::VnodeConnectionOptions options_readable; |
| options_readable.rights.read = true; |
| |
| // open |
| fbl::RefPtr<fs::Vnode> redirect; |
| auto result = svc->ValidateOptions(options_readable); |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(ZX_OK, svc->Open(result.value(), &redirect)); |
| EXPECT_NULL(redirect); |
| |
| // get attr |
| fs::VnodeAttributes attr; |
| EXPECT_EQ(ZX_OK, svc->GetAttributes(&attr)); |
| EXPECT_EQ(V_TYPE_FILE, attr.mode); |
| EXPECT_EQ(1, attr.link_count); |
| |
| // make some channels we can use for testing |
| zx::channel c1, c2; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0u, &c1, &c2)); |
| zx_handle_t hc1 = c1.get(); |
| |
| // serve, the connector will return success the first time |
| fs::SynchronousVfs vfs; |
| EXPECT_EQ(ZX_OK, vfs.Serve(svc, std::move(c1), options_readable)); |
| EXPECT_EQ(hc1, bound_channel.get()); |
| |
| // the connector will return failure because bound_channel is still valid |
| // we test that the error is propagated back up through Serve |
| EXPECT_EQ(ZX_ERR_IO, vfs.Serve(svc, std::move(c2), options_readable)); |
| EXPECT_EQ(hc1, bound_channel.get()); |
| |
| END_TEST; |
| } |
| |
| bool TestServeDirectory() { |
| BEGIN_TEST; |
| |
| zx::channel client, server; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0u, &client, &server)); |
| |
| // open client |
| zx::channel c1, c2; |
| EXPECT_EQ(ZX_OK, zx::channel::create(0u, &c1, &c2)); |
| EXPECT_EQ(ZX_OK, fdio_service_connect_at(client.get(), "abc", c2.release())); |
| |
| // close client |
| // We test the semantic that a pending open is processed even if the client |
| // has been closed. |
| client.reset(); |
| |
| // serve |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fs::SynchronousVfs vfs(loop.dispatcher()); |
| |
| auto directory = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir()); |
| auto vnode = fbl::AdoptRef<fs::Service>(new fs::Service([&loop](zx::channel channel) { |
| loop.Shutdown(); |
| return ZX_OK; |
| })); |
| directory->AddEntry("abc", vnode); |
| |
| EXPECT_EQ(ZX_OK, vfs.ServeDirectory(directory, std::move(server))); |
| EXPECT_EQ(ZX_ERR_BAD_STATE, loop.RunUntilIdle()); |
| |
| END_TEST; |
| } |
| |
| bool TestServiceNodeIsNotDirectory() { |
| BEGIN_TEST; |
| |
| // Set up the server |
| zx::channel client_end, server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0u, &client_end, &server_end)); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fs::SynchronousVfs vfs(loop.dispatcher()); |
| |
| auto directory = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir()); |
| auto vnode = fbl::AdoptRef<fs::Service>(new fs::Service([](zx::channel channel) { |
| // Should never reach here, because the directory flag is not allowed. |
| EXPECT_TRUE(false, "Should not be able to open the service"); |
| channel.reset(); |
| return ZX_OK; |
| })); |
| directory->AddEntry("abc", vnode); |
| ASSERT_EQ(ZX_OK, vfs.ServeDirectory(directory, std::move(server_end))); |
| |
| // Call |ValidateOptions| with the directory flag should fail. |
| auto result = vnode->ValidateOptions(fs::VnodeConnectionOptions::ReadWrite().set_directory()); |
| ASSERT_TRUE(result.is_error()); |
| ASSERT_EQ(ZX_ERR_NOT_DIR, result.error()); |
| |
| // Open the service through FIDL with the directory flag, which should fail. |
| zx::channel abc_client_end, abc_server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0u, &abc_client_end, &abc_server_end)); |
| |
| loop.StartThread(); |
| |
| auto open_result = |
| fio::Directory::Call::Open(zx::unowned_channel(client_end), |
| fio::OPEN_FLAG_DESCRIBE | fio::OPEN_FLAG_DIRECTORY | |
| fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_WRITABLE, |
| 0755, fidl::StringView("abc"), std::move(abc_server_end)); |
| EXPECT_EQ(open_result.status(), ZX_OK); |
| zx_status_t event_status = fio::Node::Call::HandleEvents(zx::unowned_channel(abc_client_end), |
| fio::Node::EventHandlers{ |
| .on_open = [](zx_status_t status, fio::NodeInfo info) { |
| EXPECT_EQ(ZX_ERR_NOT_DIR, status); |
| EXPECT_TRUE(info.has_invalid_tag()); |
| return ZX_OK; |
| }, |
| .unknown = []() { return ZX_ERR_INVALID_ARGS; } |
| }); |
| // Expect that |on_open| was received |
| EXPECT_EQ(ZX_OK, event_status); |
| |
| loop.Shutdown(); |
| |
| END_TEST; |
| } |
| |
| bool TestOpeningServiceWithNodeReferenceFlag() { |
| BEGIN_TEST; |
| |
| // Set up the server |
| zx::channel client_end, server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0u, &client_end, &server_end)); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| fs::SynchronousVfs vfs(loop.dispatcher()); |
| |
| auto directory = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir()); |
| auto vnode = fbl::AdoptRef<fs::Service>(new fs::Service([](zx::channel channel) { |
| channel.reset(); |
| return ZX_OK; |
| })); |
| directory->AddEntry("abc", vnode); |
| ASSERT_EQ(ZX_OK, vfs.ServeDirectory(directory, std::move(server_end))); |
| |
| zx::channel abc_client_end, abc_server_end; |
| ASSERT_EQ(ZX_OK, zx::channel::create(0u, &abc_client_end, &abc_server_end)); |
| |
| loop.StartThread(); |
| |
| auto open_result = |
| fio::Directory::Call::Open(zx::unowned_channel(client_end), fio::OPEN_FLAG_NODE_REFERENCE, |
| 0755, fidl::StringView("abc"), std::move(abc_server_end)); |
| EXPECT_EQ(open_result.status(), ZX_OK); |
| |
| // The channel should speak |fuchsia.io/Node| instead of the custom service FIDL protocol. |
| // We verify it by calling describe on it, which should return correctly. |
| auto describe_result = fio::Node::Call::Describe(zx::unowned_channel(abc_client_end)); |
| ASSERT_EQ(ZX_OK, describe_result.status()); |
| ASSERT_EQ(fio::NodeInfo::Tag::kService, describe_result->info.which()); |
| |
| loop.Shutdown(); |
| |
| END_TEST; |
| } |
| |
| } // namespace |
| |
| BEGIN_TEST_CASE(service_tests) |
| RUN_TEST(test_service) |
| RUN_TEST(TestServeDirectory) |
| RUN_TEST(TestServiceNodeIsNotDirectory) |
| RUN_TEST(TestOpeningServiceWithNodeReferenceFlag) |
| END_TEST_CASE(service_tests) |