From 7a08991404e79b4e79d844eb22dba31978b41c00 Mon Sep 17 00:00:00 2001 From: Toni Jovanoski Date: Mon, 5 Jan 2026 14:40:48 +0100 Subject: [PATCH 1/4] Fix 10010 --- src/uu/mv/src/mv.rs | 15 +++++++++++++-- tests/by-util/test_mv.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index aa34a6294ae..2971d455b07 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..c6444c25958 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2856,3 +2856,36 @@ fn test_mv_no_prompt_unwriteable_file_with_no_tty() { assert!(!at.file_exists("source_notty")); assert!(at.file_exists("target_notty")); } + +#[test] +#[cfg(unix)] +fn test_mv_cross_device_symlink_overwrite() { + use std::fs; + use std::os::unix::fs::symlink; + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let src_symlink = "/dev/shm/uutils_mv_src_link_fixed"; + let dst_file = at.plus_as_string("uutils_mv_dst_exists"); + let target_file = at.plus_as_string("file_in_src"); + + // Cleanup any leftover files from previous failed runs + let _ = fs::remove_file(&src_symlink); + let _ = fs::remove_file(&dst_file); + let _ = fs::remove_file(&target_file); + + // Create target file and symlink + fs::write(&target_file, "contents").unwrap(); + symlink(&target_file, &src_symlink).unwrap(); + + // Create an existing destination file to test overwrite + at.touch("uutils_mv_dst_exists"); + + scene.ucmd().arg(&src_symlink).arg(&dst_file).succeeds(); + + // Cleanup + let _ = fs::remove_file(&dst_file); + let _ = fs::remove_file(&src_symlink); + let _ = fs::remove_file(&target_file); +} From 7552fa1fe7512ab50c8ab1fb1aa18592d6e69e49 Mon Sep 17 00:00:00 2001 From: Toni Jovanoski Date: Mon, 5 Jan 2026 16:36:29 +0100 Subject: [PATCH 2/4] Fix clippy objections --- tests/by-util/test_mv.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index c6444c25958..5a7ae67e64b 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2871,21 +2871,21 @@ fn test_mv_cross_device_symlink_overwrite() { let target_file = at.plus_as_string("file_in_src"); // Cleanup any leftover files from previous failed runs - let _ = fs::remove_file(&src_symlink); + let _ = fs::remove_file(src_symlink); let _ = fs::remove_file(&dst_file); let _ = fs::remove_file(&target_file); // Create target file and symlink fs::write(&target_file, "contents").unwrap(); - symlink(&target_file, &src_symlink).unwrap(); + symlink(&target_file, src_symlink).unwrap(); // Create an existing destination file to test overwrite at.touch("uutils_mv_dst_exists"); - scene.ucmd().arg(&src_symlink).arg(&dst_file).succeeds(); + scene.ucmd().arg(src_symlink).arg(&dst_file).succeeds(); // Cleanup let _ = fs::remove_file(&dst_file); - let _ = fs::remove_file(&src_symlink); + let _ = fs::remove_file(src_symlink); let _ = fs::remove_file(&target_file); } From 9a91bb90c2d0597710c2d2fd6f610c9dc55a1b62 Mon Sep 17 00:00:00 2001 From: Toni Jovanoski Date: Tue, 6 Jan 2026 14:03:33 +0100 Subject: [PATCH 3/4] Improve test code --- tests/by-util/test_mv.rs | 46 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 5a7ae67e64b..c5ff5974c19 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2862,30 +2862,44 @@ fn test_mv_no_prompt_unwriteable_file_with_no_tty() { 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 src_symlink = "/dev/shm/uutils_mv_src_link_fixed"; - let dst_file = at.plus_as_string("uutils_mv_dst_exists"); - let target_file = at.plus_as_string("file_in_src"); + let target_file = "file_in_src"; + at.touch(target_file); + at.write(target_file, "contents"); - // Cleanup any leftover files from previous failed runs - let _ = fs::remove_file(src_symlink); - let _ = fs::remove_file(&dst_file); - let _ = fs::remove_file(&target_file); + let other_fs_tempdir = TempDir::new_in("/dev/shm/") + .expect("Unable to create temp directory in /dev/shm - test requires tmpfs"); - // Create target file and symlink - fs::write(&target_file, "contents").unwrap(); - symlink(&target_file, src_symlink).unwrap(); + 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"); - // Create an existing destination file to test overwrite at.touch("uutils_mv_dst_exists"); - scene.ucmd().arg(src_symlink).arg(&dst_file).succeeds(); + scene + .ucmd() + .arg(&src_symlink) + .arg("uutils_mv_dst_exists") + .succeeds() + .no_stderr(); - // Cleanup - let _ = fs::remove_file(&dst_file); - let _ = fs::remove_file(src_symlink); - let _ = fs::remove_file(&target_file); + 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" + ); } From 82b8f2ef46f20f7c542af6b9ea84cab7c7c9bfb9 Mon Sep 17 00:00:00 2001 From: Toni Jovanoski Date: Thu, 8 Jan 2026 12:04:16 +0100 Subject: [PATCH 4/4] Enable cross_device_symlink_overwrite only on Linux --- tests/by-util/test_mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index c5ff5974c19..660d024d8a6 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -2858,7 +2858,7 @@ fn test_mv_no_prompt_unwriteable_file_with_no_tty() { } #[test] -#[cfg(unix)] +#[cfg(target_os = "linux")] fn test_mv_cross_device_symlink_overwrite() { use std::fs; use std::os::unix::fs::symlink;