| // 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/debug/zxdb/console/console_impl.h" |
| |
| #include <fcntl.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #ifndef __Fuchsia__ |
| #include <signal.h> |
| #include <termios.h> |
| #endif |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <filesystem> |
| |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/system.h" |
| #include "src/developer/debug/zxdb/client/target.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/console/command.h" |
| #include "src/developer/debug/zxdb/console/command_parser.h" |
| #include "src/developer/debug/zxdb/console/output_buffer.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| #include "src/lib/fxl/strings/split_string.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "src/lib/fxl/strings/trim.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| const char* kHistoryFilename = ".zxdb_history"; |
| |
| #ifndef __Fuchsia__ |
| |
| termios stdout_saved_termios; |
| struct sigaction saved_abort; |
| struct sigaction saved_segv; |
| |
| void TerminalRestoreSignalHandler(int sig, siginfo_t* info, void* ucontext) { |
| struct sigaction _ignore; |
| |
| if (sig == SIGABRT) { |
| sigaction(SIGABRT, &saved_abort, &_ignore); |
| } else if (sig == SIGSEGV) { |
| sigaction(SIGSEGV, &saved_segv, &_ignore); |
| } else { |
| // Weird, but I'm not about to assert inside a signal handler. |
| return; |
| } |
| |
| tcsetattr(STDOUT_FILENO, TCSAFLUSH, &stdout_saved_termios); |
| raise(sig); |
| } |
| |
| void PreserveStdoutTermios() { |
| if (!isatty(STDOUT_FILENO)) |
| return; |
| |
| if (tcgetattr(STDOUT_FILENO, &stdout_saved_termios) < 0) |
| return; |
| |
| struct sigaction restore_handler; |
| |
| restore_handler.sa_sigaction = TerminalRestoreSignalHandler; |
| restore_handler.sa_flags = SA_SIGINFO; |
| |
| sigaction(SIGABRT, &restore_handler, &saved_abort); |
| sigaction(SIGSEGV, &restore_handler, &saved_segv); |
| } |
| |
| #else |
| |
| void PreserveStdoutTermios() {} |
| |
| #endif // !__Fuchsia__ |
| |
| } // namespace |
| |
| ConsoleImpl::ConsoleImpl(Session* session) : Console(session), impl_weak_factory_(this) { |
| line_input_.Init([this](const std::string& s) { ProcessInputLine(s); }, "[zxdb] "); |
| |
| // Set the line input completion callback that can know about our context. OK to bind |this| since |
| // we own the line_input object. |
| FillCommandContextCallback fill_command_context([this](Command* cmd) { |
| context_.FillOutCommand(cmd); // Ignore errors, this is for autocomplete. |
| }); |
| line_input_.SetAutocompleteCallback([fill_command_context = std::move(fill_command_context)]( |
| const std::string& prefix) -> std::vector<std::string> { |
| return GetCommandCompletions(prefix, fill_command_context); |
| }); |
| |
| // EOF (ctrl-d) should exit gracefully. |
| line_input_.SetEofCallback([this]() { |
| line_input_.Hide(); |
| debug_ipc::MessageLoop::Current()->QuitNow(); |
| }); |
| |
| // Set stdin to async mode or OnStdinReadable will block. |
| fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK); |
| } |
| |
| ConsoleImpl::~ConsoleImpl() { |
| if (!SaveHistoryFile()) |
| Console::Output(Err("Could not save history file to $HOME/%s.\n", kHistoryFilename)); |
| } |
| |
| fxl::WeakPtr<ConsoleImpl> ConsoleImpl::GetImplWeakPtr() { return impl_weak_factory_.GetWeakPtr(); } |
| |
| void ConsoleImpl::Init() { |
| PreserveStdoutTermios(); |
| |
| stdio_watch_ = debug_ipc::MessageLoop::Current()->WatchFD( |
| debug_ipc::MessageLoop::WatchMode::kRead, STDIN_FILENO, this); |
| |
| LoadHistoryFile(); |
| line_input_.Show(); |
| } |
| |
| void ConsoleImpl::LoadHistoryFile() { |
| std::filesystem::path path(getenv("HOME")); |
| if (path.empty()) |
| return; |
| path /= kHistoryFilename; |
| |
| std::string data; |
| if (!files::ReadFileToString(path, &data)) |
| return; |
| |
| auto history = fxl::SplitStringCopy(data, "\n", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty); |
| |
| for (const std::string& cmd : history) |
| line_input_.AddToHistory(cmd); |
| } |
| |
| bool ConsoleImpl::SaveHistoryFile() { |
| char* home = getenv("HOME"); |
| if (!home) |
| return false; |
| |
| // We need to invert the order the deque has the entries. |
| std::string history_data; |
| const auto& history = line_input_.GetHistory(); |
| for (auto it = history.rbegin(); it != history.rend(); it++) { |
| auto trimmed = fxl::TrimString(*it, " "); |
| // We ignore empty entries or quit commands. |
| if (trimmed.empty() || trimmed == "quit" || trimmed == "q" || trimmed == "exit") { |
| continue; |
| } |
| |
| history_data.append(trimmed).append("\n"); |
| } |
| |
| auto filepath = std::filesystem::path(home) / kHistoryFilename; |
| return files::WriteFile(filepath, history_data.data(), history_data.size()); |
| } |
| |
| void ConsoleImpl::Output(const OutputBuffer& output) { |
| // Since most operations are asynchronous, we have to hide the input line before printing anything |
| // or it will get appended to whatever the user is typing on the screen. |
| // |
| // TODO(brettw) This can cause flickering. A more advanced system would do more fancy console |
| // stuff to output above the input line so we'd never have to hide it. |
| |
| // Make sure stdout is in blocking mode since normal output won't expect non-blocking mode. We can |
| // get in this state if stdin and stdout are the same underlying handle because the constructor |
| // sets stdin to O_NONBLOCK so we can asynchronously wait for input. |
| int old_bits = fcntl(STDIN_FILENO, F_GETFL, 0); |
| if (old_bits & O_NONBLOCK) |
| fcntl(STDOUT_FILENO, F_SETFL, old_bits & ~O_NONBLOCK); |
| |
| line_input_.Hide(); |
| output.WriteToStdout(); |
| line_input_.Show(); |
| |
| if (old_bits & O_NONBLOCK) |
| fcntl(STDOUT_FILENO, F_SETFL, old_bits); |
| } |
| |
| void ConsoleImpl::ModalGetOption(const line_input::ModalPromptOptions& options, |
| OutputBuffer message, const std::string& prompt, |
| line_input::ModalLineInput::ModalCompletionCallback cb) { |
| // Print the message from within the "will show" callback to ensure proper serialization if there |
| // are multiple prompts pending. |
| // |
| // Okay to capture |this| because we own the line_input_. |
| line_input_.ModalGetOption(options, prompt, std::move(cb), |
| [this, message = std::move(message)]() { Output(message); }); |
| } |
| |
| void ConsoleImpl::Quit() { |
| line_input_.Hide(); |
| debug_ipc::MessageLoop::Current()->QuitNow(); |
| } |
| |
| void ConsoleImpl::Clear() { |
| // We write directly instead of using Output because WriteToStdout expects to append '\n' to |
| // outputs and won't flush it explicitly otherwise. |
| line_input_.Hide(); |
| const char ff[] = "\033c"; // Form feed. |
| write(STDOUT_FILENO, ff, sizeof(ff)); |
| line_input_.Show(); |
| } |
| |
| void ConsoleImpl::ProcessInputLine(const std::string& line, CommandCallback callback) { |
| Command cmd; |
| Err err; |
| if (line.empty()) { |
| // Repeat the previous command, don't add to history. |
| err = ParseCommand(previous_line_, &cmd); |
| } else { |
| line_input_.AddToHistory(line); |
| err = ParseCommand(line, &cmd); |
| previous_line_ = line; |
| } |
| |
| if (err.ok()) { |
| err = context_.FillOutCommand(&cmd); |
| if (!err.has_error()) { |
| err = DispatchCommand(&context_, cmd, std::move(callback)); |
| |
| if (cmd.thread() && cmd.verb() != Verb::kNone) { |
| // Show the right source/disassembly for the next listing. |
| context_.SetSourceAffinityForThread(cmd.thread(), |
| GetVerbRecord(cmd.verb())->source_affinity); |
| } |
| } |
| } |
| |
| if (err.has_error()) { |
| OutputBuffer out; |
| out.Append(err); |
| Output(out); |
| } |
| } |
| |
| void ConsoleImpl::OnFDReady(int fd, bool readable, bool, bool) { |
| if (!readable) |
| return; |
| |
| char ch; |
| while (read(STDIN_FILENO, &ch, 1) > 0) |
| line_input_.OnInput(ch); |
| } |
| |
| } // namespace zxdb |