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
68pub struct DirConnectorMessage {
69 pub dir: ServerEnd<fio::DirectoryMarker>,
70 pub subdir: RelativePath,
71 pub flags: Option<fio::Flags>,
72}
73
74impl DirConnectable for mpsc::UnboundedSender<DirConnectorMessage> {
75 fn maximum_flags(&self) -> fio::Flags {
80 fio::PERM_READABLE | fio::PERM_WRITABLE | fio::PERM_EXECUTABLE
81 }
82
83 fn send(
84 &self,
85 dir: ServerEnd<fio::DirectoryMarker>,
86 subdir: RelativePath,
87 flags: Option<fio::Flags>,
88 ) -> Result<(), ()> {
89 self.unbounded_send(DirConnectorMessage { dir, subdir, flags }).map_err(|_| ())
90 }
91}
92
93#[derive(Debug)]
97pub struct DirConnector {
98 inner: Box<dyn DirConnectable>,
99}
100
101impl CapabilityBound for DirConnector {
102 fn debug_typename() -> &'static str {
103 "DirConnector"
104 }
105
106 #[cfg(target_os = "fuchsia")]
107 fn try_into_directory_entry(
108 self: Arc<Self>,
109 _scope: vfs::execution_scope::ExecutionScope,
110 _token: Arc<crate::WeakInstanceToken>,
111 ) -> Result<Arc<dyn vfs::directory::entry::DirectoryEntry>, crate::ConversionError> {
112 Ok(Arc::new(crate::fidl::dir_connector::DirConnectorDirectoryEntry { dir_connector: self }))
113 }
114}
115
116impl DirConnector {
117 pub fn new() -> (DirReceiver, Arc<Self>) {
118 let (sender, receiver) = mpsc::unbounded();
119 let receiver = DirReceiver::new(receiver);
120 let this = Self::new_sendable(sender);
121 (receiver, this)
122 }
123
124 pub fn from_proxy(
125 proxy: fio::DirectoryProxy,
126 subdir: RelativePath,
127 flags: fio::Flags,
128 ) -> Arc<Self> {
129 Self::new_sendable(DirectoryProxyForwarder { proxy, subdir, flags })
130 }
131
132 pub fn new_sendable(connector: impl DirConnectable + 'static) -> Arc<Self> {
133 Arc::new(Self { inner: Box::new(connector) })
134 }
135
136 pub fn send(
137 &self,
138 dir: ServerEnd<fio::DirectoryMarker>,
139 subdir: RelativePath,
140 mut flags: Option<fio::Flags>,
141 ) -> Result<(), ()> {
142 if let Some(flags) = flags.as_mut() {
143 let mut maximum_flags_and_always_allowed =
144 self.inner.maximum_flags() | *ALWAYS_ALLOWED_FLAGS;
145 if flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
146 if !maximum_flags_and_always_allowed.contains(
147 fio::Flags::from_bits(fio::INHERITED_WRITE_PERMISSIONS.bits()).unwrap(),
148 ) {
149 flags.remove(fio::Flags::PERM_INHERIT_WRITE);
150 } else {
151 maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_WRITE);
152 }
153 }
154 if flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
155 if !maximum_flags_and_always_allowed.contains(fio::Flags::PERM_EXECUTE) {
156 flags.remove(fio::Flags::PERM_INHERIT_EXECUTE);
157 } else {
158 maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_EXECUTE);
159 }
160 }
161 if !maximum_flags_and_always_allowed.contains(*flags) {
162 return Err(());
164 }
165 }
166 self.inner.send(dir, subdir, flags)
167 }
168
169 pub fn with_subdir(self: Arc<Self>, subdir: RelativePath) -> Arc<Self> {
170 Self::new_sendable(DirConnectorSubdir { parent_dir_connector: self, subdir })
171 }
172}
173
174impl DirConnectable for DirConnector {
175 fn maximum_flags(&self) -> fio::Flags {
176 self.inner.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 self.inner.send(channel, subdir, flags)
186 }
187}
188
189#[derive(Debug)]
190struct DirConnectorSubdir {
191 parent_dir_connector: Arc<DirConnector>,
192 subdir: RelativePath,
193}
194
195impl DirConnectable for DirConnectorSubdir {
196 fn maximum_flags(&self) -> fio::Flags {
197 self.parent_dir_connector.maximum_flags()
198 }
199
200 fn send(
201 &self,
202 channel: ServerEnd<fio::DirectoryMarker>,
203 subdir: RelativePath,
204 flags: Option<fio::Flags>,
205 ) -> Result<(), ()> {
206 let mut combined_subdir = self.subdir.clone();
207 let success = combined_subdir.extend(subdir);
208 if !success {
209 return Err(());
211 }
212 self.parent_dir_connector.send(channel, combined_subdir, flags)
213 }
214}
215
216#[derive(Debug)]
217struct DirectoryProxyForwarder {
218 proxy: fio::DirectoryProxy,
219 subdir: RelativePath,
220 flags: fio::Flags,
221}
222
223impl DirConnectable for DirectoryProxyForwarder {
224 fn maximum_flags(&self) -> fio::Flags {
225 self.flags
226 }
227
228 fn send(
229 &self,
230 server_end: ServerEnd<fio::DirectoryMarker>,
231 subdir: RelativePath,
232 flags: Option<fio::Flags>,
233 ) -> Result<(), ()> {
234 let flags = flags.unwrap_or(self.flags | fio::Flags::PROTOCOL_DIRECTORY);
235 let mut combined_subdir = self.subdir.clone();
236 let success = combined_subdir.extend(subdir);
237 if !success {
238 return Err(());
240 }
241 self.proxy
242 .open(
243 &format!("{}", combined_subdir),
244 flags,
245 &fio::Options::default(),
246 server_end.into_channel(),
247 )
248 .map_err(|_| ())
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use fidl::endpoints;
256 use fidl::handle::Rights;
257 use fidl_fuchsia_component_sandbox as fsandbox;
258 use futures::StreamExt;
259
260 #[fuchsia::test]
265 async fn fidl_clone() {
266 let (receiver, sender) = DirConnector::new();
267
268 let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
270 sender.send(ch2, RelativePath::dot(), None).unwrap();
271
272 let connector: fsandbox::DirConnector = sender.to_fsandbox();
274
275 let token_clone = fsandbox::DirConnector {
277 token: connector.token.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
278 };
279 let connector_clone =
280 match crate::Capability::try_from(fsandbox::Capability::DirConnector(token_clone))
281 .unwrap()
282 {
283 crate::Capability::DirConnector(connector) => connector,
284 capability @ _ => panic!("wrong type {capability:?}"),
285 };
286
287 let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
289 connector_clone.send(ch2, RelativePath::dot(), None).unwrap();
290
291 for _ in 0..2 {
293 let _ch = receiver.receive().await.unwrap();
294 }
295 }
296
297 #[fuchsia::test]
298 async fn flags_check() {
299 #[derive(Debug)]
300 struct DirConnectableStruct {
301 maximum_flags: fio::Flags,
302 sender: mpsc::UnboundedSender<Option<fio::Flags>>,
303 }
304 impl DirConnectable for DirConnectableStruct {
305 fn maximum_flags(&self) -> fio::Flags {
306 self.maximum_flags
307 }
308 fn send(
309 &self,
310 _dir: ServerEnd<fio::DirectoryMarker>,
311 _subdir: RelativePath,
312 flags: Option<fio::Flags>,
313 ) -> Result<(), ()> {
314 self.sender.unbounded_send(flags).unwrap();
315 Ok(())
316 }
317 }
318
319 let (sender, mut receiver) = mpsc::unbounded();
320 let dc1 = DirConnector::new_sendable(DirConnectableStruct {
321 maximum_flags: fio::PERM_READABLE,
322 sender: sender.clone(),
323 });
324
325 for (input_flags, expected_output_flags) in [
326 (None, None),
327 (Some(fio::PERM_READABLE), Some(fio::PERM_READABLE)),
328 (
329 Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
330 Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
331 ),
332 (
333 Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
334 Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
335 ),
336 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
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 dc1.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 dc2 = DirConnector::new_sendable(DirConnectableStruct {
349 maximum_flags: fio::PERM_READABLE | fio::PERM_WRITABLE,
350 sender: sender.clone(),
351 });
352 for (input_flags, expected_output_flags) in [
353 (Some(fio::PERM_WRITABLE), Some(fio::PERM_WRITABLE)),
354 (
355 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
356 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
357 ),
358 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE), Some(fio::PERM_READABLE)),
359 ] {
360 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
361 assert_eq!(
362 Ok(()),
363 dc2.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 let dc3 = DirConnector::new_sendable(DirConnectableStruct {
370 maximum_flags: fio::PERM_READABLE | fio::PERM_EXECUTABLE,
371 sender: sender.clone(),
372 });
373 for (input_flags, expected_output_flags) in [
374 (Some(fio::PERM_EXECUTABLE), Some(fio::PERM_EXECUTABLE)),
375 (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
376 (
377 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
378 Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
379 ),
380 ] {
381 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
382 assert_eq!(
383 Ok(()),
384 dc3.send(server, RelativePath::dot(), input_flags),
385 "failed to send input {input_flags:?}"
386 );
387 assert_eq!(expected_output_flags, receiver.next().await.unwrap());
388 }
389
390 for (maximum_flags, input_flags) in [
391 (fio::PERM_READABLE, fio::PERM_READABLE | fio::PERM_EXECUTABLE),
392 (fio::PERM_READABLE | fio::PERM_WRITABLE, fio::PERM_EXECUTABLE),
393 (fio::PERM_READABLE | fio::PERM_EXECUTABLE, fio::PERM_WRITABLE),
394 ] {
395 let dc = DirConnector::new_sendable(DirConnectableStruct {
396 maximum_flags,
397 sender: sender.clone(),
398 });
399 let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
400 assert_eq!(
401 Err(()),
402 dc.send(server, RelativePath::dot(), Some(input_flags)),
403 "unexpectedly succeeded at sending input {input_flags:?}"
404 );
405 }
406 }
407}