1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 19:47:45 +00:00

Fix the debug results in cp --debug (#6220)

This commit is contained in:
Anirban Halder 2024-04-22 20:32:21 +05:30 committed by GitHub
parent 366af1c056
commit 421b820ec2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 1081 additions and 24 deletions

View file

@ -2,10 +2,14 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap
// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap lseek
use libc::{SEEK_DATA, SEEK_HOLE};
use std::fs::{File, OpenOptions};
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::fs::FileExt;
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
use std::os::unix::io::AsRawFd;
use std::path::Path;
@ -32,6 +36,25 @@ enum CloneFallback {
/// Use [`std::fs::copy`].
FSCopy,
/// Use sparse_copy
SparseCopy,
/// Use sparse_copy_without_hole
SparseCopyWithoutHole,
}
/// Type of method used for copying files
#[derive(Clone, Copy)]
enum CopyMethod {
/// Do a sparse copy
SparseCopy,
/// Use [`std::fs::copy`].
FSCopy,
/// Default (can either be sparse_copy or FSCopy)
Default,
/// Use sparse_copy_without_hole
SparseCopyWithoutHole,
}
/// Use the Linux `ioctl_ficlone` API to do a copy-on-write clone.
@ -53,17 +76,109 @@ where
match fallback {
CloneFallback::Error => Err(std::io::Error::last_os_error()),
CloneFallback::FSCopy => std::fs::copy(source, dest).map(|_| ()),
CloneFallback::SparseCopy => sparse_copy(source, dest),
CloneFallback::SparseCopyWithoutHole => sparse_copy_without_hole(source, dest),
}
}
/// Checks whether a file contains any non null bytes i.e. any byte != 0x0
/// This function returns a tuple of (bool, u64, u64) signifying a tuple of (whether a file has
/// data, its size, no of blocks it has allocated in disk)
#[cfg(any(target_os = "linux", target_os = "android"))]
fn check_for_data(source: &Path) -> Result<(bool, u64, u64), std::io::Error> {
let mut src_file = File::open(source)?;
let metadata = src_file.metadata()?;
let size = metadata.size();
let blocks = metadata.blocks();
// checks edge case of virtual files in /proc which have a size of zero but contains data
if size == 0 {
let mut buf: Vec<u8> = vec![0; metadata.blksize() as usize]; // Directly use metadata.blksize()
let _ = src_file.read(&mut buf)?;
return Ok((buf.iter().any(|&x| x != 0x0), size, 0));
}
let src_fd = src_file.as_raw_fd();
let result = unsafe { libc::lseek(src_fd, 0, SEEK_DATA) };
match result {
-1 => Ok((false, size, blocks)), // No data found or end of file
_ if result >= 0 => Ok((true, size, blocks)), // Data found
_ => Err(std::io::Error::last_os_error()),
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
/// Checks whether a file is sparse i.e. it contains holes, uses the crude heuristic blocks < size / 512
/// Reference:`<https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.blocks>`
fn check_sparse_detection(source: &Path) -> Result<bool, std::io::Error> {
let src_file = File::open(source)?;
let metadata = src_file.metadata()?;
let size = metadata.size();
let blocks = metadata.blocks();
if blocks < size / 512 {
return Ok(true);
}
Ok(false)
}
/// Optimized sparse_copy, doesn't create holes for large sequences of zeros in non sparse_files
/// Used when --sparse=auto
#[cfg(any(target_os = "linux", target_os = "android"))]
fn sparse_copy_without_hole<P>(source: P, dest: P) -> std::io::Result<()>
where
P: AsRef<Path>,
{
let src_file = File::open(source)?;
let dst_file = File::create(dest)?;
let dst_fd = dst_file.as_raw_fd();
let size = src_file.metadata()?.size();
if unsafe { libc::ftruncate(dst_fd, size.try_into().unwrap()) } < 0 {
return Err(std::io::Error::last_os_error());
}
let src_fd = src_file.as_raw_fd();
let mut current_offset: isize = 0;
loop {
let result = unsafe { libc::lseek(src_fd, current_offset.try_into().unwrap(), SEEK_DATA) }
.try_into()
.unwrap();
current_offset = result;
let hole: isize =
unsafe { libc::lseek(src_fd, current_offset.try_into().unwrap(), SEEK_HOLE) }
.try_into()
.unwrap();
if result == -1 || hole == -1 {
break;
}
if result <= -2 || hole <= -2 {
return Err(std::io::Error::last_os_error());
}
let len: isize = hole - current_offset;
let mut buf: Vec<u8> = vec![0x0; len as usize];
src_file.read_exact_at(&mut buf, current_offset as u64)?;
unsafe {
libc::pwrite(
dst_fd,
buf.as_ptr() as *const libc::c_void,
len as usize,
current_offset.try_into().unwrap(),
)
};
current_offset = hole;
}
Ok(())
}
/// Perform a sparse copy from one file to another.
/// Creates a holes for large sequences of zeros in non_sparse_files, used for --sparse=always
#[cfg(any(target_os = "linux", target_os = "android"))]
fn sparse_copy<P>(source: P, dest: P) -> std::io::Result<()>
where
P: AsRef<Path>,
{
use std::os::unix::prelude::MetadataExt;
let mut src_file = File::open(source)?;
let dst_file = File::create(dest)?;
let dst_fd = dst_file.as_raw_fd();
@ -97,6 +212,18 @@ where
Ok(())
}
#[cfg(any(target_os = "linux", target_os = "android"))]
/// Checks whether an existing destination is a fifo
fn check_dest_is_fifo(dest: &Path) -> bool {
// If our destination file exists and its a fifo , we do a standard copy .
let file_type = std::fs::metadata(dest);
match file_type {
Ok(f) => f.file_type().is_fifo(),
_ => false,
}
}
/// Copy the contents of the given source FIFO to the given file.
fn copy_fifo_contents<P>(source: P, dest: P) -> std::io::Result<u64>
where
@ -151,35 +278,121 @@ pub(crate) fn copy_on_write(
reflink: OffloadReflinkDebug::Unsupported,
sparse_detection: SparseDebug::No,
};
let result = match (reflink_mode, sparse_mode) {
(ReflinkMode::Never, SparseMode::Always) => {
copy_debug.sparse_detection = SparseDebug::Zeros;
copy_debug.offload = OffloadReflinkDebug::Avoided;
// Default SparseDebug val for SparseMode::Always
copy_debug.reflink = OffloadReflinkDebug::No;
sparse_copy(source, dest)
}
(ReflinkMode::Never, _) => {
copy_debug.sparse_detection = SparseDebug::No;
copy_debug.reflink = OffloadReflinkDebug::No;
std::fs::copy(source, dest).map(|_| ())
}
(ReflinkMode::Auto, SparseMode::Always) => {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_debug.sparse_detection = SparseDebug::Zeros;
copy_debug.reflink = OffloadReflinkDebug::Unsupported;
sparse_copy(source, dest)
}
(ReflinkMode::Auto, _) => {
copy_debug.sparse_detection = SparseDebug::No;
copy_debug.reflink = OffloadReflinkDebug::Unsupported;
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_never_sparse_always(source, dest);
if let Ok((debug, method)) = result {
copy_debug = debug;
copy_method = method;
}
match copy_method {
CopyMethod::FSCopy => std::fs::copy(source, dest).map(|_| ()),
_ => sparse_copy(source, dest),
}
}
}
(ReflinkMode::Never, SparseMode::Never) => {
copy_debug.reflink = OffloadReflinkDebug::No;
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let result = handle_reflink_never_sparse_never(source);
if let Ok(debug) = result {
copy_debug = debug;
}
std::fs::copy(source, dest).map(|_| ())
}
}
(ReflinkMode::Never, SparseMode::Auto) => {
copy_debug.reflink = OffloadReflinkDebug::No;
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_never_sparse_auto(source, dest);
if let Ok((debug, method)) = result {
copy_debug = debug;
copy_method = method;
}
match copy_method {
CopyMethod::SparseCopyWithoutHole => sparse_copy_without_hole(source, dest),
_ => std::fs::copy(source, dest).map(|_| ()),
}
}
}
(ReflinkMode::Auto, SparseMode::Always) => {
copy_debug.sparse_detection = SparseDebug::Zeros; // Default SparseDebug val for
// SparseMode::Always
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_auto_sparse_always(source, dest);
if let Ok((debug, method)) = result {
copy_debug = debug;
copy_method = method;
}
match copy_method {
CopyMethod::FSCopy => clone(source, dest, CloneFallback::FSCopy),
_ => clone(source, dest, CloneFallback::SparseCopy),
}
}
}
(ReflinkMode::Auto, SparseMode::Never) => {
copy_debug.reflink = OffloadReflinkDebug::No;
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Avoided;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let result = handle_reflink_auto_sparse_never(source);
if let Ok(debug) = result {
copy_debug = debug;
}
clone(source, dest, CloneFallback::FSCopy)
}
}
(ReflinkMode::Auto, SparseMode::Auto) => {
if source_is_fifo {
copy_debug.offload = OffloadReflinkDebug::Unsupported;
copy_fifo_contents(source, dest).map(|_| ())
} else {
let mut copy_method = CopyMethod::Default;
let result = handle_reflink_auto_sparse_auto(source, dest);
if let Ok((debug, method)) = result {
copy_debug = debug;
copy_method = method;
}
match copy_method {
CopyMethod::SparseCopyWithoutHole => {
clone(source, dest, CloneFallback::SparseCopyWithoutHole)
}
_ => clone(source, dest, CloneFallback::FSCopy),
}
}
}
(ReflinkMode::Always, SparseMode::Auto) => {
copy_debug.sparse_detection = SparseDebug::No;
copy_debug.reflink = OffloadReflinkDebug::Yes;
@ -193,3 +406,211 @@ pub(crate) fn copy_on_write(
result.context(context)?;
Ok(copy_debug)
}
/// Handles debug results when flags are "--reflink=auto" and "--sparse=always" and specifies what
/// type of copy should be used
fn handle_reflink_auto_sparse_always(
source: &Path,
dest: &Path,
) -> Result<(CopyDebug, CopyMethod), std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::Unsupported,
sparse_detection: SparseDebug::Zeros,
};
let mut copy_method = CopyMethod::Default;
let (data_flag, size, blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
if data_flag || size < 512 {
copy_debug.offload = OffloadReflinkDebug::Avoided;
}
match (sparse_flag, data_flag, blocks) {
(true, true, 0) => {
// Handling funny files with 0 block allocation but has data
// in it
copy_method = CopyMethod::FSCopy;
copy_debug.sparse_detection = SparseDebug::SeekHoleZeros;
}
(false, true, 0) => copy_method = CopyMethod::FSCopy,
(true, false, 0) => copy_debug.sparse_detection = SparseDebug::SeekHole,
(true, true, _) => copy_debug.sparse_detection = SparseDebug::SeekHoleZeros,
(true, false, _) => copy_debug.sparse_detection = SparseDebug::SeekHole,
(_, _, _) => (),
}
if check_dest_is_fifo(dest) {
copy_method = CopyMethod::FSCopy;
}
Ok((copy_debug, copy_method))
}
/// Handles debug results when flags are "--reflink=auto" and "--sparse=auto" and specifies what
/// type of copy should be used
fn handle_reflink_never_sparse_never(source: &Path) -> Result<CopyDebug, std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::No,
sparse_detection: SparseDebug::No,
};
let (data_flag, size, _blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
if sparse_flag {
copy_debug.sparse_detection = SparseDebug::SeekHole;
}
if data_flag || size < 512 {
copy_debug.offload = OffloadReflinkDebug::Avoided;
}
Ok(copy_debug)
}
/// Handles debug results when flags are "--reflink=auto" and "--sparse=never", files will be copied
/// through cloning them with fallback switching to std::fs::copy
fn handle_reflink_auto_sparse_never(source: &Path) -> Result<CopyDebug, std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::No,
sparse_detection: SparseDebug::No,
};
let (data_flag, size, _blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
if sparse_flag {
copy_debug.sparse_detection = SparseDebug::SeekHole;
}
if data_flag || size < 512 {
copy_debug.offload = OffloadReflinkDebug::Avoided;
}
Ok(copy_debug)
}
/// Handles debug results when flags are "--reflink=auto" and "--sparse=auto" and specifies what
/// type of copy should be used
fn handle_reflink_auto_sparse_auto(
source: &Path,
dest: &Path,
) -> Result<(CopyDebug, CopyMethod), std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::Unsupported,
sparse_detection: SparseDebug::No,
};
let mut copy_method = CopyMethod::Default;
let (data_flag, size, blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
if (data_flag && size != 0) || (size > 0 && size < 512) {
copy_debug.offload = OffloadReflinkDebug::Yes;
}
if data_flag && size == 0 {
// Handling /proc/ files
copy_debug.offload = OffloadReflinkDebug::Unsupported;
}
if sparse_flag {
if blocks == 0 && data_flag {
// Handling other "virtual" files
copy_debug.offload = OffloadReflinkDebug::Unsupported;
copy_method = CopyMethod::FSCopy; // Doing a standard copy for the virtual files
} else {
copy_method = CopyMethod::SparseCopyWithoutHole;
} // Since sparse_flag is true, sparse_detection shall be SeekHole for any non virtual
// regular sparse file and the file will be sparsely copied
copy_debug.sparse_detection = SparseDebug::SeekHole;
}
if check_dest_is_fifo(dest) {
copy_method = CopyMethod::FSCopy;
}
Ok((copy_debug, copy_method))
}
/// Handles debug results when flags are "--reflink=never" and "--sparse=auto" and specifies what
/// type of copy should be used
fn handle_reflink_never_sparse_auto(
source: &Path,
dest: &Path,
) -> Result<(CopyDebug, CopyMethod), std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::No,
sparse_detection: SparseDebug::No,
};
let (data_flag, size, blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
let mut copy_method = CopyMethod::Default;
if data_flag || size < 512 {
copy_debug.offload = OffloadReflinkDebug::Avoided;
}
if sparse_flag {
if blocks == 0 && data_flag {
copy_method = CopyMethod::FSCopy; // Handles virtual files which have size > 0 but no
// disk allocation
} else {
copy_method = CopyMethod::SparseCopyWithoutHole; // Handles regular sparse-files
}
copy_debug.sparse_detection = SparseDebug::SeekHole;
}
if check_dest_is_fifo(dest) {
copy_method = CopyMethod::FSCopy;
}
Ok((copy_debug, copy_method))
}
/// Handles debug results when flags are "--reflink=never" and "--sparse=always" and specifies what
/// type of copy should be used
fn handle_reflink_never_sparse_always(
source: &Path,
dest: &Path,
) -> Result<(CopyDebug, CopyMethod), std::io::Error> {
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
reflink: OffloadReflinkDebug::No,
sparse_detection: SparseDebug::Zeros,
};
let mut copy_method = CopyMethod::SparseCopy;
let (data_flag, size, blocks) = check_for_data(source)?;
let sparse_flag = check_sparse_detection(source)?;
if data_flag || size < 512 {
copy_debug.offload = OffloadReflinkDebug::Avoided;
}
match (sparse_flag, data_flag, blocks) {
(true, true, 0) => {
// Handling funny files with 0 block allocation but has data
// in it, e.g. files in /sys and other virtual files
copy_method = CopyMethod::FSCopy;
copy_debug.sparse_detection = SparseDebug::SeekHoleZeros;
}
(false, true, 0) => copy_method = CopyMethod::FSCopy, // Handling data containing zero sized
// files in /proc
(true, false, 0) => copy_debug.sparse_detection = SparseDebug::SeekHole, // Handles files
// with 0 blocks allocated in disk and
(true, true, _) => copy_debug.sparse_detection = SparseDebug::SeekHoleZeros, // Any
// sparse_files with data in it will display SeekHoleZeros
(true, false, _) => {
copy_debug.offload = OffloadReflinkDebug::Unknown;
copy_debug.sparse_detection = SparseDebug::SeekHole;
}
(_, _, _) => (),
}
if check_dest_is_fifo(dest) {
copy_method = CopyMethod::FSCopy;
}
Ok((copy_debug, copy_method))
}

