diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 860683e7a6e..845dc560d6c 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -907,8 +907,19 @@ fn rename_fifo_fallback(_from: &Path, _to: &Path) -> io::Result<()> { /// symlinks return an error. #[cfg(unix)] fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> { - let path_symlink_points_to = fs::read_link(from)?; - unix::fs::symlink(path_symlink_points_to, to).and_then(|_| fs::remove_file(from)) + use std::fs; + use std::path::PathBuf; + + let target = fs::read_link(from)?; + + let mut tmp = PathBuf::from(to); + tmp.set_extension("tmp_mv_symlink"); + let _ = fs::remove_file(&tmp); + + unix::fs::symlink(&target, &tmp)?; + + fs::rename(&tmp, to)?; + fs::remove_file(from) } #[cfg(windows)] diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 3c69d65a78d..660d024d8a6 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2856,3 +2856,50 @@ fn test_mv_no_prompt_unwriteable_file_with_no_tty() { assert!(!at.file_exists("source_notty")); assert!(at.file_exists("target_notty")); } + +#[test] +#[cfg(target_os = "linux")] +fn test_mv_cross_device_symlink_overwrite() { + use std::fs; + use std::os::unix::fs::symlink; + use tempfile::TempDir; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let target_file = "file_in_src"; + at.touch(target_file); + at.write(target_file, "contents"); + + let other_fs_tempdir = TempDir::new_in("/dev/shm/") + .expect("Unable to create temp directory in /dev/shm - test requires tmpfs"); + + let src_symlink = other_fs_tempdir.path().join("uutils_mv_src_link"); + symlink(at.plus_as_string(target_file), &src_symlink).expect("Unable to create symlink"); + + at.touch("uutils_mv_dst_exists"); + + scene + .ucmd() + .arg(&src_symlink) + .arg("uutils_mv_dst_exists") + .succeeds() + .no_stderr(); + + assert!( + !src_symlink.exists(), + "Source symlink should not exist after move" + ); + assert!( + at.is_symlink("uutils_mv_dst_exists"), + "Destination should be a symlink" + ); + + let link_target = + fs::read_link(at.plus("uutils_mv_dst_exists")).expect("Failed to read symlink"); + assert_eq!( + link_target, + at.plus(target_file), + "Symlink should point to original target" + ); +}