blob: fe56f4c3c13dd831f32645aafa3faae0d629613a [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 "pty-server.h"
PtyClient::PtyClient(fbl::RefPtr<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)) {}
PtyClient::~PtyClient() = default;
zx_status_t PtyClient::Create(fbl::RefPtr<PtyServer> server, uint32_t id,
fbl::RefPtr<PtyClient>* out) {
zx::eventpair local, remote;
zx_status_t status = zx::eventpair::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
*out = fbl::MakeRefCounted<PtyClient>(std::move(server), id, std::move(local), std::move(remote));
return ZX_OK;
}
zx_status_t PtyClient::Read(void* data, size_t count, size_t* out_actual) {
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;
} else {
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(fxbug.dev/35945): 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() {
zx_signals_t to_clear = 0;
zx_signals_t to_set = 0;
if (is_active()) {
to_set = fuchsia_device::wire::kDeviceSignalWritable;
} else {
to_clear = fuchsia_device::wire::kDeviceSignalWritable;
}
if (rx_fifo_.is_empty()) {
to_clear = fuchsia_device::wire::kDeviceSignalReadable;
} else {
to_set = fuchsia_device::wire::kDeviceSignalReadable;
}
local_.signal_peer(to_clear, to_set);
}
void PtyClient::Shutdown() { server_->RemoveClient(this); }