1use 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 std::collections::HashMap;
18use std::fmt;
19use std::marker::PhantomData;
20use std::slice::Iter;
21use std::sync::Arc;
22use thiserror::Error;
23
24pub struct Path<'components, Impl>
41where
42 Impl: AsRef<[&'components str]>,
43{
44 path: Impl,
45 _components: PhantomData<&'components str>,
46}
47
48impl<'components, Impl> Path<'components, Impl>
49where
50 Impl: AsRef<[&'components str]>,
51{
52 fn iter<'path>(&'path self) -> Iter<'path, &'components str>
53 where
54 'components: 'path,
55 {
56 self.path.as_ref().iter()
57 }
58}
59
60impl<'component> From<&'component str> for Path<'component, Vec<&'component str>> {
61 fn from(component: &'component str) -> Self {
62 Path { path: vec![component], _components: PhantomData }
63 }
64}
65
66impl<'components, Impl> From<Impl> for Path<'components, Impl>
67where
68 Impl: AsRef<[&'components str]>,
69{
70 fn from(path: Impl) -> Self {
71 Path { path, _components: PhantomData }
72 }
73}
74
75impl<'components, Impl> fmt::Display for Path<'components, Impl>
76where
77 Impl: AsRef<[&'components str]>,
78{
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 write!(f, "{}", self.iter().format("/"))
81 }
82}
83
84pub enum TreeBuilder {
85 Directory(HashMap<String, TreeBuilder>),
86 Leaf(Arc<dyn DirectoryEntry>),
87}
88
89impl TreeBuilder {
99 pub fn empty_dir() -> Self {
102 TreeBuilder::Directory(HashMap::new())
103 }
104
105 pub fn add_entry<'components, P: 'components, PathImpl>(
109 &mut self,
110 path: P,
111 entry: Arc<dyn DirectoryEntry>,
112 ) -> Result<(), Error>
113 where
114 P: Into<Path<'components, PathImpl>>,
115 PathImpl: AsRef<[&'components str]>,
116 {
117 let path = path.into();
118 let traversed = vec![];
119 let mut rest = path.iter();
120 match rest.next() {
121 None => Err(Error::EmptyPath),
122 Some(name) => self.add_path(
123 &path,
124 traversed,
125 name,
126 rest,
127 |entries, name, full_path, _traversed| match entries
128 .insert(name.to_string(), TreeBuilder::Leaf(entry))
129 {
130 None => Ok(()),
131 Some(TreeBuilder::Directory(_)) => {
132 Err(Error::LeafOverDirectory { path: full_path.to_string() })
133 }
134 Some(TreeBuilder::Leaf(_)) => {
135 Err(Error::LeafOverLeaf { path: full_path.to_string() })
136 }
137 },
138 ),
139 }
140 }
141
142 pub fn add_empty_dir<'components, P: 'components, PathImpl>(
170 &mut self,
171 path: P,
172 ) -> Result<(), Error>
173 where
174 P: Into<Path<'components, PathImpl>>,
175 PathImpl: AsRef<[&'components str]>,
176 {
177 let path = path.into();
178 let traversed = vec![];
179 let mut rest = path.iter();
180 match rest.next() {
181 None => Err(Error::EmptyPath),
182 Some(name) => self.add_path(
183 &path,
184 traversed,
185 name,
186 rest,
187 |entries, name, full_path, traversed| match entries
188 .entry(name.to_string())
189 .or_insert_with(|| TreeBuilder::Directory(HashMap::new()))
190 {
191 TreeBuilder::Directory(_) => Ok(()),
192 TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
193 path: full_path.to_string(),
194 traversed: traversed.iter().join("/"),
195 }),
196 },
197 ),
198 }
199 }
200
201 fn add_path<'path, 'components: 'path, PathImpl, Inserter>(
202 &mut self,
203 full_path: &'path Path<'components, PathImpl>,
204 mut traversed: Vec<&'components str>,
205 name: &'components str,
206 mut rest: Iter<'path, &'components str>,
207 inserter: Inserter,
208 ) -> Result<(), Error>
209 where
210 PathImpl: AsRef<[&'components str]>,
211 Inserter: FnOnce(
212 &mut HashMap<String, TreeBuilder>,
213 &str,
214 &Path<'components, PathImpl>,
215 Vec<&'components str>,
216 ) -> Result<(), Error>,
217 {
218 if name.len() as u64 >= fio::MAX_NAME_LENGTH {
219 return Err(Error::ComponentNameTooLong {
220 path: full_path.to_string(),
221 component: name.to_string(),
222 component_len: name.len(),
223 max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
224 });
225 }
226
227 if name.contains('/') {
228 return Err(Error::SlashInComponent {
229 path: full_path.to_string(),
230 component: name.to_string(),
231 });
232 }
233
234 match self {
235 TreeBuilder::Directory(entries) => match rest.next() {
236 None => inserter(entries, name, full_path, traversed),
237 Some(next_component) => {
238 traversed.push(name);
239 match entries.get_mut(name) {
240 None => {
241 let mut child = TreeBuilder::Directory(HashMap::new());
242 child.add_path(full_path, traversed, next_component, rest, inserter)?;
243 let existing = entries.insert(name.to_string(), child);
244 assert!(existing.is_none());
245 Ok(())
246 }
247 Some(children) => {
248 children.add_path(full_path, traversed, next_component, rest, inserter)
249 }
250 }
251 }
252 },
253 TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
254 path: full_path.to_string(),
255 traversed: traversed.iter().join("/"),
256 }),
257 }
258 }
259
260 pub fn build(self) -> Arc<Simple> {
263 let mut generator = |_| -> u64 { fio::INO_UNKNOWN };
264 self.build_with_inode_generator(&mut generator)
265 }
266
267 pub fn build_with_inode_generator(
272 self,
273 get_inode: &mut impl FnMut(String) -> u64,
274 ) -> Arc<Simple> {
275 match self {
276 TreeBuilder::Directory(mut entries) => {
277 let res = Simple::new_with_inode(get_inode(".".to_string()));
278 for (name, child) in entries.drain() {
279 res.clone()
280 .add_entry(&name, child.build_dyn(name.clone(), get_inode))
281 .map_err(|status| format!("Status: {}", status))
282 .expect(
283 "Internal error. We have already checked all the entry names. \
284 There should be no collisions, nor overly long names.",
285 );
286 }
287 res
288 }
289 TreeBuilder::Leaf(_) => {
290 panic!("Leaf nodes should not be buildable through the public API.")
291 }
292 }
293 }
294
295 fn build_dyn(
296 self,
297 dir: String,
298 get_inode: &mut impl FnMut(String) -> u64,
299 ) -> Arc<dyn DirectoryEntry> {
300 match self {
301 TreeBuilder::Directory(mut entries) => {
302 let res = Simple::new_with_inode(get_inode(dir));
303 for (name, child) in entries.drain() {
304 res.clone()
305 .add_entry(&name, child.build_dyn(name.clone(), get_inode))
306 .map_err(|status| format!("Status: {}", status))
307 .expect(
308 "Internal error. We have already checked all the entry names. \
309 There should be no collisions, nor overly long names.",
310 );
311 }
312 res
313 }
314 TreeBuilder::Leaf(entry) => entry,
315 }
316 }
317}
318
319#[derive(Debug, Error, PartialEq, Eq)]
320pub enum Error {
321 #[error("`add_entry` requires a non-empty path")]
322 EmptyPath,
323
324 #[error(
325 "Path component contains a forward slash.\n\
326 Path: {}\n\
327 Component: '{}'",
328 path,
329 component
330 )]
331 SlashInComponent { path: String, component: String },
332
333 #[error(
334 "Path component name is too long - {} characters. Maximum is {}.\n\
335 Path: {}\n\
336 Component: '{}'",
337 component_len,
338 max_len,
339 path,
340 component
341 )]
342 ComponentNameTooLong { path: String, component: String, component_len: usize, max_len: usize },
343
344 #[error(
345 "Trying to insert a leaf over an existing directory.\n\
346 Path: {}",
347 path
348 )]
349 LeafOverDirectory { path: String },
350
351 #[error(
352 "Trying to overwrite one leaf with another.\n\
353 Path: {}",
354 path
355 )]
356 LeafOverLeaf { path: String },
357
358 #[error(
359 "Trying to insert an entry inside a leaf.\n\
360 Leaf path: {}\n\
361 Path been inserted: {}",
362 path,
363 traversed
364 )]
365 EntryInsideLeaf { path: String, traversed: String },
366}
367
368#[cfg(test)]
369mod tests {
370 use super::{Error, Simple, TreeBuilder};
371
372 use crate::{
374 assert_close, assert_event, assert_get_attr_path, assert_read, assert_read_dirents,
375 assert_read_dirents_one_listing, assert_read_dirents_path_one_listing,
376 open_as_vmo_file_assert_content, open_get_directory_proxy_assert_ok, open_get_proxy_assert,
377 open_get_vmo_file_proxy_assert_ok,
378 };
379
380 use crate::directory::test_utils::run_server_client;
381 use crate::file;
382
383 use fidl_fuchsia_io as fio;
384 use vfs_macros::pseudo_directory;
385
386 #[test]
387 fn vfs_with_custom_inodes() {
388 let mut tree = TreeBuilder::empty_dir();
389 tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
390 tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
391
392 let mut get_inode = |name: String| -> u64 {
393 match &name[..] {
394 "a" => 1,
395 "b" => 2,
396 "c" => 3,
397 _ => fio::INO_UNKNOWN,
398 }
399 };
400 let root = tree.build_with_inode_generator(&mut get_inode);
401
402 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
403 assert_get_attr_path!(
404 &root,
405 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
406 "a",
407 fio::NodeAttributes {
408 mode: fio::MODE_TYPE_DIRECTORY
409 | crate::common::rights_to_posix_mode_bits(
410 true, false, true
411 ),
412 id: 1,
413 content_size: 0,
414 storage_size: 0,
415 link_count: 1,
416 creation_time: 0,
417 modification_time: 0,
418 }
419 );
420 assert_get_attr_path!(
421 &root,
422 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
423 "a/b",
424 fio::NodeAttributes {
425 mode: fio::MODE_TYPE_DIRECTORY
426 | crate::common::rights_to_posix_mode_bits(
427 true, false, true
428 ),
429 id: 2,
430 content_size: 0,
431 storage_size: 0,
432 link_count: 1,
433 creation_time: 0,
434 modification_time: 0,
435 }
436 );
437 assert_get_attr_path!(
438 &root,
439 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
440 "a/c",
441 fio::NodeAttributes {
442 mode: fio::MODE_TYPE_DIRECTORY
443 | crate::common::rights_to_posix_mode_bits(
444 true, false, true
445 ),
446 id: 3,
447 content_size: 0,
448 storage_size: 0,
449 link_count: 1,
450 creation_time: 0,
451 modification_time: 0,
452 }
453 );
454 });
455 }
456
457 #[test]
458 fn two_files() {
459 let mut tree = TreeBuilder::empty_dir();
460 tree.add_entry("a", file::read_only(b"A content")).unwrap();
461 tree.add_entry("b", file::read_only(b"B content")).unwrap();
462
463 let root = tree.build();
464
465 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
466 assert_read_dirents_one_listing!(
467 root, 1000,
468 { DIRECTORY, b"." },
469 { FILE, b"a" },
470 { FILE, b"b" },
471 );
472 open_as_vmo_file_assert_content!(
473 &root,
474 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
475 "a",
476 "A content"
477 );
478 open_as_vmo_file_assert_content!(
479 &root,
480 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
481 "b",
482 "B content"
483 );
484
485 assert_close!(root);
486 });
487 }
488
489 #[test]
490 fn overlapping_paths() {
491 let mut tree = TreeBuilder::empty_dir();
492 tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
493 tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
494 tree.add_entry("four", file::read_only(b"C")).unwrap();
495
496 let root = tree.build();
497
498 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
499 assert_read_dirents_one_listing!(
500 root, 1000,
501 { DIRECTORY, b"." },
502 { FILE, b"four" },
503 { DIRECTORY, b"one" },
504 );
505 assert_read_dirents_path_one_listing!(
506 &root, "one", 1000,
507 { DIRECTORY, b"." },
508 { FILE, b"three" },
509 { FILE, b"two" },
510 );
511
512 open_as_vmo_file_assert_content!(
513 &root,
514 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
515 "one/two",
516 "A"
517 );
518 open_as_vmo_file_assert_content!(
519 &root,
520 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
521 "one/three",
522 "B"
523 );
524 open_as_vmo_file_assert_content!(
525 &root,
526 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
527 "four",
528 "C"
529 );
530
531 assert_close!(root);
532 });
533 }
534
535 #[test]
536 fn directory_leaf() {
537 let etc = pseudo_directory! {
538 "fstab" => file::read_only(b"/dev/fs /"),
539 "ssh" => pseudo_directory! {
540 "sshd_config" => file::read_only(b"# Empty"),
541 },
542 };
543
544 let mut tree = TreeBuilder::empty_dir();
545 tree.add_entry("etc", etc).unwrap();
546 tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
547
548 let root = tree.build();
549
550 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
551 assert_read_dirents_one_listing!(
552 root, 1000,
553 { DIRECTORY, b"." },
554 { DIRECTORY, b"etc" },
555 { FILE, b"uname" },
556 );
557 assert_read_dirents_path_one_listing!(
558 &root, "etc", 1000,
559 { DIRECTORY, b"." },
560 { FILE, b"fstab" },
561 { DIRECTORY, b"ssh" },
562 );
563 assert_read_dirents_path_one_listing!(
564 &root, "etc/ssh", 1000,
565 { DIRECTORY, b"." },
566 { FILE, b"sshd_config" },
567 );
568
569 open_as_vmo_file_assert_content!(
570 &root,
571 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
572 "etc/fstab",
573 "/dev/fs /"
574 );
575 open_as_vmo_file_assert_content!(
576 &root,
577 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
578 "etc/ssh/sshd_config",
579 "# Empty"
580 );
581 open_as_vmo_file_assert_content!(
582 &root,
583 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
584 "uname",
585 "Fuchsia"
586 );
587
588 assert_close!(root);
589 });
590 }
591
592 #[test]
593 fn add_empty_dir_populate_later() {
594 let mut tree = TreeBuilder::empty_dir();
595 tree.add_empty_dir(&["one", "two"]).unwrap();
596 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
597
598 let root = tree.build();
599
600 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
601 assert_read_dirents_one_listing!(
602 root, 1000,
603 { DIRECTORY, b"." },
604 { DIRECTORY, b"one" },
605 );
606 assert_read_dirents_path_one_listing!(
607 &root, "one", 1000,
608 { DIRECTORY, b"." },
609 { DIRECTORY, b"two" },
610 );
611 assert_read_dirents_path_one_listing!(
612 &root, "one/two", 1000,
613 { DIRECTORY, b"." },
614 { FILE, b"three" },
615 );
616
617 open_as_vmo_file_assert_content!(
618 &root,
619 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
620 "one/two/three",
621 "B"
622 );
623
624 assert_close!(root);
625 });
626 }
627
628 #[test]
629 fn add_empty_dir_already_exists() {
630 let mut tree = TreeBuilder::empty_dir();
631 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
632 tree.add_empty_dir(&["one", "two"]).unwrap();
633
634 let root = tree.build();
635
636 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
637 assert_read_dirents_one_listing!(
638 root, 1000,
639 { DIRECTORY, b"." },
640 { DIRECTORY, b"one" },
641 );
642 assert_read_dirents_path_one_listing!(
643 &root, "one", 1000,
644 { DIRECTORY, b"." },
645 { DIRECTORY, b"two" },
646 );
647 assert_read_dirents_path_one_listing!(
648 &root, "one/two", 1000,
649 { DIRECTORY, b"." },
650 { FILE, b"three" },
651 );
652
653 open_as_vmo_file_assert_content!(
654 &root,
655 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
656 "one/two/three",
657 "B"
658 );
659
660 assert_close!(root);
661 });
662 }
663
664 #[test]
665 fn lone_add_empty_dir() {
666 let mut tree = TreeBuilder::empty_dir();
667 tree.add_empty_dir(&["just-me"]).unwrap();
668
669 let root = tree.build();
670
671 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
672 assert_read_dirents_one_listing!(
673 root, 1000,
674 { DIRECTORY, b"." },
675 { DIRECTORY, b"just-me" },
676 );
677 assert_read_dirents_path_one_listing!(
678 &root, "just-me", 1000,
679 { DIRECTORY, b"." },
680 );
681 assert_close!(root);
682 });
683 }
684
685 #[test]
686 fn add_empty_dir_inside_add_empty_dir() {
687 let mut tree = TreeBuilder::empty_dir();
688 tree.add_empty_dir(&["container"]).unwrap();
689 tree.add_empty_dir(&["container", "nested"]).unwrap();
690
691 let root = tree.build();
692
693 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
694 assert_read_dirents_one_listing!(
695 root, 1000,
696 { DIRECTORY, b"." },
697 { DIRECTORY, b"container" },
698 );
699 assert_read_dirents_path_one_listing!(
700 &root, "container", 1000,
701 { DIRECTORY, b"." },
702 { DIRECTORY, b"nested" },
703 );
704 assert_read_dirents_path_one_listing!(
705 &root, "container/nested", 1000,
706 { DIRECTORY, b"." },
707 );
708 assert_close!(root);
709 });
710 }
711
712 #[test]
713 fn error_empty_path_in_add_entry() {
714 let mut tree = TreeBuilder::empty_dir();
715 let err = tree
716 .add_entry(vec![], file::read_only(b"Invalid"))
717 .expect_err("Empty paths are not allowed.");
718 assert_eq!(err, Error::EmptyPath);
719 }
720
721 #[test]
722 fn error_slash_in_component() {
723 let mut tree = TreeBuilder::empty_dir();
724 let err = tree
725 .add_entry("a/b", file::read_only(b"Invalid"))
726 .expect_err("Slash in path component name.");
727 assert_eq!(
728 err,
729 Error::SlashInComponent { path: "a/b".to_string(), component: "a/b".to_string() }
730 );
731 }
732
733 #[test]
734 fn error_slash_in_second_component() {
735 let mut tree = TreeBuilder::empty_dir();
736 let err = tree
737 .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
738 .expect_err("Slash in path component name.");
739 assert_eq!(
740 err,
741 Error::SlashInComponent { path: "a/b/c".to_string(), component: "b/c".to_string() }
742 );
743 }
744
745 #[test]
746 fn error_component_name_too_long() {
747 let mut tree = TreeBuilder::empty_dir();
748
749 let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
750
751 let path: &[&str] = &["a", &long_component, "b"];
752 let err = tree
753 .add_entry(path, file::read_only(b"Invalid"))
754 .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
755 assert_eq!(
756 err,
757 Error::ComponentNameTooLong {
758 path: format!("a/{}/b", long_component),
759 component: long_component.clone(),
760 component_len: long_component.len(),
761 max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
762 }
763 );
764 }
765
766 #[test]
767 fn error_leaf_over_directory() {
768 let mut tree = TreeBuilder::empty_dir();
769
770 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
771 let err = tree
772 .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
773 .expect_err("A leaf may not be constructed over a directory.");
774 assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
775 }
776
777 #[test]
778 fn error_leaf_over_leaf() {
779 let mut tree = TreeBuilder::empty_dir();
780
781 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
782 let err = tree
783 .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
784 .expect_err("A leaf may not be constructed over another leaf.");
785 assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
786 }
787
788 #[test]
789 fn error_entry_inside_leaf() {
790 let mut tree = TreeBuilder::empty_dir();
791
792 tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
793 let err = tree
794 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
795 .expect_err("A leaf may not be constructed over another leaf.");
796 assert_eq!(
797 err,
798 Error::EntryInsideLeaf {
799 path: "top/file/nested".to_string(),
800 traversed: "top/file".to_string()
801 }
802 );
803 }
804
805 #[test]
806 fn error_entry_inside_leaf_directory() {
807 let mut tree = TreeBuilder::empty_dir();
808
809 tree.add_entry(&["top", "file"], Simple::new()).unwrap();
811 let err = tree
812 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
813 .expect_err("A leaf may not be constructed over another leaf.");
814 assert_eq!(
815 err,
816 Error::EntryInsideLeaf {
817 path: "top/file/nested".to_string(),
818 traversed: "top/file".to_string()
819 }
820 );
821 }
822}