blob: bb290e0c07188a6b14b038ce497d519b6cbaba9e [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/sysmgr/app.h"
#include <fuchsia/io/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/default.h>
#include <lib/async/dispatcher.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/service.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
namespace sysmgr {
namespace {
constexpr char kDefaultLabel[] = "sys";
#ifdef AUTO_UPDATE_PACKAGES
constexpr bool kAutoUpdatePackages = true;
#else
constexpr bool kAutoUpdatePackages = false;
#endif
} // namespace
App::App(Config config, std::shared_ptr<sys::ServiceDirectory> incoming_services, async::Loop* loop)
: loop_(loop),
incoming_services_(std::move(incoming_services)),
auto_updates_enabled_(kAutoUpdatePackages),
power_admin_(incoming_services_->Connect<fuchsia::hardware::power::statecontrol::Admin>()) {
const auto critical_components = config.TakeCriticalComponents();
for (const auto& url : critical_components) {
critical_components_.insert(url);
}
// The set of excluded services below are services that are the transitive
// closure of dependencies required for auto-updates that must not be resolved
// via the update service.
const auto update_dependencies = config.TakeUpdateDependencies();
const auto optional_services = config.TakeOptionalServices();
std::unordered_set<std::string> update_dependency_urls;
// Register services.
for (auto& pair : config.TakeServices()) {
if (std::find(update_dependencies.begin(), update_dependencies.end(), pair.first) !=
std::end(update_dependencies)) {
update_dependency_urls.insert(pair.second->url);
}
const bool optional = std::find(optional_services.begin(), optional_services.end(),
pair.first) != std::end(optional_services);
RegisterSingleton(pair.first, std::move(pair.second), optional);
}
auto env_request = env_.NewRequest();
fuchsia::sys::ServiceProviderPtr env_services;
env_->GetLauncher(env_launcher_.NewRequest());
env_->GetServices(env_services.NewRequest());
zx::channel directory;
env_services_ = sys::ServiceDirectory::CreateWithRequest(&directory);
env_->GetDirectory(std::move(directory));
if (auto_updates_enabled_) {
// Check if any component urls that are excluded (dependencies of
// PackageResolver/startup) were not registered from the above
// configuration.
bool missing_services = false;
for (auto& dep : update_dependencies) {
if (std::find(svc_names_.begin(), svc_names_.end(), dep) == svc_names_.end()) {
FX_LOGS(WARNING) << "missing service required for auto updates: " << dep;
missing_services = true;
}
}
if (missing_services) {
FX_LOGS(WARNING) << "auto_update_packages = true but some update "
"dependencies are missing in the sys environment. "
"Disabling auto-updates.";
auto_updates_enabled_ = false;
}
}
// Configure loader.
if (auto_updates_enabled_) {
package_updating_loader_ = std::make_unique<PackageUpdatingLoader>(
std::move(update_dependency_urls), std::move(env_services), async_get_default_dispatcher());
}
static const char* const kLoaderName = fuchsia::sys::Loader::Name_;
auto child =
std::make_unique<vfs::Service>([this](zx::channel channel, async_dispatcher_t* dispatcher) {
if (auto_updates_enabled_) {
package_updating_loader_->Bind(
fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel)));
} else {
incoming_services_->Connect(kLoaderName, std::move(channel));
}
});
svc_names_.push_back(kLoaderName);
svc_root_.AddEntry(kLoaderName, std::move(child));
// Set up environment for the programs we will run.
fuchsia::sys::ServiceListPtr service_list(new fuchsia::sys::ServiceList);
service_list->names = std::move(svc_names_);
service_list->host_directory = OpenAsDirectory();
fuchsia::sys::EnvironmentPtr environment;
incoming_services_->Connect(environment.NewRequest());
// Inherit services from the root appmgr realm, which includes certain
// services currently implemented by non-component processes that are passed
// through appmgr to this sys realm. Note that |service_list| will override
// the inherited services if it includes services also in the root realm.
fuchsia::sys::EnvironmentOptions options = {.inherit_parent_services = true};
environment->CreateNestedEnvironment(std::move(env_request), env_controller_.NewRequest(),
kDefaultLabel, std::move(service_list), std::move(options));
// Connect to startup services
for (auto& startup_service : config.TakeStartupServices()) {
FX_VLOGS(1) << "Connecting to startup service " << startup_service;
zx::channel h1, h2;
zx::channel::create(0, &h1, &h2);
ConnectToService(startup_service, std::move(h1));
}
// Launch startup applications.
for (auto& launch_info : config.TakeApps()) {
LaunchComponent(std::move(*launch_info), nullptr, nullptr);
}
}
App::~App() = default;
zx::channel App::OpenAsDirectory() {
fidl::InterfaceHandle<fuchsia::io::Directory> dir;
svc_root_.Serve(fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE,
dir.NewRequest().TakeChannel());
return dir.TakeChannel();
}
void App::ConnectToService(const std::string& service_name, zx::channel channel) {
vfs::internal::Node* child;
auto status = svc_root_.Lookup(service_name, &child);
if (status == ZX_OK) {
status = child->Serve(fuchsia::io::OpenFlags::RIGHT_READABLE, std::move(channel));
} else if (status == ZX_ERR_NOT_FOUND) {
FX_LOGS(WARNING) << "Service " << service_name
<< " not in service list, attempting to connect through environment";
env_services_->Connect(service_name, std::move(channel));
status = ZX_OK;
} else {
FX_LOGS(ERROR) << "Could not serve " << service_name << ": " << status;
}
}
void App::RegisterSingleton(std::string service_name, fuchsia::sys::LaunchInfoPtr launch_info,
bool is_optional_service) {
// The ComponentController is kept alive under the following functor's state.
auto child = std::make_unique<vfs::Service>(
[this, is_optional_service, service_name, launch_info = std::move(launch_info),
controller = fuchsia::sys::ComponentControllerPtr()](
zx::channel client_handle, async_dispatcher_t* dispatcher) mutable {
FX_VLOGS(2) << "Servicing singleton service request for " << service_name;
std::shared_ptr<sys::ServiceDirectory> svcs;
auto it = services_.find(launch_info->url);
// Start component if it isn't already running
if (it == services_.end()) {
FX_VLOGS(1) << "Starting singleton " << launch_info->url << " for service "
<< service_name;
LaunchComponent(
*launch_info,
[is_optional_service, service_name, url = launch_info->url](
int64_t, fuchsia::sys::TerminationReason reason) {
if (!is_optional_service &&
reason == fuchsia::sys::TerminationReason::PACKAGE_NOT_FOUND) {
FX_LOGS(ERROR) << "Could not load package for service " << service_name << " at "
<< url;
}
},
[is_optional_service, url = launch_info->url](zx_status_t) {
if (!is_optional_service) {
FX_LOGS(ERROR) << "Singleton component " << url << " died";
}
});
it = services_.find(launch_info->url);
FX_DCHECK(it != services_.end());
}
it->second->Connect(service_name, std::move(client_handle));
});
svc_names_.push_back(service_name);
svc_root_.AddEntry(service_name, std::move(child));
}
void App::LaunchComponent(const fuchsia::sys::LaunchInfo& launch_info,
fuchsia::sys::ComponentController::OnTerminatedCallback on_terminate,
fit::function<void(zx_status_t)> on_ctrl_err) {
FX_VLOGS(1) << "Launching component " << launch_info.url;
const auto& critical_it = critical_components_.find(launch_info.url);
const bool is_critical = critical_it != critical_components_.end();
fuchsia::sys::ComponentControllerPtr ctrl;
ctrl.events().OnTerminated = std::move(on_terminate);
ctrl.set_error_handler([this, on_ctrl_err = std::move(on_ctrl_err), url = launch_info.url,
is_critical](zx_status_t status) mutable {
// move the controller on to the stack first before removing it from |controllers_|; otherwise,
// this lambda's lifetime ends when we remove the controller from |controllers_|.
auto ctrl = std::move(controllers_[url]);
controllers_.erase(url);
if (on_ctrl_err) {
on_ctrl_err(status);
}
if (is_critical) {
RebootFromCriticalComponent(url);
} else {
services_.erase(url);
}
});
// Launch the component
fuchsia::sys::LaunchInfo dup_launch_info;
dup_launch_info.url = launch_info.url;
services_.emplace(launch_info.url,
sys::ServiceDirectory::CreateWithRequest(&dup_launch_info.directory_request));
fidl::Clone(launch_info.arguments, &dup_launch_info.arguments);
env_launcher_->CreateComponent(std::move(dup_launch_info), ctrl.NewRequest());
controllers_[launch_info.url] = std::move(ctrl);
}
void App::RebootFromCriticalComponent(const std::string& component_url) {
FX_LOGS(ERROR) << "Critical component " << component_url << " has crashed. Rebooting system.";
power_admin_->Reboot(
fuchsia::hardware::power::statecontrol::RebootReason::CRITICAL_COMPONENT_FAILURE,
[this](fuchsia::hardware::power::statecontrol::Admin_Reboot_Result status) {
if (status.is_err()) {
FX_PLOGS(FATAL, status.err()) << "Failed to reboot";
loop_->Quit();
}
});
}
} // namespace sysmgr