Skip to content

Commit

Permalink
Auto merge of rust-lang#13910 - hi-rustin:rustin-patch-symlink, r=wei…
Browse files Browse the repository at this point in the history
…hanglo

fix: remove symlink dir on Windows
  • Loading branch information
bors committed May 23, 2024
2 parents 3830c0c + 40ff7be commit 9f7a715
Showing 1 changed file with 89 additions and 10 deletions.
99 changes: 89 additions & 10 deletions crates/cargo-util/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,24 +517,57 @@ fn _remove_dir(p: &Path) -> Result<()> {
///
/// If the file is readonly, this will attempt to change the permissions to
/// force the file to be deleted.
/// On Windows, if the file is a symlink to a directory, this will attempt to remove
/// the symlink itself.
pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
_remove_file(p.as_ref())
}

fn _remove_file(p: &Path) -> Result<()> {
let mut err = match fs::remove_file(p) {
Ok(()) => return Ok(()),
Err(e) => e,
};

if err.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p).unwrap_or(false) {
match fs::remove_file(p) {
Ok(()) => return Ok(()),
Err(e) => err = e,
// For Windows, we need to check if the file is a symlink to a directory
// and remove the symlink itself by calling `remove_dir` instead of
// `remove_file`.
#[cfg(target_os = "windows")]
{
use std::os::windows::fs::FileTypeExt;
let metadata = symlink_metadata(p)?;
let file_type = metadata.file_type();
if file_type.is_symlink_dir() {
return remove_symlink_dir_with_permission_check(p);
}
}

Err(err).with_context(|| format!("failed to remove file `{}`", p.display()))
remove_file_with_permission_check(p)
}

#[cfg(target_os = "windows")]
fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
remove_with_permission_check(fs::remove_dir, p)
.with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
}

fn remove_file_with_permission_check(p: &Path) -> Result<()> {
remove_with_permission_check(fs::remove_file, p)
.with_context(|| format!("failed to remove file `{}`", p.display()))
}

fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
where
F: Fn(P) -> io::Result<()>,
P: AsRef<Path> + Clone,
{
match remove_func(p.clone()) {
Ok(()) => Ok(()),
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied
&& set_not_readonly(p.as_ref()).unwrap_or(false)
{
remove_func(p)
} else {
Err(e)
}
}
}
}

fn set_not_readonly(p: &Path) -> io::Result<bool> {
Expand Down Expand Up @@ -908,4 +941,50 @@ mod tests {
);
}
}

#[test]
#[cfg(windows)]
fn test_remove_symlink_dir() {
use super::*;
use std::fs;
use std::os::windows::fs::symlink_dir;

let tmpdir = tempfile::tempdir().unwrap();
let dir_path = tmpdir.path().join("testdir");
let symlink_path = tmpdir.path().join("symlink");

fs::create_dir(&dir_path).unwrap();

symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");

assert!(symlink_path.exists());

assert!(remove_file(symlink_path.clone()).is_ok());

assert!(!symlink_path.exists());
assert!(dir_path.exists());
}

#[test]
#[cfg(windows)]
fn test_remove_symlink_file() {
use super::*;
use std::fs;
use std::os::windows::fs::symlink_file;

let tmpdir = tempfile::tempdir().unwrap();
let file_path = tmpdir.path().join("testfile");
let symlink_path = tmpdir.path().join("symlink");

fs::write(&file_path, b"test").unwrap();

symlink_file(&file_path, &symlink_path).expect("failed to create symlink");

assert!(symlink_path.exists());

assert!(remove_file(symlink_path.clone()).is_ok());

assert!(!symlink_path.exists());
assert!(file_path.exists());
}
}

0 comments on commit 9f7a715

Please sign in to comment.