| // 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_) { |
| const bool resolver_missing = |
| std::find(update_dependencies.begin(), update_dependencies.end(), |
| fuchsia::pkg::PackageResolver::Name_) == update_dependencies.end(); |
| // 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 (resolver_missing || 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::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_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::OPEN_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 |