blob: ca7deacd2eba1ea67b2c54dbb5ac61003b8e8045 [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::io::Directory,
crate::{get_capabilities, get_capabilities_timeout, Only, Subcommand, WIDTH_CS_TREE},
futures::future::{join_all, BoxFuture, FutureExt},
std::sync::Arc,
};
static SPACER: &str = " ";
static UNKNOWN: &str = "UNKNOWN";
/// Used as a helper function to traverse <realm id>/r/, <realm id>/c/,
/// <component instance id>/c/, following through into their id subdirectories.
async fn open_id_directories(id_dir: Directory) -> Vec<Directory> {
let mut ids = id_dir.entries().await.expect("Could not get entries from directory.");
let proxy = Arc::new(id_dir);
join_all(ids.drain(..).map(move |id| {
let proxy = proxy.clone();
async move {
assert!(id.chars().all(char::is_numeric));
let realm = proxy.open_dir(&id).expect(
format!("open_dir() failed: failed to open realm directory with id `{}`!", &id)
.as_str(),
);
realm
}
}))
.await
}
fn visit_child_realms(
child_realms_dir: Directory,
subcommand: Subcommand,
) -> BoxFuture<'static, Vec<V1Realm>> {
async move {
let mut realm_names =
child_realms_dir.entries().await.expect("Could not get entries from directory.");
let proxy = Arc::new(child_realms_dir);
// visit all entries within <realm id>/r/
join_all(realm_names.drain(..).map(move |realm_name| {
let proxy = proxy.clone();
async move {
// visit <realm id>/r/<child realm name>/<child realm id>/
let id_dir = proxy.open_dir(&realm_name).expect(
format!(
"open_dir() failed: failed to open child realm directory with id `{}`!",
&realm_name
)
.as_str(),
);
join_all(
open_id_directories(id_dir)
.await
.drain(..)
.map(|d| V1Realm::create(d, subcommand.clone())),
)
.await
}
}))
.await
.drain(..)
.flatten()
.collect()
}
.boxed()
}
/// Traverses a directory of named components, and recurses into each component directory.
/// Each component visited is added to the |child_components| vector.
fn visit_child_components(
child_components_dir: Directory,
subcommand: Subcommand,
) -> BoxFuture<'static, Vec<V1Component>> {
async move {
let mut component_names =
child_components_dir.entries().await.expect("Could not get entries from directory.");
let proxy = Arc::new(child_components_dir);
join_all(component_names.drain(..).map(move |component_name| {
let proxy = proxy.clone();
async move {
// Visits */c/<component name>/<component instance id>.
let id_dir = proxy.open_dir(&component_name).expect(
format!("open_dir() failed: failed to open `{}` directory!", &component_name)
.as_str(),
);
join_all(
open_id_directories(id_dir)
.await
.drain(..)
.map(|d| V1Component::create(d, subcommand.clone())),
)
.await
}
}))
.await
.drain(..)
.flatten()
.collect()
}
.boxed()
}
#[derive(Debug, Eq, PartialEq)]
pub struct V1Realm {
pub name: String,
pub job_id: u32,
pub child_realms: Vec<V1Realm>,
pub child_components: Vec<V1Component>,
}
impl V1Realm {
pub async fn create(realm_dir: Directory, subcommand: Subcommand) -> V1Realm {
let child_realms_dir = realm_dir.open_dir("r").expect("open_dir(`r`) failed!");
let child_components_dir = realm_dir.open_dir("c").expect("open_dir(`c`) failed!");
let (name, job_id, child_realms, child_components) = futures::join!(
realm_dir.read_file("name"),
realm_dir.read_file("job-id"),
visit_child_realms(child_realms_dir, subcommand.clone()),
visit_child_components(child_components_dir, subcommand.clone()),
);
let name = name.expect("read_file(`name`) failed!");
let job_id = job_id
.expect("read_file(`job-id`) failed!")
.parse::<u32>()
.expect("parse(`job-id`) failed!");
V1Realm { name, job_id, child_realms, child_components }
}
pub fn print_tree_recursive(&self, level: usize, only: Only, verbose: bool) {
let space = SPACER.repeat(level - 1);
if only == Only::CMX || only == Only::Running || only == Only::All {
if verbose {
println!(
"{:<width_type$}{:<width_running_stopped$}{}{}",
"Realm",
"Running",
space,
self.name,
width_type = WIDTH_CS_TREE,
width_running_stopped = WIDTH_CS_TREE
);
} else {
println!("{}{}", space, self.name);
}
}
for child in &self.child_components {
child.print_tree_recursive(level + 1, only, verbose);
}
for child in &self.child_realms {
child.print_tree_recursive(level + 1, only, verbose);
}
}
pub fn print_details_recursive(&self, moniker_prefix: &str, filter: &str) -> bool {
let mut did_print = false;
let moniker = format!("{}{}", moniker_prefix, self.name);
// Print information about realms if there is no filter
if filter.is_empty() || self.name.contains(filter) {
println!("Moniker: {}", moniker);
println!("Job ID: {}", self.job_id);
println!("Type: v1 realm");
println!("Execution State: Running");
println!("");
did_print = true;
}
// Recurse on child components
let moniker_prefix = format!("{}/", moniker);
for child in &self.child_components {
did_print |= child.print_details_recursive(&moniker_prefix, filter);
}
// Recurse on child realms
for child in &self.child_realms {
did_print |= child.print_details_recursive(&moniker_prefix, filter);
}
did_print
}
pub fn get_process_id_recursive(&self, url: &str, pids: &mut Vec<u32>) -> Vec<u32> {
let mut process_ids = vec![];
for child in &self.child_components {
let tmp_pids = child.get_process_id_recursive(url, pids);
process_ids.extend(tmp_pids);
}
process_ids
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct V1Component {
pub name: String,
pub child_components: Vec<V1Component>,
pub details: Option<Details>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct Details {
pub job_id: u32,
pub process_id: Option<u32>,
pub url: String,
pub merkle_root: Option<String>,
pub incoming_capabilities: Vec<String>,
pub outgoing_capabilities: Option<Vec<String>>,
}
impl V1Component {
async fn create(component_dir: Directory, subcommand: Subcommand) -> V1Component {
let name = component_dir.read_file("name").await.expect("read_file(`name`) failed!");
let child_components = if component_dir.exists("c").await {
let child_components_dir = component_dir.open_dir("c").expect("open_dir(`c`) failed!");
visit_child_components(child_components_dir, subcommand.clone()).await
} else {
vec![]
};
let details = if subcommand == Subcommand::Show {
let (job_id, url) =
futures::join!(component_dir.read_file("job-id"), component_dir.read_file("url"),);
let job_id = job_id
.expect("read_file(`job-id`) failed!")
.parse::<u32>()
.expect("parse(`job-id`) failed!");
let url = url.expect("read_file(`url`) failed!");
let process_id = if component_dir.exists("process-id").await {
Some(
component_dir
.read_file("process-id")
.await
.expect("read_file(`process-id`) failed!")
.parse::<u32>()
.expect("parse(`process-id`) failed!"),
)
} else {
None
};
let in_dir = component_dir.open_dir("in").expect("open_dir(`in`) failed!");
let merkle_root = if in_dir.exists("pkg").await {
let pkg_dir = in_dir.open_dir("pkg").expect("open_dir(`pkg`) failed!");
if pkg_dir.exists("meta").await {
match pkg_dir.read_file("meta").await {
Ok(file) => Some(file),
Err(_) => None,
}
} else {
None
}
} else {
None
};
let incoming_capabilities = get_capabilities(in_dir).await;
let outgoing_capabilities = if component_dir.exists("out").await {
get_capabilities_timeout(
component_dir.open_dir("out").expect("open_dir(`out`) failed!"),
)
.await
.ok()
} else {
// The directory doesn't exist. This is probably because
// there is no runtime on the component.
None
};
Some(Details {
job_id,
process_id,
url,
merkle_root,
incoming_capabilities,
outgoing_capabilities,
})
} else {
None
};
V1Component { name, child_components, details }
}
fn print_tree_recursive(&self, level: usize, only: Only, verbose: bool) {
let space = SPACER.repeat(level - 1);
if only == Only::CMX || only == Only::Running || only == Only::All {
if verbose {
println!(
"{:<width_type$}{:<width_running_stopped$}{}{}",
"CMX",
"Running",
space,
self.name,
width_type = WIDTH_CS_TREE,
width_running_stopped = WIDTH_CS_TREE
);
} else {
println!("{}{}", space, self.name);
}
}
for child in &self.child_components {
child.print_tree_recursive(level + 1, only, verbose);
}
}
fn print_details_recursive(&self, moniker_prefix: &str, filter: &str) -> bool {
let mut did_print = false;
let moniker = format!("{}{}", moniker_prefix, self.name);
if let Some(details) = &self.details {
let unknown_merkle = UNKNOWN.to_string();
let merkle = details.merkle_root.as_ref().unwrap_or(&unknown_merkle);
if filter.is_empty() || details.url.contains(filter) || self.name.contains(filter) {
println!("Moniker: {}", moniker);
println!("URL: {}", details.url);
println!("Job ID: {}", details.job_id);
if let Some(process_id) = details.process_id {
println!("Process ID: {}", process_id);
}
println!("Merkle Root: {}", merkle);
println!("Type: v1 component");
println!("Execution State: Running");
println!("Incoming Capabilities ({}):", details.incoming_capabilities.len());
for capability in &details.incoming_capabilities {
println!("{}{}", SPACER, capability);
}
if let Some(outgoing_capabilities) = &details.outgoing_capabilities {
println!("Outgoing Capabilities ({}):", outgoing_capabilities.len());
for capability in outgoing_capabilities {
println!("{}{}", SPACER, capability);
}
}
println!("");
did_print = true;
}
} else {
eprintln!("Error! `V1Component` doesn't have `Details`!");
}
// Recurse on children
let moniker_prefix = format!("{}/", moniker);
for child in &self.child_components {
did_print |= child.print_details_recursive(&moniker_prefix, filter);
}
did_print
}
pub fn get_process_id_recursive(&self, url: &str, pids: &mut Vec<u32>) -> Vec<u32> {
let mut process_ids = vec![];
if let Some(details) = &self.details {
if details.url == url {
if let Some(process_id) = details.process_id {
process_ids.push(process_id);
}
}
}
for child in &self.child_components {
let tmp_pids = child.get_process_id_recursive(url, pids);
process_ids.extend(tmp_pids);
}
process_ids
}
}
#[cfg(test)]
mod tests {
use super::*;
use {
std::fs::{self, File},
std::io::Write,
tempfile::TempDir,
};
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_loads_name() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap().write_all("cobalt.cmx".as_bytes()).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url"))
.unwrap()
.write_all("fuchsia-pkg://fuchsia.com/cobalt#meta/cobalt.cmx".as_bytes())
.unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::List).await;
assert_eq!(v1_component.details, None);
assert_eq!(v1_component.name, "cobalt.cmx");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_loads_child_components() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- c
// |- cobalt.cmx
// |- 13579
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
fs::create_dir(root.join("c")).unwrap();
fs::create_dir(root.join("c/cobalt.cmx")).unwrap();
let child_dir_name = root.join("c/cobalt.cmx/13579");
fs::create_dir(&child_dir_name).unwrap();
fs::create_dir(child_dir_name.join("in")).unwrap();
File::create(child_dir_name.join("job-id")).unwrap().write_all("54321".as_bytes()).unwrap();
File::create(child_dir_name.join("name")).unwrap();
File::create(child_dir_name.join("process-id"))
.unwrap()
.write_all("09876".as_bytes())
.unwrap();
File::create(child_dir_name.join("url")).unwrap();
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::List).await;
let child_dir = Directory::from_namespace(child_dir_name)
.expect("from_namespace() failed: failed to open child_dir v1 hub directory!");
assert_eq!(
v1_component.child_components,
vec![V1Component::create(child_dir, Subcommand::List).await]
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_with_details_loads_job_id_and_name_and_process_id_and_url() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap().write_all("cobalt.cmx".as_bytes()).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url"))
.unwrap()
.write_all("fuchsia-pkg://fuchsia.com/cobalt#meta/cobalt.cmx".as_bytes())
.unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::Show).await;
assert_eq!(
v1_component.details,
Some(Details {
job_id: 12345,
process_id: Some(67890),
url: "fuchsia-pkg://fuchsia.com/cobalt#meta/cobalt.cmx".to_string(),
merkle_root: None,
incoming_capabilities: vec![],
outgoing_capabilities: None,
})
);
assert_eq!(v1_component.name, "cobalt.cmx");
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_with_details_loads_missing_process_id() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- in
// |- job-id
// |- name
// |- url
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap().write_all("cobalt.cmx".as_bytes()).unwrap();
File::create(root.join("url"))
.unwrap()
.write_all("fuchsia-pkg://fuchsia.com/cobalt#meta/cobalt.cmx".as_bytes())
.unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::Show).await;
assert_eq!(v1_component.details.unwrap().process_id, None);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_with_details_loads_merkle_root() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- in
// |- pkg
// |- meta
// |- job-id
// |- name
// |- process-id
// |- url
fs::create_dir(root.join("in")).unwrap();
fs::create_dir(root.join("in/pkg")).unwrap();
File::create(root.join("in/pkg/meta"))
.unwrap()
.write_all(
"eb4c673a880a232cc05363ff27691107c89d5b2766d995f782dac1056ecfe8c9".as_bytes(),
)
.unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::Show).await;
assert_eq!(
v1_component.details.unwrap().merkle_root,
Some("eb4c673a880a232cc05363ff27691107c89d5b2766d995f782dac1056ecfe8c9".to_string())
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_with_details_loads_incoming_capabilities_and_outgoing_capabilities() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- in
// |- fuchsia.logger.logSink
// |- job-id
// |- name
// |- out
// |- fuchsia.cobalt.Controller
// |- process-id
// |- url
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("in/fuchsia.logger.logSink")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap();
fs::create_dir(root.join("out")).unwrap();
File::create(root.join("out/fuchsia.cobalt.Controller")).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::Show).await;
assert_eq!(
v1_component.details,
Some(Details {
job_id: 12345,
process_id: Some(67890),
url: "".to_string(),
merkle_root: None,
incoming_capabilities: vec!["fuchsia.logger.logSink".to_string()],
outgoing_capabilities: Some(vec!["fuchsia.cobalt.Controller".to_string()],)
})
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn v1_component_with_details_loads_child_components() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- c
// |- cobalt.cmx
// |- 13579
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
// |- in
// |- job-id
// |- name
// |- process-id
// |- url
fs::create_dir(root.join("c")).unwrap();
fs::create_dir(root.join("c/cobalt.cmx")).unwrap();
let child_dir_name = root.join("c/cobalt.cmx/13579");
fs::create_dir(&child_dir_name).unwrap();
fs::create_dir(child_dir_name.join("in")).unwrap();
File::create(child_dir_name.join("job-id")).unwrap().write_all("54321".as_bytes()).unwrap();
File::create(child_dir_name.join("name")).unwrap();
File::create(child_dir_name.join("process-id"))
.unwrap()
.write_all("09876".as_bytes())
.unwrap();
File::create(child_dir_name.join("url")).unwrap();
fs::create_dir(root.join("in")).unwrap();
File::create(root.join("job-id")).unwrap().write_all("12345".as_bytes()).unwrap();
File::create(root.join("name")).unwrap();
File::create(root.join("process-id")).unwrap().write_all("67890".as_bytes()).unwrap();
File::create(root.join("url")).unwrap();
let root_dir = Directory::from_namespace(root.to_path_buf())
.expect("from_namespace() failed: failed to open root v1 hub directory!");
let v1_component = V1Component::create(root_dir, Subcommand::Show).await;
let child_dir = Directory::from_namespace(child_dir_name)
.expect("from_namespace() failed: failed to open child_dir v1 hub directory!");
assert_eq!(
v1_component.child_components,
vec![V1Component::create(child_dir, Subcommand::Show).await]
);
}
}