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_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 async fn get_id_of_path(root: &fio::DirectoryProxy, path: &str) -> u64 {
387 let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
388 root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
389 .expect("failed to call open");
390 let (_, immutable_attrs) = proxy
391 .get_attributes(fio::NodeAttributesQuery::ID)
392 .await
393 .expect("FIDL call failed")
394 .expect("GetAttributes failed");
395 immutable_attrs.id.expect("ID missing from GetAttributes response")
396 }
397
398 #[test]
399 fn vfs_with_custom_inodes() {
400 let mut tree = TreeBuilder::empty_dir();
401 tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
402 tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
403
404 let mut get_inode = |name: String| -> u64 {
405 match &name[..] {
406 "a" => 1,
407 "b" => 2,
408 "c" => 3,
409 _ => fio::INO_UNKNOWN,
410 }
411 };
412 let root = tree.build_with_inode_generator(&mut get_inode);
413
414 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
415 assert_eq!(get_id_of_path(&root, "a").await, 1);
416 assert_eq!(get_id_of_path(&root, "a/b").await, 2);
417 assert_eq!(get_id_of_path(&root, "a/c").await, 3);
418 });
419 }
420
421 #[test]
422 fn two_files() {
423 let mut tree = TreeBuilder::empty_dir();
424 tree.add_entry("a", file::read_only(b"A content")).unwrap();
425 tree.add_entry("b", file::read_only(b"B content")).unwrap();
426
427 let root = tree.build();
428
429 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
430 assert_read_dirents_one_listing!(
431 root, 1000,
432 { DIRECTORY, b"." },
433 { FILE, b"a" },
434 { FILE, b"b" },
435 );
436 open_as_vmo_file_assert_content!(
437 &root,
438 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
439 "a",
440 "A content"
441 );
442 open_as_vmo_file_assert_content!(
443 &root,
444 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
445 "b",
446 "B content"
447 );
448
449 assert_close!(root);
450 });
451 }
452
453 #[test]
454 fn overlapping_paths() {
455 let mut tree = TreeBuilder::empty_dir();
456 tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
457 tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
458 tree.add_entry("four", file::read_only(b"C")).unwrap();
459
460 let root = tree.build();
461
462 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
463 assert_read_dirents_one_listing!(
464 root, 1000,
465 { DIRECTORY, b"." },
466 { FILE, b"four" },
467 { DIRECTORY, b"one" },
468 );
469 assert_read_dirents_path_one_listing!(
470 &root, "one", 1000,
471 { DIRECTORY, b"." },
472 { FILE, b"three" },
473 { FILE, b"two" },
474 );
475
476 open_as_vmo_file_assert_content!(
477 &root,
478 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
479 "one/two",
480 "A"
481 );
482 open_as_vmo_file_assert_content!(
483 &root,
484 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
485 "one/three",
486 "B"
487 );
488 open_as_vmo_file_assert_content!(
489 &root,
490 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
491 "four",
492 "C"
493 );
494
495 assert_close!(root);
496 });
497 }
498
499 #[test]
500 fn directory_leaf() {
501 let etc = pseudo_directory! {
502 "fstab" => file::read_only(b"/dev/fs /"),
503 "ssh" => pseudo_directory! {
504 "sshd_config" => file::read_only(b"# Empty"),
505 },
506 };
507
508 let mut tree = TreeBuilder::empty_dir();
509 tree.add_entry("etc", etc).unwrap();
510 tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
511
512 let root = tree.build();
513
514 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
515 assert_read_dirents_one_listing!(
516 root, 1000,
517 { DIRECTORY, b"." },
518 { DIRECTORY, b"etc" },
519 { FILE, b"uname" },
520 );
521 assert_read_dirents_path_one_listing!(
522 &root, "etc", 1000,
523 { DIRECTORY, b"." },
524 { FILE, b"fstab" },
525 { DIRECTORY, b"ssh" },
526 );
527 assert_read_dirents_path_one_listing!(
528 &root, "etc/ssh", 1000,
529 { DIRECTORY, b"." },
530 { FILE, b"sshd_config" },
531 );
532
533 open_as_vmo_file_assert_content!(
534 &root,
535 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
536 "etc/fstab",
537 "/dev/fs /"
538 );
539 open_as_vmo_file_assert_content!(
540 &root,
541 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
542 "etc/ssh/sshd_config",
543 "# Empty"
544 );
545 open_as_vmo_file_assert_content!(
546 &root,
547 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
548 "uname",
549 "Fuchsia"
550 );
551
552 assert_close!(root);
553 });
554 }
555
556 #[test]
557 fn add_empty_dir_populate_later() {
558 let mut tree = TreeBuilder::empty_dir();
559 tree.add_empty_dir(&["one", "two"]).unwrap();
560 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
561
562 let root = tree.build();
563
564 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
565 assert_read_dirents_one_listing!(
566 root, 1000,
567 { DIRECTORY, b"." },
568 { DIRECTORY, b"one" },
569 );
570 assert_read_dirents_path_one_listing!(
571 &root, "one", 1000,
572 { DIRECTORY, b"." },
573 { DIRECTORY, b"two" },
574 );
575 assert_read_dirents_path_one_listing!(
576 &root, "one/two", 1000,
577 { DIRECTORY, b"." },
578 { FILE, b"three" },
579 );
580
581 open_as_vmo_file_assert_content!(
582 &root,
583 fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
584 "one/two/three",
585 "B"
586 );
587
588 assert_close!(root);
589 });
590 }
591
592 #[test]
593 fn add_empty_dir_already_exists() {
594 let mut tree = TreeBuilder::empty_dir();
595 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
596 tree.add_empty_dir(&["one", "two"]).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 lone_add_empty_dir() {
630 let mut tree = TreeBuilder::empty_dir();
631 tree.add_empty_dir(&["just-me"]).unwrap();
632
633 let root = tree.build();
634
635 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
636 assert_read_dirents_one_listing!(
637 root, 1000,
638 { DIRECTORY, b"." },
639 { DIRECTORY, b"just-me" },
640 );
641 assert_read_dirents_path_one_listing!(
642 &root, "just-me", 1000,
643 { DIRECTORY, b"." },
644 );
645 assert_close!(root);
646 });
647 }
648
649 #[test]
650 fn add_empty_dir_inside_add_empty_dir() {
651 let mut tree = TreeBuilder::empty_dir();
652 tree.add_empty_dir(&["container"]).unwrap();
653 tree.add_empty_dir(&["container", "nested"]).unwrap();
654
655 let root = tree.build();
656
657 run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
658 assert_read_dirents_one_listing!(
659 root, 1000,
660 { DIRECTORY, b"." },
661 { DIRECTORY, b"container" },
662 );
663 assert_read_dirents_path_one_listing!(
664 &root, "container", 1000,
665 { DIRECTORY, b"." },
666 { DIRECTORY, b"nested" },
667 );
668 assert_read_dirents_path_one_listing!(
669 &root, "container/nested", 1000,
670 { DIRECTORY, b"." },
671 );
672 assert_close!(root);
673 });
674 }
675
676 #[test]
677 fn error_empty_path_in_add_entry() {
678 let mut tree = TreeBuilder::empty_dir();
679 let err = tree
680 .add_entry(vec![], file::read_only(b"Invalid"))
681 .expect_err("Empty paths are not allowed.");
682 assert_eq!(err, Error::EmptyPath);
683 }
684
685 #[test]
686 fn error_slash_in_component() {
687 let mut tree = TreeBuilder::empty_dir();
688 let err = tree
689 .add_entry("a/b", file::read_only(b"Invalid"))
690 .expect_err("Slash in path component name.");
691 assert_eq!(
692 err,
693 Error::SlashInComponent { path: "a/b".to_string(), component: "a/b".to_string() }
694 );
695 }
696
697 #[test]
698 fn error_slash_in_second_component() {
699 let mut tree = TreeBuilder::empty_dir();
700 let err = tree
701 .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
702 .expect_err("Slash in path component name.");
703 assert_eq!(
704 err,
705 Error::SlashInComponent { path: "a/b/c".to_string(), component: "b/c".to_string() }
706 );
707 }
708
709 #[test]
710 fn error_component_name_too_long() {
711 let mut tree = TreeBuilder::empty_dir();
712
713 let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
714
715 let path: &[&str] = &["a", &long_component, "b"];
716 let err = tree
717 .add_entry(path, file::read_only(b"Invalid"))
718 .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
719 assert_eq!(
720 err,
721 Error::ComponentNameTooLong {
722 path: format!("a/{}/b", long_component),
723 component: long_component.clone(),
724 component_len: long_component.len(),
725 max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
726 }
727 );
728 }
729
730 #[test]
731 fn error_leaf_over_directory() {
732 let mut tree = TreeBuilder::empty_dir();
733
734 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
735 let err = tree
736 .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
737 .expect_err("A leaf may not be constructed over a directory.");
738 assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
739 }
740
741 #[test]
742 fn error_leaf_over_leaf() {
743 let mut tree = TreeBuilder::empty_dir();
744
745 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
746 let err = tree
747 .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
748 .expect_err("A leaf may not be constructed over another leaf.");
749 assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
750 }
751
752 #[test]
753 fn error_entry_inside_leaf() {
754 let mut tree = TreeBuilder::empty_dir();
755
756 tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
757 let err = tree
758 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
759 .expect_err("A leaf may not be constructed over another leaf.");
760 assert_eq!(
761 err,
762 Error::EntryInsideLeaf {
763 path: "top/file/nested".to_string(),
764 traversed: "top/file".to_string()
765 }
766 );
767 }
768
769 #[test]
770 fn error_entry_inside_leaf_directory() {
771 let mut tree = TreeBuilder::empty_dir();
772
773 tree.add_entry(&["top", "file"], Simple::new()).unwrap();
775 let err = tree
776 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
777 .expect_err("A leaf may not be constructed over another leaf.");
778 assert_eq!(
779 err,
780 Error::EntryInsideLeaf {
781 path: "top/file/nested".to_string(),
782 traversed: "top/file".to_string()
783 }
784 );
785 }
786}