Skip to main content

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::{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/// 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// TODO(https://fxbug.dev/324080864): Remove this layer of indirection when we no longer support
108// Open1 at any supported API level.
109#[derive(Debug)]
110pub enum RequestFlags {
111    /// fuchsia.io/Directory.Open1 (io1)
112    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "NEXT")))]
113    Open1(fio::OpenFlags),
114    /// fuchsia.io/Directory.Open3 (io2)
115    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    /// Creates a new open request.
133    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    /// Returns the path for this request.
143    pub fn path(&self) -> &Path {
144        &self.path
145    }
146
147    /// Prepends `prefix` to the path.
148    pub fn prepend_path(&mut self, prefix: &Path) {
149        self.path = self.path.with_prefix(prefix);
150    }
151
152    /// Sets the path to `path`.
153    pub fn set_path(&mut self, path: Path) {
154        self.path = path;
155    }
156
157    /// Waits until the request has a request waiting in its channel.  Returns immediately if this
158    /// request requires sending an initial event such as OnOpen or OnRepresentation.  Returns
159    /// `true` if the channel is readable (rather than just clased).
160    pub async fn wait_till_ready(&self) -> bool {
161        self.object_request.wait_till_ready().await
162    }
163
164    /// Returns `true` if the request requires the server to send an event (e.g. either OnOpen or
165    /// OnRepresentation).  If `true`, `wait_till_ready` will return immediately.  If `false`, the
166    /// caller might choose to call `wait_till_ready` if other conditions are satisfied (checking
167    /// for an empty path is usually a good idea since it is hard to know where a non-empty path
168    /// might end up being terminated).
169    pub fn requires_event(&self) -> bool {
170        self.object_request.what_to_send() != ObjectRequestSend::Nothing
171    }
172
173    /// Opens a directory.
174    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                // This will cause issues for heavily nested directory structures because it thwarts
188                // tail recursion optimization, but that shouldn't occur in practice.
189                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    /// Opens a file.
201    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    /// Opens a symlink.
233    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    /// Opens a service.
265    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    /// Forwards the request to a remote.
297    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    /// Spawns a task to handle the request.  `entry` must implement DirectoryEntryAsync.
360    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    /// Returns the execution scope for this request.
404    pub fn scope(&self) -> &ExecutionScope {
405        &self.scope
406    }
407
408    /// Replaces the scope in this request.  This is the right thing to do if any subsequently
409    /// spawned tasks should be in a different scope to the task that received this open request.
410    pub fn set_scope(&mut self, scope: ExecutionScope) {
411        self.scope = scope;
412    }
413}
414
415/// A sub-node of a directory.  This will work with types that implement Directory as well as
416/// RemoteDir.
417pub 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    /// Returns a sub node of an existing entry.  The parent should be a directory (it accepts
425    /// DirectoryEntry so that it works for remotes).
426    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
445/// Serves a directory with the given rights.  Returns a client end.  This takes a DirectoryEntry
446/// so that it works for remotes.
447pub 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}