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