use clap::{Arg, Command};
use color_eyre::Result;
use color_eyre::eyre::eyre;
use sha2::{Sha256, Digest};
use std::fs::File;
use std::io::{self, BufRead, Read};
pub struct Sha256sumOptions {
pub files: Vec<String>,
pub check: bool,
pub binary: bool,
pub zero: bool,
pub tag: bool,
}
pub fn parse_options(matches: &clap::ArgMatches) -> Result<Sha256sumOptions> {
let files: Vec<String> = matches.get_many::<String>("files")
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
let check = matches.get_flag("check");
let binary = matches.get_flag("binary");
let zero = matches.get_flag("zero");
let tag = matches.get_flag("tag");
Ok(Sha256sumOptions {
files,
check,
binary,
zero,
tag,
})
}
pub fn command() -> Command {
Command::new("sha256sum")
.about("Compute and check SHA256 message digest")
.arg(Arg::new("check")
.short('c')
.long("check")
.help("Read SHA256 sums from the FILEs and check them")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("binary")
.short('b')
.long("binary")
.help("Read in binary mode")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("text")
.short('t')
.long("text")
.help("Read in text mode (default)")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("zero")
.short('z')
.long("zero")
.help("End each output line with NUL, not newline")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("tag")
.long("tag")
.help("Create a BSD-style checksum")
.action(clap::ArgAction::SetTrue))
.arg(Arg::new("files")
.num_args(0..)
.help("Files to process (if none, read from stdin)"))
}
fn compute_sha256(file_path: Option<&str>) -> Result<String> {
let mut hasher = Sha256::new();
if let Some(path) = file_path {
let mut file = File::open(path)
.map_err(|e| eyre!("sha256sum: {}: {}", path, e))?;
let mut buffer = [0; 8192];
loop {
let bytes_read = file.read(&mut buffer)
.map_err(|e| eyre!("sha256sum: error reading {}: {}", path, e))?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
} else {
let mut buffer = [0; 8192];
loop {
let bytes_read = io::stdin().read(&mut buffer)
.map_err(|e| eyre!("sha256sum: error reading stdin: {}", e))?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
}
let result = hasher.finalize();
Ok(format!("{:x}", result))
}
fn format_output(sha256: &str, file_path: Option<&str>, options: &Sha256sumOptions) {
let delimiter = if options.zero { "\0" } else { "\n" };
if options.tag {
let name = file_path.unwrap_or("stdin");
print!("SHA256 ({}) = {}", name, sha256);
} else {
let mode_char = if options.binary { "*" } else { " " };
if let Some(path) = file_path {
print!("{}{} {}", sha256, mode_char, path);
} else {
print!("{}", sha256);
}
}
print!("{}", delimiter);
}
fn check_checksums(file_path: &str) -> Result<()> {
let file = File::open(file_path)
.map_err(|e| eyre!("sha256sum: {}: {}", file_path, e))?;
let reader = io::BufReader::new(file);
for line_result in reader.lines() {
let line = line_result
.map_err(|e| eyre!("sha256sum: error reading {}: {}", file_path, e))?;
let parts: Vec<&str> = line.splitn(2, ' ').collect();
if parts.len() != 2 {
eprintln!("sha256sum: {}: improperly formatted SHA256 checksum line", file_path);
continue;
}
let expected_sha256 = parts[0];
let filename = parts[1];
match compute_sha256(Some(filename)) {
Ok(actual_sha256) => {
if actual_sha256 == expected_sha256 {
println!("{}: OK", filename);
} else {
println!("{}: FAILED", filename);
}
}
Err(e) => {
eprintln!("sha256sum: {}", e);
}
}
}
Ok(())
}
fn compute_checksums(files: &[String], options: &Sha256sumOptions) -> Result<()> {
if files.is_empty() {
let sha256 = compute_sha256(None)?;
format_output(&sha256, None, options);
} else {
for file_path in files {
let sha256 = compute_sha256(Some(file_path))?;
format_output(&sha256, Some(file_path), options);
}
}
Ok(())
}
pub fn run(options: Sha256sumOptions) -> Result<()> {
if options.check {
for file_path in &options.files {
check_checksums(file_path)?;
}
} else {
compute_checksums(&options.files, &options)?;
}
Ok(())
}