diff --git a/src/cmp.rs b/src/cmp.rs index cdd0b5b..7dea4a3 100644 --- a/src/cmp.rs +++ b/src/cmp.rs @@ -17,9 +17,6 @@ use std::os::fd::{AsRawFd, FromRawFd}; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -#[cfg(target_os = "windows")] -use std::os::windows::fs::MetadataExt; - /// for --bytes, so really large number limits can be expressed, like 1Y. pub type BytesLimitU64 = u64; // ignore initial is currently limited to u64, as take(skip) is used. @@ -296,6 +293,20 @@ fn prepare_reader( Ok(reader) } +/// Returns the size of `path` when it is a regular file, and `None` for standard input +/// (`-`), pipes, devices and paths that cannot be stat'ed. +fn regular_file_size(path: &OsString) -> Option { + if path == "-" { + return None; + } + let meta = fs::metadata(path).ok()?; + if !meta.is_file() { + return None; + } + + Some(meta.len()) +} + #[derive(Debug)] pub enum Cmp { Equal, @@ -308,24 +319,25 @@ pub fn cmp(params: &Params) -> Result { let mut offset_width = params.max_bytes.unwrap_or(BytesLimitU64::MAX); - if let (Ok(a_meta), Ok(b_meta)) = (fs::metadata(¶ms.from), fs::metadata(¶ms.to)) { - #[cfg(not(target_os = "windows"))] - let (a_size, b_size) = (a_meta.len(), b_meta.len()); - - #[cfg(target_os = "windows")] - let (a_size, b_size) = (a_meta.file_size(), b_meta.file_size()); + let a_size = regular_file_size(¶ms.from); + let b_size = regular_file_size(¶ms.to); - // If the files have different sizes, we already know they are not identical. If we have not - // been asked to show even the first difference, we can quit early. + // If the files have different sizes, we already know they are not identical. If we have not + // been asked to show even the first difference, we can quit early. + if let (Some(a_size), Some(b_size)) = (a_size, b_size) { if params.quiet && a_size != b_size { return Ok(Cmp::Different); } + } - // The offsets count from the end of the skipped bytes. - let a_left = a_size.saturating_sub(params.skip_a.unwrap_or(0)); - let b_left = b_size.saturating_sub(params.skip_b.unwrap_or(0)); - let smaller = cmp::min(a_left, b_left) as BytesLimitU64; - offset_width = cmp::min(smaller, offset_width); + // Like GNU cmp, size the offset column from every operand that is a regular file, so that + // standard input or a pipe on one side does not widen it to the u64::MAX width. The offsets + // count from the end of the skipped bytes. + for (size, skip) in [(a_size, params.skip_a), (b_size, params.skip_b)] { + if let Some(size) = size { + let left = size.saturating_sub(skip.unwrap_or(0)) as BytesLimitU64; + offset_width = cmp::min(left, offset_width); + } } let offset_width = 1 + offset_width.checked_ilog10().unwrap_or(1) as usize; diff --git a/tests/by-util/test_cmp.rs b/tests/by-util/test_cmp.rs index 020a941..b64b3fb 100644 --- a/tests/by-util/test_cmp.rs +++ b/tests/by-util/test_cmp.rs @@ -55,6 +55,30 @@ fn cmp_verbose_offset_width_after_skip() -> Result<(), Box Result<(), Box> { + let tmp_dir = tempdir()?; + let b_path = tmp_dir.path().join("b"); + File::create(&b_path)?.write_all(b"abcdeXghiY")?; + + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.env("LC_ALL", "C"); + cmd.arg("cmp").arg("-l").arg("-").arg(&b_path); + cmd.write_stdin("abcdefghij"); + cmd.assert() + .code(predicate::eq(1)) + .stdout(predicate::eq(" 6 146 130\n10 152 131\n")); + + let mut cmd = cargo_bin_cmd!("diffutils"); + cmd.env("LC_ALL", "C"); + cmd.arg("cmp").arg("-l").arg(&b_path).arg("-"); + cmd.write_stdin("abcdefghij"); + cmd.assert() + .code(predicate::eq(1)) + .stdout(predicate::eq(" 6 130 146\n10 131 152\n")); + Ok(()) +} + #[test] fn cmp_incompatible_params() -> Result<(), Box> { let mut cmd = cargo_bin_cmd!("diffutils");