blob: d02b75acbc19946dcd4f536e3481ba7411ec1d33 [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.
// The intercepting mechanism works by creating an Environment containing a
// custom |fuchsia.sys.Loader| and |fuchsia.sys.Runner|. This custom environment
// loader, which answers to all components launches under this environment,
// responds with an autogenerated package directory with a .cmx pointing to a
// custom runner component. The runner component, which will also under the
// environment, forwards its requests back up to environment's injected
// |fuchsia.sys.Runner| implemented here.
#include <fuchsia/io/cpp/fidl.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/cpp/testing/component_interceptor.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/pseudo_file.h>
#include <lib/zx/channel.h>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <zircon/assert.h>
#include <memory>
namespace sys {
namespace testing {
namespace {
// The runner we inject in autogenerated .cmx files.
constexpr char kEnvironmentDelegatingRunner[] =
"fuchsia-pkg://fuchsia.com/environment_delegating_runner#meta/"
"environment_delegating_runner.cmx";
// Relative path within the autogenerated package directory to the manifest.
constexpr char kAutogenPkgDirManifestPath[] = "autogenerated_manifest.cmx";
// Path to the autogenerated cmx file of the intercepted component.
constexpr char kAutogenCmxPath[] =
"fuchsia-pkg://example.com/fake_pkg#autogenerated_manifest.cmx";
} // namespace
ComponentInterceptor::ComponentInterceptor(
fuchsia::sys::LoaderPtr fallback_loader, async_dispatcher_t* dispatcher)
: fallback_loader_(std::move(fallback_loader)), dispatcher_(dispatcher) {
loader_svc_ = std::make_shared<vfs::Service>(
[this](zx::channel h, async_dispatcher_t* dispatcher) mutable {
loader_bindings_.AddBinding(
this, fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(h)),
dispatcher_);
});
}
ComponentInterceptor::~ComponentInterceptor() = default;
// static
ComponentInterceptor ComponentInterceptor::CreateWithEnvironmentLoader(
const fuchsia::sys::EnvironmentPtr& env, async_dispatcher_t* dispatcher) {
// The fallback loader comes from |parent_env|.
fuchsia::sys::LoaderPtr fallback_loader;
fuchsia::sys::ServiceProviderPtr sp;
env->GetServices(sp.NewRequest());
sp->ConnectToService(fuchsia::sys::Loader::Name_,
fallback_loader.NewRequest().TakeChannel());
return ComponentInterceptor(std::move(fallback_loader), dispatcher);
}
std::unique_ptr<EnvironmentServices>
ComponentInterceptor::MakeEnvironmentServices(
const fuchsia::sys::EnvironmentPtr& parent_env) {
sys::testing::EnvironmentServices::ParentOverrides parent_overrides;
parent_overrides.loader_service_ = loader_svc_;
auto env_services = EnvironmentServices::CreateWithParentOverrides(
parent_env, std::move(parent_overrides), dispatcher_);
env_services->AddService(runner_bindings_.GetHandler(this, dispatcher_));
return env_services;
}
// Modifies the supplied |cmx| such that:
// * required fields in .cmx are set if not present:
// - program.binary
// * the runner is the environment delegating runner.
void SetDefaultsForCmx(rapidjson::Document* cmx) {
// 1. Enforce that it has delegating runner.
cmx->RemoveMember("runner");
cmx->AddMember("runner", kEnvironmentDelegatingRunner, cmx->GetAllocator());
// 2. If "program" is not set, give it a default one with an empty binary.
if (!cmx->HasMember("program")) {
rapidjson::Value program;
program.SetObject();
program.AddMember("binary", "", cmx->GetAllocator());
cmx->AddMember("program", program, cmx->GetAllocator());
}
}
bool ComponentInterceptor::InterceptURL(std::string component_url,
std::string extra_cmx_contents,
ComponentLaunchHandler handler) {
ZX_DEBUG_ASSERT_MSG(handler, "Must be a valid handler.");
// 1. Parse the extra_cmx_contents. Enforce that our delgating runner is
// specified, and give it defaults for required fields.
rapidjson::Document cmx;
cmx.Parse(extra_cmx_contents);
if (!cmx.IsObject() && !cmx.IsNull()) {
return false;
}
if (cmx.IsNull()) {
cmx.SetObject();
}
SetDefaultsForCmx(&cmx);
// 2. Construct a package directory and put the |cmx| manifest in it
// for this particular component URL.
rapidjson::StringBuffer buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
cmx.Accept(writer);
std::string cmx_str = buf.GetString();
ComponentLoadInfo info;
info.pkg_dir = std::make_unique<vfs::PseudoDir>();
info.pkg_dir->AddEntry(
kAutogenPkgDirManifestPath,
std::make_unique<vfs::PseudoFile>(
cmx_str.length(),
[cmx_str = std::move(cmx_str)](std::vector<uint8_t>* out,
size_t max_file_size) {
std::copy(cmx_str.begin(), cmx_str.end(), std::back_inserter(*out));
return ZX_OK;
}));
info.handler = std::move(handler);
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
intercepted_component_load_info_[component_url] = std::move(info);
return true;
}
void ComponentInterceptor::LoadUrl(std::string url, LoadUrlCallback response) {
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
auto it = intercepted_component_load_info_.find(url);
if (it == intercepted_component_load_info_.end()) {
fallback_loader_->LoadUrl(url, std::move(response));
return;
}
auto pkg = std::make_unique<fuchsia::sys::Package>();
fidl::InterfaceHandle<fuchsia::io::Directory> dir_handle;
it->second.pkg_dir->Serve(fuchsia::io::OPEN_RIGHT_READABLE,
dir_handle.NewRequest().TakeChannel());
pkg->directory = dir_handle.TakeChannel();
pkg->resolved_url = kAutogenCmxPath;
response(std::move(pkg));
// After this point, the runner specified in the autogenerated manifest should
// forward its requests back to us over our Runner fidl binding.
}
void ComponentInterceptor::StartComponent(
fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
// This is a buffer to store the move-only handler while we invoke it.
ComponentLaunchHandler handler;
auto url = startup_info.launch_info.url;
{
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
auto it = intercepted_component_load_info_.find(url);
ZX_DEBUG_ASSERT(it != intercepted_component_load_info_.end());
// This allows that handler to re-entrantly call InterceptURL() without
// deadlocking this on |intercept_urls_mu_|
handler = std::move(it->second.handler);
}
handler(std::move(startup_info), std::make_unique<InterceptedComponent>(
std::move(controller), dispatcher_));
// Put the |handler| back where it came from.
{
std::lock_guard<std::mutex> lock(intercept_urls_mu_);
auto it = intercepted_component_load_info_.find(url);
ZX_DEBUG_ASSERT(it != intercepted_component_load_info_.end());
it->second.handler = std::move(handler);
}
}
InterceptedComponent::InterceptedComponent(
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request,
async_dispatcher_t* dispatcher)
: binding_(this),
termination_reason_(TerminationReason::EXITED),
exit_code_(ZX_OK) {
binding_.Bind(std::move(request), dispatcher);
binding_.set_error_handler([this](zx_status_t status) {
termination_reason_ = TerminationReason::UNKNOWN;
Kill();
});
}
InterceptedComponent::~InterceptedComponent() {
on_kill_ = nullptr;
Kill();
}
void InterceptedComponent::Exit(int64_t exit_code, TerminationReason reason) {
exit_code_ = exit_code;
termination_reason_ = reason;
Kill();
}
void InterceptedComponent::Kill() {
if (on_kill_) {
on_kill_();
}
binding_.events().OnTerminated(exit_code_, termination_reason_);
binding_.Unbind();
}
void InterceptedComponent::Detach() { binding_.set_error_handler(nullptr); }
} // namespace testing
} // namespace sys