mv: support moving folder containing symlinks to different filesystem (#8605)

Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
Yuankun Zhang
2026-01-01 11:01:11 +01:00
committed by GitHub
co-authored by Sylvestre Ledru
parent 88051cba99
commit cc389e5927
2 changed files with 72 additions and 9 deletions
+20 -9
View File
@@ -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(())
+52
View File
@@ -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() {