blob: d1566929e1598e042cf329125197a62ce6392b5b [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 "managed_environment.h"
#include <fuchsia/boot/cpp/fidl.h>
#include <fuchsia/diagnostics/cpp/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <fuchsia/logger/cpp/fidl.h>
#include <fuchsia/netemul/guest/cpp/fidl.h>
#include <random>
#include <sdk/lib/sys/cpp/termination_reason.h>
#include "src/lib/fxl/strings/string_printf.h"
namespace netemul {
namespace {
// Start the Log and LogSink service (the same component publishses both
// services))
constexpr const char* kLogServiceURLNoKlog =
"fuchsia-pkg://fuchsia.com/archivist-for-embedding#meta/archivist-for-embedding.cmx";
constexpr const char* kLogServiceURLWithKlog =
"fuchsia-pkg://fuchsia.com/archivist-for-embedding#meta/archivist-with-klog.cmx";
constexpr const char* kLogServices[] = {fuchsia::logger::LogSink::Name_,
fuchsia::logger::Log::Name_,
fuchsia::diagnostics::ArchiveAccessor::Name_};
} // namespace
using sys::testing::EnclosingEnvironment;
using sys::testing::EnvironmentServices;
ManagedEnvironment::~ManagedEnvironment() {
// The environment should be torn down before anything its child components may depend on
// (i.e. loggers, virtual devices, etc.).
//
// Tearing down the loggers before we tear down the environment may cause a rust component
// to panic when it tries to write to stdout/stderr after their sockets have been closed
// (https://github.com/rust-lang/rust/blob/f6072ca/src/libstd/io/stdio.rs#L878).
env_ = nullptr;
// Always destroy loggers next so they can consume all outstanding messages.
loggers_ = nullptr;
}
ManagedEnvironment::Ptr ManagedEnvironment::CreateRoot(const fuchsia::sys::EnvironmentPtr& parent,
const SandboxEnv::Ptr& sandbox_env,
Options options) {
auto ret = ManagedEnvironment::Ptr(new ManagedEnvironment(sandbox_env));
ret->Create(parent, std::move(options));
return ret;
}
ManagedEnvironment::ManagedEnvironment(const SandboxEnv::Ptr& sandbox_env)
: sandbox_env_(sandbox_env), ready_(false) {}
sys::testing::EnclosingEnvironment& ManagedEnvironment::environment() { return *env_; }
void ManagedEnvironment::GetLauncher(::fidl::InterfaceRequest<::fuchsia::sys::Launcher> launcher) {
launcher_->Bind(std::move(launcher));
}
void ManagedEnvironment::CreateChildEnvironment(fidl::InterfaceRequest<FManagedEnvironment> me,
Options options) {
ManagedEnvironment::Ptr np(new ManagedEnvironment(sandbox_env_));
fuchsia::sys::EnvironmentPtr env;
env_->ConnectToService(env.NewRequest());
np->Create(env, std::move(options), this);
np->bindings_.AddBinding(np.get(), std::move(me));
children_.emplace_back(std::move(np));
}
void ManagedEnvironment::Create(const fuchsia::sys::EnvironmentPtr& parent,
ManagedEnvironment::Options options,
const ManagedEnvironment* managed_parent) {
// Nested environments without a name are not allowed, if empty name is
// provided, replace it with a default *randomized* value.
// Randomness there is necessary due to appmgr rules for environments with
// same name.
if (!options.has_name() || options.name().empty()) {
std::random_device rnd;
options.set_name(fxl::StringPrintf("netemul-env-%08x", rnd()));
}
// Start LogListener for this environment
log_listener_ =
LogListener::Create(std::move(*options.mutable_logger_options()), options.name(), nullptr);
auto services = EnvironmentServices::Create(parent);
services->SetServiceTerminatedCallback([this, name = options.name()](
const std::string& service, int64_t exit_code,
fuchsia::sys::TerminationReason reason) {
FX_LOGS(WARNING) << "Service " << service << " exited on environment " << name << " with ("
<< exit_code << ") reason: " << sys::HumanReadableTerminationReason(reason);
if (sandbox_env_->events().service_terminated) {
sandbox_env_->events().service_terminated(service, exit_code, reason);
}
});
if (log_listener_) {
loggers_ = std::make_unique<ManagedLoggerCollection>(options.name(),
log_listener_->GetLogListenerImpl());
} else {
loggers_ = std::make_unique<ManagedLoggerCollection>(options.name(), nullptr);
}
// Add network context service.
services->AddService(sandbox_env_->network_context().GetHandler());
// Add Bus service.
services->AddService(sandbox_env_->sync_manager().GetHandler());
// Add managed environment itself as a handler.
services->AddService(bindings_.GetHandler(this));
// Add shared tun service.
services->AddService(fidl::InterfaceRequestHandler<fuchsia::net::tun::Control>(
[this](fidl::InterfaceRequest<fuchsia::net::tun::Control> request) {
sandbox_env_->ConnectNetworkTun(std::move(request));
}));
bool enable_klog = LogListener::IsKlogsEnabled(options);
if (enable_klog) {
services->AddService(fidl::InterfaceRequestHandler<fuchsia::boot::ReadOnlyLog>(
[this](fidl::InterfaceRequest<fuchsia::boot::ReadOnlyLog> request) {
// Connect the sandbox to our namespace rather than its sandbox parent.
sandbox_env_->ConnectToReadOnlyLog(std::move(request));
}));
}
const char* log_service_url;
if (enable_klog) {
log_service_url = kLogServiceURLWithKlog;
} else {
log_service_url = kLogServiceURLNoKlog;
}
// Inject all services provided by LogService.
for (const auto* svc : kLogServices) {
// Inject Log service
services->AddServiceWithLaunchInfo(
log_service_url,
[this, log_service_url]() {
fuchsia::sys::LaunchInfo linfo;
linfo.url = log_service_url;
linfo.out = loggers_->CreateLogger(log_service_url, false);
linfo.err = loggers_->CreateLogger(log_service_url, true);
loggers_->IncrementCounter();
return linfo;
},
svc);
}
// Allow hypervisor resource for virtualization.
services->AllowParentService(fuchsia::kernel::HypervisorResource::Name_);
// prepare service configurations:
service_config_.clear();
if (options.has_inherit_parent_launch_services() && options.inherit_parent_launch_services() &&
managed_parent != nullptr) {
for (const auto& a : managed_parent->service_config_) {
LaunchService clone;
a.Clone(&clone);
service_config_.push_back(std::move(clone));
}
}
if (options.has_services()) {
std::move(options.mutable_services()->begin(), options.mutable_services()->end(),
std::back_inserter(service_config_));
}
// push all the allowable launch services:
for (const auto& svc : service_config_) {
LaunchService copy;
ZX_ASSERT(svc.Clone(&copy) == ZX_OK);
services->AddServiceWithLaunchInfo(
svc.url,
[this, svc = std::move(copy)]() {
fuchsia::sys::LaunchInfo linfo;
linfo.url = svc.url;
linfo.arguments = svc.arguments;
if (!launcher_->MakeServiceLaunchInfo(&linfo)) {
// NOTE: we can just log an return code of MakeServiceLaunchInfo here, since those are
// caused by fuchsia::sys::Loader errors that will happen again once we return the
// launch info. That failure, in turn, will be caught by the service termination
// callback installed in the services instance.
FX_LOGS(ERROR) << "Make service launch info failed";
}
return linfo;
},
svc.name);
}
if (auto guest = sandbox_env_->guest_env_.lock()) {
services->AddService<fuchsia::netemul::guest::GuestDiscovery>(
[guest = std::move(guest)](
fidl::InterfaceRequest<fuchsia::netemul::guest::GuestDiscovery> request) {
guest->ConnectToService(fuchsia::netemul::guest::GuestDiscovery::Name_,
request.TakeChannel());
});
}
if (options.has_devices()) {
// save all handles for virtual devices
for (auto& dev : *options.mutable_devices()) {
virtual_devices_.AddEntry(dev.path, dev.device.Bind());
}
}
fuchsia::sys::EnvironmentOptions sub_options = {.inherit_parent_services = false,
.use_parent_runners = false,
.kill_on_oom = true,
.delete_storage_on_death = true};
env_ = EnclosingEnvironment::Create(options.name(), parent, std::move(services), sub_options);
env_->SetRunningChangedCallback([this](bool running) {
ready_ = true;
if (running) {
for (auto& r : pending_requests_) {
Bind(std::move(r));
}
pending_requests_.clear();
if (running_callback_) {
running_callback_();
}
} else {
FX_LOGS(ERROR) << "Underlying enclosed Environment stopped running";
running_callback_ = nullptr;
children_.clear();
pending_requests_.clear();
env_ = nullptr;
launcher_ = nullptr;
bindings_.CloseAll();
}
});
launcher_ = std::make_unique<ManagedLauncher>(this);
// If we have one, bind our log listener to this environment.
// We do this after creation of log listener because
// we need to make sure the environment is created first,
// but managed logger needs our implementation of LogListenerImpl.
if (log_listener_) {
ZX_ASSERT(log_listener_->Bindable());
log_listener_->BindToLogService(this);
}
}
zx::channel ManagedEnvironment::OpenVdevDirectory(std::string path) {
return virtual_devices_.OpenAsDirectory(std::move(path));
}
void ManagedEnvironment::Bind(fidl::InterfaceRequest<ManagedEnvironment::FManagedEnvironment> req) {
if (ready_) {
bindings_.AddBinding(this, std::move(req));
} else if (env_) {
pending_requests_.push_back(std::move(req));
} else {
req.Close(ZX_ERR_INTERNAL);
}
}
ManagedLoggerCollection& ManagedEnvironment::loggers() {
ZX_ASSERT(loggers_);
return *loggers_;
}
void ManagedEnvironment::ConnectToService(std::string name, zx::channel req) {
env_->ConnectToService(name, std::move(req));
}
void ManagedEnvironment::AddDevice(fuchsia::netemul::environment::VirtualDevice device) {
virtual_devices_.AddEntry(std::move(device.path), device.device.Bind());
}
void ManagedEnvironment::RemoveDevice(std::string path) {
virtual_devices_.RemoveEntry(std::move(path));
}
} // namespace netemul