mirror of
https://github.com/uutils/coreutils
synced 2024-11-05 14:21:32 +00:00
cp: add support of cp --debug
This commit is contained in:
parent
e295cb7acb
commit
f2006a9a6b
5 changed files with 391 additions and 16 deletions
|
@ -229,10 +229,79 @@ pub struct Options {
|
|||
backup_suffix: String,
|
||||
target_dir: Option<PathBuf>,
|
||||
update: UpdateMode,
|
||||
debug: bool,
|
||||
verbose: bool,
|
||||
progress_bar: bool,
|
||||
}
|
||||
|
||||
/// Enum representing various debug states of the offload and reflink actions.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)] // All of them are used on Linux
|
||||
enum OffloadReflinkDebug {
|
||||
Unknown,
|
||||
No,
|
||||
Yes,
|
||||
Avoided,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
/// Enum representing various debug states of the sparse detection.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)] // silent for now until we use them
|
||||
enum SparseDebug {
|
||||
Unknown,
|
||||
No,
|
||||
Zeros,
|
||||
SeekHole,
|
||||
SeekHoleZeros,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
/// Struct that contains the debug state for each action in a file copy operation.
|
||||
#[derive(Debug)]
|
||||
struct CopyDebug {
|
||||
offload: OffloadReflinkDebug,
|
||||
reflink: OffloadReflinkDebug,
|
||||
sparse_detection: SparseDebug,
|
||||
}
|
||||
|
||||
impl OffloadReflinkDebug {
|
||||
fn to_string(&self) -> &'static str {
|
||||
match self {
|
||||
Self::No => "no",
|
||||
Self::Yes => "yes",
|
||||
Self::Avoided => "avoided",
|
||||
Self::Unsupported => "unsupported",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseDebug {
|
||||
fn to_string(&self) -> &'static str {
|
||||
match self {
|
||||
Self::No => "no",
|
||||
Self::Zeros => "zeros",
|
||||
Self::SeekHole => "SEEK_HOLE",
|
||||
Self::SeekHoleZeros => "SEEK_HOLE + zeros",
|
||||
Self::Unsupported => "unsupported",
|
||||
Self::Unknown => "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function prints the debug information of a file copy operation if
|
||||
/// no hard link or symbolic link is required, and data copy is required.
|
||||
/// It prints the debug information of the offload, reflink, and sparse detection actions.
|
||||
fn show_debug(copy_debug: &CopyDebug) {
|
||||
println!(
|
||||
"copy offload: {}, reflink: {}, sparse detection: {}",
|
||||
copy_debug.offload.to_string(),
|
||||
copy_debug.reflink.to_string(),
|
||||
copy_debug.sparse_detection.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
const ABOUT: &str = help_about!("cp.md");
|
||||
const USAGE: &str = help_usage!("cp.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "cp.md");
|
||||
|
@ -269,6 +338,7 @@ mod options {
|
|||
pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
|
||||
pub const SYMBOLIC_LINK: &str = "symbolic-link";
|
||||
pub const TARGET_DIRECTORY: &str = "target-directory";
|
||||
pub const DEBUG: &str = "debug";
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
}
|
||||
|
||||
|
@ -361,6 +431,12 @@ pub fn uu_app() -> Command {
|
|||
.help("remove any trailing slashes from each SOURCE argument")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::DEBUG)
|
||||
.long(options::DEBUG)
|
||||
.help("explain how a file is copied. Implies -v")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::VERBOSE)
|
||||
.short('v')
|
||||
|
@ -831,7 +907,8 @@ impl Options {
|
|||
one_file_system: matches.get_flag(options::ONE_FILE_SYSTEM),
|
||||
parents: matches.get_flag(options::PARENTS),
|
||||
update: update_mode,
|
||||
verbose: matches.get_flag(options::VERBOSE),
|
||||
debug: matches.get_flag(options::DEBUG),
|
||||
verbose: matches.get_flag(options::VERBOSE) || matches.get_flag(options::DEBUG),
|
||||
strip_trailing_slashes: matches.get_flag(options::STRIP_TRAILING_SLASHES),
|
||||
reflink_mode: {
|
||||
if let Some(reflink) = matches.get_one::<String>(options::REFLINK) {
|
||||
|
@ -1745,7 +1822,7 @@ fn copy_helper(
|
|||
} else if source_is_symlink {
|
||||
copy_link(source, dest, symlinked_files)?;
|
||||
} else {
|
||||
copy_on_write(
|
||||
let copy_debug = copy_on_write(
|
||||
source,
|
||||
dest,
|
||||
options.reflink_mode,
|
||||
|
@ -1754,6 +1831,10 @@ fn copy_helper(
|
|||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
|
||||
source_is_fifo,
|
||||
)?;
|
||||
|
||||
if !options.attributes_only && options.debug {
|
||||
show_debug(©_debug);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -13,7 +13,7 @@ use quick_error::ResultExt;
|
|||
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
use crate::{CopyResult, ReflinkMode, SparseMode};
|
||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
||||
|
||||
// From /usr/include/linux/fs.h:
|
||||
// #define FICLONE _IOW(0x94, 9, int)
|
||||
|
@ -145,24 +145,51 @@ pub(crate) fn copy_on_write(
|
|||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
source_is_fifo: bool,
|
||||
) -> CopyResult<()> {
|
||||
) -> CopyResult<CopyDebug> {
|
||||
let mut copy_debug = CopyDebug {
|
||||
offload: OffloadReflinkDebug::Unknown,
|
||||
reflink: OffloadReflinkDebug::Unsupported,
|
||||
sparse_detection: SparseDebug::No,
|
||||
};
|
||||
|
||||
let result = match (reflink_mode, sparse_mode) {
|
||||
(ReflinkMode::Never, SparseMode::Always) => sparse_copy(source, dest),
|
||||
(ReflinkMode::Never, _) => std::fs::copy(source, dest).map(|_| ()),
|
||||
(ReflinkMode::Auto, SparseMode::Always) => sparse_copy(source, dest),
|
||||
(ReflinkMode::Never, SparseMode::Always) => {
|
||||
copy_debug.sparse_detection = SparseDebug::Zeros;
|
||||
copy_debug.offload = OffloadReflinkDebug::Avoided;
|
||||
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_fifo_contents(source, dest).map(|_| ())
|
||||
} else {
|
||||
clone(source, dest, CloneFallback::FSCopy)
|
||||
}
|
||||
}
|
||||
(ReflinkMode::Always, SparseMode::Auto) => clone(source, dest, CloneFallback::Error),
|
||||
(ReflinkMode::Always, SparseMode::Auto) => {
|
||||
copy_debug.sparse_detection = SparseDebug::No;
|
||||
copy_debug.reflink = OffloadReflinkDebug::Yes;
|
||||
|
||||
clone(source, dest, CloneFallback::Error)
|
||||
}
|
||||
(ReflinkMode::Always, _) => {
|
||||
return Err("`--reflink=always` can be used only with --sparse=auto".into())
|
||||
}
|
||||
};
|
||||
result.context(context)?;
|
||||
Ok(())
|
||||
Ok(copy_debug)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::path::Path;
|
|||
|
||||
use quick_error::ResultExt;
|
||||
|
||||
use crate::{CopyResult, ReflinkMode, SparseMode};
|
||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
||||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
///
|
||||
|
@ -24,10 +24,15 @@ pub(crate) fn copy_on_write(
|
|||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
source_is_fifo: bool,
|
||||
) -> CopyResult<()> {
|
||||
) -> CopyResult<CopyDebug> {
|
||||
if sparse_mode != SparseMode::Auto {
|
||||
return Err("--sparse is only supported on linux".to_string().into());
|
||||
}
|
||||
let mut copy_debug = CopyDebug {
|
||||
offload: OffloadReflinkDebug::Unknown,
|
||||
reflink: OffloadReflinkDebug::Unsupported,
|
||||
sparse_detection: SparseDebug::Unsupported,
|
||||
};
|
||||
|
||||
// Extract paths in a form suitable to be passed to a syscall.
|
||||
// The unwrap() is safe because they come from the command-line and so contain non nul
|
||||
|
@ -72,6 +77,7 @@ pub(crate) fn copy_on_write(
|
|||
return Err(format!("failed to clone {source:?} from {dest:?}: {error}").into())
|
||||
}
|
||||
_ => {
|
||||
copy_debug.reflink = OffloadReflinkDebug::Yes;
|
||||
if source_is_fifo {
|
||||
let mut src_file = File::open(source)?;
|
||||
let mut dst_file = File::create(dest)?;
|
||||
|
@ -83,5 +89,5 @@ pub(crate) fn copy_on_write(
|
|||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(copy_debug)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::path::Path;
|
|||
|
||||
use quick_error::ResultExt;
|
||||
|
||||
use crate::{CopyResult, ReflinkMode, SparseMode};
|
||||
use crate::{CopyDebug, CopyResult, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode};
|
||||
|
||||
/// Copies `source` to `dest` for systems without copy-on-write
|
||||
pub(crate) fn copy_on_write(
|
||||
|
@ -17,7 +17,7 @@ pub(crate) fn copy_on_write(
|
|||
reflink_mode: ReflinkMode,
|
||||
sparse_mode: SparseMode,
|
||||
context: &str,
|
||||
) -> CopyResult<()> {
|
||||
) -> CopyResult<CopyDebug> {
|
||||
if reflink_mode != ReflinkMode::Never {
|
||||
return Err("--reflink is only supported on linux and macOS"
|
||||
.to_string()
|
||||
|
@ -26,8 +26,12 @@ pub(crate) fn copy_on_write(
|
|||
if sparse_mode != SparseMode::Auto {
|
||||
return Err("--sparse is only supported on linux".to_string().into());
|
||||
}
|
||||
|
||||
let copy_debug = CopyDebug {
|
||||
offload: OffloadReflinkDebug::Unsupported,
|
||||
reflink: OffloadReflinkDebug::Unsupported,
|
||||
sparse_detection: SparseDebug::Unsupported,
|
||||
};
|
||||
fs::copy(source, dest).context(context)?;
|
||||
|
||||
Ok(())
|
||||
Ok(copy_debug)
|
||||
}
|
||||
|
|
|
@ -2909,3 +2909,260 @@ fn test_cp_archive_on_directory_ending_dot() {
|
|||
ucmd.args(&["-a", "dir1/.", "dir2"]).succeeds();
|
||||
assert!(at.file_exists("dir2/file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_debug_default() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts.ucmd().arg("--debug").arg("a").arg("b").succeeds();
|
||||
|
||||
let stdout_str = result.stdout_str();
|
||||
#[cfg(target_os = "macos")]
|
||||
if !stdout_str
|
||||
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") {
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if !stdout_str
|
||||
.contains("copy offload: unsupported, reflink: unsupported, sparse detection: unsupported")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_debug_multiple_default() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
let dir = "dir";
|
||||
at.touch("a");
|
||||
at.touch("b");
|
||||
at.mkdir(dir);
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("a")
|
||||
.arg("b")
|
||||
.arg(dir)
|
||||
.succeeds();
|
||||
|
||||
let stdout_str = result.stdout_str();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if !stdout_str
|
||||
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
// two files, two occurrences
|
||||
assert_eq!(
|
||||
result
|
||||
.stdout_str()
|
||||
.matches(
|
||||
"copy offload: unknown, reflink: unsupported, sparse detection: unsupported"
|
||||
)
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
// two files, two occurrences
|
||||
assert_eq!(
|
||||
result
|
||||
.stdout_str()
|
||||
.matches("copy offload: unknown, reflink: unsupported, sparse detection: no")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if !stdout_str.contains(
|
||||
"copy offload: unsupported, reflink: unsupported, sparse detection: unsupported",
|
||||
) {
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
// two files, two occurrences
|
||||
assert_eq!(
|
||||
result
|
||||
.stdout_str()
|
||||
.matches("copy offload: unsupported, reflink: unsupported, sparse detection: unsupported")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_debug_sparse_reflink() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("--sparse=always")
|
||||
.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: zeros") {
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_debug_sparse_always() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("--sparse=always")
|
||||
.arg("a")
|
||||
.arg("b")
|
||||
.succeeds();
|
||||
let stdout_str = result.stdout_str();
|
||||
if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_debug_sparse_never() {
|
||||
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: unknown, reflink: unsupported, sparse detection: no") {
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cp_debug_sparse_auto() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("--sparse=auto")
|
||||
.arg("a")
|
||||
.arg("b")
|
||||
.succeeds();
|
||||
let stdout_str = result.stdout_str();
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if !stdout_str
|
||||
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no") {
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
|
||||
fn test_cp_debug_reflink_auto() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("--reflink=auto")
|
||||
.arg("a")
|
||||
.arg("b")
|
||||
.succeeds();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let stdout_str = result.stdout_str();
|
||||
if !stdout_str.contains("copy offload: unknown, reflink: unsupported, sparse detection: no")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let stdout_str = result.stdout_str();
|
||||
if !stdout_str
|
||||
.contains("copy offload: unknown, reflink: unsupported, sparse detection: unsupported")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_cp_debug_sparse_always_reflink_auto() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
at.touch("a");
|
||||
let result = ts
|
||||
.ucmd()
|
||||
.arg("--debug")
|
||||
.arg("--sparse=always")
|
||||
.arg("--reflink=auto")
|
||||
.arg("a")
|
||||
.arg("b")
|
||||
.succeeds();
|
||||
let stdout_str = result.stdout_str();
|
||||
if !stdout_str.contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros")
|
||||
{
|
||||
println!("Failure: stdout was \n{stdout_str}");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue