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