From 35dd6d97b0b9810b9d1a3b50ec52b1a0c25f9e06 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Fri, 18 Jul 2025 00:08:01 -0600 Subject: [PATCH] clean up code, implement all secure sha algorithms --- .rustfmt.toml | 4 ++ src/hashers.rs | 24 +++++++ src/main.rs | 166 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 148 insertions(+), 46 deletions(-) create mode 100644 .rustfmt.toml create mode 100644 src/hashers.rs diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..382ae76 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,4 @@ +edition = "2021" +tab_spaces = 2 +match_block_trailing_comma = true +#wrap_comments = true \ No newline at end of file diff --git a/src/hashers.rs b/src/hashers.rs new file mode 100644 index 0000000..f516cbc --- /dev/null +++ b/src/hashers.rs @@ -0,0 +1,24 @@ +use sha2::{Digest, Sha256, Sha384, Sha512}; +use std::fs::File; +use std::io; + +pub fn hash_sha256(mut file: File) -> String { + let mut hasher = Sha256::new(); + _ = io::copy(&mut file, &mut hasher); + + return format!("{:x}", hasher.finalize()); +} + +pub fn hash_sha384(mut file: File) -> String { + let mut hasher = Sha384::new(); + _ = io::copy(&mut file, &mut hasher); + + return format!("{:x}", hasher.finalize()); +} + +pub fn hash_sha512(mut file: File) -> String { + let mut hasher = Sha512::new(); + _ = io::copy(&mut file, &mut hasher); + + return format!("{:x}", hasher.finalize()); +} diff --git a/src/main.rs b/src/main.rs index 20b9e36..dcb1dec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,44 +1,114 @@ -use sha2::{Sha256, Digest}; -use std::ops::Deref; -use std::path::PathBuf; -use std::thread; -use std::thread::available_parallelism; -use std::fs::File; -use std::io; use clap::Parser; -use std::sync::Arc; -use std::sync::Mutex; use std::collections::VecDeque; -use std::fs; +use std::fs::{self, File}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::thread::{self, available_parallelism}; + +mod hashers; #[derive(Parser)] #[command(name = "psha")] #[command(version = option_env!("CARGO_PKG_VERSION"))] +#[command(about = "A parallel checksum tool for various algorithms")] #[command(long_about = None)] struct Args { #[arg( - trailing_var_arg = true, + short, + long, + help = "Use at most this number of threads, 0 means as many as there are processor cores", + default_value = "0" )] + threads: usize, + + #[arg( + short, + long, + help = "Enable debug output (thread info, algorithm used/detected)" + )] + debug: bool, + + #[arg( + short, + long, + help = "Specify an algorithm for hashing", + default_value = "sha256", + value_parser = clap::builder::PossibleValuesParser::new(["sha256", "sha512"]) + )] + algorithm: String, + + #[arg( + short = 'f', + long, + help = "Show canonicalized (relative paths converted to absolute) file paths" + )] + canonicalize: bool, + + #[arg( + short = 'c', + long, + help = "Read checksums from the file(s) and verify them" + )] + check: Vec, + + #[arg( + short = 'q', + long, + help = "(only used with -c) Only print checksums that fail; do not print OK for files that are successful" + )] + failures_only: bool, + + #[arg( + short = 'Q', + long, + help = "(only used with -c) Suppress all output to stdout, including failures" + )] + quiet: bool, + + #[arg(trailing_var_arg = true)] files: Vec, } -fn checksum(thread_id: usize, filenames: Arc>>) -> Result<(), String> { - let running = true; - while running { - let filename = match filenames.lock().unwrap().pop_front() { - Some(f) => f, - None => return Ok(()) - }; - let mut hasher = Sha256::new(); - let mut file = File::open(&filename).unwrap(); +struct ThreadInfo { + debug: Arc, + failures_only: Arc, + quiet: Arc, + thread_id: usize, + filenames: Arc>>, + algorithm: Arc, +} - io::copy(&mut file, &mut hasher); - let hash = hasher.finalize(); - // println!("thread {} result: {:x}\t{}", thread_id, hash, filename.as_path().display()); - println!("{:x}\t{}", hash, filename.as_path().display()); +fn hash(info: ThreadInfo) -> Result<(), String> { + loop { + let filename = match info.filenames.lock().unwrap().pop_front() { + Some(f) => f, + None => break, + }; + + if !*info.quiet && *info.debug { + eprintln!( + "thread {} is hashing file '{}'", + info.thread_id, + filename.as_path().display() + ); + } + + let file = File::open(&filename).unwrap(); + let res = match &*info.algorithm.as_str() { + "sha256" => hashers::hash_sha256(file), + "sha384" => hashers::hash_sha384(file), + "sha512" => hashers::hash_sha512(file), + _ => panic!("Somehow did not pass a supported algorithm"), + }; + + if !*info.quiet { + println!("{} {}", res, filename.as_path().display()); + } } - println!("thread {} has ran out of work", thread_id); + if !*info.quiet && *info.debug { + eprintln!("thread {} has ran out of work", info.thread_id); + } Ok(()) } @@ -49,33 +119,37 @@ fn main() { let mut buffer = VecDeque::new(); let mut handles = vec![]; for file in args.files { - // match fs::canonicalize(file.as_path()) { - // Ok(p) => buffer.push_back(p), - // Err(e) => panic!("unable to canonicalize {}: {}", file.as_path().display(), e) - // }; - buffer.push_back(file); + if args.canonicalize { + match fs::canonicalize(file.as_path()) { + Ok(p) => buffer.push_back(p), + Err(e) => panic!("unable to canonicalize {}: {}", file.as_path().display(), e), + }; + } else { + buffer.push_back(file); + } } - let arc_buf = Arc::new(Mutex::new(buffer)); - // let mut handles = vec![]; - // let chunks = Arc::new(Mutex::new(args.files.chunks(args.files.len()/ cpus).clone())); -/* let threads = chunks.into_iter().map(|chunk| { - thread::spawn(move || checksum(0, chunk.to_vec())) - }).collect::>(); */ -/* threads.into_iter().for_each(|i| { i.join().unwrap(); }); - // for file in args.files { - // if let Err(e) = checksum(0, file) { */ - // println!("{}", e); - // break; - // }; - // } + let arc_buf = Arc::new(Mutex::new(buffer)); for i in 0..cpus { - let safe = Arc::clone(&arc_buf); - handles.push(thread::spawn(move || checksum(i, safe))) + let safe_buf = Arc::clone(&arc_buf); + let safe_alg = Arc::new(args.algorithm.clone()); + handles.push(thread::spawn(move || { + hash(ThreadInfo { + debug: Arc::new(args.debug), + failures_only: Arc::new(args.failures_only), + quiet: Arc::new(args.quiet), + thread_id: i, + filenames: safe_buf, + algorithm: safe_alg, + }) + })) } for handle in handles { - handle.join().unwrap(); + match handle.join().unwrap() { + Err(e) => panic!("{}", e), + Ok(_) => (), + } } // println!("{}", available_parallelism().unwrap().get());