blob: 50161f1ff0ad9f4bade05bc4fc3c39ecde8ae49e [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 "component.h"
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/result.h>
#include <string>
#include <vector>
#include "unowned_component.h"
namespace {
// Reads a manifest from a |ManifestBytesIterator| producing a vector of bytes.
zx::result<std::vector<uint8_t>> DrainManifestBytesIterator(
fidl::ClientEnd<fuchsia_sys2::ManifestBytesIterator> iterator_client_end) {
fidl::SyncClient iterator(std::move(iterator_client_end));
std::vector<uint8_t> result;
while (true) {
auto next_res = iterator->Next();
if (next_res.is_error()) {
return zx::error(next_res.error_value().status());
}
if (next_res->infos().empty()) {
break;
}
result.insert(result.end(), next_res->infos().begin(), next_res->infos().end());
}
return zx::ok(std::move(result));
}
} // namespace
namespace {
struct Moniker {
std::string parent;
std::string collection;
std::string name;
};
zx::result<Moniker> SplitMoniker(const std::string& moniker) {
// A valid moniker for launching in a dynamic collection looks like:
// parent_moniker/collection:name
size_t leaf_divider = moniker.find_last_of('/');
if (leaf_divider == std::string::npos) {
return zx::error(ZX_ERR_BAD_PATH);
}
std::string parent_moniker = moniker.substr(0, leaf_divider);
const std::string leaf = moniker.substr(leaf_divider + 1);
size_t collection_divider = leaf.find_last_of(':');
if (collection_divider == std::string::npos) {
return zx::error(ZX_ERR_BAD_PATH);
}
std::string collection = leaf.substr(0, collection_divider);
std::string name = leaf.substr(collection_divider + 1);
return zx::ok(Moniker{parent_moniker, collection, name});
}
} // namespace
zx::result<std::unique_ptr<profiler::Component>> profiler::Component::Create(
async_dispatcher_t* dispatcher, const std::string& url, const std::string& moniker) {
std::unique_ptr component = std::make_unique<Component>(dispatcher);
auto client_end = component::Connect<fuchsia_sys2::LifecycleController>(
"/svc/fuchsia.sys2.LifecycleController.root");
if (client_end.is_error()) {
return client_end.take_error();
}
component->lifecycle_controller_client_ = fidl::SyncClient{std::move(*client_end)};
zx::result split = SplitMoniker(moniker);
if (split.is_error()) {
return split.take_error();
}
component->parent_moniker_ = split->parent;
component->collection_ = split->collection;
component->name_ = split->name;
component->moniker_ = moniker;
fidl::Result<fuchsia_sys2::LifecycleController::CreateInstance> create_res =
component->lifecycle_controller_client_->CreateInstance({{
.parent_moniker = component->parent_moniker_,
.collection = {component->collection_},
.decl = {{
.name = component->name_,
.url = url,
.startup = fuchsia_component_decl::StartupMode::kLazy,
}},
}});
if (create_res.is_error()) {
FX_LOGS(ERROR) << "Failed to create " << moniker << ": " << create_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
fidl::Result<fuchsia_sys2::LifecycleController::ResolveInstance> resolve_res =
component->lifecycle_controller_client_->ResolveInstance({{component->moniker_}});
if (resolve_res.is_error()) {
FX_LOGS(ERROR) << "Failed to resolve " << moniker << ": " << resolve_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok(std::move(component));
}
zx::result<> profiler::Component::TraverseRealm(
std::string moniker, const fit::function<zx::result<>(const std::string&)>& f) {
if (zx::result self_res = f(moniker); self_res.is_error()) {
return self_res;
}
auto manifest = GetResolvedDeclaration(moniker);
if (manifest.is_error()) {
// If this instance isn't launched yet, that's okay. We'll register to be notified when it does
// launch. Skip it for now.
return zx::ok();
}
if (manifest->children()) {
for (const auto& child : *manifest->children()) {
if (!child.name()) {
return zx::error(ZX_ERR_BAD_PATH);
}
std::string child_moniker = moniker + "/" + *child.name();
if (zx::result descendents_res = TraverseRealm(child_moniker, f);
descendents_res.is_error()) {
return descendents_res;
}
}
}
return zx::ok();
}
zx::result<> profiler::Component::Start(ComponentWatcher::ComponentEventHandler on_start) {
if (!on_start) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
on_start_ = std::move(on_start);
zx::result<> watch_result = TraverseRealm(moniker_, [this](std::string moniker) {
return component_watcher_.WatchForMoniker(
moniker, [this](std::string moniker, std::string url) { on_start_.value()(moniker, url); });
});
if (watch_result.is_error()) {
return watch_result;
}
if (zx::result res = component_watcher_.Watch(); res.is_error()) {
return res;
}
zx::result<fidl::Endpoints<fuchsia_component::Binder>> binder_endpoints =
fidl::CreateEndpoints<fuchsia_component::Binder>();
if (binder_endpoints.is_error()) {
return binder_endpoints.take_error();
}
fidl::Result<fuchsia_sys2::LifecycleController::StartInstance> start_res =
lifecycle_controller_client_->StartInstance({{
.moniker = moniker_,
.binder = std::move(binder_endpoints->server),
}});
if (start_res.is_error()) {
FX_LOGS(ERROR) << "Failed to start component: " << start_res.error_value();
return zx::error(ZX_ERR_UNAVAILABLE);
}
return zx::ok();
}
zx::result<> profiler::Component::Stop() {
if (zx::result res = component_watcher_.Reset(); res.is_error()) {
return res;
}
if (auto stop_res = lifecycle_controller_client_->StopInstance({{
.moniker = moniker_,
}});
stop_res.is_error()) {
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok();
}
zx::result<> profiler::Component::Destroy() {
if (auto destroy_res = lifecycle_controller_client_->DestroyInstance({{
.parent_moniker = parent_moniker_,
.child = {{.name = name_, .collection = collection_}},
}});
destroy_res.is_error()) {
FX_LOGS(ERROR) << "Failed to destroy " << name_ << ": " << destroy_res.error_value();
return zx::error(ZX_ERR_BAD_STATE);
}
needs_destruction_ = false;
return zx::ok();
}
zx::result<fidl::Box<fuchsia_component_decl::Component>>
profiler::Component::GetResolvedDeclaration(const std::string& moniker) {
zx::result<fidl::ClientEnd<fuchsia_sys2::RealmQuery>> client_end =
component::Connect<fuchsia_sys2::RealmQuery>("/svc/fuchsia.sys2.RealmQuery.root");
if (client_end.is_error()) {
FX_LOGS(WARNING) << "Unable to connect to RealmQuery. Component interaction is disabled";
return client_end.take_error();
}
fidl::SyncClient realm_query_client{std::move(*client_end)};
fidl::Result<::fuchsia_sys2::RealmQuery::GetResolvedDeclaration> declaration =
realm_query_client->GetResolvedDeclaration({moniker});
if (declaration.is_error()) {
return zx::error(ZX_ERR_BAD_PATH);
}
auto drain_res = DrainManifestBytesIterator(std::move(declaration->iterator()));
if (drain_res.is_error()) {
return fit::error(drain_res.error_value());
}
auto unpersist_res = fidl::InplaceUnpersist<fuchsia_component_decl::wire::Component>(
cpp20::span<uint8_t>(drain_res->begin(), drain_res->size()));
if (unpersist_res.is_error()) {
return zx::error(unpersist_res.error_value().status());
}
return zx::ok(fidl::ToNatural(*unpersist_res));
}
profiler::Component::~Component() {
if (needs_destruction_) {
(void)Destroy();
}
}