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::{
14 ffi::{OsStr, OsString},
15 path::{Path, PathBuf},
16 };
17
18 #[cfg(unix)]
19 use super::unix as sys;
20
21 #[cfg(windows)]
22 use super::windows as sys;
23
24 pub fn null_path() -> &'static Path {
29 Path::new(sys::NULL_PATH)
30 }
31
32 pub fn sanitize_str(str: impl AsRef<str>) -> String {
34 sanitize_os_str(str.as_ref())
35 .into_string()
36 .expect("Put UTF-8 in, got non-UTF-8 out")
37 }
38
39 pub fn sanitize_os_str(os_str: impl AsRef<OsStr>) -> OsString {
41 sys::sanitize_string(os_str)
42 }
43
44 pub fn sanitize_path(path: impl AsRef<Path>) -> PathBuf {
46 path.as_ref()
47 .components()
48 .map(|c| match c {
49 std::path::Component::Normal(os_str) => sanitize_os_str(os_str),
50 _ => c.as_os_str().to_owned(),
51 })
52 .collect()
53 }
54
55 pub fn is_protected_path(item: impl AsRef<Path>) -> Vec<PathBuf> {
59 let item = item.as_ref();
60
61 let mut protected_by = Vec::new();
62
63 if let Some(path) = sys::protected_paths().iter().find(|p| *p == item) {
64 protected_by.push(path.to_owned());
65 }
66
67 sys::protected_directories().iter().for_each(|path| {
68 if item.starts_with(path) {
69 protected_by.push(path.to_owned());
70 }
71 });
72
73 protected_by
74 }
75}
76
77pub fn absolutize_to_cwd(path: impl AsRef<Path>) -> io::Result<PathBuf> {
83 let abs = env::current_dir()?.join(path);
84 Ok(abs)
85}
86
87pub fn latest_existing_ancestor(path: impl AsRef<Path>) -> io::Result<PathBuf> {
92 let mut latest = Some(path.as_ref());
93
94 while let Some(path) = latest {
95 match path.symlink_metadata() {
96 Ok(meta) => {
97 if meta.is_dir() {
98 return Ok(path.to_path_buf());
99 }
100 latest = path.parent()
101 }
102 Err(err) if matches!(err.kind(), io::ErrorKind::NotFound) => latest = path.parent(),
103 Err(err) => return Err(err),
104 }
105 }
106
107 Err(io::Error::new(
108 io::ErrorKind::NotFound,
109 format!(
110 "No ancestor was found for {path}",
111 path = path.as_ref().display()
112 ),
113 ))
114}
115
116pub fn normalize_path(path: impl AsRef<Path>) -> PathBuf {
118 let mut normalized = PathBuf::new();
119
120 for component in path.as_ref().components() {
121 match component {
122 path::Component::CurDir => {}
123 path::Component::ParentDir => {
124 if !normalized.pop() {
125 normalized.push("..");
126 }
127 }
128 _ => normalized.push(component),
129 }
130 }
131
132 normalized.iter().collect()
133}
134
135pub fn follow_symlink(symlink: impl AsRef<Path>) -> io::Result<PathBuf> {
141 let target = fs::read_link(symlink.as_ref())?;
142
143 let real_target = if target.is_absolute() {
144 target
145 } else {
146 symlink
147 .as_ref()
148 .parent()
149 .unwrap_or(Path::new("/"))
150 .join(target)
151 };
152
153 if let Err(err) = real_target.symlink_metadata() {
154 if matches!(err.kind(), io::ErrorKind::NotFound) {
155 return Err(io::Error::new(
156 io::ErrorKind::NotFound,
157 format!(
158 "Target of symlink '{symlink}' does not exist ({target})",
159 symlink = symlink.as_ref().display(),
160 target = real_target.display()
161 ),
162 ));
163 } else {
164 return Err(err);
165 }
166 };
167
168 Ok(real_target)
169}
170
171pub fn follow_symlink_chain(symlink: impl AsRef<Path>) -> (Vec<PathBuf>, Option<io::Error>) {
179 let mut stack = vec![symlink.as_ref().to_path_buf()];
180
181 loop {
182 let target = match follow_symlink(stack.last().unwrap()) {
183 Ok(target) => normalize_path(target),
184 Err(err) => return (stack, Some(err)),
185 };
186
187 if stack.contains(&target) {
188 stack.push(target);
189 return (
190 stack,
191 Some(io::Error::new(
192 io::ErrorKind::TooManyLinks,
193 format!(
194 "{symlink} is a cyclical",
195 symlink = symlink.as_ref().display()
196 ),
197 )),
198 );
199 }
200
201 if target.is_symlink() {
202 stack.push(target);
203 continue;
204 } else {
205 stack.push(target);
206 return (stack, None);
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 #[test]
214 fn sanitize_is_lossless() {
215 use std::ffi::OsString;
216
217 use crate::paths::sys::sanitize_os_str;
218
219 let os_str = OsString::from("nothing_to_replace");
220 let sanitized = sanitize_os_str(os_str);
221
222 assert_eq!(sanitized, OsString::from("nothing_to_replace"))
223 }
224}