1use cm_types::{NamespacePath, Path, RelativePath};
5use fidl::endpoints::ClientEnd;
6use fidl_fuchsia_component_runtime::RouteRequest;
7use fidl_fuchsia_io as fio;
8use fuchsia_async as fasync;
9use futures::channel::mpsc::{UnboundedSender, unbounded};
10use namespace::{Entry as NamespaceEntry, EntryError, Namespace, NamespaceError, Tree};
11use router_error::Explain;
12use runtime_capabilities::{Capability, CapabilityBound, Dictionary, WeakInstanceToken};
13use std::sync::Arc;
14use thiserror::Error;
15use vfs::directory::entry::serve_directory;
16use vfs::execution_scope::ExecutionScope;
17
18pub struct NamespaceBuilder {
20 entries: Tree<Capability>,
22
23 not_found: UnboundedSender<String>,
25
26 namespace_scope: ExecutionScope,
30
31 token: Arc<WeakInstanceToken>,
33}
34
35#[derive(Error, Debug, Clone)]
36pub enum BuildNamespaceError {
37 #[error(transparent)]
38 NamespaceError(#[from] NamespaceError),
39
40 #[error(
41 "while installing capabilities within the namespace entry `{path}`, \
42 failed to convert the namespace entry to Directory: {err}"
43 )]
44 Conversion {
45 path: NamespacePath,
46 #[source]
47 err: runtime_capabilities::ConversionError,
48 },
49
50 #[error("unable to serve `{path}` after converting to directory: {err}")]
51 Serve {
52 path: NamespacePath,
53 #[source]
54 err: fidl::Status,
55 },
56}
57
58impl NamespaceBuilder {
59 pub fn new(
60 namespace_scope: ExecutionScope,
61 not_found: UnboundedSender<String>,
62 token: Arc<WeakInstanceToken>,
63 ) -> Self {
64 return NamespaceBuilder { entries: Default::default(), not_found, namespace_scope, token };
65 }
66
67 pub fn add_object(
70 self: &mut Self,
71 cap: Capability,
72 path: &Path,
73 ) -> Result<(), BuildNamespaceError> {
74 let dirname = path.parent();
75
76 let any = match self.entries.get_mut(&dirname) {
78 Some(dir) => dir,
79 None => {
80 let dict = self.make_dict_with_not_found_logging(dirname.to_string());
81 self.entries.add(&dirname, Capability::Dictionary(dict))?
82 }
83 };
84
85 let dict = match any {
88 Capability::Dictionary(d) => d,
89 _ => Err(NamespaceError::Duplicate(path.clone().into()))?,
90 };
91
92 if dict.insert(path.basename().into(), cap).is_some() {
94 return Err(NamespaceError::Duplicate(path.clone().into()).into());
95 }
96 Ok(())
97 }
98
99 pub fn add_entry(
103 self: &mut Self,
104 cap: Capability,
105 path: &NamespacePath,
106 ) -> Result<(), BuildNamespaceError> {
107 match &cap {
108 Capability::Dictionary(_)
109 | Capability::DirConnector(_)
110 | Capability::DirConnectorRouter(_)
111 | Capability::DictionaryRouter(_)
112 | Capability::Handle(_) => {}
113 _ => return Err(NamespaceError::EntryError(EntryError::UnsupportedType).into()),
114 }
115 self.entries.add(path, cap)?;
116 Ok(())
117 }
118
119 pub fn serve(self: Self) -> Result<Namespace, BuildNamespaceError> {
120 let mut entries = vec![];
121 for (path, cap) in self.entries.flatten() {
122 let client_end: ClientEnd<fio::DirectoryMarker> = match cap {
123 Capability::Handle(handle) => ClientEnd::new(fidl::handle::Channel::from(
124 handle.take().expect("namespace handle already taken"),
125 )),
126 Capability::DirConnector(c) => {
127 let (client, server) =
128 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
129 let _ = self.namespace_scope.spawn(async move {
132 let res =
133 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
134 if res.is_err() {
135 return;
136 }
137 let _ = c.send(server, RelativePath::dot(), None);
142 });
143 client
144 }
145 Capability::DirConnectorRouter(c) => {
146 let (client, server) =
147 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
148 let token = self.token.clone();
151 let _ = self.namespace_scope.spawn(async move {
152 let res =
153 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
154 if res.is_err() {
155 return;
156 }
157 match c.route(RouteRequest::default(), token.clone()).await {
158 Ok(Some(dir_connector)) => {
159 let _ = dir_connector.send(server, RelativePath::dot(), None);
162 }
163 Ok(None) => {
164 let _ = server.close_with_epitaph(fidl::Status::NOT_FOUND);
165 }
166 Err(e) => {
167 let _ = server.close_with_epitaph(e.as_zx_status());
171 }
172 }
173 });
174 client
175 }
176 Capability::Dictionary(dict) => {
177 let entry = dict
178 .try_into_directory_entry(self.namespace_scope.clone(), self.token.clone())
179 .map_err(|err| BuildNamespaceError::Conversion {
180 path: path.clone(),
181 err,
182 })?;
183 if entry.entry_info().type_() != fio::DirentType::Directory {
184 return Err(BuildNamespaceError::Conversion {
185 path: path.clone(),
186 err: runtime_capabilities::ConversionError::NotSupported,
187 });
188 }
189 serve_directory(
190 entry,
191 &self.namespace_scope,
192 fio::Flags::PROTOCOL_DIRECTORY
193 | fio::PERM_READABLE
194 | fio::Flags::PERM_INHERIT_WRITE
195 | fio::Flags::PERM_INHERIT_EXECUTE,
196 )
197 .map_err(|err| BuildNamespaceError::Serve { path: path.clone(), err })?
198 }
199 Capability::DictionaryRouter(router) => {
200 let (client, server) =
201 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
202 let path = path.clone();
205 let scope = self.namespace_scope.clone();
206 let token = self.token.clone();
207 let _ = self.namespace_scope.spawn(async move {
208 let res =
209 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
210 if res.is_err() {
211 return;
212 }
213 match router.route(RouteRequest::default(), token.clone()).await {
214 Ok(Some(dictionary)) => {
215 let entry = match dictionary.try_into_directory_entry(scope.clone(), token.clone()) {
216 Ok(entry) => entry,
217 Err(e) => {
218 log::error!("failed to convert namespace dictionary at path {path} into dir entry: {e:?}");
219 return;
220 }
221 };
222 let client_end = serve_directory(
223 entry,
224 &scope,
225 fio::Flags::PROTOCOL_DIRECTORY
226 | fio::PERM_READABLE
227 | fio::Flags::PERM_INHERIT_WRITE
228 | fio::Flags::PERM_INHERIT_EXECUTE,
229 ).expect("failed to serve dictionary as directory");
230 let proxy = client_end.into_proxy();
231 fuchsia_fs::directory::clone_onto(&proxy, server).expect("failed to clone directory we are hosting");
232 }
233 Ok(None) => {
234 let _ = server.close_with_epitaph(fidl::Status::NOT_FOUND);
235 }
236 Err(e) => {
237 let _ = server.close_with_epitaph(e.as_zx_status());
241 }
242 }
243 });
244 client
245 }
246 _ => return Err(NamespaceError::EntryError(EntryError::UnsupportedType).into()),
247 };
248 entries.push(NamespaceEntry { path, directory: client_end.into() })
249 }
250 let ns = entries.try_into()?;
251 Ok(ns)
252 }
253
254 fn make_dict_with_not_found_logging(&self, root_path: String) -> Arc<Dictionary> {
255 let not_found = self.not_found.clone();
256 let new_dict = Dictionary::new_with_not_found(move |key| {
257 let requested_path = format!("{}/{}", root_path, key);
258 let _ = not_found.unbounded_send(requested_path);
261 });
262 new_dict
263 }
264}
265
266pub fn ignore_not_found() -> UnboundedSender<String> {
268 let (sender, _receiver) = unbounded();
269 sender
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use anyhow::Result;
276 use assert_matches::assert_matches;
277 use fidl::Peered;
278 use fidl::endpoints::{self, Proxy};
279 use fuchsia_fs::directory::DirEntry;
280 use futures::channel::mpsc;
281 use futures::{StreamExt, TryStreamExt};
282 use runtime_capabilities::{Connector, DirConnector, Receiver};
283 use std::sync::Arc;
284 use test_case::test_case;
285 use vfs::directory::entry::{DirectoryEntry, EntryInfo, GetEntryInfo, OpenRequest};
286 use vfs::remote::RemoteLike;
287 use vfs::{ObjectRequestRef, path, pseudo_directory};
288
289 use fidl_fuchsia_io as fio;
290 use fuchsia_async as fasync;
291
292 fn multishot() -> (Arc<Connector>, Receiver) {
293 let (receiver, sender) = Connector::new();
294 (sender, receiver)
295 }
296
297 fn connector_cap() -> Capability {
298 let (sender, _receiver) = multishot();
299 Capability::Connector(sender)
300 }
301
302 fn dir_connector_cap() -> Capability {
303 let fs = pseudo_directory! {};
304 Into::into(DirConnector::from_directory_entry(fs, fio::PERM_READABLE))
305 }
306
307 fn ns_path(str: &str) -> NamespacePath {
308 str.parse().unwrap()
309 }
310
311 fn path(str: &str) -> Path {
312 str.parse().unwrap()
313 }
314
315 fn parents_valid(paths: Vec<&str>) -> Result<(), BuildNamespaceError> {
316 let scope = ExecutionScope::new();
317 let mut shadow =
318 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
319 for p in paths {
320 shadow.add_object(connector_cap(), &path(p))?;
321 }
322 Ok(())
323 }
324
325 #[fuchsia::test]
326 async fn test_shadow() {
327 assert_matches!(parents_valid(vec!["/svc/foo/bar/Something", "/svc/Something"]), Err(_));
328 assert_matches!(parents_valid(vec!["/svc/Something", "/svc/foo/bar/Something"]), Err(_));
329 assert_matches!(parents_valid(vec!["/svc/Something", "/foo"]), Err(_));
330
331 assert_matches!(parents_valid(vec!["/foo/bar/a", "/foo/bar/b", "/foo/bar/c"]), Ok(()));
332 assert_matches!(parents_valid(vec!["/a", "/b", "/c"]), Ok(()));
333
334 let scope = ExecutionScope::new();
335 let mut shadow =
336 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
337 shadow.add_object(connector_cap(), &path("/svc/foo")).unwrap();
338 assert_matches!(shadow.add_object(connector_cap(), &path("/svc/foo/bar")), Err(_));
339
340 let scope = ExecutionScope::new();
341 let mut not_shadow =
342 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
343 not_shadow.add_object(connector_cap(), &path("/svc/foo")).unwrap();
344 assert_matches!(not_shadow.add_entry(dir_connector_cap(), &ns_path("/svc2")), Ok(_));
345 }
346
347 #[fuchsia::test]
348 async fn test_duplicate_object() {
349 let scope = ExecutionScope::new();
350 let mut namespace =
351 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
352 namespace.add_object(connector_cap(), &path("/svc/a")).expect("");
353 assert_matches!(
355 namespace.add_object(connector_cap(), &path("/svc/a")),
356 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
357 if path.to_string() == "/svc/a"
358 );
359 }
360
361 #[fuchsia::test]
362 async fn test_duplicate_entry() {
363 let scope = ExecutionScope::new();
364 let mut namespace =
365 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
366 namespace.add_entry(dir_connector_cap(), &ns_path("/svc/a")).expect("");
367 assert_matches!(
369 namespace.add_entry(dir_connector_cap(), &ns_path("/svc/a")),
370 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
371 if path.to_string() == "/svc/a"
372 );
373 }
374
375 #[fuchsia::test]
376 async fn test_duplicate_object_and_entry() {
377 let scope = ExecutionScope::new();
378 let mut namespace =
379 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
380 namespace.add_object(connector_cap(), &path("/svc/a")).expect("");
381 assert_matches!(
382 namespace.add_entry(dir_connector_cap(), &ns_path("/svc/a")),
383 Err(BuildNamespaceError::NamespaceError(NamespaceError::Shadow(path)))
384 if path.to_string() == "/svc/a"
385 );
386 }
387
388 #[fuchsia::test]
391 async fn test_duplicate_entry_at_object_parent() {
392 let scope = ExecutionScope::new();
393 let mut namespace =
394 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
395 namespace.add_object(connector_cap(), &path("/foo/bar")).expect("");
396 assert_matches!(
397 namespace.add_entry(dir_connector_cap(), &ns_path("/foo")),
398 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
399 if path.to_string() == "/foo"
400 );
401 }
402
403 #[fuchsia::test]
407 async fn test_duplicate_object_parent_at_entry() {
408 let scope = ExecutionScope::new();
409 let mut namespace =
410 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
411 namespace.add_entry(dir_connector_cap(), &ns_path("/foo")).expect("");
412 assert_matches!(
413 namespace.add_object(connector_cap(), &path("/foo/bar")),
414 Err(BuildNamespaceError::NamespaceError(NamespaceError::Duplicate(path)))
415 if path.to_string() == "/foo/bar"
416 );
417 }
418
419 #[fuchsia::test]
420 async fn test_empty() {
421 let scope = ExecutionScope::new();
422 let namespace =
423 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
424 let ns = namespace.serve().unwrap();
425 assert_eq!(ns.flatten().len(), 0);
426 }
427
428 #[fuchsia::test]
429 async fn test_one_connector_end_to_end() {
430 let (sender, receiver) = multishot();
431
432 let scope = ExecutionScope::new();
433 let mut namespace =
434 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
435 namespace.add_object(Into::into(sender), &path("/svc/a")).unwrap();
436 let ns = namespace.serve().unwrap();
437
438 let mut ns = ns.flatten();
439 assert_eq!(ns.len(), 1);
440 assert_eq!(ns[0].path.to_string(), "/svc");
441
442 let dir = ns.pop().unwrap().directory.into_proxy();
444 let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
445 assert_eq!(
446 entries,
447 vec![DirEntry { name: "a".to_string(), kind: fio::DirentType::Service }]
448 );
449
450 let (client_end, server_end) = zx::Channel::create();
452 fdio::service_connect_at(&dir.into_channel().unwrap().into_zx_channel(), "a", server_end)
453 .unwrap();
454
455 let server_end = receiver.receive().await.unwrap();
457 client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
458 server_end.wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE_PAST).unwrap();
459 }
460
461 #[fuchsia::test]
462 async fn test_two_connectors_in_same_namespace_entry() {
463 let scope = ExecutionScope::new();
464 let mut namespace =
465 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
466 namespace.add_object(connector_cap(), &path("/svc/a")).unwrap();
467 namespace.add_object(connector_cap(), &path("/svc/b")).unwrap();
468 let ns = namespace.serve().unwrap();
469
470 let mut ns = ns.flatten();
471 assert_eq!(ns.len(), 1);
472 assert_eq!(ns[0].path.to_string(), "/svc");
473
474 let dir = ns.pop().unwrap().directory.into_proxy();
476 let mut entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
477 let mut expectation = vec![
478 DirEntry { name: "a".to_string(), kind: fio::DirentType::Service },
479 DirEntry { name: "b".to_string(), kind: fio::DirentType::Service },
480 ];
481 entries.sort();
482 expectation.sort();
483 assert_eq!(entries, expectation);
484
485 drop(dir);
486 }
487
488 #[fuchsia::test]
489 async fn test_two_connectors_in_different_namespace_entries() {
490 let scope = ExecutionScope::new();
491 let mut namespace =
492 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
493 namespace.add_object(connector_cap(), &path("/svc1/a")).unwrap();
494 namespace.add_object(connector_cap(), &path("/svc2/b")).unwrap();
495 let ns = namespace.serve().unwrap();
496
497 let ns = ns.flatten();
498 assert_eq!(ns.len(), 2);
499 let (mut svc1, ns): (Vec<_>, Vec<_>) =
500 ns.into_iter().partition(|e| e.path.to_string() == "/svc1");
501 let (mut svc2, _ns): (Vec<_>, Vec<_>) =
502 ns.into_iter().partition(|e| e.path.to_string() == "/svc2");
503
504 {
506 let dir = svc1.pop().unwrap().directory.into_proxy();
507 assert_eq!(
508 fuchsia_fs::directory::readdir(&dir).await.unwrap(),
509 vec![DirEntry { name: "a".to_string(), kind: fio::DirentType::Service },]
510 );
511 }
512 {
513 let dir = svc2.pop().unwrap().directory.into_proxy();
514 assert_eq!(
515 fuchsia_fs::directory::readdir(&dir).await.unwrap(),
516 vec![DirEntry { name: "b".to_string(), kind: fio::DirentType::Service },]
517 );
518 }
519
520 drop(svc1);
521 drop(svc2);
522 }
523
524 #[fuchsia::test]
525 async fn test_not_found() {
526 let (not_found_sender, mut not_found_receiver) = unbounded();
527 let scope = ExecutionScope::new();
528 let mut namespace =
529 NamespaceBuilder::new(scope, not_found_sender, WeakInstanceToken::new_invalid());
530 namespace.add_object(connector_cap(), &path("/svc/a")).unwrap();
531 let ns = namespace.serve().unwrap();
532
533 let mut ns = ns.flatten();
534 assert_eq!(ns.len(), 1);
535 assert_eq!(ns[0].path.to_string(), "/svc");
536
537 let dir = ns.pop().unwrap().directory.into_proxy();
538 let (client_end, server_end) = zx::Channel::create();
539 let _ = fdio::service_connect_at(
540 &dir.into_channel().unwrap().into_zx_channel(),
541 "non_existent",
542 server_end,
543 );
544
545 fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
547
548 assert_eq!(not_found_receiver.next().await, Some("/svc/non_existent".to_string()));
550
551 drop(ns);
552 }
553
554 #[fuchsia::test]
555 async fn test_not_directory() {
556 let (not_found_sender, _) = unbounded();
557 let scope = ExecutionScope::new();
558 let mut namespace =
559 NamespaceBuilder::new(scope, not_found_sender, WeakInstanceToken::new_invalid());
560 let (_, sender) = runtime_capabilities::Connector::new();
561 assert_matches!(
562 namespace.add_entry(Into::into(sender), &ns_path("/a")),
563 Err(BuildNamespaceError::NamespaceError(NamespaceError::EntryError(
564 EntryError::UnsupportedType
565 )))
566 );
567 }
568
569 #[test_case(fio::PERM_READABLE)]
570 #[test_case(fio::PERM_READABLE | fio::PERM_EXECUTABLE)]
571 #[test_case(fio::PERM_READABLE | fio::PERM_WRITABLE)]
572 #[test_case(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE | fio::Flags::PERM_INHERIT_EXECUTE)]
573 #[fuchsia::test]
574 async fn test_directory_rights(rights: fio::Flags) {
575 let (open_tx, mut open_rx) = mpsc::channel::<()>(1);
576
577 struct MockDir {
578 tx: mpsc::Sender<()>,
579 rights: fio::Flags,
580 }
581 impl DirectoryEntry for MockDir {
582 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
583 request.open_remote(self)
584 }
585 }
586 impl GetEntryInfo for MockDir {
587 fn entry_info(&self) -> EntryInfo {
588 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
589 }
590 }
591 impl RemoteLike for MockDir {
592 fn open(
593 self: Arc<Self>,
594 _scope: ExecutionScope,
595 relative_path: path::Path,
596 flags: fio::Flags,
597 _object_request: ObjectRequestRef<'_>,
598 ) -> Result<(), zx::Status> {
599 assert_eq!(relative_path.into_string(), "");
600 assert_eq!(flags, fio::Flags::PROTOCOL_DIRECTORY | self.rights);
601 self.tx.clone().try_send(()).unwrap();
602 Ok(())
603 }
604 }
605
606 let mock = Arc::new(MockDir { tx: open_tx, rights });
607
608 let fs = pseudo_directory! {
609 "foo" => mock,
610 };
611 let dir = DirConnector::from_directory_entry(fs, rights);
612
613 let scope = ExecutionScope::new();
614 let mut namespace =
615 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
616 namespace.add_entry(Into::into(dir), &ns_path("/dir")).unwrap();
617 let mut ns = namespace.serve().unwrap();
618 let dir_proxy = ns.remove(&"/dir".parse().unwrap()).unwrap();
619 let dir_proxy = dir_proxy.into_proxy();
620 let (_, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
621 dir_proxy
622 .open(
623 "foo",
624 fio::Flags::PROTOCOL_DIRECTORY | rights,
625 &fio::Options::default(),
626 server_end.into_channel(),
627 )
628 .unwrap();
629
630 open_rx.next().await.unwrap();
632 }
633
634 #[fuchsia::test]
635 async fn test_directory_non_executable() {
636 let (open_tx, mut open_rx) = mpsc::channel::<()>(1);
637
638 struct MockDir(mpsc::Sender<()>);
639 impl DirectoryEntry for MockDir {
640 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), zx::Status> {
641 request.open_remote(self)
642 }
643 }
644 impl GetEntryInfo for MockDir {
645 fn entry_info(&self) -> EntryInfo {
646 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Directory)
647 }
648 }
649 impl RemoteLike for MockDir {
650 fn open(
651 self: Arc<Self>,
652 _scope: ExecutionScope,
653 relative_path: path::Path,
654 flags: fio::Flags,
655 _object_request: ObjectRequestRef<'_>,
656 ) -> Result<(), zx::Status> {
657 assert_eq!(relative_path.into_string(), "");
658 assert_eq!(flags, fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE);
659 self.0.clone().try_send(()).unwrap();
660 Ok(())
661 }
662 }
663
664 let mock = Arc::new(MockDir(open_tx));
665
666 let fs = pseudo_directory! {
667 "foo" => mock,
668 };
669 let dir = DirConnector::from_directory_entry(fs, fio::PERM_READABLE);
670
671 let scope = ExecutionScope::new();
672 let mut namespace =
673 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
674 namespace.add_entry(Into::into(dir), &ns_path("/dir")).unwrap();
675 let mut ns = namespace.serve().unwrap();
676 let dir_proxy = ns.remove(&"/dir".parse().unwrap()).unwrap();
677 let dir_proxy = dir_proxy.into_proxy();
678
679 let (node, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
681 dir_proxy
682 .open(
683 "foo",
684 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE | fio::PERM_EXECUTABLE,
685 &fio::Options::default(),
686 server_end.into_channel(),
687 )
688 .unwrap();
689 let node = node.into_proxy();
690 let mut node = node.take_event_stream();
691 assert_matches!(
692 node.try_next().await,
693 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
694 );
695
696 let (_, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
698 dir_proxy
699 .open(
700 "foo",
701 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
702 &fio::Options::default(),
703 server_end.into_channel(),
704 )
705 .unwrap();
706
707 open_rx.next().await.unwrap();
709 }
710}