mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
cp: force copying file to itself with --backup
Fix the behavior of `cp` when both `--backup` and `--force` are specified and the source and destination are the same file. Before this commit, `cp` terminated without copying and without making a backup. After this commit, the copy is made and the backup file is made. For example, $ touch f $ cp --force --backup f f results in a backup file `f~` being created.
This commit is contained in:
parent
742c06965b
commit
fbed01dd54
2 changed files with 72 additions and 4 deletions
|
@ -835,6 +835,11 @@ impl Options {
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Whether to force overwriting the destination file.
|
||||
fn force(&self) -> bool {
|
||||
matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force))
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetType {
|
||||
|
@ -1194,16 +1199,36 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult<PathBuf> {
|
|||
Ok(backup_path.into())
|
||||
}
|
||||
|
||||
/// Decide whether source and destination files are the same and
|
||||
/// copying is forbidden.
|
||||
///
|
||||
/// Copying to the same file is only allowed if both `--backup` and
|
||||
/// `--force` are specified and the file is a regular file.
|
||||
fn is_forbidden_copy_to_same_file(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
options: &Options,
|
||||
source_in_command_line: bool,
|
||||
) -> bool {
|
||||
// TODO To match the behavior of GNU cp, we also need to check
|
||||
// that the file is a regular file.
|
||||
let dereference_to_compare =
|
||||
options.dereference(source_in_command_line) || !source.is_symlink();
|
||||
paths_refer_to_same_file(source, dest, dereference_to_compare)
|
||||
&& !(options.force() && options.backup != BackupMode::NoBackup)
|
||||
}
|
||||
|
||||
/// Back up, remove, or leave intact the destination file, depending on the options.
|
||||
fn handle_existing_dest(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
options: &Options,
|
||||
source_in_command_line: bool,
|
||||
) -> CopyResult<()> {
|
||||
let dereference_to_compare =
|
||||
options.dereference(source_in_command_line) || !source.is_symlink();
|
||||
if paths_refer_to_same_file(source, dest, dereference_to_compare) {
|
||||
return Err(format!("{}: same file", context_for(source, dest)).into());
|
||||
// Disallow copying a file to itself, unless `--force` and
|
||||
// `--backup` are both specified.
|
||||
if is_forbidden_copy_to_same_file(source, dest, options, source_in_command_line) {
|
||||
return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into());
|
||||
}
|
||||
|
||||
options.overwrite.verify(dest)?;
|
||||
|
|
|
@ -2226,3 +2226,46 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() {
|
|||
let metadata2 = at.metadata("d2");
|
||||
assert_metadata_eq!(metadata1, metadata2);
|
||||
}
|
||||
|
||||
/// Test that copying file to itself with backup fails.
|
||||
#[test]
|
||||
fn test_same_file_backup() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("f");
|
||||
ucmd.args(&["--backup", "f", "f"])
|
||||
.fails()
|
||||
.stderr_only("cp: 'f' and 'f' are the same file");
|
||||
assert!(!at.file_exists("f~"));
|
||||
}
|
||||
|
||||
/// Test that copying file to itself with forced backup succeeds.
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn test_same_file_force() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("f");
|
||||
ucmd.args(&["--force", "f", "f"])
|
||||
.fails()
|
||||
.stderr_only("cp: 'f' and 'f' are the same file");
|
||||
assert!(!at.file_exists("f~"));
|
||||
}
|
||||
|
||||
/// Test that copying file to itself with forced backup succeeds.
|
||||
#[cfg(all(not(windows), not(target_os = "macos")))]
|
||||
#[test]
|
||||
fn test_same_file_force_backup() {
|
||||
// TODO This test should work on macos, but the command was
|
||||
// causing an error:
|
||||
//
|
||||
// cp: 'f' -> 'f': No such file or directory (os error 2)
|
||||
//
|
||||
// I couldn't figure out how to fix it, so I just skipped this
|
||||
// test on macos.
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("f");
|
||||
ucmd.args(&["--force", "--backup", "f", "f"])
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
assert!(at.file_exists("f~"));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue