Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/uu/cp/src/platform/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,23 +167,28 @@ where
let mut src_file = open_source(source, nofollow)?;
let dst_file = create_dest_restrictive(dest, false)?;

let size: usize = src_file.metadata()?.size().try_into().unwrap();
ftruncate(&dst_file, size.try_into().unwrap())?;
let size = src_file.metadata()?.size();
ftruncate(&dst_file, size)?;

let blksize = dst_file.metadata()?.blksize();
let mut buf: Vec<u8> = vec![0; blksize.try_into().unwrap()];
let mut current_offset: usize = 0;
let blksize = dst_file.metadata()?.blksize() as usize;
let mut buf: Vec<u8> = vec![0; blksize];
let mut current_offset: u64 = 0;

// TODO Perhaps we can employ the "fiemap ioctl" API to get the
// file extent mappings:
// https://www.kernel.org/doc/html/latest/filesystems/fiemap.html
while current_offset < size {
let this_read = src_file.read(&mut buf)?;
if this_read == 0 {
// The source shrank (e.g. truncated concurrently) before we reached
// its reported size; stop instead of looping forever on EOF reads.
break;
}
let buf = &buf[..this_read];
if buf.iter().any(|&x| x != 0) {
dst_file.write_all_at(buf, current_offset.try_into().unwrap())?;
dst_file.write_all_at(buf, current_offset)?;
}
current_offset += this_read;
current_offset += this_read as u64;
}
Ok(())
}
Expand Down
20 changes: 20 additions & 0 deletions tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3942,6 +3942,26 @@ fn test_reflink_never_sparse_always() {
assert_eq!(dest_metadata.len(), 1024 * 1024);
}

#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_reflink_never_sparse_always_data_is_copied() {
// `--reflink=never --sparse=always` goes through the read-loop sparse copy.
// A file with data spread across several blocks must be copied byte-for-byte.
let (at, mut ucmd) = at_and_ucmd!();

let mut buf = vec![0u8; 4096 * 4 + 7];
for i in [0, buf.len() / 2, buf.len() - 1] {
buf[i] = b'x';
}
at.write_bytes("src", &buf);

ucmd.args(&["--reflink=never", "--sparse=always", "src", "dest"])
.succeeds()
.no_output();

assert_eq!(at.read_bytes("dest"), buf);
}

/// Test for preserving attributes of a hard link in a directory.
#[test]
#[cfg(not(any(target_os = "android", target_os = "openbsd")))]
Expand Down
Loading