[fdio][zxio] Wrap fuchsia.io.Node containing tty

This teaches zxio how to wrap a fuchsia.io.Node instance containing a
tty object. Inside zxio, this is a remote with an event pair from the
describe message. fdio additionally allocates a separate type for tty
instances in order to perform window size ioctls.

Bug: 43267
Change-Id: I0cfdf349b2b2dad4697791f6e29a05a54797de1e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/539913
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: James Robinson <jamesr@google.com>
Reviewed-by: Tamir Duberstein <tamird@google.com>
diff --git a/sdk/lib/fdio/remoteio.cc b/sdk/lib/fdio/remoteio.cc
index bab8351..47311fa 100644
--- a/sdk/lib/fdio/remoteio.cc
+++ b/sdk/lib/fdio/remoteio.cc
@@ -71,6 +71,9 @@
     case ZXIO_OBJECT_TYPE_SERVICE:
       io = fbl::MakeRefCounted<fdio_internal::remote>();
       break;
+    case ZXIO_OBJECT_TYPE_TTY:
+      io = fbl::MakeRefCounted<fdio_internal::pty>();
+      break;
     case ZXIO_OBJECT_TYPE_VMO:
       io = fbl::MakeRefCounted<fdio_internal::remote>();
       break;
@@ -121,11 +124,6 @@
   }
 
   switch (info.which()) {
-    case fio::wire::NodeInfo::Tag::kTty: {
-      auto& tty = info.mutable_tty();
-      return fdio_internal::pty::create(fidl::ClientEnd<fpty::Device>(node.TakeChannel()),
-                                        std::move(tty.event));
-    }
     case fio::wire::NodeInfo::Tag::kDatagramSocket: {
       auto& socket = info.mutable_datagram_socket();
       return zx::ok(fdio_datagram_socket_create(
diff --git a/sdk/lib/fdio/zxio.cc b/sdk/lib/fdio/zxio.cc
index de6a417..1c03e5c 100644
--- a/sdk/lib/fdio/zxio.cc
+++ b/sdk/lib/fdio/zxio.cc
@@ -313,19 +313,6 @@
          zxio_abilities_to_posix_permissions_for_directory(abilities);
 }
 
-zx::status<fdio_ptr> pty::create(fidl::ClientEnd<fpty::Device> device, zx::eventpair event) {
-  fdio_ptr io = fbl::MakeRefCounted<pty>();
-  if (io == nullptr) {
-    return zx::error(ZX_ERR_NO_MEMORY);
-  }
-  zx_status_t status =
-      zxio_remote_init(&io->zxio_storage(), device.channel().release(), event.release());
-  if (status != ZX_OK) {
-    return zx::error(status);
-  }
-  return zx::ok(io);
-}
-
 Errno pty::posix_ioctl(int request, va_list va) {
   switch (request) {
     case TIOCGWINSZ: {
diff --git a/sdk/lib/fdio/zxio.h b/sdk/lib/fdio/zxio.h
index aadfcb5..470c47b 100644
--- a/sdk/lib/fdio/zxio.h
+++ b/sdk/lib/fdio/zxio.h
@@ -111,8 +111,6 @@
 };
 
 struct pty : public remote {
-  static zx::status<fdio_ptr> create(fidl::ClientEnd<fuchsia_hardware_pty::Device> device,
-                                     zx::eventpair event);
   Errno posix_ioctl(int request, va_list va) override;
 
  protected:
diff --git a/sdk/lib/zxio/create.cc b/sdk/lib/zxio/create.cc
index 17b2679..45e941a 100644
--- a/sdk/lib/zxio/create.cc
+++ b/sdk/lib/zxio/create.cc
@@ -149,6 +149,11 @@
     case fio::wire::NodeInfo::Tag::kService: {
       return zxio_remote_init(storage, node.TakeChannel().release(), ZX_HANDLE_INVALID);
     }
+    case fio::wire::NodeInfo::Tag::kTty: {
+      auto& tty = info.mutable_tty();
+      zx::eventpair event = std::move(tty.event);
+      return zxio_remote_init(storage, node.TakeChannel().release(), event.release());
+    }
     case fio::wire::NodeInfo::Tag::kVmofile: {
       auto& file = info.mutable_vmofile();
       auto control = fidl::ClientEnd<fio::File>(node.TakeChannel());
@@ -164,8 +169,9 @@
       return zxio_vmofile_init(storage, fidl::BindSyncClient(std::move(control)),
                                std::move(file.vmo), file.offset, file.length, result->offset);
     }
-    default:
+    default: {
       zxio_handle_holder_init(storage, node.TakeChannel());
       return ZX_ERR_NOT_SUPPORTED;
+    }
   }
 }
diff --git a/sdk/lib/zxio/inception.cc b/sdk/lib/zxio/inception.cc
index f8b4f4f..6b4cf11 100644
--- a/sdk/lib/zxio/inception.cc
+++ b/sdk/lib/zxio/inception.cc
@@ -70,6 +70,9 @@
     case fio::wire::NodeInfo::Tag::kService:
       type = ZXIO_OBJECT_TYPE_SERVICE;
       break;
+    case fio::wire::NodeInfo::Tag::kTty:
+      type = ZXIO_OBJECT_TYPE_TTY;
+      break;
     case fio::wire::NodeInfo::Tag::kVmofile:
       type = ZXIO_OBJECT_TYPE_VMOFILE;
       break;
diff --git a/sdk/lib/zxio/tests/BUILD.gn b/sdk/lib/zxio/tests/BUILD.gn
index 36ca645..3d338d3 100644
--- a/sdk/lib/zxio/tests/BUILD.gn
+++ b/sdk/lib/zxio/tests/BUILD.gn
@@ -38,6 +38,7 @@
   ]
   deps = [
     "//sdk/fidl/fuchsia.boot:fuchsia.boot_llcpp",
+    "//sdk/fidl/fuchsia.hardware.pty:fuchsia.hardware.pty_llcpp",
     "//sdk/fidl/fuchsia.io:fuchsia.io_llcpp",
     "//sdk/fidl/fuchsia.io2:fuchsia.io2_llcpp",
     "//sdk/lib/fdio",
diff --git a/sdk/lib/zxio/tests/inception-test.cc b/sdk/lib/zxio/tests/inception-test.cc
index fa29c91..cec159b 100644
--- a/sdk/lib/zxio/tests/inception-test.cc
+++ b/sdk/lib/zxio/tests/inception-test.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <fuchsia/hardware/pty/llcpp/fidl_test_base.h>
 #include <fuchsia/io/llcpp/fidl_test_base.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/default.h>
@@ -388,6 +389,70 @@
   service_control_loop.Shutdown();
 }
 
+class TestTtyServer : public fuchsia_hardware_pty::testing::Device_TestBase {
+ public:
+  void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) final {
+    ADD_FAILURE("unexpected message received: %s", name.c_str());
+    completer.Close(ZX_ERR_NOT_SUPPORTED);
+  }
+
+  void Close(CloseRequestView request, CloseCompleter::Sync& completer) final {
+    completer.Reply(ZX_OK);
+    // After the reply, we should close the connection.
+    completer.Close(ZX_OK);
+  }
+};
+
+TEST(CreateWithInfo, Tty) {
+  auto node_ends = fidl::CreateEndpoints<fuchsia_io::Node>();
+  ASSERT_OK(node_ends.status_value());
+  auto [node_client, node_server] = std::move(node_ends.value());
+
+  zx::eventpair event0, event1;
+  ASSERT_OK(zx::eventpair::create(0, &event0, &event1));
+
+  fuchsia_io::wire::Tty tty = {.event = std::move(event1)};
+  fidl::FidlAllocator fidl_allocator;
+  auto node_info = fuchsia_io::wire::NodeInfo::WithTty(fidl_allocator, std::move(tty));
+
+  auto allocator = [](zxio_object_type_t type, zxio_storage_t** out_storage, void** out_context) {
+    if (type != ZXIO_OBJECT_TYPE_TTY) {
+      return ZX_ERR_NOT_SUPPORTED;
+    }
+    *out_storage = new zxio_storage_t;
+    *out_context = *out_storage;
+    return ZX_OK;
+  };
+
+  async::Loop tty_control_loop(&kAsyncLoopConfigNoAttachToCurrentThread);
+  TestTtyServer server;
+  fidl::ServerEnd<fuchsia_hardware_pty::Device> tty_server(node_server.TakeChannel());
+  fidl::BindServer(tty_control_loop.dispatcher(), std::move(tty_server), &server);
+  tty_control_loop.StartThread("tty_control_thread");
+
+  void* context = nullptr;
+  ASSERT_OK(zxio_create_with_allocator(std::move(node_client), node_info, allocator, &context));
+  ASSERT_NE(context, nullptr);
+
+  // The event in node_info should be consumed by zxio.
+  EXPECT_FALSE(node_info.tty().event.is_valid());
+
+  std::unique_ptr<zxio_storage_t> storage(static_cast<zxio_storage_t*>(context));
+  zxio_t* zxio = &(storage->io);
+
+  // Closing the zxio object should close our eventpair's peer event.
+  zx_signals_t pending = 0;
+  ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
+  EXPECT_NE(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
+
+  ASSERT_OK(zxio_close(zxio));
+
+  ASSERT_EQ(event0.wait_one(0u, zx::time::infinite_past(), &pending), ZX_ERR_TIMED_OUT);
+  EXPECT_EQ(pending & ZX_EVENTPAIR_PEER_CLOSED, ZX_EVENTPAIR_PEER_CLOSED, "pending is %u", pending);
+
+  tty_control_loop.Shutdown();
+}
+
 class TestVmofileServer : public zxio_tests::TestFileServerBase {
  public:
   explicit TestVmofileServer(uint64_t seek_start_offset) : seek_start_offset_(seek_start_offset) {}