add more compact output to bytediff

This commit is contained in:
xenofem 2025-02-26 02:40:21 -05:00
parent a01fdc7de7
commit 9e62430e2d

View file

@ -1,7 +1,53 @@
use std::fs::File; use std::fs::File;
use std::io::{prelude::*, BufReader}; use std::io::{prelude::*, BufReader};
use std::path::PathBuf;
use anyhow::{anyhow, Context, Result}; use anyhow::{Context, Result};
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(author, version)]
/// diff binary files
///
/// bytediff checks whether binary files match, and prints out
/// a list of differences at the byte level if they don't.
struct Cli {
/// Print out a full hexdump of differing bytes, not just a summary of differences
#[arg(short, long)]
detailed: bool,
/// First file to diff
#[arg(required = true)]
file1: PathBuf,
/// Second file to diff
#[arg(required = true)]
file2: PathBuf,
}
#[derive(PartialEq, Eq)]
enum DiffState {
Same,
DifferNonZero,
DifferAZero,
DifferBZero,
}
fn is_byte_vec_zero(v: &[u8]) -> bool {
v.iter().all(|c| *c == 0u8)
}
fn compare_byte_vecs(a: &[u8], b: &[u8]) -> DiffState {
if a == b {
DiffState::Same
} else if is_byte_vec_zero(a) {
DiffState::DifferAZero
} else if is_byte_vec_zero(b) {
DiffState::DifferBZero
} else {
DiffState::DifferNonZero
}
}
struct ZipLonger<A: Iterator, B: Iterator> { struct ZipLonger<A: Iterator, B: Iterator> {
a: A, a: A,
@ -83,53 +129,89 @@ fn color_byte_display(this: &[u8], that: &[u8], color: u8) -> String {
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let mut args = std::env::args(); let cli = Cli::parse();
let name = args
.next() let a = cli.file1;
.ok_or_else(|| anyhow!("Missing executable name in args"))?; let b = cli.file2;
let (a, b) = match (args.next(), args.next()) {
(Some(a), Some(b)) => (a, b),
_ => return Err(anyhow!("Usage: {} file file", name)),
};
let a_bytes = BufReader::new( let a_bytes = BufReader::new(
File::open(a.clone()).with_context(|| format!("Failed to open input file {}", a))?, File::open(a.clone())
.with_context(|| format!("Failed to open input file {}", a.display()))?,
) )
.bytes(); .bytes();
let b_bytes = BufReader::new( let b_bytes = BufReader::new(
File::open(b.clone()).with_context(|| format!("Failed to open input file {}", b))?, File::open(b.clone())
.with_context(|| format!("Failed to open input file {}", b.display()))?,
) )
.bytes(); .bytes();
let mut in_differing_region = false; let mut diff_state = DiffState::Same;
let mut region_start: usize = 0;
for (i, (rva, rvb)) in for (i, (rva, rvb)) in
ZipLonger::new(Chunks::new(a_bytes, 16), Chunks::new(b_bytes, 16)).enumerate() ZipLonger::new(Chunks::new(a_bytes, 16), Chunks::new(b_bytes, 16)).enumerate()
{ {
let va: Vec<u8> = rva let va: Vec<u8> = rva
.transpose() .transpose()
.with_context(|| format!("Error reading from input file {}", a))? .with_context(|| format!("Error reading from input file {}", a.display()))?
.unwrap_or_default(); .unwrap_or_default();
let vb: Vec<u8> = rvb let vb: Vec<u8> = rvb
.transpose() .transpose()
.with_context(|| format!("Error reading from input file {}", b))? .with_context(|| format!("Error reading from input file {}", b.display()))?
.unwrap_or_default(); .unwrap_or_default();
if va == vb { let new_diff_state = compare_byte_vecs(&va, &vb);
if in_differing_region {
in_differing_region = false;
println!("---");
}
continue;
}
in_differing_region = true;
println!( if cli.detailed {
"{0:08x}: {1}\n{0:08x}: {2}", if new_diff_state == DiffState::Same {
i * 16, if diff_state != DiffState::Same {
color_byte_display(&va, &vb, 31), println!("---");
color_byte_display(&vb, &va, 32), }
); } else {
println!(
"{0:08x}: {1}\n{0:08x}: {2}",
i * 16,
color_byte_display(&va, &vb, 31),
color_byte_display(&vb, &va, 32),
);
}
} else {
if new_diff_state != diff_state {
if diff_state != DiffState::Same {
print!("{0:08x} to {1:08x}: ", region_start, i * 16);
}
match diff_state {
DiffState::Same => (),
DiffState::DifferAZero => {
println!("First file is zero bytes");
}
DiffState::DifferBZero => {
println!("Second file is zero bytes");
}
DiffState::DifferNonZero => {
println!("Files differ");
}
}
region_start = i * 16;
}
}
diff_state = new_diff_state;
}
match diff_state {
DiffState::Same => (),
DiffState::DifferAZero => {
println!("First file is zero bytes from {0:08x} to end", region_start);
}
DiffState::DifferBZero => {
println!(
"Second file is zero bytes from {0:08x} to end",
region_start
);
}
DiffState::DifferNonZero => {
println!("Files differ from {0:08x} to end", region_start);
}
} }
Ok(()) Ok(())