| // 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 "pty-server.h" |
| |
| #include <fidl/fuchsia.hardware.pty/cpp/wire.h> |
| #include <lib/zx/eventpair.h> |
| |
| #include "pty-client.h" |
| |
| zx::result<PtyServer::Args> PtyServer::Args::Create() { |
| PtyServer::Args args; |
| if (zx_status_t status = zx::eventpair::create(0, &args.local_, &args.remote_); status != ZX_OK) { |
| return zx::error(status); |
| } |
| // Create the FIFO in the "hung-up" state. Note that this is |
| // considered "readable" so that clients will try to read and see an |
| // EOF condition via a 0-byte response with ZX_OK. |
| if (zx_status_t status = args.local_.signal_peer( |
| 0, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable | |
| fuchsia_device::wire::DeviceSignal::kHangup)); |
| status != ZX_OK) { |
| return zx::error(status); |
| } |
| return zx::ok(std::move(args)); |
| } |
| |
| PtyServer::PtyServer(Args args, async_dispatcher_t* dispatcher) |
| : local_(std::move(args.local_)), remote_(std::move(args.remote_)), dispatcher_(dispatcher) { |
| bindings_.set_empty_set_handler([this]() { |
| for (auto& [id, client] : clients_) { |
| // inform clients that server is gone |
| client.AssertHangupSignal(); |
| } |
| active_.reset(); |
| }); |
| } |
| |
| PtyServer::~PtyServer() = default; |
| |
| void PtyServer::AddConnection(fidl::ServerEnd<fuchsia_hardware_pty::Device> request) { |
| bindings_.AddBinding(dispatcher(), std::move(request), this, |
| [keep_alive = shared_from_this()](fidl::UnbindInfo) {}); |
| } |
| |
| void PtyServer::Clone2(Clone2RequestView request, Clone2Completer::Sync& completer) { |
| AddConnection(fidl::ServerEnd<fuchsia_hardware_pty::Device>(request->request.TakeChannel())); |
| } |
| |
| void PtyServer::Close(CloseCompleter::Sync& completer) { |
| completer.ReplySuccess(); |
| completer.Close(ZX_OK); |
| } |
| |
| void PtyServer::Query(QueryCompleter::Sync& completer) { |
| const std::string_view kProtocol = fuchsia_hardware_pty::wire::kDeviceProtocolName; |
| // TODO(https://fxbug.dev/42052765): avoid the const cast. |
| uint8_t* data = reinterpret_cast<uint8_t*>(const_cast<char*>(kProtocol.data())); |
| completer.Reply(fidl::VectorView<uint8_t>::FromExternal(data, kProtocol.size())); |
| } |
| |
| void PtyServer::Read(ReadRequestView request, ReadCompleter::Sync& completer) { |
| uint8_t data[fuchsia_io::wire::kMaxBuf]; |
| uint64_t len = std::min(request->count, sizeof(data)); |
| size_t out_actual; |
| if (zx_status_t status = Read(data, len, &out_actual); status != ZX_OK) { |
| return completer.ReplyError(status); |
| } |
| return completer.ReplySuccess(fidl::VectorView<uint8_t>::FromExternal(data, out_actual)); |
| } |
| |
| void PtyServer::Write(WriteRequestView request, WriteCompleter::Sync& completer) { |
| size_t out_actual; |
| if (zx_status_t status = Write(request->data.data(), request->data.count(), &out_actual); |
| status != ZX_OK) { |
| return completer.ReplyError(status); |
| } |
| return completer.ReplySuccess(out_actual); |
| } |
| |
| void PtyServer::Describe(DescribeCompleter::Sync& completer) { |
| zx::eventpair event; |
| if (zx_status_t status = remote_.duplicate(ZX_RIGHTS_BASIC, &event); status != ZX_OK) { |
| completer.Close(status); |
| } else { |
| fidl::Arena alloc; |
| completer.Reply(fuchsia_hardware_pty::wire::DeviceDescribeResponse::Builder(alloc) |
| .event(std::move(event)) |
| .Build()); |
| } |
| } |
| |
| void PtyServer::OpenClient(OpenClientRequestView request, OpenClientCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::OpenClient> buf; |
| completer.buffer(buf.view()).Reply(CreateClient(request->id, std::move(request->client))); |
| } |
| |
| void PtyServer::ClrSetFeature(ClrSetFeatureRequestView request, |
| ClrSetFeatureCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::ClrSetFeature> buf; |
| completer.buffer(buf.view()).Reply(ZX_ERR_NOT_SUPPORTED, 0); |
| } |
| |
| void PtyServer::GetWindowSize(GetWindowSizeCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::GetWindowSize> buf; |
| completer.buffer(buf.view()).Reply(ZX_ERR_NOT_SUPPORTED, {}); |
| } |
| |
| void PtyServer::MakeActive(MakeActiveRequestView request, MakeActiveCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::MakeActive> buf; |
| completer.buffer(buf.view()).Reply(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void PtyServer::ReadEvents(ReadEventsCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::ReadEvents> buf; |
| completer.buffer(buf.view()).Reply(ZX_ERR_NOT_SUPPORTED, 0); |
| } |
| |
| void PtyServer::SetWindowSize(SetWindowSizeRequestView request, |
| SetWindowSizeCompleter::Sync& completer) { |
| fidl::ServerBuffer<fuchsia_hardware_pty::Device::SetWindowSize> buf; |
| size_ = request->size; |
| events_ |= fuchsia_hardware_pty::wire::kEventWindowSize; |
| if (control_.has_value()) { |
| control_.value().get().AssertEventSignal(); |
| } |
| completer.buffer(buf.view()).Reply(ZX_OK); |
| } |
| |
| zx_status_t PtyServer::Read(void* data, size_t count, size_t* out_actual) { |
| if (count == 0) { |
| *out_actual = 0; |
| return ZX_OK; |
| } |
| |
| bool eof = false; |
| |
| bool was_full = rx_fifo_.is_full(); |
| size_t length = rx_fifo_.Read(data, count); |
| if (rx_fifo_.is_empty()) { |
| if (clients_.empty()) { |
| eof = true; |
| } else if (length > 0) { |
| // We only need to clear the READABLE signal if we read anything. |
| local_.signal_peer(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable), |
| 0); |
| } |
| } |
| if (was_full && length > 0) { |
| if (active_.has_value()) { |
| active_.value().get().AssertWritableSignal(); |
| } |
| } |
| |
| if (length > 0) { |
| *out_actual = length; |
| return ZX_OK; |
| } |
| if (eof) { |
| *out_actual = 0; |
| return ZX_OK; |
| } |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| zx_status_t PtyServer::Write(const void* data, size_t count, size_t* out_actual) { |
| size_t length; |
| if (zx_status_t status = Send(data, count, &length); status != ZX_OK) { |
| return status; |
| } |
| *out_actual = length; |
| return ZX_OK; |
| } |
| |
| zx_status_t PtyServer::CreateClient(uint32_t id, |
| fidl::ServerEnd<fuchsia_hardware_pty::Device> client_request) { |
| // Make sure we don't already have a client with the requested id. |
| if (clients_.find(id) != clients_.end()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx::eventpair local, remote; |
| if (zx_status_t status = zx::eventpair::create(0, &local, &remote); status != ZX_OK) { |
| return status; |
| } |
| |
| if (clients_.empty()) { |
| // if there were no clients, make sure we take server |
| // out of HANGUP and READABLE, where it landed if all |
| // its clients had closed |
| if (zx_status_t status = local_.signal_peer( |
| static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable | |
| fuchsia_device::wire::DeviceSignal::kHangup), |
| 0); |
| status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| const auto [it, inserted] = |
| clients_.try_emplace(id, shared_from_this(), id, std::move(local), std::move(remote)); |
| ZX_ASSERT(inserted); |
| PtyClient& client = it->second; |
| client.AddConnection(std::move(client_request)); |
| |
| if (!active_.has_value()) { |
| MakeActive(client); |
| } |
| if (id == 0) { |
| control_ = client; |
| if (events_) { |
| client.AssertEventSignal(); |
| } |
| } |
| |
| client.AdjustSignals(); |
| return ZX_OK; |
| } |
| |
| void PtyServer::RemoveClient(uint32_t id) { |
| auto client_node = clients_.extract(id); |
| ZX_ASSERT(!client_node.empty()); |
| PtyClient& client = client_node.mapped(); |
| if (client.is_control()) { |
| control_.reset(); |
| } |
| |
| if (client.is_active()) { |
| // signal controlling client, if there is one |
| if (control_.has_value()) { |
| // Note that in the implementation this is ported from, DEVICE_SIGNAL_HANGUP is never |
| // cleared after being asserted by this? This seems likely to be a bug. |
| control_.value().get().AssertActiveHungup(); |
| } |
| active_.reset(); |
| } |
| |
| // signal server, if the last client has gone away |
| if (clients_.empty()) { |
| local_.signal_peer(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable), |
| static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable | |
| fuchsia_device::wire::DeviceSignal::kHangup)); |
| } |
| } |
| |
| zx_status_t PtyServer::Recv(const void* data, size_t len, size_t* actual, bool* is_full) { |
| if (len == 0) { |
| *actual = 0; |
| return ZX_OK; |
| } |
| |
| bool was_empty = rx_fifo_.is_empty(); |
| *actual = rx_fifo_.Write(data, len, false); |
| if (was_empty && *actual) { |
| local_.signal_peer(0, static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kReadable)); |
| } |
| |
| *is_full = rx_fifo_.is_full(); |
| |
| if (*actual == 0) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t PtyServer::Send(const void* data, size_t len, size_t* actual) { |
| if (!active_.has_value()) { |
| *actual = 0; |
| return ZX_ERR_PEER_CLOSED; |
| } |
| |
| if (len == 0) { |
| *actual = 0; |
| return ZX_OK; |
| } |
| |
| PtyClient& active = active_.value().get(); |
| Fifo* client_fifo = active.rx_fifo(); |
| if (client_fifo->is_full()) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| bool was_empty = client_fifo->is_empty(); |
| if (active.in_raw_mode()) { |
| *actual = client_fifo->Write(data, len, false); |
| } else { |
| if (len > Fifo::kSize) { |
| len = Fifo::kSize; |
| } |
| auto ch = static_cast<const uint8_t*>(data); |
| unsigned n = 0; |
| unsigned evt = 0; |
| while (n < len) { |
| // The ASCII code that Ctrl-C generates |
| constexpr uint8_t kCtrlC = 0x3; |
| if (*ch++ == kCtrlC) { |
| evt = fuchsia_hardware_pty::wire::kEventInterrupt; |
| break; |
| } |
| n++; |
| } |
| size_t r = client_fifo->Write(data, n, false); |
| if ((r == n) && evt) { |
| // consume the event |
| r++; |
| events_ |= evt; |
| if (control_.has_value()) { |
| control_.value().get().AssertEventSignal(); |
| } |
| } |
| *actual = r; |
| } |
| if (was_empty && !client_fifo->is_empty()) { |
| active.AssertReadableSignal(); |
| } |
| if (client_fifo->is_full()) { |
| local_.signal_peer(static_cast<zx_signals_t>(fuchsia_device::wire::DeviceSignal::kWritable), 0); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t PtyServer::MakeActive(uint32_t id) { |
| const auto it = clients_.find(id); |
| if (it == clients_.end()) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| MakeActive(it->second); |
| return ZX_OK; |
| } |
| |
| void PtyServer::MakeActive(PtyClient& client) { |
| if (active_.has_value() && &active_.value().get() == &client) { |
| return; |
| } |
| if (std::optional active = std::exchange(active_, client); active.has_value()) { |
| active.value().get().DeAssertWritableSignal(); |
| } |
| client.AssertWritableSignal(); |
| |
| fuchsia_device::wire::DeviceSignal to_clear = fuchsia_device::wire::DeviceSignal::kHangup; |
| fuchsia_device::wire::DeviceSignal to_set; |
| if (client.rx_fifo()->is_full()) { |
| to_clear |= fuchsia_device::wire::DeviceSignal::kWritable; |
| } else { |
| to_set |= fuchsia_device::wire::DeviceSignal::kWritable; |
| } |
| |
| local_.signal_peer(static_cast<zx_signals_t>(to_clear), static_cast<zx_signals_t>(to_set)); |
| } |
| |
| uint32_t PtyServer::DrainEvents() { |
| uint32_t events = events_; |
| events_ = 0; |
| if (!active_.has_value()) { |
| events |= fuchsia_hardware_pty::wire::kEventHangup; |
| } |
| if (control_.has_value()) { |
| control_.value().get().DeAssertEventSignal(); |
| } |
| return events; |
| } |