diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index a43b92eb850..aa34a6294ae 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -1099,7 +1099,13 @@ fn copy_dir_contents_recursive( } #[cfg(not(unix))] { - fs::copy(&from_path, &to_path)?; + if from_path.is_symlink() { + // Copy a symlink file (no-follow). + rename_symlink_fallback(&from_path, &to_path)?; + } else { + // Copy a regular file. + fs::copy(&from_path, &to_path)?; + } } // Print verbose message for file @@ -1142,14 +1148,19 @@ fn copy_file_with_hardlinks_helper( return Ok(()); } - // Regular file copy - #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] - { - fs::copy(from, to).and_then(|_| fsxattr::copy_xattrs(&from, &to))?; - } - #[cfg(any(target_os = "macos", target_os = "redox"))] - { - fs::copy(from, to)?; + if from.is_symlink() { + // Copy a symlink file (no-follow). + rename_symlink_fallback(from, to)?; + } else { + // Copy a regular file. + #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))] + { + fs::copy(from, to).and_then(|_| fsxattr::copy_xattrs(&from, &to))?; + } + #[cfg(any(target_os = "macos", target_os = "redox"))] + { + fs::copy(from, to)?; + } } Ok(()) diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 7e22d930b49..3c69d65a78d 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -623,6 +623,58 @@ fn test_mv_symlink_into_target() { ucmd.arg("dir-link").arg("dir").succeeds(); } +#[cfg(all(unix, not(target_os = "android")))] +#[ignore = "requires sudo"] +#[test] +fn test_mv_broken_symlink_to_another_fs() { + let scene = TestScenario::new(util_name!()); + + scene.fixtures.mkdir("foo"); + + let output = scene + .cmd("sudo") + .env("PATH", env!("PATH")) + .args(&["-E", "--non-interactive", "ls"]) + .run(); + println!("test output: {output:?}"); + + let mount = scene + .cmd("sudo") + .env("PATH", env!("PATH")) + .args(&[ + "-E", + "--non-interactive", + "mount", + "none", + "-t", + "tmpfs", + "foo", + ]) + .run(); + + if !mount.succeeded() { + print!("Test skipped; requires root user"); + return; + } + + scene.fixtures.mkdir("bar"); + scene.fixtures.symlink_file("nonexistent", "bar/baz"); + + scene + .ucmd() + .arg("bar") + .arg("foo") + .succeeds() + .no_stderr() + .no_stdout(); + + scene + .cmd("sudo") + .env("PATH", env!("PATH")) + .args(&["-E", "--non-interactive", "umount", "foo"]) + .succeeds(); +} + #[test] #[cfg(all(unix, not(target_os = "android")))] fn test_mv_hardlink_to_symlink() {