blob: ea322da9938d6eef78489c9964c5775002116cc7 [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/commands/verb_connect.h"
#include <map>
#include <string>
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/common/inet_util.h"
#include "src/developer/debug/zxdb/console/command.h"
#include "src/developer/debug/zxdb/console/console.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/developer/debug/zxdb/console/verbs.h"
namespace zxdb {
namespace {
constexpr int kUnixSwitch = 1;
const char kConnectShortHelp[] = R"(connect: Connect to a remote system for debugging.)";
const char kConnectHelp[] =
R"(connect [ <remote_address> ]
Connects to a debug_agent at the given address/port. With no arguments,
attempts to reconnect to the previously used remote address.
See also "disconnect".
Addresses
Addresses can be of the form "<host> <port>" or "<host>:<port>". When using
the latter form, IPv6 addresses must be [bracketed]. Otherwise the brackets
are optional.
Options
--unix-socket
-u
Attempt to connect to a unix socket. In this case <host> is a filesystem
path.
Examples
connect mystem.localnetwork 1234
connect mystem.localnetwork:1234
connect 192.168.0.4:1234
connect 192.168.0.4 1234
connect [1234:5678::9abc] 1234
connect 1234:5678::9abc 1234
connect [1234:5678::9abc]:1234
connect -u /path/to/socket
)";
// Displays the failed connection error message. Connections are normally initiated on startup
// and it can be difficult to see the message with all the other normal startup messages. This
// can confuse users who wonder why nothing is working. As a result, make the message really big.
void DisplayConnectionFailed(const Err& err) {
OutputBuffer out;
if (Console::get()->context().session()->IsConnected()) {
// There could be a race connection (like the user hit enter twice rapidly when issuing the
// connection command) that will cause a connection to fail because there's already one pending.
// This might not have been knowable before issuing the command. If there's already a
// connection, skip the big scary message.
out.Append(err);
} else {
out.Append(Syntax::kError, "╒═══════════════════════════════════════════╕\n│ ");
out.Append(Syntax::kHeading, "Connection to the debugged system failed. ");
out.Append(Syntax::kError, "│\n╘═══════════════════════════════════════════╛\n");
out.Append(err);
out.Append(Syntax::kError, "\n\nThe debugger will not be usable without connecting.\n\n");
}
Console::get()->Output(out);
}
Err RunVerbConnect(ConsoleContext* context, const Command& cmd,
CommandCallback callback = nullptr) {
SessionConnectionInfo connection_info;
// Catch the "already connected" case early to display a simple low-key error message. This
// avoids the more complex error messages issues by the Session object which might seem
// out-of-context.
if (context->session()->IsConnected())
return Err("connect: Already connected to the debugged system. Type \"status\" for more.");
if (cmd.HasSwitch(kUnixSwitch)) {
connection_info.type = SessionConnectionType::kUnix;
if (cmd.args().size() == 1) {
connection_info.host = cmd.args()[0];
} else {
return Err(ErrType::kInput, "Too many arguments.");
}
} else {
// 0 args means pass empty string and 0 port to try to reconnect.
if (cmd.args().size() == 1) {
const std::string& host_port = cmd.args()[0];
// Provide an additional assist to users if they forget to wrap an IPv6 address in [].
if (Ipv6HostPortIsMissingBrackets(host_port)) {
return Err(ErrType::kInput,
"For IPv6 addresses use either: \"[::1]:1234\"\n"
"or the two-parameter form: \"::1 1234.");
}
Err err = ParseHostPort(host_port, &connection_info.host, &connection_info.port);
if (err.has_error())
return err;
connection_info.type = SessionConnectionType::kNetwork;
} else if (cmd.args().size() == 2) {
Err err =
ParseHostPort(cmd.args()[0], cmd.args()[1], &connection_info.host, &connection_info.port);
if (err.has_error())
return err;
connection_info.type = SessionConnectionType::kNetwork;
} else if (cmd.args().size() > 2) {
return Err(ErrType::kInput, "Too many arguments.");
}
}
context->session()->Connect(
connection_info, [callback = std::move(callback), cmd](const Err& err) mutable {
if (err.has_error()) {
// Don't display error message if they canceled the connection.
if (err.type() != ErrType::kCanceled)
DisplayConnectionFailed(err);
} else {
OutputBuffer msg;
msg.Append("Connected successfully.\n");
// Assume if there's a callback this is not being run interactively. Otherwise, show the
// usage tip.
if (!callback) {
msg.Append(Syntax::kWarning, "👉 ");
msg.Append(Syntax::kComment,
"Normally you will \"run <program path>\" or \"attach "
"<process koid>\".");
}
Console::get()->Output(msg);
}
if (callback)
callback(err);
});
Console::get()->Output("Connecting (use \"disconnect\" to cancel)...\n");
return Err();
}
} // namespace
VerbRecord GetConnectVerbRecord() {
SwitchRecord unix_switch(kUnixSwitch, false, "unix-socket", 'u');
VerbRecord connect_record = VerbRecord(&RunVerbConnect, {"connect"}, kConnectShortHelp,
kConnectHelp, CommandGroup::kGeneral);
connect_record.switches.push_back(unix_switch);
return connect_record;
}
} // namespace zxdb