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