1 use crate::error::{Error, Result};
2 use crate::gen::fs;
3 use crate::paths;
4 use std::path::Path;
5 
write(path: impl AsRef<Path>, content: &[u8]) -> Result<()>6 pub(crate) fn write(path: impl AsRef<Path>, content: &[u8]) -> Result<()> {
7     let path = path.as_ref();
8 
9     let mut create_dir_error = None;
10     if path.exists() {
11         if let Ok(existing) = fs::read(path) {
12             if existing == content {
13                 // Avoid bumping modified time with unchanged contents.
14                 return Ok(());
15             }
16         }
17         best_effort_remove(path);
18     } else {
19         let parent = path.parent().unwrap();
20         create_dir_error = fs::create_dir_all(parent).err();
21     }
22 
23     match fs::write(path, content) {
24         // As long as write succeeded, ignore any create_dir_all error.
25         Ok(()) => Ok(()),
26         // If create_dir_all and write both failed, prefer the first error.
27         Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
28     }
29 }
30 
symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()>31 pub(crate) fn symlink_file(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
32     let original = original.as_ref();
33     let link = link.as_ref();
34 
35     let mut create_dir_error = None;
36     if link.exists() {
37         best_effort_remove(link);
38     } else {
39         let parent = link.parent().unwrap();
40         create_dir_error = fs::create_dir_all(parent).err();
41     }
42 
43     match paths::symlink_or_copy(original, link) {
44         // As long as symlink_or_copy succeeded, ignore any create_dir_all error.
45         Ok(()) => Ok(()),
46         // If create_dir_all and symlink_or_copy both failed, prefer the first error.
47         Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
48     }
49 }
50 
symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()>51 pub(crate) fn symlink_dir(original: impl AsRef<Path>, link: impl AsRef<Path>) -> Result<()> {
52     let original = original.as_ref();
53     let link = link.as_ref();
54 
55     let mut create_dir_error = None;
56     if link.exists() {
57         best_effort_remove(link);
58     } else {
59         let parent = link.parent().unwrap();
60         create_dir_error = fs::create_dir_all(parent).err();
61     }
62 
63     match fs::symlink_dir(original, link) {
64         // As long as symlink_dir succeeded, ignore any create_dir_all error.
65         Ok(()) => Ok(()),
66         // If create_dir_all and symlink_dir both failed, prefer the first error.
67         Err(err) => Err(Error::Fs(create_dir_error.unwrap_or(err))),
68     }
69 }
70 
best_effort_remove(path: &Path)71 fn best_effort_remove(path: &Path) {
72     use std::fs;
73 
74     let file_type = match if cfg!(windows) {
75         // On Windows, the correct choice of remove_file vs remove_dir needs to
76         // be used according to what the symlink *points to*. Trying to use
77         // remove_file to remove a symlink which points to a directory fails
78         // with "Access is denied".
79         fs::metadata(path)
80     } else {
81         // On non-Windows, we check metadata not following symlinks. All
82         // symlinks are removed using remove_file.
83         fs::symlink_metadata(path)
84     } {
85         Ok(metadata) => metadata.file_type(),
86         Err(_) => return,
87     };
88 
89     if file_type.is_dir() {
90         let _ = fs::remove_dir_all(path);
91     } else {
92         let _ = fs::remove_file(path);
93     }
94 }
95