blob: 1ec9f3cda9c7df8ffc1ddbc26abb4d1382e3bef6 [file] [log] [blame]
// 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 "pty-client-vnode.h"
#include "pty-client.h"
PtyServer::PtyServer(zx::eventpair local, zx::eventpair remote, fs::Vfs* vfs)
: local_(std::move(local)), remote_(std::move(remote)), vfs_(vfs) {}
PtyServer::~PtyServer() = default;
zx_status_t PtyServer::Create(fbl::RefPtr<PtyServer>* out, fs::Vfs* vfs) {
zx::eventpair local, remote;
zx_status_t status = zx::eventpair::create(0, &local, &remote);
if (status != ZX_OK) {
return 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.
local.signal_peer(
0, fuchsia_device::wire::kDeviceSignalReadable | fuchsia_device::wire::kDeviceSignalHangup);
*out = fbl::MakeRefCounted<PtyServer>(std::move(local), std::move(remote), vfs);
return 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_.is_empty()) {
eof = true;
} else if (length > 0) {
// We only need to clear the READABLE signal if we read anything.
local_.signal_peer(fuchsia_device::wire::kDeviceSignalReadable, 0);
}
}
if (was_full && length > 0) {
if (active_) {
active_->AssertWritableSignal();
}
}
if (length > 0) {
*out_actual = length;
return ZX_OK;
} else if (eof) {
*out_actual = 0;
return ZX_OK;
} else {
return ZX_ERR_SHOULD_WAIT;
}
}
zx_status_t PtyServer::Write(const void* data, size_t count, size_t* out_actual) {
size_t length;
zx_status_t status;
if ((status = Send(data, count, &length)) != ZX_OK) {
return status;
} else {
*out_actual = length;
return ZX_OK;
}
}
zx_status_t PtyServer::CreateClient(uint32_t id,
fidl::ServerEnd<fuchsia_hardware_pty::Device> client_request) {
unsigned num_clients = 0;
// Make sure we don't already have a client with the requested id.
for (auto& client : clients_) {
if (client.id() == id) {
return ZX_ERR_INVALID_ARGS;
}
num_clients++;
}
fbl::RefPtr<PtyClient> client;
zx_status_t status = PtyClient::Create(fbl::RefPtr(this), id, &client);
if (status != ZX_OK) {
return status;
}
clients_.push_back(client);
auto vnode = fbl::MakeRefCounted<PtyClientVnode>(client);
status = vfs_->Serve(vnode, fidl::ServerEnd<fuchsia_io::Node>(client_request.TakeChannel()),
fs::VnodeConnectionOptions::ReadWrite());
if (status != ZX_OK) {
return status;
}
if (active_ == nullptr) {
MakeActive(client);
}
if (id == 0) {
control_ = client;
if (events_) {
control_->AssertEventSignal();
}
}
if (num_clients == 0) {
// if there were no clients, make sure we take server
// out of HANGUP and READABLE, where it landed if all
// its clients had closed
local_.signal_peer(
fuchsia_device::wire::kDeviceSignalReadable | fuchsia_device::wire::kDeviceSignalHangup, 0);
}
client->AdjustSignals();
return ZX_OK;
}
void PtyServer::RemoveClient(PtyClient* client) {
ZX_ASSERT(client->server().get() == this);
if (client->is_control()) {
control_.reset();
}
if (client->is_active()) {
// signal controlling client, if there is one
if (control_) {
// 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_->AssertActiveHungup();
}
active_.reset();
}
clients_.erase(*client);
// signal server, if the last client has gone away
if (clients_.is_empty()) {
local_.signal_peer(
fuchsia_device::wire::kDeviceSignalWritable,
fuchsia_device::wire::kDeviceSignalReadable | fuchsia_device::wire::kDeviceSignalHangup);
}
}
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, fuchsia_device::wire::kDeviceSignalReadable);
}
*is_full = rx_fifo_.is_full();
if (*actual == 0) {
return ZX_ERR_SHOULD_WAIT;
} else {
return ZX_OK;
}
}
zx_status_t PtyServer::Send(const void* data, size_t len, size_t* actual) {
if (!active_) {
*actual = 0;
return ZX_ERR_PEER_CLOSED;
}
if (len == 0) {
*actual = 0;
return ZX_OK;
}
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_) {
control_->AssertEventSignal();
}
}
*actual = r;
}
if (was_empty && !client_fifo->is_empty()) {
active_->AssertReadableSignal();
}
if (client_fifo->is_full()) {
local_.signal_peer(fuchsia_device::wire::kDeviceSignalWritable, 0);
}
return ZX_OK;
}
zx_status_t PtyServer::MakeActive(uint32_t id) {
for (auto& client : clients_) {
if (client.id() == id) {
MakeActive(fbl::RefPtr(&client));
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
void PtyServer::MakeActive(fbl::RefPtr<PtyClient> client) {
if (active_ == client) {
return;
}
if (active_) {
active_->DeAssertWritableSignal();
}
active_ = std::move(client);
active_->AssertWritableSignal();
zx_signals_t to_clear = fuchsia_device::wire::kDeviceSignalHangup;
zx_signals_t to_set = 0;
if (active_->rx_fifo()->is_full()) {
to_clear |= fuchsia_device::wire::kDeviceSignalWritable;
} else {
to_set |= fuchsia_device::wire::kDeviceSignalWritable;
}
local_.signal_peer(to_clear, to_set);
}
void PtyServer::Shutdown() {
for (auto& client : clients_) {
// inform clients that server is gone
client.AssertHangupSignal();
}
active_.reset();
}
uint32_t PtyServer::DrainEvents() {
uint32_t events = events_;
events_ = 0;
if (active_ == nullptr) {
events |= fuchsia_hardware_pty::wire::kEventHangup;
}
control_->DeAssertEventSignal();
return events;
}