blob: 3355234e8c7e936e6bab51b0a1f35ca55ea1b615 [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-client.h"
#include <fidl/fuchsia.io/cpp/wire.h>
#include <utility>
#include "pty-server.h"
PtyClient::PtyClient(std::shared_ptr<PtyServer> server, uint32_t id, zx::eventpair local,
zx::eventpair remote)
: server_(std::move(server)), id_(id), local_(std::move(local)), remote_(std::move(remote)) {
bindings_.set_empty_set_handler([this]() { this->server().RemoveClient(id_); });
}
void PtyClient::AddConnection(fidl::ServerEnd<fuchsia_hardware_pty::Device> request) {
bindings_.AddBinding(server().dispatcher(), std::move(request), this, [](fidl::UnbindInfo) {});
}
void PtyClient::Clone2(Clone2RequestView request, Clone2Completer::Sync& completer) {
AddConnection(fidl::ServerEnd<fuchsia_hardware_pty::Device>(request->request.TakeChannel()));
}
void PtyClient::Close(CloseCompleter::Sync& completer) {
completer.ReplySuccess();
completer.Close(ZX_OK);
}
void PtyClient::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 PtyClient::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 PtyClient::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 PtyClient::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 PtyClient::SetWindowSize(SetWindowSizeRequestView request,
SetWindowSizeCompleter::Sync& completer) {
server().SetWindowSize(request, completer);
}
void PtyClient::OpenClient(OpenClientRequestView request, OpenClientCompleter::Sync& completer) {
fidl::ServerBuffer<fuchsia_hardware_pty::Device::OpenClient> buf;
// Only controlling clients (and the server itself) may create new clients
if (!is_control()) {
completer.buffer(buf.view()).Reply(ZX_ERR_ACCESS_DENIED);
return;
}
// Clients may not create controlling clients
if (request->id == 0) {
completer.buffer(buf.view()).Reply(ZX_ERR_INVALID_ARGS);
return;
}
zx_status_t status = server().CreateClient(request->id, std::move(request->client));
completer.buffer(buf.view()).Reply(status);
}
void PtyClient::ClrSetFeature(ClrSetFeatureRequestView request,
ClrSetFeatureCompleter::Sync& completer) {
fidl::ServerBuffer<fuchsia_hardware_pty::Device::ClrSetFeature> buf;
constexpr uint32_t kAllowedFeatureBits = fuchsia_hardware_pty::wire::kFeatureRaw;
zx_status_t status = ZX_OK;
if ((request->clr & ~kAllowedFeatureBits) || (request->set & ~kAllowedFeatureBits)) {
status = ZX_ERR_NOT_SUPPORTED;
} else {
ClearSetFlags(request->clr, request->set);
}
completer.buffer(buf.view()).Reply(status, flags());
}
void PtyClient::GetWindowSize(GetWindowSizeCompleter::Sync& completer) {
fidl::ServerBuffer<fuchsia_hardware_pty::Device::GetWindowSize> buf;
auto size = server().window_size();
fuchsia_hardware_pty::wire::WindowSize wsz = {.width = size.width, .height = size.height};
completer.buffer(buf.view()).Reply(ZX_OK, wsz);
}
void PtyClient::MakeActive(MakeActiveRequestView request, MakeActiveCompleter::Sync& completer) {
fidl::ServerBuffer<fuchsia_hardware_pty::Device::MakeActive> buf;
if (!is_control()) {
completer.buffer(buf.view()).Reply(ZX_ERR_ACCESS_DENIED);
return;
}
zx_status_t status = server().MakeActive(request->client_pty_id);
completer.buffer(buf.view()).Reply(status);
}
void PtyClient::ReadEvents(ReadEventsCompleter::Sync& completer) {
fidl::ServerBuffer<fuchsia_hardware_pty::Device::ReadEvents> buf;
if (!is_control()) {
completer.buffer(buf.view()).Reply(ZX_ERR_ACCESS_DENIED, 0);
return;
}
uint32_t events = server().DrainEvents();
completer.buffer(buf.view()).Reply(ZX_OK, events);
}
zx_status_t PtyClient::Read(void* data, size_t count, size_t* out_actual) {
if (count == 0) {
*out_actual = 0;
return ZX_OK;
}
bool was_full = rx_fifo_.is_full();
size_t length = rx_fifo_.Read(data, count);
if (rx_fifo_.is_empty()) {
DeAssertReadableSignal();
}
if (was_full && length) {
server().AssertWritableSignal();
}
if (length > 0) {
*out_actual = length;
return ZX_OK;
}
return is_peer_closed() ? ZX_ERR_PEER_CLOSED : ZX_ERR_SHOULD_WAIT;
}
zx_status_t PtyClient::Write(const void* data, size_t count, size_t* out_actual) {
if (is_peer_closed()) {
return ZX_ERR_PEER_CLOSED;
}
if (!is_active()) {
return ZX_ERR_SHOULD_WAIT;
}
if (count == 0) {
*out_actual = 0;
return ZX_OK;
}
if (in_raw_mode()) {
return WriteChunk(data, count, out_actual);
}
// Since we're not in raw mode, perform \n -> \r\n translation.
auto chunk_start = static_cast<const uint8_t*>(data);
auto chunk_end = chunk_start;
size_t chunk_length;
size_t chunk_actual;
size_t sent = 0;
auto partial_result = [&sent, out_actual](zx_status_t status) {
if (sent) {
*out_actual = sent;
return ZX_OK;
}
return status;
};
zx_status_t status;
for (size_t i = 0; i < count; i++) {
// Iterate until there's a linefeed character.
if (*chunk_end != '\n') {
chunk_end++;
continue;
}
// Send up to (but not including) the linefeed.
chunk_length = chunk_end - chunk_start;
status = WriteChunk(chunk_start, chunk_length, &chunk_actual);
if (status != ZX_OK) {
return partial_result(status);
}
sent += chunk_actual;
if (chunk_actual != chunk_length) {
return partial_result(status);
}
// Send the translated line ending.
// TODO(https://fxbug.dev/42111418): Prevent torn writes here by wiring through support
// for Fifo::Write's "atomic" flag.
status = WriteChunk("\r\n", 2, &chunk_actual);
if (status != ZX_OK) {
return partial_result(status);
}
// This case means only the \r of the \r\n was sent; report to the caller
// as if it didn't work at all.
if (chunk_actual != 2) {
return partial_result(status);
}
// Don't increment for the \r.
sent++;
chunk_start = chunk_end + 1;
chunk_end = chunk_start;
}
// Write out the rest of the buffer if necessary.
chunk_length = chunk_end - chunk_start;
status = WriteChunk(chunk_start, chunk_length, &chunk_actual);
if (status == ZX_OK) {
sent += chunk_actual;
}
return partial_result(status);
}
zx_status_t PtyClient::WriteChunk(const void* buf, size_t count, size_t* actual) {
size_t length;
bool is_full = false;
zx_status_t status = server().Recv(buf, count, &length, &is_full);
if (status == ZX_OK) {
*actual = length;
}
if (is_full) {
DeAssertWritableSignal();
}
return status;
}
void PtyClient::AdjustSignals() {
fuchsia_device::wire::DeviceSignal to_clear, to_set;
if (is_active()) {
to_set = fuchsia_device::wire::DeviceSignal::kWritable;
} else {
to_clear = fuchsia_device::wire::DeviceSignal::kWritable;
}
if (rx_fifo_.is_empty()) {
to_clear = fuchsia_device::wire::DeviceSignal::kReadable;
} else {
to_set = fuchsia_device::wire::DeviceSignal::kReadable;
}
local_.signal_peer(static_cast<zx_signals_t>(to_clear), static_cast<zx_signals_t>(to_set));
}