blob: 8de7f5fec6052659391de84593c10747e067a107 [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::common_utils::common::macros::{fx_err_and_bail, with_line};
use crate::component::types::{
ComponentLaunchRequest, ComponentLaunchResponse, ComponentSearchRequest, ComponentSearchResult,
};
use anyhow::{format_err, Context as _, Error};
use component_debug::cli::{list_cmd_serialized, show_cmd_serialized, ListFilter};
use component_events::events::*;
use component_events::matcher::*;
use fuchsia_component::client;
use serde_json::{from_value, Value};
use tracing::info;
use {
fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fcdecl,
fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys,
};
// CFv2 components will be launched in the collection with this name.
static LAUNCHED_COMPONENTS_COLLECTION_NAME: &'static str = "launched_components";
/// Perform operations related to Component.
///
/// Note this object is shared among all threads created by server.
///
#[derive(Debug)]
pub struct ComponentFacade {}
impl ComponentFacade {
pub fn new() -> ComponentFacade {
ComponentFacade {}
}
/// Launch component with url and optional arguments
/// # Arguments
/// * `args`: will be parsed to ComponentLaunchRequest in create_launch_app
/// with fields:
/// - `url`: url of the component (ending in `.cm`)
/// - `arguments`: optional arguments for the component (CFv1 only)
/// - `wait_until_stop`: if true, block until the component stops running
pub async fn launch(&self, args: Value) -> Result<ComponentLaunchResponse, Error> {
let tag = "ComponentFacade::create_launch_app";
let req: ComponentLaunchRequest = from_value(args)?;
// check if it's full url
let component_url = match req.url {
Some(x) => {
if !x.starts_with("fuchsia-pkg") {
return Err(format_err!("Need full component url to launch"));
}
info!(
"Executing Launch {} in Component Facade with arguments {:?}.",
x, req.arguments
);
x
}
None => return Err(format_err!("Need full component url to launch")),
};
if req.arguments.is_some() {
return Err(format_err!(
"CFv2 components currently don't support command line arguments"
));
}
self.launch_v2(tag, &component_url, req.wait_until_stop).await
}
/// Launch component with url
/// # Arguments
/// * `tag`: the sl4f command tag/name
/// * `url`: url of the component
/// * `wait_until_stop`: if true, block until the component stops running
async fn launch_v2(
&self,
tag: &str,
url: &str,
wait_until_stop: bool,
) -> Result<ComponentLaunchResponse, Error> {
let collection_name = LAUNCHED_COMPONENTS_COLLECTION_NAME;
let child_name =
if let (Some(last_dot), Some(last_slash)) = (url.rfind('.'), (url.rfind('/'))) {
&url[last_slash + 1..last_dot]
} else {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Component URL must end with a manifest file name: {url}")
)
};
// Subscribe to stopped events for child components and then
// wait for the component's `Stopped` event, and exit this command.
let mut event_stream = EventStream::open().await.unwrap();
let realm = client::connect_to_protocol::<fcomponent::RealmMarker>()?;
let collection_ref = fcdecl::CollectionRef { name: collection_name.to_string() };
let child_decl = fcdecl::Child {
name: Some(child_name.to_string()),
url: Some(url.to_string()),
startup: Some(fcdecl::StartupMode::Lazy), // Dynamic children can only be started lazily.
environment: None,
..Default::default()
};
let child_args =
fcomponent::CreateChildArgs { numbered_handles: None, ..Default::default() };
if let Err(err) = realm.create_child(&collection_ref, &child_decl, child_args).await? {
fx_err_and_bail!(&with_line!(tag), format_err!("Failed to create CFv2 child: {err:?}"));
}
let child_ref = fcdecl::ChildRef {
name: child_name.to_string(),
collection: Some(collection_name.to_string()),
};
let (exposed_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>()?;
if let Err(err) = realm.open_exposed_dir(&child_ref, server_end).await? {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Failed to open exposed directory for CFv2 child: {err:?}")
);
}
// Connect to the Binder protocol to start the component.
let _ = client::connect_to_protocol_at_dir_root::<fcomponent::BinderMarker>(&exposed_dir)?;
if wait_until_stop {
// Important! The `moniker_regex` must end with `$` to ensure the
// `EventMatcher` does not observe stopped events of child components of
// the launched component.
info!("Waiting for Stopped events for child {child_name}");
let stopped_event = EventMatcher::ok()
.moniker(format!("{LAUNCHED_COMPONENTS_COLLECTION_NAME}:{child_name}"))
.wait::<Stopped>(&mut event_stream)
.await
.context(format!("failed to observe {child_name} Stopped event"))?;
if let Err(err) = realm.destroy_child(&child_ref).await? {
fx_err_and_bail!(
&with_line!(tag),
format_err!("Failed to destroy CFv2 child: {err:?}")
);
}
let stopped_payload =
stopped_event.result().map_err(|err| anyhow!("StoppedError: {err:?}"))?;
info!("Returning {stopped_payload:?} event for child {child_name}");
match stopped_payload.status {
ExitStatus::Crash(status) => {
info!("Component terminated unexpectedly. Status: {status}");
Ok(ComponentLaunchResponse::Fail(status as i64))
}
ExitStatus::Clean => Ok(ComponentLaunchResponse::Success),
}
} else {
Ok(ComponentLaunchResponse::Success)
}
}
/// Search component with component's moniker
/// # Arguments
/// * `args`: will be parsed to ComponentSearchRequest
/// * `name`: name of the component (should be like "core/foo")
pub async fn search(&self, args: Value) -> Result<ComponentSearchResult, Error> {
let req: ComponentSearchRequest = from_value(args)?;
let name = match req.name {
Some(x) => {
info!("Searching Component {} in ComponentSearch Facade", x,);
x
}
None => return Err(format_err!("Need name of the component to search.")),
};
let query = client::connect_to_protocol::<fsys::RealmQueryMarker>()?;
match show_cmd_serialized(name.to_string(), query).await {
Ok(_) => Ok(ComponentSearchResult::Success),
Err(_) => Ok(ComponentSearchResult::NotFound),
}
}
/// List running components, returns a vector containing component full URL.
pub async fn list(&self) -> Result<Vec<String>, Error> {
info!("List running Component in ComponentSearch Facade",);
let query = client::connect_to_protocol::<fsys::RealmQueryMarker>()?;
let instances = list_cmd_serialized(Some(ListFilter::Running), query).await?;
let urls: Vec<String> = instances.into_iter().map(|i| i.url).collect();
Ok(urls)
}
}