Skip to content

Commit 38fbf6c

Browse files
committed
cp: backup, hardlink fixes
1 parent 082528d commit 38fbf6c

File tree

2 files changed

+93
-8
lines changed

2 files changed

+93
-8
lines changed

src/uu/cp/src/cp.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,10 +1499,11 @@ fn context_for(src: &Path, dest: &Path) -> String {
14991499
format!("{} -> {}", src.quote(), dest.quote())
15001500
}
15011501

1502-
/// Implements a simple backup copy for the destination file.
1502+
/// Implements a simple backup copy for the destination file .
1503+
/// if is_dest_symlink flag is set to true dest will be renamed to backup_path
15031504
/// TODO: for the backup, should this function be replaced by `copy_file(...)`?
1504-
fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult<PathBuf> {
1505-
if dest.is_symlink() {
1505+
fn backup_dest(dest: &Path, backup_path: &Path, is_dest_symlink: bool) -> CopyResult<PathBuf> {
1506+
if is_dest_symlink {
15061507
fs::rename(dest, backup_path)?;
15071508
} else {
15081509
fs::copy(dest, backup_path)?;
@@ -1536,6 +1537,7 @@ fn handle_existing_dest(
15361537
dest: &Path,
15371538
options: &Options,
15381539
source_in_command_line: bool,
1540+
copied_files: &mut HashMap<FileInformation, PathBuf>,
15391541
) -> CopyResult<()> {
15401542
// Disallow copying a file to itself, unless `--force` and
15411543
// `--backup` are both specified.
@@ -1545,6 +1547,7 @@ fn handle_existing_dest(
15451547

15461548
options.overwrite.verify(dest)?;
15471549

1550+
let mut is_dest_removed = false;
15481551
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
15491552
if let Some(backup_path) = backup_path {
15501553
if paths_refer_to_same_file(source, &backup_path, true) {
@@ -1555,13 +1558,16 @@ fn handle_existing_dest(
15551558
)
15561559
.into());
15571560
} else {
1558-
backup_dest(dest, &backup_path)?;
1561+
is_dest_removed = dest.is_symlink();
1562+
backup_dest(dest, &backup_path, is_dest_removed)?;
15591563
}
15601564
}
15611565
match options.overwrite {
15621566
// FIXME: print that the file was removed if --verbose is enabled
15631567
OverwriteMode::Clobber(ClobberMode::Force) => {
1564-
if is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly() {
1568+
if !is_dest_removed
1569+
&& (is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly())
1570+
{
15651571
fs::remove_file(dest)?;
15661572
}
15671573
}
@@ -1582,7 +1588,18 @@ fn handle_existing_dest(
15821588
// `dest/src/f` and `dest/src/f` has the contents of
15831589
// `src/f`, we delete the existing file to allow the hard
15841590
// linking.
1585-
if options.preserve_hard_links() {
1591+
1592+
if options.preserve_hard_links()
1593+
// only try to remove dest file only if the current source is hardlink to a file that is already copied
1594+
&& copied_files.contains_key(
1595+
&FileInformation::from_path(
1596+
source,
1597+
options.dereference(source_in_command_line),
1598+
)
1599+
.context(format!("cannot stat {}", source.quote()))?,
1600+
)
1601+
&& !is_dest_removed
1602+
{
15861603
fs::remove_file(dest)?;
15871604
}
15881605
}
@@ -1708,7 +1725,7 @@ fn handle_copy_mode(
17081725
let backup_path =
17091726
backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
17101727
if let Some(backup_path) = backup_path {
1711-
backup_dest(dest, &backup_path)?;
1728+
backup_dest(dest, &backup_path, dest.is_symlink())?;
17121729
fs::remove_file(dest)?;
17131730
}
17141731
if options.overwrite == OverwriteMode::Clobber(ClobberMode::Force) {
@@ -1935,7 +1952,7 @@ fn copy_file(
19351952
{
19361953
return Ok(());
19371954
}
1938-
handle_existing_dest(source, dest, options, source_in_command_line)?;
1955+
handle_existing_dest(source, dest, options, source_in_command_line, copied_files)?;
19391956
}
19401957

19411958
if options.attributes_only

tests/by-util/test_cp.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3855,3 +3855,71 @@ fn test_cp_no_dereference_attributes_only_with_symlink() {
38553855
"file2 content does not match expected"
38563856
);
38573857
}
3858+
3859+
#[cfg(all(unix, not(target_os = "macos")))]
3860+
#[test]
3861+
fn test_cp_deref_dest_before_copying_with_preserve_links() {
3862+
let scene = TestScenario::new(util_name!());
3863+
let at = &scene.fixtures;
3864+
3865+
at.write("file1", "1");
3866+
at.write("file2", "2");
3867+
at.symlink_file("file1", "sym1");
3868+
3869+
scene.ucmd().args(&["-d", "file2", "sym1"]).succeeds();
3870+
3871+
assert!(at.symlink_exists("sym1"));
3872+
3873+
assert_eq!(at.read("file1"), "2",);
3874+
}
3875+
3876+
#[cfg(all(unix, not(target_os = "macos")))]
3877+
#[test]
3878+
fn test_cp_backup_when_dest_is_symlink_to_same_file_with_preserve_links() {
3879+
let scene = TestScenario::new(util_name!());
3880+
let at = &scene.fixtures;
3881+
3882+
at.write("file1", "1");
3883+
at.symlink_file("file1", "sym1");
3884+
3885+
scene.ucmd().args(&["-bd", "file1", "sym1"]).succeeds();
3886+
3887+
assert!(at.symlink_exists("sym1~"));
3888+
assert!(at.file_exists("sym1"));
3889+
3890+
assert_eq!(at.read("sym1"), "1",);
3891+
}
3892+
3893+
#[cfg(all(unix, not(target_os = "macos")))]
3894+
#[test]
3895+
fn test_cp_backup_when_dest_is_symlink_to_same_file_with_force() {
3896+
let scene = TestScenario::new(util_name!());
3897+
let at = &scene.fixtures;
3898+
3899+
at.write("file1", "1");
3900+
at.symlink_file("file1", "sym1");
3901+
3902+
scene.ucmd().args(&["-bf", "file1", "sym1"]).succeeds();
3903+
3904+
assert!(at.symlink_exists("sym1~"));
3905+
assert!(at.file_exists("sym1"));
3906+
3907+
assert_eq!(at.read("sym1"), "1",);
3908+
}
3909+
3910+
#[cfg(all(unix, not(target_os = "macos")))]
3911+
#[test]
3912+
fn test_cp_backup_when_dest_is_symlink_to_same_file_with_force_and_preserve_links() {
3913+
let scene = TestScenario::new(util_name!());
3914+
let at = &scene.fixtures;
3915+
3916+
at.write("file1", "1");
3917+
at.symlink_file("file1", "sym1");
3918+
3919+
scene.ucmd().args(&["-bdf", "file1", "sym1"]).succeeds();
3920+
3921+
assert!(at.symlink_exists("sym1~"));
3922+
assert!(at.file_exists("sym1"));
3923+
3924+
assert_eq!(at.read("sym1"), "1",);
3925+
}

0 commit comments

Comments
 (0)