From 54b130afa268f886bddb93e7cf086c75ae0f1529 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Thu, 26 Dec 2024 12:03:37 +0000 Subject: [PATCH] Fix renaming symlinks on Windows Previously we only detected mount points and not other types of links when determining reparse point behaviour. --- library/std/src/fs/tests.rs | 29 +++++++++++++++++++++++++++ library/std/src/sys/pal/windows/fs.rs | 17 +++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 0308a5f433a99..28f16da1ed8d2 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -1953,3 +1953,32 @@ fn test_rename_directory_to_non_empty_directory() { error!(fs::rename(source_path, target_path), 145); // ERROR_DIR_NOT_EMPTY } + +#[test] +fn test_rename_symlink() { + let tmpdir = tmpdir(); + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + symlink_file(not_exist, &original).unwrap(); + fs::rename(&original, &dest).unwrap(); + // Make sure that renaming `original` to `dest` preserves the symlink. + assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); +} + +#[test] +#[cfg(windows)] +fn test_rename_junction() { + let tmpdir = tmpdir(); + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + junction_point(¬_exist, &original).unwrap(); + fs::rename(&original, &dest).unwrap(); + + // Make sure that renaming `original` to `dest` preserves the junction point. + // Junction links are always absolute so we just check the file name is correct. + assert_eq!(fs::read_link(&dest).unwrap().file_name(), Some(not_exist.as_os_str())); +} diff --git a/library/std/src/sys/pal/windows/fs.rs b/library/std/src/sys/pal/windows/fs.rs index dda4259919b6d..2f300e32d7c84 100644 --- a/library/std/src/sys/pal/windows/fs.rs +++ b/library/std/src/sys/pal/windows/fs.rs @@ -1295,15 +1295,18 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { } else { // SAFETY: The struct has been initialized by GetFileInformationByHandleEx let file_attribute_tag_info = unsafe { file_attribute_tag_info.assume_init() }; + let file_type = FileType::new( + file_attribute_tag_info.FileAttributes, + file_attribute_tag_info.ReparseTag, + ); - if file_attribute_tag_info.FileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 - && file_attribute_tag_info.ReparseTag != c::IO_REPARSE_TAG_MOUNT_POINT - { - // The file is not a mount point: Reopen the file without inhibiting reparse point behavior. - None - } else { - // The file is a mount point: Don't reopen the file so that the mount point gets renamed. + if file_type.is_symlink() { + // The file is a mount point, junction point or symlink so + // don't reopen the file so that the link gets renamed. Some(Ok(handle)) + } else { + // Otherwise reopen the file without inhibiting reparse point behavior. + None } } }