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