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