blob: d42778affaf1f68460484bfe318a67402d0ceba0 [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::{
capability::{CapabilityProvider, CapabilitySource, InternalCapability},
channel,
model::{
addable_directory::AddableDirectoryWithResult,
dir_tree::DirTree,
error::ModelError,
hooks::{Event, EventPayload, EventType, Hook, HooksRegistration, RuntimeInfo},
realm::WeakRealm,
routing_fns::{route_expose_fn, route_use_fn},
},
},
async_trait::async_trait,
cm_rust::ComponentDecl,
directory_broker,
fidl::endpoints::ServerEnd,
fidl_fuchsia_io::{DirectoryProxy, NodeMarker, CLONE_FLAG_SAME_RIGHTS, MODE_TYPE_DIRECTORY},
fuchsia_trace as trace, fuchsia_zircon as zx,
futures::lock::Mutex,
moniker::AbsoluteMoniker,
std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, Weak},
},
vfs::{
directory::entry::DirectoryEntry, directory::immutable::simple as pfs,
execution_scope::ExecutionScope, file::pcb::asynchronous::read_only_static,
path::Path as pfsPath,
},
};
// Declare simple directory type for brevity
type Directory = Arc<pfs::Simple>;
struct HubCapabilityProvider {
abs_moniker: AbsoluteMoniker,
hub: Arc<Hub>,
}
impl HubCapabilityProvider {
pub fn new(abs_moniker: AbsoluteMoniker, hub: Arc<Hub>) -> Self {
HubCapabilityProvider { abs_moniker, hub }
}
}
#[async_trait]
impl CapabilityProvider for HubCapabilityProvider {
async fn open(
self: Box<Self>,
flags: u32,
open_mode: u32,
relative_path: PathBuf,
server_end: &mut zx::Channel,
) -> Result<(), ModelError> {
let mut relative_path = relative_path
.to_str()
.ok_or_else(|| ModelError::path_is_not_utf8(relative_path.clone()))?
.to_string();
relative_path.push('/');
let dir_path = pfsPath::validate_and_split(relative_path.clone()).map_err(|_| {
ModelError::open_directory_error(self.abs_moniker.clone(), relative_path)
})?;
self.hub.open(&self.abs_moniker, flags, open_mode, dir_path, server_end).await?;
Ok(())
}
}
/// Hub state on an instance of a component.
struct Instance {
pub abs_moniker: AbsoluteMoniker,
pub component_url: String,
pub execution: Option<Execution>,
pub directory: Directory,
pub children_directory: Directory,
pub deleting_directory: Directory,
}
/// The execution state for a component that has started running.
struct Execution {
pub resolved_url: String,
pub directory: Directory,
}
/// The Hub is a directory tree representing the component topology. Through the Hub,
/// debugging and instrumentation tools can query information about component instances
/// on the system, such as their component URLs, execution state and so on.
pub struct Hub {
instances: Mutex<HashMap<AbsoluteMoniker, Instance>>,
scope: ExecutionScope,
}
impl Hub {
/// Create a new Hub given a `component_url` for the root component.
pub fn new(component_url: String) -> Result<Self, ModelError> {
let mut instances_map = HashMap::new();
let abs_moniker = AbsoluteMoniker::root();
Hub::add_instance_if_necessary(&abs_moniker, component_url, &mut instances_map)?
.expect("Did not create directory.");
Ok(Hub { instances: Mutex::new(instances_map), scope: ExecutionScope::new() })
}
pub async fn open_root(
&self,
flags: u32,
mut server_end: zx::Channel,
) -> Result<(), ModelError> {
let root_moniker = AbsoluteMoniker::root();
self.open(&root_moniker, flags, MODE_TYPE_DIRECTORY, pfsPath::empty(), &mut server_end)
.await?;
Ok(())
}
pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
vec![HooksRegistration::new(
"Hub",
vec![
EventType::CapabilityRouted,
EventType::Discovered,
EventType::Destroyed,
EventType::MarkedForDestruction,
EventType::Started,
EventType::Stopped,
],
Arc::downgrade(self) as Weak<dyn Hook>,
)]
}
pub async fn open(
&self,
abs_moniker: &AbsoluteMoniker,
flags: u32,
open_mode: u32,
relative_path: pfsPath,
server_end: &mut zx::Channel,
) -> Result<(), ModelError> {
let instances_map = self.instances.lock().await;
if !instances_map.contains_key(&abs_moniker) {
return Err(ModelError::open_directory_error(
abs_moniker.clone(),
relative_path.into_string(),
));
}
let server_end = channel::take_channel(server_end);
instances_map[&abs_moniker].directory.clone().open(
self.scope.clone(),
flags,
open_mode,
relative_path,
ServerEnd::<NodeMarker>::new(server_end),
);
Ok(())
}
fn add_instance_if_necessary(
abs_moniker: &AbsoluteMoniker,
component_url: String,
instance_map: &mut HashMap<AbsoluteMoniker, Instance>,
) -> Result<Option<Directory>, ModelError> {
trace::duration!("component_manager", "hub:add_instance_if_necessary");
if instance_map.contains_key(&abs_moniker) {
return Ok(None);
}
let instance = pfs::simple();
// Add a 'url' file.
instance.add_node(
"url",
read_only_static(component_url.clone().into_bytes()),
&abs_moniker,
)?;
// Add an 'id' file.
// For consistency sake, the Hub assumes that the root instance also
// has ID 0, like any other static instance.
let id =
if let Some(child_moniker) = abs_moniker.leaf() { child_moniker.instance() } else { 0 };
let component_type = if id > 0 { "dynamic" } else { "static" };
instance.add_node("id", read_only_static(id.to_string().into_bytes()), &abs_moniker)?;
// Add a 'component_type' file.
instance.add_node(
"component_type",
read_only_static(component_type.to_string().into_bytes()),
&abs_moniker,
)?;
// Add a children directory.
let children = pfs::simple();
instance.add_node("children", children.clone(), &abs_moniker)?;
// Add a deleting directory.
let deleting = pfs::simple();
instance.add_node("deleting", deleting.clone(), &abs_moniker)?;
instance_map.insert(
abs_moniker.clone(),
Instance {
abs_moniker: abs_moniker.clone(),
component_url,
execution: None,
directory: instance.clone(),
children_directory: children.clone(),
deleting_directory: deleting.clone(),
},
);
Ok(Some(instance))
}
async fn add_instance_to_parent_if_necessary<'a>(
abs_moniker: &'a AbsoluteMoniker,
component_url: String,
mut instances_map: &'a mut HashMap<AbsoluteMoniker, Instance>,
) -> Result<(), ModelError> {
if let Some(controlled) =
Hub::add_instance_if_necessary(&abs_moniker, component_url, &mut instances_map)?
{
if let (Some(leaf), Some(parent_moniker)) = (abs_moniker.leaf(), abs_moniker.parent()) {
// In the children directory, the child's instance id is not used
trace::duration!("component_manager", "hub:add_instance_to_parent");
let partial_moniker = leaf.to_partial();
instances_map[&parent_moniker].children_directory.add_node(
partial_moniker.as_str(),
controlled.clone(),
&abs_moniker,
)?;
}
}
Ok(())
}
fn add_resolved_url_file(
execution_directory: Directory,
resolved_url: String,
abs_moniker: &AbsoluteMoniker,
) -> Result<(), ModelError> {
execution_directory.add_node(
"resolved_url",
read_only_static(resolved_url.into_bytes()),
&abs_moniker,
)?;
Ok(())
}
fn add_in_directory(
execution_directory: Directory,
component_decl: ComponentDecl,
package_dir: Option<DirectoryProxy>,
target_moniker: &AbsoluteMoniker,
target_realm: WeakRealm,
) -> Result<(), ModelError> {
let tree = DirTree::build_from_uses(route_use_fn, target_realm, component_decl);
let mut in_dir = pfs::simple();
tree.install(target_moniker, &mut in_dir)?;
if let Some(pkg_dir) = package_dir {
in_dir.add_node(
"pkg",
directory_broker::DirectoryBroker::from_directory_proxy(pkg_dir),
target_moniker,
)?;
}
execution_directory.add_node("in", in_dir, target_moniker)?;
Ok(())
}
fn add_expose_directory(
execution_directory: Directory,
component_decl: ComponentDecl,
target_moniker: &AbsoluteMoniker,
target_realm: WeakRealm,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:add_expose_directory");
let tree = DirTree::build_from_exposes(route_expose_fn, target_realm, component_decl);
let mut expose_dir = pfs::simple();
tree.install(target_moniker, &mut expose_dir)?;
execution_directory.add_node("expose", expose_dir, target_moniker)?;
Ok(())
}
fn add_out_directory(
execution_directory: Directory,
outgoing_dir: Option<DirectoryProxy>,
target_moniker: &AbsoluteMoniker,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:add_out_directory");
if let Some(out_dir) = outgoing_dir {
execution_directory.add_node(
"out",
directory_broker::DirectoryBroker::from_directory_proxy(out_dir),
target_moniker,
)?;
}
Ok(())
}
fn add_runtime_directory(
execution_directory: Directory,
runtime_dir: Option<DirectoryProxy>,
abs_moniker: &AbsoluteMoniker,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:add_runtime_directory");
if let Some(runtime_dir) = runtime_dir {
execution_directory.add_node(
"runtime",
directory_broker::DirectoryBroker::from_directory_proxy(runtime_dir),
abs_moniker,
)?;
}
Ok(())
}
async fn on_started_async<'a>(
&'a self,
target_moniker: &AbsoluteMoniker,
target_realm: &WeakRealm,
component_url: String,
runtime: &RuntimeInfo,
component_decl: &'a ComponentDecl,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_start_instance_async");
let mut instances_map = self.instances.lock().await;
Self::add_instance_to_parent_if_necessary(
target_moniker,
component_url,
&mut instances_map,
)
.await?;
let instance = instances_map
.get_mut(target_moniker)
.expect(&format!("Unable to find instance {} in map.", target_moniker));
// If we haven't already created an execution directory, create one now.
if instance.execution.is_none() {
trace::duration!("component_manager", "hub:create_execution");
let execution_directory = pfs::simple();
let exec = Execution {
resolved_url: runtime.resolved_url.clone(),
directory: execution_directory.clone(),
};
instance.execution = Some(exec);
Self::add_resolved_url_file(
execution_directory.clone(),
runtime.resolved_url.clone(),
target_moniker,
)?;
Self::add_in_directory(
execution_directory.clone(),
component_decl.clone(),
Self::clone_dir(runtime.package_dir.as_ref()),
target_moniker,
target_realm.clone(),
)?;
Self::add_expose_directory(
execution_directory.clone(),
component_decl.clone(),
target_moniker,
target_realm.clone(),
)?;
Self::add_out_directory(
execution_directory.clone(),
Self::clone_dir(runtime.outgoing_dir.as_ref()),
target_moniker,
)?;
Self::add_runtime_directory(
execution_directory.clone(),
Self::clone_dir(runtime.runtime_dir.as_ref()),
&target_moniker,
)?;
instance.directory.add_node("exec", execution_directory, &target_moniker)?;
}
Ok(())
}
async fn on_discovered_async(
&self,
target_moniker: &AbsoluteMoniker,
component_url: String,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_discovered_async");
let mut instances_map = self.instances.lock().await;
Self::add_instance_to_parent_if_necessary(
target_moniker,
component_url,
&mut instances_map,
)
.await?;
Ok(())
}
async fn on_destroyed_async(&self, target_moniker: &AbsoluteMoniker) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_destroyed_async");
let mut instance_map = self.instances.lock().await;
// TODO(xbhatnag): Investigate error handling scenarios here.
// Can these errors arise from faulty components or from
// a bug in ComponentManager?
let parent_moniker = target_moniker.parent().expect("a root component cannot be dynamic");
let leaf = target_moniker.leaf().expect("a root component cannot be dynamic");
instance_map[&parent_moniker].deleting_directory.remove_node(leaf.as_str())?;
instance_map
.remove(&target_moniker)
.expect("the dynamic component must exist in the instance map");
Ok(())
}
async fn on_stopped_async(&self, target_moniker: &AbsoluteMoniker) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_stopped_async");
let mut instance_map = self.instances.lock().await;
instance_map[target_moniker].directory.remove_node("exec")?;
instance_map.get_mut(target_moniker).expect("instance must exist").execution = None;
Ok(())
}
async fn on_marked_for_destruction_async(
&self,
target_moniker: &AbsoluteMoniker,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_marked_for_destruction_async");
let parent_moniker = target_moniker.parent().expect("A root component cannot be destroyed");
let instance_map = self.instances.lock().await;
if !instance_map.contains_key(&parent_moniker) {
// Evidently this a duplicate dispatch of MarkedForDestruction.
return Ok(());
}
let leaf = target_moniker.leaf().expect("A root component cannot be destroyed");
// In the children directory, the child's instance id is not used
// TODO: It's possible for the MarkedForDestruction event to be dispatched twice if there
// are two concurrent `DestroyChild` operations. In such cases we should probably cause
// this update to no-op instead of returning an error.
let partial_moniker = leaf.to_partial();
let directory = instance_map[&parent_moniker]
.children_directory
.remove_node(partial_moniker.as_str())
.map_err(|_| ModelError::remove_entry_error(leaf.as_str()))?;
instance_map[&parent_moniker].deleting_directory.add_node(
leaf.as_str(),
directory,
target_moniker,
)?;
Ok(())
}
/// Given a `CapabilitySource`, determine if it is a framework-provided
/// hub capability. If so, update the given `capability_provider` to
/// provide a hub directory.
async fn on_capability_routed_async(
self: Arc<Self>,
_target_moniker: &AbsoluteMoniker,
source: CapabilitySource,
capability_provider: Arc<Mutex<Option<Box<dyn CapabilityProvider>>>>,
) -> Result<(), ModelError> {
trace::duration!("component_manager", "hub:on_capability_routed_async");
// If this is a scoped framework directory capability, then check the source path
if let CapabilitySource::Framework {
capability: InternalCapability::Directory(source_name),
scope_moniker,
} = source
{
if source_name.str() != "hub" {
return Ok(());
}
// Set the capability provider, if not already set.
let mut capability_provider = capability_provider.lock().await;
if capability_provider.is_none() {
*capability_provider =
Some(Box::new(HubCapabilityProvider::new(scope_moniker.clone(), self.clone())))
}
}
Ok(())
}
// TODO(fsamuel): We should probably preserve the original error messages
// instead of dropping them.
fn clone_dir(dir: Option<&DirectoryProxy>) -> Option<DirectoryProxy> {
dir.and_then(|d| io_util::clone_directory(d, CLONE_FLAG_SAME_RIGHTS).ok())
}
}
#[async_trait]
impl Hook for Hub {
async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
match &event.result {
Ok(EventPayload::CapabilityRouted { source, capability_provider }) => {
self.on_capability_routed_async(
&event.target_moniker,
source.clone(),
capability_provider.clone(),
)
.await?;
}
Ok(EventPayload::Destroyed) => {
self.on_destroyed_async(&event.target_moniker).await?;
}
Ok(EventPayload::Discovered) => {
self.on_discovered_async(&event.target_moniker, event.component_url.to_string())
.await?;
}
Ok(EventPayload::MarkedForDestruction) => {
self.on_marked_for_destruction_async(&event.target_moniker).await?;
}
Ok(EventPayload::Started { realm, runtime, component_decl, .. }) => {
self.on_started_async(
&event.target_moniker,
realm,
event.component_url.clone(),
&runtime,
&component_decl,
)
.await?;
}
Ok(EventPayload::Stopped { .. }) => {
self.on_stopped_async(&event.target_moniker).await?;
}
_ => {}
};
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::{
builtin_environment::BuiltinEnvironment,
config::RuntimeConfig,
model::{
binding::Binder,
model::Model,
realm::BindReason,
rights,
testing::{
test_helpers::*,
test_helpers::{
dir_contains, list_directory, list_directory_recursive, read_file,
},
test_hook::HubInjectionTestHook,
},
},
},
cm_rust::{
self, CapabilityPath, ComponentDecl, DirectoryDecl, ExposeDecl, ExposeDirectoryDecl,
ExposeProtocolDecl, ExposeSource, ExposeTarget, ProtocolDecl, UseDecl,
UseDirectoryDecl, UseEventDecl, UseEventStreamDecl, UseProtocolDecl, UseSource,
},
fidl::endpoints::ServerEnd,
fidl_fuchsia_io::{
DirectoryMarker, DirectoryProxy, MODE_TYPE_DIRECTORY, OPEN_RIGHT_READABLE,
OPEN_RIGHT_WRITABLE,
},
std::{convert::TryFrom, path::Path},
vfs::{
directory::entry::DirectoryEntry, execution_scope::ExecutionScope,
file::pcb::asynchronous::read_only_static, path::Path as pfsPath, pseudo_directory,
},
};
/// Hosts an out directory with a 'foo' file.
fn foo_out_dir_fn() -> Box<dyn Fn(ServerEnd<DirectoryMarker>) + Send + Sync> {
Box::new(move |server_end: ServerEnd<DirectoryMarker>| {
let out_dir = pseudo_directory!(
"foo" => read_only_static(b"bar"),
"test" => pseudo_directory!(
"aaa" => read_only_static(b"bbb"),
),
);
out_dir.clone().open(
ExecutionScope::new(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_DIRECTORY,
pfsPath::empty(),
ServerEnd::new(server_end.into_channel()),
);
})
}
/// Hosts a runtime directory with a 'bleep' file.
fn bleep_runtime_dir_fn() -> Box<dyn Fn(ServerEnd<DirectoryMarker>) + Send + Sync> {
Box::new(move |server_end: ServerEnd<DirectoryMarker>| {
let pseudo_dir = pseudo_directory!(
"bleep" => read_only_static(b"blah"),
);
pseudo_dir.clone().open(
ExecutionScope::new(),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
MODE_TYPE_DIRECTORY,
pfsPath::empty(),
ServerEnd::new(server_end.into_channel()),
);
})
}
type DirectoryCallback = Box<dyn Fn(ServerEnd<DirectoryMarker>) + Send + Sync>;
struct ComponentDescriptor {
pub name: &'static str,
pub decl: ComponentDecl,
pub host_fn: Option<DirectoryCallback>,
pub runtime_host_fn: Option<DirectoryCallback>,
}
async fn start_component_manager_with_hub(
root_component_url: String,
components: Vec<ComponentDescriptor>,
) -> (Arc<Model>, Arc<BuiltinEnvironment>, DirectoryProxy) {
start_component_manager_with_hub_and_hooks(root_component_url, components, vec![]).await
}
async fn start_component_manager_with_hub_and_hooks(
root_component_url: String,
components: Vec<ComponentDescriptor>,
additional_hooks: Vec<HooksRegistration>,
) -> (Arc<Model>, Arc<BuiltinEnvironment>, DirectoryProxy) {
let resolved_root_component_url = format!("{}_resolved", root_component_url);
let decls = components.iter().map(|c| (c.name, c.decl.clone())).collect();
let TestModelResult { model, builtin_environment, mock_runner, .. } =
new_test_model("root", decls, RuntimeConfig::default()).await;
for component in components.into_iter() {
if let Some(host_fn) = component.host_fn {
mock_runner.add_host_fn(&resolved_root_component_url, host_fn);
}
if let Some(runtime_host_fn) = component.runtime_host_fn {
mock_runner.add_runtime_host_fn(&resolved_root_component_url, runtime_host_fn);
}
}
let hub_proxy =
builtin_environment.bind_service_fs_for_hub().await.expect("unable to bind service_fs");
model.root_realm.hooks.install(additional_hooks).await;
let root_moniker = AbsoluteMoniker::root();
let res = model.bind(&root_moniker, &BindReason::Root).await;
assert!(res.is_ok());
(model, builtin_environment, hub_proxy)
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_basic() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![
ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new().add_lazy_child("a").build(),
host_fn: None,
runtime_host_fn: None,
},
ComponentDescriptor {
name: "a",
decl: component_decl_with_test_runner(),
host_fn: None,
runtime_host_fn: None,
},
],
)
.await;
assert_eq!(root_component_url, read_file(&hub_proxy, "url").await);
assert_eq!(
format!("{}_resolved", root_component_url),
read_file(&hub_proxy, "exec/resolved_url").await
);
// Verify IDs
assert_eq!("0", read_file(&hub_proxy, "id").await);
assert_eq!("0", read_file(&hub_proxy, "children/a/id").await);
// Verify Component Type
assert_eq!("static", read_file(&hub_proxy, "component_type").await);
assert_eq!("static", read_file(&hub_proxy, "children/a/component_type").await);
assert_eq!("test:///a", read_file(&hub_proxy, "children/a/url").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_out_directory() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new().add_lazy_child("a").build(),
host_fn: Some(foo_out_dir_fn()),
runtime_host_fn: None,
}],
)
.await;
assert!(dir_contains(&hub_proxy, "exec", "out").await);
assert!(dir_contains(&hub_proxy, "exec/out", "foo").await);
assert!(dir_contains(&hub_proxy, "exec/out/test", "aaa").await);
assert_eq!("bar", read_file(&hub_proxy, "exec/out/foo").await);
assert_eq!("bbb", read_file(&hub_proxy, "exec/out/test/aaa").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_runtime_directory() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new().add_lazy_child("a").build(),
host_fn: None,
runtime_host_fn: Some(bleep_runtime_dir_fn()),
}],
)
.await;
assert_eq!("blah", read_file(&hub_proxy, "exec/runtime/bleep").await);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_test_hook_interception() {
let root_component_url = "test:///root".to_string();
let hub_injection_test_hook = Arc::new(HubInjectionTestHook::new());
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub_and_hooks(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new()
.add_lazy_child("a")
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Framework,
source_name: "hub".into(),
target_path: CapabilityPath::try_from("/hub").unwrap(),
rights: *rights::READ_RIGHTS,
subdir: None,
}))
.build(),
host_fn: None,
runtime_host_fn: None,
}],
hub_injection_test_hook.hooks(),
)
.await;
let in_dir = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(vec!["hub"], list_directory(&in_dir).await);
let scoped_hub_dir_proxy = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in/hub"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
// There are no out or runtime directories because there is no program running.
assert_eq!(vec!["old_hub"], list_directory(&scoped_hub_dir_proxy).await);
let old_hub_dir_proxy = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in/hub/old_hub/exec"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(
vec!["expose", "in", "out", "resolved_url", "runtime"],
list_directory(&old_hub_dir_proxy).await
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_in_directory() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new()
.add_lazy_child("a")
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Framework,
source_name: "hub".into(),
target_path: CapabilityPath::try_from("/hub").unwrap(),
rights: *rights::READ_RIGHTS,
subdir: Some("exec".into()),
}))
.use_(UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "baz-svc".into(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
}))
.use_(UseDecl::Directory(UseDirectoryDecl {
source: UseSource::Parent,
source_name: "foo-dir".into(),
target_path: CapabilityPath::try_from("/data/bar").unwrap(),
rights: *rights::READ_RIGHTS | *rights::WRITE_RIGHTS,
subdir: None,
}))
.build(),
host_fn: None,
runtime_host_fn: None,
}],
)
.await;
let in_dir = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(vec!["data", "hub", "svc"], list_directory(&in_dir).await);
let scoped_hub_dir_proxy = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in/hub"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(
vec!["expose", "in", "out", "resolved_url", "runtime"],
list_directory(&scoped_hub_dir_proxy).await
);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_no_event_stream_in_incoming_directory() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new()
.add_lazy_child("a")
.use_(UseDecl::Event(UseEventDecl {
source: UseSource::Framework,
source_name: "started".into(),
target_name: "started".into(),
filter: None,
}))
.use_(UseDecl::EventStream(UseEventStreamDecl {
target_path: CapabilityPath::try_from("/svc/fuchsia.sys2.EventStream")
.unwrap(),
events: vec!["started".to_string()],
}))
.build(),
host_fn: None,
runtime_host_fn: None,
}],
)
.await;
let in_dir = io_util::open_directory(
&hub_proxy,
&Path::new("exec/in"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(0, list_directory(&in_dir).await.len());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn hub_expose_directory() {
let root_component_url = "test:///root".to_string();
let (_model, _builtin_environment, hub_proxy) = start_component_manager_with_hub(
root_component_url.clone(),
vec![ComponentDescriptor {
name: "root",
decl: ComponentDeclBuilder::new()
.add_lazy_child("a")
.protocol(ProtocolDecl {
name: "foo".into(),
source_path: "/svc/foo".parse().unwrap(),
})
.directory(DirectoryDecl {
name: "baz".into(),
source_path: "/data".parse().unwrap(),
rights: *rights::READ_RIGHTS,
})
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "foo".into(),
target_name: "bar".into(),
target: ExposeTarget::Parent,
}))
.expose(ExposeDecl::Directory(ExposeDirectoryDecl {
source: ExposeSource::Self_,
source_name: "baz".into(),
target_name: "hippo".into(),
target: ExposeTarget::Parent,
rights: None,
subdir: None,
}))
.build(),
host_fn: None,
runtime_host_fn: None,
}],
)
.await;
let expose_dir = io_util::open_directory(
&hub_proxy,
&Path::new("exec/expose"),
OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
)
.expect("Failed to open directory");
assert_eq!(vec!["bar", "hippo"], list_directory_recursive(&expose_dir).await);
}
}