1#![warn(missing_docs)]
8
9use crate::common::IntoAny;
10use crate::directory::entry_container::Directory;
11use crate::execution_scope::ExecutionScope;
12use crate::file::{self, FileLike};
13use crate::object_request::ObjectRequestSend;
14use crate::path::Path;
15use crate::service::{self, ServiceLike};
16use crate::symlink::{self, Symlink};
17use crate::{ObjectRequestRef, ToObjectRequest};
18
19use fidl::endpoints::{ClientEnd, create_endpoints};
20use fidl_fuchsia_io as fio;
21use std::fmt;
22use std::future::Future;
23use std::sync::Arc;
24use zx_status::Status;
25
26#[derive(PartialEq, Eq, Clone)]
30pub struct EntryInfo(u64, fio::DirentType);
31
32impl EntryInfo {
33 pub fn new(inode: u64, type_: fio::DirentType) -> Self {
35 Self(inode, type_)
36 }
37
38 pub fn inode(&self) -> u64 {
40 let Self(inode, _type) = self;
41 *inode
42 }
43
44 pub fn type_(&self) -> fio::DirentType {
46 let Self(_inode, type_) = self;
47 *type_
48 }
49}
50
51impl fmt::Debug for EntryInfo {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 let Self(inode, type_) = self;
54 if *inode == fio::INO_UNKNOWN {
55 write!(f, "{:?}(fio::INO_UNKNOWN)", type_)
56 } else {
57 write!(f, "{:?}({})", type_, inode)
58 }
59 }
60}
61
62pub trait GetEntryInfo {
64 fn entry_info(&self) -> EntryInfo;
66}
67
68pub trait DirectoryEntry: GetEntryInfo + IntoAny + Sync + Send + 'static {
74 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status>;
76
77 fn scope(&self) -> Option<ExecutionScope> {
83 None
84 }
85}
86
87pub trait DirectoryEntryAsync: DirectoryEntry {
89 fn open_entry_async(
91 self: Arc<Self>,
92 request: OpenRequest<'_>,
93 ) -> impl Future<Output = Result<(), Status>> + Send;
94}
95
96#[derive(Debug)]
98pub struct OpenRequest<'a> {
99 scope: ExecutionScope,
100 request_flags: RequestFlags,
101 path: Path,
102 object_request: ObjectRequestRef<'a>,
103}
104
105#[derive(Debug)]
110pub enum RequestFlags {
111 #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
113 Open1(fio::OpenFlags),
114 Open3(fio::Flags),
116}
117
118#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
119impl From<fio::OpenFlags> for RequestFlags {
120 fn from(value: fio::OpenFlags) -> Self {
121 RequestFlags::Open1(value)
122 }
123}
124
125impl From<fio::Flags> for RequestFlags {
126 fn from(value: fio::Flags) -> Self {
127 RequestFlags::Open3(value)
128 }
129}
130
131impl<'a> OpenRequest<'a> {
132 pub fn new(
134 scope: ExecutionScope,
135 request_flags: impl Into<RequestFlags>,
136 path: Path,
137 object_request: ObjectRequestRef<'a>,
138 ) -> Self {
139 Self { scope, request_flags: request_flags.into(), path, object_request }
140 }
141
142 pub fn path(&self) -> &Path {
144 &self.path
145 }
146
147 pub fn prepend_path(&mut self, prefix: &Path) {
149 self.path = self.path.with_prefix(prefix);
150 }
151
152 pub fn set_path(&mut self, path: Path) {
154 self.path = path;
155 }
156
157 pub async fn wait_till_ready(&self) -> bool {
161 self.object_request.wait_till_ready().await
162 }
163
164 pub fn requires_event(&self) -> bool {
170 self.object_request.what_to_send() != ObjectRequestSend::Nothing
171 }
172
173 pub fn open_dir(self, dir: Arc<impl Directory>) -> Result<(), Status> {
175 match self {
176 #[cfg(any(
177 fuchsia_api_level_at_least = "PLATFORM",
178 not(fuchsia_api_level_at_least = "NEXT")
179 ))]
180 OpenRequest {
181 scope,
182 request_flags: RequestFlags::Open1(flags),
183 path,
184 object_request,
185 } => {
186 dir.deprecated_open(scope, flags, path, object_request.take().into_server_end());
187 Ok(())
190 }
191 OpenRequest {
192 scope,
193 request_flags: RequestFlags::Open3(flags),
194 path,
195 object_request,
196 } => dir.open(scope, path, flags, object_request),
197 }
198 }
199
200 pub fn open_file(self, file: Arc<impl FileLike>) -> Result<(), Status> {
202 match self {
203 #[cfg(any(
204 fuchsia_api_level_at_least = "PLATFORM",
205 not(fuchsia_api_level_at_least = "NEXT")
206 ))]
207 OpenRequest {
208 scope,
209 request_flags: RequestFlags::Open1(flags),
210 path,
211 object_request,
212 } => {
213 if !path.is_empty() {
214 return Err(Status::NOT_DIR);
215 }
216 file::serve(file, scope, &flags, object_request)
217 }
218 OpenRequest {
219 scope,
220 request_flags: RequestFlags::Open3(flags),
221 path,
222 object_request,
223 } => {
224 if !path.is_empty() {
225 return Err(Status::NOT_DIR);
226 }
227 file::serve(file, scope, &flags, object_request)
228 }
229 }
230 }
231
232 pub fn open_symlink(self, service: Arc<impl Symlink>) -> Result<(), Status> {
234 match self {
235 #[cfg(any(
236 fuchsia_api_level_at_least = "PLATFORM",
237 not(fuchsia_api_level_at_least = "NEXT")
238 ))]
239 OpenRequest {
240 scope,
241 request_flags: RequestFlags::Open1(flags),
242 path,
243 object_request,
244 } => {
245 if !path.is_empty() {
246 return Err(Status::NOT_DIR);
247 }
248 symlink::serve(service, scope, flags, object_request)
249 }
250 OpenRequest {
251 scope,
252 request_flags: RequestFlags::Open3(flags),
253 path,
254 object_request,
255 } => {
256 if !path.is_empty() {
257 return Err(Status::NOT_DIR);
258 }
259 symlink::serve(service, scope, flags, object_request)
260 }
261 }
262 }
263
264 pub fn open_service(self, service: Arc<impl ServiceLike>) -> Result<(), Status> {
266 match self {
267 #[cfg(any(
268 fuchsia_api_level_at_least = "PLATFORM",
269 not(fuchsia_api_level_at_least = "NEXT")
270 ))]
271 OpenRequest {
272 scope,
273 request_flags: RequestFlags::Open1(flags),
274 path,
275 object_request,
276 } => {
277 if !path.is_empty() {
278 return Err(Status::NOT_DIR);
279 }
280 service::serve(service, scope, &flags, object_request)
281 }
282 OpenRequest {
283 scope,
284 request_flags: RequestFlags::Open3(flags),
285 path,
286 object_request,
287 } => {
288 if !path.is_empty() {
289 return Err(Status::NOT_DIR);
290 }
291 service::serve(service, scope, &flags, object_request)
292 }
293 }
294 }
295
296 pub fn open_remote(
298 self,
299 remote: Arc<impl crate::remote::RemoteLike + Send + Sync + 'static>,
300 ) -> Result<(), Status> {
301 match self {
302 #[cfg(any(
303 fuchsia_api_level_at_least = "PLATFORM",
304 not(fuchsia_api_level_at_least = "NEXT")
305 ))]
306 OpenRequest {
307 scope,
308 request_flags: RequestFlags::Open1(flags),
309 path,
310 object_request,
311 } => {
312 if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
313 {
314 let object_request = object_request.take();
315 scope.clone().spawn(async move {
316 if object_request.wait_till_ready().await {
317 remote.deprecated_open(
318 scope,
319 flags,
320 path,
321 object_request.into_server_end(),
322 );
323 }
324 });
325 } else {
326 remote.deprecated_open(
327 scope,
328 flags,
329 path,
330 object_request.take().into_server_end(),
331 );
332 }
333 Ok(())
334 }
335 OpenRequest {
336 scope,
337 request_flags: RequestFlags::Open3(flags),
338 path,
339 object_request,
340 } => {
341 if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
342 {
343 let object_request = object_request.take();
344 scope.clone().spawn(async move {
345 if object_request.wait_till_ready().await {
346 object_request.handle(|object_request| {
347 remote.open(scope, path, flags, object_request)
348 });
349 }
350 });
351 Ok(())
352 } else {
353 remote.open(scope, path, flags, object_request)
354 }
355 }
356 }
357 }
358
359 pub fn spawn(self, entry: Arc<impl DirectoryEntryAsync>) {
361 let OpenRequest { scope, request_flags, path, object_request } = self;
362 let mut object_request = object_request.take();
363 match request_flags {
364 #[cfg(any(
365 fuchsia_api_level_at_least = "PLATFORM",
366 not(fuchsia_api_level_at_least = "NEXT")
367 ))]
368 RequestFlags::Open1(flags) => {
369 scope.clone().spawn(async move {
370 match entry
371 .open_entry_async(OpenRequest::new(
372 scope,
373 RequestFlags::Open1(flags),
374 path,
375 &mut object_request,
376 ))
377 .await
378 {
379 Ok(()) => {}
380 Err(s) => object_request.shutdown(s),
381 }
382 });
383 }
384 RequestFlags::Open3(flags) => {
385 scope.clone().spawn(async move {
386 match entry
387 .open_entry_async(OpenRequest::new(
388 scope,
389 RequestFlags::Open3(flags),
390 path,
391 &mut object_request,
392 ))
393 .await
394 {
395 Ok(()) => {}
396 Err(s) => object_request.shutdown(s),
397 }
398 });
399 }
400 }
401 }
402
403 pub fn scope(&self) -> &ExecutionScope {
405 &self.scope
406 }
407
408 pub fn set_scope(&mut self, scope: ExecutionScope) {
411 self.scope = scope;
412 }
413}
414
415pub struct SubNode<T: ?Sized> {
418 parent: Arc<T>,
419 path: Path,
420 entry_type: fio::DirentType,
421}
422
423impl<T: DirectoryEntry + ?Sized> SubNode<T> {
424 pub fn new(parent: Arc<T>, path: Path, entry_type: fio::DirentType) -> SubNode<T> {
427 assert_eq!(parent.entry_info().type_(), fio::DirentType::Directory);
428 Self { parent, path, entry_type }
429 }
430}
431
432impl<T: DirectoryEntry + ?Sized> GetEntryInfo for SubNode<T> {
433 fn entry_info(&self) -> EntryInfo {
434 EntryInfo::new(fio::INO_UNKNOWN, self.entry_type)
435 }
436}
437
438impl<T: DirectoryEntry + ?Sized> DirectoryEntry for SubNode<T> {
439 fn open_entry(self: Arc<Self>, mut request: OpenRequest<'_>) -> Result<(), Status> {
440 request.path = request.path.with_prefix(&self.path);
441 self.parent.clone().open_entry(request)
442 }
443}
444
445pub fn serve_directory(
448 dir: Arc<impl DirectoryEntry + ?Sized>,
449 scope: &ExecutionScope,
450 flags: fio::Flags,
451) -> Result<ClientEnd<fio::DirectoryMarker>, Status> {
452 assert_eq!(dir.entry_info().type_(), fio::DirentType::Directory);
453 let (client, server) = create_endpoints::<fio::DirectoryMarker>();
454 flags
455 .to_object_request(server)
456 .handle(|object_request| {
457 Ok(dir.open_entry(OpenRequest::new(scope.clone(), flags, Path::dot(), object_request)))
458 })
459 .unwrap()?;
460 Ok(client)
461}
462
463#[cfg(test)]
464mod tests {
465 use super::{
466 DirectoryEntry, DirectoryEntryAsync, EntryInfo, OpenRequest, RequestFlags, SubNode,
467 };
468 use crate::directory::entry::GetEntryInfo;
469 use crate::execution_scope::ExecutionScope;
470 use crate::file::read_only;
471 use crate::path::Path;
472 use crate::{ObjectRequest, assert_read, pseudo_directory};
473 use assert_matches::assert_matches;
474 use fidl::endpoints::create_proxy;
475 use fidl_fuchsia_io as fio;
476 use futures::StreamExt;
477 use std::sync::Arc;
478 use zx_status::Status;
479
480 #[fuchsia::test]
481 async fn sub_node() {
482 let root = pseudo_directory!(
483 "a" => pseudo_directory!(
484 "b" => pseudo_directory!(
485 "c" => pseudo_directory!(
486 "d" => read_only(b"foo")
487 )
488 )
489 )
490 );
491 let sub_node = Arc::new(SubNode::new(
492 root,
493 Path::validate_and_split("a/b").unwrap(),
494 fio::DirentType::Directory,
495 ));
496
497 let root2 = pseudo_directory!(
498 "e" => sub_node
499 );
500
501 let file_proxy = crate::serve_file(
502 root2,
503 Path::validate_and_split("e/c/d").unwrap(),
504 fio::PERM_READABLE,
505 );
506 assert_read!(file_proxy, "foo");
507 }
508
509 #[fuchsia::test]
510 async fn object_request_spawn() {
511 struct MockNode<F: Send + Sync + 'static>
512 where
513 for<'a> F: Fn(OpenRequest<'a>) -> Status,
514 {
515 callback: F,
516 }
517 impl<F: Send + Sync + 'static> DirectoryEntry for MockNode<F>
518 where
519 for<'a> F: Fn(OpenRequest<'a>) -> Status,
520 {
521 fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
522 request.spawn(self);
523 Ok(())
524 }
525 }
526 impl<F: Send + Sync + 'static> GetEntryInfo for MockNode<F>
527 where
528 for<'a> F: Fn(OpenRequest<'a>) -> Status,
529 {
530 fn entry_info(&self) -> EntryInfo {
531 EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Unknown)
532 }
533 }
534 impl<F: Send + Sync + 'static> DirectoryEntryAsync for MockNode<F>
535 where
536 for<'a> F: Fn(OpenRequest<'a>) -> Status,
537 {
538 async fn open_entry_async(
539 self: Arc<Self>,
540 request: OpenRequest<'_>,
541 ) -> Result<(), Status> {
542 Err((self.callback)(request))
543 }
544 }
545
546 let scope = ExecutionScope::new();
547
548 let (proxy, server) = create_proxy::<fio::NodeMarker>();
549 let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FILE_APPEND;
550 let mut object_request =
551 ObjectRequest::new(flags, &Default::default(), server.into_channel());
552
553 Arc::new(MockNode {
554 callback: move |request| {
555 assert_matches!(
556 request,
557 OpenRequest {
558 request_flags: RequestFlags::Open3(f),
559 path,
560 ..
561 } if f == flags && path.as_ref() == "a/b/c"
562 );
563 Status::BAD_STATE
564 },
565 })
566 .open_entry(OpenRequest::new(
567 scope.clone(),
568 flags,
569 "a/b/c".try_into().unwrap(),
570 &mut object_request,
571 ))
572 .unwrap();
573
574 assert_matches!(
575 proxy.take_event_stream().next().await,
576 Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
577 if status == Status::BAD_STATE
578 );
579 }
580}