blob: 3b1b8699831baa922fbde84904702113b0c1e7e4 [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 "src/developer/debug/zxdb/console/console_impl.h"
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#ifndef __Fuchsia__
#include <signal.h>
#include <sys/ioctl.h>
#include <termios.h>
#endif
#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/logging.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), line_input_("[zxdb] ") {
// Set the line input completion callback that can know about our context.
// OK to bind |this| since we own the line_input object.
auto fill_command_context = [this](Command* cmd) {
context_.FillOutCommand(cmd); // Ignore errors, this is for autocomplete.
};
line_input_.set_completion_callback(
[fill_command_context](
const std::string& prefix) -> std::vector<std::string> {
return GetCommandCompletions(prefix, fill_command_context);
});
// 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));
}
void ConsoleImpl::Init() {
PreserveStdoutTermios();
line_input_.BeginReadLine();
stdio_watch_ = debug_ipc::MessageLoop::Current()->WatchFD(
debug_ipc::MessageLoop::WatchMode::kRead, STDIN_FILENO, this);
LoadHistoryFile();
}
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_.history();
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.ToString()).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::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();
}
Console::Result ConsoleImpl::DispatchInputLine(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()) {
if (cmd.verb() == Verb::kQuit) {
return Result::kQuit;
} else {
err = context_.FillOutCommand(&cmd);
if (!err.has_error()) {
err = DispatchCommand(&context_, cmd, 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);
}
return Result::kContinue;
}
Console::Result ConsoleImpl::ProcessInputLine(const std::string& line,
CommandCallback callback) {
Result result = DispatchInputLine(line, callback);
if (result == Result::kQuit)
debug_ipc::MessageLoop::Current()->QuitNow();
return result;
}
void ConsoleImpl::OnFDReady(int fd, bool readable, bool, bool) {
if (!readable) {
return;
}
char ch;
while (read(STDIN_FILENO, &ch, 1) > 0) {
if (line_input_.OnInput(ch)) {
// EOF (ctrl-d) should exit gracefully.
if (line_input_.eof()) {
line_input_.EnsureNoRawMode();
Console::Output("\n");
debug_ipc::MessageLoop::Current()->QuitNow();
return;
}
std::string line = line_input_.line();
Result result = ProcessInputLine(line);
if (result == Result::kQuit)
return;
line_input_.BeginReadLine();
}
}
}
} // namespace zxdb