1use std::env;
2use std::ffi::{OsStr, OsString};
3use std::fs::{self, File, OpenOptions};
4use std::io;
5use std::io::prelude::*;
6use std::iter;
7use std::path::{Component, Path, PathBuf};
8
9use filetime::FileTime;
10
11use crate::util::errors::{CargoResult, CargoResultExt};
12
13pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> CargoResult<OsString> {
14 env::join_paths(paths.iter())
15 .chain_err(|| {
16 let paths = paths.iter().map(Path::new).collect::<Vec<_>>();
17 format!("failed to join path array: {:?}", paths)
18 })
19 .chain_err(|| {
20 format!(
21 "failed to join search paths together\n\
22 Does ${} have an unterminated quote character?",
23 env
24 )
25 })
26}
27
28pub fn dylib_path_envvar() -> &'static str {
29 if cfg!(windows) {
30 "PATH"
31 } else if cfg!(target_os = "macos") {
32 "DYLD_FALLBACK_LIBRARY_PATH"
48 } else {
49 "LD_LIBRARY_PATH"
50 }
51}
52
53pub fn dylib_path() -> Vec<PathBuf> {
54 match env::var_os(dylib_path_envvar()) {
55 Some(var) => env::split_paths(&var).collect(),
56 None => Vec::new(),
57 }
58}
59
60pub fn normalize_path(path: &Path) -> PathBuf {
61 let mut components = path.components().peekable();
62 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
63 components.next();
64 PathBuf::from(c.as_os_str())
65 } else {
66 PathBuf::new()
67 };
68
69 for component in components {
70 match component {
71 Component::Prefix(..) => unreachable!(),
72 Component::RootDir => {
73 ret.push(component.as_os_str());
74 }
75 Component::CurDir => {}
76 Component::ParentDir => {
77 ret.pop();
78 }
79 Component::Normal(c) => {
80 ret.push(c);
81 }
82 }
83 }
84 ret
85}
86
87pub fn resolve_executable(exec: &Path) -> CargoResult<PathBuf> {
88 if exec.components().count() == 1 {
89 let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?;
90 let candidates = env::split_paths(&paths).flat_map(|path| {
91 let candidate = path.join(&exec);
92 let with_exe = if env::consts::EXE_EXTENSION == "" {
93 None
94 } else {
95 Some(candidate.with_extension(env::consts::EXE_EXTENSION))
96 };
97 iter::once(candidate).chain(with_exe)
98 });
99 for candidate in candidates {
100 if candidate.is_file() {
101 return Ok(candidate.canonicalize()?);
104 }
105 }
106
107 anyhow::bail!("no executable for `{}` found in PATH", exec.display())
108 } else {
109 Ok(exec.canonicalize()?)
110 }
111}
112
113pub fn read(path: &Path) -> CargoResult<String> {
114 match String::from_utf8(read_bytes(path)?) {
115 Ok(s) => Ok(s),
116 Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()),
117 }
118}
119
120pub fn read_bytes(path: &Path) -> CargoResult<Vec<u8>> {
121 let res = (|| -> CargoResult<_> {
122 let mut ret = Vec::new();
123 let mut f = File::open(path)?;
124 if let Ok(m) = f.metadata() {
125 ret.reserve(m.len() as usize + 1);
126 }
127 f.read_to_end(&mut ret)?;
128 Ok(ret)
129 })()
130 .chain_err(|| format!("failed to read `{}`", path.display()))?;
131 Ok(res)
132}
133
134pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> {
135 (|| -> CargoResult<()> {
136 let mut f = File::create(path)?;
137 f.write_all(contents)?;
138 Ok(())
139 })()
140 .chain_err(|| format!("failed to write `{}`", path.display()))?;
141 Ok(())
142}
143
144pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> CargoResult<()> {
145 (|| -> CargoResult<()> {
146 let contents = contents.as_ref();
147 let mut f = OpenOptions::new()
148 .read(true)
149 .write(true)
150 .create(true)
151 .open(&path)?;
152 let mut orig = Vec::new();
153 f.read_to_end(&mut orig)?;
154 if orig != contents {
155 f.set_len(0)?;
156 f.seek(io::SeekFrom::Start(0))?;
157 f.write_all(contents)?;
158 }
159 Ok(())
160 })()
161 .chain_err(|| format!("failed to write `{}`", path.as_ref().display()))?;
162 Ok(())
163}
164
165pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> {
166 (|| -> CargoResult<()> {
167 let mut f = OpenOptions::new()
168 .write(true)
169 .append(true)
170 .create(true)
171 .open(path)?;
172
173 f.write_all(contents)?;
174 Ok(())
175 })()
176 .chain_err(|| format!("failed to write `{}`", path.display()))?;
177 Ok(())
178}
179
180pub fn mtime(path: &Path) -> CargoResult<FileTime> {
181 let meta = fs::metadata(path).chain_err(|| format!("failed to stat `{}`", path.display()))?;
182 Ok(FileTime::from_last_modification_time(&meta))
183}
184
185pub fn set_invocation_time(path: &Path) -> CargoResult<FileTime> {
188 let timestamp = path.join("invoked.timestamp");
191 write(
192 ×tamp,
193 b"This file has an mtime of when this was started.",
194 )?;
195 let ft = mtime(×tamp)?;
196 log::debug!("invocation time for {:?} is {}", path, ft);
197 Ok(ft)
198}
199
200#[cfg(unix)]
201pub fn path2bytes(path: &Path) -> CargoResult<&[u8]> {
202 use std::os::unix::prelude::*;
203 Ok(path.as_os_str().as_bytes())
204}
205#[cfg(windows)]
206pub fn path2bytes(path: &Path) -> CargoResult<&[u8]> {
207 match path.as_os_str().to_str() {
208 Some(s) => Ok(s.as_bytes()),
209 None => Err(anyhow::format_err!(
210 "invalid non-unicode path: {}",
211 path.display()
212 )),
213 }
214}
215
216#[cfg(unix)]
217pub fn bytes2path(bytes: &[u8]) -> CargoResult<PathBuf> {
218 use std::os::unix::prelude::*;
219 Ok(PathBuf::from(OsStr::from_bytes(bytes)))
220}
221#[cfg(windows)]
222pub fn bytes2path(bytes: &[u8]) -> CargoResult<PathBuf> {
223 use std::str;
224 match str::from_utf8(bytes) {
225 Ok(s) => Ok(PathBuf::from(s)),
226 Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
227 }
228}
229
230pub fn ancestors(path: &Path) -> PathAncestors<'_> {
231 PathAncestors::new(path)
232}
233
234pub struct PathAncestors<'a> {
235 current: Option<&'a Path>,
236 stop_at: Option<PathBuf>,
237}
238
239impl<'a> PathAncestors<'a> {
240 fn new(path: &Path) -> PathAncestors<'_> {
241 PathAncestors {
242 current: Some(path),
243 stop_at: env::var("__CARGO_TEST_ROOT").ok().map(PathBuf::from),
245 }
246 }
247}
248
249impl<'a> Iterator for PathAncestors<'a> {
250 type Item = &'a Path;
251
252 fn next(&mut self) -> Option<&'a Path> {
253 if let Some(path) = self.current {
254 self.current = path.parent();
255
256 if let Some(ref stop_at) = self.stop_at {
257 if path == stop_at {
258 self.current = None;
259 }
260 }
261
262 Some(path)
263 } else {
264 None
265 }
266 }
267}
268
269pub fn create_dir_all(p: impl AsRef<Path>) -> CargoResult<()> {
270 _create_dir_all(p.as_ref())
271}
272
273fn _create_dir_all(p: &Path) -> CargoResult<()> {
274 fs::create_dir_all(p).chain_err(|| format!("failed to create directory `{}`", p.display()))?;
275 Ok(())
276}
277
278pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> CargoResult<()> {
279 _remove_dir_all(p.as_ref())
280}
281
282fn _remove_dir_all(p: &Path) -> CargoResult<()> {
283 if p.symlink_metadata()?.file_type().is_symlink() {
284 return remove_file(p);
285 }
286 let entries = p
287 .read_dir()
288 .chain_err(|| format!("failed to read directory `{}`", p.display()))?;
289 for entry in entries {
290 let entry = entry?;
291 let path = entry.path();
292 if entry.file_type()?.is_dir() {
293 remove_dir_all(&path)?;
294 } else {
295 remove_file(&path)?;
296 }
297 }
298 remove_dir(&p)
299}
300
301pub fn remove_dir<P: AsRef<Path>>(p: P) -> CargoResult<()> {
302 _remove_dir(p.as_ref())
303}
304
305fn _remove_dir(p: &Path) -> CargoResult<()> {
306 fs::remove_dir(p).chain_err(|| format!("failed to remove directory `{}`", p.display()))?;
307 Ok(())
308}
309
310pub fn remove_file<P: AsRef<Path>>(p: P) -> CargoResult<()> {
311 _remove_file(p.as_ref())
312}
313
314fn _remove_file(p: &Path) -> CargoResult<()> {
315 let mut err = match fs::remove_file(p) {
316 Ok(()) => return Ok(()),
317 Err(e) => e,
318 };
319
320 if err.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p).unwrap_or(false) {
321 match fs::remove_file(p) {
322 Ok(()) => return Ok(()),
323 Err(e) => err = e,
324 }
325 }
326
327 Err(err).chain_err(|| format!("failed to remove file `{}`", p.display()))?;
328 Ok(())
329}
330
331fn set_not_readonly(p: &Path) -> io::Result<bool> {
332 let mut perms = p.metadata()?.permissions();
333 if !perms.readonly() {
334 return Ok(false);
335 }
336 perms.set_readonly(false);
337 fs::set_permissions(p, perms)?;
338 Ok(true)
339}
340
341pub fn link_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> CargoResult<()> {
345 let src = src.as_ref();
346 let dst = dst.as_ref();
347 _link_or_copy(src, dst)
348}
349
350fn _link_or_copy(src: &Path, dst: &Path) -> CargoResult<()> {
351 log::debug!("linking {} to {}", src.display(), dst.display());
352 if same_file::is_same_file(src, dst).unwrap_or(false) {
353 return Ok(());
354 }
355
356 if fs::symlink_metadata(dst).is_ok() {
361 remove_file(&dst)?;
362 }
363
364 let link_result = if src.is_dir() {
365 #[cfg(target_os = "redox")]
366 use std::os::redox::fs::symlink;
367 #[cfg(unix)]
368 use std::os::unix::fs::symlink;
369 #[cfg(windows)]
370 use std::os::windows::fs::symlink_dir as symlink;
375
376 let dst_dir = dst.parent().unwrap();
377 let src = if src.starts_with(dst_dir) {
378 src.strip_prefix(dst_dir).unwrap()
379 } else {
380 src
381 };
382 symlink(src, dst)
383 } else if env::var_os("__CARGO_COPY_DONT_LINK_DO_NOT_USE_THIS").is_some() {
384 fs::copy(src, dst).map(|_| ())
394 } else {
395 fs::hard_link(src, dst)
396 };
397 link_result
398 .or_else(|err| {
399 log::debug!("link failed {}. falling back to fs::copy", err);
400 fs::copy(src, dst).map(|_| ())
401 })
402 .chain_err(|| {
403 format!(
404 "failed to link or copy `{}` to `{}`",
405 src.display(),
406 dst.display()
407 )
408 })?;
409 Ok(())
410}