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

cp: preserve hard links when target already exists

Prevent a panic in `cp -a` when the target of a hard link already
exists in the target directory structure.

For example,

    $ mkdir -p src dest/src
    $ touch src/f dest/src/f
    $ ln src/f src/link
    $ cp -a src dest

The `cp` command now succeeds without error.
This commit is contained in:
Jeffrey Finkelstein 2022-12-02 23:46:48 -05:00
parent 3ca6139e0f
commit 66ee373373
3 changed files with 51 additions and 0 deletions

View file

@ -918,6 +918,23 @@ fn preserve_hardlinks(
for hard_link in hard_links.iter() {
if hard_link.1 == inode {
// Consider the following files:
//
// * `src/f` - a regular file
// * `src/link` - a hard link to `src/f`
// * `dest/src/f` - a different regular file
//
// In this scenario, if we do `cp -a src/ dest/`, it is
// possible that the order of traversal causes `src/link`
// to get copied first (to `dest/src/link`). In that case,
// in order to make sure `dest/src/link` is a hard link to
// `dest/src/f` and `dest/src/f` has the contents of
// `src/f`, we delete the existing file to allow the hard
// linking.
if file_or_link_exists(dest) && file_or_link_exists(Path::new(&hard_link.0)) {
std::fs::remove_file(dest)?;
}
std::fs::hard_link(hard_link.0.clone(), dest).unwrap();
*found_hard_link = true;
}

View file

@ -2363,3 +2363,32 @@ fn test_reflink_never_sparse_always() {
assert_eq!(src_metadata.blocks(), dest_metadata.blocks());
assert_eq!(dest_metadata.len(), 1024 * 1024);
}
/// Test for preserving attributes of a hard link in a directory.
#[test]
fn test_preserve_hardlink_attributes_in_directory() {
let (at, mut ucmd) = at_and_ucmd!();
// The source directory tree.
at.mkdir("src");
at.touch("src/f");
at.hard_link("src/f", "src/link");
// The destination directory tree.
//
// The file `f` already exists, but the `link` does not.
at.mkdir_all("dest/src");
at.touch("dest/src/f");
ucmd.args(&["-a", "src", "dest"]).succeeds().no_output();
// The hard link should now appear in the destination directory tree.
//
// A hard link should have the same inode as the target file.
at.file_exists("dest/src/link");
#[cfg(unix)]
assert_eq!(
at.metadata("dest/src/f").ino(),
at.metadata("dest/src/link").ino()
);
}

View file

@ -228,6 +228,11 @@ impl CmdResult {
self
}
/// Assert that there is output to neither stderr nor stdout.
pub fn no_output(&self) -> &Self {
self.no_stdout().no_stderr()
}
/// asserts that the command resulted in stdout stream output that equals the
/// passed in value, trailing whitespace are kept to force strict comparison (#1235)
/// stdout_only is a better choice unless stderr may or will be non-empty