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