blob: c102068097247bb48fd6a4deaa3c16d27507b3a6 [file] [log] [blame]
// Copyright 2017 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/sessionmgr/sessionmgr_impl.h"
#include <fuchsia/element/cpp/fidl.h>
#include <fuchsia/session/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/eventpair.h>
#include <zircon/status.h>
#include <memory>
#include <fbl/unique_fd.h>
#include "fuchsia/intl/cpp/fidl.h"
#include "lib/fdio/directory.h"
#include "lib/fidl/cpp/interface_request.h"
#include "lib/vfs/cpp/pseudo_dir.h"
#include "src/lib/files/directory.h"
#include "src/lib/fsl/io/fd.h"
#include "src/lib/fsl/types/type_converters.h"
#include "src/modular/bin/basemgr/cobalt/cobalt.h"
#include "src/modular/bin/sessionmgr/puppet_master/make_production_impl.h"
#include "src/modular/bin/sessionmgr/story_runner/story_controller_impl.h"
#include "src/modular/lib/common/teardown.h"
#include "src/modular/lib/fidl/array_to_string.h"
#include "src/modular/lib/fidl/clone.h"
#include "src/modular/lib/fidl/json_xdr.h"
namespace modular {
using cobalt_registry::ModularLifetimeEventsMetricDimensionEventType;
namespace {
constexpr char kSessionEnvironmentLabelPrefix[] = "session-";
// The name in the outgoing debug directory (hub) for developer session control
// services.
constexpr char kSessionCtlDir[] = "sessionctl";
// Creates a function that can be used as termination action passed to OnTerminate(),
// which when called invokes the reset() method on the object pointed to by the
// argument. Used to reset() fidl pointers and std::unique_ptr<>s fields.
template <typename X>
fit::function<void(fit::function<void()>)> Reset(std::unique_ptr<X>* const field) {
return [field](fit::function<void()> cont) {
field->reset();
cont();
};
}
template <typename X>
fit::function<void(fit::function<void()>)> Reset(fidl::InterfacePtr<X>* const field) {
return [field](fit::function<void()> cont) {
field->Unbind();
cont();
};
}
// Creates a function that can be used as termination action passed to OnTerminate(),
// which when called asynchronously invokes the Teardown() method on the object
// pointed to by the argument. Used to teardown AppClient and AsyncHolder
// members.
template <typename X>
fit::function<void(fit::function<void()>)> Teardown(const zx::duration timeout,
const char* const message, X* const field) {
return [timeout, message, field](fit::function<void()> cont) {
field->Teardown(timeout, [message, cont = std::move(cont)] {
if (message) {
FX_DLOGS(INFO) << "- " << message << " down.";
}
cont();
});
};
}
} // namespace
SessionmgrImpl::SessionmgrImpl(sys::ComponentContext* const component_context,
modular::ModularConfigAccessor config_accessor,
inspect::Node node_object)
: sessionmgr_context_(component_context),
config_accessor_(std::move(config_accessor)),
inspect_root_node_(std::move(node_object)),
story_provider_impl_("StoryProviderImpl"),
agent_runner_("AgentRunner"),
weak_ptr_factory_(this) {
sessionmgr_context_->outgoing()->AddPublicService<fuchsia::modular::internal::Sessionmgr>(
[this](fidl::InterfaceRequest<fuchsia::modular::internal::Sessionmgr> request) {
bindings_.AddBinding(this, std::move(request));
});
}
SessionmgrImpl::~SessionmgrImpl() = default;
// Initialize is called for each new session, denoted by a unique session_id. In other words, it
// initializes a session, not a SessionmgrImpl (despite the class-scoped name). (Ironically, the
// |finitish_initialization_| lambda does initialize some Sessionmgr-scoped resources only once,
// upon demand.)
void SessionmgrImpl::Initialize(
std::string session_id,
fidl::InterfaceHandle<fuchsia::modular::internal::SessionContext> session_context,
fuchsia::sys::ServiceList v2_services_for_sessionmgr,
fidl::InterfaceRequest<fuchsia::io::Directory> svc_from_v1_sessionmgr,
fuchsia::ui::views::ViewToken view_token, fuchsia::ui::views::ViewRefControl control_ref,
fuchsia::ui::views::ViewRef view_ref) {
FX_LOGS(INFO) << "SessionmgrImpl::Initialize() called.";
session_context_ = session_context.Bind();
OnTerminate(Reset(&session_context_));
session_storage_ = std::make_unique<SessionStorage>();
OnTerminate(Reset(&session_storage_));
InitializeSessionEnvironment(session_id);
// Create |puppet_master_| before |agent_runner_| to ensure agents can use it when terminating.
InitializePuppetMaster();
InitializeElementManager();
InitializeStartupAgentLauncher(std::move(v2_services_for_sessionmgr));
InitializeAgentRunner(config_accessor_.session_shell_app_config().url());
InitializeStartupAgents();
scenic::ViewRefPair view_ref_pair =
scenic::ViewRefPair{.control_ref = {std::move(control_ref.reference)},
.view_ref = {std::move(view_ref.reference)}};
InitializeSessionShell(CloneStruct(config_accessor_.session_shell_app_config()),
std::move(view_token), std::move(view_ref_pair));
// We create |story_provider_impl_| after |agent_runner_| so
// story_provider_impl_ is terminated before agent_runner_, which will cause
// all modules to be terminated before agents are terminated. Agents must
// outlive the stories which contain modules that are connected to those
// agents.
InitializeStoryProvider(CloneStruct(config_accessor_.story_shell_app_config()),
config_accessor_.use_session_shell_for_story_shell_factory());
ConnectSessionShellToStoryProvider();
InitializeSessionCtl();
ServeSvcFromV1SessionmgrDir(std::move(svc_from_v1_sessionmgr));
ReportEvent(ModularLifetimeEventsMetricDimensionEventType::BootedToSessionMgr);
}
void SessionmgrImpl::ConnectSessionShellToStoryProvider() {
// If connecting to the SessionShell errors out, use the GraphicalPresenter
ui_handlers_.session_shell.set_error_handler(
[weak_this = weak_ptr_factory_.GetWeakPtr()](zx_status_t status) mutable {
if (!weak_this) {
return;
}
FX_PLOGS(INFO, status) << "Failed to connect to SessionShell, using GraphicalPresenter";
if (weak_this->story_provider_impl_.get() && weak_this->ui_handlers_.graphical_presenter) {
weak_this->story_provider_impl_.get()->SetPresentationProtocol(
PresentationProtocolPtr{std::move(weak_this->ui_handlers_.graphical_presenter)});
}
});
// If connecting to the GraphicalPresenter errors out, use the SessionShell
ui_handlers_.graphical_presenter.set_error_handler(
[weak_this = weak_ptr_factory_.GetWeakPtr()](zx_status_t status) mutable {
if (!weak_this) {
return;
}
FX_PLOGS(INFO, status) << "Failed to connect to GraphicalPresenter, using SessionShell";
if (weak_this->story_provider_impl_.get() && weak_this->ui_handlers_.session_shell) {
weak_this->story_provider_impl_.get()->SetPresentationProtocol(
PresentationProtocolPtr{std::move(weak_this->ui_handlers_.session_shell)});
}
});
ConnectToSessionShellService(ui_handlers_.session_shell.NewRequest());
ConnectToSessionShellService(ui_handlers_.graphical_presenter.NewRequest());
}
// Create an environment in which to launch story shells and mods. Note that agents cannot be
// launched from this environment because the environment hosts its data directories in a
// session-specific subdirectory of data, and certain agents in existing test devices expect the
// data at a hardcoded, top-level /data directory.
//
// True separation among multiple sessions is currently NOT supported for many reasons, so as
// a temporary workaround, agents are started in the /sys realm via a different launcher.
//
// Future implementations will use the new SessionFramework, which will provide support for
// multiple sessions.
void SessionmgrImpl::InitializeSessionEnvironment(std::string session_id) {
session_id_ = std::move(session_id);
// Create the session's environment (in which we run stories, modules, agents, and so on) as a
// child of sessionmgr's environment. Add session-provided additional services, |kEnvServices|.
static const auto* const kEnvServices =
new std::vector<std::string>{fuchsia::intl::PropertyProvider::Name_};
session_environment_ = std::make_unique<Environment>(
/* parent_env = */ sessionmgr_context_->svc()->Connect<fuchsia::sys::Environment>(),
std::string(kSessionEnvironmentLabelPrefix) + session_id_, *kEnvServices,
/* kill_on_oom = */ true);
// Get the default launcher from the new |session_environment_| to wrap in an
// |ArgvInjectingLauncher|
fuchsia::sys::LauncherPtr session_environment_launcher;
session_environment_->environment()->GetLauncher(session_environment_launcher.NewRequest());
// Wrap the launcher and override it with the new |ArgvInjectingLauncher|
ArgvInjectingLauncher::ArgvMap argv_map;
for (auto& component : config_accessor_.sessionmgr_config().component_args()) {
argv_map.insert(std::make_pair(component.url(), component.args()));
}
session_environment_->OverrideLauncher(
std::make_unique<ArgvInjectingLauncher>(std::move(session_environment_launcher), argv_map));
// Add session-provided services.
session_environment_->AddService<fuchsia::intl::PropertyProvider>(
[this](fidl::InterfaceRequest<fuchsia::intl::PropertyProvider> request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
sessionmgr_context_->svc()->Connect<fuchsia::intl::PropertyProvider>(std::move(request));
});
OnTerminate(Reset(&session_environment_));
}
void SessionmgrImpl::InitializeStartupAgentLauncher(
fuchsia::sys::ServiceList v2_services_for_sessionmgr) {
FX_DCHECK(puppet_master_impl_);
startup_agent_launcher_ = std::make_unique<StartupAgentLauncher>(
&config_accessor_,
[this](fidl::InterfaceRequest<fuchsia::modular::PuppetMaster> request) {
puppet_master_impl_->Connect(std::move(request));
},
[this](fidl::InterfaceRequest<fuchsia::modular::SessionRestartController> request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
session_restart_controller_bindings_.AddBinding(this, std::move(request));
},
[this](fidl::InterfaceRequest<fuchsia::intl::PropertyProvider> request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
sessionmgr_context_->svc()->Connect<fuchsia::intl::PropertyProvider>(std::move(request));
},
[this](fidl::InterfaceRequest<fuchsia::element::Manager> request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
element_manager_impl_->Connect(std::move(request));
},
std::move(v2_services_for_sessionmgr), [this]() { return terminating_; });
OnTerminate(Reset(&startup_agent_launcher_));
}
void SessionmgrImpl::InitializeAgentRunner(const std::string& session_shell_url) {
FX_DCHECK(startup_agent_launcher_);
// Initialize the AgentRunner.
//
// The AgentRunner must use its own |ArgvInjectingLauncher|, different from the
// |ArgvInjectingLauncher| launcher used for mods: The AgentRunner's launcher must come from the
// sys realm (the realm that sessionmgr is running in) due to devices in the field which rely on
// agents /data path mappings being consistent. There is no current solution for the migration of
// /data when a component topology changes. This will be resolved in Session Framework, which
// will soon deprecated and replace this Modular solution.
//
// Create a new launcher that uses sessionmgr's realm launcher.
std::map<std::string, std::string> agent_service_index;
for (auto& entry : config_accessor_.sessionmgr_config().agent_service_index()) {
agent_service_index.emplace(entry.service_name(), entry.agent_url());
}
ArgvInjectingLauncher::ArgvMap argv_map;
for (auto& component : config_accessor_.sessionmgr_config().component_args()) {
argv_map.insert(std::make_pair(component.url(), component.args()));
}
fuchsia::sys::LauncherPtr launcher;
session_environment_->environment()->GetLauncher(launcher.NewRequest());
agent_runner_launcher_ = std::make_unique<ArgvInjectingLauncher>(std::move(launcher), argv_map);
auto restart_session_on_agent_crash =
config_accessor_.sessionmgr_config().restart_session_on_agent_crash();
restart_session_on_agent_crash.push_back(session_shell_url);
agent_runner_.reset(new AgentRunner(
&config_accessor_, agent_runner_launcher_.get(), startup_agent_launcher_.get(),
&inspect_root_node_,
/*on_critical_agent_crash=*/[this] { RestartDueToCriticalFailure(); },
std::move(agent_service_index), config_accessor_.sessionmgr_config().session_agents(),
restart_session_on_agent_crash, sessionmgr_context_));
OnTerminate(Teardown(kAgentRunnerTimeout, "AgentRunner", &agent_runner_));
}
void SessionmgrImpl::InitializeStartupAgents() {
FX_DCHECK(startup_agent_launcher_);
FX_DCHECK(agent_runner_.get());
startup_agent_launcher_->StartAgents(agent_runner_.get(),
config_accessor_.sessionmgr_config().session_agents(),
config_accessor_.sessionmgr_config().startup_agents());
}
void SessionmgrImpl::InitializeStoryProvider(
fuchsia::modular::session::AppConfig story_shell_config,
bool use_session_shell_for_story_shell_factory) {
FX_DCHECK(agent_runner_.get());
FX_DCHECK(session_environment_);
FX_DCHECK(session_storage_);
FX_DCHECK(startup_agent_launcher_);
// The StoryShellFactory to use when creating story shells, or nullptr if no
// such factory exists.
fidl::InterfacePtr<fuchsia::modular::StoryShellFactory> story_shell_factory_ptr;
if (use_session_shell_for_story_shell_factory) {
ConnectToSessionShellService(story_shell_factory_ptr.NewRequest());
}
ComponentContextInfo component_context_info{
agent_runner_.get(), config_accessor_.sessionmgr_config().session_agents()};
story_provider_impl_.reset(new StoryProviderImpl(
session_environment_.get(), session_storage_.get(), std::move(story_shell_config),
std::move(story_shell_factory_ptr), component_context_info, startup_agent_launcher_.get(),
&inspect_root_node_));
OnTerminate(Teardown(kStoryProviderTimeout, "StoryProvider", &story_provider_impl_));
}
void SessionmgrImpl::InitializePuppetMaster() {
FX_DCHECK(session_storage_);
story_command_executor_ = MakeProductionStoryCommandExecutor(session_storage_.get());
OnTerminate(Reset(&story_command_executor_));
puppet_master_impl_ =
std::make_unique<PuppetMasterImpl>(session_storage_.get(), story_command_executor_.get());
OnTerminate(Reset(&puppet_master_impl_));
}
void SessionmgrImpl::InitializeElementManager() {
FX_DCHECK(session_storage_);
element_manager_impl_ = std::make_unique<ElementManagerImpl>(session_storage_.get());
OnTerminate(Reset(&element_manager_impl_));
}
void SessionmgrImpl::InitializeSessionCtl() {
FX_DCHECK(puppet_master_impl_);
session_ctl_ = std::make_unique<SessionCtl>(sessionmgr_context_->outgoing()->debug_dir(),
kSessionCtlDir, puppet_master_impl_.get());
OnTerminate(Reset(&session_ctl_));
}
void SessionmgrImpl::ServeSvcFromV1SessionmgrDir(
fidl::InterfaceRequest<fuchsia::io::Directory> svc_from_v1_sessionmgr) {
zx_status_t status = svc_from_v1_sessionmgr_dir_.Serve(
fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_WRITABLE |
fuchsia::io::OpenFlags::DIRECTORY,
svc_from_v1_sessionmgr.TakeChannel());
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "Failed to serve the svc_from_v1_sessionmgr_dir";
}
}
void SessionmgrImpl::InitializeSessionShell(
fuchsia::modular::session::AppConfig session_shell_config,
fuchsia::ui::views::ViewToken view_token, scenic::ViewRefPair view_ref_pair) {
FX_DCHECK(session_environment_);
FX_DCHECK(agent_runner_.get());
FX_DCHECK(puppet_master_impl_);
session_shell_url_ = session_shell_config.url();
ComponentContextInfo component_context_info{
agent_runner_.get(), config_accessor_.sessionmgr_config().session_agents()};
session_shell_component_context_impl_ = std::make_unique<ComponentContextImpl>(
component_context_info, session_shell_url_, session_shell_url_);
OnTerminate(Reset(&session_shell_component_context_impl_));
// |service_list| enumerates which services are made available to the session
// shell.
auto service_list = std::make_unique<fuchsia::sys::ServiceList>();
for (const auto& service_name : agent_runner_->GetAgentServices()) {
service_list->names.push_back(service_name);
}
agent_runner_->PublishAgentServices(session_shell_url_, &session_shell_services_);
service_list->names.push_back(fuchsia::modular::SessionShellContext::Name_);
session_shell_services_.AddService<fuchsia::modular::SessionShellContext>([this](auto request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
session_shell_context_bindings_.AddBinding(this, std::move(request));
});
service_list->names.push_back(fuchsia::modular::ComponentContext::Name_);
session_shell_services_.AddService<fuchsia::modular::ComponentContext>([this](auto request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
session_shell_component_context_impl_->Connect(std::move(request));
});
service_list->names.push_back(fuchsia::modular::PuppetMaster::Name_);
session_shell_services_.AddService<fuchsia::modular::PuppetMaster>([this](auto request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
puppet_master_impl_->Connect(std::move(request));
});
service_list->names.push_back(fuchsia::element::Manager::Name_);
session_shell_services_.AddService<fuchsia::element::Manager>([this](auto request) {
if (terminating_) {
request.Close(ZX_ERR_UNAVAILABLE);
return;
}
element_manager_impl_->Connect(std::move(request));
});
// The services in |session_shell_services_| are provided through the
// connection held in |session_shell_service_provider| connected to
// |session_shell_services_|.
{
fuchsia::sys::ServiceProviderPtr session_shell_service_provider;
session_shell_services_.AddBinding(session_shell_service_provider.NewRequest());
service_list->provider = std::move(session_shell_service_provider);
}
for (const auto& service_name : service_list->names) {
fuchsia::sys::ServiceProviderPtr service_provider;
session_shell_services_.AddBinding(service_provider.NewRequest());
zx_status_t status = svc_from_v1_sessionmgr_dir_.AddEntry(
service_name, std::make_unique<vfs::Service>(
[service_provider = std::move(service_provider), service_name](
zx::channel request, async_dispatcher_t* dispatcher) {
service_provider->ConnectToService(service_name, std::move(request));
}));
if (status != ZX_OK) {
FX_PLOGS(WARNING, status)
<< "Could not add service_list handler to svc_from_v1_sessionmgr, for service name: "
<< service_name;
}
}
auto session_shell_app = std::make_unique<AppClient<fuchsia::modular::Lifecycle>>(
session_environment_->GetLauncher(), std::move(session_shell_config),
/* data_origin = */ "", std::move(service_list));
fuchsia::ui::app::ViewProviderPtr view_provider;
session_shell_app->services().ConnectToService(view_provider.NewRequest());
view_provider->CreateViewWithViewRef(std::move(view_token.value),
std::move(view_ref_pair.control_ref),
std::move(view_ref_pair.view_ref));
agent_runner_->AddRunningAgent(session_shell_url_, std::move(session_shell_app));
}
void SessionmgrImpl::Terminate(fit::function<void()> done) {
FX_LOGS(INFO) << "Sessionmgr::Terminate()";
terminating_ = true;
terminate_done_ = std::move(done);
TerminateRecurse(static_cast<int>(on_terminate_cbs_.size()) - 1);
}
void SessionmgrImpl::GetComponentContext(
fidl::InterfaceRequest<fuchsia::modular::ComponentContext> request) {
session_shell_component_context_impl_->Connect(std::move(request));
}
void SessionmgrImpl::GetPresentation(
fidl::InterfaceRequest<fuchsia::ui::policy::Presentation> request) {
session_context_->GetPresentation(std::move(request));
}
void SessionmgrImpl::GetStoryProvider(
fidl::InterfaceRequest<fuchsia::modular::StoryProvider> request) {
story_provider_impl_->Connect(std::move(request));
}
void SessionmgrImpl::Logout() { Restart(); }
void SessionmgrImpl::Restart() { session_context_->Restart(); }
void SessionmgrImpl::RestartDueToCriticalFailure() {
session_context_->RestartDueToCriticalFailure();
}
void SessionmgrImpl::OnTerminate(fit::function<void(fit::function<void()>)> action) {
on_terminate_cbs_.emplace_back(std::move(action));
}
void SessionmgrImpl::TerminateRecurse(const int i) {
if (i >= 0) {
on_terminate_cbs_[i]([this, i] { TerminateRecurse(i - 1); });
} else {
FX_LOGS(INFO) << "Sessionmgr::Terminate(): done";
terminate_done_();
}
}
} // namespace modular