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 fidl_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>),
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() -> Self {
104        TreeBuilder::Directory(HashMap::new())
105    }
106
107    /// Adds a [`DirectoryEntry`] at the specified path.  It can be either a file or a directory.
108    /// In case it is a directory, this builder cannot add new child nodes inside of the added
109    /// directory.  Any `entry` is treated as an opaque "leaf" as far as the builder is concerned.
110    pub fn add_entry<'components, P: 'components, PathImpl>(
111        &mut self,
112        path: P,
113        entry: Arc<dyn DirectoryEntry>,
114    ) -> Result<(), Error>
115    where
116        P: Into<Path<'components, PathImpl>>,
117        PathImpl: AsRef<[&'components str]>,
118    {
119        let path = path.into();
120        let traversed = vec![];
121        let mut rest = path.iter();
122        match rest.next() {
123            None => Err(Error::EmptyPath),
124            Some(name) => self.add_path(
125                &path,
126                traversed,
127                name,
128                rest,
129                |entries, name, full_path, _traversed| match entries
130                    .insert(name, TreeBuilder::Leaf(entry))
131                {
132                    None => Ok(()),
133                    Some(TreeBuilder::Directory(_)) => {
134                        Err(Error::LeafOverDirectory { path: full_path.to_string() })
135                    }
136                    Some(TreeBuilder::Leaf(_)) => {
137                        Err(Error::LeafOverLeaf { path: full_path.to_string() })
138                    }
139                },
140            ),
141        }
142    }
143
144    /// Adds an empty directory into the generated tree at the specified path.  The difference with
145    /// the [`crate::directory::helper::DirectlyMutable::add_entry`] that adds an entry that is a directory is that the builder can can only
146    /// add leaf nodes.  In other words, code like this will fail:
147    ///
148    /// ```should_panic
149    /// use crate::{
150    ///     directory::immutable::Simple,
151    ///     file::vmo::read_only,
152    /// };
153    ///
154    /// let mut tree = TreeBuilder::empty_dir();
155    /// tree.add_entry(&["dir1"], Simple::new());
156    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
157    /// ```
158    ///
159    /// The problem is that the builder does not see "dir1" as a directory, but as a leaf node that
160    /// it cannot descend into.
161    ///
162    /// If you use `add_empty_dir()` instead, it would work:
163    ///
164    /// ```
165    /// use crate::file::vmo::read_only;
166    ///
167    /// let mut tree = TreeBuilder::empty_dir();
168    /// tree.add_empty_dir(&["dir1"]);
169    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
170    /// ```
171    pub fn add_empty_dir<'components, P: 'components, PathImpl>(
172        &mut self,
173        path: P,
174    ) -> Result<(), Error>
175    where
176        P: Into<Path<'components, PathImpl>>,
177        PathImpl: AsRef<[&'components str]>,
178    {
179        let path = path.into();
180        let traversed = vec![];
181        let mut rest = path.iter();
182        match rest.next() {
183            None => Err(Error::EmptyPath),
184            Some(name) => self.add_path(
185                &path,
186                traversed,
187                name,
188                rest,
189                |entries, name, full_path, traversed| match entries
190                    .entry(name)
191                    .or_insert_with(|| TreeBuilder::Directory(HashMap::new()))
192                {
193                    TreeBuilder::Directory(_) => Ok(()),
194                    TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
195                        path: full_path.to_string(),
196                        traversed: traversed.iter().join("/"),
197                    }),
198                },
199            ),
200        }
201    }
202
203    fn add_path<'path, 'components: 'path, PathImpl, Inserter>(
204        &mut self,
205        full_path: &'path Path<'components, PathImpl>,
206        mut traversed: Vec<&'components str>,
207        name: &'components str,
208        mut rest: Iter<'path, &'components str>,
209        inserter: Inserter,
210    ) -> Result<(), Error>
211    where
212        PathImpl: AsRef<[&'components str]>,
213        Inserter: FnOnce(
214            &mut HashMap<Name, TreeBuilder>,
215            Name,
216            &Path<'components, PathImpl>,
217            Vec<&'components str>,
218        ) -> Result<(), Error>,
219    {
220        let parsed_name =
221            Name::try_from(name.to_string()).map_err(|error| Error::InvalidComponent {
222                path: full_path.to_string(),
223                component: name.to_string(),
224                error,
225            })?;
226
227        match self {
228            TreeBuilder::Directory(entries) => match rest.next() {
229                None => inserter(entries, parsed_name, full_path, traversed),
230                Some(next_component) => {
231                    traversed.push(name);
232                    match entries.entry(parsed_name) {
233                        Entry::Vacant(slot) => {
234                            let mut child = TreeBuilder::Directory(HashMap::new());
235                            child.add_path(full_path, traversed, next_component, rest, inserter)?;
236                            slot.insert(child);
237                            Ok(())
238                        }
239                        Entry::Occupied(mut slot) => slot.get_mut().add_path(
240                            full_path,
241                            traversed,
242                            next_component,
243                            rest,
244                            inserter,
245                        ),
246                    }
247                }
248            },
249            TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
250                path: full_path.to_string(),
251                traversed: traversed.iter().join("/"),
252            }),
253        }
254    }
255
256    // Helper function for building a tree with a default inode generator. Use if you don't
257    // care about directory inode values.
258    pub fn build(self) -> Arc<Simple> {
259        let mut generator = |_: &str| -> u64 { fio::INO_UNKNOWN };
260        self.build_with_inode_generator(&mut generator)
261    }
262
263    /// Consumes the builder, producing a tree with all the nodes provided to
264    /// [`crate::directory::helper::DirectlyMutable::add_entry()`] at their respective locations.
265    /// The tree itself is built using [`crate::directory::immutable::Simple`]
266    /// nodes, and the top level is a directory.
267    pub fn build_with_inode_generator<F>(self, get_inode: &mut F) -> Arc<Simple>
268    where
269        F: for<'a> FnMut(&'a str) -> u64,
270    {
271        match self {
272            TreeBuilder::Directory(mut entries) => {
273                let res = Simple::new_with_inode(get_inode("."));
274                for (name, child) in entries.drain() {
275                    let child = child.build_dyn(&name, get_inode);
276                    res.add_entry_impl(name, child, /*overwrite=*/ false)
277                        .map_err(|status| format!("Status: {}", status))
278                        .expect(
279                            "Internal error. We have already checked all the entry names. \
280                             There should be no collisions.",
281                        );
282                }
283                res
284            }
285            TreeBuilder::Leaf(_) => {
286                panic!("Leaf nodes should not be buildable through the public API.")
287            }
288        }
289    }
290
291    fn build_dyn<F>(self, dir: &str, get_inode: &mut F) -> Arc<dyn DirectoryEntry>
292    where
293        F: for<'a> FnMut(&'a str) -> u64,
294    {
295        match self {
296            TreeBuilder::Directory(mut entries) => {
297                let res = Simple::new_with_inode(get_inode(dir));
298                for (name, child) in entries.drain() {
299                    let child = child.build_dyn(&name, get_inode);
300                    res.add_entry(name, child)
301                        .map_err(|status| format!("Status: {}", status))
302                        .expect(
303                            "Internal error.  We have already checked all the entry names. \
304                             There should be no collisions, nor overly long names.",
305                        );
306                }
307                res
308            }
309            TreeBuilder::Leaf(entry) => entry,
310        }
311    }
312}
313
314#[derive(Debug, Error, PartialEq, Eq)]
315pub enum Error {
316    #[error("`add_entry` requires a non-empty path")]
317    EmptyPath,
318
319    #[error(
320        "Path component is invalid.\n\
321                   Path: {}\n\
322                   Component: '{}'\n\
323                   Error: '{}'",
324        path,
325        component,
326        error
327    )]
328    InvalidComponent { path: String, component: String, error: ParseNameError },
329
330    #[error(
331        "Trying to insert a leaf over an existing directory.\n\
332                   Path: {}",
333        path
334    )]
335    LeafOverDirectory { path: String },
336
337    #[error(
338        "Trying to overwrite one leaf with another.\n\
339                   Path: {}",
340        path
341    )]
342    LeafOverLeaf { path: String },
343
344    #[error(
345        "Trying to insert an entry inside a leaf.\n\
346                   Leaf path: {}\n\
347                   Path been inserted: {}",
348        path,
349        traversed
350    )]
351    EntryInsideLeaf { path: String, traversed: String },
352}
353
354#[cfg(test)]
355mod tests {
356    use super::{Error, Simple, TreeBuilder};
357
358    // Macros are exported into the root of the crate.
359    use crate::{assert_close, assert_read};
360
361    use crate::directory::serve;
362    use crate::file;
363
364    use fidl_fuchsia_io as fio;
365    use fuchsia_fs::directory::{DirEntry, DirentKind, open_directory, readdir};
366    use vfs_macros::pseudo_directory;
367
368    async fn assert_open_file_contents(
369        root: &fio::DirectoryProxy,
370        path: &str,
371        flags: fio::Flags,
372        expected_contents: &str,
373    ) {
374        let file = fuchsia_fs::directory::open_file(&root, path, flags).await.unwrap();
375        assert_read!(file, expected_contents);
376        assert_close!(file);
377    }
378
379    async fn get_id_of_path(root: &fio::DirectoryProxy, path: &str) -> u64 {
380        let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
381        root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
382            .expect("failed to call open");
383        let (_, immutable_attrs) = proxy
384            .get_attributes(fio::NodeAttributesQuery::ID)
385            .await
386            .expect("FIDL call failed")
387            .expect("GetAttributes failed");
388        immutable_attrs.id.expect("ID missing from GetAttributes response")
389    }
390
391    #[fuchsia::test]
392    async fn vfs_with_custom_inodes() {
393        let mut tree = TreeBuilder::empty_dir();
394        tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
395        tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
396
397        let mut get_inode = |name: &str| -> u64 {
398            match name {
399                "a" => 1,
400                "b" => 2,
401                "c" => 3,
402                _ => fio::INO_UNKNOWN,
403            }
404        };
405        let root = tree.build_with_inode_generator(&mut get_inode);
406        let root = serve(root, fio::PERM_READABLE);
407        assert_eq!(get_id_of_path(&root, "a").await, 1);
408        assert_eq!(get_id_of_path(&root, "a/b").await, 2);
409        assert_eq!(get_id_of_path(&root, "a/c").await, 3);
410    }
411
412    #[fuchsia::test]
413    async fn two_files() {
414        let mut tree = TreeBuilder::empty_dir();
415        tree.add_entry("a", file::read_only(b"A content")).unwrap();
416        tree.add_entry("b", file::read_only(b"B content")).unwrap();
417
418        let root = tree.build();
419        let root = serve(root, fio::PERM_READABLE);
420
421        assert_eq!(
422            readdir(&root).await.unwrap(),
423            vec![
424                DirEntry { name: String::from("a"), kind: DirentKind::File },
425                DirEntry { name: String::from("b"), kind: DirentKind::File },
426            ]
427        );
428        assert_open_file_contents(&root, "a", fio::PERM_READABLE, "A content").await;
429        assert_open_file_contents(&root, "b", fio::PERM_READABLE, "B content").await;
430
431        assert_close!(root);
432    }
433
434    #[fuchsia::test]
435    async fn overlapping_paths() {
436        let mut tree = TreeBuilder::empty_dir();
437        tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
438        tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
439        tree.add_entry("four", file::read_only(b"C")).unwrap();
440
441        let root = tree.build();
442        let root = serve(root, fio::PERM_READABLE);
443
444        assert_eq!(
445            readdir(&root).await.unwrap(),
446            vec![
447                DirEntry { name: String::from("four"), kind: DirentKind::File },
448                DirEntry { name: String::from("one"), kind: DirentKind::Directory },
449            ]
450        );
451        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
452        assert_eq!(
453            readdir(&one_dir).await.unwrap(),
454            vec![
455                DirEntry { name: String::from("three"), kind: DirentKind::File },
456                DirEntry { name: String::from("two"), kind: DirentKind::File },
457            ]
458        );
459        assert_close!(one_dir);
460
461        assert_open_file_contents(&root, "one/two", fio::PERM_READABLE, "A").await;
462        assert_open_file_contents(&root, "one/three", fio::PERM_READABLE, "B").await;
463        assert_open_file_contents(&root, "four", fio::PERM_READABLE, "C").await;
464
465        assert_close!(root);
466    }
467
468    #[fuchsia::test]
469    async fn directory_leaf() {
470        let etc = pseudo_directory! {
471            "fstab" => file::read_only(b"/dev/fs /"),
472            "ssh" => pseudo_directory! {
473                "sshd_config" => file::read_only(b"# Empty"),
474            },
475        };
476
477        let mut tree = TreeBuilder::empty_dir();
478        tree.add_entry("etc", etc).unwrap();
479        tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
480
481        let root = tree.build();
482        let root = serve(root, fio::PERM_READABLE);
483
484        assert_eq!(
485            readdir(&root).await.unwrap(),
486            vec![
487                DirEntry { name: String::from("etc"), kind: DirentKind::Directory },
488                DirEntry { name: String::from("uname"), kind: DirentKind::File },
489            ]
490        );
491        let etc_dir = open_directory(&root, "etc", fio::PERM_READABLE).await.unwrap();
492        assert_eq!(
493            readdir(&etc_dir).await.unwrap(),
494            vec![
495                DirEntry { name: String::from("fstab"), kind: DirentKind::File },
496                DirEntry { name: String::from("ssh"), kind: DirentKind::Directory },
497            ]
498        );
499        assert_close!(etc_dir);
500        let ssh_dir = open_directory(&root, "etc/ssh", fio::PERM_READABLE).await.unwrap();
501        assert_eq!(
502            readdir(&ssh_dir).await.unwrap(),
503            vec![DirEntry { name: String::from("sshd_config"), kind: DirentKind::File }]
504        );
505        assert_close!(ssh_dir);
506
507        assert_open_file_contents(&root, "etc/fstab", fio::PERM_READABLE, "/dev/fs /").await;
508        assert_open_file_contents(&root, "etc/ssh/sshd_config", fio::PERM_READABLE, "# Empty")
509            .await;
510        assert_open_file_contents(&root, "uname", fio::PERM_READABLE, "Fuchsia").await;
511
512        assert_close!(root);
513    }
514
515    #[fuchsia::test]
516    async fn add_empty_dir_populate_later() {
517        let mut tree = TreeBuilder::empty_dir();
518        tree.add_empty_dir(&["one", "two"]).unwrap();
519        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
520
521        let root = tree.build();
522        let root = serve(root, fio::PERM_READABLE);
523
524        assert_eq!(
525            readdir(&root).await.unwrap(),
526            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
527        );
528        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
529        assert_eq!(
530            readdir(&one_dir).await.unwrap(),
531            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
532        );
533        assert_close!(one_dir);
534        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
535        assert_eq!(
536            readdir(&two_dir).await.unwrap(),
537            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
538        );
539        assert_close!(two_dir);
540
541        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
542
543        assert_close!(root);
544    }
545
546    #[fuchsia::test]
547    async fn add_empty_dir_already_exists() {
548        let mut tree = TreeBuilder::empty_dir();
549        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
550        tree.add_empty_dir(&["one", "two"]).unwrap();
551
552        let root = tree.build();
553        let root = serve(root, fio::PERM_READABLE);
554
555        assert_eq!(
556            readdir(&root).await.unwrap(),
557            vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
558        );
559
560        let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
561        assert_eq!(
562            readdir(&one_dir).await.unwrap(),
563            vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
564        );
565        assert_close!(one_dir);
566
567        let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
568        assert_eq!(
569            readdir(&two_dir).await.unwrap(),
570            vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
571        );
572        assert_close!(two_dir);
573
574        assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
575
576        assert_close!(root);
577    }
578
579    #[fuchsia::test]
580    async fn lone_add_empty_dir() {
581        let mut tree = TreeBuilder::empty_dir();
582        tree.add_empty_dir(&["just-me"]).unwrap();
583
584        let root = tree.build();
585        let root = serve(root, fio::PERM_READABLE);
586
587        assert_eq!(
588            readdir(&root).await.unwrap(),
589            vec![DirEntry { name: String::from("just-me"), kind: DirentKind::Directory }]
590        );
591        let just_me_dir = open_directory(&root, "just-me", fio::PERM_READABLE).await.unwrap();
592        assert_eq!(readdir(&just_me_dir).await.unwrap(), Vec::new());
593
594        assert_close!(just_me_dir);
595        assert_close!(root);
596    }
597
598    #[fuchsia::test]
599    async fn add_empty_dir_inside_add_empty_dir() {
600        let mut tree = TreeBuilder::empty_dir();
601        tree.add_empty_dir(&["container"]).unwrap();
602        tree.add_empty_dir(&["container", "nested"]).unwrap();
603
604        let root = tree.build();
605        let root = serve(root, fio::PERM_READABLE);
606
607        assert_eq!(
608            readdir(&root).await.unwrap(),
609            vec![DirEntry { name: String::from("container"), kind: DirentKind::Directory }]
610        );
611
612        let container_dir = open_directory(&root, "container", fio::PERM_READABLE).await.unwrap();
613        assert_eq!(
614            readdir(&container_dir).await.unwrap(),
615            vec![DirEntry { name: String::from("nested"), kind: DirentKind::Directory }]
616        );
617        assert_close!(container_dir);
618
619        let nested_dir =
620            open_directory(&root, "container/nested", fio::PERM_READABLE).await.unwrap();
621        assert_eq!(readdir(&nested_dir).await.unwrap(), Vec::new());
622        assert_close!(nested_dir);
623
624        assert_close!(root);
625    }
626
627    #[fuchsia::test]
628    fn error_empty_path_in_add_entry() {
629        let mut tree = TreeBuilder::empty_dir();
630        let err = tree
631            .add_entry(&[], file::read_only(b"Invalid"))
632            .expect_err("Empty paths are not allowed.");
633        assert_eq!(err, Error::EmptyPath);
634    }
635
636    #[fuchsia::test]
637    fn error_empty_first_component() {
638        let mut tree = TreeBuilder::empty_dir();
639        let err = tree
640            .add_entry(&[""], file::read_only(b"Invalid"))
641            .expect_err("Empty paths are not allowed.");
642        assert_eq!(
643            err,
644            Error::InvalidComponent {
645                path: String::new(),
646                component: String::new(),
647                error: name::ParseNameError::Empty
648            }
649        );
650    }
651
652    #[fuchsia::test]
653    fn error_empty_component() {
654        let mut tree = TreeBuilder::empty_dir();
655        let err = tree
656            .add_entry(&["a", "", "c"], file::read_only(b"Invalid"))
657            .expect_err("Empty paths are not allowed.");
658        assert_eq!(
659            err,
660            Error::InvalidComponent {
661                path: "a//c".to_string(),
662                component: String::new(),
663                error: name::ParseNameError::Empty
664            }
665        );
666    }
667
668    #[fuchsia::test]
669    fn error_slash_in_component() {
670        let mut tree = TreeBuilder::empty_dir();
671        let err = tree
672            .add_entry("a/b", file::read_only(b"Invalid"))
673            .expect_err("Slash in path component name.");
674        assert_eq!(
675            err,
676            Error::InvalidComponent {
677                path: "a/b".to_string(),
678                component: "a/b".to_string(),
679                error: name::ParseNameError::Slash
680            }
681        );
682    }
683
684    #[fuchsia::test]
685    fn error_slash_in_second_component() {
686        let mut tree = TreeBuilder::empty_dir();
687        let err = tree
688            .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
689            .expect_err("Slash in path component name.");
690        assert_eq!(
691            err,
692            Error::InvalidComponent {
693                path: "a/b/c".to_string(),
694                component: "b/c".to_string(),
695                error: name::ParseNameError::Slash
696            }
697        );
698    }
699
700    #[fuchsia::test]
701    fn error_component_name_too_long() {
702        let mut tree = TreeBuilder::empty_dir();
703
704        let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
705
706        let path: &[&str] = &["a", &long_component, "b"];
707        let err = tree
708            .add_entry(path, file::read_only(b"Invalid"))
709            .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
710        assert_eq!(
711            err,
712            Error::InvalidComponent {
713                path: format!("a/{}/b", long_component),
714                component: long_component.clone(),
715                error: name::ParseNameError::TooLong(long_component)
716            }
717        );
718    }
719
720    #[fuchsia::test]
721    fn error_dot_in_component() {
722        let mut tree = TreeBuilder::empty_dir();
723        let err = tree
724            .add_entry(&["a", "."], file::read_only(b"Invalid"))
725            .expect_err("Dot in path component name.");
726        assert_eq!(
727            err,
728            Error::InvalidComponent {
729                path: "a/.".to_string(),
730                component: ".".to_string(),
731                error: name::ParseNameError::Dot
732            }
733        );
734    }
735
736    #[fuchsia::test]
737    fn error_dot_dot_in_component() {
738        let mut tree = TreeBuilder::empty_dir();
739        let err = tree
740            .add_entry(&["a", ".."], file::read_only(b"Invalid"))
741            .expect_err("Dot dot in path component name.");
742        assert_eq!(
743            err,
744            Error::InvalidComponent {
745                path: "a/..".to_string(),
746                component: "..".to_string(),
747                error: name::ParseNameError::DotDot
748            }
749        );
750    }
751
752    #[fuchsia::test]
753    fn error_null_in_component() {
754        let mut tree = TreeBuilder::empty_dir();
755        let err = tree
756            .add_entry(&["a", "foo\0bar"], file::read_only(b"Invalid"))
757            .expect_err("Embedded null in component.");
758        assert_eq!(
759            err,
760            Error::InvalidComponent {
761                path: "a/foo\0bar".to_string(),
762                component: "foo\0bar".to_string(),
763                error: name::ParseNameError::EmbeddedNul
764            }
765        );
766    }
767
768    #[fuchsia::test]
769    fn error_leaf_over_directory() {
770        let mut tree = TreeBuilder::empty_dir();
771
772        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
773        let err = tree
774            .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
775            .expect_err("A leaf may not be constructed over a directory.");
776        assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
777    }
778
779    #[fuchsia::test]
780    fn error_leaf_over_leaf() {
781        let mut tree = TreeBuilder::empty_dir();
782
783        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
784        let err = tree
785            .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
786            .expect_err("A leaf may not be constructed over another leaf.");
787        assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
788    }
789
790    #[fuchsia::test]
791    fn error_entry_inside_leaf() {
792        let mut tree = TreeBuilder::empty_dir();
793
794        tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
795        let err = tree
796            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
797            .expect_err("A leaf may not be constructed over another leaf.");
798        assert_eq!(
799            err,
800            Error::EntryInsideLeaf {
801                path: "top/file/nested".to_string(),
802                traversed: "top/file".to_string()
803            }
804        );
805    }
806
807    #[fuchsia::test]
808    fn error_entry_inside_leaf_directory() {
809        let mut tree = TreeBuilder::empty_dir();
810
811        // Even when a leaf is itself a directory the tree builder cannot insert a nested entry.
812        tree.add_entry(&["top", "file"], Simple::new()).unwrap();
813        let err = tree
814            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
815            .expect_err("A leaf may not be constructed over another leaf.");
816        assert_eq!(
817            err,
818            Error::EntryInsideLeaf {
819                path: "top/file/nested".to_string(),
820                traversed: "top/file".to_string()
821            }
822        );
823    }
824}