fix: remove symlink on Windows

Signed-off-by: hi-rustin <rustin.liu@gmail.com>
This commit is contained in:
hi-rustin 2024-05-21 20:06:35 +08:00
parent 0e1b115ccd
commit 40ff7be1ad
No known key found for this signature in database

View File

@ -517,24 +517,57 @@ fn _remove_dir(p: &Path) -> Result<()> {
/// ///
/// If the file is readonly, this will attempt to change the permissions to /// If the file is readonly, this will attempt to change the permissions to
/// force the file to be deleted. /// 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<()> { pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
_remove_file(p.as_ref()) _remove_file(p.as_ref())
} }
fn _remove_file(p: &Path) -> Result<()> { fn _remove_file(p: &Path) -> Result<()> {
let mut err = match fs::remove_file(p) { // For Windows, we need to check if the file is a symlink to a directory
Ok(()) => return Ok(()), // and remove the symlink itself by calling `remove_dir` instead of
Err(e) => e, // `remove_file`.
}; #[cfg(target_os = "windows")]
{
if err.kind() == io::ErrorKind::PermissionDenied && set_not_readonly(p).unwrap_or(false) { use std::os::windows::fs::FileTypeExt;
match fs::remove_file(p) { let metadata = symlink_metadata(p)?;
Ok(()) => return Ok(()), let file_type = metadata.file_type();
Err(e) => err = e, 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> { fn set_not_readonly(p: &Path) -> io::Result<bool> {
@ -926,9 +959,9 @@ mod tests {
assert!(symlink_path.exists()); assert!(symlink_path.exists());
assert!(remove_file(symlink_path.clone()).is_err()); assert!(remove_file(symlink_path.clone()).is_ok());
assert!(symlink_path.exists()); assert!(!symlink_path.exists());
assert!(dir_path.exists()); assert!(dir_path.exists());
} }