Skip to content

Commit 91c048d

Browse files
committed
mv: support moving folder containing symlinks to different filesystem
1 parent 32eef06 commit 91c048d

File tree

2 files changed

+63
-9
lines changed

2 files changed

+63
-9
lines changed

src/uu/mv/src/mv.rs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,11 @@ fn copy_dir_contents_recursive(
10441044
}
10451045
#[cfg(not(unix))]
10461046
{
1047-
fs::copy(&from_path, &to_path)?;
1047+
if from_path.is_symlink() {
1048+
rename_symlink_fallback(&from_path, &to_path)?;
1049+
} else {
1050+
fs::copy(&from_path, &to_path)?;
1051+
}
10481052
}
10491053
}
10501054

@@ -1076,14 +1080,19 @@ fn copy_file_with_hardlinks_helper(
10761080
return Ok(());
10771081
}
10781082

1079-
// Regular file copy
1080-
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
1081-
{
1082-
fs::copy(from, to).and_then(|_| fsxattr::copy_xattrs(&from, &to))?;
1083-
}
1084-
#[cfg(any(target_os = "macos", target_os = "redox"))]
1085-
{
1086-
fs::copy(from, to)?;
1083+
if from.is_symlink() {
1084+
// Symlink copy
1085+
rename_symlink_fallback(from, to)?;
1086+
} else {
1087+
// Regular file copy
1088+
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
1089+
{
1090+
fs::copy(from, to).and_then(|_| fsxattr::copy_xattrs(&from, &to))?;
1091+
}
1092+
#[cfg(any(target_os = "macos", target_os = "redox"))]
1093+
{
1094+
fs::copy(from, to)?;
1095+
}
10871096
}
10881097

10891098
Ok(())

tests/by-util/test_mv.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use uutests::new_ucmd;
1616
use uutests::util::TestScenario;
1717
use uutests::{at_and_ucmd, util_name};
1818

19+
pub const PATH: &str = env!("PATH");
20+
1921
#[test]
2022
fn test_mv_invalid_arg() {
2123
new_ucmd!().arg("--definitely-invalid").fails_with_code(1);
@@ -621,6 +623,49 @@ fn test_mv_symlink_into_target() {
621623
ucmd.arg("dir-link").arg("dir").succeeds();
622624
}
623625

626+
#[cfg(all(unix, not(target_os = "android")))]
627+
#[test]
628+
fn test_mv_broken_symlink_to_another_fs() {
629+
let scene = TestScenario::new(util_name!());
630+
631+
scene.fixtures.mkdir("foo");
632+
633+
let mount = scene
634+
.cmd("sudo")
635+
.env("PATH", PATH)
636+
.args(&[
637+
"-E",
638+
"--non-interactive",
639+
"mount",
640+
"none",
641+
"-t",
642+
"tmpfs",
643+
"foo",
644+
])
645+
.run();
646+
647+
if !mount.succeeded() {
648+
print!("Test skipped; requires root user");
649+
return;
650+
}
651+
652+
scene.fixtures.mkdir("bar");
653+
scene.fixtures.symlink_file("nonexistent", "bar/baz");
654+
655+
scene
656+
.ucmd()
657+
.arg("bar")
658+
.arg("foo")
659+
.succeeds()
660+
.no_stderr()
661+
.no_stdout();
662+
663+
scene
664+
.cmd("sudo")
665+
.args(&["-E", "--non-interactive", "umount", "foo"])
666+
.succeeds();
667+
}
668+
624669
#[test]
625670
#[cfg(all(unix, not(target_os = "android")))]
626671
fn test_mv_hardlink_to_symlink() {

0 commit comments

Comments
 (0)