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, WeakInstanceToken};
51    use assert_matches::assert_matches;
52    use fidl::endpoints::ClientEnd;
53    use fidl::{AsHandleRef, Channel};
54    use futures::StreamExt;
55    use vfs::ToObjectRequest as _;
56    use vfs::directory::entry::OpenRequest;
57    use vfs::execution_scope::ExecutionScope;
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(
68            sender
69                .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
70                .unwrap(),
71        );
72        let (client_end, server_end) = Channel::create();
73        FLAGS.to_object_request(server_end).handle(|request| {
74            dir_entry.open_entry(OpenRequest::new(scope, FLAGS, vfs::Path::dot(), request))
75        });
76        let msg = receiver.receive().await.unwrap();
77        assert_eq!(
78            client_end.basic_info().unwrap().related_koid,
79            msg.channel.basic_info().unwrap().koid
80        );
81    }
82
83    #[test]
84    fn test_connector_into_open_extra_path() {
85        let mut ex = fasync::TestExecutor::new();
86
87        let (receiver, sender) = Connector::new();
88        let scope = ExecutionScope::new();
89        let dir_entry = DirEntry::new(
90            sender
91                .try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid())
92                .unwrap(),
93        );
94        let (client_end, server_end) = Channel::create();
95        let path = vfs::Path::validate_and_split("foo").unwrap();
96        FLAGS
97            .to_object_request(server_end)
98            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
99
100        let mut fut = std::pin::pin!(receiver.receive());
101        assert!(ex.run_until_stalled(&mut fut).is_pending());
102
103        let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
104        let node: fio::NodeProxy = client_end.into_proxy();
105        let result = ex.run_singlethreaded(node.take_event_stream().next()).unwrap();
106        assert_matches!(
107            result,
108            Err(fidl::Error::ClientChannelClosed { status, .. })
109            if status == Status::NOT_DIR
110        );
111    }
112
113    #[fuchsia::test]
114    async fn test_connector_into_open_via_dict() {
115        let dict = Dict::new();
116        let (receiver, sender) = Connector::new();
117        dict.insert("echo".parse().unwrap(), Capability::Connector(sender))
118            .expect("dict entry already exists");
119
120        let scope = ExecutionScope::new();
121        let dir_entry = DirEntry::new(
122            dict.try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid()).unwrap(),
123        );
124        let (client_end, server_end) = Channel::create();
125        let path = vfs::Path::validate_and_split("echo").unwrap();
126        FLAGS
127            .to_object_request(server_end)
128            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
129
130        let msg = receiver.receive().await.unwrap();
131        assert_eq!(
132            client_end.basic_info().unwrap().related_koid,
133            msg.channel.basic_info().unwrap().koid
134        );
135    }
136
137    #[test]
138    fn test_connector_into_open_via_dict_extra_path() {
139        let mut ex = fasync::TestExecutor::new();
140
141        let dict = Dict::new();
142        let (receiver, sender) = Connector::new();
143        dict.insert("echo".parse().unwrap(), Capability::Connector(sender))
144            .expect("dict entry already exists");
145
146        let scope = ExecutionScope::new();
147        let dir_entry = DirEntry::new(
148            dict.try_into_directory_entry(scope.clone(), WeakInstanceToken::new_invalid()).unwrap(),
149        );
150        let (client_end, server_end) = Channel::create();
151        let path = vfs::Path::validate_and_split("echo/foo").unwrap();
152        FLAGS
153            .to_object_request(server_end)
154            .handle(|request| dir_entry.open_entry(OpenRequest::new(scope, FLAGS, path, request)));
155
156        let mut fut = std::pin::pin!(receiver.receive());
157        assert!(ex.run_until_stalled(&mut fut).is_pending());
158
159        let client_end: ClientEnd<fio::NodeMarker> = client_end.into();
160        let node: fio::NodeProxy = client_end.into_proxy();
161        let result = ex.run_singlethreaded(node.take_event_stream().next()).unwrap();
162        assert_matches!(
163            result,
164            Err(fidl::Error::ClientChannelClosed { status, .. })
165            if status == Status::NOT_DIR
166        );
167    }
168}