blob: 76c6d676a6fb362b00eb671501b1f5f928cd1568 [file] [log] [blame]
// Copyright 2016 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/sys/appmgr/component_controller_impl.h"
#include <fuchsia/inspect/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fit/bridge.h>
#include <lib/fit/function.h>
#include <lib/inspect/service/cpp/service.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/job.h>
#include <lib/zx/status.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <cinttypes>
#include <string>
#include <utility>
#include <fbl/string_printf.h>
#include <fs/pseudo_file.h>
#include <fs/remote_dir.h>
#include <fs/service.h>
#include <task-utils/walker.h>
#include "fbl/ref_ptr.h"
#include "lib/inspect/service/cpp/service.h"
#include "lib/vfs/cpp/service.h"
#include "src/lib/fsl/handles/object_info.h"
#include "src/sys/appmgr/component_container.h"
#include "src/sys/appmgr/namespace.h"
#include "src/sys/appmgr/realm.h"
namespace component {
using fuchsia::sys::TerminationReason;
namespace {
zx::process DuplicateProcess(const zx::process& process) {
zx::process ret;
zx_status_t status = process.duplicate(ZX_RIGHT_SAME_RIGHTS, &ret);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to duplicate process handle: " << status;
}
return ret;
}
// TODO(fxbug.dev/46803): The out/diagnostics directory propagation for runners includes a retry.
// The reason of this is that flutter fills the out/ directory *after*
// serving it. Therefore we need to watch that directory to notify.
// Sadly the PseudoDir exposed in the SDK (and used by flutter) returns ZX_ERR_NOT_SUPPORTED on
// Watch. A solution using a watcher is implemented in fxr/366977 pending watch support.
const uint32_t MAX_RETRIES_OUT_DIAGNOSTICS = 30;
const uint32_t OUT_DIAGNOSTICS_INITIAL_RETRY_DELAY_MS = 500;
const uint32_t OUT_DIAGNOSTICS_MAXIMUM_DELAY_MS = 15000;
} // namespace
ComponentRequestWrapper::ComponentRequestWrapper(
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request, int64_t default_return,
fuchsia::sys::TerminationReason default_reason)
: request_(std::move(request)), return_code_(default_return), reason_(default_reason) {}
ComponentRequestWrapper::~ComponentRequestWrapper() {
if (request_) {
FailedComponentController failed(return_code_, reason_, std::move(request_));
}
}
ComponentRequestWrapper::ComponentRequestWrapper(ComponentRequestWrapper&& other) {
*this = std::move(other);
}
void ComponentRequestWrapper::operator=(ComponentRequestWrapper&& other) {
request_ = std::move(other.request_);
return_code_ = std::move(other.return_code_);
reason_ = std::move(other.reason_);
}
void ComponentRequestWrapper::SetReturnValues(int64_t return_code, TerminationReason reason) {
return_code_ = return_code;
reason_ = reason;
}
FailedComponentController::FailedComponentController(
int64_t return_code, TerminationReason termination_reason,
fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller)
: binding_(this), return_code_(return_code), termination_reason_(termination_reason) {
if (controller) {
binding_.Bind(std::move(controller));
}
}
FailedComponentController::~FailedComponentController() {
// This can be false if other side of the channel dies before this object dies.
if (binding_.is_bound()) {
binding_.events().OnTerminated(return_code_, termination_reason_);
}
}
void FailedComponentController::Kill() {}
void FailedComponentController::Detach() {}
ComponentControllerBase::ComponentControllerBase(
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request, std::string url,
std::string args, std::string label, std::string hub_instance_id, fxl::RefPtr<Namespace> ns,
zx::channel exported_dir, zx::channel client_request, uint32_t diagnostics_max_retries)
: executor_(async_get_default_dispatcher()),
binding_(this),
label_(std::move(label)),
hub_instance_id_(std::move(hub_instance_id)),
url_(std::move(url)),
hub_(fbl::AdoptRef(new fs::PseudoDir())),
ns_(std::move(ns)),
weak_ptr_factory_(this),
diagnostics_max_retries_(diagnostics_max_retries),
diagnostics_retry_backoff_ms_(OUT_DIAGNOSTICS_INITIAL_RETRY_DELAY_MS) {
if (request.is_valid()) {
binding_.Bind(std::move(request));
binding_.set_error_handler([this](zx_status_t /*status*/) { Kill(); });
}
if (!exported_dir) {
return;
}
exported_dir_.Bind(std::move(exported_dir), async_get_default_dispatcher());
if (client_request) {
fdio_service_connect_at(exported_dir_.channel().get(), "svc", client_request.release());
}
ns_->set_component_id(hub_instance_id_);
hub_.SetName(label_);
hub_.AddEntry("url", url_);
hub_.AddEntry("args", std::move(args));
exported_dir_->Clone(fuchsia::io::OPEN_FLAG_DESCRIBE | fuchsia::io::OPEN_RIGHT_READABLE |
fuchsia::io::OPEN_RIGHT_WRITABLE,
cloned_exported_dir_.NewRequest());
cloned_exported_dir_.events().OnOpen = [this](zx_status_t status,
std::unique_ptr<fuchsia::io::NodeInfo> /*info*/) {
if (status != ZX_OK) {
FX_LOGS(WARNING) << "could not bind out directory for component" << label_ << "): " << status;
return;
}
out_ready_ = true;
auto output_dir = fbl::AdoptRef(new fs::RemoteDir(cloned_exported_dir_.Unbind().TakeChannel()));
hub_.PublishOut(std::move(output_dir));
NotifyDiagnosticsDirReady(diagnostics_max_retries_);
TRACE_DURATION_BEGIN("appmgr", "ComponentController::OnDirectoryReady");
SendOnDirectoryReadyEvent();
TRACE_DURATION_END("appmgr", "ComponentController::OnDirectoryReady");
};
cloned_exported_dir_.set_error_handler(
[this](zx_status_t status) { cloned_exported_dir_.Unbind(); });
}
void ComponentControllerBase::NotifyDiagnosticsDirReady(uint32_t max_retries) {
auto promise =
GetDiagnosticsDir()
.and_then([self = weak_ptr_factory_.GetWeakPtr()](
fidl::InterfaceHandle<fuchsia::io::Directory>& dir) {
if (self) {
self->ns_->NotifyComponentDiagnosticsDirReady(self->url_, self->label_,
self->hub_instance_id_, std::move(dir));
}
})
.or_else([self = weak_ptr_factory_.GetWeakPtr(), max_retries](zx_status_t& status) {
if (self && status == ZX_ERR_NOT_FOUND) {
zx::duration next_backoff_duration = zx::msec(self->diagnostics_retry_backoff_ms_);
self->diagnostics_retry_backoff_ms_ = std::min(
self->diagnostics_retry_backoff_ms_ + OUT_DIAGNOSTICS_INITIAL_RETRY_DELAY_MS,
OUT_DIAGNOSTICS_MAXIMUM_DELAY_MS);
async::PostDelayedTask(
self->executor_.dispatcher(),
[self = std::move(self), max_retries]() {
if (self && max_retries > 0) {
self->NotifyDiagnosticsDirReady(max_retries - 1);
}
},
next_backoff_duration);
}
return fit::error(status);
});
executor_.schedule_task(std::move(promise));
}
fit::promise<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>
ComponentControllerBase::GetDir(std::string path) {
// This error would occur if the method was called when the component out/ directory wasn't ready
// yet. This can be triggered when a listener is attached to a realm and notifies about existing
// components. It could happen that the component exists, but its out is not ready yet. Under such
// scenario, the listener will receive a START event for the existing component, but won't
// receive a DIAGNOSTICS_DIR_READY event during the existing flow. The DIAGNOSTICS_READY_EVENT
// will be triggered later once the out/ directory is ready if the component exposes a
// diagnostics directory.
if (!out_ready_) {
return fit::make_result_promise<fidl::InterfaceHandle<fuchsia::io::Directory>>(
fit::error(ZX_ERR_BAD_STATE));
}
fuchsia::io::NodePtr diagnostics_dir_node;
fit::bridge<void, zx_status_t> bridge;
diagnostics_dir_node.events().OnOpen =
[completer = std::move(bridge.completer), label = label_](
zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> node_info) mutable {
if (status != ZX_OK) {
completer.complete_error(status);
} else if (!node_info) {
completer.complete_error(ZX_ERR_NOT_FOUND);
} else if (!node_info->is_directory()) {
completer.complete_error(ZX_ERR_NOT_DIR);
} else {
completer.complete_ok();
}
};
const uint32_t flags = fuchsia::io::OPEN_FLAG_DESCRIBE | fuchsia::io::OPEN_RIGHT_READABLE |
fuchsia::io::OPEN_RIGHT_WRITABLE;
exported_dir_->Open(flags, 0u /* mode */, path, diagnostics_dir_node.NewRequest());
return bridge.consumer.promise().and_then([diagnostics_dir_node =
std::move(diagnostics_dir_node)]() mutable {
auto diagnostics_dir =
fidl::InterfaceHandle<fuchsia::io::Directory>(diagnostics_dir_node.Unbind().TakeChannel());
return fit::make_result_promise<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>(
fit::ok(std::move(diagnostics_dir)));
});
}
fit::promise<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>
ComponentControllerBase::GetDiagnosticsDir() {
return GetDir("diagnostics");
}
fit::promise<fidl::InterfaceHandle<fuchsia::io::Directory>, zx_status_t>
ComponentControllerBase::GetServiceDir() {
return GetDir("svc");
}
void ComponentControllerBase::SendOnDirectoryReadyEvent() {
// This can be false if
// 1. Other side of the channel dies before this call happens.
// 2. Component Controller request was not passed while creating the component.
if (binding_.is_bound()) {
binding_.events().OnDirectoryReady();
}
}
void ComponentControllerBase::SendOnTerminationEvent(
int64_t return_code, fuchsia::sys::TerminationReason termination_reason) {
// `binding_.is_bound()` can be false if
// 1. Other side of the channel dies before this call happens.
// 2. Component Controller request was not passed while creating the component.
if (on_terminated_event_sent_ || !binding_.is_bound()) {
return;
}
FX_VLOGS(1) << "Sending termination callback with return code: " << return_code;
binding_.events().OnTerminated(return_code, termination_reason);
on_terminated_event_sent_ = true;
}
ComponentControllerBase::~ComponentControllerBase() { ns_->FlushAndShutdown(ns_); };
HubInfo ComponentControllerBase::HubInfo() {
return component::HubInfo(label_, hub_instance_id_, hub_.dir());
}
void ComponentControllerBase::Detach() {
binding_.set_error_handler([](zx_status_t /*status*/) {});
}
ComponentControllerImpl::ComponentControllerImpl(
fidl::InterfaceRequest<fuchsia::sys::ComponentController> request,
ComponentContainer<ComponentControllerImpl>* container, zx::job job, zx::process process,
std::string url, std::string args, std::string label, fxl::RefPtr<Namespace> ns,
zx::channel exported_dir, zx::channel client_request, zx::channel package_handle)
: ComponentControllerBase(std::move(request), std::move(url), std::move(args), std::move(label),
std::to_string(fsl::GetKoid(process.get())), std::move(ns),
std::move(exported_dir), std::move(client_request)),
container_(container),
job_(std::move(job)),
process_(std::move(process)),
process_koid_(std::to_string(fsl::GetKoid(process_.get()))),
job_koid_(std::to_string(fsl::GetKoid(job_.get()))),
wait_(this, process_.get(), ZX_TASK_TERMINATED),
system_diagnostics_(DuplicateProcess(process_)) {
zx_status_t status = wait_.Begin(async_get_default_dispatcher());
FX_DCHECK(status == ZX_OK);
hub()->SetJobId(job_koid_);
hub()->SetProcessId(process_koid_);
// Serve connections to the system_diagnostics interface.
auto system_diagnostics = fbl::MakeRefCounted<fs::PseudoDir>();
system_diagnostics->AddEntry(
fuchsia::inspect::Tree::Name_,
fbl::AdoptRef(new fs::Service(
[handler = inspect::MakeTreeHandler(&system_diagnostics_.inspector())](zx::channel chan) {
handler(fidl::InterfaceRequest<fuchsia::inspect::Tree>(std::move(chan)));
return ZX_OK;
})));
hub()->AddEntry("system_diagnostics", system_diagnostics);
hub()->AddIncomingServices(this->incoming_services());
if (package_handle.is_valid()) {
hub()->AddPackageHandle(fbl::MakeRefCounted<fs::RemoteDir>(std::move(package_handle)));
}
zx::job watch_job;
if (job_.duplicate(ZX_RIGHT_SAME_RIGHTS, &watch_job) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to duplicate job handle";
}
ComputeComponentInstancePath();
auto realm = this->ns()->realm();
if (realm && realm->cpu_watcher() && !instance_path_.empty()) {
realm->cpu_watcher()->AddTask(instance_path_, std::move(watch_job));
}
}
ComponentControllerImpl::~ComponentControllerImpl() {
zx_info_handle_basic_t info = {};
// Two ways we end up here:
// 1) OnHandleReady() destroys this object; in which case, process is dead.
// 2) Our owner destroys this object; in which case, the process may still be
// alive.
if (job_) {
job_.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
job_.kill();
// Our owner destroyed this object before we could obtain a termination
// reason.
SendOnTerminationEvent(-1, TerminationReason::UNKNOWN);
}
auto realm = this->ns()->realm();
if (realm && realm->cpu_watcher()) {
if (!instance_path_.empty()) {
realm->cpu_watcher()->RemoveTask(instance_path_);
}
}
// Clean up system diagnostics before deleting the backing objects.
hub()->dir()->RemoveEntry("system_diagnostics");
}
void ComponentControllerImpl::Kill() {
FX_VLOGS(1) << "ComponentControllerImpl::Kill() called";
TRACE_DURATION("appmgr", "ComponentController::Kill");
if (job_) {
job_.kill();
job_.reset();
}
}
bool ComponentControllerImpl::SendReturnCodeIfTerminated() {
// Get process info.
zx_info_process_t process_info;
zx_status_t result =
process_.get_info(ZX_INFO_PROCESS, &process_info, sizeof(process_info), NULL, NULL);
FX_DCHECK(result == ZX_OK);
if (process_info.exited) {
SendOnTerminationEvent(process_info.return_code, TerminationReason::EXITED);
}
return process_info.exited;
}
zx_status_t ComponentControllerImpl::AddSubComponentHub(const component::HubInfo& hub_info) {
hub()->EnsureComponentDir();
return hub()->AddComponent(hub_info);
}
zx_status_t ComponentControllerImpl::RemoveSubComponentHub(const component::HubInfo& hub_info) {
return hub()->RemoveComponent(hub_info);
}
// Called when process terminates, regardless of if Kill() was invoked.
void ComponentControllerImpl::Handler(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal* signal) {
FX_DCHECK(status == ZX_OK);
FX_DCHECK(signal->observed == ZX_TASK_TERMINATED);
FX_VLOGS(1) << "ComponentControllerImpl::Handler() called";
bool terminated = SendReturnCodeIfTerminated();
FX_DCHECK(terminated);
process_.reset();
container_->ExtractComponent(this);
// The destructor of the temporary returned by ExtractComponent destroys
// |this| at the end of the previous statement.
}
void ComponentControllerImpl::ComputeComponentInstancePath() {
if (!instance_path_.empty()) {
return;
}
std::vector<std::string> path;
auto realm = this->ns()->realm();
if (realm) {
instance_path_.push_back(this->label());
auto cur = realm;
while (cur) {
instance_path_.push_back(cur->label());
cur = cur->parent();
}
std::reverse(instance_path_.begin(), instance_path_.end());
instance_path_.push_back(job_koid_);
}
}
ComponentBridge::ComponentBridge(fidl::InterfaceRequest<fuchsia::sys::ComponentController> request,
fuchsia::sys::ComponentControllerPtr remote_controller,
ComponentContainer<ComponentBridge>* container, std::string url,
std::string args, std::string label, std::string hub_instance_id,
fxl::RefPtr<Namespace> ns, zx::channel exported_dir,
zx::channel client_request,
std::optional<zx::channel> package_handle)
: ComponentControllerBase(std::move(request), std::move(url), std::move(args), std::move(label),
hub_instance_id, std::move(ns), std::move(exported_dir),
std::move(client_request), MAX_RETRIES_OUT_DIAGNOSTICS),
remote_controller_(std::move(remote_controller)),
container_(std::move(container)),
termination_reason_(TerminationReason::UNKNOWN) {
// Forward termination callbacks from the remote component over the bridge.
remote_controller_.events().OnTerminated = [this](int64_t return_code,
TerminationReason termination_reason) mutable {
// Propagate the events to the external proxy.
if (on_terminated_event_) {
on_terminated_event_(return_code, termination_reason);
}
SendOnTerminationEvent(return_code, termination_reason);
remote_controller_.events().OnTerminated = nullptr;
container_->ExtractComponent(this);
};
remote_controller_.events().OnDirectoryReady = [this] { SendOnDirectoryReadyEvent(); };
remote_controller_.set_error_handler([this](zx_status_t status) {
if (remote_controller_.events().OnTerminated != nullptr) {
remote_controller_.events().OnTerminated(-1, TerminationReason::UNKNOWN);
}
});
// The destructor of the temporary returned by ExtractComponent destroys
// |this| at the end of the previous statement.
hub()->AddIncomingServices(this->incoming_services());
if (package_handle.has_value() && package_handle->is_valid()) {
hub()->AddPackageHandle(fbl::MakeRefCounted<fs::RemoteDir>(std::move(*package_handle)));
}
}
void ComponentBridge::NotifyStopped() {
ns()->NotifyComponentStopped(url(), label(), hub_instance_id());
}
ComponentBridge::~ComponentBridge() {
if (remote_controller_.events().OnTerminated) {
SendOnTerminationEvent(-1, termination_reason_);
}
}
void ComponentBridge::SetParentJobId(const std::string& id) { hub()->SetJobId(id); }
void ComponentBridge::Kill() { remote_controller_->Kill(); }
void ComponentBridge::SetTerminationReason(TerminationReason termination_reason) {
termination_reason_ = termination_reason;
}
} // namespace component