[cpp-vfs] Implement vfs::Service.
DX-387 #comment
TEST=fx run-test vfs_cpp_tests
Change-Id: I5356bb65644defae46c282a39fa289f5c86a04ee
diff --git a/garnet/public/lib/vfs/cpp/BUILD.gn b/garnet/public/lib/vfs/cpp/BUILD.gn
index da3eb1f3..77fd660 100644
--- a/garnet/public/lib/vfs/cpp/BUILD.gn
+++ b/garnet/public/lib/vfs/cpp/BUILD.gn
@@ -30,6 +30,8 @@
"pseudo_dir.h",
"pseudo_file.cc",
"pseudo_file.h",
+ "service.cc",
+ "service.h",
]
public_deps = [
@@ -47,10 +49,12 @@
"file_unittest.cc",
"pseudo_dir_unittest.cc",
"pseudo_file_unittest.cc",
+ "service_unittest.cc",
]
deps = [
":cpp",
+ "//garnet/examples/fidl/services:echo2",
"//garnet/public/lib/gtest",
"//third_party/googletest:gtest_main",
]
diff --git a/garnet/public/lib/vfs/cpp/directory.cc b/garnet/public/lib/vfs/cpp/directory.cc
index d8f7b3a..7b2c597 100644
--- a/garnet/public/lib/vfs/cpp/directory.cc
+++ b/garnet/public/lib/vfs/cpp/directory.cc
@@ -28,6 +28,8 @@
}
uint32_t Directory::GetAdditionalAllowedFlags() const {
+ // TODO(ZX-3251): overide this in PseudoDir and remove OPEN_RIGHT_WRITABLE
+ // flag.
return fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE |
fuchsia::io::OPEN_FLAG_DIRECTORY;
}
diff --git a/garnet/public/lib/vfs/cpp/node.cc b/garnet/public/lib/vfs/cpp/node.cc
index 0cfbaad..0dc3c31 100644
--- a/garnet/public/lib/vfs/cpp/node.cc
+++ b/garnet/public/lib/vfs/cpp/node.cc
@@ -110,6 +110,12 @@
SendOnOpenEventOnError(flags, std::move(request), status);
return status;
}
+ return Connect(flags, std::move(request), dispatcher);
+}
+
+zx_status_t Node::Connect(uint32_t flags, zx::channel request,
+ async_dispatcher_t* dispatcher) {
+ zx_status_t status;
std::unique_ptr<Connection> connection;
if (Flags::IsPathOnly(flags)) {
status = Node::CreateConnection(flags, &connection);
diff --git a/garnet/public/lib/vfs/cpp/node.h b/garnet/public/lib/vfs/cpp/node.h
index b00169a..212c3bf 100644
--- a/garnet/public/lib/vfs/cpp/node.h
+++ b/garnet/public/lib/vfs/cpp/node.h
@@ -85,8 +85,7 @@
// |async_get_default_dispatcher| to obtain the default dispatcher for the
// current thread.
//
- // Uses |CreateConnection| to create a connection appropriate for the concrete
- // type of this object.
+ // Calls |Connect| after validating flags and modes.
zx_status_t Serve(uint32_t flags, zx::channel request,
async_dispatcher_t* dispatcher = nullptr);
@@ -109,6 +108,16 @@
virtual zx_status_t Lookup(const std::string& name, Node** out_node) const;
protected:
+ // Called by |Serve| after validating flags and modes.
+ // This should be implemented by sub classes which doesn't create a connection
+ // class.
+ //
+ // Default implementation:
+ // Uses |CreateConnection| to create a connection appropriate for the concrete
+ // type of this object.
+ virtual zx_status_t Connect(uint32_t flags, zx::channel request,
+ async_dispatcher_t* dispatcher);
+
const std::vector<std::unique_ptr<Connection>>& connections() const {
return connections_;
}
diff --git a/garnet/public/lib/vfs/cpp/service.cc b/garnet/public/lib/vfs/cpp/service.cc
new file mode 100644
index 0000000..ba723f7f
--- /dev/null
+++ b/garnet/public/lib/vfs/cpp/service.cc
@@ -0,0 +1,57 @@
+// 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 <lib/vfs/cpp/service.h>
+
+#include <lib/fdio/vfs.h>
+#include <lib/vfs/cpp/flags.h>
+
+namespace vfs {
+
+Service::Service(Connector connector) : connector_(std::move(connector)) {}
+
+Service::~Service() = default;
+
+void Service::Describe(fuchsia::io::NodeInfo* out_info) {
+ out_info->set_service(fuchsia::io::Service());
+}
+
+zx_status_t Service::CreateConnection(uint32_t flags,
+ std::unique_ptr<Connection>* connection) {
+ return ZX_ERR_NOT_SUPPORTED;
+}
+
+uint32_t Service::GetAdditionalAllowedFlags() const {
+ // TODO(ZX-3251): remove OPEN_RIGHT_WRITABLE flag
+ return fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE;
+}
+
+zx_status_t Service::Connect(uint32_t flags, zx::channel request,
+ async_dispatcher_t* dispatcher) {
+ if (Flags::IsPathOnly(flags)) {
+ return Node::Connect(flags, std::move(request), dispatcher);
+ }
+ if (connector_ == nullptr) {
+ SendOnOpenEventOnError(flags, std::move(request), ZX_ERR_NOT_SUPPORTED);
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+ connector_(std::move(request), dispatcher);
+ return ZX_OK;
+}
+
+bool Service::IsDirectory() const { return false; }
+
+zx_status_t Service::GetAttr(
+ fuchsia::io::NodeAttributes* out_attributes) const {
+ out_attributes->mode = fuchsia::io::MODE_TYPE_SERVICE | V_IRUSR;
+ out_attributes->id = fuchsia::io::INO_UNKNOWN;
+ out_attributes->content_size = 0;
+ out_attributes->storage_size = 0;
+ out_attributes->link_count = 1;
+ out_attributes->creation_time = 0;
+ out_attributes->modification_time = 0;
+ return ZX_OK;
+}
+
+} // namespace vfs
\ No newline at end of file
diff --git a/garnet/public/lib/vfs/cpp/service.h b/garnet/public/lib/vfs/cpp/service.h
new file mode 100644
index 0000000..8fcf1a2
--- /dev/null
+++ b/garnet/public/lib/vfs/cpp/service.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef LIB_VFS_CPP_SERVICE_H_
+#define LIB_VFS_CPP_SERVICE_H_
+
+#include <fuchsia/io/cpp/fidl.h>
+#include <lib/vfs/cpp/node.h>
+
+namespace vfs {
+
+// A |Node| which binds a channel to a service implementation when opened.
+//
+// This class is thread-safe.
+class Service : public Node {
+ public:
+ // Handler called to bind the provided channel to an implementation
+ // of the service.
+ using Connector =
+ fit::function<void(zx::channel channel, async_dispatcher_t* dispatcher)>;
+
+ // Adds the specified interface to the set of public interfaces.
+ //
+ // Creates |Service| with a |connector| with the given |service_name|, using
+ // the given |interface_request_handler|. |interface_request_handler| should
+ // remain valid for the lifetime of this object.
+ //
+ // A typical usage may be:
+ //
+ // vfs::Service foo_service(foobar_bindings_.GetHandler(this, dispatcher));
+ //
+ // For now this implementation ignores |dispatcher| that we get from |Serve|
+ // call, if you want to use dispatcher call |Service(Connector)|.
+ template <typename Interface>
+ explicit Service(fidl::InterfaceRequestHandler<Interface> handler)
+ : Service([handler = std::move(handler)](zx::channel channel,
+ async_dispatcher_t* dispatcher) {
+ handler(fidl::InterfaceRequest<Interface>(std::move(channel)));
+ }) {}
+
+ // Creates a service with the specified connector.
+ //
+ // If the |connector| is null, then incoming connection requests will be
+ // dropped.
+ explicit Service(Connector connector);
+
+ // Destroys the services and releases its connector.
+ ~Service() override;
+
+ // |Node| implementation:
+ zx_status_t GetAttr(fuchsia::io::NodeAttributes* out_attributes) const final;
+
+ void Describe(fuchsia::io::NodeInfo* out_info) override final;
+
+ const Connector& connector() const { return connector_; }
+
+ protected:
+ // |Node| implementations:
+ zx_status_t CreateConnection(uint32_t flags,
+ std::unique_ptr<Connection>* connection) final;
+
+ uint32_t GetAdditionalAllowedFlags() const override final;
+
+ bool IsDirectory() const override final;
+
+ zx_status_t Connect(uint32_t flags, zx::channel request,
+ async_dispatcher_t* dispatcher) override;
+
+ private:
+ Connector connector_;
+};
+
+} // namespace vfs
+
+#endif // LIB_VFS_CPP_SERVICE_H_
\ No newline at end of file
diff --git a/garnet/public/lib/vfs/cpp/service_unittest.cc b/garnet/public/lib/vfs/cpp/service_unittest.cc
new file mode 100644
index 0000000..da1bef1
--- /dev/null
+++ b/garnet/public/lib/vfs/cpp/service_unittest.cc
@@ -0,0 +1,136 @@
+// 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 "lib/vfs/cpp/service.h"
+
+#include <fidl/examples/echo/cpp/fidl.h>
+#include <lib/fdio/vfs.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include "lib/gtest/real_loop_fixture.h"
+#include "lib/vfs/cpp/pseudo_dir.h"
+
+class ServiceTest : public gtest::RealLoopFixture,
+ public fidl::examples::echo::Echo {
+ void EchoString(fidl::StringPtr value, EchoStringCallback callback) override {
+ callback(answer_);
+ }
+
+ protected:
+ ServiceTest()
+ : answer_("my_fake_ans"),
+ service_name_("echo_service"),
+ second_loop_(&kAsyncLoopConfigNoAttachToThread) {
+ auto service = std::make_unique<vfs::Service>(
+ bindings_.GetHandler(this, second_loop_.dispatcher()));
+
+ dir_.Serve(0, dir_ptr_.NewRequest().TakeChannel(),
+ second_loop_.dispatcher());
+ dir_.AddEntry(service_name_, std::move(service));
+ second_loop_.StartThread("vfs test thread");
+ }
+
+ const std::string& answer() const { return answer_; }
+
+ const std::string& service_name() const { return service_name_; }
+
+ void AssertInValidOpen(uint32_t flag, uint32_t mode,
+ zx_status_t expected_status) {
+ SCOPED_TRACE("flag: " + std::to_string(flag) +
+ ", mode: " + std::to_string(mode));
+ fuchsia::io::NodePtr node_ptr;
+ dir_ptr()->Open(flag | fuchsia::io::OPEN_FLAG_DESCRIBE, mode,
+ service_name(), node_ptr.NewRequest());
+
+ bool on_open_called = false;
+
+ node_ptr.events().OnOpen =
+ [&](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> unused) {
+ EXPECT_FALSE(on_open_called); // should be called only once
+ on_open_called = true;
+ EXPECT_EQ(expected_status, status);
+ };
+
+ ASSERT_TRUE(RunLoopWithTimeoutOrUntil([&]() { return on_open_called; },
+ zx::sec(1), zx::msec(1)));
+ }
+
+ fuchsia::io::DirectoryPtr& dir_ptr() { return dir_ptr_; }
+
+ private:
+ std::string answer_;
+ std::string service_name_;
+ fidl::BindingSet<Echo> bindings_;
+ vfs::PseudoDir dir_;
+ fuchsia::io::DirectoryPtr dir_ptr_;
+ async::Loop second_loop_;
+};
+
+TEST_F(ServiceTest, CanOpenAsNodeReferenceAndTestGetAttr) {
+ fuchsia::io::NodeSyncPtr ptr;
+ dir_ptr()->Open(fuchsia::io::OPEN_FLAG_NODE_REFERENCE, 0, service_name(),
+ ptr.NewRequest());
+
+ zx_status_t s;
+ fuchsia::io::NodeAttributes attr;
+ ptr->GetAttr(&s, &attr);
+ EXPECT_EQ(ZX_OK, s);
+ EXPECT_EQ(fuchsia::io::MODE_TYPE_SERVICE,
+ attr.mode & fuchsia::io::MODE_TYPE_SERVICE);
+}
+
+TEST_F(ServiceTest, TestDescribe) {
+ fuchsia::io::NodeSyncPtr ptr;
+ dir_ptr()->Open(fuchsia::io::OPEN_FLAG_NODE_REFERENCE, 0, service_name(),
+ ptr.NewRequest());
+
+ fuchsia::io::NodeInfo info;
+ ptr->Describe(&info);
+ EXPECT_TRUE(info.is_service());
+}
+
+TEST_F(ServiceTest, CanOpenAsAService) {
+ uint32_t flags[] = {0, fuchsia::io::OPEN_RIGHT_READABLE,
+ fuchsia::io::OPEN_RIGHT_WRITABLE};
+ uint32_t modes[] = {
+ 0, fuchsia::io::MODE_TYPE_SERVICE, V_IRWXU, V_IRUSR, V_IWUSR, V_IXUSR};
+
+ for (uint32_t mode : modes) {
+ for (uint32_t flag : flags) {
+ SCOPED_TRACE("flag: " + std::to_string(flag) +
+ ", mode: " + std::to_string(mode));
+ fidl::examples::echo::EchoSyncPtr ptr;
+ dir_ptr()->Open(flag, mode, service_name(),
+ fidl::InterfaceRequest<fuchsia::io::Node>(
+ ptr.NewRequest().TakeChannel()));
+
+ fidl::StringPtr ans;
+ ptr->EchoString("hello", &ans);
+ EXPECT_EQ(answer(), ans);
+ }
+ }
+}
+
+TEST_F(ServiceTest, CannotOpenServiceWithInvalidFlags) {
+ uint32_t flags[] = {fuchsia::io::OPEN_RIGHT_ADMIN,
+ fuchsia::io::OPEN_FLAG_CREATE,
+ fuchsia::io::OPEN_FLAG_CREATE_IF_ABSENT,
+ fuchsia::io::OPEN_FLAG_TRUNCATE,
+ fuchsia::io::OPEN_FLAG_APPEND,
+ fuchsia::io::OPEN_FLAG_NO_REMOTE};
+
+ for (uint32_t flag : flags) {
+ AssertInValidOpen(flag, 0, ZX_ERR_NOT_SUPPORTED);
+ }
+ AssertInValidOpen(fuchsia::io::OPEN_FLAG_DIRECTORY, 0, ZX_ERR_NOT_DIR);
+}
+
+TEST_F(ServiceTest, CannotOpenServiceWithInvalidMode) {
+ uint32_t modes[] = {
+ fuchsia::io::MODE_TYPE_DIRECTORY, fuchsia::io::MODE_TYPE_BLOCK_DEVICE,
+ fuchsia::io::MODE_TYPE_FILE, fuchsia::io::MODE_TYPE_SOCKET};
+
+ for (uint32_t mode : modes) {
+ AssertInValidOpen(0, mode, ZX_ERR_INVALID_ARGS);
+ }
+}
\ No newline at end of file