blob: 6fab6def8aa8732ca5028f2c3e63dcfe59444bf0 [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,
anyhow::Result,
fuchsia_async::TimeoutExt,
futures::future::{join, join_all, BoxFuture},
futures::FutureExt,
moniker::{AbsoluteMoniker, AbsoluteMonikerBase, ChildMoniker, ChildMonikerBase},
};
static CAPABILITY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
/// Components that were found by |find_components|, separated into two vectors (one for components
/// that expose the capability, the other for components that use the capability).
pub struct MatchingComponents {
pub exposed: Vec<AbsoluteMoniker>,
pub used: Vec<AbsoluteMoniker>,
}
struct CapabilityExists {
exposed: bool,
used: bool,
}
/// Given a v2 hub directory, collect components that expose or use |capability|.
/// This function is recursive and will find matching CMX and CML components.
pub async fn find_components(capability: String, hub_dir: Directory) -> Result<MatchingComponents> {
find_components_internal(capability, String::new(), AbsoluteMoniker::root(), hub_dir).await
}
fn find_components_internal(
capability: String,
name: String,
moniker: AbsoluteMoniker,
hub_dir: Directory,
) -> BoxFuture<'static, Result<MatchingComponents>> {
async move {
let mut futures = vec![];
let children_dir = hub_dir.open_dir_readable("children")?;
for child_dir in children_dir.entries().await? {
let child_hub_dir = children_dir.open_dir_readable(&child_dir)?;
let child_name = child_hub_dir.read_file("moniker").await?;
let child_moniker = ChildMoniker::parse(&child_name)?;
let child_moniker = moniker.child(child_moniker);
let child_future = find_components_internal(
capability.clone(),
child_name,
child_moniker,
child_hub_dir,
);
futures.push(child_future);
}
if name == "appmgr" {
let realm_dir = hub_dir.open_dir_readable("exec/out/hub")?;
let appmgr_future = find_cmx_realms(capability.clone(), moniker.clone(), realm_dir);
futures.push(appmgr_future);
}
let results = join_all(futures).await;
let mut matching_components = MatchingComponents { exposed: vec![], used: vec![] };
for result in results {
let MatchingComponents { mut exposed, mut used } = result?;
matching_components.exposed.append(&mut exposed);
matching_components.used.append(&mut used);
}
let CapabilityExists { exposed, used } =
capability_is_exposed_or_used_v2(hub_dir, capability).await?;
if exposed {
matching_components.exposed.push(moniker.clone());
}
if used {
matching_components.used.push(moniker.clone());
}
Ok(matching_components)
}
.boxed()
}
// Given a v1 realm directory, return monikers of components that expose |capability|.
// |moniker| corresponds to the moniker of the current realm.
fn find_cmx_realms(
capability: String,
moniker: AbsoluteMoniker,
hub_dir: Directory,
) -> BoxFuture<'static, Result<MatchingComponents>> {
async move {
let c_dir = hub_dir.open_dir_readable("c")?;
let c_future = find_cmx_components_in_c_dir(capability.clone(), moniker.clone(), c_dir);
let r_dir = hub_dir.open_dir_readable("r")?;
let r_future = find_cmx_realms_in_r_dir(capability, moniker, r_dir);
let (matching_components_c, matching_components_r) = join(c_future, r_future).await;
let MatchingComponents { mut exposed, mut used } = matching_components_c?;
let mut matching_components_r = matching_components_r?;
exposed.append(&mut matching_components_r.exposed);
used.append(&mut matching_components_r.used);
Ok(MatchingComponents { exposed, used })
}
.boxed()
}
// Given a v1 component directory, return monikers of components that expose |capability|.
// |moniker| corresponds to the moniker of the current component.
fn find_cmx_components(
capability: String,
moniker: AbsoluteMoniker,
hub_dir: Directory,
) -> BoxFuture<'static, Result<MatchingComponents>> {
async move {
let mut matching_components_exposed = vec![];
let mut matching_components_used = vec![];
// Component runners can have a `c` dir with child components
if hub_dir.exists("c").await? {
let c_dir = hub_dir.open_dir_readable("c")?;
let MatchingComponents { mut exposed, mut used } =
find_cmx_components_in_c_dir(capability.clone(), moniker.clone(), c_dir).await?;
matching_components_exposed.append(&mut exposed);
matching_components_used.append(&mut used);
}
let CapabilityExists { exposed, used } =
capability_is_exposed_or_used_v1(hub_dir, capability).await?;
if exposed {
matching_components_exposed.push(moniker.clone());
}
if used {
matching_components_used.push(moniker.clone())
}
Ok(MatchingComponents {
exposed: matching_components_exposed,
used: matching_components_used,
})
}
.boxed()
}
async fn find_cmx_components_in_c_dir(
capability: String,
moniker: AbsoluteMoniker,
c_dir: Directory,
) -> Result<MatchingComponents> {
// Get all CMX child components
let child_component_names = c_dir.entries().await?;
let mut future_children = vec![];
for child_component_name in child_component_names {
let child_moniker = ChildMoniker::parse(&child_component_name)?;
let child_moniker = moniker.child(child_moniker);
let job_ids_dir = c_dir.open_dir_readable(&child_component_name)?;
let hub_dirs = open_all_job_ids(job_ids_dir).await?;
for hub_dir in hub_dirs {
let future_child =
find_cmx_components(capability.clone(), child_moniker.clone(), hub_dir);
future_children.push(future_child);
}
}
let results = join_all(future_children).await;
let mut flattened_components = MatchingComponents { exposed: vec![], used: vec![] };
for result in results {
let MatchingComponents { mut exposed, mut used } = result?;
flattened_components.exposed.append(&mut exposed);
flattened_components.used.append(&mut used);
}
Ok(flattened_components)
}
async fn find_cmx_realms_in_r_dir(
capability: String,
moniker: AbsoluteMoniker,
r_dir: Directory,
) -> Result<MatchingComponents> {
// Get all CMX child realms
let mut future_realms = vec![];
for child_realm_name in r_dir.entries().await? {
let child_moniker = ChildMoniker::parse(&child_realm_name)?;
let child_moniker = moniker.child(child_moniker);
let job_ids_dir = r_dir.open_dir_readable(&child_realm_name)?;
let hub_dirs = open_all_job_ids(job_ids_dir).await?;
for hub_dir in hub_dirs {
let future_realm = find_cmx_realms(capability.clone(), child_moniker.clone(), hub_dir);
future_realms.push(future_realm);
}
}
let results = join_all(future_realms).await;
let mut flattened_components = MatchingComponents { exposed: vec![], used: vec![] };
for result in results {
let MatchingComponents { mut exposed, mut used } = result?;
flattened_components.exposed.append(&mut exposed);
flattened_components.used.append(&mut used);
}
Ok(flattened_components)
}
async fn open_all_job_ids(job_ids_dir: Directory) -> Result<Vec<Directory>> {
// Recurse on the job_ids
let mut dirs = vec![];
for job_id in job_ids_dir.entries().await? {
let dir = job_ids_dir.open_dir_readable(&job_id)?;
dirs.push(dir);
}
Ok(dirs)
}
/// Determine if |capability| is exposed or used by this v2 component.
async fn capability_is_exposed_or_used_v2(
hub_dir: Directory,
capability: String,
) -> Result<CapabilityExists> {
if !hub_dir.exists("resolved").await? {
// We have no information about an unresolved component
return Ok(CapabilityExists { exposed: false, used: false });
}
let exec_dir = hub_dir.open_dir_readable("resolved/expose")?;
let expose_capabilities = get_capabilities(exec_dir).await?;
let use_dir = hub_dir.open_dir_readable("resolved/use")?;
let used_capabilities = get_capabilities(use_dir).await?;
Ok(CapabilityExists {
exposed: expose_capabilities.iter().any(|c| c.as_str() == capability),
used: used_capabilities.iter().any(|c| c.as_str() == capability),
})
}
/// Determine if |capability| is exposed or used by this v1 component.
async fn capability_is_exposed_or_used_v1(
hub_dir: Directory,
capability: String,
) -> Result<CapabilityExists> {
if !hub_dir.exists("out").await? {
// No `out` directory implies no exposed capabilities
return Ok(CapabilityExists { exposed: false, used: false });
}
let out_dir = hub_dir.open_dir_readable("out")?;
let out_capabilities =
get_capabilities(out_dir).on_timeout(CAPABILITY_TIMEOUT, || Ok(vec![])).await?;
let in_dir = hub_dir.open_dir_readable("in")?;
let in_capabilities =
get_capabilities(in_dir).on_timeout(CAPABILITY_TIMEOUT, || Ok(vec![])).await?;
Ok(CapabilityExists {
exposed: out_capabilities.iter().any(|c| c.as_str() == capability),
used: in_capabilities.iter().any(|c| c.as_str() == capability),
})
}
// Get all entries in a capabilities directory. If there is a "svc" directory, traverse it and
// collect all protocol names as well.
async fn get_capabilities(capability_dir: Directory) -> Result<Vec<String>> {
let mut entries = capability_dir.entries().await?;
for (index, name) in entries.iter().enumerate() {
if name == "svc" {
entries.remove(index);
let svc_dir = capability_dir.open_dir_readable("svc")?;
let mut svc_entries = svc_dir.entries().await?;
entries.append(&mut svc_entries);
break;
}
}
Ok(entries)
}
#[cfg(test)]
mod tests {
use super::*;
use {
std::fs::{self, File},
tempfile::TempDir,
};
#[fuchsia_async::run_singlethreaded(test)]
async fn unresolved_cml() {
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 hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { exposed, used } =
find_components("fuchsia.logger.LogSink".to_string(), hub_dir).await.unwrap();
assert!(exposed.is_empty());
assert!(used.is_empty());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cml_protocol_found() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- resolved
// |- expose
// |- svc
// |- fuchsia.logger.LogSink
// |- use
// |- svc
// |- fuchsia.logger.LogSink
fs::create_dir(root.join("children")).unwrap();
fs::create_dir_all(root.join("resolved/expose/svc")).unwrap();
File::create(root.join("resolved/expose/svc/fuchsia.logger.LogSink")).unwrap();
fs::create_dir_all(root.join("resolved/use/svc")).unwrap();
File::create(root.join("resolved/use/svc/fuchsia.logger.LogSink")).unwrap();
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { mut exposed, mut used } =
find_components("fuchsia.logger.LogSink".to_string(), hub_dir).await.unwrap();
assert_eq!(exposed.len(), 1);
assert_eq!(used.len(), 1);
let exposed_component = exposed.remove(0);
let used_component = used.remove(0);
assert!(exposed_component.is_root());
assert!(used_component.is_root());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cml_dir_found() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- resolved
// |- expose
// |- hub
// |- use
// |- hub
fs::create_dir(root.join("children")).unwrap();
fs::create_dir_all(root.join("resolved/expose/hub")).unwrap();
fs::create_dir_all(root.join("resolved/use/hub")).unwrap();
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { mut exposed, mut used } =
find_components("hub".to_string(), hub_dir).await.unwrap();
assert_eq!(exposed.len(), 1);
assert_eq!(used.len(), 1);
let exposed_component = exposed.remove(0);
let used_component = used.remove(0);
assert!(exposed_component.is_root());
assert!(used_component.is_root());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn nested_cml() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- core
// |- children
// |- resolved
// |- expose
// |- use
// |- minfs
// |- resolved
// |- expose
// |- minfs
// |- use
fs::create_dir(root.join("children")).unwrap();
fs::create_dir_all(root.join("resolved/expose/minfs")).unwrap();
fs::create_dir_all(root.join("resolved/use")).unwrap();
{
let core = root.join("children/core");
fs::create_dir_all(core.join("children")).unwrap();
fs::create_dir_all(core.join("resolved/expose")).unwrap();
fs::create_dir_all(core.join("resolved/use/minfs")).unwrap();
fs::write(core.join("moniker"), "core").unwrap();
}
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { mut exposed, mut used } =
find_components("minfs".to_string(), hub_dir).await.unwrap();
assert_eq!(exposed.len(), 1);
assert_eq!(used.len(), 1);
let exposed_component = exposed.remove(0);
let used_component = used.remove(0);
assert!(exposed_component.is_root());
assert_eq!(used_component, vec!["core"].into());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cmx() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- appmgr
// |- children
// |- exec
// |- out
// |- hub
// |- r
// |- c
// |- sshd.cmx
// |- 9898
// |- in
// |- dev
// |- out
// |- dev
fs::create_dir(root.join("children")).unwrap();
{
let appmgr = root.join("children/appmgr");
fs::create_dir(&appmgr).unwrap();
fs::create_dir(appmgr.join("children")).unwrap();
fs::create_dir_all(appmgr.join("exec/out/hub/r")).unwrap();
fs::write(appmgr.join("moniker"), "appmgr").unwrap();
{
let sshd = appmgr.join("exec/out/hub/c/sshd.cmx/9898");
fs::create_dir_all(&sshd).unwrap();
fs::create_dir_all(sshd.join("in/dev")).unwrap();
fs::create_dir_all(sshd.join("out/dev")).unwrap();
}
}
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { mut exposed, mut used } =
find_components("dev".to_string(), hub_dir).await.unwrap();
assert_eq!(exposed.len(), 1);
assert_eq!(used.len(), 1);
let exposed_component = exposed.remove(0);
let used_component = used.remove(0);
assert_eq!(exposed_component, vec!["appmgr", "sshd.cmx"].into());
assert_eq!(used_component, vec!["appmgr", "sshd.cmx"].into());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn runner_cmx() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- appmgr
// |- children
// |- exec
// |- out
// |- hub
// |- r
// |- c
// |- sshd.cmx
// |- 9898
// |- c
// |- foo.cmx
// |- 1919
// |- in
// |- dev
// |- out
// |- dev
fs::create_dir(root.join("children")).unwrap();
{
let appmgr = root.join("children/appmgr");
fs::create_dir(&appmgr).unwrap();
fs::create_dir(appmgr.join("children")).unwrap();
fs::create_dir_all(appmgr.join("exec/out/hub/r")).unwrap();
fs::write(appmgr.join("moniker"), "appmgr").unwrap();
{
let sshd = appmgr.join("exec/out/hub/c/sshd.cmx/9898");
fs::create_dir_all(&sshd).unwrap();
{
let dev = sshd.join("c/foo.cmx/1919/in/dev");
fs::create_dir_all(&dev).unwrap();
let dev = sshd.join("c/foo.cmx/1919/out/dev");
fs::create_dir_all(&dev).unwrap();
}
}
}
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { mut exposed, mut used } =
find_components("dev".to_string(), hub_dir).await.unwrap();
assert_eq!(exposed.len(), 1);
assert_eq!(used.len(), 1);
let exposed_component = exposed.remove(0);
let used_component = used.remove(0);
assert_eq!(exposed_component, vec!["appmgr", "sshd.cmx", "foo.cmx"].into());
assert_eq!(used_component, vec!["appmgr", "sshd.cmx", "foo.cmx"].into());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn cmx_no_out() {
let test_dir = TempDir::new_in("/tmp").unwrap();
let root = test_dir.path();
// Create the following structure
// .
// |- children
// |- appmgr
// |- children
// |- exec
// |- out
// |- hub
// |- r
// |- c
// |- sshd.cmx
// |- 9898
fs::create_dir(root.join("children")).unwrap();
{
let appmgr = root.join("children/appmgr");
fs::create_dir(&appmgr).unwrap();
fs::create_dir(appmgr.join("children")).unwrap();
fs::create_dir_all(appmgr.join("exec/out/hub/r")).unwrap();
fs::write(appmgr.join("moniker"), "appmgr").unwrap();
{
let sshd = appmgr.join("exec/out/hub/c/sshd.cmx/9898");
fs::create_dir_all(&sshd).unwrap();
}
}
let hub_dir = Directory::from_namespace(root.to_path_buf()).unwrap();
let MatchingComponents { exposed, used } =
find_components("dev".to_string(), hub_dir).await.unwrap();
assert!(exposed.is_empty());
assert!(used.is_empty());
}
}