| // 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 "src/modular/bin/basemgr/basemgr_impl.h" |
| |
| #include <fuchsia/ui/app/cpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/fit/bridge.h> |
| #include <lib/fostr/fidl/fuchsia/modular/session/formatting.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "src/lib/fsl/vmo/strings.h" |
| #include "src/modular/lib/common/async_holder.h" |
| #include "src/modular/lib/common/teardown.h" |
| #include "src/modular/lib/fidl/app_client.h" |
| #include "src/modular/lib/fidl/clone.h" |
| #include "src/modular/lib/modular_config/modular_config.h" |
| #include "src/modular/lib/modular_config/modular_config_constants.h" |
| |
| namespace modular { |
| |
| // Timeout for tearing down the session launcher component. |
| static constexpr auto kSessionLauncherComponentTimeout = zx::sec(1); |
| |
| using cobalt_registry::ModularLifetimeEventsMetricDimensionEventType; |
| |
| // Implementation of the |fuchsia::modular::session::Launcher| protocol. |
| class LauncherImpl : public fuchsia::modular::session::Launcher { |
| public: |
| explicit LauncherImpl(modular::BasemgrImpl* basemgr_impl) : basemgr_impl_(basemgr_impl) {} |
| |
| // |Launcher| |
| void LaunchSessionmgr(fuchsia::mem::Buffer config) override { |
| LaunchSessionmgrWithServices(std::move(config), fuchsia::sys::ServiceList()); |
| } |
| |
| // |Launcher| |
| void LaunchSessionmgrWithServices(fuchsia::mem::Buffer config, |
| fuchsia::sys::ServiceList additional_services) override { |
| if (additional_services.names.size() > 0 && !additional_services.host_directory) { |
| FX_LOGS(ERROR) |
| << "LaunchSessionmgrWithServices() requires additional_servicces.host_directory"; |
| binding_->Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| FX_DCHECK(binding_); |
| |
| if (basemgr_impl_->state() == BasemgrImpl::State::SHUTTING_DOWN) { |
| binding_->Close(ZX_ERR_BAD_STATE); |
| return; |
| } |
| |
| // Read the configuration from the buffer. |
| std::string config_str; |
| if (auto is_read_ok = fsl::StringFromVmo(config, &config_str); !is_read_ok) { |
| binding_->Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| // Parse the configuration. |
| auto config_result = modular::ParseConfig(config_str); |
| if (config_result.is_error()) { |
| binding_->Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| // The configuration cannot try to override the session launcher component. |
| if (config_result.value().has_basemgr_config() && |
| config_result.value().basemgr_config().has_session_launcher()) { |
| binding_->Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| basemgr_impl_->LaunchSessionmgr(config_result.take_value(), std::move(additional_services)); |
| } |
| |
| void set_binding(modular::BasemgrImpl::LauncherBinding* binding) { binding_ = binding; } |
| |
| DISALLOW_COPY_ASSIGN_AND_MOVE(LauncherImpl); |
| |
| private: |
| modular::BasemgrImpl* basemgr_impl_; // Not owned. |
| modular::BasemgrImpl::LauncherBinding* binding_ = nullptr; // Not owned. |
| }; |
| |
| BasemgrImpl::BasemgrImpl(modular::ModularConfigAccessor config_accessor, |
| std::shared_ptr<sys::ServiceDirectory> incoming_services, |
| std::shared_ptr<sys::OutgoingDirectory> outgoing_services, |
| fuchsia::sys::LauncherPtr launcher, |
| fuchsia::ui::policy::PresenterPtr presenter, |
| fuchsia::hardware::power::statecontrol::AdminPtr device_administrator, |
| fit::function<void()> on_shutdown) |
| : config_accessor_(std::move(config_accessor)), |
| component_context_services_(std::move(incoming_services)), |
| outgoing_services_(std::move(outgoing_services)), |
| launcher_(std::move(launcher)), |
| presenter_(std::move(presenter)), |
| device_administrator_(std::move(device_administrator)), |
| on_shutdown_(std::move(on_shutdown)), |
| session_provider_("SessionProvider"), |
| executor_(async_get_default_dispatcher()) { |
| component_context_services_->Connect(base_environment_.NewRequest()); |
| |
| outgoing_services_->AddPublicService<fuchsia::modular::Lifecycle>( |
| lifecycle_bindings_.GetHandler(this)); |
| outgoing_services_->AddPublicService(process_lifecycle_bindings_.GetHandler(this), |
| "fuchsia.process.lifecycle.Lifecycle"); |
| |
| // Bind the |Launcher| protocol to a client-specific implementation that delegates back to |this|. |
| fidl::InterfaceRequestHandler<fuchsia::modular::session::Launcher> launcher_handler = |
| [this](fidl::InterfaceRequest<fuchsia::modular::session::Launcher> request) { |
| auto impl = std::make_unique<LauncherImpl>(this); |
| session_launcher_bindings_.AddBinding(std::move(impl), std::move(request), |
| /*dispatcher=*/nullptr); |
| const auto& binding = session_launcher_bindings_.bindings().back().get(); |
| binding->impl()->set_binding(binding); |
| }; |
| session_launcher_component_service_dir_.AddEntry( |
| fuchsia::modular::session::Launcher::Name_, |
| std::make_unique<vfs::Service>(std::move(launcher_handler))); |
| |
| Start(); |
| } |
| |
| BasemgrImpl::~BasemgrImpl() = default; |
| |
| void BasemgrImpl::Connect( |
| fidl::InterfaceRequest<fuchsia::modular::internal::BasemgrDebug> request) { |
| basemgr_debug_bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| fit::promise<void, zx_status_t> BasemgrImpl::StopScenic() { |
| fit::bridge<void, zx_status_t> bridge; |
| |
| if (!presenter_) { |
| FX_LOGS(INFO) << "StopScenic: no presenter; assuming that Scenic has not been launched"; |
| bridge.completer.complete_ok(); |
| return bridge.consumer.promise(); |
| } |
| |
| // Lazily connect to lifecycle controller, instead of keeping open an often-unused channel. |
| component_context_services_->Connect(scenic_lifecycle_controller_.NewRequest()); |
| scenic_lifecycle_controller_->Terminate(); |
| |
| scenic_lifecycle_controller_.set_error_handler( |
| [completer = std::move(bridge.completer)](zx_status_t status) mutable { |
| if (status == ZX_ERR_PEER_CLOSED) { |
| completer.complete_ok(); |
| } else { |
| completer.complete_error(status); |
| } |
| }); |
| |
| return bridge.consumer.promise(); |
| } |
| |
| void BasemgrImpl::Start() { |
| ReportEvent(ModularLifetimeEventsMetricDimensionEventType::BootedToBaseMgr); |
| |
| if (config_accessor_.basemgr_config().has_session_launcher()) { |
| StartSessionLauncherComponent(); |
| } else { |
| CreateSessionProvider(&config_accessor_, fuchsia::sys::ServiceList()); |
| |
| auto start_session_result = StartSession(); |
| if (start_session_result.is_error()) { |
| FX_PLOGS(FATAL, start_session_result.error()) << "Could not start session"; |
| } |
| } |
| } |
| |
| void BasemgrImpl::Shutdown() { |
| FX_LOGS(INFO) << "Shutting down basemgr"; |
| |
| // Prevent the shutdown sequence from running twice. |
| if (state_ == State::SHUTTING_DOWN) { |
| return; |
| } |
| |
| state_ = State::SHUTTING_DOWN; |
| |
| // Teardown the session provider if it exists. |
| // Always completes successfully. |
| auto teardown_session_provider = [this]() { |
| auto bridge = fit::bridge(); |
| if (session_provider_.get()) { |
| session_provider_.Teardown(kSessionProviderTimeout, bridge.completer.bind()); |
| } else { |
| bridge.completer.complete_ok(); |
| } |
| return bridge.consumer.promise(); |
| }; |
| |
| // Teardown the session component if it exists. |
| // Always completes successfully. |
| auto teardown_session_component_app = [this]() { |
| auto bridge = fit::bridge(); |
| if (session_launcher_component_app_) { |
| session_launcher_component_app_->Teardown(kSessionLauncherComponentTimeout, |
| bridge.completer.bind()); |
| } else { |
| bridge.completer.complete_ok(); |
| } |
| return bridge.consumer.promise(); |
| }; |
| |
| // Always completes successfully. |
| auto stop_scenic = [this]() { |
| return StopScenic().then([](const fit::result<void, zx_status_t>& result) { |
| if (result.is_error()) { |
| FX_PLOGS(ERROR, result.error()) |
| << "Scenic LifecycleController experienced some error other than PEER_CLOSED"; |
| } else { |
| FX_DLOGS(INFO) << "- fuchsia::ui::Scenic down"; |
| } |
| }); |
| }; |
| |
| auto shutdown = teardown_session_provider() |
| .and_then(teardown_session_component_app()) |
| .and_then(stop_scenic()) |
| .and_then([this]() { |
| basemgr_debug_bindings_.CloseAll(ZX_OK); |
| on_shutdown_(); |
| }); |
| |
| executor_.schedule_task(std::move(shutdown)); |
| } |
| |
| void BasemgrImpl::Terminate() { Shutdown(); } |
| |
| void BasemgrImpl::Stop() { Shutdown(); } |
| |
| void BasemgrImpl::CreateSessionProvider(const ModularConfigAccessor* const config_accessor, |
| fuchsia::sys::ServiceList services_from_session_launcher) { |
| FX_DCHECK(!session_provider_.get()); |
| |
| session_provider_.reset(new SessionProvider( |
| /*delegate=*/this, launcher_.get(), base_environment_.get(), device_administrator_.get(), |
| config_accessor, std::move(services_from_session_launcher), |
| /*on_zero_sessions=*/[this] { |
| if (state_ == State::SHUTTING_DOWN) { |
| return; |
| } |
| FX_DLOGS(INFO) << "Re-starting due to session closure"; |
| auto start_session_result = StartSession(); |
| if (start_session_result.is_error()) { |
| FX_PLOGS(FATAL, start_session_result.error()) << "Could not restart session"; |
| } |
| })); |
| } |
| |
| BasemgrImpl::StartSessionResult BasemgrImpl::StartSession() { |
| if (state_ == State::SHUTTING_DOWN || !session_provider_.get() || |
| session_provider_->is_session_running()) { |
| return fit::error(ZX_ERR_BAD_STATE); |
| } |
| |
| auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); |
| auto start_session_result = session_provider_->StartSession(std::move(view_token)); |
| FX_CHECK(start_session_result.is_ok()); |
| |
| // TODO(fxbug.dev/56132): Ownership of the Presenter should be moved to the session shell. |
| if (presenter_) { |
| presentation_container_ = |
| std::make_unique<PresentationContainer>(presenter_.get(), std::move(view_holder_token)); |
| presenter_.set_error_handler( |
| [this](zx_status_t /* unused */) { presentation_container_.reset(); }); |
| } |
| |
| return fit::ok(); |
| } |
| |
| void BasemgrImpl::RestartSession(RestartSessionCallback on_restart_complete) { |
| if (state_ == State::SHUTTING_DOWN || !session_provider_.get()) { |
| return; |
| } |
| session_provider_->RestartSession(std::move(on_restart_complete)); |
| } |
| |
| void BasemgrImpl::StartSessionWithRandomId() { |
| // If there is a session already running, exit. |
| if (session_provider_.get()) { |
| return; |
| } |
| FX_CHECK(!session_provider_.get()); |
| |
| // The new session uses a configuration based on its existing configuration, |
| // with an argument set that ensures it starts with a random session ID. |
| // |
| // Create a copy of the configuration that ensures a random session ID is used. |
| // TODO(fxbug.dev/51752): Create a config field for use_random_session_id and remove base shell |
| auto new_config = CloneStruct(config_accessor_.config()); |
| new_config.mutable_basemgr_config() |
| ->mutable_base_shell() |
| ->mutable_app_config() |
| ->mutable_args() |
| ->push_back(modular_config::kPersistUserArg); |
| |
| // Set the new config and create a session provider. |
| // |
| // Overwrite the config accessor that was the source for the original configuration, |
| // and which will be used to launch sessions in the future. |
| // |
| // This method, StartSessionWithRandomId(), is defined on the BasemgrDebug interface. |
| // It only ever launches a new session, and thus will use the default config. |
| config_accessor_ = ModularConfigAccessor(std::move(new_config)); |
| CreateSessionProvider(&config_accessor_, fuchsia::sys::ServiceList()); |
| |
| if (auto result = StartSession(); result.is_error()) { |
| FX_PLOGS(ERROR, result.error()) << "Could not start session"; |
| } |
| } |
| |
| void BasemgrImpl::GetPresentation( |
| fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> request) { |
| if (!presentation_container_) { |
| request.Close(ZX_ERR_NOT_FOUND); |
| return; |
| } |
| presentation_container_->GetPresentation(std::move(request)); |
| } |
| |
| void BasemgrImpl::LaunchSessionmgr(fuchsia::modular::session::ModularConfig config, |
| fuchsia::sys::ServiceList services_from_session_launcher) { |
| // If there is a session provider, tear it down and try again. This stops any running session. |
| if (session_provider_.get()) { |
| session_provider_.Teardown(kSessionProviderTimeout, |
| [this, config = std::move(config), |
| services = std::move(services_from_session_launcher)]() mutable { |
| session_provider_.reset(nullptr); |
| LaunchSessionmgr(std::move(config), std::move(services)); |
| }); |
| return; |
| } |
| |
| launch_sessionmgr_config_accessor_ = |
| std::make_unique<modular::ModularConfigAccessor>(std::move(config)); |
| |
| CreateSessionProvider(launch_sessionmgr_config_accessor_.get(), |
| std::move(services_from_session_launcher)); |
| |
| if (auto result = StartSession(); result.is_error()) { |
| FX_PLOGS(ERROR, result.error()) << "Could not start session"; |
| } |
| } |
| |
| void BasemgrImpl::StartSessionLauncherComponent() { |
| auto app_config = CloneStruct(config_accessor_.basemgr_config().session_launcher()); |
| |
| auto services = CreateAndServeSessionLauncherComponentServices(); |
| |
| FX_LOGS(INFO) << "Starting session launcher component: " << app_config.url(); |
| |
| session_launcher_component_app_ = std::make_unique<AppClient<fuchsia::modular::Lifecycle>>( |
| launcher_.get(), std::move(app_config), /*data_origin=*/"", std::move(services), |
| /*flat_namespace=*/nullptr); |
| } |
| |
| fuchsia::sys::ServiceListPtr BasemgrImpl::CreateAndServeSessionLauncherComponentServices() { |
| fidl::InterfaceHandle<fuchsia::io::Directory> dir_handle; |
| session_launcher_component_service_dir_.Serve( |
| fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE, |
| dir_handle.NewRequest().TakeChannel()); |
| |
| auto services = fuchsia::sys::ServiceList::New(); |
| services->names.push_back(fuchsia::modular::session::Launcher::Name_); |
| services->host_directory = dir_handle.TakeChannel(); |
| |
| return services; |
| } |
| |
| } // namespace modular |