blob: 3b147c3f7588230e377e426f06420e608ba63812 [file] [log] [blame]
// Copyright 2020 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::{
error::ModelError,
hooks::{
Event, EventError, EventErrorPayload, EventPayload, EventType, Hook, HooksRegistration,
},
model::Model,
moniker::AbsoluteMoniker,
realm::Realm,
rights::{Rights, READ_RIGHTS, WRITE_RIGHTS},
},
async_trait::async_trait,
cm_rust::{CapabilityPath, ExposeDecl, ExposeDirectoryDecl, ExposeProtocolDecl},
fidl::endpoints::{Proxy, ServerEnd},
fidl_fuchsia_io::{self as fio, DirectoryProxy, NodeEvent, NodeMarker, NodeProxy},
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::stream::StreamExt,
io_util,
log::*,
std::sync::{Arc, Weak},
};
/// Awaits for `Started` events and for each capability exposed to framework, dispatches a
/// `CapabilityReady` event.
pub struct CapabilityReadyNotifier {
model: Weak<Model>,
}
impl CapabilityReadyNotifier {
pub fn new(model: Weak<Model>) -> Self {
Self { model }
}
pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
vec![HooksRegistration::new(
"CapabilityReadyNotifier",
vec![EventType::Started],
Arc::downgrade(self) as Weak<dyn Hook>,
)]
}
async fn on_component_started(
self: &Arc<Self>,
target_moniker: &AbsoluteMoniker,
outgoing_dir: &DirectoryProxy,
expose_decls: Vec<ExposeDecl>,
) -> Result<(), ModelError> {
// Forward along errors into the new task so that dispatch can forward the
// error as an event.
let outgoing_node_result = async move {
let outgoing_dir = io_util::clone_directory(
&outgoing_dir,
fio::CLONE_FLAG_SAME_RIGHTS | fio::OPEN_FLAG_DESCRIBE,
)
.map_err(|_| ModelError::open_directory_error(target_moniker.clone(), "/"))?;
let outgoing_dir_channel = outgoing_dir
.into_channel()
.map_err(|_| ModelError::open_directory_error(target_moniker.clone(), "/"))?;
Ok(NodeProxy::from_channel(outgoing_dir_channel))
}
.await;
// Don't block the handling on the event on the exposed capabilities being ready
let this = self.clone();
let moniker = target_moniker.clone();
fasync::spawn(async move {
// If we can't find the realm then we can't dispatch any CapabilityReady event,
// error or otherwise. This isn't necessarily an error as the model or realm might've been
// destroyed in the intervening time, so we just exit early.
let target_realm = match this.model.upgrade() {
Some(model) => {
if let Ok(realm) = model.look_up_realm(&moniker).await {
realm
} else {
return;
}
}
None => return,
};
if let Err(e) = this
.dispatch_capabilities_ready(outgoing_node_result, expose_decls, &target_realm)
.await
{
error!("Failed CapabilityReady dispatch for {}: {:?}", moniker, e);
return;
}
});
Ok(())
}
/// Waits for the OnOpen event on the directory. This will hang until the component starts
/// serving that directory. The directory should have been cloned/opened with DESCRIBE.
async fn wait_for_on_open(
&self,
node: &NodeProxy,
target_moniker: &AbsoluteMoniker,
path: String,
) -> Result<(), ModelError> {
let mut events = node.take_event_stream();
match events.next().await {
Some(Ok(NodeEvent::OnOpen_ { s: status, info: _ })) => zx::Status::ok(status)
.map_err(|_| ModelError::open_directory_error(target_moniker.clone(), path)),
_ => Err(ModelError::open_directory_error(target_moniker.clone(), path)),
}
}
/// Waits for the outgoing directory to be ready and then notifies hooks of all the capabilities
/// inside it that were exposed to the framework by the component.
async fn dispatch_capabilities_ready(
&self,
outgoing_node_result: Result<NodeProxy, ModelError>,
expose_decls: Vec<ExposeDecl>,
target_realm: &Arc<Realm>,
) -> Result<(), ModelError> {
// Forward along the result for opening the outgoing directory into the CapabilityReady
// dispatch in order to propagate any potential errors as an event.
let outgoing_dir_result = async move {
let outgoing_node = outgoing_node_result?;
self.wait_for_on_open(&outgoing_node, &target_realm.abs_moniker, "/".to_string())
.await?;
io_util::node_to_directory(outgoing_node).map_err(|_| {
ModelError::open_directory_error(target_realm.abs_moniker.clone(), "/")
})
}
.await;
for expose_decl in expose_decls {
match expose_decl {
ExposeDecl::Directory(ExposeDirectoryDecl { target_path, rights, .. }) => {
self.dispatch_capability_ready(
&target_realm,
outgoing_dir_result.as_ref(),
fio::MODE_TYPE_DIRECTORY,
Rights::from(rights.unwrap_or(*READ_RIGHTS)),
target_path,
)
.await
}
ExposeDecl::Protocol(ExposeProtocolDecl { target_path, .. }) => {
self.dispatch_capability_ready(
&target_realm,
outgoing_dir_result.as_ref(),
fio::MODE_TYPE_SERVICE,
Rights::from(*WRITE_RIGHTS),
target_path,
)
.await
}
_ => Ok(()),
}
.unwrap_or_else(|e| {
error!("Error notifying capability ready for {}: {:?}", target_realm.abs_moniker, e)
});
}
Ok(())
}
/// Dispatches an event with the directory at the given `target_path` inside the provided
/// outgoing directory if the capability is available.
async fn dispatch_capability_ready(
&self,
target_realm: &Arc<Realm>,
outgoing_dir_result: Result<&DirectoryProxy, &ModelError>,
mode: u32,
rights: Rights,
target_path: CapabilityPath,
) -> Result<(), ModelError> {
// DirProxy.open fails on absolute paths.
let path = target_path.to_string();
let canonicalized_path = io_util::canonicalize_path(&path);
let node_result = async move {
let outgoing_dir = outgoing_dir_result.map_err(|e| e.clone())?;
let (node, server_end) = fidl::endpoints::create_proxy::<NodeMarker>().unwrap();
outgoing_dir
.open(
rights.into_legacy() | fio::OPEN_FLAG_DESCRIBE,
mode,
&canonicalized_path,
ServerEnd::new(server_end.into_channel()),
)
.map_err(|_| {
ModelError::open_directory_error(
target_realm.abs_moniker.clone(),
target_path.to_string(),
)
})?;
self.wait_for_on_open(&node, &target_realm.abs_moniker, canonicalized_path.to_string())
.await?;
Ok(node)
}
.await;
match node_result {
Ok(node) => {
let event = Event::new(
target_realm.abs_moniker.clone(),
Ok(EventPayload::CapabilityReady { path, node }),
);
target_realm.hooks.dispatch(&event).await
}
Err(e) => {
let event = Event::new(
target_realm.abs_moniker.clone(),
Err(EventError::new(&e, EventErrorPayload::CapabilityReady { path })),
);
target_realm.hooks.dispatch(&event).await?;
return Err(e);
}
}
}
}
#[async_trait]
impl Hook for CapabilityReadyNotifier {
async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
match &event.result {
Ok(EventPayload::Started { runtime, component_decl, .. }) => {
let expose_decls = component_decl.get_self_capabilities_exposed_to_framework();
if expose_decls.is_empty() {
return Ok(());
}
if let Some(outgoing_dir) = &runtime.outgoing_dir {
self.on_component_started(&event.target_moniker, outgoing_dir, expose_decls)
.await?;
}
}
_ => {}
}
Ok(())
}
}