|  | // Copyright 2015 The Chromium 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 "apps/moterm/moterm_driver.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <limits> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "mojo/services/files/interfaces/ioctl.mojom.h" | 
|  | #include "mojo/services/files/interfaces/ioctl_terminal.mojom.h" | 
|  |  | 
|  | // Character constants: | 
|  | const uint8_t kEOT = 4; | 
|  | const uint8_t kNL = 10; | 
|  | const uint8_t kCR = 13; | 
|  | const uint8_t kDEL = 127; | 
|  |  | 
|  | // Short forms for various counts/indices used for the get/set settings ioctls. | 
|  | const size_t kBaseFieldCount = mojo::files::kIoctlTerminalTermiosBaseFieldCount; | 
|  | const size_t kTotalFieldCount = | 
|  | kBaseFieldCount + mojo::files::kIoctlTerminalTermiosCtrlCharCount; | 
|  | const size_t kIFlagIdx = mojo::files::kIoctlTerminalTermiosIFlagIndex; | 
|  | const size_t kOFlagIdx = mojo::files::kIoctlTerminalTermiosOFlagIndex; | 
|  | const size_t kLFlagIdx = mojo::files::kIoctlTerminalTermiosLFlagIndex; | 
|  | const size_t kVEraseIdx = mojo::files::kIoctlTerminalTermiosCtrlCharVERASEIndex; | 
|  | const size_t kVEOFIdx = mojo::files::kIoctlTerminalTermiosCtrlCharVERASEIndex; | 
|  |  | 
|  | MotermDriver::PendingRead::PendingRead(uint32_t num_bytes, | 
|  | const ReadCallback& callback) | 
|  | : num_bytes(num_bytes), callback(callback) { | 
|  | } | 
|  |  | 
|  | MotermDriver::PendingRead::~PendingRead() { | 
|  | } | 
|  |  | 
|  | // static | 
|  | base::WeakPtr<MotermDriver> MotermDriver::Create( | 
|  | Client* client, | 
|  | mojo::InterfaceRequest<mojo::files::File> request) { | 
|  | return (new MotermDriver(client, request.Pass()))->weak_factory_.GetWeakPtr(); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Detach() { | 
|  | client_ = nullptr; | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | void MotermDriver::SendData(const void* bytes, size_t num_bytes) { | 
|  | DCHECK(client_); | 
|  | DCHECK(!is_closed_); | 
|  |  | 
|  | if (icanon_) { | 
|  | for (size_t i = 0; i < num_bytes; i++) | 
|  | HandleCanonicalModeInput(static_cast<const uint8_t*>(bytes)[i]); | 
|  | } else { | 
|  | // If not in canonical ("cooked") mode, just send data. | 
|  | const uint8_t* b = static_cast<const uint8_t*>(bytes); | 
|  | for (size_t i = 0; i < num_bytes; i++) | 
|  | send_data_queue_.push_back(b[i]); | 
|  |  | 
|  | CompletePendingReads(); | 
|  | } | 
|  | } | 
|  |  | 
|  | MotermDriver::MotermDriver(Client* client, | 
|  | mojo::InterfaceRequest<mojo::files::File> request) | 
|  | : client_(client), | 
|  | is_closed_(false), | 
|  | binding_(this, request.Pass()), | 
|  | icanon_(true), | 
|  | icrnl_(true), | 
|  | veof_(kEOT), | 
|  | verase_(kDEL), | 
|  | onlcr_(true), | 
|  | weak_factory_(this) { | 
|  | DCHECK(client_); | 
|  | } | 
|  |  | 
|  | MotermDriver::~MotermDriver() { | 
|  | if (client_) { | 
|  | // Invalidate weak pointers now, to make sure the client doesn't call us. | 
|  | weak_factory_.InvalidateWeakPtrs(); | 
|  | client_->OnDestroyed(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MotermDriver::HandleCanonicalModeInput(uint8_t ch) { | 
|  | DCHECK(icanon_); | 
|  |  | 
|  | // Translate CR to NL, if appropriate. | 
|  | if (ch == kCR && icrnl_) | 
|  | ch = kNL; | 
|  |  | 
|  | // Newline. | 
|  | // TODO(vtl): In addition to NL, we're supposed to handle settable EOL and | 
|  | // EOL2. | 
|  | if (ch == kNL) {  // Newline. | 
|  | input_line_queue_.push_back(ch); | 
|  | HandleOutput(&ch, 1); | 
|  | FlushInputLine(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // EOF at beginning of line. | 
|  | if (ch == veof_ && input_line_queue_.empty()) { | 
|  | // Don't add the character. Just flush and nuke ourselves. | 
|  | FlushInputLine(); | 
|  | delete this; | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ch == verase_) {  // Erase (backspace). | 
|  | if (!input_line_queue_.empty()) { | 
|  | // TODO(vtl): This is wrong for utf-8 (see Linux's IUTF8). | 
|  | input_line_queue_.pop_back(); | 
|  | HandleOutput(reinterpret_cast<const uint8_t*>("\x08 \x08"), 3); | 
|  | }  // Else do nothing. | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Everything else. | 
|  | // TODO(vtl): Probably want to display control characters as ^X (e.g.). | 
|  | // TODO(vtl): Handle ^W. | 
|  | input_line_queue_.push_back(ch); | 
|  | HandleOutput(&ch, 1); | 
|  | } | 
|  |  | 
|  | void MotermDriver::CompletePendingReads() { | 
|  | while (send_data_queue_.size() && pending_read_queue_.size()) { | 
|  | PendingRead pending_read = pending_read_queue_.front(); | 
|  | pending_read_queue_.pop_front(); | 
|  |  | 
|  | size_t data_size = std::min(static_cast<size_t>(pending_read.num_bytes), | 
|  | send_data_queue_.size()); | 
|  | auto data = mojo::Array<uint8_t>::New(data_size); | 
|  | for (size_t i = 0; i < data_size; i++) { | 
|  | data[i] = send_data_queue_[i]; | 
|  | // In canonical mode, each read only gets a single line. | 
|  | if (icanon_ && data[i] == kNL) { | 
|  | data_size = i + 1; | 
|  | data.resize(data_size); | 
|  | break; | 
|  | } | 
|  | } | 
|  | send_data_queue_.erase(send_data_queue_.begin(), | 
|  | send_data_queue_.begin() + data_size); | 
|  |  | 
|  | pending_read.callback.Run(mojo::files::Error::OK, data.Pass()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MotermDriver::FlushInputLine() { | 
|  | for (size_t i = 0; i < input_line_queue_.size(); i++) | 
|  | send_data_queue_.push_back(input_line_queue_[i]); | 
|  | input_line_queue_.clear(); | 
|  | CompletePendingReads(); | 
|  | } | 
|  |  | 
|  | void MotermDriver::HandleOutput(const uint8_t* bytes, size_t num_bytes) { | 
|  | // Can we be smarter and not always copy? | 
|  | std::vector<uint8_t> translated_bytes; | 
|  | translated_bytes.reserve(num_bytes); | 
|  |  | 
|  | for (size_t i = 0; i < num_bytes; i++) { | 
|  | uint8_t ch = bytes[i]; | 
|  | if (ch == kNL && onlcr_) { | 
|  | translated_bytes.push_back(kCR); | 
|  | translated_bytes.push_back(kNL); | 
|  | } else { | 
|  | translated_bytes.push_back(ch); | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(vtl): It seems extremely unlikely that we'd overlow a |uint32_t| here | 
|  | // (but it's theoretically possible). But perhaps we should handle it more | 
|  | // gracefully if it ever comes up. | 
|  | CHECK_LE(translated_bytes.size(), std::numeric_limits<uint32_t>::max()); | 
|  | client_->OnDataReceived(translated_bytes.data(), | 
|  | static_cast<uint32_t>(translated_bytes.size())); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Close(const CloseCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Call pending read callbacks? | 
|  |  | 
|  | is_closed_ = true; | 
|  | callback.Run(mojo::files::Error::OK); | 
|  |  | 
|  | client_->OnClosed(); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Read(uint32_t num_bytes_to_read, | 
|  | int64_t offset, | 
|  | mojo::files::Whence whence, | 
|  | const ReadCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (offset != 0 || whence != mojo::files::Whence::FROM_CURRENT) { | 
|  | // TODO(vtl): Is this the "right" behavior? | 
|  | callback.Run(mojo::files::Error::INVALID_ARGUMENT, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!num_bytes_to_read) { | 
|  | callback.Run(mojo::files::Error::OK, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | pending_read_queue_.push_back(PendingRead(num_bytes_to_read, callback)); | 
|  | CompletePendingReads(); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Write(mojo::Array<uint8_t> bytes_to_write, | 
|  | int64_t offset, | 
|  | mojo::files::Whence whence, | 
|  | const WriteCallback& callback) { | 
|  | DCHECK(!bytes_to_write.is_null()); | 
|  |  | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (offset != 0 || whence != mojo::files::Whence::FROM_CURRENT) { | 
|  | // TODO(vtl): Is this the "right" behavior? | 
|  | callback.Run(mojo::files::Error::INVALID_ARGUMENT, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!bytes_to_write.size()) { | 
|  | callback.Run(mojo::files::Error::OK, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | HandleOutput(&bytes_to_write.front(), bytes_to_write.size()); | 
|  |  | 
|  | // TODO(vtl): Is this OK if the client detached (and we're destroyed?). | 
|  | callback.Run(mojo::files::Error::OK, | 
|  | static_cast<uint32_t>(bytes_to_write.size())); | 
|  | } | 
|  |  | 
|  | void MotermDriver::ReadToStream(mojo::ScopedDataPipeProducerHandle source, | 
|  | int64_t offset, | 
|  | mojo::files::Whence whence, | 
|  | int64_t num_bytes_to_read, | 
|  | const ReadToStreamCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl) | 
|  | NOTIMPLEMENTED(); | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED); | 
|  | } | 
|  |  | 
|  | void MotermDriver::WriteFromStream(mojo::ScopedDataPipeConsumerHandle sink, | 
|  | int64_t offset, | 
|  | mojo::files::Whence whence, | 
|  | const WriteFromStreamCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl) | 
|  | NOTIMPLEMENTED(); | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Tell(const TellCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE, 0); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Seek(int64_t offset, | 
|  | mojo::files::Whence whence, | 
|  | const SeekCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE, 0); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Stat(const StatCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl) | 
|  | NOTIMPLEMENTED(); | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED, nullptr); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Truncate(int64_t size, const TruncateCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Touch(mojo::files::TimespecOrNowPtr atime, | 
|  | mojo::files::TimespecOrNowPtr mtime, | 
|  | const TouchCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Dup(mojo::InterfaceRequest<mojo::files::File> file, | 
|  | const DupCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Reopen(mojo::InterfaceRequest<mojo::files::File> file, | 
|  | uint32_t open_flags, | 
|  | const ReopenCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | } | 
|  |  | 
|  | void MotermDriver::AsBuffer(const AsBufferCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, mojo::ScopedSharedBufferHandle()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe | 
|  | // unsupported/EINVAL is better.) | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE, | 
|  | mojo::ScopedSharedBufferHandle()); | 
|  | } | 
|  |  | 
|  | void MotermDriver::Ioctl(uint32_t request, | 
|  | mojo::Array<uint32_t> in_values, | 
|  | const IoctlCallback& callback) { | 
|  | if (is_closed_) { | 
|  | callback.Run(mojo::files::Error::CLOSED, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (request != mojo::files::kIoctlTerminal) { | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // "Is TTY?" Yes. | 
|  | if (!in_values || !in_values.size()) { | 
|  | callback.Run(mojo::files::Error::OK, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | switch (in_values[0]) { | 
|  | case mojo::files::kIoctlTerminalGetSettings: | 
|  | IoctlGetSettings(in_values.Pass(), callback); | 
|  | return; | 
|  | case mojo::files::kIoctlTerminalSetSettings: | 
|  | IoctlSetSettings(in_values.Pass(), callback); | 
|  | return; | 
|  | case mojo::files::kIoctlTerminalGetWindowSize: | 
|  | case mojo::files::kIoctlTerminalSetWindowSize: | 
|  | NOTIMPLEMENTED(); | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED, nullptr); | 
|  | return; | 
|  | default: | 
|  | callback.Run(mojo::files::Error::UNIMPLEMENTED, nullptr); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void MotermDriver::IoctlGetSettings(mojo::Array<uint32_t> in_values, | 
|  | const IoctlCallback& callback) { | 
|  | if (in_values.size() != 1u) { | 
|  | callback.Run(mojo::files::Error::INVALID_ARGUMENT, nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto out_values = mojo::Array<uint32_t>::New(kTotalFieldCount); | 
|  |  | 
|  | // TODO(vtl): Add support for various things. Also, some values should be | 
|  | // hard-coded. | 
|  |  | 
|  | // iflag: | 
|  | if (icrnl_) | 
|  | out_values[kIFlagIdx] |= mojo::files::kIoctlTerminalTermiosIFlagICRNL; | 
|  |  | 
|  | // oflag: | 
|  | if (onlcr_) | 
|  | out_values[kOFlagIdx] |= mojo::files::kIoctlTerminalTermiosOFlagONLCR; | 
|  |  | 
|  | // lflag: | 
|  | if (icanon_) | 
|  | out_values[kLFlagIdx] |= mojo::files::kIoctlTerminalTermiosLFlagICANON; | 
|  |  | 
|  | // cc: | 
|  | out_values[kVEraseIdx] = verase_; | 
|  | out_values[kVEOFIdx] = veof_; | 
|  |  | 
|  | callback.Run(mojo::files::Error::OK, out_values.Pass()); | 
|  | } | 
|  |  | 
|  | void MotermDriver::IoctlSetSettings(mojo::Array<uint32_t> in_values, | 
|  | const IoctlCallback& callback) { | 
|  | callback.Run(IoctlSetSettingsHelper(in_values.Pass()), nullptr); | 
|  | } | 
|  |  | 
|  | mojo::files::Error MotermDriver::IoctlSetSettingsHelper( | 
|  | mojo::Array<uint32_t> in_values) { | 
|  | // Note: "termios" offsets (and sizes) are increased by 1, to accomodate the | 
|  | // subrequest at index 0. | 
|  |  | 
|  | // The "cc" values are optional. | 
|  | if (in_values.size() < 1 + kBaseFieldCount) | 
|  | return mojo::files::Error::INVALID_ARGUMENT; | 
|  |  | 
|  | // TODO(vtl): Add support for various things. Also, some values can't be | 
|  | // changed. | 
|  |  | 
|  | // iflag: | 
|  | icrnl_ = !!(in_values[1 + kIFlagIdx] & | 
|  | mojo::files::kIoctlTerminalTermiosIFlagICRNL); | 
|  |  | 
|  | // oflag: | 
|  | onlcr_ = !!(in_values[1 + kOFlagIdx] & | 
|  | mojo::files::kIoctlTerminalTermiosOFlagONLCR); | 
|  |  | 
|  | // lflag: | 
|  | icanon_ = !!(in_values[1 + kLFlagIdx] & | 
|  | mojo::files::kIoctlTerminalTermiosLFlagICANON); | 
|  |  | 
|  | // TODO(vtl): Check that ispeed and ospeed are not set? | 
|  |  | 
|  | // cc: | 
|  | if (1 + kVEraseIdx < in_values.size()) { | 
|  | uint32_t value = in_values[1 + kVEraseIdx]; | 
|  | if (value > std::numeric_limits<uint8_t>::max()) | 
|  | return mojo::files::Error::INVALID_ARGUMENT; | 
|  | verase_ = static_cast<uint8_t>(value); | 
|  | } | 
|  | if (1 + kVEOFIdx < in_values.size()) { | 
|  | uint32_t value = in_values[1 + kVEOFIdx]; | 
|  | if (value > std::numeric_limits<uint8_t>::max()) | 
|  | return mojo::files::Error::INVALID_ARGUMENT; | 
|  | veof_ = static_cast<uint8_t>(value); | 
|  | } | 
|  |  | 
|  | return mojo::files::Error::OK; | 
|  | } |