mirror of
https://github.com/uutils/coreutils.git
synced 2026-10-28 10:45:25 +08:00
install: check for the same file before removing the destination with -D
With -D, the destination was removed before the same-file check ran, so `install -D a a` deleted `a`. Run the check before the removal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3ee13ab487
commit
217973b166
2 files changed
+49
-13
No files matched your search
@@ -814,6 +814,21 @@ fn standard(mut paths: Vec<OsString>, b: &Behavior) -> UResult<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Check before the destination is removed below, which would
|
||||
// remove the source if both are the same file.
|
||||
let source_meta = metadata(source)?;
|
||||
if let Ok(to_stat) =
|
||||
parent_fd.stat_at(filename.as_os_str(), SymlinkBehavior::Follow)
|
||||
{
|
||||
// st_dev and st_ino types vary by platform (i32/u32 on macOS, u64 on Linux)
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if source_meta.dev() == to_stat.st_dev as u64
|
||||
&& source_meta.ino() == to_stat.st_ino as u64
|
||||
{
|
||||
return Err(InstallError::SameFile(source.clone(), target.clone()).into());
|
||||
}
|
||||
}
|
||||
|
||||
let backup_path = perform_backup(source, &target, b)?;
|
||||
|
||||
if let Err(e) = parent_fd.unlink_at(filename.as_os_str(), false)
|
||||
@@ -1009,19 +1024,6 @@ fn perform_backup(from: &Path, to: &Path, b: &Behavior) -> UResult<Option<PathBu
|
||||
/// - `copy_file` uses path-based `OpenOptions::new().create_new().open()`
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn copy_file_safe(from: &Path, to_parent_fd: &DirFd, to_filename: &std::ffi::OsStr) -> UResult<()> {
|
||||
let from_meta = metadata(from)?;
|
||||
|
||||
// Check if source and destination are the same file
|
||||
if let Ok(to_stat) = to_parent_fd.stat_at(to_filename, SymlinkBehavior::Follow) {
|
||||
// st_dev and st_ino types vary by platform (i32/u32 on macOS, u64 on Linux)
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if from_meta.dev() == to_stat.st_dev as u64 && from_meta.ino() == to_stat.st_ino as u64 {
|
||||
return Err(
|
||||
InstallError::SameFile(from.to_path_buf(), PathBuf::from(to_filename)).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut src = File::open(from)?;
|
||||
let mut dst = to_parent_fd.open_file_at(to_filename)?;
|
||||
copy_fast(&mut src, &mut dst)?;
|
||||
|
||||
@@ -2163,6 +2163,40 @@ fn test_install_symlink_same_file() {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_create_leading_same_file() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "file";
|
||||
|
||||
at.write(file, "content");
|
||||
ucmd.arg("-D")
|
||||
.arg(file)
|
||||
.arg(file)
|
||||
.fails()
|
||||
.stderr_contains("'file' and 'file' are the same file");
|
||||
assert_eq!(at.read(file), "content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_create_leading_symlink_same_file() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "file";
|
||||
let target_dir = "target_dir";
|
||||
let target_link = "target_link";
|
||||
|
||||
at.mkdir(target_dir);
|
||||
at.write(&format!("{target_dir}/{file}"), "content");
|
||||
at.symlink_file(target_dir, target_link);
|
||||
ucmd.arg("-D")
|
||||
.arg(format!("{target_dir}/{file}"))
|
||||
.arg(format!("{target_link}/{file}"))
|
||||
.fails()
|
||||
.stderr_contains(format!(
|
||||
"'{target_dir}/{file}' and '{target_link}/{file}' are the same file"
|
||||
));
|
||||
assert_eq!(at.read(&format!("{target_dir}/{file}")), "content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_failing_cannot_overwrite() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
Reference in new issue
Block a user