1use std::env;
5use std::ffi::{OsStr, OsString};
6use std::fs::{self, File, Metadata, OpenOptions};
7use std::io;
8use std::io::prelude::*;
9use std::iter;
10use std::path::{Component, Path, PathBuf};
11
12use anyhow::{Context, Result};
13use tempfile::Builder as TempFileBuilder;
14
15pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
22 env::join_paths(paths.iter()).with_context(|| {
23 let mut message = format!(
24 "failed to join paths from `${env}` together\n\n\
25 Check if any of path segments listed below contain an \
26 unterminated quote character or path separator:"
27 );
28 for path in paths {
29 use std::fmt::Write;
30 write!(&mut message, "\n {:?}", Path::new(path)).unwrap();
31 }
32
33 message
34 })
35}
36
37pub fn dylib_path_envvar() -> &'static str {
40 if cfg!(windows) {
41 "PATH"
42 } else if cfg!(target_os = "macos") {
43 "DYLD_FALLBACK_LIBRARY_PATH"
59 } else if cfg!(target_os = "aix") {
60 "LIBPATH"
61 } else {
62 "LD_LIBRARY_PATH"
63 }
64}
65
66pub fn dylib_path() -> Vec<PathBuf> {
71 match env::var_os(dylib_path_envvar()) {
72 Some(var) => env::split_paths(&var).collect(),
73 None => Vec::new(),
74 }
75}
76
77pub fn normalize_path(path: &Path) -> PathBuf {
86 let mut components = path.components().peekable();
87 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
88 components.next();
89 PathBuf::from(c.as_os_str())
90 } else {
91 PathBuf::new()
92 };
93
94 for component in components {
95 match component {
96 Component::Prefix(..) => unreachable!(),
97 Component::RootDir => {
98 ret.push(component.as_os_str());
99 }
100 Component::CurDir => {}
101 Component::ParentDir => {
102 ret.pop();
103 }
104 Component::Normal(c) => {
105 ret.push(c);
106 }
107 }
108 }
109 ret
110}
111
112pub fn resolve_executable(exec: &Path) -> Result<PathBuf> {
117 if exec.components().count() == 1 {
118 let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?;
119 let candidates = env::split_paths(&paths).flat_map(|path| {
120 let candidate = path.join(exec);
121 let with_exe = if env::consts::EXE_EXTENSION.is_empty() {
122 None
123 } else {
124 Some(candidate.with_extension(env::consts::EXE_EXTENSION))
125 };
126 iter::once(candidate).chain(with_exe)
127 });
128 for candidate in candidates {
129 if candidate.is_file() {
130 return Ok(candidate);
131 }
132 }
133
134 anyhow::bail!("no executable for `{}` found in PATH", exec.display())
135 } else {
136 Ok(exec.into())
137 }
138}
139
140pub fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
144 let path = path.as_ref();
145 std::fs::metadata(path)
146 .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
147}
148
149pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
153 let path = path.as_ref();
154 std::fs::symlink_metadata(path)
155 .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
156}
157
158pub fn read(path: &Path) -> Result<String> {
162 match String::from_utf8(read_bytes(path)?) {
163 Ok(s) => Ok(s),
164 Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()),
165 }
166}
167
168pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
172 fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
173}
174
175pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
179 let path = path.as_ref();
180 fs::write(path, contents.as_ref())
181 .with_context(|| format!("failed to write `{}`", path.display()))
182}
183
184pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
188 let path = path.as_ref();
189
190 #[cfg(unix)]
195 let perms = path.metadata().ok().map(|meta| {
196 use std::os::unix::fs::PermissionsExt;
197
198 #[allow(clippy::useless_conversion)]
200 let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
201 let mode = meta.permissions().mode() & mask;
202
203 std::fs::Permissions::from_mode(mode)
204 });
205
206 let mut tmp = TempFileBuilder::new()
207 .prefix(path.file_name().unwrap())
208 .tempfile_in(path.parent().unwrap())?;
209 tmp.write_all(contents.as_ref())?;
210
211 #[cfg(unix)]
215 if let Some(perms) = perms {
216 tmp.as_file().set_permissions(perms)?;
217 }
218
219 tmp.persist(path)?;
220 Ok(())
221}
222
223pub fn temp_dir_in<P: AsRef<Path>, T>(path: P, f: impl FnOnce(&Path) -> Result<T>) -> Result<T> {
231 let path = path.as_ref();
232
233 std::fs::create_dir_all(path)
234 .with_context(|| format!("failed to create directory for tmpdir `{}`", path.display()))?;
235
236 let tmp = TempFileBuilder::new().tempdir_in(path)?;
237 f(tmp.path())
238}
239
240pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
243 (|| -> Result<()> {
244 let contents = contents.as_ref();
245 let mut f = OpenOptions::new()
246 .read(true)
247 .write(true)
248 .create(true)
249 .truncate(false)
252 .open(&path)?;
253 let mut orig = Vec::new();
254 f.read_to_end(&mut orig)?;
255 if orig != contents {
256 f.set_len(0)?;
257 f.seek(io::SeekFrom::Start(0))?;
258 f.write_all(contents)?;
259 }
260 Ok(())
261 })()
262 .with_context(|| format!("failed to write `{}`", path.as_ref().display()))?;
263 Ok(())
264}
265
266pub fn append(path: &Path, contents: &[u8]) -> Result<()> {
269 (|| -> Result<()> {
270 let mut f = OpenOptions::new().append(true).create(true).open(path)?;
271
272 f.write_all(contents)?;
273 Ok(())
274 })()
275 .with_context(|| format!("failed to write `{}`", path.display()))?;
276 Ok(())
277}
278
279pub fn create<P: AsRef<Path>>(path: P) -> Result<File> {
281 let path = path.as_ref();
282 File::create(path).with_context(|| format!("failed to create file `{}`", path.display()))
283}
284
285pub fn open<P: AsRef<Path>>(path: P) -> Result<File> {
287 let path = path.as_ref();
288 File::open(path).with_context(|| format!("failed to open file `{}`", path.display()))
289}
290
291pub fn path2bytes(path: &Path) -> Result<&[u8]> {
293 #[cfg(unix)]
294 {
295 use std::os::unix::prelude::*;
296 Ok(path.as_os_str().as_bytes())
297 }
298 #[cfg(windows)]
299 {
300 match path.as_os_str().to_str() {
301 Some(s) => Ok(s.as_bytes()),
302 None => Err(anyhow::format_err!(
303 "invalid non-unicode path: {}",
304 path.display()
305 )),
306 }
307 }
308 #[cfg(not(any(unix, windows)))]
309 {
310 match path.as_os_str().to_str() {
311 Some(s) => Ok(s.as_bytes()),
312 None => Err(anyhow::format_err!(
313 "invalid non-unicode path: {}",
314 path.display()
315 )),
316 }
317 }
318}
319
320pub fn bytes2path(bytes: &[u8]) -> Result<PathBuf> {
322 #[cfg(unix)]
323 {
324 use std::os::unix::prelude::*;
325 Ok(PathBuf::from(OsStr::from_bytes(bytes)))
326 }
327 #[cfg(windows)]
328 {
329 use std::str;
330 match str::from_utf8(bytes) {
331 Ok(s) => Ok(PathBuf::from(s)),
332 Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
333 }
334 }
335 #[cfg(not(any(unix, windows)))]
336 {
337 use std::str;
338 match str::from_utf8(bytes) {
339 Ok(s) => Ok(PathBuf::from(s)),
340 Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
341 }
342 }
343}
344
345pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
351 PathAncestors::new(path, stop_root_at)
352}
353
354pub struct PathAncestors<'a> {
356 current: Option<&'a Path>,
357 stop_at: Option<PathBuf>,
358}
359
360impl<'a> PathAncestors<'a> {
361 fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
362 let stop_at = env::var("__CARGO_TEST_ROOT")
363 .ok()
364 .map(PathBuf::from)
365 .or_else(|| stop_root_at.map(|p| p.to_path_buf()));
366 PathAncestors {
367 current: Some(path),
368 stop_at,
370 }
371 }
372}
373
374impl<'a> Iterator for PathAncestors<'a> {
375 type Item = &'a Path;
376
377 fn next(&mut self) -> Option<&'a Path> {
378 if let Some(path) = self.current {
379 self.current = path.parent();
380
381 if let Some(ref stop_at) = self.stop_at
382 && path == stop_at
383 {
384 self.current = None;
385 }
386
387 Some(path)
388 } else {
389 None
390 }
391 }
392}
393
394pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
396 _create_dir_all(p.as_ref())
397}
398
399fn _create_dir_all(p: &Path) -> Result<()> {
400 fs::create_dir_all(p)
401 .with_context(|| format!("failed to create directory `{}`", p.display()))?;
402 Ok(())
403}
404
405pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> Result<()> {
409 _remove_dir_all(p.as_ref()).or_else(|prev_err| {
410 fs::remove_dir_all(p.as_ref()).with_context(|| {
414 format!(
415 "{:?}\n\nError: failed to remove directory `{}`",
416 prev_err,
417 p.as_ref().display(),
418 )
419 })
420 })
421}
422
423fn _remove_dir_all(p: &Path) -> Result<()> {
424 if symlink_metadata(p)?.is_symlink() {
425 return remove_file(p);
426 }
427 let entries = p
428 .read_dir()
429 .with_context(|| format!("failed to read directory `{}`", p.display()))?;
430 for entry in entries {
431 let entry = entry?;
432 let path = entry.path();
433 if entry.file_type()?.is_dir() {
434 remove_dir_all(&path)?;
435 } else {
436 remove_file(&path)?;
437 }
438 }
439 remove_dir(p)
440}
441
442pub fn remove_dir<P: AsRef<Path>>(p: P) -> Result<()> {
444 _remove_dir(p.as_ref())
445}
446
447fn _remove_dir(p: &Path) -> Result<()> {
448 fs::remove_dir(p).with_context(|| format!("failed to remove directory `{}`", p.display()))?;
449 Ok(())
450}
451
452pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
459 _remove_file(p.as_ref())
460}
461
462fn _remove_file(p: &Path) -> Result<()> {
463 #[cfg(target_os = "windows")]
467 {
468 use std::os::windows::fs::FileTypeExt;
469 let metadata = symlink_metadata(p)?;
470 let file_type = metadata.file_type();
471 if file_type.is_symlink_dir() {
472 return remove_symlink_dir_with_permission_check(p);
473 }
474 }
475
476 remove_file_with_permission_check(p)
477}
478
479#[cfg(target_os = "windows")]
480fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
481 remove_with_permission_check(fs::remove_dir, p)
482 .with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
483}
484
485fn remove_file_with_permission_check(p: &Path) -> Result<()> {
486 remove_with_permission_check(fs::remove_file, p)
487 .with_context(|| format!("failed to remove file `{}`", p.display()))
488}
489
490fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
491where
492 F: Fn(P) -> io::Result<()>,
493 P: AsRef<Path> + Clone,
494{
495 match remove_func(p.clone()) {
496 Ok(()) => Ok(()),
497 Err(e) => {
498 if e.kind() == io::ErrorKind::PermissionDenied
499 && set_not_readonly(p.as_ref()).unwrap_or(false)
500 {
501 remove_func(p)
502 } else {
503 Err(e)
504 }
505 }
506 }
507}
508
509fn set_not_readonly(p: &Path) -> io::Result<bool> {
510 let mut perms = p.metadata()?.permissions();
511 if !perms.readonly() {
512 return Ok(false);
513 }
514
515 #[cfg(unix)]
516 {
517 use std::os::unix::fs::PermissionsExt;
518 perms.set_mode(0o640);
519 }
520 #[cfg(not(unix))]
521 #[allow(clippy::permissions_set_readonly_false)]
522 {
523 perms.set_readonly(false);
524 }
525
526 fs::set_permissions(p, perms)?;
527 Ok(true)
528}
529
530pub fn link_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
534 let src = src.as_ref();
535 let dst = dst.as_ref();
536 _link_or_copy(src, dst)
537}
538
539fn _link_or_copy(src: &Path, dst: &Path) -> Result<()> {
540 log::debug!("linking {} to {}", src.display(), dst.display());
541 if same_file::is_same_file(src, dst).unwrap_or(false) {
542 return Ok(());
543 }
544
545 if fs::symlink_metadata(dst).is_ok() {
550 remove_file(dst)?;
551 }
552
553 let link_result = if src.is_dir() {
554 #[cfg(target_os = "redox")]
555 use std::os::redox::fs::symlink;
556 #[cfg(unix)]
557 use std::os::unix::fs::symlink;
558 #[cfg(windows)]
559 use std::os::windows::fs::symlink_dir as symlink;
564
565 let dst_dir = dst.parent().unwrap();
566 let src = if src.starts_with(dst_dir) {
567 src.strip_prefix(dst_dir).unwrap()
568 } else {
569 src
570 };
571 #[cfg(any(unix, windows, target_os = "redox"))]
572 {
573 symlink(src, dst)
574 }
575 #[cfg(not(any(unix, windows, target_os = "redox")))]
576 {
577 let _ = src;
578 Err(io::Error::new(
579 io::ErrorKind::Unsupported,
580 "symlinks are not supported on this target",
581 ))
582 }
583 } else if cfg!(target_os = "macos") {
584 fs::copy(src, dst).map_or_else(
595 |e| {
596 if e.raw_os_error()
597 .map_or(false, |os_err| os_err == 35 )
598 {
599 log::info!("copy failed {e:?}. falling back to fs::hard_link");
600
601 fs::hard_link(src, dst)
605 } else {
606 Err(e)
607 }
608 },
609 |_| Ok(()),
610 )
611 } else {
612 fs::hard_link(src, dst)
613 };
614 link_result
615 .or_else(|err| {
616 log::debug!("link failed {err}. falling back to fs::copy");
617 fs::copy(src, dst).map(|_| ())
618 })
619 .with_context(|| {
620 format!(
621 "failed to link or copy `{}` to `{}`",
622 src.display(),
623 dst.display()
624 )
625 })?;
626 Ok(())
627}
628
629pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
633 let from = from.as_ref();
634 let to = to.as_ref();
635 fs::copy(from, to)
636 .with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))
637}
638
639pub fn copy_dir_all<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
643 let from = from.as_ref();
644 let to = to.as_ref();
645 _copy_dir_all(from, to)
646}
647
648fn _copy_dir_all(from: &Path, to: &Path) -> Result<()> {
649 create_dir_all(to)?;
650 let entries = from
651 .read_dir()
652 .with_context(|| format!("failed to read directory `{}`", from.display()))?;
653
654 for entry in entries {
655 let entry = entry
656 .with_context(|| format!("failed to read directory entry in `{}`", from.display()))?;
657 let source = entry.path();
658 let target = to.join(entry.file_name());
659 let file_type = entry
660 .file_type()
661 .with_context(|| format!("failed to read file type for `{}`", source.display()))?;
662
663 if file_type.is_dir() {
664 copy_dir_all(&source, &target)?;
665 } else if file_type.is_file() {
666 copy(&source, &target)?;
667 } else if file_type.is_symlink() {
668 let metadata = metadata(&source)?;
669 if metadata.is_dir() {
670 copy_dir_all(&source, &target)?;
671 } else {
672 copy(&source, &target)?;
673 }
674 }
675 }
676
677 Ok(())
678}
679
680pub fn strip_prefix_canonical<P: AsRef<Path>>(
686 path: P,
687 base: P,
688) -> Result<PathBuf, std::path::StripPrefixError> {
689 let safe_canonicalize = |path: &Path| match path.canonicalize() {
691 Ok(p) => p,
692 Err(e) => {
693 log::warn!("cannot canonicalize {path:?}: {e:?}");
694 path.to_path_buf()
695 }
696 };
697 let canon_path = safe_canonicalize(path.as_ref());
698 let canon_base = safe_canonicalize(base.as_ref());
699 canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
700}
701
702pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> Result<()> {
712 let path = p.as_ref();
713 if path.is_dir() {
714 return Ok(());
715 }
716
717 let parent = path.parent().unwrap();
718 let base = path.file_name().unwrap();
719 create_dir_all(parent)?;
720 let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
732 exclude_from_backups(tempdir.path());
733 exclude_from_content_indexing(tempdir.path());
734 if let Err(e) = fs::rename(tempdir.path(), path)
742 && !path.exists()
743 {
744 return Err(anyhow::Error::from(e))
745 .with_context(|| format!("failed to create directory `{}`", path.display()));
746 }
747 Ok(())
748}
749
750pub fn exclude_from_backups_and_indexing(p: impl AsRef<Path>) {
754 let path = p.as_ref();
755 exclude_from_backups(path);
756 exclude_from_content_indexing(path);
757}
758
759fn exclude_from_backups(path: &Path) {
768 exclude_from_time_machine(path);
769 let file = path.join("CACHEDIR.TAG");
770 if !file.exists() {
771 let _ = std::fs::write(
772 file,
773 "Signature: 8a477f597d28d172789f06886806bc55
774# This file is a cache directory tag created by cargo.
775# For information about cache directory tags see https://bford.info/cachedir/
776",
777 );
778 }
781}
782
783fn exclude_from_content_indexing(path: &Path) {
791 #[cfg(windows)]
792 {
793 use std::iter::once;
794 use std::os::windows::prelude::OsStrExt;
795 use windows_sys::Win32::Storage::FileSystem::{
796 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, GetFileAttributesW, SetFileAttributesW,
797 };
798
799 let path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
800 unsafe {
802 SetFileAttributesW(
803 path.as_ptr(),
804 GetFileAttributesW(path.as_ptr()) | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
805 );
806 }
807 }
808 #[cfg(not(windows))]
809 {
810 let _ = path;
811 }
812}
813
814#[cfg(not(target_os = "macos"))]
815fn exclude_from_time_machine(_: &Path) {}
816
817#[cfg(target_os = "macos")]
818fn exclude_from_time_machine(path: &Path) {
820 use core_foundation::base::TCFType;
821 use core_foundation::{number, string, url};
822 use std::ptr;
823
824 let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
827 let path = url::CFURL::from_path(path, false);
828 if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
829 unsafe {
830 url::CFURLSetResourcePropertyForKey(
831 path.as_concrete_TypeRef(),
832 is_excluded_key.as_concrete_TypeRef(),
833 number::kCFBooleanTrue as *const _,
834 ptr::null_mut(),
835 );
836 }
837 }
838 }
841
842#[cfg(test)]
843mod tests {
844 use super::join_paths;
845 use super::write;
846 use super::write_atomic;
847
848 #[test]
849 fn write_works() {
850 let original_contents = "[dependencies]\nfoo = 0.1.0";
851
852 let tmpdir = tempfile::tempdir().unwrap();
853 let path = tmpdir.path().join("Cargo.toml");
854 write(&path, original_contents).unwrap();
855 let contents = std::fs::read_to_string(&path).unwrap();
856 assert_eq!(contents, original_contents);
857 }
858 #[test]
859 fn write_atomic_works() {
860 let original_contents = "[dependencies]\nfoo = 0.1.0";
861
862 let tmpdir = tempfile::tempdir().unwrap();
863 let path = tmpdir.path().join("Cargo.toml");
864 write_atomic(&path, original_contents).unwrap();
865 let contents = std::fs::read_to_string(&path).unwrap();
866 assert_eq!(contents, original_contents);
867 }
868
869 #[test]
870 #[cfg(unix)]
871 fn write_atomic_permissions() {
872 use std::os::unix::fs::PermissionsExt;
873
874 #[allow(clippy::useless_conversion)]
875 let perms = u32::from(libc::S_IRWXU | libc::S_IRGRP | libc::S_IWGRP | libc::S_IROTH);
876 let original_perms = std::fs::Permissions::from_mode(perms);
877
878 let tmp = tempfile::Builder::new().tempfile().unwrap();
879
880 tmp.as_file()
882 .set_permissions(original_perms.clone())
883 .unwrap();
884
885 write_atomic(tmp.path(), "new").unwrap();
888 assert_eq!(std::fs::read_to_string(tmp.path()).unwrap(), "new");
889
890 let new_perms = std::fs::metadata(tmp.path()).unwrap().permissions();
891
892 #[allow(clippy::useless_conversion)]
893 let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
894 assert_eq!(original_perms.mode(), new_perms.mode() & mask);
895 }
896
897 #[test]
898 fn join_paths_lists_paths_on_error() {
899 let valid_paths = vec!["/testing/one", "/testing/two"];
900 let _joined = join_paths(&valid_paths, "TESTING1").unwrap();
902
903 #[cfg(unix)]
904 {
905 let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"];
906 let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
907 assert_eq!(
908 err.to_string(),
909 "failed to join paths from `$TESTING2` together\n\n\
910 Check if any of path segments listed below contain an \
911 unterminated quote character or path separator:\
912 \n \"/testing/one\"\
913 \n \"/testing/t:wo/three\"\
914 "
915 );
916 }
917 #[cfg(windows)]
918 {
919 let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"];
920 let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
921 assert_eq!(
922 err.to_string(),
923 "failed to join paths from `$TESTING2` together\n\n\
924 Check if any of path segments listed below contain an \
925 unterminated quote character or path separator:\
926 \n \"/testing/one\"\
927 \n \"/testing/t\\\"wo/three\"\
928 "
929 );
930 }
931 }
932
933 #[test]
934 #[cfg(windows)]
935 fn test_remove_symlink_dir() {
936 use super::*;
937 use std::fs;
938 use std::os::windows::fs::symlink_dir;
939
940 let tmpdir = tempfile::tempdir().unwrap();
941 let dir_path = tmpdir.path().join("testdir");
942 let symlink_path = tmpdir.path().join("symlink");
943
944 fs::create_dir(&dir_path).unwrap();
945
946 symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
947
948 assert!(symlink_path.exists());
949
950 assert!(remove_file(symlink_path.clone()).is_ok());
951
952 assert!(!symlink_path.exists());
953 assert!(dir_path.exists());
954 }
955
956 #[test]
957 #[cfg(windows)]
958 fn test_remove_symlink_file() {
959 use super::*;
960 use std::fs;
961 use std::os::windows::fs::symlink_file;
962
963 let tmpdir = tempfile::tempdir().unwrap();
964 let file_path = tmpdir.path().join("testfile");
965 let symlink_path = tmpdir.path().join("symlink");
966
967 fs::write(&file_path, b"test").unwrap();
968
969 symlink_file(&file_path, &symlink_path).expect("failed to create symlink");
970
971 assert!(symlink_path.exists());
972
973 assert!(remove_file(symlink_path.clone()).is_ok());
974
975 assert!(!symlink_path.exists());
976 assert!(file_path.exists());
977 }
978}