1use std::{
2 env, fs, io, path,
3 path::{Path, PathBuf},
4};
5
6#[cfg(unix)]
7pub mod unix;
8
9#[cfg(windows)]
10pub mod windows;
11
12pub mod sys {
13 use std::path::{Path, PathBuf};
14
15 #[cfg(unix)]
16 use super::unix as sys;
17
18 #[cfg(windows)]
19 use super::windows as sys;
20
21 pub fn is_protected_path(item: impl AsRef<Path>) -> Vec<PathBuf> {
25 let item = item.as_ref();
26
27 let mut protected_by = Vec::new();
28
29 if let Some(path) = sys::protected_paths().iter().find(|p| *p == item) {
30 protected_by.push(path.to_owned());
31 }
32
33 sys::protected_directories().iter().for_each(|path| {
34 if item.starts_with(path) {
35 protected_by.push(path.to_owned());
36 }
37 });
38
39 protected_by
40 }
41}
42
43pub fn absolutize_to_cwd(path: impl AsRef<Path>) -> io::Result<PathBuf> {
49 let abs = env::current_dir()?.join(path);
50 Ok(abs)
51}
52
53pub fn latest_existing_ancestor(path: impl AsRef<Path>) -> io::Result<PathBuf> {
58 let mut latest = Some(path.as_ref());
59
60 while let Some(path) = latest {
61 match path.symlink_metadata() {
62 Ok(meta) => {
63 if meta.is_dir() {
64 return Ok(path.to_path_buf());
65 }
66 latest = path.parent()
67 }
68 Err(err) if matches!(err.kind(), io::ErrorKind::NotFound) => latest = path.parent(),
69 Err(err) => return Err(err),
70 }
71 }
72
73 Err(io::Error::new(
74 io::ErrorKind::NotFound,
75 format!(
76 "No ancestor was found for {path}",
77 path = path.as_ref().display()
78 ),
79 ))
80}
81
82pub fn normalize_path(path: impl AsRef<Path>) -> PathBuf {
84 let mut normalized = PathBuf::new();
85
86 for component in path.as_ref().components() {
87 match component {
88 path::Component::CurDir => {}
89 path::Component::ParentDir => {
90 if !normalized.pop() {
91 normalized.push("..");
92 }
93 }
94 _ => normalized.push(component),
95 }
96 }
97
98 normalized.iter().collect()
99}
100
101pub fn follow_symlink(symlink: impl AsRef<Path>) -> io::Result<PathBuf> {
107 let target = fs::read_link(symlink.as_ref())?;
108
109 let real_target = if target.is_absolute() {
110 target
111 } else {
112 symlink
113 .as_ref()
114 .parent()
115 .unwrap_or(Path::new("/"))
116 .join(target)
117 };
118
119 if let Err(err) = real_target.symlink_metadata() {
120 if matches!(err.kind(), io::ErrorKind::NotFound) {
121 return Err(io::Error::new(
122 io::ErrorKind::NotFound,
123 format!(
124 "Target of symlink '{symlink}' does not exist ({target})",
125 symlink = symlink.as_ref().display(),
126 target = real_target.display()
127 ),
128 ));
129 } else {
130 return Err(err);
131 }
132 };
133
134 Ok(real_target)
135}
136
137pub fn follow_symlink_chain(symlink: impl AsRef<Path>) -> (Vec<PathBuf>, Option<io::Error>) {
145 let mut stack = vec![symlink.as_ref().to_path_buf()];
146
147 loop {
148 let target = match follow_symlink(stack.last().unwrap()) {
149 Ok(target) => normalize_path(target),
150 Err(err) => return (stack, Some(err)),
151 };
152
153 if stack.contains(&target) {
154 stack.push(target);
155 return (
156 stack,
157 Some(io::Error::new(
158 io::ErrorKind::TooManyLinks,
159 format!(
160 "{symlink} is a cyclical",
161 symlink = symlink.as_ref().display()
162 ),
163 )),
164 );
165 }
166
167 if target.is_symlink() {
168 stack.push(target);
169 continue;
170 } else {
171 stack.push(target);
172 return (stack, None);
173 }
174 }
175}