| // 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 "src/developer/shell/console/console.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.hardware.pty/cpp/wire.h> |
| #include <poll.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| |
| #include <iostream> |
| #include <utility> |
| |
| namespace fpty = fuchsia_hardware_pty; |
| |
| namespace shell::console { |
| |
| Console::Client::~Client() = default; |
| |
| Console::Console(Client* client, async_dispatcher_t* dispatcher, int input_fd, std::ostream& out, |
| std::ostream& err) |
| : client_(client), |
| input_fd_(input_fd), |
| input_waiter_(dispatcher), |
| interrupt_waiter_(dispatcher), |
| out_stream_(out), |
| err_stream_(err) {} |
| |
| Console::~Console() { |
| if (tty_) { |
| fdio_unsafe_release(tty_); |
| tty_ = nullptr; |
| } |
| } |
| |
| void Console::Init(std::string prompt) { |
| fcntl(input_fd_, F_SETFL, fcntl(input_fd_, F_GETFL, 0) | O_NONBLOCK); |
| |
| if (isatty(input_fd_)) { |
| tty_ = fdio_unsafe_fd_to_io(input_fd_); |
| WaitForInterruptAsynchronously(); |
| } |
| |
| line_input_.Init([this](const std::string& line) { OnAccept(line); }, std::move(prompt)); |
| line_input_.SetEofCallback([this] { OnError(ZX_ERR_PEER_CLOSED); }); |
| line_input_.SetAutocompleteCallback([](const std::string& line) { |
| // TODO(fidl-tools-team): What to do on autocomplete? |
| return std::vector<std::string>(); |
| }); |
| } |
| |
| void Console::GetNextCommand() { |
| ZX_DEBUG_ASSERT(!should_read_); |
| line_input_.Show(); |
| should_read_ = true; |
| WaitForInputAsynchronously(); |
| } |
| |
| void Console::WaitForInputAsynchronously() { |
| input_waiter_.Wait( |
| [this](zx_status_t status, uint32_t observed) { |
| ZX_DEBUG_ASSERT(should_read_); |
| if (status != ZX_OK) { |
| OnError(status); |
| return; |
| } |
| for (;;) { |
| char ch = 0; |
| ssize_t rv = read(input_fd_, &ch, 1); |
| if (rv < 0) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| WaitForInputAsynchronously(); |
| return; |
| } |
| OnError(ZX_ERR_IO); |
| return; |
| } |
| if (rv == 0) { |
| OnError(ZX_ERR_PEER_CLOSED); |
| return; |
| } |
| line_input_.OnInput(ch); |
| if (!should_read_) { |
| return; |
| } |
| } |
| }, |
| input_fd_, POLLIN); |
| } |
| |
| void Console::WaitForInterruptAsynchronously() { |
| interrupt_waiter_.Wait( |
| [this](zx_status_t status, uint32_t observed) { |
| if (status != ZX_OK) { |
| return; |
| } |
| uint32_t events = 0; |
| auto end = fidl::UnownedClientEnd<fpty::Device>(fdio_unsafe_borrow_channel(tty_)); |
| auto result = fidl::WireCall(end)->ReadEvents(); |
| if (result.status() == ZX_OK && result->status == ZX_OK) { |
| events = result->events; |
| } |
| WaitForInterruptAsynchronously(); |
| if (events & fpty::wire::kEventInterrupt) { |
| if (should_read_) { |
| line_input_.OnInput(line_input::SpecialCharacters::kKeyControlC); |
| } else { |
| client_->OnConsoleInterrupt(); |
| } |
| } |
| }, |
| input_fd_, POLLPRI); |
| } |
| |
| void Console::OnAccept(const std::string& line) { |
| ZX_DEBUG_ASSERT(should_read_); |
| line_input_.AddToHistory(line); |
| auto command = std::make_unique<Command>(); |
| command->Parse(line); |
| |
| Err status = client_->OnConsoleCommand(std::move(command)); |
| ZX_DEBUG_ASSERT(status.code == ZX_ERR_STOP || status.code == ZX_ERR_NEXT || |
| status.code == ZX_ERR_ASYNC); |
| if (status.code != ZX_ERR_NEXT) { |
| line_input_.Hide(); |
| should_read_ = false; |
| } |
| } |
| |
| void Console::OnError(zx_status_t status) { |
| ZX_DEBUG_ASSERT(should_read_); |
| line_input_.Hide(); |
| should_read_ = false; |
| client_->OnConsoleError(status); |
| } |
| |
| } // namespace shell::console |