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 trace::duration!("storage", "Directory::Query");
256 let () = responder.send(fio::DirectoryMarker::PROTOCOL_NAME.as_bytes())?;
257 }
258 fio::DirectoryRequest::QueryFilesystem { responder } => {
259 trace::duration!("storage", "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 fio::DirectoryRequest::Open { path, mut flags, options, object, control_handle: _ } => {
289 {
290 if !self.options.rights.contains(fio::INHERITED_WRITE_PERMISSIONS) {
292 flags &= !fio::Flags::PERM_INHERIT_WRITE;
293 }
294 if !self.options.rights.contains(fio::Rights::EXECUTE) {
295 flags &= !fio::Flags::PERM_INHERIT_EXECUTE;
296 }
297
298 ObjectRequest::new(flags, &options, object)
299 .handle_async(async |req| self.handle_open(path, flags, req).await)
300 .trace(trace::trace_future_args!("storage", "Directory::Open3"))
301 .await;
302 }
303 yield_to_executor().await;
306 }
307 fio::DirectoryRequest::_UnknownMethod { .. } => (),
308 }
309 Ok(ConnectionState::Alive)
310 }
311
312 fn handle_clone(&mut self, object: fidl::Channel) {
313 let flags = fio::Flags::from(&self.options);
314 ObjectRequest::new(flags, &Default::default(), object)
315 .handle(|req| self.directory.clone().open(self.scope.clone(), Path::dot(), flags, req));
316 }
317
318 fn handle_deprecated_open(
319 &self,
320 mut flags: fio::OpenFlags,
321 path: String,
322 server_end: ServerEnd<fio::NodeMarker>,
323 ) {
324 let describe = flags.intersects(fio::OpenFlags::DESCRIBE);
325
326 let path = match Path::validate_and_split(path) {
327 Ok(path) => path,
328 Err(status) => {
329 send_on_open_with_error(describe, server_end, status);
330 return;
331 }
332 };
333
334 if path.is_dir() {
335 flags |= fio::OpenFlags::DIRECTORY;
336 }
337
338 let flags = match check_child_connection_flags(self.options.to_io1(), flags) {
339 Ok(updated) => updated,
340 Err(status) => {
341 send_on_open_with_error(describe, server_end, status);
342 return;
343 }
344 };
345 if path.is_dot() {
346 if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
347 send_on_open_with_error(describe, server_end, Status::INVALID_ARGS);
348 return;
349 }
350 if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT) {
351 send_on_open_with_error(describe, server_end, Status::ALREADY_EXISTS);
352 return;
353 }
354 }
355
356 let directory = self.directory.clone();
358 directory.deprecated_open(self.scope.clone(), flags, path, server_end);
359 }
360
361 async fn handle_open(
362 &self,
363 path: String,
364 flags: fio::Flags,
365 object_request: ObjectRequestRef<'_>,
366 ) -> Result<(), Status> {
367 let path = Path::validate_and_split(path)?;
368
369 if let Some(rights) = flags.rights() {
371 if rights.intersects(!self.options.rights) {
372 return Err(Status::ACCESS_DENIED);
373 }
374 }
375
376 if !object_request.attributes().is_empty()
378 && !self.options.rights.contains(fio::Operations::GET_ATTRIBUTES)
379 {
380 return Err(Status::ACCESS_DENIED);
381 }
382
383 match flags.creation_mode() {
384 CreationMode::Never => {
385 if object_request.create_attributes().is_some() {
386 return Err(Status::INVALID_ARGS);
387 }
388 }
389 CreationMode::UnnamedTemporary | CreationMode::UnlinkableUnnamedTemporary => {
390 if !flags.intersects(fio::Flags::PROTOCOL_FILE) {
392 return Err(Status::NOT_SUPPORTED);
393 }
394 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
396 return Err(Status::ACCESS_DENIED);
397 }
398 }
405 CreationMode::AllowExisting | CreationMode::Always => {
406 if !self.options.rights.contains(fio::Rights::MODIFY_DIRECTORY) {
408 return Err(Status::ACCESS_DENIED);
409 }
410
411 let protocol_flags = flags & fio::MASK_KNOWN_PROTOCOLS;
412 if protocol_flags.is_empty()
415 || (protocol_flags.bits() & (protocol_flags.bits() - 1)) != 0
416 {
417 return Err(Status::INVALID_ARGS);
418 }
419 if !protocol_flags
421 .intersects(fio::Flags::PROTOCOL_DIRECTORY | fio::Flags::PROTOCOL_FILE)
422 {
423 return Err(Status::NOT_SUPPORTED);
424 }
425 }
426 }
427
428 if path.is_dot() && flags.creation_mode() == CreationMode::Always {
429 return Err(Status::ALREADY_EXISTS);
430 }
431
432 self.directory.clone().open_async(self.scope.clone(), path, flags, object_request).await
433 }
434
435 async fn handle_read_dirents(&mut self, max_bytes: u64) -> (Status, Vec<u8>) {
436 async {
437 let (new_pos, sealed) =
438 self.directory.read_dirents(&self.seek, read_dirents::Sink::new(max_bytes)).await?;
439 self.seek = new_pos;
440 let read_dirents::Done { buf, status } = *sealed
441 .open()
442 .downcast::<read_dirents::Done>()
443 .map_err(|_: Box<dyn std::any::Any>| {
444 #[cfg(debug)]
445 panic!(
446 "`read_dirents()` returned a `dirents_sink::Sealed`
447 instance that is not an instance of the \
448 `read_dirents::Done`. This is a bug in the \
449 `read_dirents()` implementation."
450 );
451 Status::NOT_SUPPORTED
452 })?;
453 Ok((status, buf))
454 }
455 .await
456 .unwrap_or_else(|status| (status, Vec::new()))
457 }
458
459 async fn handle_link(
460 &self,
461 source_name: &str,
462 target_parent_token: fidl::NullableHandle,
463 target_name: String,
464 ) -> Result<(), Status> {
465 if source_name.contains('/') || target_name.contains('/') {
466 return Err(Status::INVALID_ARGS);
467 }
468
469 if !self.options.rights.contains(fio::RW_STAR_DIR) {
475 return Err(Status::BAD_HANDLE);
476 }
477
478 let target_parent = self
479 .scope
480 .token_registry()
481 .get_owner(target_parent_token)?
482 .ok_or(Err(Status::NOT_FOUND))?;
483
484 target_parent.link(target_name, self.directory.clone().into_any(), source_name).await
485 }
486
487 fn handle_watch(
488 &mut self,
489 mask: fio::WatchMask,
490 watcher: DirectoryWatcher,
491 ) -> Result<(), Status> {
492 let directory = self.directory.clone();
493 directory.register_watcher(self.scope.clone(), mask, watcher)
494 }
495
496 async fn handle_list_extended_attribute(
497 &self,
498 iterator: ServerEnd<fio::ExtendedAttributeIteratorMarker>,
499 ) {
500 let attributes = match self.directory.list_extended_attributes().await {
501 Ok(attributes) => attributes,
502 Err(status) => {
503 #[cfg(any(test, feature = "use_log"))]
504 log::error!(status:?; "list extended attributes failed");
505 #[allow(clippy::unnecessary_lazy_evaluations)]
506 iterator.close_with_epitaph(status).unwrap_or_else(|_error| {
507 #[cfg(any(test, feature = "use_log"))]
508 log::error!(_error:?; "failed to send epitaph")
509 });
510 return;
511 }
512 };
513 self.scope.spawn(extended_attributes_sender(iterator, attributes));
514 }
515
516 async fn handle_get_extended_attribute(
517 &self,
518 name: Vec<u8>,
519 ) -> Result<fio::ExtendedAttributeValue, Status> {
520 let value = self.directory.get_extended_attribute(name).await?;
521 encode_extended_attribute_value(value)
522 }
523
524 async fn handle_set_extended_attribute(
525 &self,
526 name: Vec<u8>,
527 value: fio::ExtendedAttributeValue,
528 mode: fio::SetExtendedAttributeMode,
529 ) -> Result<(), Status> {
530 if name.contains(&0) {
531 return Err(Status::INVALID_ARGS);
532 }
533 let val = decode_extended_attribute_value(value)?;
534 self.directory.set_extended_attribute(name, val, mode).await
535 }
536
537 async fn handle_remove_extended_attribute(&self, name: Vec<u8>) -> Result<(), Status> {
538 self.directory.remove_extended_attribute(name).await
539 }
540}
541
542impl<DirectoryType: Directory> Representation for BaseConnection<DirectoryType> {
543 type Protocol = fio::DirectoryMarker;
544
545 async fn get_representation(
546 &self,
547 requested_attributes: fio::NodeAttributesQuery,
548 ) -> Result<fio::Representation, Status> {
549 Ok(fio::Representation::Directory(fio::DirectoryInfo {
550 attributes: if requested_attributes.is_empty() {
551 None
552 } else {
553 Some(self.directory.get_attributes(requested_attributes).await?)
554 },
555 ..Default::default()
556 }))
557 }
558
559 async fn node_info(&self) -> Result<fio::NodeInfoDeprecated, Status> {
560 Ok(fio::NodeInfoDeprecated::Directory(fio::DirectoryObject))
561 }
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::directory::immutable::Simple;
568 use assert_matches::assert_matches;
569 use fidl_fuchsia_io as fio;
570
571 #[fuchsia::test]
572 async fn test_open_not_found() {
573 let dir = Simple::new();
574 let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
575
576 let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
578 &dir_proxy,
579 "foo",
580 fio::PERM_READABLE,
581 )
582 .unwrap();
583
584 assert_matches!(
586 node_proxy.query().await,
587 Err(fidl::Error::ClientChannelClosed {
588 status: Status::NOT_FOUND,
589 protocol_name: "fuchsia.io.Node",
590 ..
591 })
592 );
593 }
594
595 #[fuchsia::test]
596 async fn test_open_with_send_representation_not_found() {
597 let dir = Simple::new();
598 let dir_proxy = crate::directory::serve(dir, fio::PERM_READABLE);
599
600 let node_proxy = fuchsia_fs::directory::open_async::<fio::NodeMarker>(
602 &dir_proxy,
603 "foo",
604 fio::PERM_READABLE | fio::Flags::FLAG_SEND_REPRESENTATION,
605 )
606 .unwrap();
607
608 assert_matches!(
610 node_proxy.query().await,
611 Err(fidl::Error::ClientChannelClosed {
612 status: Status::NOT_FOUND,
613 protocol_name: "fuchsia.io.Node",
614 ..
615 })
616 );
617 }
618}