mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +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:
parent
3ca6139e0f
commit
66ee373373
3 changed files with 51 additions and 0 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue