1use crate::error::{Error, Result};
2use crate::gen::fs;
3use crate::paths;
4use std::path::{Component, Path, PathBuf};
5use std::{env, io};
6
7pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> {
8 let path = path.as_ref();
9
10 let mut create_dir_error = None;
11 if fs::exists(path) {
12 if let Ok(existing) = fs::read(path) {
13 if existing == content {
14 return Ok(());
16 }
17 }
18 best_effort_remove(path);
19 } else {
20 let parent = path.parent().unwrap();
21 create_dir_error = fs::create_dir_all(parent).err();
22 }
23
24 match fs::write(path, content) {
25 Ok(()) => Ok(()),
27 Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
29 }
30}
31
32pub(crate) fn relative_symlink_file(
33 original: impl AsRef<Path>,
34 link: impl AsRef<Path>,
35) -> Result<()> {
36 let original = original.as_ref();
37 let link = link.as_ref();
38
39 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
40 let relativized = best_effort_relativize_symlink(original, link);
41
42 symlink_file(&relativized, original, link, parent_directory_error)
43}
44
45pub(crate) fn absolute_symlink_file(
46 original: impl AsRef<Path>,
47 link: impl AsRef<Path>,
48) -> Result<()> {
49 let original = original.as_ref();
50 let link = link.as_ref();
51
52 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
53
54 symlink_file(original, original, link, parent_directory_error)
55}
56
57pub(crate) fn relative_symlink_dir(
58 original: impl AsRef<Path>,
59 link: impl AsRef<Path>,
60) -> Result<()> {
61 let original = original.as_ref();
62 let link = link.as_ref();
63
64 let parent_directory_error = prepare_parent_directory_for_symlink(link).err();
65 let relativized = best_effort_relativize_symlink(original, link);
66
67 symlink_dir(&relativized, link, parent_directory_error)
68}
69
70fn prepare_parent_directory_for_symlink(link: &Path) -> fs::Result<()> {
71 if fs::exists(link) {
72 best_effort_remove(link);
73 Ok(())
74 } else {
75 let parent = link.parent().unwrap();
76 fs::create_dir_all(parent)
77 }
78}
79
80fn symlink_file(
81 path_for_symlink: &Path,
82 path_for_copy: &Path,
83 link: &Path,
84 parent_directory_error: Option<fs::Error>,
85) -> Result<()> {
86 match paths::symlink_or_copy(path_for_symlink, path_for_copy, link) {
87 Ok(()) => Ok(()),
89 Err(err) => {
90 if err.kind() == io::ErrorKind::AlreadyExists {
91 Ok(())
99 } else {
100 Err(Error::Fs(parent_directory_error.unwrap_or(err)))
103 }
104 }
105 }
106}
107
108fn symlink_dir(
109 path_for_symlink: &Path,
110 link: &Path,
111 parent_directory_error: Option<fs::Error>,
112) -> Result<()> {
113 match fs::symlink_dir(path_for_symlink, link) {
114 Ok(()) => Ok(()),
116 Err(err) => Err(Error::Fs(parent_directory_error.unwrap_or(err))),
118 }
119}
120
121fn best_effort_remove(path: &Path) {
122 use std::fs;
123
124 if cfg!(windows) {
125 if let Ok(metadata) = fs::metadata(path) {
130 if metadata.is_dir() {
131 let _ = fs::remove_dir_all(path);
132 } else {
133 let _ = fs::remove_file(path);
134 }
135 } else if fs::symlink_metadata(path).is_ok() {
136 if fs::remove_dir_all(path).is_err() {
140 let _ = fs::remove_file(path);
141 }
142 }
143 } else {
144 if let Ok(metadata) = fs::symlink_metadata(path) {
147 if metadata.is_dir() {
148 let _ = fs::remove_dir_all(path);
149 } else {
150 let _ = fs::remove_file(path);
151 }
152 }
153 }
154}
155
156fn best_effort_relativize_symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> PathBuf {
157 let original = original.as_ref();
158 let link = link.as_ref();
159
160 let Some(relative_path) = abstractly_relativize_symlink(original, link) else {
161 return original.to_path_buf();
162 };
163
164 if let Ok(original_canonical) = original.canonicalize() {
170 if let Ok(relative_canonical) = link.parent().unwrap().join(&relative_path).canonicalize() {
171 if original_canonical == relative_canonical {
172 return relative_path;
173 }
174 }
175 }
176
177 original.to_path_buf()
178}
179
180fn abstractly_relativize_symlink(
181 original: impl AsRef<Path>,
182 link: impl AsRef<Path>,
183) -> Option<PathBuf> {
184 let original = original.as_ref();
185 let link = link.as_ref();
186
187 let likely_no_semantic_prefix = env::var_os("CARGO_TARGET_DIR").is_some();
201
202 if likely_no_semantic_prefix
203 || original.is_relative()
204 || link.is_relative()
205 || path_contains_intermediate_components(original)
206 || path_contains_intermediate_components(link)
207 {
208 return None;
209 }
210
211 let (common_prefix, rest_of_original, rest_of_link) = split_after_common_prefix(original, link);
212
213 if common_prefix == Path::new("") {
214 return None;
215 }
216
217 let mut rest_of_link = rest_of_link.components();
218 rest_of_link
219 .next_back()
220 .expect("original can't be a subdirectory of link");
221
222 let mut path_to_common_prefix = PathBuf::new();
223 while rest_of_link.next_back().is_some() {
224 path_to_common_prefix.push(Component::ParentDir);
225 }
226
227 Some(path_to_common_prefix.join(rest_of_original))
228}
229
230fn path_contains_intermediate_components(path: impl AsRef<Path>) -> bool {
231 path.as_ref()
232 .components()
233 .any(|component| component == Component::ParentDir)
234}
235
236fn split_after_common_prefix<'first, 'second>(
237 first: &'first Path,
238 second: &'second Path,
239) -> (&'first Path, &'first Path, &'second Path) {
240 let entire_first = first;
241 let mut first = first.components();
242 let mut second = second.components();
243 loop {
244 let rest_of_first = first.as_path();
245 let rest_of_second = second.as_path();
246 match (first.next(), second.next()) {
247 (Some(first_component), Some(second_component))
248 if first_component == second_component => {}
249 _ => {
250 let mut common_prefix = entire_first;
251 for _ in rest_of_first.components().rev() {
252 if let Some(parent) = common_prefix.parent() {
253 common_prefix = parent;
254 } else {
255 common_prefix = Path::new("");
256 break;
257 }
258 }
259 return (common_prefix, rest_of_first, rest_of_second);
260 }
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use crate::out::abstractly_relativize_symlink;
268 use std::path::Path;
269
270 #[cfg(not(windows))]
271 #[test]
272 fn test_relativize_symlink_unix() {
273 assert_eq!(
274 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/eggs").as_deref(),
275 Some(Path::new("../bar/baz")),
276 );
277 assert_eq!(
278 abstractly_relativize_symlink("/foo/bar/../baz", "/foo/spam/eggs"),
279 None,
280 );
281 assert_eq!(
282 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/./eggs").as_deref(),
283 Some(Path::new("../bar/baz")),
284 );
285 }
286
287 #[cfg(windows)]
288 #[test]
289 fn test_relativize_symlink_windows() {
290 use std::path::PathBuf;
291
292 let windows_target = PathBuf::from_iter(["c:\\", "windows", "foo"]);
293 let windows_link = PathBuf::from_iter(["c:\\", "users", "link"]);
294 let windows_different_volume_link = PathBuf::from_iter(["d:\\", "users", "link"]);
295
296 assert_eq!(
297 abstractly_relativize_symlink(&windows_target, windows_link).as_deref(),
298 Some(Path::new("..\\windows\\foo")),
299 );
300 assert_eq!(
301 abstractly_relativize_symlink(&windows_target, windows_different_volume_link),
302 None,
303 );
304 }
305}