| // 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/cmd/console.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <poll.h> | |
| #include <unistd.h> | |
| #include <zircon/assert.h> | |
| #include <utility> | |
| namespace cmd { | |
| Console::Client::~Client() = default; | |
| Console::Console(Client* client, async_dispatcher_t* dispatcher, int input_fd) | |
| : client_(client), | |
| input_fd_(input_fd), | |
| input_waiter_(dispatcher), | |
| interrupt_waiter_(dispatcher) {} | |
| 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([this](const std::string& line) { | |
| Autocomplete autocomplete(line); | |
| client_->OnConsoleAutocomplete(&autocomplete); | |
| return autocomplete.TakeCompletions(); | |
| }); | |
| } | |
| 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; | |
| zx_status_t pty_status = ZX_OK; | |
| status = fuchsia_hardware_pty_DeviceReadEvents(fdio_unsafe_borrow_channel(tty_), | |
| &pty_status, &events); | |
| WaitForInterruptAsynchronously(); | |
| if (events & fuchsia_hardware_pty_EVENT_INTERRUPT) { | |
| 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); | |
| Command command; | |
| command.Parse(line); | |
| zx_status_t status = client_->OnConsoleCommand(std::move(command)); | |
| ZX_DEBUG_ASSERT(status == ZX_ERR_STOP || status == ZX_ERR_NEXT || status == ZX_ERR_ASYNC); | |
| if (status != 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 cmd |