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