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