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
44 changes: 28 additions & 16 deletions src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<u64> {
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,
Expand All @@ -308,24 +319,25 @@ pub fn cmp(params: &Params) -> Result<Cmp, String> {

let mut offset_width = params.max_bytes.unwrap_or(BytesLimitU64::MAX);

if let (Ok(a_meta), Ok(b_meta)) = (fs::metadata(&params.from), fs::metadata(&params.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(&params.from);
let b_size = regular_file_size(&params.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;
Expand Down
24 changes: 24 additions & 0 deletions tests/by-util/test_cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ fn cmp_verbose_offset_width_after_skip() -> Result<(), Box<dyn std::error::Error
Ok(())
}

#[test]
fn cmp_verbose_offset_width_stdin() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
let mut cmd = cargo_bin_cmd!("diffutils");
Expand Down
Loading