View file

@ -3581,7 +3581,7 @@ fn test_cp_debug_sparse_never() {
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") {
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
@ -3831,6 +3831,642 @@ fn test_acl_preserve() {
assert!(compare_xattrs(&file, &file_target));
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.write("a", "hello");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
if dst_file_metadata.blocks() != src_file_metadata.blocks() {
panic!("File not sparsely copied");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
if dst_file_metadata.blocks() != src_file_metadata.blocks() {
panic!("File not sparsely copied");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
if dst_file_metadata.blocks() != src_file_metadata.blocks() {
panic!("File not sparsely copied");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: SEEK_HOLE")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=auto")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_without_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", "hello".as_bytes());
let filler_bytes = [0_u8; 10000];
at.append_bytes("a", &filler_bytes);
let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: yes, reflink: unsupported, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
if dst_file_metadata.blocks() != src_file_metadata.blocks() {
panic!("File not sparsely copied");
}
let stdout_str = result.stdout_str();
if !stdout_str
.contains("copy offload: unknown, reflink: unsupported, sparse detection: SEEK_HOLE")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.write("a", "hello");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
if dst_file_metadata.blocks() != src_file_metadata.blocks() {
panic!("File not sparsely copied");
}
let stdout_str = result.stdout_str();
if !stdout_str
.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE + zeros")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_without_hole() {
let ts = TestScenario::new(util_name!());
let empty_bytes = [0_u8; 10000];
let at = &ts.fixtures;
at.touch("a");
at.write("a", "hello");
at.append_bytes("a", &empty_bytes);
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 {
panic!("Zero sequenced blocks not removed");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: zeros") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_default_virtual_file() {
use std::os::unix::prelude::MetadataExt;
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
ts.ucmd()
.arg("/sys/kernel/address_bits")
.arg("b")
.succeeds();
let dest_size = std::fs::metadata(at.plus("b"))
.expect("Metadata of copied file cannot be read")
.size();
if dest_size == 0 {
panic!("Copy unsuccessful");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_auto_sparse_always_non_sparse_file_with_long_zero_sequence() {
let ts = TestScenario::new(util_name!());
let buf: Vec<u8> = vec![0; 4096 * 4];
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", &buf);
at.append_bytes("a", "hello".as_bytes());
let result = ts
.ucmd()
.arg("--debug")
.arg("--sparse=always")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 {
panic!("Zero sequenced blocks not removed");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_never_empty_sparse_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let result = ts
.ucmd()
.arg("--debug")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_non_sparse_file_with_long_zero_sequence() {
let ts = TestScenario::new(util_name!());
let buf: Vec<u8> = vec![0; 4096 * 4];
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", &buf);
at.append_bytes("a", "hello".as_bytes());
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
.succeeds();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
if dst_file_metadata.blocks() != dst_file_metadata.blksize() / 512 {
panic!("Zero sequenced blocks not removed");
}
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: zeros") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_always_sparse_virtual_file() {
let ts = TestScenario::new(util_name!());
let result = ts
.ucmd()
.arg("--debug")
.arg("--sparse=always")
.arg("/sys/kernel/address_bits")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains(
"copy offload: avoided, reflink: unsupported, sparse detection: SEEK_HOLE + zeros",
) {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_without_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", "hello".as_bytes());
let filler_bytes = [0_u8; 10000];
at.append_bytes("a", &filler_bytes);
let result = ts
.ucmd()
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("--debug")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_default_sparse_virtual_file() {
let ts = TestScenario::new(util_name!());
let result = ts
.ucmd()
.arg("--debug")
.arg("/sys/kernel/address_bits")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str
.contains("copy offload: unsupported, reflink: unsupported, sparse detection: SEEK_HOLE")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_never_zero_sized_virtual_file() {
let ts = TestScenario::new(util_name!());
let result = ts
.ucmd()
.arg("--debug")
.arg("--sparse=never")
.arg("/proc/version")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_default_zero_sized_virtual_file() {
let ts = TestScenario::new(util_name!());
let result = ts
.ucmd()
.arg("--debug")
.arg("/proc/version")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: unsupported, reflink: unsupported, sparse detection: no")
{
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_without_hole() {
let ts = TestScenario::new(util_name!());
let filler_bytes = [0_u8; 1000];
let at = &ts.fixtures;
at.touch("a");
at.write("a", "hello");
at.append_bytes("a", &filler_bytes);
let result = ts
.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
.succeeds();
let stdout_str = result.stdout_str();
if !stdout_str.contains("copy offload: avoided, reflink: no, sparse detection: no") {
panic!("Failure: stdout was \n{stdout_str}");
}
}
#[test]
fn test_cp_force_remove_destination_attributes_only_with_symlink() {