broot/tree/
tree_line_type.rs1use {
2 rustc_hash::FxHashSet,
3 std::{
4 fs,
5 io,
6 path::{
7 Path,
8 PathBuf,
9 },
10 },
11};
12
13const MAX_LINK_CHAIN_LENGTH: usize = 128;
15
16#[derive(Debug, Clone, PartialEq)]
19pub enum TreeLineType {
20 File,
21 Dir,
22 BrokenSymLink(String),
23 SymLink {
24 direct_target: String,
25 final_is_dir: bool,
26 final_target: PathBuf,
27 },
28 Pruning, }
30
31pub fn read_link(path: &Path) -> io::Result<PathBuf> {
32 let mut target = fs::read_link(path)?;
33 if target.is_relative() {
34 target = path.parent().unwrap().join(&target);
35 }
36 Ok(target)
37}
38
39impl TreeLineType {
40 pub fn is_pruning(&self) -> bool {
41 matches!(self, Self::Pruning)
42 }
43
44 fn resolve(direct_target: &Path) -> io::Result<Self> {
45 let mut final_target = direct_target.to_path_buf();
46 let mut final_metadata = fs::symlink_metadata(&final_target)?;
47 let mut final_ft = final_metadata.file_type();
48 let mut final_is_dir = final_ft.is_dir();
49 let mut link_chain_length = 0;
50 let mut visited = FxHashSet::default();
51 while final_ft.is_symlink() {
52 final_target = read_link(&final_target)?;
53 if visited.contains(&final_target) {
54 info!(
55 "circular symlink opened by {} and closed by {}",
56 direct_target.display(),
57 final_target.display(),
58 );
59 return Ok(Self::BrokenSymLink(
60 direct_target.to_string_lossy().into_owned(),
61 ));
62 }
63 visited.insert(final_target.clone());
64 final_metadata = fs::symlink_metadata(&final_target)?;
65 final_ft = final_metadata.file_type();
66 final_is_dir = final_ft.is_dir();
67 link_chain_length += 1;
68 if link_chain_length > MAX_LINK_CHAIN_LENGTH {
69 info!("too long link chain at {}", direct_target.display());
70 return Ok(Self::BrokenSymLink(
71 direct_target.to_string_lossy().into_owned(),
72 ));
73 }
74 }
75 let direct_target = direct_target.to_string_lossy().into_owned();
76 Ok(Self::SymLink {
77 direct_target,
78 final_is_dir,
79 final_target,
80 })
81 }
82
83 pub fn new(
84 path: &Path,
85 ft: fs::FileType,
86 ) -> Self {
87 if ft.is_dir() {
88 Self::Dir
89 } else if ft.is_symlink() {
90 if let Ok(direct_target) = read_link(path) {
91 Self::resolve(&direct_target).unwrap_or_else(|_| {
92 Self::BrokenSymLink(direct_target.to_string_lossy().to_string())
93 })
94 } else {
95 Self::BrokenSymLink("???".to_string())
96 }
97 } else {
98 Self::File
99 }
100 }
101}