From 621eff415c0342f1d6e6af9c059513ea071a1ae4 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Sun, 20 Jul 2025 14:44:34 -0600 Subject: [PATCH] implement verify functions, fail counters --- src/main.rs | 202 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 166 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index a2dba18..0c2eca6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ use clap::Parser; -use std::collections::VecDeque; -use std::fs::{self, File}; +use std::collections::{HashMap, VecDeque}; +use std::fs::{self, read_to_string, File}; use std::path::PathBuf; +use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Mutex}; -use std::thread::{self, available_parallelism}; +use std::thread::{self, available_parallelism, JoinHandle}; mod common; mod hashers; @@ -85,12 +86,15 @@ struct Args { } struct ThreadInfo { - debug: Arc, - failures_only: Arc, - quiet: Arc, + debug: bool, + quiet: bool, + print_failures: bool, thread_id: usize, filenames: Arc>>, algorithm: Arc, + hash_map: Option>>>, + file_errors: Arc, + hash_errors: Arc, } fn hash(info: ThreadInfo) -> Result<(), String> { @@ -100,7 +104,7 @@ fn hash(info: ThreadInfo) -> Result<(), String> { None => break, }; - if !*info.quiet && *info.debug { + if !info.quiet && info.debug { common::debug(format!( "thread {} is hashing file '{}'", info.thread_id, @@ -110,12 +114,14 @@ fn hash(info: ThreadInfo) -> Result<(), String> { if filename.is_dir() { common::error(format!("{}: Is a directory", filename.as_path().display())); + info.file_errors.fetch_add(1, Ordering::SeqCst); continue; } let file = match File::open(&filename) { Err(e) => { common::error(format!("{}: {}", filename.as_path().display(), e)); + info.file_errors.fetch_add(1, Ordering::SeqCst); continue; }, Ok(f) => f, @@ -138,33 +144,135 @@ fn hash(info: ThreadInfo) -> Result<(), String> { _ => panic!("Somehow did not pass a supported algorithm"), }; - if !*info.quiet { - println!("{} {}", res, filename.as_path().display()); + match &info.hash_map { + Some(h) => { + if h.lock().unwrap()[&filename] == res && !info.quiet { + println!("{}: OK", filename.as_path().display()); + } else { + println!("{}: FAILED", filename.as_path().display()); + info.hash_errors.fetch_add(1, Ordering::SeqCst); + } + }, + None => { + if !info.quiet { + println!("{} {}", res, filename.as_path().display()); + } + }, } } - if !*info.quiet && *info.debug { + if !info.quiet && info.debug { common::debug(format!("thread {} has ran out of work", info.thread_id)); } Ok(()) } -fn main() { - let args = Args::parse(); - let mut buffer = VecDeque::new(); +fn verify( + cpus: usize, + algorithm: String, + debug: bool, + quiet: bool, + print_failures: bool, + checksum_files: Vec, +) -> ( + Vec>>, + Arc, + Arc, +) { let mut handles = vec![]; - for file in args.files { - 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 mut hash_map: HashMap = HashMap::new(); + let mut buffer = VecDeque::new(); + for file in checksum_files { + match read_to_string(&file) { + Err(e) => { + common::error(format!("{}: {}", file.as_path().display(), e)); + continue; + }, + Ok(f) => { + for line in f.lines() { + let split: Vec = line.split_whitespace().map(|x| x.to_string()).collect(); + // println!("{}, {}", split.size_hint().0, split.size_hint().1) + match split.len() { + 2 => { + hash_map.insert(PathBuf::from(split[1].clone()), split[0].clone()); + buffer.push_back(PathBuf::from(split[1].clone())); + }, + _ => common::error(format!("malformed line: {}", line)), + } + } + }, + }; } + let arc_fe = Arc::new(AtomicI32::new(0)); + let arc_he = Arc::new(AtomicI32::new(0)); + let arc_buf = Arc::new(Mutex::new(buffer)); + let arc_hash = Arc::new(Mutex::new(hash_map)); + for i in 0..cpus { + let safe_fe = Arc::clone(&arc_fe); + let safe_he = Arc::clone(&arc_he); + let safe_buf = Arc::clone(&arc_buf); + let safe_alg = Arc::new(algorithm.clone()); + let safe_hash = Arc::clone(&arc_hash); + handles.push(thread::spawn(move || { + hash(ThreadInfo { + debug, + quiet, + print_failures, + thread_id: i, + filenames: safe_buf, + algorithm: safe_alg, + hash_map: Some(safe_hash), + file_errors: safe_fe, + hash_errors: safe_he, + }) + })) + } + + return (handles, arc_fe, arc_he); +} + +fn generate( + cpus: usize, + buffer: VecDeque, + algorithm: String, + debug: bool, + quiet: bool, +) -> ( + Vec>>, + Arc, + Arc, +) { + let mut handles = vec![]; + let arc_fe = Arc::new(AtomicI32::new(0)); + let arc_he = Arc::new(AtomicI32::new(0)); + let arc_buf = Arc::new(Mutex::new(buffer)); + for i in 0..cpus { + let safe_fe = Arc::clone(&arc_fe); + let safe_he = Arc::clone(&arc_he); + let safe_buf = Arc::clone(&arc_buf); + let safe_alg = Arc::new(algorithm.clone()); + handles.push(thread::spawn(move || { + hash(ThreadInfo { + debug, + quiet, + print_failures: false, + thread_id: i, + filenames: safe_buf, + algorithm: safe_alg, + hash_map: None, + file_errors: safe_fe, + hash_errors: safe_he, + }) + })) + } + + return (handles, arc_fe, arc_he); +} + +fn main() { + let args = Args::parse(); let cpus = match args.threads { 0 => available_parallelism().unwrap().get(), _ => args.threads, @@ -184,20 +292,32 @@ fn main() { )); } - let arc_buf = Arc::new(Mutex::new(buffer)); - for i in 0..cpus { - 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, - }) - })) + let handles; + let arc_fe; + let arc_he; + if &args.check.len() >= &1 { + (handles, arc_fe, arc_he) = verify( + cpus, + args.algorithm, + args.debug, + args.quiet, + args.failures_only, + args.check, + ); + } else { + let mut buffer = VecDeque::new(); + for file in args.files { + 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); + } + } + + (handles, arc_fe, arc_he) = generate(cpus, buffer, args.algorithm, args.debug, args.quiet); } for handle in handles { @@ -206,4 +326,14 @@ fn main() { Ok(_) => (), } } + + let fe = arc_fe.load(Ordering::SeqCst); + let he = arc_he.load(Ordering::SeqCst); + if fe != 0 { + common::warning(format!("{} listed files could not be read", fe)); + } + + if he != 0 { + common::warning(format!("{} computed checksums did NOT match", he)); + } }