blob: 8527a96dbedeaac7ed3e5a8944894418351f1f47 [file]
// 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::io::Directory,
crate::Only,
futures::future::{join_all, BoxFuture, FutureExt},
};
static SPACER: &str = " ";
static WIDTH_CS_TREE: usize = 19;
/// Basic information about a component for the `list` command.
pub struct Component {
// Name of the component. This gets printed out.
pub name: String,
// True if component is of appmgr/CMX type.
// False if it is of the component_manager/CML type.
pub is_cmx: bool,
// CML components may not always be running.
// Always true for CMX components.
pub is_running: bool,
// Children of this component.
pub children: Vec<Component>,
}
impl Component {
pub fn parse(name: String, hub_dir: Directory) -> BoxFuture<'static, Component> {
async move {
let is_running = hub_dir.exists("exec").await;
// Recurse on the CML children
let mut future_children = vec![];
let children_dir = hub_dir
.open_dir("children")
.expect(&format!("Could not open `children` dir for {}", name));
for child_name in children_dir
.entries()
.await
.expect(&format!("Could not get entries of `children` dir for {}", name))
{
let hub_dir = children_dir
.open_dir(&child_name)
.expect(&format!("Could not open child hub {} of parent {}", child_name, name));
let future_child = Component::parse(child_name, hub_dir);
future_children.push(future_child);
}
let mut children = join_all(future_children).await;
if name == "appmgr" {
// Get all CMX components + realms
let realm_dir =
hub_dir.open_dir("exec/out/hub").expect("Could not open appmgr hub");
let component = Component::parse_cmx("".to_string(), realm_dir).await;
children.extend(component.children);
}
Component { name, children, is_cmx: false, is_running }
}
.boxed()
}
fn parse_cmx(realm_name: String, realm_dir: Directory) -> BoxFuture<'static, Component> {
async move {
// Get all CMX child components
let children_dir = realm_dir
.open_dir("c")
.expect(&format!("Could not open component dir for realm {}", realm_name));
let child_component_names = children_dir.entries().await.expect(&format!(
"Could not get entries in component dir for realm {}",
realm_name
));
let mut children = vec![];
for child_component_name in child_component_names {
// CMX components are leaf nodes (no children).
// CMX components are always running if they exist in tree.
children.push(Component {
name: child_component_name,
is_cmx: true,
is_running: true,
children: vec![],
})
}
// Recurse on the CMX child realms
let mut future_realm_lists = vec![];
let child_realms_dir = realm_dir
.open_dir("r")
.expect(&format!("Could not open realm dir for realm {}", realm_name));
for child_realm_name in child_realms_dir
.entries()
.await
.expect(&format!("Could not get entries of realm dir for realm {}", realm_name))
{
let job_ids_dir = child_realms_dir.open_dir(&child_realm_name).expect(&format!(
"Could not open child realm hub {} of parent realm {}",
child_realm_name, realm_name
));
let future_realm_list = Component::parse_cmx_job_ids(child_realm_name, job_ids_dir);
future_realm_lists.push(future_realm_list);
}
let realm_lists: Vec<Vec<Component>> = join_all(future_realm_lists).await;
let realms: Vec<Component> = realm_lists.into_iter().flatten().collect();
children.extend(realms);
// Consider a realm as a CMX component that has children.
// This is not technically true, but works as a generalization.
Component { name: realm_name, children, is_cmx: true, is_running: true }
}
.boxed()
}
fn parse_cmx_job_ids(
realm_name: String,
job_ids_dir: Directory,
) -> BoxFuture<'static, Vec<Component>> {
async move {
// Recurse on the job_ids
let mut future_realms = vec![];
for job_id in job_ids_dir
.entries()
.await
.expect(&format!("Could not get entries of job id dir for realm {}", realm_name))
{
let realm_dir = job_ids_dir.open_dir(&job_id).expect(&format!(
"Could not open job id dir {} for realm {}",
job_id, realm_name
));
let future_realm = Component::parse_cmx(realm_name.clone(), realm_dir);
future_realms.push(future_realm)
}
join_all(future_realms).await
}
.boxed()
}
fn should_print(&self, only: &Only) -> bool {
if only == &Only::CML && self.is_cmx {
return false;
} else if only == &Only::CMX && !self.is_cmx {
return false;
}
if only == &Only::Running && !self.is_running {
return false;
} else if only == &Only::Stopped && self.is_running {
return false;
}
return true;
}
pub fn print(&self, only: &Only, verbose: bool, indent: usize) {
let space = SPACER.repeat(indent);
if self.should_print(&only) {
if verbose {
let component_type = if self.is_cmx { "CMX" } else { "CML" };
let state = if self.is_running { "Running" } else { "Stopped" };
println!(
"{:<width_type$}{:<width_state$}{}{}",
component_type,
state,
space,
self.name,
width_type = WIDTH_CS_TREE,
width_state = WIDTH_CS_TREE
);
} else {
println!("{}{}", space, self.name);
}
}
for child in &self.children {
child.print(only, verbose, indent + 1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use {std::fs, tempfile::TempDir};
#[fuchsia_async::run_singlethreaded(test)]
async fn no_children() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
fs::create_dir(root.join("children")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let component = Component::parse(".".to_string(), root_dir).await;
assert!(component.children.is_empty());
assert!(!component.is_cmx);
assert!(!component.is_running);
assert_eq!(component.name, ".");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn running() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- exec
fs::create_dir(root.join("children")).unwrap();
fs::create_dir(root.join("exec")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let component = Component::parse(".".to_string(), root_dir).await;
assert!(!component.is_cmx);
assert!(component.is_running);
assert_eq!(component.name, ".");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn single_cmx_child() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// appmgr
// |- children
// |- exec
// |- out
// |- hub
// |- c
// |- foo.cmx
fs::create_dir(root.join("children")).unwrap();
fs::create_dir_all(root.join("exec/out/hub/c/foo.cmx")).unwrap();
fs::create_dir_all(root.join("exec/out/hub/r")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let component = Component::parse("appmgr".to_string(), root_dir).await;
assert!(!component.is_cmx);
assert!(component.is_running);
assert_eq!(component.name, "appmgr");
assert_eq!(component.children.len(), 1);
let child = component.children.get(0).unwrap();
assert_eq!(child.name, "foo.cmx");
assert!(child.is_running);
assert!(child.is_cmx);
assert!(child.children.is_empty());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn single_cml_child() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- test
// |- children
fs::create_dir_all(root.join("children/foo/children")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let component = Component::parse(".".to_string(), root_dir).await;
assert!(!component.is_cmx);
assert!(!component.is_running);
assert_eq!(component.name, ".");
assert_eq!(component.children.len(), 1);
let child = component.children.get(0).unwrap();
assert_eq!(child.name, "foo");
assert!(!child.is_running);
assert!(!child.is_cmx);
assert!(child.children.is_empty());
}
}