sandbox/
dir_entry.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::CapabilityBound;
6use std::fmt;
7
8/// [DirEntry] is a [Capability] that's a thin wrapper over [vfs::directory::entry::DirectoryEntry]
9/// When externalized to FIDL, a [DirEntry] becomes an opaque `eventpair` token. This means that
10/// external users can delegate [DirEntry]s and put them in [Dict]s, but they cannot create their
11/// own.
12///
13/// The [Capability::try_into_directory_entry] implementation simply extracts the inner
14/// [vfs::directory::entry::DirectoryEntry] object.
15///
16/// [DirEntry] is a stopgap for representing CF capabilities that don't have a natural bedrock
17/// representation yet. https://fxbug.dev/340891837 tracks its planned deletion.
18///
19/// When built for host, [DirEntry] is an empty placeholder type, as vfs is not supported on host.
20#[derive(Clone)]
21pub struct DirEntry {
22    #[cfg(target_os = "fuchsia")]
23    pub(crate) entry: std::sync::Arc<dyn vfs::directory::entry::DirectoryEntry>,
24}
25
26impl CapabilityBound for DirEntry {
27    fn debug_typename() -> &'static str {
28        "DirEntry"
29    }
30}
31
32#[cfg(target_os = "fuchsia")]
33impl fmt::Debug for DirEntry {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        f.debug_struct("DirEntry").field("entry_type", &self.entry.entry_info().type_()).finish()
36    }
37}
38
39#[cfg(not(target_os = "fuchsia"))]
40impl fmt::Debug for DirEntry {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.debug_struct("DirEntry").finish()
43    }
44}
45
46#[cfg(target_os = "fuchsia")]
47#[cfg(test)]
48mod tests {
49    use crate::fidl::RemotableCapability;
50    use crate::{Capability, Connector, Dict, DirEntry};
51    use assert_matches::assert_matches;
52    use fidl::endpoints::ClientEnd;
53    use fidl::{AsHandleRef, Channel};
54    use futures::StreamExt;
55    use vfs::directory::entry::OpenRequest;
56    use vfs::execution_scope::ExecutionScope;
57    use vfs::ToObjectRequest as _;
58    use zx::Status;
59    use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
60
61    const FLAGS: fio::Flags = fio::Flags::PROTOCOL_SERVICE;
62
63    #[fuchsia::test]
64    async fn test_connector_into_open() {
65        let (receiver, sender) = Connector::new();
66        let scope = ExecutionScope::new();
67        let dir_entry = DirEntry::new(sender.try_into_directory_entry(scope.clone()).unwrap());
68        let (client_end, server_end) = Channel::create();
69        FLAGS.to_object_request(server_end).handle(|request| {
70            dir_entry.open_entry(OpenRequest::new(scope, FLAGS, vfs::Path::dot(), request))
71        });
72        let msg = receiver.receive().await.unwrap();
73        assert_eq!(
74            client_end.basic_info().unwrap().related_koid,
75            msg.channel.basic_info().unwrap().koid
76        );
77    }
78
79    #[test]
80    fn test_connector_into_open_extra_path() {
81        let mut ex = fasync::TestExecutor::new();
82
83        let (receiver, sender) = Connector::new();
84        let scope = ExecutionScope::new();
85        let dir_entry = DirEntry::new(sender.try_into_directory_entry(scope.clone()).unwrap());
86        let (client_end, server_end) = Channel::create();
87        let path = vfs::Path::validate_and_split("foo").unwrap();
88        FLAGS
89            .to_object_request(server_end)
90            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
91
92        let mut fut = std::pin::pin!(receiver.receive());
93        assert!(ex.run_until_stalled(&mut fut).is_pending());
94
95        let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
96        let node: fio::NodeProxy = client_end.into_proxy();
97        let result = ex.run_singlethreaded(node.take_event_stream().next()).unwrap();
98        assert_matches!(
99            result,
100            Err(fidl::Error::ClientChannelClosed { status, .. })
101            if status == Status::NOT_DIR
102        );
103    }
104
105    #[fuchsia::test]
106    async fn test_connector_into_open_via_dict() {
107        let dict = Dict::new();
108        let (receiver, sender) = Connector::new();
109        dict.insert("echo".parse().unwrap(), Capability::Connector(sender))
110            .expect("dict entry already exists");
111
112        let scope = ExecutionScope::new();
113        let dir_entry = DirEntry::new(dict.try_into_directory_entry(scope.clone()).unwrap());
114        let (client_end, server_end) = Channel::create();
115        let path = vfs::Path::validate_and_split("echo").unwrap();
116        FLAGS
117            .to_object_request(server_end)
118            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
119
120        let msg = receiver.receive().await.unwrap();
121        assert_eq!(
122            client_end.basic_info().unwrap().related_koid,
123            msg.channel.basic_info().unwrap().koid
124        );
125    }
126
127    #[test]
128    fn test_connector_into_open_via_dict_extra_path() {
129        let mut ex = fasync::TestExecutor::new();
130
131        let dict = Dict::new();
132        let (receiver, sender) = Connector::new();
133        dict.insert("echo".parse().unwrap(), Capability::Connector(sender))
134            .expect("dict entry already exists");
135
136        let scope = ExecutionScope::new();
137        let dir_entry = DirEntry::new(dict.try_into_directory_entry(scope.clone()).unwrap());
138        let (client_end, server_end) = Channel::create();
139        let path = vfs::Path::validate_and_split("echo/foo").unwrap();
140        FLAGS
141            .to_object_request(server_end)
142            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
143
144        let mut fut = std::pin::pin!(receiver.receive());
145        assert!(ex.run_until_stalled(&mut fut).is_pending());
146
147        let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
148        let node: fio::NodeProxy = client_end.into_proxy();
149        let result = ex.run_singlethreaded(node.take_event_stream().next()).unwrap();
150        assert_matches!(
151            result,
152            Err(fidl::Error::ClientChannelClosed { status, .. })
153            if status == Status::NOT_DIR
154        );
155    }
156}