1use crate::bridge::fs;
2use crate::error::{Error, Result};
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 && existing == content
14 {
15 return Ok(());
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 falsecfg!(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 && let Ok(relative_canonical) = link.parent().unwrap().join(&relative_path).canonicalize()
171 && original_canonical == relative_canonical
172 {
173 return relative_path;
174 }
175
176 original.to_path_buf()
177}
178
179fn abstractly_relativize_symlink(
180 original: impl AsRef<Path>,
181 link: impl AsRef<Path>,
182) -> Option<PathBuf> {
183 let original = original.as_ref();
184 let link = link.as_ref();
185
186 let likely_no_semantic_prefix = env::var_os("CARGO_TARGET_DIR").is_some();
200
201 if likely_no_semantic_prefix
202 || original.is_relative()
203 || link.is_relative()
204 || path_contains_intermediate_components(original)
205 || path_contains_intermediate_components(link)
206 {
207 return None;
208 }
209
210 let (common_prefix, rest_of_original, rest_of_link) = split_after_common_prefix(original, link);
211
212 if common_prefix == Path::new("") {
213 return None;
214 }
215
216 let mut rest_of_link = rest_of_link.components();
217 rest_of_link
218 .next_back()
219 .expect("original can't be a subdirectory of link");
220
221 let mut path_to_common_prefix = PathBuf::new();
222 while rest_of_link.next_back().is_some() {
223 path_to_common_prefix.push(Component::ParentDir);
224 }
225
226 Some(path_to_common_prefix.join(rest_of_original))
227}
228
229fn path_contains_intermediate_components(path: impl AsRef<Path>) -> bool {
230 path.as_ref()
231 .components()
232 .any(|component| component == Component::ParentDir)
233}
234
235fn split_after_common_prefix<'first, 'second>(
236 first: &'first Path,
237 second: &'second Path,
238) -> (&'first Path, &'first Path, &'second Path) {
239 let entire_first = first;
240 let mut first = first.components();
241 let mut second = second.components();
242 loop {
243 let rest_of_first = first.as_path();
244 let rest_of_second = second.as_path();
245 match (first.next(), second.next()) {
246 (Some(first_component), Some(second_component))
247 if first_component == second_component => {}
248 _ => {
249 let mut common_prefix = entire_first;
250 for _ in rest_of_first.components().rev() {
251 if let Some(parent) = common_prefix.parent() {
252 common_prefix = parent;
253 } else {
254 common_prefix = Path::new("");
255 break;
256 }
257 }
258 return (common_prefix, rest_of_first, rest_of_second);
259 }
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use crate::out::abstractly_relativize_symlink;
267 use std::path::Path;
268
269 #[cfg(not(windows))]
270 #[test]
271 fn test_relativize_symlink_unix() {
272 assert_eq!(
273 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/eggs").as_deref(),
274 Some(Path::new("../bar/baz")),
275 );
276 assert_eq!(
277 abstractly_relativize_symlink("/foo/bar/../baz", "/foo/spam/eggs"),
278 None,
279 );
280 assert_eq!(
281 abstractly_relativize_symlink("/foo/bar/baz", "/foo/spam/./eggs").as_deref(),
282 Some(Path::new("../bar/baz")),
283 );
284 }
285
286 #[cfg(windows)]
287 #[test]
288 fn test_relativize_symlink_windows() {
289 use std::path::PathBuf;
290
291 let windows_target = PathBuf::from_iter(["c:\\", "windows", "foo"]);
292 let windows_link = PathBuf::from_iter(["c:\\", "users", "link"]);
293 let windows_different_volume_link = PathBuf::from_iter(["d:\\", "users", "link"]);
294
295 assert_eq!(
296 abstractly_relativize_symlink(&windows_target, windows_link).as_deref(),
297 Some(Path::new("..\\windows\\foo")),
298 );
299 assert_eq!(
300 abstractly_relativize_symlink(&windows_target, windows_different_volume_link),
301 None,
302 );
303 }
304}