blob: fb761f08b676251d290abfd2cdaf619fad1b5c63 [file] [log] [blame]
// Copyright 2023 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/debug_agent/launcher.h"
#include <fidl/fuchsia.component.decl/cpp/fidl.h>
#include <fidl/fuchsia.component/cpp/fidl.h>
#include <fidl/fuchsia.debugger/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/processargs.h>
#include <charconv>
#include <deque>
#include <random>
#include "lib/async/cpp/task.h"
#include "lib/fit/defer.h"
namespace {
constexpr char kChildUrl[] = "fuchsia-pkg://fuchsia.com/debug_agent#meta/debug_agent.cm";
constexpr char kCollectionName[] = "agents";
constexpr char kAgentPrefix[] = "agent-";
// Convert various result types to zx_status_t.
template <typename ResultType>
zx_status_t ResultToStatus(const ResultType& result) {
zx_status_t error = ZX_OK;
if (result.is_error()) {
if (result.error_value().is_domain_error()) {
error = fidl::ToUnderlying(result.error_value().domain_error());
} else {
error = result.error_value().framework_error().status();
}
}
return error;
}
std::string GetRandomHexString() {
std::mt19937 prng{std::random_device{}()};
uint32_t num = prng();
std::string s;
s.resize(8);
auto [end, ec] = std::to_chars(s.data(), s.data() + s.size(), num, 16);
FX_CHECK(ec == std::errc()) << std::make_error_code(ec).message();
return std::string{s.data(), end};
}
fidl::ClientEnd<fuchsia_component::Realm> ConnectToRealmQuery() {
zx::result realm_client_end = component::Connect<fuchsia_component::Realm>();
FX_CHECK(realm_client_end.is_ok());
return std::move(*realm_client_end);
}
class AgentIterator;
void ConnectToAgentAt(const std::string& name,
fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end) {
fidl::Client<fuchsia_component::Realm> realm(ConnectToRealmQuery(),
async_get_default_dispatcher());
fuchsia_component_decl::ChildRef child_ref(name, kCollectionName);
auto [dir_client_end, dir_server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
realm->OpenExposedDir({{.child = child_ref, .exposed_dir = std::move(dir_server_end)}})
.Then([name, server_end = std::move(server_end), dir_client_end = std::move(dir_client_end),
realm = std::move(realm)](auto& result) mutable {
if (auto status = ResultToStatus(result); status != ZX_OK) {
FX_LOGS(WARNING) << "OpenExposedDir failed: " << result.error_value();
return;
}
auto connect_at_result = component::ConnectAt<fuchsia_debugger::DebugAgent>(
dir_client_end, std::move(server_end));
if (connect_at_result.is_error()) {
FX_LOGS(WARNING) << "Failed to connect to " << name << ": "
<< connect_at_result.error_value();
return;
}
});
}
class AgentIterator : public fidl::Server<fuchsia_debugger::AgentIterator> {
public:
void GetNext(GetNextCompleter::Sync& completer) override {
GetNextCompleter::Async async_completer = completer.ToAsync();
// First call, get the remote iterator from ComponentManager.
if (!child_iterator_.is_valid()) {
fidl::Client<fuchsia_component::Realm> realm(ConnectToRealmQuery(),
async_get_default_dispatcher());
fuchsia_component_decl::CollectionRef collection(kCollectionName);
auto [client_end, server_end] = fidl::Endpoints<fuchsia_component::ChildIterator>::Create();
child_iterator_ = fidl::Client(std::move(client_end), async_get_default_dispatcher());
realm->ListChildren({collection, std::move(server_end)})
.Then([this, completer = &async_completer, realm = std::move(realm)](
fidl::Result<fuchsia_component::Realm::ListChildren>& result) mutable {
if (auto status = ResultToStatus(result); status != ZX_OK) {
FX_LOGS(WARNING) << "ListChildren failed: " << result.error_value();
if (auto unbind_result = child_iterator_.UnbindMaybeGetEndpoint();
unbind_result.is_error()) {
FX_LOGS(WARNING) << "Failed to unbind iterator: " << unbind_result.error_value();
}
completer->Close(status);
}
});
} else if (!agents_.empty()) {
async::PostTask(async_get_default_dispatcher(),
[this, completer = std::move(async_completer)]() mutable {
completer.Reply({{std::move(agents_)}});
});
return;
}
// Get the next batch of results, |children| will be an empty vector when there are no more
// child instances. It is possible for this call to return more results than we can send in a
// single response.
if (child_iterator_.is_valid()) {
child_iterator_->Next().Then(
[this, completer = std::move(async_completer)](
fidl::Result<fuchsia_component::ChildIterator::Next>& children) mutable {
if (children.is_error()) {
FX_LOGS(WARNING) << "Child iterator returned error: " << children.error_value();
completer.Close(ZX_ERR_UNAVAILABLE);
return;
} else if (children->children().empty()) {
// No more children, reply with what we have an hang up.
completer.Reply(std::move(agents_));
completer.Close(ZX_OK);
return;
}
OnChildrenReady(children->children(), std::move(completer));
});
}
}
private:
void OnChildrenReady(const std::vector<fuchsia_component_decl::ChildRef>& children,
GetNextCompleter::Async completer) {
std::vector<fuchsia_debugger::Agent> batch;
// There's no way to query the completer to know that we've given a response already, but we can
// still collate data from the child iterator while we wait for another request from the client.
bool completer_called = false;
for (auto it = children.begin(); it != children.end(); ++it) {
auto [client_end, server_end] = fidl::Endpoints<fuchsia_debugger::DebugAgent>::Create();
if (!completer_called) {
batch.emplace_back(it->name(), std::move(client_end));
if (batch.size() == kMaxBatchedAgents || std::next(it) == children.end()) {
completer.Reply(std::move(batch));
completer_called = true;
}
} else {
// Buffer the rest of the agents to be returned in the next |GetNext| call from the client.
agents_.emplace_back(it->name(), std::move(client_end));
}
// This will finish asynchronously, but we don't need to worry about the result here. If an
// error occurs, the client_end will see a PEER_CLOSED.
ConnectToAgentAt(it->name(), std::move(server_end));
}
}
// Each struct is a string and a handle, the string is the child component's name which can be up
// to 1024 bytes, which is conveniently the same as the 64 handle limit per message.
static constexpr size_t kMaxBatchedAgents = 64;
fidl::Client<fuchsia_component::ChildIterator> child_iterator_;
std::vector<fuchsia_debugger::Agent> agents_;
};
} // namespace
void DebugAgentLauncher::Launch(LaunchRequest& request, LaunchCompleter::Sync& completer) {
std::string name(kAgentPrefix);
name += GetRandomHexString();
LaunchDebugAgent(std::move(request.agent()), name,
[completer = completer.ToAsync()](zx_status_t status) mutable {
completer.Reply(zx::make_result(status));
});
}
void DebugAgentLauncher::GetAgents(GetAgentsRequest& request, GetAgentsCompleter::Sync& completer) {
fidl::BindServer(async_get_default_dispatcher(),
fidl::ServerEnd<fuchsia_debugger::AgentIterator>(std::move(request.iterator())),
std::make_unique<AgentIterator>());
}
void DebugAgentLauncher::LaunchDebugAgent(fidl::ServerEnd<fuchsia_debugger::DebugAgent> server_end,
const std::string& name,
fit::callback<void(zx_status_t)> cb) {
zx::result client_end = component::Connect<fuchsia_component::Realm>();
fidl::Client<fuchsia_component::Realm> realm(std::move(*client_end),
async_get_default_dispatcher());
auto [controller_client_end, controller_server_end] =
fidl::Endpoints<fuchsia_component::Controller>::Create();
fidl::Client<fuchsia_component::Controller> controller(std::move(controller_client_end),
async_get_default_dispatcher());
// Create the child decl and all the starting parameters.
fuchsia_component_decl::Child child_decl;
child_decl.name(name);
child_decl.url(kChildUrl);
child_decl.startup(fuchsia_component_decl::StartupMode::kLazy);
fuchsia_component_decl::CollectionRef collection(kCollectionName);
std::vector<fuchsia_process::HandleInfo> handles;
handles.emplace_back(server_end.TakeHandle(), PA_HND(PA_USER0, 0));
fuchsia_component::CreateChildArgs args;
args.numbered_handles() = std::move(handles);
args.controller(std::move(controller_server_end));
// Create and Start the child.
realm->CreateChild({collection, child_decl, std::move(args)})
.Then([realm = std::move(realm), controller = std::move(controller), cb = std::move(cb)](
fidl::Result<fuchsia_component::Realm::CreateChild>& result) mutable {
if (auto status = ResultToStatus(result); status != ZX_OK) {
return cb(status);
}
controller->IsStarted().Then(
[controller = std::move(controller), cb = std::move(cb)](
fidl::Result<fuchsia_component::Controller::IsStarted>& result) mutable {
if (auto status = ResultToStatus(result); status != ZX_OK) {
return cb(status);
}
// If the component is already started, then we're done. This is
// not guaranteed by the Realm protocol.
if (result.value().is_started()) {
return cb(ZX_OK);
}
// Otherwise, we need to explicitly start the child.
auto [exec_controller_client, exec_controller_server] =
fidl::Endpoints<fuchsia_component::ExecutionController>::Create();
fidl::Client<fuchsia_component::ExecutionController> execution_controller(
std::move(exec_controller_client), async_get_default_dispatcher());
controller->Start({{.execution_controller = std::move(exec_controller_server)}})
.Then([controller = std::move(controller), cb = std::move(cb)](
fidl::Result<fuchsia_component::Controller::Start>& result) mutable {
if (auto status = ResultToStatus(result); status != ZX_OK) {
return cb(status);
}
cb(ZX_OK);
});
});
});
}