1use 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
26pub 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
91impl TreeBuilder {
101 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 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 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 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 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, 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 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 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}