vfs/directory/
entry.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Common trait for all the directory entry objects.
6
7#![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::{create_endpoints, ClientEnd};
20use fidl_fuchsia_io as fio;
21use std::fmt;
22use std::future::Future;
23use std::sync::Arc;
24use zx_status::Status;
25
26/// Information about a directory entry, used to populate ReadDirents() output.
27/// The first element is the inode number, or INO_UNKNOWN (from fuchsia.io) if not set, and the second
28/// element is one of the DIRENT_TYPE_* constants defined in the fuchsia.io.
29#[derive(PartialEq, Eq, Clone)]
30pub struct EntryInfo(u64, fio::DirentType);
31
32impl EntryInfo {
33    /// Constructs a new directory entry information object.
34    pub fn new(inode: u64, type_: fio::DirentType) -> Self {
35        Self(inode, type_)
36    }
37
38    /// Retrives the `inode` argument of the [`EntryInfo::new()`] constructor.
39    pub fn inode(&self) -> u64 {
40        let Self(inode, _type) = self;
41        *inode
42    }
43
44    /// Retrieves the `type_` argument of the [`EntryInfo::new()`] constructor.
45    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
62/// Give useful information about the entry, for example, the directory entry type.
63pub trait GetEntryInfo {
64    /// This method is used to populate ReadDirents() output.
65    fn entry_info(&self) -> EntryInfo;
66}
67
68/// Pseudo directories contain items that implement this trait.  Pseudo directories refer to the
69/// items they contain as `Arc<dyn DirectoryEntry>`.
70///
71/// *NOTE*: This trait only needs to be implemented if you want to add your nodes to a pseudo
72/// directory.
73pub trait DirectoryEntry: GetEntryInfo + IntoAny + Sync + Send + 'static {
74    /// Opens this entry.
75    fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status>;
76}
77
78/// Trait that can be implemented to process open requests asynchronously.
79pub trait DirectoryEntryAsync: DirectoryEntry {
80    /// Implementers may use this if desired by using the `spawn` method below.
81    fn open_entry_async(
82        self: Arc<Self>,
83        request: OpenRequest<'_>,
84    ) -> impl Future<Output = Result<(), Status>> + Send;
85}
86
87/// An open request.
88#[derive(Debug)]
89pub struct OpenRequest<'a> {
90    scope: ExecutionScope,
91    request_flags: RequestFlags,
92    path: Path,
93    object_request: ObjectRequestRef<'a>,
94}
95
96/// Wraps flags used for open requests based on which fuchsia.io/Directory.Open method was used.
97/// Used to delegate [`OpenRequest`] to the corresponding method when the entry is opened.
98#[derive(Debug)]
99pub enum RequestFlags {
100    /// fuchsia.io/Directory.Open1 (io1)
101    Open1(fio::OpenFlags),
102    /// fuchsia.io/Directory.Open3 (io2)
103    Open3(fio::Flags),
104}
105
106impl From<fio::OpenFlags> for RequestFlags {
107    fn from(value: fio::OpenFlags) -> Self {
108        RequestFlags::Open1(value)
109    }
110}
111
112impl From<fio::Flags> for RequestFlags {
113    fn from(value: fio::Flags) -> Self {
114        RequestFlags::Open3(value)
115    }
116}
117
118impl<'a> OpenRequest<'a> {
119    /// Creates a new open request.
120    pub fn new(
121        scope: ExecutionScope,
122        request_flags: impl Into<RequestFlags>,
123        path: Path,
124        object_request: ObjectRequestRef<'a>,
125    ) -> Self {
126        Self { scope, request_flags: request_flags.into(), path, object_request }
127    }
128
129    /// Returns the path for this request.
130    pub fn path(&self) -> &Path {
131        &self.path
132    }
133
134    /// Prepends `prefix` to the path.
135    pub fn prepend_path(&mut self, prefix: &Path) {
136        self.path = self.path.with_prefix(prefix);
137    }
138
139    /// Sets the path to `path`.
140    pub fn set_path(&mut self, path: Path) {
141        self.path = path;
142    }
143
144    /// Waits until the request has a request waiting in its channel.  Returns immediately if this
145    /// request requires sending an initial event such as OnOpen or OnRepresentation.  Returns
146    /// `true` if the channel is readable (rather than just clased).
147    pub async fn wait_till_ready(&self) -> bool {
148        self.object_request.wait_till_ready().await
149    }
150
151    /// Returns `true` if the request requires the server to send an event (e.g. either OnOpen or
152    /// OnRepresentation).  If `true`, `wait_till_ready` will return immediately.  If `false`, the
153    /// caller might choose to call `wait_till_ready` if other conditions are satisfied (checking
154    /// for an empty path is usually a good idea since it is hard to know where a non-empty path
155    /// might end up being terminated).
156    pub fn requires_event(&self) -> bool {
157        self.object_request.what_to_send() != ObjectRequestSend::Nothing
158    }
159
160    /// Opens a directory.
161    pub fn open_dir(self, dir: Arc<impl Directory>) -> Result<(), Status> {
162        match self {
163            OpenRequest {
164                scope,
165                request_flags: RequestFlags::Open1(flags),
166                path,
167                object_request,
168            } => {
169                dir.deprecated_open(scope, flags, path, object_request.take().into_server_end());
170                // This will cause issues for heavily nested directory structures because it thwarts
171                // tail recursion optimization, but that shouldn't occur in practice.
172                Ok(())
173            }
174            OpenRequest {
175                scope,
176                request_flags: RequestFlags::Open3(flags),
177                path,
178                object_request,
179            } => dir.open(scope, path, flags, object_request),
180        }
181    }
182
183    /// Opens a file.
184    pub fn open_file(self, file: Arc<impl FileLike>) -> Result<(), Status> {
185        match self {
186            OpenRequest {
187                scope,
188                request_flags: RequestFlags::Open1(flags),
189                path,
190                object_request,
191            } => {
192                if !path.is_empty() {
193                    return Err(Status::NOT_DIR);
194                }
195                file::serve(file, scope, &flags, object_request)
196            }
197            OpenRequest {
198                scope,
199                request_flags: RequestFlags::Open3(flags),
200                path,
201                object_request,
202            } => {
203                if !path.is_empty() {
204                    return Err(Status::NOT_DIR);
205                }
206                file::serve(file, scope, &flags, object_request)
207            }
208        }
209    }
210
211    /// Opens a symlink.
212    pub fn open_symlink(self, service: Arc<impl Symlink>) -> Result<(), Status> {
213        match self {
214            OpenRequest {
215                scope,
216                request_flags: RequestFlags::Open1(flags),
217                path,
218                object_request,
219            } => {
220                if !path.is_empty() {
221                    return Err(Status::NOT_DIR);
222                }
223                symlink::serve(service, scope, flags, object_request)
224            }
225            OpenRequest {
226                scope,
227                request_flags: RequestFlags::Open3(flags),
228                path,
229                object_request,
230            } => {
231                if !path.is_empty() {
232                    return Err(Status::NOT_DIR);
233                }
234                symlink::serve(service, scope, flags, object_request)
235            }
236        }
237    }
238
239    /// Opens a service.
240    pub fn open_service(self, service: Arc<impl ServiceLike>) -> Result<(), Status> {
241        match self {
242            OpenRequest {
243                scope,
244                request_flags: RequestFlags::Open1(flags),
245                path,
246                object_request,
247            } => {
248                if !path.is_empty() {
249                    return Err(Status::NOT_DIR);
250                }
251                service::serve(service, scope, &flags, object_request)
252            }
253            OpenRequest {
254                scope,
255                request_flags: RequestFlags::Open3(flags),
256                path,
257                object_request,
258            } => {
259                if !path.is_empty() {
260                    return Err(Status::NOT_DIR);
261                }
262                service::serve(service, scope, &flags, object_request)
263            }
264        }
265    }
266
267    /// Forwards the request to a remote.
268    pub fn open_remote(
269        self,
270        remote: Arc<impl crate::remote::RemoteLike + Send + Sync + 'static>,
271    ) -> Result<(), Status> {
272        match self {
273            OpenRequest {
274                scope,
275                request_flags: RequestFlags::Open1(flags),
276                path,
277                object_request,
278            } => {
279                if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
280                {
281                    let object_request = object_request.take();
282                    scope.clone().spawn(async move {
283                        if object_request.wait_till_ready().await {
284                            remote.deprecated_open(
285                                scope,
286                                flags,
287                                path,
288                                object_request.into_server_end(),
289                            );
290                        }
291                    });
292                } else {
293                    remote.deprecated_open(
294                        scope,
295                        flags,
296                        path,
297                        object_request.take().into_server_end(),
298                    );
299                }
300                Ok(())
301            }
302            OpenRequest {
303                scope,
304                request_flags: RequestFlags::Open3(flags),
305                path,
306                object_request,
307            } => {
308                if object_request.what_to_send() == ObjectRequestSend::Nothing && remote.lazy(&path)
309                {
310                    let object_request = object_request.take();
311                    scope.clone().spawn(async move {
312                        if object_request.wait_till_ready().await {
313                            object_request.handle(|object_request| {
314                                remote.open(scope, path, flags, object_request)
315                            });
316                        }
317                    });
318                    Ok(())
319                } else {
320                    remote.open(scope, path, flags, object_request)
321                }
322            }
323        }
324    }
325
326    /// Spawns a task to handle the request.  `entry` must implement DirectoryEntryAsync.
327    pub fn spawn(self, entry: Arc<impl DirectoryEntryAsync>) {
328        let OpenRequest { scope, request_flags, path, object_request } = self;
329        let mut object_request = object_request.take();
330        match request_flags {
331            RequestFlags::Open1(flags) => {
332                scope.clone().spawn(async move {
333                    match entry
334                        .open_entry_async(OpenRequest::new(
335                            scope,
336                            RequestFlags::Open1(flags),
337                            path,
338                            &mut object_request,
339                        ))
340                        .await
341                    {
342                        Ok(()) => {}
343                        Err(s) => object_request.shutdown(s),
344                    }
345                });
346            }
347            RequestFlags::Open3(flags) => {
348                scope.clone().spawn(async move {
349                    match entry
350                        .open_entry_async(OpenRequest::new(
351                            scope,
352                            RequestFlags::Open3(flags),
353                            path,
354                            &mut object_request,
355                        ))
356                        .await
357                    {
358                        Ok(()) => {}
359                        Err(s) => object_request.shutdown(s),
360                    }
361                });
362            }
363        }
364    }
365
366    /// Returns the execution scope for this request.
367    pub fn scope(&self) -> &ExecutionScope {
368        &self.scope
369    }
370
371    /// Replaces the scope in this request.  This is the right thing to do if any subsequently
372    /// spawned tasks should be in a different scope to the task that received this open request.
373    pub fn set_scope(&mut self, scope: ExecutionScope) {
374        self.scope = scope;
375    }
376}
377
378/// A sub-node of a directory.  This will work with types that implement Directory as well as
379/// RemoteDir.
380pub struct SubNode<T: ?Sized> {
381    parent: Arc<T>,
382    path: Path,
383    entry_type: fio::DirentType,
384}
385
386impl<T: DirectoryEntry + ?Sized> SubNode<T> {
387    /// Returns a sub node of an existing entry.  The parent should be a directory (it accepts
388    /// DirectoryEntry so that it works for remotes).
389    pub fn new(parent: Arc<T>, path: Path, entry_type: fio::DirentType) -> SubNode<T> {
390        assert_eq!(parent.entry_info().type_(), fio::DirentType::Directory);
391        Self { parent, path, entry_type }
392    }
393}
394
395impl<T: DirectoryEntry + ?Sized> GetEntryInfo for SubNode<T> {
396    fn entry_info(&self) -> EntryInfo {
397        EntryInfo::new(fio::INO_UNKNOWN, self.entry_type)
398    }
399}
400
401impl<T: DirectoryEntry + ?Sized> DirectoryEntry for SubNode<T> {
402    fn open_entry(self: Arc<Self>, mut request: OpenRequest<'_>) -> Result<(), Status> {
403        request.path = request.path.with_prefix(&self.path);
404        self.parent.clone().open_entry(request)
405    }
406}
407
408/// Serves a directory with the given rights.  Returns a client end.  This takes a DirectoryEntry
409/// so that it works for remotes.
410pub fn serve_directory(
411    dir: Arc<impl DirectoryEntry + ?Sized>,
412    scope: &ExecutionScope,
413    flags: fio::Flags,
414) -> Result<ClientEnd<fio::DirectoryMarker>, Status> {
415    assert_eq!(dir.entry_info().type_(), fio::DirentType::Directory);
416    let (client, server) = create_endpoints::<fio::DirectoryMarker>();
417    flags
418        .to_object_request(server)
419        .handle(|object_request| {
420            Ok(dir.open_entry(OpenRequest::new(scope.clone(), flags, Path::dot(), object_request)))
421        })
422        .unwrap()?;
423    Ok(client)
424}
425
426#[cfg(test)]
427mod tests {
428    use super::{
429        DirectoryEntry, DirectoryEntryAsync, EntryInfo, OpenRequest, RequestFlags, SubNode,
430    };
431    use crate::directory::entry::GetEntryInfo;
432    use crate::directory::entry_container::Directory;
433    use crate::execution_scope::ExecutionScope;
434    use crate::file::read_only;
435    use crate::path::Path;
436    use crate::{assert_read, pseudo_directory, ObjectRequest, ToObjectRequest};
437    use assert_matches::assert_matches;
438    use fidl::endpoints::{create_endpoints, create_proxy, ClientEnd};
439    use fidl_fuchsia_io as fio;
440    use futures::StreamExt;
441    use std::sync::Arc;
442    use zx_status::Status;
443
444    #[fuchsia::test]
445    async fn sub_node() {
446        let root = pseudo_directory!(
447            "a" => pseudo_directory!(
448                "b" => pseudo_directory!(
449                    "c" => pseudo_directory!(
450                        "d" => read_only(b"foo")
451                    )
452                )
453            )
454        );
455        let sub_node = Arc::new(SubNode::new(
456            root,
457            Path::validate_and_split("a/b").unwrap(),
458            fio::DirentType::Directory,
459        ));
460        let scope = ExecutionScope::new();
461        let (client, server) = create_endpoints();
462
463        let root2 = pseudo_directory!(
464            "e" => sub_node
465        );
466
467        root2.deprecated_open(
468            scope.clone(),
469            fio::OpenFlags::RIGHT_READABLE,
470            Path::validate_and_split("e/c/d").unwrap(),
471            server,
472        );
473        assert_read!(ClientEnd::<fio::FileMarker>::from(client.into_channel()).into_proxy(), "foo");
474    }
475
476    #[fuchsia::test]
477    async fn object_request_spawn() {
478        struct MockNode<F: Send + Sync + 'static>
479        where
480            for<'a> F: Fn(OpenRequest<'a>) -> Status,
481        {
482            callback: F,
483        }
484        impl<F: Send + Sync + 'static> DirectoryEntry for MockNode<F>
485        where
486            for<'a> F: Fn(OpenRequest<'a>) -> Status,
487        {
488            fn open_entry(self: Arc<Self>, request: OpenRequest<'_>) -> Result<(), Status> {
489                request.spawn(self);
490                Ok(())
491            }
492        }
493        impl<F: Send + Sync + 'static> GetEntryInfo for MockNode<F>
494        where
495            for<'a> F: Fn(OpenRequest<'a>) -> Status,
496        {
497            fn entry_info(&self) -> EntryInfo {
498                EntryInfo::new(fio::INO_UNKNOWN, fio::DirentType::Unknown)
499            }
500        }
501        impl<F: Send + Sync + 'static> DirectoryEntryAsync for MockNode<F>
502        where
503            for<'a> F: Fn(OpenRequest<'a>) -> Status,
504        {
505            async fn open_entry_async(
506                self: Arc<Self>,
507                request: OpenRequest<'_>,
508            ) -> Result<(), Status> {
509                Err((self.callback)(request))
510            }
511        }
512
513        let scope = ExecutionScope::new();
514        let (proxy, server) = create_proxy::<fio::NodeMarker>();
515        let flags = fio::OpenFlags::DIRECTORY | fio::OpenFlags::RIGHT_READABLE;
516        let mut object_request = flags.to_object_request(server);
517
518        let flags_copy = flags;
519        Arc::new(MockNode {
520            callback: move |request| {
521                assert_matches!(
522                    request,
523                    OpenRequest {
524                        request_flags: RequestFlags::Open1(f),
525                        path,
526                        ..
527                    } if f == flags_copy && path.as_ref() == "a/b/c"
528                );
529                Status::BAD_STATE
530            },
531        })
532        .open_entry(OpenRequest::new(
533            scope.clone(),
534            flags,
535            "a/b/c".try_into().unwrap(),
536            &mut object_request,
537        ))
538        .unwrap();
539
540        assert_matches!(
541            proxy.take_event_stream().next().await,
542            Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
543                if status == Status::BAD_STATE
544        );
545
546        let (proxy, server) = create_proxy::<fio::NodeMarker>();
547        let flags = fio::Flags::PROTOCOL_FILE | fio::Flags::FILE_APPEND;
548        let mut object_request =
549            ObjectRequest::new(flags, &Default::default(), server.into_channel());
550
551        Arc::new(MockNode {
552            callback: move |request| {
553                assert_matches!(
554                    request,
555                    OpenRequest {
556                        request_flags: RequestFlags::Open3(f),
557                        path,
558                        ..
559                    } if f == flags && path.as_ref() == "a/b/c"
560                );
561                Status::BAD_STATE
562            },
563        })
564        .open_entry(OpenRequest::new(
565            scope.clone(),
566            flags,
567            "a/b/c".try_into().unwrap(),
568            &mut object_request,
569        ))
570        .unwrap();
571
572        assert_matches!(
573            proxy.take_event_stream().next().await,
574            Some(Err(fidl::Error::ClientChannelClosed { status, .. }))
575                if status == Status::BAD_STATE
576        );
577    }
578}