blob: b47da2b5d4ebb9b6fb273a74ed5e0d858547c9cd [file] [log] [blame]
// Copyright 2018 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 "garnet/bin/zxdb/console/console.h"
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <filesystem>
#include "garnet/bin/zxdb/client/process.h"
#include "garnet/bin/zxdb/client/session.h"
#include "garnet/bin/zxdb/client/system.h"
#include "garnet/bin/zxdb/client/target.h"
#include "garnet/bin/zxdb/client/thread.h"
#include "garnet/bin/zxdb/common/err.h"
#include "garnet/bin/zxdb/console/command.h"
#include "garnet/bin/zxdb/console/command_parser.h"
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "lib/fxl/files/file.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/join_strings.h"
#include "lib/fxl/strings/split_string.h"
#include "lib/fxl/strings/string_printf.h"
#include "lib/fxl/strings/trim.h"
namespace zxdb {
namespace {
const char* kHistoryFilename = ".zxdb_history";
} // namespace
Console* Console::singleton_ = nullptr;
Console::Console(Session* session)
: context_(session), line_input_("[zxdb] "), weak_factory_(this) {
FXL_DCHECK(!singleton_);
singleton_ = this;
line_input_.set_completion_callback(&GetCommandCompletions);
// Set stdin to async mode or OnStdinReadable will block, but make sure
// stdout is in blocking mode since we don't expect to retry.
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK);
}
Console::~Console() {
FXL_DCHECK(singleton_ == this);
singleton_ = nullptr;
if (!SaveHistoryFile())
Output(Err("Could not save history file to $HOME/%s.\n", kHistoryFilename));
}
void Console::Init() {
line_input_.BeginReadLine();
stdio_watch_ = debug_ipc::MessageLoop::Current()->WatchFD(
debug_ipc::MessageLoop::WatchMode::kRead, STDIN_FILENO, this);
LoadHistoryFile();
}
void Console::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 Console::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 Console::Output(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.
line_input_.Hide();
output.WriteToStdout();
line_input_.Show();
}
void Console::Output(const std::string& s) {
OutputBuffer buffer;
buffer.Append(s);
Output(std::move(buffer));
}
void Console::Output(const Err& err) {
OutputBuffer buffer;
buffer.Append(err);
Output(std::move(buffer));
}
void Console::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 Console::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(std::move(out));
}
return Result::kContinue;
}
Console::Result Console::ProcessInputLine(const std::string& line,
CommandCallback callback) {
Result result = DispatchInputLine(line, callback);
if (result == Result::kQuit) {
// If we're not connected, quit immediatelly.
if (!quit_agent_on_quit() || !context().session()->IsConnected()) {
debug_ipc::MessageLoop::Current()->QuitNow();
return result;
}
// At this point we know that we're connected and we want to quit the agent
// at exit. We post an exit command and wait for the result.
Output("Stopping debug agent. Will exit on success.");
context().session()->QuitAgent([console = GetWeakPtr()](const Err& err) {
FXL_DCHECK(console);
if (!console)
return;
// If there was an error quitting the debug agent, let the user
// know and react to it.
if (err.has_error()) {
console->Output(err);
return;
}
// Here we have successfully exited.
debug_ipc::MessageLoop::Current()->QuitNow();
});
}
return result;
}
void Console::OnFDReadable(int fd) {
char ch;
while (read(STDIN_FILENO, &ch, 1) > 0) {
if (line_input_.OnInput(ch)) {
Result result = ProcessInputLine(line_input_.line());
if (result == Result::kQuit)
return;
line_input_.BeginReadLine();
}
}
}
fxl::WeakPtr<Console> Console::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace zxdb