Skip to main content

serve_processargs/
namespace.rs

1// Use of this source code is governed by a BSD-style license that can be
2// found in the LICENSE file.
3
4use 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
16/// A builder object for assembling a program's incoming namespace.
17pub struct NamespaceBuilder {
18    /// Mapping from namespace path to capabilities that can be turned into `Directory`.
19    entries: Tree<Capability>,
20
21    /// Path-not-found errors are sent here.
22    not_found: UnboundedSender<String>,
23
24    /// Scope in which the namespace vfs executes.
25    ///
26    /// This can be used to terminate the vfs.
27    namespace_scope: ExecutionScope,
28
29    /// The token for the component whose namespace this is. This is used for route attribution.
30    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    /// Add a capability `cap` at `path`. As a result, the framework will create a
66    /// namespace entry at the parent directory of `path`.
67    pub fn add_object(
68        self: &mut Self,
69        cap: Capability,
70        path: &Path,
71    ) -> Result<(), BuildNamespaceError> {
72        let dirname = path.parent();
73
74        // Get the entry, or if it doesn't exist, make an empty dictionary.
75        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        // Cast the namespace entry as a Dict. This may fail if the user added a duplicate
84        // namespace entry that is not a Dict (see `add_entry`).
85        let dict = match any {
86            Capability::Dictionary(d) => d,
87            _ => Err(NamespaceError::Duplicate(path.clone().into()))?,
88        };
89
90        // Insert the capability into the Dict.
91        dict.insert(path.basename().into(), cap)
92            .map_err(|_| NamespaceError::Duplicate(path.clone().into()).into())
93    }
94
95    /// Add a capability `cap` at `path`. As a result, the framework will create a
96    /// namespace entry at `path` directly. The capability will be exercised when the user
97    /// opens the `path`.
98    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                    // We don't wait to sit around for the results of this task, so we drop the
126                    // fuchsia_async::JoinHandle which causes the task to be detached.
127                    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                        // We set the rights to `None` because no rights are given to us by the
134                        // client for this operation (opening a directory in their namespace). The
135                        // `DirConnector` should apply the "default" rights for this connection, as
136                        // determined by capability routing.
137                        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                    // We don't wait to sit around for the results of this task, so we drop the
145                    // fuchsia_async::JoinHandle which causes the task to be detached.
146                    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                                // See the comment in the `DirConnector` branch for why rights are
156                                // `None`.
157                                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                                // Error logging will be performed by the ErrorReporter router set
167                                // up by sandbox construction, so we don't need to log about
168                                // routing errors here.
169                                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                    // We don't wait to sit around for the results of this task, so we drop the
202                    // fuchsia_async::JoinHandle which causes the task to be detached.
203                    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                                // Error logging will be performed by the ErrorReporter router set
240                                // up by sandbox construction, so we don't need to log about
241                                // routing errors here.
242                                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            // Ignore the result of sending. The receiver is free to break away to ignore all the
261            // not-found errors.
262            let _ = not_found.unbounded_send(requested_path);
263        });
264        new_dict
265    }
266}
267
268/// Returns a disconnected sender which should ignore all the path-not-found errors.
269pub 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        // Adding again will fail.
356        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        // Adding again will fail.
370        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    /// If we added a namespaced object at "/foo/bar", thus creating a namespace entry at "/foo",
391    /// we cannot add another entry directly at "/foo" again.
392    #[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    /// If we directly added an entry at "/foo", it's not possible to add a namespaced object at
406    /// "/foo/bar", as that would've required overwriting "/foo" with a namespace entry served by
407    /// the framework.
408    #[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        // Check that there is exactly one protocol inside the svc directory.
445        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        // Connect to the protocol using namespace functionality.
453        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        // Make sure the server_end is received, and test connectivity.
458        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        // Check that there are exactly two protocols inside the svc directory.
477        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        // Check that there are one protocol inside each directory.
507        {
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        // Server endpoint is closed because the path does not exist.
548        fasync::Channel::from_channel(client_end).on_closed().await.unwrap();
549
550        // We should get a notification about this path.
551        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        // The MockDir should receive the Open request.
633        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        // Try to open as executable. Should fail (ACCESS_DENIED)
682        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        // Try to open as read-only. Should succeed.
699        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        // The MockDir should receive the Open request.
710        open_rx.next().await.unwrap();
711    }
712}