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
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to force overwriting the destination file.
|
||||||
|
fn force(&self) -> bool {
|
||||||
|
matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetType {
|
impl TargetType {
|
||||||
|
@ -1194,16 +1199,36 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult<PathBuf> {
|
||||||
Ok(backup_path.into())
|
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(
|
fn handle_existing_dest(
|
||||||
source: &Path,
|
source: &Path,
|
||||||
dest: &Path,
|
dest: &Path,
|
||||||
options: &Options,
|
options: &Options,
|
||||||
source_in_command_line: bool,
|
source_in_command_line: bool,
|
||||||
) -> CopyResult<()> {
|
) -> CopyResult<()> {
|
||||||
let dereference_to_compare =
|
// Disallow copying a file to itself, unless `--force` and
|
||||||
options.dereference(source_in_command_line) || !source.is_symlink();
|
// `--backup` are both specified.
|
||||||
if paths_refer_to_same_file(source, dest, dereference_to_compare) {
|
if is_forbidden_copy_to_same_file(source, dest, options, source_in_command_line) {
|
||||||
return Err(format!("{}: same file", context_for(source, dest)).into());
|
return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
options.overwrite.verify(dest)?;
|
options.overwrite.verify(dest)?;
|
||||||
|
|
|
@ -2226,3 +2226,46 @@ fn test_copy_dir_preserve_permissions_inaccessible_file() {
|
||||||
let metadata2 = at.metadata("d2");
|
let metadata2 = at.metadata("d2");
|
||||||
assert_metadata_eq!(metadata1, metadata2);
|
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