1use anyhow::{Context, Result};
4use filetime::FileTime;
5use std::env;
6use std::ffi::{OsStr, OsString};
7use std::fs::{self, File, Metadata, OpenOptions};
8use std::io;
9use std::io::prelude::*;
10use std::iter;
11use std::path::{Component, Path, PathBuf};
12use tempfile::Builder as TempFileBuilder;
13
14pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
21 env::join_paths(paths.iter()).with_context(|| {
22 let mut message = format!(
23 "failed to join paths from `${env}` together\n\n\
24 Check if any of path segments listed below contain an \
25 unterminated quote character or path separator:"
26 );
27 for path in paths {
28 use std::fmt::Write;
29 write!(&mut message, "\n {:?}", Path::new(path)).unwrap();
30 }
31
32 message
33 })
34}
35
36pub fn dylib_path_envvar() -> &'static str {
39 if cfg!(windows) {
40 "PATH"
41 } else if cfg!(target_os = "macos") {
42 "DYLD_FALLBACK_LIBRARY_PATH"
58 } else if cfg!(target_os = "aix") {
59 "LIBPATH"
60 } else {
61 "LD_LIBRARY_PATH"
62 }
63}
64
65pub fn dylib_path() -> Vec<PathBuf> {
70 match env::var_os(dylib_path_envvar()) {
71 Some(var) => env::split_paths(&var).collect(),
72 None => Vec::new(),
73 }
74}
75
76pub fn normalize_path(path: &Path) -> PathBuf {
85 let mut components = path.components().peekable();
86 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
87 components.next();
88 PathBuf::from(c.as_os_str())
89 } else {
90 PathBuf::new()
91 };
92
93 for component in components {
94 match component {
95 Component::Prefix(..) => unreachable!(),
96 Component::RootDir => {
97 ret.push(Component::RootDir);
98 }
99 Component::CurDir => {}
100 Component::ParentDir => {
101 if ret.ends_with(Component::ParentDir) {
102 ret.push(Component::ParentDir);
103 } else {
104 let popped = ret.pop();
105 if !popped && !ret.has_root() {
106 ret.push(Component::ParentDir);
107 }
108 }
109 }
110 Component::Normal(c) => {
111 ret.push(c);
112 }
113 }
114 }
115 ret
116}
117
118pub fn resolve_executable(exec: &Path) -> Result<PathBuf> {
123 if exec.components().count() == 1 {
124 let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?;
125 let candidates = env::split_paths(&paths).flat_map(|path| {
126 let candidate = path.join(&exec);
127 let with_exe = if env::consts::EXE_EXTENSION.is_empty() {
128 None
129 } else {
130 Some(candidate.with_extension(env::consts::EXE_EXTENSION))
131 };
132 iter::once(candidate).chain(with_exe)
133 });
134 for candidate in candidates {
135 if candidate.is_file() {
136 return Ok(candidate);
137 }
138 }
139
140 anyhow::bail!("no executable for `{}` found in PATH", exec.display())
141 } else {
142 Ok(exec.into())
143 }
144}
145
146pub fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
150 let path = path.as_ref();
151 std::fs::metadata(path)
152 .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
153}
154
155pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
159 let path = path.as_ref();
160 std::fs::symlink_metadata(path)
161 .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
162}
163
164pub fn read(path: &Path) -> Result<String> {
168 match String::from_utf8(read_bytes(path)?) {
169 Ok(s) => Ok(s),
170 Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()),
171 }
172}
173
174pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
178 fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
179}
180
181pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
185 let path = path.as_ref();
186 fs::write(path, contents.as_ref())
187 .with_context(|| format!("failed to write `{}`", path.display()))
188}
189
190pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
194 let path = path.as_ref();
195
196 #[cfg(unix)]
200 let perms = path.metadata().ok().map(|meta| {
201 use std::os::unix::fs::PermissionsExt;
202
203 let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
205 let mode = meta.permissions().mode() & mask;
206
207 std::fs::Permissions::from_mode(mode)
208 });
209
210 let mut tmp = TempFileBuilder::new()
211 .prefix(path.file_name().unwrap())
212 .tempfile_in(path.parent().unwrap())?;
213 tmp.write_all(contents.as_ref())?;
214
215 #[cfg(unix)]
219 if let Some(perms) = perms {
220 tmp.as_file().set_permissions(perms)?;
221 }
222
223 tmp.persist(path)?;
224 Ok(())
225}
226
227pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
230 (|| -> Result<()> {
231 let contents = contents.as_ref();
232 let mut f = OpenOptions::new()
233 .read(true)
234 .write(true)
235 .create(true)
236 .open(&path)?;
237 let mut orig = Vec::new();
238 f.read_to_end(&mut orig)?;
239 if orig != contents {
240 f.set_len(0)?;
241 f.seek(io::SeekFrom::Start(0))?;
242 f.write_all(contents)?;
243 }
244 Ok(())
245 })()
246 .with_context(|| format!("failed to write `{}`", path.as_ref().display()))?;
247 Ok(())
248}
249
250pub fn append(path: &Path, contents: &[u8]) -> Result<()> {
253 (|| -> Result<()> {
254 let mut f = OpenOptions::new()
255 .write(true)
256 .append(true)
257 .create(true)
258 .open(path)?;
259
260 f.write_all(contents)?;
261 Ok(())
262 })()
263 .with_context(|| format!("failed to write `{}`", path.display()))?;
264 Ok(())
265}
266
267pub fn create<P: AsRef<Path>>(path: P) -> Result<File> {
269 let path = path.as_ref();
270 File::create(path).with_context(|| format!("failed to create file `{}`", path.display()))
271}
272
273pub fn open<P: AsRef<Path>>(path: P) -> Result<File> {
275 let path = path.as_ref();
276 File::open(path).with_context(|| format!("failed to open file `{}`", path.display()))
277}
278
279pub fn mtime(path: &Path) -> Result<FileTime> {
281 let meta = metadata(path)?;
282 Ok(FileTime::from_last_modification_time(&meta))
283}
284
285pub fn mtime_recursive(path: &Path) -> Result<FileTime> {
288 let meta = metadata(path)?;
289 if !meta.is_dir() {
290 return Ok(FileTime::from_last_modification_time(&meta));
291 }
292 let max_meta = walkdir::WalkDir::new(path)
293 .follow_links(true)
294 .into_iter()
295 .filter_map(|e| match e {
296 Ok(e) => Some(e),
297 Err(e) => {
298 tracing::debug!("failed to determine mtime while walking directory: {}", e);
301 None
302 }
303 })
304 .filter_map(|e| {
305 if e.path_is_symlink() {
306 let sym_meta = match std::fs::symlink_metadata(e.path()) {
310 Ok(m) => m,
311 Err(err) => {
312 tracing::debug!(
316 "failed to determine mtime while fetching symlink metadata of {}: {}",
317 e.path().display(),
318 err
319 );
320 return None;
321 }
322 };
323 let sym_mtime = FileTime::from_last_modification_time(&sym_meta);
324 match e.metadata() {
326 Ok(target_meta) => {
327 let target_mtime = FileTime::from_last_modification_time(&target_meta);
328 Some(sym_mtime.max(target_mtime))
329 }
330 Err(err) => {
331 tracing::debug!(
335 "failed to determine mtime of symlink target for {}: {}",
336 e.path().display(),
337 err
338 );
339 Some(sym_mtime)
340 }
341 }
342 } else {
343 let meta = match e.metadata() {
344 Ok(m) => m,
345 Err(err) => {
346 tracing::debug!(
350 "failed to determine mtime while fetching metadata of {}: {}",
351 e.path().display(),
352 err
353 );
354 return None;
355 }
356 };
357 Some(FileTime::from_last_modification_time(&meta))
358 }
359 })
360 .max()
361 .unwrap_or_else(|| FileTime::from_last_modification_time(&meta));
363 Ok(max_meta)
364}
365
366pub fn set_invocation_time(path: &Path) -> Result<FileTime> {
369 let timestamp = path.join("invoked.timestamp");
372 write(
373 ×tamp,
374 "This file has an mtime of when this was started.",
375 )?;
376 let ft = mtime(×tamp)?;
377 tracing::debug!("invocation time for {:?} is {}", path, ft);
378 Ok(ft)
379}
380
381pub fn path2bytes(path: &Path) -> Result<&[u8]> {
383 #[cfg(unix)]
384 {
385 use std::os::unix::prelude::*;
386 Ok(path.as_os_str().as_bytes())
387 }
388 #[cfg(windows)]
389 {
390 match path.as_os_str().to_str() {
391 Some(s) => Ok(s.as_bytes()),
392 None => Err(anyhow::format_err!(
393 "invalid non-unicode path: {}",
394 path.display()
395 )),
396 }
397 }
398}
399
400pub fn bytes2path(bytes: &[u8]) -> Result<PathBuf> {
402 #[cfg(unix)]
403 {
404 use std::os::unix::prelude::*;
405 Ok(PathBuf::from(OsStr::from_bytes(bytes)))
406 }
407 #[cfg(windows)]
408 {
409 use std::str;
410 match str::from_utf8(bytes) {
411 Ok(s) => Ok(PathBuf::from(s)),
412 Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
413 }
414 }
415}
416
417pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
423 PathAncestors::new(path, stop_root_at)
424}
425
426pub struct PathAncestors<'a> {
427 current: Option<&'a Path>,
428 stop_at: Option<PathBuf>,
429}
430
431impl<'a> PathAncestors<'a> {
432 fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
433 let stop_at = env::var("__CARGO_TEST_ROOT")
434 .ok()
435 .map(PathBuf::from)
436 .or_else(|| stop_root_at.map(|p| p.to_path_buf()));
437 PathAncestors {
438 current: Some(path),
439 stop_at,
441 }
442 }
443}
444
445impl<'a> Iterator for PathAncestors<'a> {
446 type Item = &'a Path;
447
448 fn next(&mut self) -> Option<&'a Path> {
449 if let Some(path) = self.current {
450 self.current = path.parent();
451
452 if let Some(ref stop_at) = self.stop_at {
453 if path == stop_at {
454 self.current = None;
455 }
456 }
457
458 Some(path)
459 } else {
460 None
461 }
462 }
463}
464
465pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
467 _create_dir_all(p.as_ref())
468}
469
470fn _create_dir_all(p: &Path) -> Result<()> {
471 fs::create_dir_all(p)
472 .with_context(|| format!("failed to create directory `{}`", p.display()))?;
473 Ok(())
474}
475
476pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> Result<()> {
480 _remove_dir_all(p.as_ref()).or_else(|prev_err| {
481 fs::remove_dir_all(p.as_ref()).with_context(|| {
485 format!(
486 "{:?}\n\nError: failed to remove directory `{}`",
487 prev_err,
488 p.as_ref().display(),
489 )
490 })
491 })
492}
493
494fn _remove_dir_all(p: &Path) -> Result<()> {
495 if symlink_metadata(p)?.is_symlink() {
496 return remove_file(p);
497 }
498 let entries = p
499 .read_dir()
500 .with_context(|| format!("failed to read directory `{}`", p.display()))?;
501 for entry in entries {
502 let entry = entry?;
503 let path = entry.path();
504 if entry.file_type()?.is_dir() {
505 remove_dir_all(&path)?;
506 } else {
507 remove_file(&path)?;
508 }
509 }
510 remove_dir(&p)
511}
512
513pub fn remove_dir<P: AsRef<Path>>(p: P) -> Result<()> {
515 _remove_dir(p.as_ref())
516}
517
518fn _remove_dir(p: &Path) -> Result<()> {
519 fs::remove_dir(p).with_context(|| format!("failed to remove directory `{}`", p.display()))?;
520 Ok(())
521}
522
523pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
530 _remove_file(p.as_ref())
531}
532
533fn _remove_file(p: &Path) -> Result<()> {
534 #[cfg(target_os = "windows")]
538 {
539 use std::os::windows::fs::FileTypeExt;
540 let metadata = symlink_metadata(p)?;
541 let file_type = metadata.file_type();
542 if file_type.is_symlink_dir() {
543 return remove_symlink_dir_with_permission_check(p);
544 }
545 }
546
547 remove_file_with_permission_check(p)
548}
549
550#[cfg(target_os = "windows")]
551fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
552 remove_with_permission_check(fs::remove_dir, p)
553 .with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
554}
555
556fn remove_file_with_permission_check(p: &Path) -> Result<()> {
557 remove_with_permission_check(fs::remove_file, p)
558 .with_context(|| format!("failed to remove file `{}`", p.display()))
559}
560
561fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
562where
563 F: Fn(P) -> io::Result<()>,
564 P: AsRef<Path> + Clone,
565{
566 match remove_func(p.clone()) {
567 Ok(()) => Ok(()),
568 Err(e) => {
569 if e.kind() == io::ErrorKind::PermissionDenied
570 && set_not_readonly(p.as_ref()).unwrap_or(false)
571 {
572 remove_func(p)
573 } else {
574 Err(e)
575 }
576 }
577 }
578}
579
580fn set_not_readonly(p: &Path) -> io::Result<bool> {
581 let mut perms = p.metadata()?.permissions();
582 if !perms.readonly() {
583 return Ok(false);
584 }
585 perms.set_readonly(false);
586 fs::set_permissions(p, perms)?;
587 Ok(true)
588}
589
590pub fn link_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
594 let src = src.as_ref();
595 let dst = dst.as_ref();
596 _link_or_copy(src, dst)
597}
598
599fn _link_or_copy(src: &Path, dst: &Path) -> Result<()> {
600 tracing::debug!("linking {} to {}", src.display(), dst.display());
601 if same_file::is_same_file(src, dst).unwrap_or(false) {
602 return Ok(());
603 }
604
605 if fs::symlink_metadata(dst).is_ok() {
610 remove_file(&dst)?;
611 }
612
613 let link_result = if src.is_dir() {
614 #[cfg(target_os = "redox")]
615 use std::os::redox::fs::symlink;
616 #[cfg(unix)]
617 use std::os::unix::fs::symlink;
618 #[cfg(windows)]
619 use std::os::windows::fs::symlink_dir as symlink;
624
625 let dst_dir = dst.parent().unwrap();
626 let src = if src.starts_with(dst_dir) {
627 src.strip_prefix(dst_dir).unwrap()
628 } else {
629 src
630 };
631 symlink(src, dst)
632 } else {
633 if cfg!(target_os = "macos") {
634 fs::copy(src, dst).map_or_else(
645 |e| {
646 if e.raw_os_error()
647 .map_or(false, |os_err| os_err == 35 )
648 {
649 tracing::info!("copy failed {e:?}. falling back to fs::hard_link");
650
651 fs::hard_link(src, dst)
655 } else {
656 Err(e)
657 }
658 },
659 |_| Ok(()),
660 )
661 } else {
662 fs::hard_link(src, dst)
663 }
664 };
665 link_result
666 .or_else(|err| {
667 tracing::debug!("link failed {}. falling back to fs::copy", err);
668 fs::copy(src, dst).map(|_| ())
669 })
670 .with_context(|| {
671 format!(
672 "failed to link or copy `{}` to `{}`",
673 src.display(),
674 dst.display()
675 )
676 })?;
677 Ok(())
678}
679
680pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
684 let from = from.as_ref();
685 let to = to.as_ref();
686 fs::copy(from, to)
687 .with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))
688}
689
690pub fn set_file_time_no_err<P: AsRef<Path>>(path: P, time: FileTime) {
696 let path = path.as_ref();
697 match filetime::set_file_times(path, time, time) {
698 Ok(()) => tracing::debug!("set file mtime {} to {}", path.display(), time),
699 Err(e) => tracing::warn!(
700 "could not set mtime of {} to {}: {:?}",
701 path.display(),
702 time,
703 e
704 ),
705 }
706}
707
708pub fn strip_prefix_canonical<P: AsRef<Path>>(
714 path: P,
715 base: P,
716) -> Result<PathBuf, std::path::StripPrefixError> {
717 let safe_canonicalize = |path: &Path| match path.canonicalize() {
719 Ok(p) => p,
720 Err(e) => {
721 tracing::warn!("cannot canonicalize {:?}: {:?}", path, e);
722 path.to_path_buf()
723 }
724 };
725 let canon_path = safe_canonicalize(path.as_ref());
726 let canon_base = safe_canonicalize(base.as_ref());
727 canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
728}
729
730pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> Result<()> {
738 let path = p.as_ref();
739 if path.is_dir() {
740 return Ok(());
741 }
742
743 let parent = path.parent().unwrap();
744 let base = path.file_name().unwrap();
745 create_dir_all(parent)?;
746 let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
758 exclude_from_backups(tempdir.path());
759 exclude_from_content_indexing(tempdir.path());
760 if let Err(e) = fs::rename(tempdir.path(), path) {
767 if !path.exists() {
768 return Err(anyhow::Error::from(e))
769 .with_context(|| format!("failed to create directory `{}`", path.display()));
770 }
771 }
772 Ok(())
773}
774
775pub fn exclude_from_backups_and_indexing(p: impl AsRef<Path>) {
779 let path = p.as_ref();
780 exclude_from_backups(path);
781 exclude_from_content_indexing(path);
782}
783
784fn exclude_from_backups(path: &Path) {
792 exclude_from_time_machine(path);
793 let file = path.join("CACHEDIR.TAG");
794 if !file.exists() {
795 let _ = std::fs::write(
796 file,
797 "Signature: 8a477f597d28d172789f06886806bc55
798# This file is a cache directory tag created by cargo.
799# For information about cache directory tags see https://bford.info/cachedir/
800",
801 );
802 }
804}
805
806fn exclude_from_content_indexing(path: &Path) {
814 #[cfg(windows)]
815 {
816 use std::iter::once;
817 use std::os::windows::prelude::OsStrExt;
818 use windows_sys::Win32::Storage::FileSystem::{
819 GetFileAttributesW, SetFileAttributesW, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
820 };
821
822 let path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
823 unsafe {
824 SetFileAttributesW(
825 path.as_ptr(),
826 GetFileAttributesW(path.as_ptr()) | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
827 );
828 }
829 }
830 #[cfg(not(windows))]
831 {
832 let _ = path;
833 }
834}
835
836#[cfg(not(target_os = "macos"))]
837fn exclude_from_time_machine(_: &Path) {}
838
839#[cfg(target_os = "macos")]
840fn exclude_from_time_machine(path: &Path) {
842 use core_foundation::base::TCFType;
843 use core_foundation::{number, string, url};
844 use std::ptr;
845
846 let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
848 let path = url::CFURL::from_path(path, false);
849 if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
850 unsafe {
851 url::CFURLSetResourcePropertyForKey(
852 path.as_concrete_TypeRef(),
853 is_excluded_key.as_concrete_TypeRef(),
854 number::kCFBooleanTrue as *const _,
855 ptr::null_mut(),
856 );
857 }
858 }
859 }
862
863#[cfg(test)]
864mod tests {
865 use super::join_paths;
866 use super::normalize_path;
867 use super::write;
868 use super::write_atomic;
869
870 #[test]
871 fn test_normalize_path() {
872 let cases = &[
873 ("", ""),
874 (".", ""),
875 (".////./.", ""),
876 ("/", "/"),
877 ("/..", "/"),
878 ("/foo/bar", "/foo/bar"),
879 ("/foo/bar/", "/foo/bar"),
880 ("/foo/bar/./././///", "/foo/bar"),
881 ("/foo/bar/..", "/foo"),
882 ("/foo/bar/../..", "/"),
883 ("/foo/bar/../../..", "/"),
884 ("foo/bar", "foo/bar"),
885 ("foo/bar/", "foo/bar"),
886 ("foo/bar/./././///", "foo/bar"),
887 ("foo/bar/..", "foo"),
888 ("foo/bar/../..", ""),
889 ("foo/bar/../../..", ".."),
890 ("../../foo/bar", "../../foo/bar"),
891 ("../../foo/bar/", "../../foo/bar"),
892 ("../../foo/bar/./././///", "../../foo/bar"),
893 ("../../foo/bar/..", "../../foo"),
894 ("../../foo/bar/../..", "../.."),
895 ("../../foo/bar/../../..", "../../.."),
896 ];
897 for (input, expected) in cases {
898 let actual = normalize_path(std::path::Path::new(input));
899 assert_eq!(actual, std::path::Path::new(expected), "input: {input}");
900 }
901 }
902
903 #[test]
904 fn write_works() {
905 let original_contents = "[dependencies]\nfoo = 0.1.0";
906
907 let tmpdir = tempfile::tempdir().unwrap();
908 let path = tmpdir.path().join("Cargo.toml");
909 write(&path, original_contents).unwrap();
910 let contents = std::fs::read_to_string(&path).unwrap();
911 assert_eq!(contents, original_contents);
912 }
913 #[test]
914 fn write_atomic_works() {
915 let original_contents = "[dependencies]\nfoo = 0.1.0";
916
917 let tmpdir = tempfile::tempdir().unwrap();
918 let path = tmpdir.path().join("Cargo.toml");
919 write_atomic(&path, original_contents).unwrap();
920 let contents = std::fs::read_to_string(&path).unwrap();
921 assert_eq!(contents, original_contents);
922 }
923
924 #[test]
925 #[cfg(unix)]
926 fn write_atomic_permissions() {
927 use std::os::unix::fs::PermissionsExt;
928
929 let original_perms = std::fs::Permissions::from_mode(u32::from(
930 libc::S_IRWXU | libc::S_IRGRP | libc::S_IWGRP | libc::S_IROTH,
931 ));
932
933 let tmp = tempfile::Builder::new().tempfile().unwrap();
934
935 tmp.as_file()
937 .set_permissions(original_perms.clone())
938 .unwrap();
939
940 write_atomic(tmp.path(), "new").unwrap();
942 assert_eq!(std::fs::read_to_string(tmp.path()).unwrap(), "new");
943
944 let new_perms = std::fs::metadata(tmp.path()).unwrap().permissions();
945
946 let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
947 assert_eq!(original_perms.mode(), new_perms.mode() & mask);
948 }
949
950 #[test]
951 fn join_paths_lists_paths_on_error() {
952 let valid_paths = vec!["/testing/one", "/testing/two"];
953 let _joined = join_paths(&valid_paths, "TESTING1").unwrap();
955
956 #[cfg(unix)]
957 {
958 let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"];
959 let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
960 assert_eq!(
961 err.to_string(),
962 "failed to join paths from `$TESTING2` together\n\n\
963 Check if any of path segments listed below contain an \
964 unterminated quote character or path separator:\
965 \n \"/testing/one\"\
966 \n \"/testing/t:wo/three\"\
967 "
968 );
969 }
970 #[cfg(windows)]
971 {
972 let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"];
973 let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
974 assert_eq!(
975 err.to_string(),
976 "failed to join paths from `$TESTING2` together\n\n\
977 Check if any of path segments listed below contain an \
978 unterminated quote character or path separator:\
979 \n \"/testing/one\"\
980 \n \"/testing/t\\\"wo/three\"\
981 "
982 );
983 }
984 }
985
986 #[test]
987 #[cfg(windows)]
988 fn test_remove_symlink_dir() {
989 use super::*;
990 use std::fs;
991 use std::os::windows::fs::symlink_dir;
992
993 let tmpdir = tempfile::tempdir().unwrap();
994 let dir_path = tmpdir.path().join("testdir");
995 let symlink_path = tmpdir.path().join("symlink");
996
997 fs::create_dir(&dir_path).unwrap();
998
999 symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
1000
1001 assert!(symlink_path.exists());
1002
1003 assert!(remove_file(symlink_path.clone()).is_ok());
1004
1005 assert!(!symlink_path.exists());
1006 assert!(dir_path.exists());
1007 }
1008
1009 #[test]
1010 #[cfg(windows)]
1011 fn test_remove_symlink_file() {
1012 use super::*;
1013 use std::fs;
1014 use std::os::windows::fs::symlink_file;
1015
1016 let tmpdir = tempfile::tempdir().unwrap();
1017 let file_path = tmpdir.path().join("testfile");
1018 let symlink_path = tmpdir.path().join("symlink");
1019
1020 fs::write(&file_path, b"test").unwrap();
1021
1022 symlink_file(&file_path, &symlink_path).expect("failed to create symlink");
1023
1024 assert!(symlink_path.exists());
1025
1026 assert!(remove_file(symlink_path.clone()).is_ok());
1027
1028 assert!(!symlink_path.exists());
1029 assert!(file_path.exists());
1030 }
1031}