1use cm_types::{NamespacePath, Path, RelativePath};
5use fidl::endpoints::ClientEnd;
6use futures::channel::mpsc::{UnboundedSender, unbounded};
7use namespace::{Entry as NamespaceEntry, EntryError, Namespace, NamespaceError, Tree};
8use router_error::Explain;
9use sandbox::{Capability, Dict, RemotableCapability, RouterResponse, WeakInstanceToken};
10use thiserror::Error;
11use vfs::directory::entry::serve_directory;
12use vfs::execution_scope::ExecutionScope;
13use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
14
15pub struct NamespaceBuilder {
17 entries: Tree<Capability>,
19
20 not_found: UnboundedSender<String>,
22
23 namespace_scope: ExecutionScope,
27
28 token: WeakInstanceToken,
30}
31
32#[derive(Error, Debug, Clone)]
33pub enum BuildNamespaceError {
34 #[error(transparent)]
35 NamespaceError(#[from] NamespaceError),
36
37 #[error(
38 "while installing capabilities within the namespace entry `{path}`, \
39 failed to convert the namespace entry to Directory: {err}"
40 )]
41 Conversion {
42 path: NamespacePath,
43 #[source]
44 err: sandbox::ConversionError,
45 },
46
47 #[error("unable to serve `{path}` after converting to directory: {err}")]
48 Serve {
49 path: NamespacePath,
50 #[source]
51 err: fidl::Status,
52 },
53}
54
55impl NamespaceBuilder {
56 pub fn new(
57 namespace_scope: ExecutionScope,
58 not_found: UnboundedSender<String>,
59 token: WeakInstanceToken,
60 ) -> Self {
61 return NamespaceBuilder { entries: Default::default(), not_found, namespace_scope, token };
62 }
63
64 pub fn add_object(
67 self: &mut Self,
68 cap: Capability,
69 path: &Path,
70 ) -> Result<(), BuildNamespaceError> {
71 let dirname = path.parent();
72
73 let any = match self.entries.get_mut(&dirname) {
75 Some(dir) => dir,
76 None => {
77 let dict = self.make_dict_with_not_found_logging(dirname.to_string());
78 self.entries.add(&dirname, Capability::Dictionary(dict))?
79 }
80 };
81
82 let dict = match any {
85 Capability::Dictionary(d) => d,
86 _ => Err(NamespaceError::Duplicate(path.clone().into()))?,
87 };
88
89 dict.insert(path.basename().into(), cap)
91 .map_err(|_| NamespaceError::Duplicate(path.clone().into()).into())
92 }
93
94 pub fn add_entry(
98 self: &mut Self,
99 cap: Capability,
100 path: &NamespacePath,
101 ) -> Result<(), BuildNamespaceError> {
102 match &cap {
103 Capability::Directory(_)
104 | Capability::Dictionary(_)
105 | Capability::DirEntry(_)
106 | Capability::DirConnector(_)
107 | Capability::DirConnectorRouter(_)
108 | Capability::DictionaryRouter(_) => {}
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::Directory(d) => d.into(),
120 Capability::DirConnector(c) => {
121 let (client, server) =
122 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
123 let _ = self.namespace_scope.spawn(async move {
126 let res =
127 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
128 if res.is_err() {
129 return;
130 }
131 let _ = c.send(server, RelativePath::dot(), None);
136 });
137 client
138 }
139 Capability::DirConnectorRouter(c) => {
140 let (client, server) =
141 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
142 let token = self.token.clone();
145 let _ = self.namespace_scope.spawn(async move {
146 let res =
147 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
148 if res.is_err() {
149 return;
150 }
151 match c.route(None, false, token.clone()).await {
152 Ok(RouterResponse::Capability(dir_connector)) => {
153 let _ = dir_connector.send(server, RelativePath::dot(), None);
156 }
157 Ok(RouterResponse::Unavailable) => {
158 let _ = server.close_with_epitaph(fidl::Status::NOT_FOUND);
159 }
160 Ok(RouterResponse::Debug(_)) => {
161 panic!("debug response wasn't requested");
162 }
163 Err(e) => {
164 let _ = server.close_with_epitaph(e.as_zx_status());
168 }
169 }
170 });
171 client
172 }
173 Capability::Dictionary(dict) => {
174 let entry = dict
175 .try_into_directory_entry(self.namespace_scope.clone(), self.token.clone())
176 .map_err(|err| BuildNamespaceError::Conversion {
177 path: path.clone(),
178 err,
179 })?;
180 if entry.entry_info().type_() != fio::DirentType::Directory {
181 return Err(BuildNamespaceError::Conversion {
182 path: path.clone(),
183 err: sandbox::ConversionError::NotSupported,
184 });
185 }
186 serve_directory(
187 entry,
188 &self.namespace_scope,
189 fio::Flags::PROTOCOL_DIRECTORY
190 | fio::PERM_READABLE
191 | fio::Flags::PERM_INHERIT_WRITE
192 | fio::Flags::PERM_INHERIT_EXECUTE,
193 )
194 .map_err(|err| BuildNamespaceError::Serve { path: path.clone(), err })?
195 }
196 Capability::DictionaryRouter(router) => {
197 let (client, server) =
198 fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
199 let path = path.clone();
202 let scope = self.namespace_scope.clone();
203 let token = self.token.clone();
204 let _ = self.namespace_scope.spawn(async move {
205 let res =
206 fasync::OnSignals::new(&server, fidl::Signals::OBJECT_READABLE).await;
207 if res.is_err() {
208 return;
209 }
210 match router.route(None, false, token.clone()).await {
211 Ok(RouterResponse::Capability(dictionary)) => {
212 let entry = match dictionary.try_into_directory_entry(scope.clone(), token.clone()) {
213 Ok(entry) => entry,
214 Err(e) => {
215 log::error!("failed to convert namespace dictionary at path {path} into dir entry: {e:?}");
216 return;
217 }
218 };
219 let client_end = serve_directory(
220 entry,
221 &scope,
222 fio::Flags::PROTOCOL_DIRECTORY
223 | fio::PERM_READABLE
224 | fio::Flags::PERM_INHERIT_WRITE
225 | fio::Flags::PERM_INHERIT_EXECUTE,
226 ).expect("failed to serve dictionary as directory");
227 let proxy = client_end.into_proxy();
228 fuchsia_fs::directory::clone_onto(&proxy, server).expect("failed to clone directory we are hosting");
229 }
230 Ok(RouterResponse::Unavailable) => {
231 let _ = server.close_with_epitaph(fidl::Status::NOT_FOUND);
232 }
233 Ok(RouterResponse::Debug(_)) => {
234 panic!("debug response wasn't requested");
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) -> Dict {
255 let not_found = self.not_found.clone();
256 let new_dict = Dict::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 sandbox::{Connector, Directory, 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 use zx::AsHandleRef;
289 use {fidl_fuchsia_io as fio, 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 directory_cap() -> Capability {
302 let (client, _server) = endpoints::create_endpoints();
303 Capability::Directory(Directory::new(client))
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(directory_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(directory_cap(), &ns_path("/svc/a")).expect("");
366 assert_matches!(
368 namespace.add_entry(directory_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(directory_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(directory_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(directory_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: zx::Channel = receiver.receive().await.unwrap().channel.into();
456 client_end.signal_peer(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
457 server_end.wait_handle(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) = sandbox::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 = Directory::from(vfs::directory::serve(fs, rights).into_client_end().unwrap());
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 = Directory::from(
669 vfs::directory::serve(fs, fio::PERM_READABLE).into_client_end().unwrap(),
670 );
671
672 let scope = ExecutionScope::new();
673 let mut namespace =
674 NamespaceBuilder::new(scope, ignore_not_found(), WeakInstanceToken::new_invalid());
675 namespace.add_entry(dir.into(), &ns_path("/dir")).unwrap();
676 let mut ns = namespace.serve().unwrap();
677 let dir_proxy = ns.remove(&"/dir".parse().unwrap()).unwrap();
678 let dir_proxy = dir_proxy.into_proxy();
679
680 let (node, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
682 dir_proxy
683 .open(
684 "foo",
685 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE | fio::PERM_EXECUTABLE,
686 &fio::Options::default(),
687 server_end.into_channel(),
688 )
689 .unwrap();
690 let node = node.into_proxy();
691 let mut node = node.take_event_stream();
692 assert_matches!(
693 node.try_next().await,
694 Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
695 );
696
697 let (_, server_end) = endpoints::create_endpoints::<fio::NodeMarker>();
699 dir_proxy
700 .open(
701 "foo",
702 fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE,
703 &fio::Options::default(),
704 server_end.into_channel(),
705 )
706 .unwrap();
707
708 open_rx.next().await.unwrap();
710 }
711}