Skip to content

Commit 6b8f4c6

Browse files
committed
cp: fix cp throwing error when dest is symlink and options backup and --rem is given
1 parent 5baf382 commit 6b8f4c6

File tree

2 files changed

+52
-33
lines changed

2 files changed

+52
-33
lines changed

src/uu/cp/src/cp.rs

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,34 +1669,33 @@ fn handle_existing_dest(
16691669
backup_dest(dest, &backup_path, is_dest_removed)?;
16701670
}
16711671
}
1672-
match options.overwrite {
1673-
// FIXME: print that the file was removed if --verbose is enabled
1674-
OverwriteMode::Clobber(ClobberMode::Force) => {
1675-
if !is_dest_removed
1676-
&& (is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly())
1677-
{
1672+
if !is_dest_removed {
1673+
match options.overwrite {
1674+
// FIXME: print that the file was removed if --verbose is enabled
1675+
OverwriteMode::Clobber(ClobberMode::Force) => {
1676+
if is_symlink_loop(dest) || fs::metadata(dest)?.permissions().readonly() {
1677+
fs::remove_file(dest)?;
1678+
}
1679+
}
1680+
OverwriteMode::Clobber(ClobberMode::RemoveDestination) => {
16781681
fs::remove_file(dest)?;
16791682
}
1680-
}
1681-
OverwriteMode::Clobber(ClobberMode::RemoveDestination) => {
1682-
fs::remove_file(dest)?;
1683-
}
1684-
OverwriteMode::Clobber(ClobberMode::Standard) => {
1685-
// Consider the following files:
1686-
//
1687-
// * `src/f` - a regular file
1688-
// * `src/link` - a hard link to `src/f`
1689-
// * `dest/src/f` - a different regular file
1690-
//
1691-
// In this scenario, if we do `cp -a src/ dest/`, it is
1692-
// possible that the order of traversal causes `src/link`
1693-
// to get copied first (to `dest/src/link`). In that case,
1694-
// in order to make sure `dest/src/link` is a hard link to
1695-
// `dest/src/f` and `dest/src/f` has the contents of
1696-
// `src/f`, we delete the existing file to allow the hard
1697-
// linking.
1698-
1699-
if options.preserve_hard_links()
1683+
OverwriteMode::Clobber(ClobberMode::Standard) => {
1684+
// Consider the following files:
1685+
//
1686+
// * `src/f` - a regular file
1687+
// * `src/link` - a hard link to `src/f`
1688+
// * `dest/src/f` - a different regular file
1689+
//
1690+
// In this scenario, if we do `cp -a src/ dest/`, it is
1691+
// possible that the order of traversal causes `src/link`
1692+
// to get copied first (to `dest/src/link`). In that case,
1693+
// in order to make sure `dest/src/link` is a hard link to
1694+
// `dest/src/f` and `dest/src/f` has the contents of
1695+
// `src/f`, we delete the existing file to allow the hard
1696+
// linking.
1697+
1698+
if options.preserve_hard_links()
17001699
// only try to remove dest file only if the current source
17011700
// is hardlink to a file that is already copied
17021701
&& copied_files.contains_key(
@@ -1705,14 +1704,13 @@ fn handle_existing_dest(
17051704
options.dereference(source_in_command_line),
17061705
)
17071706
.context(format!("cannot stat {}", source.quote()))?,
1708-
)
1709-
&& !is_dest_removed
1710-
{
1711-
fs::remove_file(dest)?;
1707+
) {
1708+
fs::remove_file(dest)?;
1709+
}
17121710
}
1713-
}
1714-
_ => (),
1715-
};
1711+
_ => (),
1712+
};
1713+
}
17161714

17171715
Ok(())
17181716
}
@@ -2043,6 +2041,7 @@ fn copy_file(
20432041
options.overwrite,
20442042
OverwriteMode::Clobber(ClobberMode::RemoveDestination)
20452043
)
2044+
&& options.backup == BackupMode::NoBackup
20462045
{
20472046
fs::remove_file(dest)?;
20482047
}

tests/by-util/test_cp.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5696,3 +5696,23 @@ fn test_cp_parents_absolute_path() {
56965696
let res = format!("dest{}/a/b/f", at.root_dir_resolved());
56975697
at.file_exists(res);
56985698
}
5699+
5700+
// make sure that cp backup dest symlink before removing it.
5701+
#[test]
5702+
fn test_cp_with_options_backup_and_rem_when_dest_is_symlink() {
5703+
let scene = TestScenario::new(util_name!());
5704+
let at = &scene.fixtures;
5705+
at.write("file", "xyz");
5706+
at.mkdir("inner_dir");
5707+
at.write("inner_dir/inner_file", "abc");
5708+
at.relative_symlink_file("inner_file", "inner_dir/sl");
5709+
scene
5710+
.ucmd()
5711+
.args(&["-b", "--rem", "file", "inner_dir/sl"])
5712+
.succeeds();
5713+
assert!(at.file_exists("inner_dir/inner_file"));
5714+
assert_eq!(at.read("inner_dir/inner_file"), "abc");
5715+
assert!(at.symlink_exists("inner_dir/sl~"));
5716+
assert!(!at.symlink_exists("inner_dir/sl"));
5717+
assert_eq!(at.read("inner_dir/sl"), "xyz");
5718+
}

0 commit comments

Comments
 (0)