Skip to main content

vfs/
tree_builder.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//! A helper to build a tree of directory nodes.  It is useful in case when a nested tree is
6//! desired, with specific nodes to be inserted as the leafs of this tree.  It is similar to the
7//! functionality provided by the [`vfs_macros::pseudo_directory!`] macro, except that the macro
8//! expects the tree structure to be defined at compile time, while this helper allows the tree
9//! structure to be dynamic.
10
11use crate::directory::entry::DirectoryEntry;
12use crate::directory::helper::DirectlyMutable;
13use crate::directory::immutable::Simple;
14
15use flex_fuchsia_io as fio;
16use itertools::Itertools;
17use name::{Name, ParseNameError};
18use std::collections::HashMap;
19use std::collections::hash_map::Entry;
20use std::fmt;
21use std::marker::PhantomData;
22use std::slice::Iter;
23use std::sync::Arc;
24use thiserror::Error;
25
26/// Represents a paths provided to [`TreeBuilder::add_entry()`].  See [`TreeBuilder`] for details.
27// I think it would be a bit more straightforward to have two different types that implement a
28// `Path` trait, `OwnedPath` and `SharedPath`.  But, `add_entry` then needs two type variables: one
29// for the type of the value passed in, and one for the type of the `Path` trait (either
30// `OwnedPath` or `SharedPath`).  Type inference fails with two variables requiring explicit type
31// annotation.  And that defeats the whole purpose of the overloading in the API.
32//
33//     pub fn add_entry<'path, 'components: 'path, F, P: 'path>(
34//         &mut self,
35//         path: F,
36//         entry: Arc<dyn DirectoryEntry>,
37//     ) -> Result<(), Error>
38//
39// Instead we capture the underlying implementation of the path in the `Impl` type and just wrap
40// our type around it.  `'components` and `AsRef` constraints on the struct itself are not actually
41// needed, but it makes it more the usage a bit easier to understand.
42pub struct Path<'components, Impl>
43where
44    Impl: AsRef<[&'components str]>,
45{
46    path: Impl,
47    _components: PhantomData<&'components str>,
48}
49
50impl<'components, Impl> Path<'components, Impl>
51where
52    Impl: AsRef<[&'components str]>,
53{
54    fn iter<'path>(&'path self) -> Iter<'path, &'components str>
55    where
56        'components: 'path,
57    {
58        self.path.as_ref().iter()
59    }
60}
61
62impl<'component> From<&'component str> for Path<'component, Vec<&'component str>> {
63    fn from(component: &'component str) -> Self {
64        Path { path: vec![component], _components: PhantomData }
65    }
66}
67
68impl<'components, Impl> From<Impl> for Path<'components, Impl>
69where
70    Impl: AsRef<[&'components str]>,
71{
72    fn from(path: Impl) -> Self {
73        Path { path, _components: PhantomData }
74    }
75}
76
77impl<'components, Impl> fmt::Display for Path<'components, Impl>
78where
79    Impl: AsRef<[&'components str]>,
80{
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "{}", self.iter().format("/"))
83    }
84}
85
86pub enum TreeBuilder {
87    Directory(HashMap<Name, TreeBuilder>, #[cfg(feature = "fdomain")] Arc<flex_client::Client>),
88    Leaf(Arc<dyn DirectoryEntry>),
89}
90
91/// Collects a number of [`DirectoryEntry`] nodes and corresponding paths and the constructs a tree
92/// of [`crate::directory::immutable::simple::Simple`] directories that hold these nodes.  This is a
93/// companion tool, related to the [`vfs_macros::pseudo_directory!`] macro, except that it is
94/// collecting the paths dynamically, while the [`vfs_macros::pseudo_directory!`] expects the tree
95/// to be specified at compilation time.
96///
97/// Note that the final tree is build as a result of the [`Self::build()`] method that consumes the
98/// builder.  You would need to use the [`crate::directory::helper::DirectlyMutable::add_entry()`]
99/// interface to add any new nodes afterwards (a [`crate::directory::watchers::Controller`] APIs).
100impl TreeBuilder {
101    /// Constructs an empty builder.  It is always an empty [`crate::directory::immutable::Simple`]
102    /// directory.
103    pub fn empty_dir(#[cfg(feature = "fdomain")] client: Arc<flex_client::Client>) -> Self {
104        TreeBuilder::Directory(
105            HashMap::new(),
106            #[cfg(feature = "fdomain")]
107            client,
108        )
109    }
110
111    /// Adds a [`DirectoryEntry`] at the specified path.  It can be either a file or a directory.
112    /// In case it is a directory, this builder cannot add new child nodes inside of the added
113    /// directory.  Any `entry` is treated as an opaque "leaf" as far as the builder is concerned.
114    pub fn add_entry<'components, P: 'components, PathImpl>(
115        &mut self,
116        path: P,
117        entry: Arc<dyn DirectoryEntry>,
118    ) -> Result<(), Error>
119    where
120        P: Into<Path<'components, PathImpl>>,
121        PathImpl: AsRef<[&'components str]>,
122    {
123        let path = path.into();
124        let traversed = vec![];
125        let mut rest = path.iter();
126        match rest.next() {
127            None => Err(Error::EmptyPath),
128            Some(name) => self.add_path(
129                &path,
130                traversed,
131                name,
132                rest,
133                |entries, name, full_path, _traversed| match entries
134                    .insert(name, TreeBuilder::Leaf(entry))
135                {
136                    None => Ok(()),
137                    Some(TreeBuilder::Directory(..)) => {
138                        Err(Error::LeafOverDirectory { path: full_path.to_string() })
139                    }
140                    Some(TreeBuilder::Leaf(_)) => {
141                        Err(Error::LeafOverLeaf { path: full_path.to_string() })
142                    }
143                },
144            ),
145        }
146    }
147
148    #[cfg(feature = "fdomain")]
149    fn domain(&self) -> Option<Arc<flex_client::Client>> {
150        match self {
151            TreeBuilder::Directory(_, client) => Some(client.clone()),
152            TreeBuilder::Leaf(_) => None,
153        }
154    }
155
156    /// Adds an empty directory into the generated tree at the specified path.  The difference with
157    /// the [`crate::directory::helper::DirectlyMutable::add_entry`] that adds an entry that is a directory is that the builder can can only
158    /// add leaf nodes.  In other words, code like this will fail:
159    ///
160    /// ```should_panic
161    /// use crate::{
162    ///     directory::immutable::Simple,
163    ///     file::vmo::read_only,
164    /// };
165    ///
166    /// let mut tree = TreeBuilder::empty_dir();
167    /// tree.add_entry(&["dir1"], Simple::new());
168    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
169    /// ```
170    ///
171    /// The problem is that the builder does not see "dir1" as a directory, but as a leaf node that
172    /// it cannot descend into.
173    ///
174    /// If you use `add_empty_dir()` instead, it would work:
175    ///
176    /// ```
177    /// use crate::file::vmo::read_only;
178    ///
179    /// let mut tree = TreeBuilder::empty_dir();
180    /// tree.add_empty_dir(&["dir1"]);
181    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
182    /// ```
183    pub fn add_empty_dir<'components, P: 'components, PathImpl>(
184        &mut self,
185        path: P,
186    ) -> Result<(), Error>
187    where
188        P: Into<Path<'components, PathImpl>>,
189        PathImpl: AsRef<[&'components str]>,
190    {
191        let path = path.into();
192        let traversed = vec![];
193        let mut rest = path.iter();
194        #[cfg(feature = "fdomain")]
195        let client = self.domain();
196        match rest.next() {
197            None => Err(Error::EmptyPath),
198            Some(name) => self.add_path(
199                &path,
200                traversed,
201                name,
202                rest,
203                |entries, name, full_path, traversed| match entries.entry(name).or_insert_with(
204                    || {
205                        TreeBuilder::Directory(
206                            HashMap::new(),
207                            #[cfg(feature = "fdomain")]
208                            client.clone().unwrap(),
209                        )
210                    },
211                ) {
212                    TreeBuilder::Directory(..) => Ok(()),
213                    TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
214                        path: full_path.to_string(),
215                        traversed: traversed.iter().join("/"),
216                    }),
217                },
218            ),
219        }
220    }
221
222    fn add_path<'path, 'components: 'path, PathImpl, Inserter>(
223        &mut self,
224        full_path: &'path Path<'components, PathImpl>,
225        mut traversed: Vec<&'components str>,
226        name: &'components str,
227        mut rest: Iter<'path, &'components str>,
228        inserter: Inserter,
229    ) -> Result<(), Error>
230    where
231        PathImpl: AsRef<[&'components str]>,
232        Inserter: FnOnce(
233            &mut HashMap<Name, TreeBuilder>,
234            Name,
235            &Path<'components, PathImpl>,
236            Vec<&'components str>,
237        ) -> Result<(), Error>,
238    {
239        let parsed_name =
240            Name::try_from(name.to_string()).map_err(|error| Error::InvalidComponent {
241                path: full_path.to_string(),
242                component: name.to_string(),
243                error,
244            })?;
245
246        #[cfg(feature = "fdomain")]
247        let client = self.domain();
248        match self {
249            TreeBuilder::Directory(entries, ..) => match rest.next() {
250                None => inserter(entries, parsed_name, full_path, traversed),
251                Some(next_component) => {
252                    traversed.push(name);
253                    match entries.entry(parsed_name) {
254                        Entry::Vacant(slot) => {
255                            let mut child = TreeBuilder::Directory(
256                                HashMap::new(),
257                                #[cfg(feature = "fdomain")]
258                                client.unwrap(),
259                            );
260                            child.add_path(full_path, traversed, next_component, rest, inserter)?;
261                            slot.insert(child);
262                            Ok(())
263                        }
264                        Entry::Occupied(mut slot) => slot.get_mut().add_path(
265                            full_path,
266                            traversed,
267                            next_component,
268                            rest,
269                            inserter,
270                        ),
271                    }
272                }
273            },
274            TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
275                path: full_path.to_string(),
276                traversed: traversed.iter().join("/"),
277            }),
278        }
279    }
280
281    // Helper function for building a tree with a default inode generator. Use if you don't
282    // care about directory inode values.
283    pub fn build(self) -> Arc<Simple> {
284        let mut generator = |_: &str| -> u64 { fio::INO_UNKNOWN };
285        self.build_with_inode_generator(&mut generator)
286    }
287
288    /// Consumes the builder, producing a tree with all the nodes provided to
289    /// [`crate::directory::helper::DirectlyMutable::add_entry()`] at their respective locations.
290    /// The tree itself is built using [`crate::directory::immutable::Simple`]
291    /// nodes, and the top level is a directory.
292    pub fn build_with_inode_generator<F>(self, get_inode: &mut F) -> Arc<Simple>
293    where
294        F: for<'a> FnMut(&'a str) -> u64,
295    {
296        match self {
297            TreeBuilder::Directory(mut entries, ..) => {
298                let res = Simple::new_with_inode(get_inode("."));
299                for (name, child) in entries.drain() {
300                    let child = child.build_dyn(&name, get_inode);
301                    res.add_entry_impl(name, child, /*overwrite=*/ false)
302                        .map_err(|status| format!("Status: {}", status))
303                        .expect(
304                            "Internal error. We have already checked all the entry names. \
305                             There should be no collisions.",
306                        );
307                }
308                res
309            }
310            TreeBuilder::Leaf(_) => {
311                panic!("Leaf nodes should not be buildable through the public API.")
312            }
313        }
314    }
315
316    fn build_dyn<F>(self, dir: &str, get_inode: &mut F) -> Arc<dyn DirectoryEntry>
317    where
318        F: for<'a> FnMut(&'a str) -> u64,
319    {
320        match self {
321            TreeBuilder::Directory(mut entries, ..) => {
322                let res = Simple::new_with_inode(get_inode(dir));
323                for (name, child) in entries.drain() {
324                    let child = child.build_dyn(&name, get_inode);
325                    res.add_entry(name, child)
326                        .map_err(|status| format!("Status: {}", status))
327                        .expect(
328                            "Internal error.  We have already checked all the entry names. \
329                             There should be no collisions, nor overly long names.",
330                        );
331                }
332                res
333            }
334            TreeBuilder::Leaf(entry) => entry,
335        }
336    }
337}
338
339#[derive(Debug, Error, PartialEq, Eq)]
340pub enum Error {
341    #[error("`add_entry` requires a non-empty path")]
342    EmptyPath,
343
344    #[error(
345        "Path component is invalid.\n\
346                   Path: {}\n\
347                   Component: '{}'\n\
348                   Error: '{}'",
349        path,
350        component,
351        error
352    )]
353    InvalidComponent { path: String, component: String, error: ParseNameError },
354
355    #[error(
356        "Trying to insert a leaf over an existing directory.\n\
357                   Path: {}",
358        path
359    )]
360    LeafOverDirectory { path: String },
361
362    #[error(
363        "Trying to overwrite one leaf with another.\n\
364                   Path: {}",
365        path
366    )]
367    LeafOverLeaf { path: String },
368
369    #[error(
370        "Trying to insert an entry inside a leaf.\n\
371                   Leaf path: {}\n\
372                   Path been inserted: {}",
373        path,
374        traversed
375    )]
376    EntryInsideLeaf { path: String, traversed: String },
377}
378
379#[cfg(test)]
380mod tests {
381    use super::{Error, Simple, TreeBuilder};
382
383    // Macros are exported into the root of the crate.
384    use crate::directory::serve;
385    use crate::{assert_close, assert_read, file, pseudo_directory};
386
387    use flex_fuchsia_io as fio;
388    #[cfg(not(feature = "fdomain"))]
389    use fuchsia_fs::directory::{DirEntry, DirentKind, open_directory, open_file, readdir};
390    #[cfg(feature = "fdomain")]
391    use fuchsia_fs_fdomain::directory::{DirEntry, DirentKind, open_directory, open_file, readdir};
392
393    #[cfg(feature = "fdomain")]
394    fn empty_dir() -> TreeBuilder {
395        TreeBuilder::empty_dir(flex_local::local_client_empty())
396    }
397
398    #[cfg(not(feature = "fdomain"))]
399    fn empty_dir() -> TreeBuilder {
400        TreeBuilder::empty_dir()
401    }
402
403    async fn assert_open_file_contents(
404        root: &fio::DirectoryProxy,
405        path: &str,
406        flags: fio::Flags,
407        expected_contents: &str,
408    ) {
409        let file = open_file(&root, path, flags).await.unwrap();
410        assert_read!(file, expected_contents);
411        assert_close!(file);
412    }
413
414    async fn get_id_of_path(
415        root: &fio::DirectoryProxy,
416        path: &str,
417        #[cfg(feature = "fdomain")] client: &std::sync::Arc<flex_client::Client>,
418    ) -> u64 {
419        #[cfg(feature = "fdomain")]
420        let (proxy, server) = client.create_proxy::<fio::NodeMarker>();
421        #[cfg(not(feature = "fdomain"))]
422        let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
423        root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
424            .expect("failed to call open");
425        let (_, immutable_attrs) = proxy
426            .get_attributes(fio::NodeAttributesQuery::ID)
427            .await
428            .expect("FIDL call failed")
429            .expect("GetAttributes failed");
430        immutable_attrs.id.expect("ID missing from GetAttributes response")
431    }
432
433    #[fuchsia::test]
434    async fn vfs_with_custom_inodes() {
435        let mut tree = empty_dir();
436        tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
437        tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
438
439        let mut get_inode = |name: &str| -> u64 {
440            match name {
441                "a" => 1,
442                "b" => 2,
443                "c" => 3,
444                _ => fio::INO_UNKNOWN,
445            }
446        };
447        let root = tree.build_with_inode_generator(&mut get_inode);
448        #[cfg(feature = "fdomain")]
449        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
450        #[cfg(not(feature = "fdomain"))]
451        let scope = crate::execution_scope::ExecutionScope::new();
452        let root = serve(root, scope.clone(), fio::PERM_READABLE);
453        assert_eq!(
454            get_id_of_path(
455                &root,
456                "a",
457                #[cfg(feature = "fdomain")]
458                &scope.domain()
459            )
460            .await,
461            1
462        );
463        assert_eq!(
464            get_id_of_path(
465                &root,
466                "a/b",
467                #[cfg(feature = "fdomain")]
468                &scope.domain()
469            )
470            .await,
471            2
472        );
473        assert_eq!(
474            get_id_of_path(
475                &root,
476                "a/c",
477                #[cfg(feature = "fdomain")]
478                &scope.domain()
479            )
480            .await,
481            3
482        );
483    }
484
485    #[fuchsia::test]
486    async fn two_files() {
487        let mut tree = empty_dir();
488        tree.add_entry("a", file::read_only(b"A content")).unwrap();
489        tree.add_entry("b", file::read_only(b"B content")).unwrap();
490
491        let root = tree.build();
492        #[cfg(feature = "fdomain")]
493        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
494        #[cfg(not(feature = "fdomain"))]
495        let scope = crate::execution_scope::ExecutionScope::new();
496        let root = serve(root, scope.clone(), fio::PERM_READABLE);
497
498        assert_eq!(
499            readdir(&root).await.unwrap(),
500            vec![
501                DirEntry { name: String::from("a"), kind: DirentKind::File },
502                DirEntry { name: String::from("b"), kind: DirentKind::File },
503            ]
504        );
505        assert_open_file_contents(&root, "a", fio::PERM_READABLE, "A content").await;
506        assert_open_file_contents(&root, "b", fio::PERM_READABLE, "B content").await;
507
508        assert_close!(root);
509    }
510
511    #[fuchsia::test]
512    async fn overlapping_paths() {
513        let mut tree = empty_dir();
514        tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
515        tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
516        tree.add_entry("four", file::read_only(b"C")).unwrap();
517
518        let root = tree.build();
519        #[cfg(feature = "fdomain")]
520        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
521        #[cfg(not(feature = "fdomain"))]
522        let scope = crate::execution_scope::ExecutionScope::new();
523        let root = serve(root, scope.clone(), fio::PERM_READABLE);
524
525        assert_eq!(
526            readdir(&root).await.unwrap(),
527            vec![
528                DirEntry { name: String::from("four"), kind: DirentKind::File },
529                DirEntry { name: String::from("one"), kind: DirentKind::Directory },
530            ]
531        );
532        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
533        assert_eq!(
534            readdir(&one_dir).await.unwrap(),
535            vec![
536                DirEntry { name: String::from("three"), kind: DirentKind::File },
537                DirEntry { name: String::from("two"), kind: DirentKind::File },
538            ]
539        );
540        assert_close!(one_dir);
541
542        assert_open_file_contents(&root, "one/two", fio::PERM_READABLE, "A").await;
543        assert_open_file_contents(&root, "one/three", fio::PERM_READABLE, "B").await;
544        assert_open_file_contents(&root, "four", fio::PERM_READABLE, "C").await;
545
546        assert_close!(root);
547    }
548
549    #[fuchsia::test]
550    async fn directory_leaf() {
551        #[cfg(feature = "fdomain")]
552        let client = fdomain_local::local_client_empty();
553        #[cfg(feature = "fdomain")]
554        let _dummy_handle = client.create_proxy::<fio::NodeMarker>();
555        #[cfg(not(feature = "fdomain"))]
556        let client = flex_client::fidl::ZirconClient;
557        let _ = &client;
558        let etc = pseudo_directory! {
559            "fstab" => file::read_only(b"/dev/fs /"),
560            "ssh" => pseudo_directory! {
561                "sshd_config" => file::read_only(b"# Empty"),
562            },
563        };
564
565        let mut tree = empty_dir();
566        tree.add_entry("etc", etc).unwrap();
567        tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
568
569        let root = tree.build();
570        #[cfg(feature = "fdomain")]
571        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
572        #[cfg(not(feature = "fdomain"))]
573        let scope = crate::execution_scope::ExecutionScope::new();
574        let root = serve(root, scope.clone(), fio::PERM_READABLE);
575
576        assert_eq!(
577            readdir(&root).await.unwrap(),
578            vec![
579                DirEntry { name: String::from("etc"), kind: DirentKind::Directory },
580                DirEntry { name: String::from("uname"), kind: DirentKind::File },
581            ]
582        );
583        let etc_dir = open_directory(&root, "etc", fio::PERM_READABLE).await.unwrap();
584        assert_eq!(
585            readdir(&etc_dir).await.unwrap(),
586            vec![
587                DirEntry { name: String::from("fstab"), kind: DirentKind::File },
588                DirEntry { name: String::from("ssh"), kind: DirentKind::Directory },
589            ]
590        );
591        assert_close!(etc_dir);
592        let ssh_dir = open_directory(&root, "etc/ssh", fio::PERM_READABLE).await.unwrap();
593        assert_eq!(
594            readdir(&ssh_dir).await.unwrap(),
595            vec![DirEntry { name: String::from("sshd_config"), kind: DirentKind::File }]
596        );
597        assert_close!(ssh_dir);
598
599        assert_open_file_contents(&root, "etc/fstab", fio::PERM_READABLE, "/dev/fs /").await;
600        assert_open_file_contents(&root, "etc/ssh/sshd_config", fio::PERM_READABLE, "# Empty")
601            .await;
602        assert_open_file_contents(&root, "uname", fio::PERM_READABLE, "Fuchsia").await;
603
604        assert_close!(root);
605    }
606
607    #[fuchsia::test]
608    async fn add_empty_dir_populate_later() {
609        let mut tree = empty_dir();
610        tree.add_empty_dir(&["one", "two"]).unwrap();
611        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
612
613        let root = tree.build();
614        #[cfg(feature = "fdomain")]
615        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
616        #[cfg(not(feature = "fdomain"))]
617        let scope = crate::execution_scope::ExecutionScope::new();
618        let root = serve(root, scope.clone(), fio::PERM_READABLE);
619
620        assert_eq!(
621            readdir(&root).await.unwrap(),
622            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
623        );
624        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
625        assert_eq!(
626            readdir(&one_dir).await.unwrap(),
627            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
628        );
629        assert_close!(one_dir);
630        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
631        assert_eq!(
632            readdir(&two_dir).await.unwrap(),
633            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
634        );
635        assert_close!(two_dir);
636
637        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
638
639        assert_close!(root);
640    }
641
642    #[fuchsia::test]
643    async fn add_empty_dir_already_exists() {
644        let mut tree = empty_dir();
645        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
646        tree.add_empty_dir(&["one", "two"]).unwrap();
647
648        let root = tree.build();
649        #[cfg(feature = "fdomain")]
650        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
651        #[cfg(not(feature = "fdomain"))]
652        let scope = crate::execution_scope::ExecutionScope::new();
653        let root = serve(root, scope.clone(), fio::PERM_READABLE);
654
655        assert_eq!(
656            readdir(&root).await.unwrap(),
657            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
658        );
659
660        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
661        assert_eq!(
662            readdir(&one_dir).await.unwrap(),
663            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
664        );
665        assert_close!(one_dir);
666
667        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
668        assert_eq!(
669            readdir(&two_dir).await.unwrap(),
670            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
671        );
672        assert_close!(two_dir);
673
674        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
675
676        assert_close!(root);
677    }
678
679    #[fuchsia::test]
680    async fn lone_add_empty_dir() {
681        let mut tree = empty_dir();
682        tree.add_empty_dir(&["just-me"]).unwrap();
683
684        let root = tree.build();
685        #[cfg(feature = "fdomain")]
686        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
687        #[cfg(not(feature = "fdomain"))]
688        let scope = crate::execution_scope::ExecutionScope::new();
689        let root = serve(root, scope.clone(), fio::PERM_READABLE);
690
691        assert_eq!(
692            readdir(&root).await.unwrap(),
693            vec![DirEntry { name: String::from("just-me"), kind: DirentKind::Directory }]
694        );
695        let just_me_dir = open_directory(&root, "just-me", fio::PERM_READABLE).await.unwrap();
696        assert_eq!(readdir(&just_me_dir).await.unwrap(), Vec::new());
697
698        assert_close!(just_me_dir);
699        assert_close!(root);
700    }
701
702    #[fuchsia::test]
703    async fn add_empty_dir_inside_add_empty_dir() {
704        let mut tree = empty_dir();
705        tree.add_empty_dir(&["container"]).unwrap();
706        tree.add_empty_dir(&["container", "nested"]).unwrap();
707
708        let root = tree.build();
709        #[cfg(feature = "fdomain")]
710        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
711        #[cfg(not(feature = "fdomain"))]
712        let scope = crate::execution_scope::ExecutionScope::new();
713        let root = serve(root, scope.clone(), fio::PERM_READABLE);
714
715        assert_eq!(
716            readdir(&root).await.unwrap(),
717            vec![DirEntry { name: String::from("container"), kind: DirentKind::Directory }]
718        );
719
720        let container_dir = open_directory(&root, "container", fio::PERM_READABLE).await.unwrap();
721        assert_eq!(
722            readdir(&container_dir).await.unwrap(),
723            vec![DirEntry { name: String::from("nested"), kind: DirentKind::Directory }]
724        );
725        assert_close!(container_dir);
726
727        let nested_dir =
728            open_directory(&root, "container/nested", fio::PERM_READABLE).await.unwrap();
729        assert_eq!(readdir(&nested_dir).await.unwrap(), Vec::new());
730        assert_close!(nested_dir);
731
732        assert_close!(root);
733    }
734
735    #[fuchsia::test]
736    async fn error_empty_path_in_add_entry() {
737        let mut tree = empty_dir();
738        let err = tree
739            .add_entry(&[], file::read_only(b"Invalid"))
740            .expect_err("Empty paths are not allowed.");
741        assert_eq!(err, Error::EmptyPath);
742    }
743
744    #[fuchsia::test]
745    async fn error_empty_first_component() {
746        let mut tree = empty_dir();
747        let err = tree
748            .add_entry(&[""], file::read_only(b"Invalid"))
749            .expect_err("Empty paths are not allowed.");
750        assert_eq!(
751            err,
752            Error::InvalidComponent {
753                path: String::new(),
754                component: String::new(),
755                error: name::ParseNameError::Empty
756            }
757        );
758    }
759
760    #[fuchsia::test]
761    async fn error_empty_component() {
762        let mut tree = empty_dir();
763        let err = tree
764            .add_entry(&["a", "", "c"], file::read_only(b"Invalid"))
765            .expect_err("Empty paths are not allowed.");
766        assert_eq!(
767            err,
768            Error::InvalidComponent {
769                path: "a//c".to_string(),
770                component: String::new(),
771                error: name::ParseNameError::Empty
772            }
773        );
774    }
775
776    #[fuchsia::test]
777    async fn error_slash_in_component() {
778        let mut tree = empty_dir();
779        let err = tree
780            .add_entry("a/b", file::read_only(b"Invalid"))
781            .expect_err("Slash in path component name.");
782        assert_eq!(
783            err,
784            Error::InvalidComponent {
785                path: "a/b".to_string(),
786                component: "a/b".to_string(),
787                error: name::ParseNameError::Slash
788            }
789        );
790    }
791
792    #[fuchsia::test]
793    async fn error_slash_in_second_component() {
794        let mut tree = empty_dir();
795        let err = tree
796            .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
797            .expect_err("Slash in path component name.");
798        assert_eq!(
799            err,
800            Error::InvalidComponent {
801                path: "a/b/c".to_string(),
802                component: "b/c".to_string(),
803                error: name::ParseNameError::Slash
804            }
805        );
806    }
807
808    #[fuchsia::test]
809    async fn error_component_name_too_long() {
810        let mut tree = empty_dir();
811
812        let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
813
814        let path: &[&str] = &["a", &long_component, "b"];
815        let err = tree
816            .add_entry(path, file::read_only(b"Invalid"))
817            .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
818        assert_eq!(
819            err,
820            Error::InvalidComponent {
821                path: format!("a/{}/b", long_component),
822                component: long_component.clone(),
823                error: name::ParseNameError::TooLong
824            }
825        );
826    }
827
828    #[fuchsia::test]
829    async fn error_dot_in_component() {
830        let mut tree = empty_dir();
831        let err = tree
832            .add_entry(&["a", "."], file::read_only(b"Invalid"))
833            .expect_err("Dot in path component name.");
834        assert_eq!(
835            err,
836            Error::InvalidComponent {
837                path: "a/.".to_string(),
838                component: ".".to_string(),
839                error: name::ParseNameError::Dot
840            }
841        );
842    }
843
844    #[fuchsia::test]
845    async fn error_dot_dot_in_component() {
846        let mut tree = empty_dir();
847        let err = tree
848            .add_entry(&["a", ".."], file::read_only(b"Invalid"))
849            .expect_err("Dot dot in path component name.");
850        assert_eq!(
851            err,
852            Error::InvalidComponent {
853                path: "a/..".to_string(),
854                component: "..".to_string(),
855                error: name::ParseNameError::DotDot
856            }
857        );
858    }
859
860    #[fuchsia::test]
861    async fn error_null_in_component() {
862        let mut tree = empty_dir();
863        let err = tree
864            .add_entry(&["a", "foo\0bar"], file::read_only(b"Invalid"))
865            .expect_err("Embedded null in component.");
866        assert_eq!(
867            err,
868            Error::InvalidComponent {
869                path: "a/foo\0bar".to_string(),
870                component: "foo\0bar".to_string(),
871                error: name::ParseNameError::EmbeddedNul
872            }
873        );
874    }
875
876    #[fuchsia::test]
877    async fn error_leaf_over_directory() {
878        let mut tree = empty_dir();
879
880        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
881        let err = tree
882            .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
883            .expect_err("A leaf may not be constructed over a directory.");
884        assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
885    }
886
887    #[fuchsia::test]
888    async fn error_leaf_over_leaf() {
889        let mut tree = empty_dir();
890
891        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
892        let err = tree
893            .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
894            .expect_err("A leaf may not be constructed over another leaf.");
895        assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
896    }
897
898    #[fuchsia::test]
899    async fn error_entry_inside_leaf() {
900        let mut tree = empty_dir();
901
902        tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
903        let err = tree
904            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
905            .expect_err("A leaf may not be constructed over another leaf.");
906        assert_eq!(
907            err,
908            Error::EntryInsideLeaf {
909                path: "top/file/nested".to_string(),
910                traversed: "top/file".to_string()
911            }
912        );
913    }
914
915    #[fuchsia::test]
916    async fn error_entry_inside_leaf_directory() {
917        let mut tree = empty_dir();
918
919        // Even when a leaf is itself a directory the tree builder cannot insert a nested entry.
920        tree.add_entry(&["top", "file"], Simple::new()).unwrap();
921        let err = tree
922            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
923            .expect_err("A leaf may not be constructed over another leaf.");
924        assert_eq!(
925            err,
926            Error::EntryInsideLeaf {
927                path: "top/file/nested".to_string(),
928                traversed: "top/file".to_string()
929            }
930        );
931    }
932}