blob: f25b250e5c6bf9e20acfdbe495453f1fbb127030 [file] [log] [blame]
// Copyright 2023 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 fuchsia_runtime::{HandleInfo, HandleType};
use futures::channel::mpsc::UnboundedSender;
use process_builder::StartupHandle;
use processargs::ProcessArgs;
use sandbox::{Capability, Dict, DictKey};
use std::collections::HashMap;
use std::iter::once;
use thiserror::Error;
use vfs::execution_scope::ExecutionScope;
mod namespace;
pub use crate::namespace::{ignore_not_found, BuildNamespaceError, NamespaceBuilder};
/// How to deliver a particular capability from a dict to an Elf process. Broadly speaking,
/// one could either deliver a capability using namespace entries, or using numbered handles.
pub enum Delivery {
/// Install the capability as a `fuchsia.io` object, within some parent directory serviced by
/// the framework, and discoverable at a path such as "/svc/foo/bar".
///
/// As a result, a namespace entry will be created in the resulting processargs, corresponding
/// to the parent directory, e.g. "/svc/foo".
///
/// For example, installing a `sandbox::Sender` at "/svc/fuchsia.examples.Echo" will
/// cause the framework to spin up a `fuchsia.io/Directory` implementation backing "/svc",
/// containing a filesystem object named "fuchsia.examples.Echo".
///
/// Not all capability types are installable as `fuchsia.io` objects. A one-shot handle is not
/// supported because `fuchsia.io` does not have a protocol for delivering one-shot handles.
/// Use [Delivery::Handle] for those.
NamespacedObject(cm_types::Path),
/// Install the capability as a `fuchsia.io` object by creating a namespace entry at the
/// provided path. The difference between [Delivery::NamespacedObject] and
/// [Delivery::NamespaceEntry] is that the former will create a namespace entry at the parent
/// directory.
///
/// For example, installing a `sandbox::Directory` at "/data" will result in a namespace entry
/// at "/data". A request will be sent to the capability when the user writes to the
/// namespace entry.
NamespaceEntry(cm_types::Path),
/// Installs the Zircon handle representation of this capability at the processargs slot
/// described by [HandleInfo].
///
/// The following handle types are disallowed because they will collide with the implementation
/// of incoming namespace and outgoing directory:
///
/// - [HandleType::NamespaceDirectory]
/// - [HandleType::DirectoryRequest]
///
Handle(HandleInfo),
}
pub enum DeliveryMapEntry {
Delivery(Delivery),
Dict(DeliveryMap),
}
/// A nested dictionary mapping capability names to delivery method.
///
/// Each entry in a [Dict] should have a corresponding entry here describing how the
/// capability will be delivered to the process. If a [Dict] has a nested [Dict], then there
/// will be a corresponding nested [DeliveryMapEntry::Dict] containing the [DeliveryMap] for the
/// capabilities in the nested [Dict].
pub type DeliveryMap = HashMap<DictKey, DeliveryMapEntry>;
/// Visits `dict` and installs its capabilities into appropriate locations in the
/// `processargs`, as determined by a `delivery_map`.
///
/// If the process opens non-existent paths within one of the namespace entries served
/// by the framework, that path will be sent down `not_found`. Callers should either monitor
/// the stream, or drop the receiver, to prevent unbounded buffering.
///
/// On success, returns a future that services the namespace.
pub fn add_to_processargs(
scope: ExecutionScope,
dict: Dict,
processargs: &mut ProcessArgs,
delivery_map: &DeliveryMap,
not_found: UnboundedSender<String>,
) -> Result<(), DeliveryError> {
let mut namespace = NamespaceBuilder::new(scope, not_found);
// Iterate over the delivery map.
// Take entries away from dict and install them accordingly.
visit_map(delivery_map, dict, &mut |cap: Capability, delivery: &Delivery| match delivery {
Delivery::NamespacedObject(path) => {
namespace.add_object(cap, &path).map_err(DeliveryError::NamespaceError)
}
Delivery::NamespaceEntry(path) => {
namespace.add_entry(cap, &path.clone().into()).map_err(DeliveryError::NamespaceError)
}
Delivery::Handle(info) => {
processargs.add_handles(once(translate_handle(cap, info)?));
Ok(())
}
})?;
let namespace = namespace.serve().map_err(DeliveryError::NamespaceError)?;
let namespace: Vec<_> = namespace.into();
processargs.namespace_entries.extend(namespace);
Ok(())
}
#[derive(Error, Debug)]
pub enum DeliveryError {
#[error("the key `{0}` is not found in the dict")]
NotInDict(DictKey),
#[error("wrong type: the delivery map expected `{0}` to be a nested Dict in the dict")]
NotADict(DictKey),
#[error("unused capabilities in dict: `{0:?}`")]
UnusedCapabilities(Vec<DictKey>),
#[error("handle type `{0:?}` is not allowed to be installed into processargs")]
UnsupportedHandleType(HandleType),
#[error("namespace configuration error: `{0}`")]
NamespaceError(namespace::BuildNamespaceError),
#[error("capability `{0:?}` is not allowed to be installed into processargs")]
UnsupportedCapability(Capability),
}
fn translate_handle(cap: Capability, info: &HandleInfo) -> Result<StartupHandle, DeliveryError> {
validate_handle_type(info.handle_type())?;
let one_shot = match cap.clone() {
Capability::OneShotHandle(h) => h,
c => return Err(DeliveryError::UnsupportedCapability(c)),
};
let handle = one_shot.get_handle().map_err(|_| DeliveryError::UnsupportedCapability(cap))?;
Ok(StartupHandle { handle, info: *info })
}
fn visit_map(
map: &DeliveryMap,
dict: Dict,
f: &mut impl FnMut(Capability, &Delivery) -> Result<(), DeliveryError>,
) -> Result<(), DeliveryError> {
let mut entries = dict.lock_entries();
for (key, entry) in map {
match entries.remove(key) {
Some(value) => match entry {
DeliveryMapEntry::Delivery(delivery) => f(value, delivery)?,
DeliveryMapEntry::Dict(sub_map) => {
let nested_dict: Dict = match value {
Capability::Dictionary(d) => d,
_ => return Err(DeliveryError::NotADict(key.to_owned())),
};
visit_map(sub_map, nested_dict, f)?;
}
},
None => return Err(DeliveryError::NotInDict(key.to_owned())),
}
}
if entries.iter().count() > 0 {
let keys = entries.iter().map(|(key, _)| key).cloned().collect();
return Err(DeliveryError::UnusedCapabilities(keys));
}
Ok(())
}
fn validate_handle_type(handle_type: HandleType) -> Result<(), DeliveryError> {
match handle_type {
HandleType::NamespaceDirectory | HandleType::DirectoryRequest => {
Err(DeliveryError::UnsupportedHandleType(handle_type))
}
_ => Ok(()),
}
}
#[cfg(test)]
mod test_util {
use {
fidl::endpoints::ServerEnd,
fidl_fuchsia_io as fio, fuchsia_zircon as zx,
sandbox::{Receiver, Sender},
std::sync::Arc,
vfs::{
directory::entry::{DirectoryEntry, EntryInfo, OpenRequest},
execution_scope::ExecutionScope,
path::Path,
remote::RemoteLike,
},
};
pub fn multishot() -> (Sender, Receiver) {
let (receiver, sender) = Receiver::new();
(sender, receiver)
}
pub fn mock_dir() -> (Arc<impl DirectoryEntry>, async_channel::Receiver<(Path, zx::Channel)>) {
let (sender, receiver) = async_channel::unbounded::<(Path, zx::Channel)>();
struct Sender(async_channel::Sender<(Path, zx::Channel)>);
impl DirectoryEntry for Sender {
fn entry_info(&self) -> EntryInfo {
EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
}
fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
request.open_remote(self)
}
}
impl RemoteLike for Sender {
fn open(
self: Arc<Self>,
scope: ExecutionScope,
_flags: fio::OpenFlags,
relative_path: Path,
server_end: ServerEnd<fio::NodeMarker>,
) {
scope.spawn(async move {
self.0.send((relative_path, server_end.into_channel())).await.unwrap();
})
}
fn lazy(&self, path: &Path) -> bool {
path.is_empty()
}
}
(Arc::new(Sender(sender)), receiver)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::namespace::ignore_not_found as ignore,
anyhow::{anyhow, Result},
assert_matches::assert_matches,
fidl::endpoints::{Proxy, ServerEnd},
fidl_fuchsia_io as fio, fuchsia_async as fasync,
fuchsia_fs::directory::DirEntry,
fuchsia_zircon as zx,
fuchsia_zircon::{AsHandleRef, HandleBased, Peered, Signals, Time},
futures::TryStreamExt,
maplit::hashmap,
sandbox::OneShotHandle,
std::pin::pin,
std::str::FromStr,
test_util::{mock_dir, multishot},
vfs::directory::entry::serve_directory,
};
#[fuchsia::test]
async fn test_empty() -> Result<()> {
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
let delivery_map = DeliveryMap::new();
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.namespace_entries.len(), 0);
assert_eq!(processargs.handles.len(), 0);
drop(processargs);
scope.wait().await;
Ok(())
}
#[fuchsia::test]
async fn test_handle() -> Result<()> {
let (sock0, sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
dict.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle().into_handle())),
)
.map_err(|e| anyhow!("{e:?}"))?;
let delivery_map = hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
)
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.namespace_entries.len(), 0);
assert_eq!(processargs.handles.len(), 1);
assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
assert_eq!(processargs.handles[0].info.arg(), 0);
// Test connectivity.
const PAYLOAD: &'static [u8] = b"Hello";
let handles = std::mem::take(&mut processargs.handles);
let sock0 = zx::Socket::from(handles.into_iter().next().unwrap().handle);
assert_eq!(sock0.write(PAYLOAD).unwrap(), 5);
let mut buf = [0u8; PAYLOAD.len() + 1];
assert_eq!(sock1.read(&mut buf[..]), Ok(PAYLOAD.len()));
assert_eq!(&buf[..PAYLOAD.len()], PAYLOAD);
drop(processargs);
scope.wait().await;
Ok(())
}
#[fuchsia::test]
async fn test_create_nested_dict() -> Result<()> {
let (sock0, _sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
// Put a socket at "/handles/stdin". This implements a capability bundling pattern.
let handles = Dict::new();
handles
.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle())),
)
.map_err(|e| anyhow!("{e:?}"))?;
let dict = Dict::new();
dict.lock_entries()
.insert("handles".parse().unwrap(), Capability::Dictionary(handles))
.map_err(|e| anyhow!("{e:?}"))?;
let delivery_map = hashmap! {
"handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
)
})
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.namespace_entries.len(), 0);
assert_eq!(processargs.handles.len(), 1);
assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
assert_eq!(processargs.handles[0].info.arg(), 0);
drop(processargs);
scope.wait().await;
Ok(())
}
/// Test accessing capabilities from a Dict inside a Dict.
#[fuchsia::test]
async fn test_access_in_nested_dict() -> Result<()> {
let (ep0, ep1) = zx::EventPair::create();
let mut processargs = ProcessArgs::new();
let handles = Dict::new();
handles
.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(ep0.into_handle())),
)
.map_err(|e| anyhow!("{e:?}"))?;
let dict = Dict::new();
dict.lock_entries()
.insert("handles".parse().unwrap(), Capability::Dictionary(handles))
.map_err(|e| anyhow!("{e:?}"))?;
let delivery_map = hashmap! {
"handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
)
})
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.namespace_entries.len(), 0);
assert_eq!(processargs.handles.len(), 1);
assert_eq!(processargs.handles[0].info.handle_type(), HandleType::FileDescriptor);
assert_eq!(processargs.handles[0].info.arg(), 0);
let ep0 = processargs.handles.pop().unwrap().handle;
ep1.signal_peer(Signals::NONE, Signals::USER_1).unwrap();
assert_eq!(ep0.wait_handle(Signals::USER_1, Time::INFINITE).unwrap(), Signals::USER_1);
drop(ep0);
drop(processargs);
scope.wait().await;
Ok(())
}
#[fuchsia::test]
async fn test_wrong_dict_destructuring() {
let (sock0, _sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
// The type of "/handles" is a socket capability but we try to open it as a dict and extract
// a "stdin" inside. This should fail.
let dict = Dict::new();
dict.lock_entries()
.insert(
"handles".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle())),
)
.expect("dict entry already exists");
let delivery_map = hashmap! {
"handles".parse().unwrap() => DeliveryMapEntry::Dict(hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
)
})
};
assert_matches!(
add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).err().unwrap(),
DeliveryError::NotADict(name)
if name.as_str() == "handles"
);
}
#[fuchsia::test]
async fn test_handle_unused() {
let (sock0, _sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
dict.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle())),
)
.expect("dict entry already exists");
let delivery_map = DeliveryMap::new();
assert_matches!(
add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).err().unwrap(),
DeliveryError::UnusedCapabilities(keys)
if keys == vec![DictKey::from_str("stdin").unwrap()]
);
}
#[fuchsia::test]
async fn test_handle_unsupported() {
let (sock0, _sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
dict.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle())),
)
.expect("dict entry already exists");
let delivery_map = hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::DirectoryRequest, 0))
)
};
assert_matches!(
add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).err().unwrap(),
DeliveryError::UnsupportedHandleType(handle_type)
if handle_type == HandleType::DirectoryRequest
);
}
#[fuchsia::test]
async fn test_handle_not_found() {
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
let delivery_map = hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::Handle(HandleInfo::new(HandleType::FileDescriptor, 0))
)
};
assert_matches!(
add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).err().unwrap(),
DeliveryError::NotInDict(name)
if name.as_str() == "stdin"
);
}
/// Two protocol capabilities in `/svc`. One of them has a receiver waiting for incoming
/// requests. The other is disconnected from the receiver, which should close all incoming
/// connections to that protocol.
#[fuchsia::test]
async fn test_namespace_object_end_to_end() -> Result<()> {
let (sender, receiver) = multishot();
let peer_closed_open = multishot().0;
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
{
let mut entries = dict.lock_entries();
entries
.insert("normal".parse().unwrap(), sender.into())
.expect("dict entry already exists");
entries
.insert("closed".parse().unwrap(), peer_closed_open.into())
.expect("dict entry already exists");
}
let delivery_map = hashmap! {
"normal".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
),
"closed".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Closed").unwrap())
)
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.handles.len(), 0);
assert_eq!(processargs.namespace_entries.len(), 1);
let entry = processargs.namespace_entries.pop().unwrap();
assert_eq!(entry.path.to_str().unwrap(), "/svc");
// Check that there are the expected two protocols inside the svc directory.
let dir = entry.directory.into_proxy().unwrap();
let mut entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
let mut expectation = vec![
DirEntry { name: "fuchsia.Normal".to_string(), kind: fio::DirentType::Service },
DirEntry { name: "fuchsia.Closed".to_string(), kind: fio::DirentType::Service },
];
entries.sort();
expectation.sort();
assert_eq!(entries, expectation);
let dir = dir.into_channel().unwrap().into_zx_channel();
// Connect to the protocol using namespace functionality.
let (client_end, server_end) = zx::Channel::create();
fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
// Make sure the server_end is received, and test connectivity.
let server_end: zx::Channel = receiver.receive().await.unwrap().channel.into();
client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
server_end.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST).unwrap();
// Connect to the closed protocol. Because the receiver is discarded, anything we send
// should get peer-closed.
let (client_end, server_end) = zx::Channel::create();
fdio::service_connect_at(&dir, "fuchsia.Closed", server_end).unwrap();
fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
drop(dir);
drop(processargs);
scope.wait().await;
Ok(())
}
#[fuchsia::test]
async fn test_namespace_scope_shutdown() -> Result<()> {
let (sender, receiver) = multishot();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
{
let mut entries = dict.lock_entries();
entries
.insert("normal".parse().unwrap(), sender.into())
.expect("dict entry already exists");
}
let delivery_map = hashmap! {
"normal".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
),
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.handles.len(), 0);
assert_eq!(processargs.namespace_entries.len(), 1);
let entry = processargs.namespace_entries.pop().unwrap();
assert_eq!(entry.path.to_str().unwrap(), "/svc");
let dir = entry.directory.into_proxy().unwrap();
let dir = dir.into_channel().unwrap().into_zx_channel();
// Connect to the protocol using namespace functionality.
let (client_end, server_end) = zx::Channel::create();
fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
// Make sure the server_end is received, and test connectivity.
let server_end: zx::Channel = receiver.receive().await.unwrap().channel.into();
client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
server_end.wait_handle(zx::Signals::USER_0, zx::Time::INFINITE_PAST).unwrap();
// Shutdown the execution scope.
scope.shutdown();
scope.wait().await;
// Connect to the protocol again. This time, because the namespace was shutdown, anything we send
// should get peer-closed.
let (client_end, server_end) = zx::Channel::create();
fdio::service_connect_at(&dir, "fuchsia.Normal", server_end).unwrap();
fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
drop(dir);
drop(processargs);
Ok(())
}
/// Install an `Open` capability at "/data". Test that opening "/data/abc" means the
/// open capability receives a request to open the current node, and the server endpoint
/// in that request has buffered an open request for "abc". This replicates what
/// component_manager does for directory capabilities.
#[test]
fn test_namespace_entry_end_to_end() -> Result<()> {
use futures::task::Poll;
let mut exec = fasync::TestExecutor::new();
let (dir, receiver) = mock_dir();
let scope = ExecutionScope::new();
let dir_proxy = serve_directory(dir, &scope, fio::OpenFlags::DIRECTORY).unwrap();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
dict.lock_entries()
.insert(
"data".parse().unwrap(),
Capability::Directory(sandbox::Directory::new(dir_proxy)),
)
.map_err(|e| anyhow!("{e:?}"))?;
let delivery_map = hashmap! {
"data".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::NamespaceEntry(cm_types::Path::from_str("/data").unwrap())
),
};
let scope = ExecutionScope::new();
add_to_processargs(scope.clone(), dict, &mut processargs, &delivery_map, ignore())?;
assert_eq!(processargs.handles.len(), 0);
assert_eq!(processargs.namespace_entries.len(), 1);
let entry = processargs.namespace_entries.pop().unwrap();
assert_eq!(entry.path.to_str().unwrap(), "/data");
// No request yet. Not until we write to the client endpoint.
assert_matches!(exec.run_until_stalled(&mut receiver.recv()), Poll::Pending);
let dir = entry.directory.into_proxy().unwrap();
let dir = dir.into_channel().unwrap().into_zx_channel();
let (client_end, server_end) = zx::Channel::create();
// Test that the flags are passed correctly.
let flags_for_abc = fio::OpenFlags::DIRECTORY | fio::OpenFlags::DESCRIBE;
fdio::open_at(&dir, "abc", flags_for_abc, server_end).unwrap();
// Capability is opened with "." and a `server_end`.
let (relative_path, server_end) = exec.run_singlethreaded(&mut receiver.recv()).unwrap();
assert!(relative_path.is_dot());
// Verify there is an open message for "abc" with `flags_for_abc` on `server_end`.
let server_end: ServerEnd<fio::DirectoryMarker> = server_end.into();
let mut stream = server_end.into_stream().unwrap();
let request = exec.run_singlethreaded(&mut stream.try_next()).unwrap().unwrap();
assert_matches!(
&request,
fio::DirectoryRequest::Open { flags, path, .. }
if path == "abc" && *flags == flags_for_abc
);
let client_end = fasync::Channel::from_channel(client_end.into());
assert_matches!(exec.run_until_stalled(&mut client_end.on_closed()), Poll::Pending);
// Drop the request, including the server endpoint.
drop(request);
// Client should observe that the server endpoint was dropped.
exec.run_singlethreaded(&mut client_end.on_closed()).unwrap();
drop(dir);
drop(processargs);
exec.run_singlethreaded(pin!(scope.wait()));
Ok(())
}
#[fuchsia::test]
async fn test_handle_unsupported_in_namespace() {
let (sock0, _sock1) = zx::Socket::create_stream();
let mut processargs = ProcessArgs::new();
let dict = Dict::new();
dict.lock_entries()
.insert(
"stdin".parse().unwrap(),
Capability::OneShotHandle(OneShotHandle::from(sock0.into_handle())),
)
.expect("dict entry already exists");
let delivery_map = hashmap! {
"stdin".parse().unwrap() => DeliveryMapEntry::Delivery(
Delivery::NamespacedObject(cm_types::Path::from_str("/svc/fuchsia.Normal").unwrap())
)
};
assert_matches!(
add_to_processargs(ExecutionScope::new(), dict, &mut processargs, &delivery_map, ignore()).err().unwrap(),
DeliveryError::NamespaceError(BuildNamespaceError::Conversion {
path, ..
})
if path.to_string() == "/svc"
);
}
}