Skip to content

Commit 12cb14b

Browse files
committed
fix: remove symlink on Windows
Signed-off-by: hi-rustin <rustin.liu@gmail.com>
1 parent 4de0094 commit 12cb14b

File tree

1 file changed

+62
-10
lines changed

1 file changed

+62
-10
lines changed

crates/cargo-util/src/paths.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -493,24 +493,53 @@ fn _remove_dir(p: &Path) -> Result<()> {
493493
///
494494
/// If the file is readonly, this will attempt to change the permissions to
495495
/// force the file to be deleted.
496+
/// And if the file is a symlink to a directory, this will attempt to remove
497+
/// the symlink itself.
496498
pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
497499
_remove_file(p.as_ref())
498500
}
499501

500502
fn _remove_file(p: &Path) -> Result<()> {
501-
let mut err = match fs::remove_file(p) {
502-
Ok(()) => return Ok(()),
503-
Err(e) => e,
504-
};
505-
506-
if err.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p).unwrap_or(false) {
507-
match fs::remove_file(p) {
508-
Ok(()) => return Ok(()),
509-
Err(e) => err = e,
503+
#[cfg(target_os = "windows")]
504+
{
505+
let metadata = symlink_metadata(p)?;
506+
let file_type = metadata.file_type();
507+
if file_type.is_symlink() && metadata.is_dir() {
508+
return remove_symlink_dir_with_permission_check(p);
510509
}
511510
}
512511

513-
Err(err).with_context(|| format!("failed to remove file `{}`", p.display()))
512+
remove_file_with_permission_check(p)
513+
}
514+
515+
#[cfg(target_os = "windows")]
516+
fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
517+
remove_with_permission_check(fs::remove_dir, p)
518+
.context(format!("failed to remove symlink dir `{}`", p.display()))
519+
}
520+
521+
fn remove_file_with_permission_check(p: &Path) -> Result<()> {
522+
remove_with_permission_check(fs::remove_file, p)
523+
.context(format!("failed to remove file `{}`", p.display()))
524+
}
525+
526+
fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
527+
where
528+
F: Fn(P) -> io::Result<()>,
529+
P: AsRef<Path> + Clone,
530+
{
531+
match remove_func(p.clone()) {
532+
Ok(()) => Ok(()),
533+
Err(e) => {
534+
if e.kind() == io::ErrorKind::PermissionDenied
535+
&& set_not_readonly(p.as_ref()).unwrap_or(false)
536+
{
537+
remove_func(p)
538+
} else {
539+
Err(e)
540+
}
541+
}
542+
}
514543
}
515544

516545
fn set_not_readonly(p: &Path) -> io::Result<bool> {
@@ -858,4 +887,27 @@ mod tests {
858887
);
859888
}
860889
}
890+
891+
#[test]
892+
#[cfg(windows)]
893+
fn test_remove_symlink_dir() {
894+
use super::*;
895+
use std::fs;
896+
use std::os::windows::fs::symlink_dir;
897+
898+
let tmpdir = tempfile::tempdir().unwrap();
899+
let dir_path = tmpdir.path().join("testdir");
900+
let symlink_path = tmpdir.path().join("symlink");
901+
902+
fs::create_dir(&dir_path).unwrap();
903+
904+
symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
905+
906+
assert!(symlink_path.exists());
907+
908+
remove_dir(symlink_path.clone()).unwrap();
909+
910+
assert!(!symlink_path.exists());
911+
assert!(dir_path.exists());
912+
}
861913
}

0 commit comments

Comments
 (0)