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::{assert_close, assert_read};
385
386    use crate::directory::serve;
387    use crate::file;
388
389    use flex_fuchsia_io as fio;
390    #[cfg(not(feature = "fdomain"))]
391    use fuchsia_fs::directory::{DirEntry, DirentKind, open_directory, open_file, readdir};
392    #[cfg(feature = "fdomain")]
393    use fuchsia_fs_fdomain::directory::{DirEntry, DirentKind, open_directory, open_file, readdir};
394
395    #[cfg(feature = "fdomain")]
396    fn empty_dir() -> TreeBuilder {
397        TreeBuilder::empty_dir(flex_local::local_client_empty())
398    }
399
400    #[cfg(not(feature = "fdomain"))]
401    fn empty_dir() -> TreeBuilder {
402        TreeBuilder::empty_dir()
403    }
404    use vfs_macros::pseudo_directory;
405
406    async fn assert_open_file_contents(
407        root: &fio::DirectoryProxy,
408        path: &str,
409        flags: fio::Flags,
410        expected_contents: &str,
411    ) {
412        let file = open_file(&root, path, flags).await.unwrap();
413        assert_read!(file, expected_contents);
414        assert_close!(file);
415    }
416
417    async fn get_id_of_path(
418        root: &fio::DirectoryProxy,
419        path: &str,
420        #[cfg(feature = "fdomain")] client: &std::sync::Arc<flex_client::Client>,
421    ) -> u64 {
422        #[cfg(feature = "fdomain")]
423        let (proxy, server) = client.create_proxy::<fio::NodeMarker>();
424        #[cfg(not(feature = "fdomain"))]
425        let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
426        root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
427            .expect("failed to call open");
428        let (_, immutable_attrs) = proxy
429            .get_attributes(fio::NodeAttributesQuery::ID)
430            .await
431            .expect("FIDL call failed")
432            .expect("GetAttributes failed");
433        immutable_attrs.id.expect("ID missing from GetAttributes response")
434    }
435
436    #[fuchsia::test]
437    async fn vfs_with_custom_inodes() {
438        let mut tree = empty_dir();
439        tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
440        tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
441
442        let mut get_inode = |name: &str| -> u64 {
443            match name {
444                "a" => 1,
445                "b" => 2,
446                "c" => 3,
447                _ => fio::INO_UNKNOWN,
448            }
449        };
450        let root = tree.build_with_inode_generator(&mut get_inode);
451        #[cfg(feature = "fdomain")]
452        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
453        #[cfg(not(feature = "fdomain"))]
454        let scope = crate::execution_scope::ExecutionScope::new();
455        let root = serve(root, scope.clone(), fio::PERM_READABLE);
456        assert_eq!(
457            get_id_of_path(
458                &root,
459                "a",
460                #[cfg(feature = "fdomain")]
461                &scope.domain()
462            )
463            .await,
464            1
465        );
466        assert_eq!(
467            get_id_of_path(
468                &root,
469                "a/b",
470                #[cfg(feature = "fdomain")]
471                &scope.domain()
472            )
473            .await,
474            2
475        );
476        assert_eq!(
477            get_id_of_path(
478                &root,
479                "a/c",
480                #[cfg(feature = "fdomain")]
481                &scope.domain()
482            )
483            .await,
484            3
485        );
486    }
487
488    #[fuchsia::test]
489    async fn two_files() {
490        let mut tree = empty_dir();
491        tree.add_entry("a", file::read_only(b"A content")).unwrap();
492        tree.add_entry("b", file::read_only(b"B content")).unwrap();
493
494        let root = tree.build();
495        #[cfg(feature = "fdomain")]
496        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
497        #[cfg(not(feature = "fdomain"))]
498        let scope = crate::execution_scope::ExecutionScope::new();
499        let root = serve(root, scope.clone(), fio::PERM_READABLE);
500
501        assert_eq!(
502            readdir(&root).await.unwrap(),
503            vec![
504                DirEntry { name: String::from("a"), kind: DirentKind::File },
505                DirEntry { name: String::from("b"), kind: DirentKind::File },
506            ]
507        );
508        assert_open_file_contents(&root, "a", fio::PERM_READABLE, "A content").await;
509        assert_open_file_contents(&root, "b", fio::PERM_READABLE, "B content").await;
510
511        assert_close!(root);
512    }
513
514    #[fuchsia::test]
515    async fn overlapping_paths() {
516        let mut tree = empty_dir();
517        tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
518        tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
519        tree.add_entry("four", file::read_only(b"C")).unwrap();
520
521        let root = tree.build();
522        #[cfg(feature = "fdomain")]
523        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
524        #[cfg(not(feature = "fdomain"))]
525        let scope = crate::execution_scope::ExecutionScope::new();
526        let root = serve(root, scope.clone(), fio::PERM_READABLE);
527
528        assert_eq!(
529            readdir(&root).await.unwrap(),
530            vec![
531                DirEntry { name: String::from("four"), kind: DirentKind::File },
532                DirEntry { name: String::from("one"), kind: DirentKind::Directory },
533            ]
534        );
535        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
536        assert_eq!(
537            readdir(&one_dir).await.unwrap(),
538            vec![
539                DirEntry { name: String::from("three"), kind: DirentKind::File },
540                DirEntry { name: String::from("two"), kind: DirentKind::File },
541            ]
542        );
543        assert_close!(one_dir);
544
545        assert_open_file_contents(&root, "one/two", fio::PERM_READABLE, "A").await;
546        assert_open_file_contents(&root, "one/three", fio::PERM_READABLE, "B").await;
547        assert_open_file_contents(&root, "four", fio::PERM_READABLE, "C").await;
548
549        assert_close!(root);
550    }
551
552    #[fuchsia::test]
553    async fn directory_leaf() {
554        #[cfg(feature = "fdomain")]
555        let client = fdomain_local::local_client_empty();
556        #[cfg(feature = "fdomain")]
557        let _dummy_handle = client.create_proxy::<fio::NodeMarker>();
558        #[cfg(not(feature = "fdomain"))]
559        let client = flex_client::fidl::ZirconClient;
560        let _ = &client;
561        let etc = pseudo_directory! {
562            "fstab" => file::read_only(b"/dev/fs /"),
563            "ssh" => pseudo_directory! {
564                "sshd_config" => file::read_only(b"# Empty"),
565            },
566        };
567
568        let mut tree = empty_dir();
569        tree.add_entry("etc", etc).unwrap();
570        tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
571
572        let root = tree.build();
573        #[cfg(feature = "fdomain")]
574        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
575        #[cfg(not(feature = "fdomain"))]
576        let scope = crate::execution_scope::ExecutionScope::new();
577        let root = serve(root, scope.clone(), fio::PERM_READABLE);
578
579        assert_eq!(
580            readdir(&root).await.unwrap(),
581            vec![
582                DirEntry { name: String::from("etc"), kind: DirentKind::Directory },
583                DirEntry { name: String::from("uname"), kind: DirentKind::File },
584            ]
585        );
586        let etc_dir = open_directory(&root, "etc", fio::PERM_READABLE).await.unwrap();
587        assert_eq!(
588            readdir(&etc_dir).await.unwrap(),
589            vec![
590                DirEntry { name: String::from("fstab"), kind: DirentKind::File },
591                DirEntry { name: String::from("ssh"), kind: DirentKind::Directory },
592            ]
593        );
594        assert_close!(etc_dir);
595        let ssh_dir = open_directory(&root, "etc/ssh", fio::PERM_READABLE).await.unwrap();
596        assert_eq!(
597            readdir(&ssh_dir).await.unwrap(),
598            vec![DirEntry { name: String::from("sshd_config"), kind: DirentKind::File }]
599        );
600        assert_close!(ssh_dir);
601
602        assert_open_file_contents(&root, "etc/fstab", fio::PERM_READABLE, "/dev/fs /").await;
603        assert_open_file_contents(&root, "etc/ssh/sshd_config", fio::PERM_READABLE, "# Empty")
604            .await;
605        assert_open_file_contents(&root, "uname", fio::PERM_READABLE, "Fuchsia").await;
606
607        assert_close!(root);
608    }
609
610    #[fuchsia::test]
611    async fn add_empty_dir_populate_later() {
612        let mut tree = empty_dir();
613        tree.add_empty_dir(&["one", "two"]).unwrap();
614        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
615
616        let root = tree.build();
617        #[cfg(feature = "fdomain")]
618        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
619        #[cfg(not(feature = "fdomain"))]
620        let scope = crate::execution_scope::ExecutionScope::new();
621        let root = serve(root, scope.clone(), fio::PERM_READABLE);
622
623        assert_eq!(
624            readdir(&root).await.unwrap(),
625            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
626        );
627        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
628        assert_eq!(
629            readdir(&one_dir).await.unwrap(),
630            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
631        );
632        assert_close!(one_dir);
633        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
634        assert_eq!(
635            readdir(&two_dir).await.unwrap(),
636            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
637        );
638        assert_close!(two_dir);
639
640        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
641
642        assert_close!(root);
643    }
644
645    #[fuchsia::test]
646    async fn add_empty_dir_already_exists() {
647        let mut tree = empty_dir();
648        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
649        tree.add_empty_dir(&["one", "two"]).unwrap();
650
651        let root = tree.build();
652        #[cfg(feature = "fdomain")]
653        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
654        #[cfg(not(feature = "fdomain"))]
655        let scope = crate::execution_scope::ExecutionScope::new();
656        let root = serve(root, scope.clone(), fio::PERM_READABLE);
657
658        assert_eq!(
659            readdir(&root).await.unwrap(),
660            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
661        );
662
663        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
664        assert_eq!(
665            readdir(&one_dir).await.unwrap(),
666            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
667        );
668        assert_close!(one_dir);
669
670        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
671        assert_eq!(
672            readdir(&two_dir).await.unwrap(),
673            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
674        );
675        assert_close!(two_dir);
676
677        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
678
679        assert_close!(root);
680    }
681
682    #[fuchsia::test]
683    async fn lone_add_empty_dir() {
684        let mut tree = empty_dir();
685        tree.add_empty_dir(&["just-me"]).unwrap();
686
687        let root = tree.build();
688        #[cfg(feature = "fdomain")]
689        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
690        #[cfg(not(feature = "fdomain"))]
691        let scope = crate::execution_scope::ExecutionScope::new();
692        let root = serve(root, scope.clone(), fio::PERM_READABLE);
693
694        assert_eq!(
695            readdir(&root).await.unwrap(),
696            vec![DirEntry { name: String::from("just-me"), kind: DirentKind::Directory }]
697        );
698        let just_me_dir = open_directory(&root, "just-me", fio::PERM_READABLE).await.unwrap();
699        assert_eq!(readdir(&just_me_dir).await.unwrap(), Vec::new());
700
701        assert_close!(just_me_dir);
702        assert_close!(root);
703    }
704
705    #[fuchsia::test]
706    async fn add_empty_dir_inside_add_empty_dir() {
707        let mut tree = empty_dir();
708        tree.add_empty_dir(&["container"]).unwrap();
709        tree.add_empty_dir(&["container", "nested"]).unwrap();
710
711        let root = tree.build();
712        #[cfg(feature = "fdomain")]
713        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
714        #[cfg(not(feature = "fdomain"))]
715        let scope = crate::execution_scope::ExecutionScope::new();
716        let root = serve(root, scope.clone(), fio::PERM_READABLE);
717
718        assert_eq!(
719            readdir(&root).await.unwrap(),
720            vec![DirEntry { name: String::from("container"), kind: DirentKind::Directory }]
721        );
722
723        let container_dir = open_directory(&root, "container", fio::PERM_READABLE).await.unwrap();
724        assert_eq!(
725            readdir(&container_dir).await.unwrap(),
726            vec![DirEntry { name: String::from("nested"), kind: DirentKind::Directory }]
727        );
728        assert_close!(container_dir);
729
730        let nested_dir =
731            open_directory(&root, "container/nested", fio::PERM_READABLE).await.unwrap();
732        assert_eq!(readdir(&nested_dir).await.unwrap(), Vec::new());
733        assert_close!(nested_dir);
734
735        assert_close!(root);
736    }
737
738    #[fuchsia::test]
739    async fn error_empty_path_in_add_entry() {
740        let mut tree = empty_dir();
741        let err = tree
742            .add_entry(&[], file::read_only(b"Invalid"))
743            .expect_err("Empty paths are not allowed.");
744        assert_eq!(err, Error::EmptyPath);
745    }
746
747    #[fuchsia::test]
748    async fn error_empty_first_component() {
749        let mut tree = empty_dir();
750        let err = tree
751            .add_entry(&[""], file::read_only(b"Invalid"))
752            .expect_err("Empty paths are not allowed.");
753        assert_eq!(
754            err,
755            Error::InvalidComponent {
756                path: String::new(),
757                component: String::new(),
758                error: name::ParseNameError::Empty
759            }
760        );
761    }
762
763    #[fuchsia::test]
764    async fn error_empty_component() {
765        let mut tree = empty_dir();
766        let err = tree
767            .add_entry(&["a", "", "c"], file::read_only(b"Invalid"))
768            .expect_err("Empty paths are not allowed.");
769        assert_eq!(
770            err,
771            Error::InvalidComponent {
772                path: "a//c".to_string(),
773                component: String::new(),
774                error: name::ParseNameError::Empty
775            }
776        );
777    }
778
779    #[fuchsia::test]
780    async fn error_slash_in_component() {
781        let mut tree = empty_dir();
782        let err = tree
783            .add_entry("a/b", file::read_only(b"Invalid"))
784            .expect_err("Slash in path component name.");
785        assert_eq!(
786            err,
787            Error::InvalidComponent {
788                path: "a/b".to_string(),
789                component: "a/b".to_string(),
790                error: name::ParseNameError::Slash
791            }
792        );
793    }
794
795    #[fuchsia::test]
796    async fn error_slash_in_second_component() {
797        let mut tree = empty_dir();
798        let err = tree
799            .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
800            .expect_err("Slash in path component name.");
801        assert_eq!(
802            err,
803            Error::InvalidComponent {
804                path: "a/b/c".to_string(),
805                component: "b/c".to_string(),
806                error: name::ParseNameError::Slash
807            }
808        );
809    }
810
811    #[fuchsia::test]
812    async fn error_component_name_too_long() {
813        let mut tree = empty_dir();
814
815        let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
816
817        let path: &[&str] = &["a", &long_component, "b"];
818        let err = tree
819            .add_entry(path, file::read_only(b"Invalid"))
820            .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
821        assert_eq!(
822            err,
823            Error::InvalidComponent {
824                path: format!("a/{}/b", long_component),
825                component: long_component.clone(),
826                error: name::ParseNameError::TooLong(long_component)
827            }
828        );
829    }
830
831    #[fuchsia::test]
832    async fn error_dot_in_component() {
833        let mut tree = empty_dir();
834        let err = tree
835            .add_entry(&["a", "."], file::read_only(b"Invalid"))
836            .expect_err("Dot in path component name.");
837        assert_eq!(
838            err,
839            Error::InvalidComponent {
840                path: "a/.".to_string(),
841                component: ".".to_string(),
842                error: name::ParseNameError::Dot
843            }
844        );
845    }
846
847    #[fuchsia::test]
848    async fn error_dot_dot_in_component() {
849        let mut tree = empty_dir();
850        let err = tree
851            .add_entry(&["a", ".."], file::read_only(b"Invalid"))
852            .expect_err("Dot dot in path component name.");
853        assert_eq!(
854            err,
855            Error::InvalidComponent {
856                path: "a/..".to_string(),
857                component: "..".to_string(),
858                error: name::ParseNameError::DotDot
859            }
860        );
861    }
862
863    #[fuchsia::test]
864    async fn error_null_in_component() {
865        let mut tree = empty_dir();
866        let err = tree
867            .add_entry(&["a", "foo\0bar"], file::read_only(b"Invalid"))
868            .expect_err("Embedded null in component.");
869        assert_eq!(
870            err,
871            Error::InvalidComponent {
872                path: "a/foo\0bar".to_string(),
873                component: "foo\0bar".to_string(),
874                error: name::ParseNameError::EmbeddedNul
875            }
876        );
877    }
878
879    #[fuchsia::test]
880    async fn error_leaf_over_directory() {
881        let mut tree = empty_dir();
882
883        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
884        let err = tree
885            .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
886            .expect_err("A leaf may not be constructed over a directory.");
887        assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
888    }
889
890    #[fuchsia::test]
891    async fn error_leaf_over_leaf() {
892        let mut tree = empty_dir();
893
894        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
895        let err = tree
896            .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
897            .expect_err("A leaf may not be constructed over another leaf.");
898        assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
899    }
900
901    #[fuchsia::test]
902    async fn error_entry_inside_leaf() {
903        let mut tree = empty_dir();
904
905        tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
906        let err = tree
907            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
908            .expect_err("A leaf may not be constructed over another leaf.");
909        assert_eq!(
910            err,
911            Error::EntryInsideLeaf {
912                path: "top/file/nested".to_string(),
913                traversed: "top/file".to_string()
914            }
915        );
916    }
917
918    #[fuchsia::test]
919    async fn error_entry_inside_leaf_directory() {
920        let mut tree = empty_dir();
921
922        // Even when a leaf is itself a directory the tree builder cannot insert a nested entry.
923        tree.add_entry(&["top", "file"], Simple::new()).unwrap();
924        let err = tree
925            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
926            .expect_err("A leaf may not be constructed over another leaf.");
927        assert_eq!(
928            err,
929            Error::EntryInsideLeaf {
930                path: "top/file/nested".to_string(),
931                traversed: "top/file".to_string()
932            }
933        );
934    }
935}