blob: 97203b136678c4490b9c1a13e2ad93ae95c6ca21 [file] [log] [blame]
// Copyright 2024 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.
use {
crate::model::component::ComponentInstance,
::routing::{
capability_source::{BuiltinCapabilities, NamespaceCapabilities},
component_instance::TopInstanceInterface,
},
cm_util::TaskGroup,
errors::RebootError,
fidl::endpoints::{self},
fidl_fuchsia_hardware_power_statecontrol as fstatecontrol, fidl_fuchsia_io as fio,
fuchsia_async as fasync,
fuchsia_component::client,
fuchsia_zircon as zx,
futures::lock::Mutex,
std::sync::Arc,
vfs::{directory::entry::OpenRequest, path::Path, ToObjectRequest},
};
/// A special instance identified with component manager, at the top of the tree.
#[derive(Debug)]
pub struct ComponentManagerInstance {
/// The list of capabilities offered from component manager's namespace.
pub namespace_capabilities: NamespaceCapabilities,
/// The list of capabilities offered from component manager as built-in capabilities.
pub builtin_capabilities: BuiltinCapabilities,
/// Tasks owned by component manager's instance.
task_group: TaskGroup,
/// Mutable state for component manager's instance.
state: Mutex<ComponentManagerInstanceState>,
}
/// Mutable state for component manager's instance.
pub struct ComponentManagerInstanceState {
/// The root component instance, this instance's only child.
root: Option<Arc<ComponentInstance>>,
/// Task that is rebooting the system, if any.
reboot_task: Option<fasync::Task<()>>,
}
impl ComponentManagerInstance {
pub fn new(
namespace_capabilities: NamespaceCapabilities,
builtin_capabilities: BuiltinCapabilities,
) -> Self {
Self {
namespace_capabilities,
builtin_capabilities,
state: Mutex::new(ComponentManagerInstanceState::new()),
task_group: TaskGroup::new(),
}
}
/// Returns a group where tasks can be run scoped to this instance
pub fn task_group(&self) -> TaskGroup {
self.task_group.clone()
}
#[cfg(test)]
pub async fn has_reboot_task(&self) -> bool {
self.state.lock().await.reboot_task.is_some()
}
/// Returns the root component instance.
///
/// REQUIRES: The root has already been set. Otherwise panics.
pub async fn root(&self) -> Arc<ComponentInstance> {
self.state.lock().await.root.as_ref().expect("root not set").clone()
}
/// Initializes the state of the instance. Panics if already initialized.
pub async fn init(&self, root: Arc<ComponentInstance>) {
let mut state = self.state.lock().await;
assert!(state.root.is_none(), "child of top instance already set");
state.root = Some(root);
}
/// Triggers a graceful system reboot. Panics if the reboot call fails (which will trigger a
/// forceful reboot if this is the root component manager instance).
///
/// Returns as soon as the call has been made. In the background, component_manager will wait
/// on the `Reboot` call.
pub(super) async fn trigger_reboot(self: &Arc<Self>) {
let mut state = self.state.lock().await;
if state.reboot_task.is_some() {
// Reboot task was already scheduled, nothing to do.
return;
}
let this = self.clone();
state.reboot_task = Some(fasync::Task::spawn(async move {
let res = async move {
let statecontrol_proxy = this.connect_to_statecontrol_admin().await?;
statecontrol_proxy
.reboot(fstatecontrol::RebootReason::CriticalComponentFailure)
.await?
.map_err(|s| RebootError::AdminError(zx::Status::from_raw(s)))
}
.await;
if let Err(e) = res {
// TODO(https://fxbug.dev/42161535): Instead of panicking, we could fall back more gently by
// triggering component_manager's shutdown.
panic!(
"Component with on_terminate=REBOOT terminated, but triggering \
reboot failed. Crashing component_manager instead: {}",
e
);
}
}));
}
/// Obtains a connection to power_manager's `statecontrol` protocol.
async fn connect_to_statecontrol_admin(
&self,
) -> Result<fstatecontrol::AdminProxy, RebootError> {
let (exposed_dir, server) =
endpoints::create_proxy::<fio::DirectoryMarker>().expect("failed to create proxy");
let root = self.root().await;
let mut object_request = fio::OpenFlags::empty().to_object_request(server);
root.open_exposed(OpenRequest::new(
root.execution_scope.clone(),
fio::OpenFlags::empty(),
Path::dot(),
&mut object_request,
))
.await?;
let statecontrol_proxy =
client::connect_to_protocol_at_dir_root::<fstatecontrol::AdminMarker>(&exposed_dir)
.map_err(RebootError::ConnectToAdminFailed)?;
Ok(statecontrol_proxy)
}
}
impl ComponentManagerInstanceState {
pub fn new() -> Self {
Self { reboot_task: None, root: None }
}
}
impl TopInstanceInterface for ComponentManagerInstance {
fn namespace_capabilities(&self) -> &NamespaceCapabilities {
&self.namespace_capabilities
}
fn builtin_capabilities(&self) -> &BuiltinCapabilities {
&self.builtin_capabilities
}
}