blob: 34ddb66424348bf4aba3b2e87a385a3778751091 [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 "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