1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #3966 from jfinkels/cp-backup-force

cp: force copying file to itself with --backup
This commit is contained in:
Sylvestre Ledru 2022-11-06 08:43:52 +01:00 committed by GitHub
commit 8e5c259e4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 4 deletions

View file

@ -834,6 +834,11 @@ impl Options {
}
false
}
/// Whether to force overwriting the destination file.
fn force(&self) -> bool {
matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force))
}
}
impl TargetType {
@ -1193,16 +1198,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)?;

View file

@ -2252,6 +2252,49 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() {
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~"));
}
/// Test for copying the contents of a FIFO as opposed to the FIFO object itself.
#[cfg(unix)]
#[test]