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