Skip to content

Commit 9f7a715

Browse files
committed
Auto merge of #13910 - hi-rustin:rustin-patch-symlink, r=weihanglo
fix: remove symlink dir on Windows
2 parents 3830c0c + 40ff7be commit 9f7a715

File tree

1 file changed

+89
-10
lines changed

1 file changed

+89
-10
lines changed

crates/cargo-util/src/paths.rs

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -517,24 +517,57 @@ fn _remove_dir(p: &Path) -> Result<()> {
517517
///
518518
/// If the file is readonly, this will attempt to change the permissions to
519519
/// force the file to be deleted.
520+
/// On Windows, if the file is a symlink to a directory, this will attempt to remove
521+
/// the symlink itself.
520522
pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
521523
_remove_file(p.as_ref())
522524
}
523525

524526
fn _remove_file(p: &Path) -> Result<()> {
525-
let mut err = match fs::remove_file(p) {
526-
Ok(()) => return Ok(()),
527-
Err(e) => e,
528-
};
529-
530-
if err.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p).unwrap_or(false) {
531-
match fs::remove_file(p) {
532-
Ok(()) => return Ok(()),
533-
Err(e) => err = e,
527+
// For Windows, we need to check if the file is a symlink to a directory
528+
// and remove the symlink itself by calling `remove_dir` instead of
529+
// `remove_file`.
530+
#[cfg(target_os = "windows")]
531+
{
532+
use std::os::windows::fs::FileTypeExt;
533+
let metadata = symlink_metadata(p)?;
534+
let file_type = metadata.file_type();
535+
if file_type.is_symlink_dir() {
536+
return remove_symlink_dir_with_permission_check(p);
534537
}
535538
}
536539

537-
Err(err).with_context(|| format!("failed to remove file `{}`", p.display()))
540+
remove_file_with_permission_check(p)
541+
}
542+
543+
#[cfg(target_os = "windows")]
544+
fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
545+
remove_with_permission_check(fs::remove_dir, p)
546+
.with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
547+
}
548+
549+
fn remove_file_with_permission_check(p: &Path) -> Result<()> {
550+
remove_with_permission_check(fs::remove_file, p)
551+
.with_context(|| format!("failed to remove file `{}`", p.display()))
552+
}
553+
554+
fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
555+
where
556+
F: Fn(P) -> io::Result<()>,
557+
P: AsRef<Path> + Clone,
558+
{
559+
match remove_func(p.clone()) {
560+
Ok(()) => Ok(()),
561+
Err(e) => {
562+
if e.kind() == io::ErrorKind::PermissionDenied
563+
&& set_not_readonly(p.as_ref()).unwrap_or(false)
564+
{
565+
remove_func(p)
566+
} else {
567+
Err(e)
568+
}
569+
}
570+
}
538571
}
539572

540573
fn set_not_readonly(p: &Path) -> io::Result<bool> {
@@ -908,4 +941,50 @@ mod tests {
908941
);
909942
}
910943
}
944+
945+
#[test]
946+
#[cfg(windows)]
947+
fn test_remove_symlink_dir() {
948+
use super::*;
949+
use std::fs;
950+
use std::os::windows::fs::symlink_dir;
951+
952+
let tmpdir = tempfile::tempdir().unwrap();
953+
let dir_path = tmpdir.path().join("testdir");
954+
let symlink_path = tmpdir.path().join("symlink");
955+
956+
fs::create_dir(&dir_path).unwrap();
957+
958+
symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
959+
960+
assert!(symlink_path.exists());
961+
962+
assert!(remove_file(symlink_path.clone()).is_ok());
963+
964+
assert!(!symlink_path.exists());
965+
assert!(dir_path.exists());
966+
}
967+
968+
#[test]
969+
#[cfg(windows)]
970+
fn test_remove_symlink_file() {
971+
use super::*;
972+
use std::fs;
973+
use std::os::windows::fs::symlink_file;
974+
975+
let tmpdir = tempfile::tempdir().unwrap();
976+
let file_path = tmpdir.path().join("testfile");
977+
let symlink_path = tmpdir.path().join("symlink");
978+
979+
fs::write(&file_path, b"test").unwrap();
980+
981+
symlink_file(&file_path, &symlink_path).expect("failed to create symlink");
982+
983+
assert!(symlink_path.exists());
984+
985+
assert!(remove_file(symlink_path.clone()).is_ok());
986+
987+
assert!(!symlink_path.exists());
988+
assert!(file_path.exists());
989+
}
911990
}

0 commit comments

Comments
 (0)