1use crate::{CapabilityBound, DirReceiver};
6use cm_types::RelativePath;
7use fidl::endpoints::ServerEnd;
8use fidl_fuchsia_io as fio;
9use futures::channel::mpsc;
10use std::fmt::Debug;
11use std::sync::{Arc, LazyLock};
12
13static ALWAYS_ALLOWED_FLAGS: LazyLock<fio::Flags> = LazyLock::new(|| {
16 fio::Flags::PROTOCOL_SERVICE
17 | fio::Flags::PROTOCOL_NODE
18 | fio::Flags::PROTOCOL_DIRECTORY
19 | fio::Flags::PROTOCOL_FILE
20 | fio::Flags::PROTOCOL_SYMLINK
21 | fio::Flags::FLAG_SEND_REPRESENTATION
22 | fio::Flags::FLAG_MAYBE_CREATE
23 | fio::Flags::FLAG_MUST_CREATE
24 | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
25 | fio::Flags::FILE_APPEND
26 | fio::Flags::FILE_TRUNCATE
27});
28
29pub trait DirConnectable: Send + Sync + Debug {
32 fn maximum_flags(&self) -> fio::Flags;
59
60 fn send(
61 &self,
62 dir: ServerEnd<fio::DirectoryMarker>,
63 subdir: RelativePath,
64 flags: Option<fio::Flags>,
65 ) -> Result<(), ()>;
66}
67
68impl DirConnectable for mpsc::UnboundedSender<ServerEnd<fio::DirectoryMarker>> {
69 fn maximum_flags(&self) -> fio::Flags {
70 fio::Flags::empty()
71 }
72
73 fn send(
74 &self,
75 dir: ServerEnd<fio::DirectoryMarker>,
76 subdir: RelativePath,
77 flags: Option<fio::Flags>,
78 ) -> Result<(), ()> {
79 assert_eq!(subdir, RelativePath::dot());
80 assert_eq!(flags, None);
81 self.unbounded_send(dir).map_err(|_| ())
82 }
83}
84
85#[derive(Debug, Clone)]
89pub struct DirConnector {
90 inner: Arc<dyn DirConnectable>,
91}
92
93impl CapabilityBound for DirConnector {
94 fn debug_typename() -> &'static str {
95 "DirConnector"
96 }
97}
98
99impl DirConnector {
100 pub fn new() -> (DirReceiver, Self) {
101 let (sender, receiver) = mpsc::unbounded();
102 let receiver = DirReceiver::new(receiver);
103 let this = Self::new_sendable(sender);
104 (receiver, this)
105 }
106
107 pub fn from_proxy(proxy: fio::DirectoryProxy, subdir: RelativePath, flags: fio::Flags) -> Self {
108 Self::new_sendable(DirectoryProxyForwarder { proxy, subdir, flags })
109 }
110
111 pub fn new_sendable(connector: impl DirConnectable + 'static) -> Self {
112 Self { inner: Arc::new(connector) }
113 }
114
115 pub fn send(
116 &self,
117 dir: ServerEnd<fio::DirectoryMarker>,
118 subdir: RelativePath,
119 mut flags: Option<fio::Flags>,
120 ) -> Result<(), ()> {
121 if let Some(flags) = flags.as_mut() {
122 let mut maximum_flags_and_always_allowed =
123 self.inner.maximum_flags() | *ALWAYS_ALLOWED_FLAGS;
124 if flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
125 if !maximum_flags_and_always_allowed.contains(
126 fio::Flags::from_bits(fio::INHERITED_WRITE_PERMISSIONS.bits()).unwrap(),
127 ) {
128 flags.remove(fio::Flags::PERM_INHERIT_WRITE);
129 } else {
130 maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_WRITE);
131 }
132 }
133 if flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
134 if !maximum_flags_and_always_allowed.contains(fio::Flags::PERM_EXECUTE) {
135 flags.remove(fio::Flags::PERM_INHERIT_EXECUTE);
136 } else {
137 maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_EXECUTE);
138 }
139 }
140 if !maximum_flags_and_always_allowed.contains(*flags) {
141 return Err(());
143 }
144 }
145 self.inner.send(dir, subdir, flags)
146 }
147
148 pub fn with_subdir(self, subdir: RelativePath) -> Self {
149 Self::new_sendable(DirConnectorSubdir { parent_dir_connector: self, subdir })
150 }
151}
152
153impl DirConnectable for DirConnector {
154 fn maximum_flags(&self) -> fio::Flags {
155 self.inner.maximum_flags()
156 }
157
158 fn send(
159 &self,
160 channel: ServerEnd<fio::DirectoryMarker>,
161 subdir: RelativePath,
162 flags: Option<fio::Flags>,
163 ) -> Result<(), ()> {
164 self.inner.send(channel, subdir, flags)
165 }
166}
167
168#[derive(Debug)]
169struct DirConnectorSubdir {
170 parent_dir_connector: DirConnector,
171 subdir: RelativePath,
172}
173
174impl DirConnectable for DirConnectorSubdir {
175 fn maximum_flags(&self) -> fio::Flags {
176 self.parent_dir_connector.maximum_flags()
177 }
178
179 fn send(
180 &self,
181 channel: ServerEnd<fio::DirectoryMarker>,
182 subdir: RelativePath,
183 flags: Option<fio::Flags>,
184 ) -> Result<(), ()> {
185 let mut combined_subdir = self.subdir.clone();
186 let success = combined_subdir.extend(subdir);
187 if !success {
188 return Err(());
190 }
191 self.parent_dir_connector.send(channel, combined_subdir, flags)
192 }
193}
194
195#[derive(Debug)]
196struct DirectoryProxyForwarder {
197 proxy: fio::DirectoryProxy,
198 subdir: RelativePath,
199 flags: fio::Flags,
200}
201
202impl DirConnectable for DirectoryProxyForwarder {
203 fn maximum_flags(&self) -> fio::Flags {
204 self.flags
205 }
206
207 fn send(
208 &self,
209 server_end: ServerEnd<fio::DirectoryMarker>,
210 subdir: RelativePath,
211 flags: Option<fio::Flags>,
212 ) -> Result<(), ()> {
213 let flags = flags.unwrap_or(self.flags | fio::Flags::PROTOCOL_DIRECTORY);
214 let mut combined_subdir = self.subdir.clone();
215 let success = combined_subdir.extend(subdir);
216 if !success {
217 return Err(());
219 }
220 self.proxy
221 .open(
222 &format!("{}", combined_subdir),
223 flags,
224 &fio::Options::default(),
225 server_end.into_channel(),
226 )
227 .map_err(|_| ())
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use fidl::endpoints;
235 use fidl::handle::{HandleBased, Rights};
236 use fidl_fuchsia_component_sandbox as fsandbox;
237 use futures::StreamExt;
238
239 #[fuchsia::test]
244 async fn fidl_clone() {
245 let (receiver, sender) = DirConnector::new();
246
247 let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
249 sender.send(ch2, RelativePath::dot(), None).unwrap();
250
251 let connector: fsandbox::DirConnector = sender.into();
253
254 let token_clone = fsandbox::DirConnector {
256 token: connector.token.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
257 };
258 let connector_clone =
259 match crate::Capability::try_from(fsandbox::Capability::DirConnector(token_clone))
260 .unwrap()
261 {
262 crate::Capability::DirConnector(connector) => connector,
263 capability @ _ => panic!("wrong type {capability:?}"),
264 };
265
266 let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
268 connector_clone.send(ch2, RelativePath::dot(), None).unwrap();
269
270 for _ in 0..2 {
272 let _ch = receiver.receive().await.unwrap();
273 }
274 }
275
276 #[fuchsia::test]
277 async fn flags_check() {
278 #[derive(Debug)]
279 struct DirConnectableStruct {
280 maximum_flags: fio::Flags,
281 sender: mpsc::UnboundedSender<Option<fio::Flags>>,
282 }
283 impl DirConnectable for DirConnectableStruct {
284 fn maximum_flags(&self) -> fio::Flags {
285 self.maximum_flags
286 }
287 fn send(
288 &self,
289 _dir: ServerEnd<fio::DirectoryMarker>,
290 _subdir: RelativePath,
291 flags: Option<fio::Flags>,
292 ) -> Result<(), ()> {
293 self.sender.unbounded_send(flags).unwrap();
294 Ok(())
295 }
296 }
297
298 let (sender, mut receiver) = mpsc::unbounded();
299 let dc1 = DirConnector::new_sendable(DirConnectableStruct {
300 maximum_flags: fio::PERM_READABLE,
301 sender: sender.clone(),
302 });
303
304 for (input_flags, expected_output_flags) in [
305 (None, None),
306 (Some(fio::PERM_READABLE), Some(fio::PERM_READABLE)),
307 (
308 Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
309 Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
310 ),
311 (
312 Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
313 Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
314 ),
315 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
316 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE), Some(fio::PERM_READABLE)),
317 ] {
318 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
319 assert_eq!(
320 Ok(()),
321 dc1.send(server, RelativePath::dot(), input_flags),
322 "failed to send input {input_flags:?}"
323 );
324 assert_eq!(expected_output_flags, receiver.next().await.unwrap());
325 }
326
327 let dc2 = DirConnector::new_sendable(DirConnectableStruct {
328 maximum_flags: fio::PERM_READABLE | fio::PERM_WRITABLE,
329 sender: sender.clone(),
330 });
331 for (input_flags, expected_output_flags) in [
332 (Some(fio::PERM_WRITABLE), Some(fio::PERM_WRITABLE)),
333 (
334 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
335 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
336 ),
337 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE), Some(fio::PERM_READABLE)),
338 ] {
339 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
340 assert_eq!(
341 Ok(()),
342 dc2.send(server, RelativePath::dot(), input_flags),
343 "failed to send input {input_flags:?}"
344 );
345 assert_eq!(expected_output_flags, receiver.next().await.unwrap());
346 }
347
348 let dc3 = DirConnector::new_sendable(DirConnectableStruct {
349 maximum_flags: fio::PERM_READABLE | fio::PERM_EXECUTABLE,
350 sender: sender.clone(),
351 });
352 for (input_flags, expected_output_flags) in [
353 (Some(fio::PERM_EXECUTABLE), Some(fio::PERM_EXECUTABLE)),
354 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
355 (
356 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
357 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
358 ),
359 ] {
360 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
361 assert_eq!(
362 Ok(()),
363 dc3.send(server, RelativePath::dot(), input_flags),
364 "failed to send input {input_flags:?}"
365 );
366 assert_eq!(expected_output_flags, receiver.next().await.unwrap());
367 }
368
369 for (maximum_flags, input_flags) in [
370 (fio::PERM_READABLE, fio::PERM_READABLE | fio::PERM_EXECUTABLE),
371 (fio::PERM_READABLE | fio::PERM_WRITABLE, fio::PERM_EXECUTABLE),
372 (fio::PERM_READABLE | fio::PERM_EXECUTABLE, fio::PERM_WRITABLE),
373 ] {
374 let dc = DirConnector::new_sendable(DirConnectableStruct {
375 maximum_flags,
376 sender: sender.clone(),
377 });
378 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
379 assert_eq!(
380 Err(()),
381 dc.send(server, RelativePath::dot(), Some(input_flags)),
382 "unexpectedly succeeded at sending input {input_flags:?}"
383 );
384 }
385 }
386}