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