1use crate::common::send_on_open_with_error;
6use crate::directory::common::check_child_connection_flags;
7use crate::directory::entry_container::{Directory, DirectoryWatcher};
8use crate::directory::traversal_position::TraversalPosition;
9use crate::directory::{DirectoryOptions, read_dirents};
10use crate::execution_scope::{ExecutionScope, yield_to_executor};
11use crate::node::OpenNode;
12use crate::object_request::Representation;
13use crate::path::Path;
14use fidl::endpoints::DiscoverableProtocolMarker as _;
15
16use anyhow::Error;
17use fidl::endpoints::ServerEnd;
18use fidl_fuchsia_io as fio;
19use storage_trace::{self as trace, TraceFutureExt};
20use zx_status::Status;
21
22use crate::common::CreationMode;
23use crate::{ObjectRequest, ObjectRequestRef, ProtocolsExt};
24
25pub enum ConnectionState {
27 Alive,
29 Closed,
31}
32
33pub(in crate::directory) struct BaseConnection<DirectoryType: Directory> {
38 pub(in crate::directory) scope: ExecutionScope,
41
42 pub(in crate::directory) directory: OpenNode<DirectoryType>,
43
44 pub(in crate::directory) options: DirectoryOptions,
46
47 seek: TraversalPosition,
62}
63
64impl<DirectoryType: Directory> BaseConnection<DirectoryType> {
65 pub(in crate::directory) fn new(
69 scope: ExecutionScope,
70 directory: OpenNode<DirectoryType>,
71 options: DirectoryOptions,
72 ) -> Self {
73 BaseConnection { scope, directory, options, seek: Default::default() }
74 }
75
76 pub(in crate::directory) async fn handle_request(
79 &mut self,
80 request: fio::DirectoryRequest,
81 ) -> Result<ConnectionState, Error> {
82 match request {
83 #[cfg(any(
84 fuchsia_api_level_at_least = "PLATFORM",
85 not(fuchsia_api_level_at_least = "NEXT")
86 ))]
87 fio::DirectoryRequest::DeprecatedClone { flags, object, control_handle: _ } => {
88 trace::duration!(c"storage", c"Directory::DeprecatedClone");
89 crate::common::send_on_open_with_error(
90 flags.contains(fio::OpenFlags::DESCRIBE),
91 object,
92 Status::NOT_SUPPORTED,
93 );
94 }
95 fio::DirectoryRequest::Clone { request, control_handle: _ } => {
96 trace::duration!(c"storage", c"Directory::Clone");
97 self.handle_clone(request.into_channel());
98 }
99 fio::DirectoryRequest::Close { responder } => {
100 trace::duration!(c"storage", c"Directory::Close");
101 responder.send(Ok(()))?;
102 return Ok(ConnectionState::Closed);
103 }
104 #[cfg(fuchsia_api_level_at_least = "28")]
105 fio::DirectoryRequest::DeprecatedGetAttr { responder } => {
106 async move {
107 let (status, attrs) = crate::common::io2_to_io1_attrs(
108 self.directory.as_ref(),
109 self.options.rights,
110 )
111 .await;
112 responder.send(status.into_raw(), &attrs)
113 }
114 .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttr"))
115 .await?;
116 }
117 #[cfg(not(fuchsia_api_level_at_least = "28"))]
118 fio::DirectoryRequest::GetAttr { responder } => {
119 async move {
120 let (status, attrs) = crate::common::io2_to_io1_attrs(
121 self.directory.as_ref(),
122 self.options.rights,
123 )
124 .await;
125 responder.send(status.into_raw(), &attrs)
126 }
127 .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttr"))
128 .await?;
129 }
130 fio::DirectoryRequest::GetAttributes { query, responder } => {
131 async move {
132 let attrs = self.directory.get_attributes(query).await;
134 responder.send(
135 attrs
136 .as_ref()
137 .map(|attrs| (&attrs.mutable_attributes, &attrs.immutable_attributes))
138 .map_err(|status| status.into_raw()),
139 )
140 }
141 .trace(trace::trace_future_args!(c"storage", c"Directory::GetAttributes"))
142 .await?;
143 }
144 fio::DirectoryRequest::UpdateAttributes { payload: _, responder } => {
145 trace::duration!(c"storage", c"Directory::UpdateAttributes");
146 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
148 }
149 fio::DirectoryRequest::ListExtendedAttributes { iterator, .. } => {
150 trace::duration!(c"storage", c"Directory::ListExtendedAttributes");
151 iterator.close_with_epitaph(Status::NOT_SUPPORTED)?;
152 }
153 fio::DirectoryRequest::GetExtendedAttribute { responder, .. } => {
154 trace::duration!(c"storage", c"Directory::GetExtendedAttribute");
155 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
156 }
157 fio::DirectoryRequest::SetExtendedAttribute { responder, .. } => {
158 trace::duration!(c"storage", c"Directory::SetExtendedAttribute");
159 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
160 }
161 fio::DirectoryRequest::RemoveExtendedAttribute { responder, .. } => {
162 trace::duration!(c"storage", c"Directory::RemoveExtendedAttribute");
163 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
164 }
165 #[cfg(fuchsia_api_level_at_least = "27")]
166 fio::DirectoryRequest::GetFlags { responder } => {
167 trace::duration!(c"storage", c"Directory::GetFlags");
168 responder.send(Ok(fio::Flags::from(&self.options)))?;
169 }
170 #[cfg(fuchsia_api_level_at_least = "27")]
171 fio::DirectoryRequest::SetFlags { flags: _, responder } => {
172 trace::duration!(c"storage", c"Directory::SetFlags");
173 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
174 }
175 #[cfg(fuchsia_api_level_at_least = "27")]
176 fio::DirectoryRequest::DeprecatedGetFlags { responder } => {
177 trace::duration!(c"storage", c"Directory::DeprecatedGetFlags");
178 responder.send(Status::OK.into_raw(), self.options.to_io1())?;
179 }
180 #[cfg(fuchsia_api_level_at_least = "27")]
181 fio::DirectoryRequest::DeprecatedSetFlags { flags: _, responder } => {
182 trace::duration!(c"storage", c"Directory::DeprecatedSetFlags");
183 responder.send(Status::NOT_SUPPORTED.into_raw())?;
184 }
185 #[cfg(not(fuchsia_api_level_at_least = "27"))]
186 fio::DirectoryRequest::GetFlags { responder } => {
187 trace::duration!(c"storage", c"Directory::GetFlags");
188 responder.send(Status::OK.into_raw(), self.options.to_io1())?;
189 }
190 #[cfg(not(fuchsia_api_level_at_least = "27"))]
191 fio::DirectoryRequest::SetFlags { flags: _, responder } => {
192 trace::duration!(c"storage", c"Directory::SetFlags");
193 responder.send(Status::NOT_SUPPORTED.into_raw())?;
194 }
195 #[cfg(fuchsia_api_level_at_least = "27")]
196 fio::DirectoryRequest::DeprecatedOpen {
197 flags,
198 mode: _,
199 path,
200 object,
201 control_handle: _,
202 } => {
203 {
204 trace::duration!(c"storage", c"Directory::Open");
205 self.handle_deprecated_open(flags, path, object);
206 }
207 yield_to_executor().await;
210 }
211 #[cfg(not(fuchsia_api_level_at_least = "27"))]
212 fio::DirectoryRequest::Open { flags, mode: _, path, object, control_handle: _ } => {
213 {
214 trace::duration!(c"storage", c"Directory::Open");
215 self.handle_deprecated_open(flags, path, object);
216 }
217 yield_to_executor().await;
220 }
221 fio::DirectoryRequest::AdvisoryLock { request: _, responder } => {
222 trace::duration!(c"storage", c"Directory::AdvisoryLock");
223 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
224 }
225 fio::DirectoryRequest::ReadDirents { max_bytes, responder } => {
226 async move {
227 let (status, entries) = self.handle_read_dirents(max_bytes).await;
228 responder.send(status.into_raw(), entries.as_slice())
229 }
230 .trace(trace::trace_future_args!(c"storage", c"Directory::ReadDirents"))
231 .await?;
232 }
233 fio::DirectoryRequest::Rewind { responder } => {
234 trace::duration!(c"storage", c"Directory::Rewind");
235 self.seek = Default::default();
236 responder.send(Status::OK.into_raw())?;
237 }
238 fio::DirectoryRequest::Link { src, dst_parent_token, dst, responder } => {
239 async move {
240 let status: Status = self.handle_link(&src, dst_parent_token, dst).await.into();
241 responder.send(status.into_raw())
242 }
243 .trace(trace::trace_future_args!(c"storage", c"Directory::Link"))
244 .await?;
245 }
246 fio::DirectoryRequest::Watch { mask, options, watcher, responder } => {
247 trace::duration!(c"storage", c"Directory::Watch");
248 let status = if options != 0 {
249 Status::INVALID_ARGS
250 } else {
251 self.handle_watch(mask, watcher.into()).into()
252 };
253 responder.send(status.into_raw())?;
254 }
255 fio::DirectoryRequest::Query { responder } => {
256 let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
257 }
258 fio::DirectoryRequest::QueryFilesystem { responder } => {
259 trace::duration!(c"storage", c"Directory::QueryFilesystem");
260 match self.directory.query_filesystem() {
261 Err(status) => responder.send(status.into_raw(), None)?,
262 Ok(info) => responder.send(0, Some(&info))?,
263 }
264 }
265 fio::DirectoryRequest::Unlink { name: _, options: _, responder } => {
266 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
267 }
268 fio::DirectoryRequest::GetToken { responder } => {
269 responder.send(Status::NOT_SUPPORTED.into_raw(), None)?;
270 }
271 fio::DirectoryRequest::Rename { src: _, dst_parent_token: _, dst: _, responder } => {
272 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
273 }
274 #[cfg(fuchsia_api_level_at_least = "28")]
275 fio::DirectoryRequest::DeprecatedSetAttr { flags: _, attributes: _, responder } => {
276 responder.send(Status::NOT_SUPPORTED.into_raw())?;
277 }
278 #[cfg(not(fuchsia_api_level_at_least = "28"))]
279 fio::DirectoryRequest::SetAttr { flags: _, attributes: _, responder } => {
280 responder.send(Status::NOT_SUPPORTED.into_raw())?;
281 }
282 fio::DirectoryRequest::Sync { responder } => {
283 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
284 }
285 fio::DirectoryRequest::CreateSymlink { responder, .. } => {
286 responder.send(Err(Status::NOT_SUPPORTED.into_raw()))?;
287 }
288 #[cfg(fuchsia_api_level_at_least = "27")]
289 fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
290 {
291 if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
293 flags &= !fio::Flags::PERM_INHERIT_WRITE;
294 }
295 if !self.options.rights.contains(fio::Rights::EXECUTE) {
296 flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
297 }
298
299 ObjectRequest::new(flags, &options, object)
300 .handle_async(async |req| self.handle_open(path, flags, req).await)
301 .trace(trace::trace_future_args!(c"storage", c"Directory::Open3"))
302 .await;
303 }
304 yield_to_executor().await;
307 }
308 #[cfg(not(fuchsia_api_level_at_least = "27"))]
309 fio::DirectoryRequest::Open3 {
310 path,
311 mut flags,
312 options,
313 object,
314 control_handle: _,
315 } => {
316 {
317 if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
319 flags &= !fio::Flags::PERM_INHERIT_WRITE;
320 }
321 if !self.options.rights.contains(fio::Rights::EXECUTE) {
322 flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
323 }
324
325 ObjectRequest::new(flags, &options, object)
326 .handle_async(async |req| self.handle_open(path, flags, req).await)
327 .trace(trace::trace_future_args!(c"storage", c"Directory::Open3"))
328 .await;
329 }
330 yield_to_executor().await;
333 }
334 fio::DirectoryRequest::_UnknownMethod { .. } => (),
335 }
336 Ok(ConnectionState::Alive)
337 }
338
339 fn handle_clone(&mut self, object: fidl::Channel) {
340 let flags = fio::Flags::from(&self.options);
341 ObjectRequest::new(flags, &Default::default(), object)
342 .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
343 }
344
345 fn handle_deprecated_open(
346 &self,
347 mut flags: fio::OpenFlags,
348 path: String,
349 server_end: ServerEnd<fio::NodeMarker>,
350 ) {
351 let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
352
353 let path = match Path::validate_and_split(path) {
354 Ok(path) => path,
355 Err(status) => {
356 send_on_open_with_error(describe, server_end, status);
357 return;
358 }
359 };
360
361 if path.is_dir() {
362 flags |= fio::OpenFlags::DIRECTORY;
363 }
364
365 let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
366 Ok(updated) => updated,
367 Err(status) => {
368 send_on_open_with_error(describe, server_end, status);
369 return;
370 }
371 };
372 if path.is_dot() {
373 if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
374 send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
375 return;
376 }
377 if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
378 send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
379 return;
380 }
381 }
382
383 let directory = self.directory.clone();
385 directory.deprecated_open(self.scope.clone(), flags, path, server_end);
386 }
387
388 async fn handle_open(
389 &self,
390 path: String,
391 flags: fio::Flags,
392 object_request: ObjectRequestRef<'_>,
393 ) -> Result<(), Status> {
394 let path = Path::validate_and_split(path)?;
395
396 if let Some(rights) = flags.rights() {
398 if rights.intersects(!self.options.rights) {
399 return Err(Status::ACCESS_DENIED);
400 }
401 }
402
403 if !object_request.attributes().is_empty()
405 && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
406 {
407 return Err(Status::ACCESS_DENIED);
408 }
409
410 match flags.creation_mode() {
411 CreationMode::Never => {
412 if object_request.create_attributes().is_some() {
413 return Err(Status::INVALID_ARGS);
414 }
415 }
416 CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
417 if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
419 return Err(Status::NOT_SUPPORTED);
420 }
421 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
423 return Err(Status::ACCESS_DENIED);
424 }
425 }
432 CreationMode::AllowExisting | CreationMode::Always => {
433 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
435 return Err(Status::ACCESS_DENIED);
436 }
437
438 let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
439 if protocol_flags.is_empty()
442 || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
443 {
444 return Err(Status::INVALID_ARGS);
445 }
446 if !protocol_flags
448 .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
449 {
450 return Err(Status::NOT_SUPPORTED);
451 }
452 }
453 }
454
455 if path.is_dot() && flags.creation_mode() == CreationMode::Always {
456 return Err(Status::ALREADY_EXISTS);
457 }
458
459 self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
460 }
461
462 async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Status, Vec<u8>) {
463 async {
464 let (new_pos, sealed) =
465 self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await?;
466 self.seek = new_pos;
467 let read_dirents::Done { buf, status } = *sealed
468 .open()
469 .downcast::<read_dirents::Done>()
470 .map_err(|_: Box<dyn std::any::Any>| {
471 #[cfg(debug)]
472 panic!(
473 "`read_dirents()` returned a `dirents_sink::Sealed`
474 instance that is not an instance of the \
475 `read_dirents::Done`. This is a bug in the \
476 `read_dirents()` implementation."
477 );
478 Status::NOT_SUPPORTED
479 })?;
480 Ok((status, buf))
481 }
482 .await
483 .unwrap_or_else(|status| (status, Vec::new()))
484 }
485
486 async fn handle_link(
487 &self,
488 source_name: &str,
489 target_parent_token: fidl::Handle,
490 target_name: String,
491 ) -> Result<(), Status> {
492 if source_name.contains('/') || target_name.contains('/') {
493 return Err(Status::INVALID_ARGS);
494 }
495
496 if !self.options.rights.contains(fio::RW_STAR_DIR) {
502 return Err(Status::BAD_HANDLE);
503 }
504
505 let target_parent = self
506 .scope
507 .token_registry()
508 .get_owner(target_parent_token)?
509 .ok_or(Err(Status::NOT_FOUND))?;
510
511 target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
512 }
513
514 fn handle_watch(
515 &mut self,
516 mask: fio::WatchMask,
517 watcher: DirectoryWatcher,
518 ) -> Result<(), Status> {
519 let directory = self.directory.clone();
520 directory.register_watcher(self.scope.clone(), mask, watcher)
521 }
522}
523
524impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
525 type Protocol = fio::DirectoryMarker;
526
527 async fn get_representation(
528 &self,
529 requested_attributes: fio::NodeAttributesQuery,
530 ) -> Result<fio::Representation, Status> {
531 Ok(fio::Representation::Directory(fio::DirectoryInfo {
532 attributes: if requested_attributes.is_empty() {
533 None
534 } else {
535 Some(self.directory.get_attributes(requested_attributes).await?)
536 },
537 ..Default::default()
538 }))
539 }
540
541 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
542 Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use crate::directory::immutable::Simple;
550 use assert_matches::assert_matches;
551 use fidl_fuchsia_io as fio;
552
553 #[fuchsia::test]
554 async fn test_open_not_found() {
555 let dir = Simple::new();
556 let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
557
558 let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
560 &dir_proxy,
561 "foo",
562 fio::PERM_READABLE,
563 )
564 .unwrap();
565
566 assert_matches!(
568 node_proxy.query().await,
569 Err(fidl::Error::ClientChannelClosed {
570 status: Status::NOT_FOUND,
571 protocol_name: "fuchsia.io.Node",
572 ..
573 })
574 );
575 }
576
577 #[fuchsia::test]
578 async fn test_open_with_send_representation_not_found() {
579 let dir = Simple::new();
580 let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
581
582 let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
584 &dir_proxy,
585 "foo",
586 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
587 )
588 .unwrap();
589
590 assert_matches!(
592 node_proxy.query().await,
593 Err(fidl::Error::ClientChannelClosed {
594 status: Status::NOT_FOUND,
595 protocol_name: "fuchsia.io.Node",
596 ..
597 })
598 );
599 }
600}