blob: 63705d6a6484b0f725b0ccff443cec19ef9876a7 [file] [log] [blame]
// Copyright 2019 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::{addable_directory::AddableDirectory, error::ModelError, realm::WeakRealm},
cm_rust::{CapabilityPath, ComponentDecl, ExposeDecl, UseDecl},
directory_broker::{DirectoryBroker, RoutingFn},
moniker::AbsoluteMoniker,
std::collections::HashMap,
vfs::directory::immutable::simple as pfs,
};
/// Represents the directory hierarchy of the exposed directory, not including the nodes for the
/// capabilities themselves.
pub(super) struct DirTree {
directory_nodes: HashMap<String, Box<DirTree>>,
broker_nodes: HashMap<String, RoutingFn>,
}
impl DirTree {
/// Builds a directory hierarchy from a component's `uses` declarations.
/// `routing_factory` is a closure that generates the routing function that will be called
/// when a leaf node is opened.
pub fn build_from_uses(
routing_factory: impl Fn(WeakRealm, UseDecl) -> RoutingFn,
realm: WeakRealm,
decl: ComponentDecl,
) -> Self {
let mut tree = DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() };
for use_ in decl.uses {
tree.add_use_capability(&routing_factory, realm.clone(), &use_);
}
tree
}
/// Builds a directory hierarchy from a component's `exposes` declarations.
/// `routing_factory` is a closure that generates the routing function that will be called
/// when a leaf node is opened.
pub fn build_from_exposes(
routing_factory: impl Fn(WeakRealm, ExposeDecl) -> RoutingFn,
realm: WeakRealm,
decl: ComponentDecl,
) -> Self {
let mut tree = DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() };
for expose in decl.exposes {
tree.add_expose_capability(&routing_factory, realm.clone(), &expose);
}
tree
}
/// Installs the directory tree into `root_dir`.
pub fn install<'entries>(
self,
abs_moniker: &AbsoluteMoniker,
root_dir: &mut impl AddableDirectory,
) -> Result<(), ModelError> {
for (name, subtree) in self.directory_nodes {
let mut subdir = pfs::simple();
subtree.install(abs_moniker, &mut subdir)?;
root_dir.add_node(&name, subdir, abs_moniker)?;
}
for (name, route_fn) in self.broker_nodes {
let node = DirectoryBroker::new(route_fn);
root_dir.add_node(&name, node, abs_moniker)?;
}
Ok(())
}
fn add_use_capability(
&mut self,
routing_factory: &impl Fn(WeakRealm, UseDecl) -> RoutingFn,
realm: WeakRealm,
use_: &UseDecl,
) {
// Event, EventStream and Runner capabilities are used by the framework
// itself and not given to components directly.
match use_ {
cm_rust::UseDecl::Runner(_)
| cm_rust::UseDecl::Event(_)
| cm_rust::UseDecl::EventStream(_) => return,
_ => {}
}
let path = match use_.path() {
Some(path) => path,
None => return,
};
let tree = self.to_directory_node(path);
let routing_fn = routing_factory(realm, use_.clone());
tree.broker_nodes.insert(path.basename.to_string(), routing_fn);
}
fn add_expose_capability(
&mut self,
routing_factory: &impl Fn(WeakRealm, ExposeDecl) -> RoutingFn,
realm: WeakRealm,
expose: &ExposeDecl,
) {
let path = match expose {
cm_rust::ExposeDecl::Service(d) => {
format!("/{}", d.target_name).parse().expect("couldn't parse name as path")
}
cm_rust::ExposeDecl::Protocol(d) => {
format!("/{}", d.target_name).parse().expect("couldn't parse name as path")
}
cm_rust::ExposeDecl::Directory(d) => {
format!("/{}", d.target_name).parse().expect("couldn't parse name as path")
}
cm_rust::ExposeDecl::Runner(_) | cm_rust::ExposeDecl::Resolver(_) => {
// Runners and resolvers do not add directory entries.
return;
}
};
let tree = self.to_directory_node(&path);
let routing_fn = routing_factory(realm, expose.clone());
tree.broker_nodes.insert(path.basename.to_string(), routing_fn);
}
fn to_directory_node(&mut self, path: &CapabilityPath) -> &mut DirTree {
let components = path.dirname.split("/");
let mut tree = self;
for component in components {
if !component.is_empty() {
tree = tree.directory_nodes.entry(component.to_string()).or_insert(Box::new(
DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() },
));
}
}
tree
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::{
environment::Environment,
realm::Realm,
testing::{mocks, test_helpers, test_helpers::*},
},
cm_rust::{
CapabilityName, CapabilityPath, ExposeDecl, ExposeDirectoryDecl, ExposeProtocolDecl,
ExposeRunnerDecl, ExposeSource, ExposeTarget, UseDecl, UseDirectoryDecl,
UseProtocolDecl, UseRunnerDecl, UseSource, UseStorageDecl,
},
fidl::endpoints::{ClientEnd, ServerEnd},
fidl_fuchsia_io::MODE_TYPE_DIRECTORY,
fidl_fuchsia_io::{DirectoryMarker, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE},
fidl_fuchsia_io2 as fio2, fuchsia_zircon as zx,
std::{
convert::{TryFrom, TryInto},
sync::{Arc, Weak},
},
vfs::{directory::entry::DirectoryEntry, execution_scope::ExecutionScope, path},
};
#[fuchsia_async::run_singlethreaded(test)]
async fn build_from_uses() {
// Call `build_from_uses` with a routing factory that routes to a mock directory or service,
// and a `ComponentDecl` with `use` declarations.
let routing_factory = mocks::proxy_use_routing_factory();
let decl = ComponentDecl {
uses: vec![
UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "baz-dir".into(),
target_path: CapabilityPath::try_from("/in/data/hippo").unwrap(),
rights: fio2::Operations::Connect,
subdir: None,
}),
UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "baz-svc".into(),
target_path: CapabilityPath::try_from("/in/svc/hippo").unwrap(),
}),
UseDecl::Storage(UseStorageDecl {
source_name: "data".into(),
target_path: "/in/data/persistent".try_into().unwrap(),
}),
UseDecl::Storage(UseStorageDecl {
source_name: "cache".into(),
target_path: "/in/data/cache".try_into().unwrap(),
}),
UseDecl::Runner(UseRunnerDecl { source_name: CapabilityName::from("elf") }),
],
..default_component_decl()
};
let root_realm = Arc::new(Realm::new_root_realm(
Environment::empty(),
Weak::new(),
Weak::new(),
"test://root".to_string(),
));
let tree = DirTree::build_from_uses(routing_factory, root_realm.as_weak(), decl.clone());
// Convert the tree to a directory.
let mut in_dir = pfs::simple();
tree.install(&root_realm.abs_moniker, &mut in_dir)
.expect("Unable to build pseudodirectory");
let (in_dir_client, in_dir_server) = zx::Channel::create().unwrap();
in_dir.open(
ExecutionScope::new(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_DIRECTORY,
path::Path::empty(),
ServerEnd::<NodeMarker>::new(in_dir_server.into()),
);
let in_dir_proxy = ClientEnd::<DirectoryMarker>::new(in_dir_client)
.into_proxy()
.expect("failed to create directory proxy");
assert_eq!(
vec!["in/data/cache", "in/data/hippo", "in/data/persistent", "in/svc/hippo"],
test_helpers::list_directory_recursive(&in_dir_proxy).await
);
// Expect that calls on the directory nodes reach the mock directory/service.
assert_eq!("friend", test_helpers::read_file(&in_dir_proxy, "in/data/hippo/hello").await);
assert_eq!(
"friend",
test_helpers::read_file(&in_dir_proxy, "in/data/persistent/hello").await
);
assert_eq!("friend", test_helpers::read_file(&in_dir_proxy, "in/data/cache/hello").await);
assert_eq!(
"hippos".to_string(),
test_helpers::call_echo(&in_dir_proxy, "in/svc/hippo").await
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn build_from_exposes() {
// Call `build_from_exposes` with a routing factory that routes to a mock directory or
// service, and a `ComponentDecl` with `expose` declarations.
let routing_factory = mocks::proxy_expose_routing_factory();
let decl = ComponentDecl {
exposes: vec![
ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "baz-dir".into(),
target_name: "hippo-dir".into(),
target: ExposeTarget::Parent,
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "foo-dir".into(),
target_name: "bar-dir".into(),
target: ExposeTarget::Parent,
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "baz-svc".into(),
target_name: "hippo-svc".into(),
target: ExposeTarget::Parent,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: ExposeSource::Self_,
source_name: CapabilityName::from("elf"),
target: ExposeTarget::Parent,
target_name: CapabilityName::from("elf"),
}),
],
..default_component_decl()
};
let root_realm = Arc::new(Realm::new_root_realm(
Environment::empty(),
Weak::new(),
Weak::new(),
"test://root".to_string(),
));
let tree = DirTree::build_from_exposes(routing_factory, root_realm.as_weak(), decl.clone());
// Convert the tree to a directory.
let mut expose_dir = pfs::simple();
tree.install(&root_realm.abs_moniker, &mut expose_dir)
.expect("Unable to build pseudodirectory");
let (expose_dir_client, expose_dir_server) = zx::Channel::create().unwrap();
expose_dir.open(
ExecutionScope::new(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_DIRECTORY,
path::Path::empty(),
ServerEnd::<NodeMarker>::new(expose_dir_server.into()),
);
let expose_dir_proxy = ClientEnd::<DirectoryMarker>::new(expose_dir_client)
.into_proxy()
.expect("failed to create directory proxy");
assert_eq!(
vec!["bar-dir", "hippo-dir", "hippo-svc"],
test_helpers::list_directory_recursive(&expose_dir_proxy).await
);
// Expect that calls on the directory nodes reach the mock directory/service.
assert_eq!("friend", test_helpers::read_file(&expose_dir_proxy, "bar-dir/hello").await);
assert_eq!("friend", test_helpers::read_file(&expose_dir_proxy, "hippo-dir/hello").await);
assert_eq!(
"hippos".to_string(),
test_helpers::call_echo(&expose_dir_proxy, "hippo-svc").await
);
}
}