Skip to content

Commit 5d07fe5

Browse files
fix: handle symlinks properly in write_atomic
When Cargo.toml is a symlink, write_atomic now follows the symlink and writes to the actual target file instead of replacing the symlink with a regular file. Fixes rust-lang#14960
1 parent 35d2a30 commit 5d07fe5

File tree

1 file changed

+39
-0
lines changed

1 file changed

+39
-0
lines changed

crates/cargo-util/src/paths.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,20 @@ pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()>
190190
/// Writes a file to disk atomically.
191191
///
192192
/// This uses `tempfile::persist` to accomplish atomic writes.
193+
/// If the path is a symlink, it will follow the symlink and write to the actual target.
193194
pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
194195
let path = path.as_ref();
195196

197+
// Check if the path is a symlink and follow it if it is
198+
let resolved_path;
199+
let path = if path.is_symlink() {
200+
resolved_path = fs::read_link(path)
201+
.with_context(|| format!("failed to read symlink at `{}`", path.display()))?;
202+
&resolved_path
203+
} else {
204+
path
205+
};
206+
196207
// On unix platforms, get the permissions of the original file. Copy only the user/group/other
197208
// read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600,
198209
// and we'll set the proper permissions after creating the file.
@@ -983,6 +994,34 @@ mod tests {
983994
}
984995
}
985996

997+
998+
#[test]
999+
fn write_atomic_symlink() {
1000+
let tmpdir = tempfile::tempdir().unwrap();
1001+
let target_path = tmpdir.path().join("target.txt");
1002+
let symlink_path = tmpdir.path().join("symlink.txt");
1003+
1004+
// Create initial file
1005+
write(&target_path, "initial").unwrap();
1006+
1007+
// Create symlink
1008+
#[cfg(unix)]
1009+
std::os::unix::fs::symlink(&target_path, &symlink_path).unwrap();
1010+
#[cfg(windows)]
1011+
std::os::windows::fs::symlink_file(&target_path, &symlink_path).unwrap();
1012+
1013+
// Write through symlink
1014+
write_atomic(&symlink_path, "updated").unwrap();
1015+
1016+
// Verify both paths show the updated content
1017+
assert_eq!(std::fs::read_to_string(&target_path).unwrap(), "updated");
1018+
assert_eq!(std::fs::read_to_string(&symlink_path).unwrap(), "updated");
1019+
1020+
// Verify symlink still exists and points to the same target
1021+
assert!(symlink_path.is_symlink());
1022+
assert_eq!(std::fs::read_link(&symlink_path).unwrap(), target_path);
1023+
}
1024+
9861025
#[test]
9871026
#[cfg(windows)]
9881027
fn test_remove_symlink_dir() {

0 commit comments

Comments
 (0)