about summary refs log tree commit diff
diff options
context:
space:
mode:
authorChris Denton <chris@chrisdenton.dev>2024-12-26 12:03:37 +0000
committerChris Denton <chris@chrisdenton.dev>2024-12-27 10:07:10 +0000
commit54b130afa268f886bddb93e7cf086c75ae0f1529 (patch)
treea70f0ce83d0e8715c610cb426cdcc0e61be5071d
parenta25032cf444eeba7652ce5165a2be450430890ba (diff)
downloadrust-54b130afa268f886bddb93e7cf086c75ae0f1529.tar.gz
rust-54b130afa268f886bddb93e7cf086c75ae0f1529.zip
Fix renaming symlinks on Windows
Previously we only detected mount points and not other types of links when determining reparse point behaviour.
-rw-r--r--library/std/src/fs/tests.rs29
-rw-r--r--library/std/src/sys/pal/windows/fs.rs17
2 files changed, 39 insertions, 7 deletions
diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs
index 0308a5f433a..28f16da1ed8 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(&not_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 dda4259919b..2f300e32d7c 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
                 }
             }
         }