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 = |_: &str| -> u64 { fio::INO_UNKNOWN };
264 self.build_with_inode_generator(&mut generator)
265 }
266
267 pub fn build_with_inode_generator<F>(self, get_inode: &mut F) -> Arc<Simple>
272 where
273 F: for<'a> FnMut(&'a str) -> u64,
274 {
275 match self {
276 TreeBuilder::Directory(mut entries) => {
277 let res = Simple::new_with_inode(get_inode("."));
278 for (name, child) in entries.drain() {
279 let child = child.build_dyn(&name, get_inode);
280 res.add_entry(name, child)
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<F>(self, dir: &str, get_inode: &mut F) -> Arc<dyn DirectoryEntry>
296 where
297 F: for<'a> FnMut(&'a str) -> u64,
298 {
299 match self {
300 TreeBuilder::Directory(mut entries) => {
301 let res = Simple::new_with_inode(get_inode(dir));
302 for (name, child) in entries.drain() {
303 let child = child.build_dyn(&name, get_inode);
304 res.add_entry(name, child)
305 .map_err(|status| format!("Status: {}", status))
306 .expect(
307 "Internal error. We have already checked all the entry names. \
308 There should be no collisions, nor overly long names.",
309 );
310 }
311 res
312 }
313 TreeBuilder::Leaf(entry) => entry,
314 }
315 }
316}
317
318#[derive(Debug, Error, PartialEq, Eq)]
319pub enum Error {
320 #[error("`add_entry` requires a non-empty path")]
321 EmptyPath,
322
323 #[error(
324 "Path component contains a forward slash.\n\
325 Path: {}\n\
326 Component: '{}'",
327 path,
328 component
329 )]
330 SlashInComponent { path: String, component: String },
331
332 #[error(
333 "Path component name is too long - {} characters. Maximum is {}.\n\
334 Path: {}\n\
335 Component: '{}'",
336 component_len,
337 max_len,
338 path,
339 component
340 )]
341 ComponentNameTooLong { path: String, component: String, component_len: usize, max_len: usize },
342
343 #[error(
344 "Trying to insert a leaf over an existing directory.\n\
345 Path: {}",
346 path
347 )]
348 LeafOverDirectory { path: String },
349
350 #[error(
351 "Trying to overwrite one leaf with another.\n\
352 Path: {}",
353 path
354 )]
355 LeafOverLeaf { path: String },
356
357 #[error(
358 "Trying to insert an entry inside a leaf.\n\
359 Leaf path: {}\n\
360 Path been inserted: {}",
361 path,
362 traversed
363 )]
364 EntryInsideLeaf { path: String, traversed: String },
365}
366
367#[cfg(test)]
368mod tests {
369 use super::{Error, Simple, TreeBuilder};
370
371 use crate::{assert_close, assert_read};
373
374 use crate::directory::serve;
375 use crate::file;
376
377 use fidl_fuchsia_io as fio;
378 use fuchsia_fs::directory::{DirEntry, DirentKind, open_directory, readdir};
379 use vfs_macros::pseudo_directory;
380
381 async fn assert_open_file_contents(
382 root: &fio::DirectoryProxy,
383 path: &str,
384 flags: fio::Flags,
385 expected_contents: &str,
386 ) {
387 let file = fuchsia_fs::directory::open_file(&root, path, flags).await.unwrap();
388 assert_read!(file, expected_contents);
389 assert_close!(file);
390 }
391
392 async fn get_id_of_path(root: &fio::DirectoryProxy, path: &str) -> u64 {
393 let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
394 root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
395 .expect("failed to call open");
396 let (_, immutable_attrs) = proxy
397 .get_attributes(fio::NodeAttributesQuery::ID)
398 .await
399 .expect("FIDL call failed")
400 .expect("GetAttributes failed");
401 immutable_attrs.id.expect("ID missing from GetAttributes response")
402 }
403
404 #[fuchsia::test]
405 async fn vfs_with_custom_inodes() {
406 let mut tree = TreeBuilder::empty_dir();
407 tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
408 tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
409
410 let mut get_inode = |name: &str| -> u64 {
411 match name {
412 "a" => 1,
413 "b" => 2,
414 "c" => 3,
415 _ => fio::INO_UNKNOWN,
416 }
417 };
418 let root = tree.build_with_inode_generator(&mut get_inode);
419 let root = serve(root, fio::PERM_READABLE);
420 assert_eq!(get_id_of_path(&root, "a").await, 1);
421 assert_eq!(get_id_of_path(&root, "a/b").await, 2);
422 assert_eq!(get_id_of_path(&root, "a/c").await, 3);
423 }
424
425 #[fuchsia::test]
426 async fn two_files() {
427 let mut tree = TreeBuilder::empty_dir();
428 tree.add_entry("a", file::read_only(b"A content")).unwrap();
429 tree.add_entry("b", file::read_only(b"B content")).unwrap();
430
431 let root = tree.build();
432 let root = serve(root, fio::PERM_READABLE);
433
434 assert_eq!(
435 readdir(&root).await.unwrap(),
436 vec![
437 DirEntry { name: String::from("a"), kind: DirentKind::File },
438 DirEntry { name: String::from("b"), kind: DirentKind::File },
439 ]
440 );
441 assert_open_file_contents(&root, "a", fio::PERM_READABLE, "A content").await;
442 assert_open_file_contents(&root, "b", fio::PERM_READABLE, "B content").await;
443
444 assert_close!(root);
445 }
446
447 #[fuchsia::test]
448 async fn overlapping_paths() {
449 let mut tree = TreeBuilder::empty_dir();
450 tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
451 tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
452 tree.add_entry("four", file::read_only(b"C")).unwrap();
453
454 let root = tree.build();
455 let root = serve(root, fio::PERM_READABLE);
456
457 assert_eq!(
458 readdir(&root).await.unwrap(),
459 vec![
460 DirEntry { name: String::from("four"), kind: DirentKind::File },
461 DirEntry { name: String::from("one"), kind: DirentKind::Directory },
462 ]
463 );
464 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
465 assert_eq!(
466 readdir(&one_dir).await.unwrap(),
467 vec![
468 DirEntry { name: String::from("three"), kind: DirentKind::File },
469 DirEntry { name: String::from("two"), kind: DirentKind::File },
470 ]
471 );
472 assert_close!(one_dir);
473
474 assert_open_file_contents(&root, "one/two", fio::PERM_READABLE, "A").await;
475 assert_open_file_contents(&root, "one/three", fio::PERM_READABLE, "B").await;
476 assert_open_file_contents(&root, "four", fio::PERM_READABLE, "C").await;
477
478 assert_close!(root);
479 }
480
481 #[fuchsia::test]
482 async fn directory_leaf() {
483 let etc = pseudo_directory! {
484 "fstab" => file::read_only(b"/dev/fs /"),
485 "ssh" => pseudo_directory! {
486 "sshd_config" => file::read_only(b"# Empty"),
487 },
488 };
489
490 let mut tree = TreeBuilder::empty_dir();
491 tree.add_entry("etc", etc).unwrap();
492 tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
493
494 let root = tree.build();
495 let root = serve(root, fio::PERM_READABLE);
496
497 assert_eq!(
498 readdir(&root).await.unwrap(),
499 vec![
500 DirEntry { name: String::from("etc"), kind: DirentKind::Directory },
501 DirEntry { name: String::from("uname"), kind: DirentKind::File },
502 ]
503 );
504 let etc_dir = open_directory(&root, "etc", fio::PERM_READABLE).await.unwrap();
505 assert_eq!(
506 readdir(&etc_dir).await.unwrap(),
507 vec![
508 DirEntry { name: String::from("fstab"), kind: DirentKind::File },
509 DirEntry { name: String::from("ssh"), kind: DirentKind::Directory },
510 ]
511 );
512 assert_close!(etc_dir);
513 let ssh_dir = open_directory(&root, "etc/ssh", fio::PERM_READABLE).await.unwrap();
514 assert_eq!(
515 readdir(&ssh_dir).await.unwrap(),
516 vec![DirEntry { name: String::from("sshd_config"), kind: DirentKind::File }]
517 );
518 assert_close!(ssh_dir);
519
520 assert_open_file_contents(&root, "etc/fstab", fio::PERM_READABLE, "/dev/fs /").await;
521 assert_open_file_contents(&root, "etc/ssh/sshd_config", fio::PERM_READABLE, "# Empty")
522 .await;
523 assert_open_file_contents(&root, "uname", fio::PERM_READABLE, "Fuchsia").await;
524
525 assert_close!(root);
526 }
527
528 #[fuchsia::test]
529 async fn add_empty_dir_populate_later() {
530 let mut tree = TreeBuilder::empty_dir();
531 tree.add_empty_dir(&["one", "two"]).unwrap();
532 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
533
534 let root = tree.build();
535 let root = serve(root, fio::PERM_READABLE);
536
537 assert_eq!(
538 readdir(&root).await.unwrap(),
539 vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
540 );
541 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
542 assert_eq!(
543 readdir(&one_dir).await.unwrap(),
544 vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
545 );
546 assert_close!(one_dir);
547 let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
548 assert_eq!(
549 readdir(&two_dir).await.unwrap(),
550 vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
551 );
552 assert_close!(two_dir);
553
554 assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
555
556 assert_close!(root);
557 }
558
559 #[fuchsia::test]
560 async fn add_empty_dir_already_exists() {
561 let mut tree = TreeBuilder::empty_dir();
562 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
563 tree.add_empty_dir(&["one", "two"]).unwrap();
564
565 let root = tree.build();
566 let root = serve(root, fio::PERM_READABLE);
567
568 assert_eq!(
569 readdir(&root).await.unwrap(),
570 vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
571 );
572
573 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
574 assert_eq!(
575 readdir(&one_dir).await.unwrap(),
576 vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
577 );
578 assert_close!(one_dir);
579
580 let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
581 assert_eq!(
582 readdir(&two_dir).await.unwrap(),
583 vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
584 );
585 assert_close!(two_dir);
586
587 assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
588
589 assert_close!(root);
590 }
591
592 #[fuchsia::test]
593 async fn lone_add_empty_dir() {
594 let mut tree = TreeBuilder::empty_dir();
595 tree.add_empty_dir(&["just-me"]).unwrap();
596
597 let root = tree.build();
598 let root = serve(root, fio::PERM_READABLE);
599
600 assert_eq!(
601 readdir(&root).await.unwrap(),
602 vec![DirEntry { name: String::from("just-me"), kind: DirentKind::Directory }]
603 );
604 let just_me_dir = open_directory(&root, "just-me", fio::PERM_READABLE).await.unwrap();
605 assert_eq!(readdir(&just_me_dir).await.unwrap(), Vec::new());
606
607 assert_close!(just_me_dir);
608 assert_close!(root);
609 }
610
611 #[fuchsia::test]
612 async fn add_empty_dir_inside_add_empty_dir() {
613 let mut tree = TreeBuilder::empty_dir();
614 tree.add_empty_dir(&["container"]).unwrap();
615 tree.add_empty_dir(&["container", "nested"]).unwrap();
616
617 let root = tree.build();
618 let root = serve(root, fio::PERM_READABLE);
619
620 assert_eq!(
621 readdir(&root).await.unwrap(),
622 vec![DirEntry { name: String::from("container"), kind: DirentKind::Directory }]
623 );
624
625 let container_dir = open_directory(&root, "container", fio::PERM_READABLE).await.unwrap();
626 assert_eq!(
627 readdir(&container_dir).await.unwrap(),
628 vec![DirEntry { name: String::from("nested"), kind: DirentKind::Directory }]
629 );
630 assert_close!(container_dir);
631
632 let nested_dir =
633 open_directory(&root, "container/nested", fio::PERM_READABLE).await.unwrap();
634 assert_eq!(readdir(&nested_dir).await.unwrap(), Vec::new());
635 assert_close!(nested_dir);
636
637 assert_close!(root);
638 }
639
640 #[fuchsia::test]
641 fn error_empty_path_in_add_entry() {
642 let mut tree = TreeBuilder::empty_dir();
643 let err = tree
644 .add_entry(vec![], file::read_only(b"Invalid"))
645 .expect_err("Empty paths are not allowed.");
646 assert_eq!(err, Error::EmptyPath);
647 }
648
649 #[fuchsia::test]
650 fn error_slash_in_component() {
651 let mut tree = TreeBuilder::empty_dir();
652 let err = tree
653 .add_entry("a/b", file::read_only(b"Invalid"))
654 .expect_err("Slash in path component name.");
655 assert_eq!(
656 err,
657 Error::SlashInComponent { path: "a/b".to_string(), component: "a/b".to_string() }
658 );
659 }
660
661 #[fuchsia::test]
662 fn error_slash_in_second_component() {
663 let mut tree = TreeBuilder::empty_dir();
664 let err = tree
665 .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
666 .expect_err("Slash in path component name.");
667 assert_eq!(
668 err,
669 Error::SlashInComponent { path: "a/b/c".to_string(), component: "b/c".to_string() }
670 );
671 }
672
673 #[fuchsia::test]
674 fn error_component_name_too_long() {
675 let mut tree = TreeBuilder::empty_dir();
676
677 let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
678
679 let path: &[&str] = &["a", &long_component, "b"];
680 let err = tree
681 .add_entry(path, file::read_only(b"Invalid"))
682 .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
683 assert_eq!(
684 err,
685 Error::ComponentNameTooLong {
686 path: format!("a/{}/b", long_component),
687 component: long_component.clone(),
688 component_len: long_component.len(),
689 max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
690 }
691 );
692 }
693
694 #[fuchsia::test]
695 fn error_leaf_over_directory() {
696 let mut tree = TreeBuilder::empty_dir();
697
698 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
699 let err = tree
700 .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
701 .expect_err("A leaf may not be constructed over a directory.");
702 assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
703 }
704
705 #[fuchsia::test]
706 fn error_leaf_over_leaf() {
707 let mut tree = TreeBuilder::empty_dir();
708
709 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
710 let err = tree
711 .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
712 .expect_err("A leaf may not be constructed over another leaf.");
713 assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
714 }
715
716 #[fuchsia::test]
717 fn error_entry_inside_leaf() {
718 let mut tree = TreeBuilder::empty_dir();
719
720 tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
721 let err = tree
722 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
723 .expect_err("A leaf may not be constructed over another leaf.");
724 assert_eq!(
725 err,
726 Error::EntryInsideLeaf {
727 path: "top/file/nested".to_string(),
728 traversed: "top/file".to_string()
729 }
730 );
731 }
732
733 #[fuchsia::test]
734 fn error_entry_inside_leaf_directory() {
735 let mut tree = TreeBuilder::empty_dir();
736
737 tree.add_entry(&["top", "file"], Simple::new()).unwrap();
739 let err = tree
740 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
741 .expect_err("A leaf may not be constructed over another leaf.");
742 assert_eq!(
743 err,
744 Error::EntryInsideLeaf {
745 path: "top/file/nested".to_string(),
746 traversed: "top/file".to_string()
747 }
748 );
749 }
750}