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