coreutils_rs/hash/
core.rs1use std::fs::File;
2use std::io::{self, BufRead, BufReader, Read, Write};
3use std::path::Path;
4
5use blake2::Blake2b512;
6use md5::Md5;
7use sha2::{Digest, Sha256};
8
9#[derive(Debug, Clone, Copy)]
11pub enum HashAlgorithm {
12 Sha256,
13 Md5,
14 Blake2b,
15}
16
17impl HashAlgorithm {
18 pub fn name(self) -> &'static str {
19 match self {
20 HashAlgorithm::Sha256 => "SHA256",
21 HashAlgorithm::Md5 => "MD5",
22 HashAlgorithm::Blake2b => "BLAKE2b",
23 }
24 }
25}
26
27pub fn hash_reader<R: Read>(algo: HashAlgorithm, mut reader: R) -> io::Result<String> {
29 let mut buf = vec![0u8; 256 * 1024]; match algo {
32 HashAlgorithm::Sha256 => {
33 let mut hasher = Sha256::new();
34 loop {
35 let n = reader.read(&mut buf)?;
36 if n == 0 {
37 break;
38 }
39 hasher.update(&buf[..n]);
40 }
41 Ok(hex_encode(&hasher.finalize()))
42 }
43 HashAlgorithm::Md5 => {
44 let mut hasher = Md5::new();
45 loop {
46 let n = reader.read(&mut buf)?;
47 if n == 0 {
48 break;
49 }
50 hasher.update(&buf[..n]);
51 }
52 Ok(hex_encode(&hasher.finalize()))
53 }
54 HashAlgorithm::Blake2b => {
55 let mut hasher = Blake2b512::new();
56 loop {
57 let n = reader.read(&mut buf)?;
58 if n == 0 {
59 break;
60 }
61 hasher.update(&buf[..n]);
62 }
63 Ok(hex_encode(&hasher.finalize()))
64 }
65 }
66}
67
68pub fn hash_file(algo: HashAlgorithm, path: &Path) -> io::Result<String> {
70 let file = File::open(path)?;
71 let reader = BufReader::with_capacity(256 * 1024, file);
72 hash_reader(algo, reader)
73}
74
75pub fn hash_stdin(algo: HashAlgorithm) -> io::Result<String> {
77 hash_reader(algo, io::stdin().lock())
78}
79
80pub fn print_hash(
82 out: &mut impl Write,
83 hash: &str,
84 filename: &str,
85 binary: bool,
86) -> io::Result<()> {
87 let mode_char = if binary { '*' } else { ' ' };
88 writeln!(out, "{} {}{}", hash, mode_char, filename)
89}
90
91pub fn print_hash_tag(
93 out: &mut impl Write,
94 algo: HashAlgorithm,
95 hash: &str,
96 filename: &str,
97) -> io::Result<()> {
98 writeln!(out, "{} ({}) = {}", algo.name(), filename, hash)
99}
100
101pub struct CheckOptions {
103 pub quiet: bool,
104 pub status_only: bool,
105 pub strict: bool,
106 pub warn: bool,
107}
108
109pub fn check_file<R: BufRead>(
113 algo: HashAlgorithm,
114 reader: R,
115 opts: &CheckOptions,
116 out: &mut impl Write,
117 err_out: &mut impl Write,
118) -> io::Result<(usize, usize, usize)> {
119 let CheckOptions {
120 quiet,
121 status_only,
122 strict,
123 warn,
124 } = *opts;
125 let mut ok_count = 0;
126 let mut fail_count = 0;
127 let mut format_errors = 0;
128 let mut line_num = 0;
129
130 for line_result in reader.lines() {
131 line_num += 1;
132 let line = line_result?;
133 let line = line.trim_end();
134
135 if line.is_empty() {
136 continue;
137 }
138
139 let (expected_hash, filename) = match parse_check_line(line) {
141 Some(v) => v,
142 None => {
143 format_errors += 1;
144 if warn {
145 writeln!(
146 err_out,
147 "line {}: improperly formatted checksum line",
148 line_num
149 )?;
150 }
151 continue;
152 }
153 };
154
155 let actual = match hash_file(algo, Path::new(filename)) {
157 Ok(h) => h,
158 Err(e) => {
159 fail_count += 1;
160 if !status_only {
161 writeln!(err_out, "{}: FAILED open or read: {}", filename, e)?;
162 }
163 continue;
164 }
165 };
166
167 if actual == expected_hash {
168 ok_count += 1;
169 if !quiet && !status_only {
170 writeln!(out, "{}: OK", filename)?;
171 }
172 } else {
173 fail_count += 1;
174 if !status_only {
175 writeln!(out, "{}: FAILED", filename)?;
176 }
177 }
178 }
179
180 if strict && format_errors > 0 {
181 fail_count += format_errors;
182 }
183
184 Ok((ok_count, fail_count, format_errors))
185}
186
187pub(crate) fn parse_check_line(line: &str) -> Option<(&str, &str)> {
189 if let Some(idx) = line.find(" ") {
191 let hash = &line[..idx];
192 let rest = &line[idx + 2..];
193 return Some((hash, rest));
194 }
195 if let Some(idx) = line.find(" *") {
197 let hash = &line[..idx];
198 let rest = &line[idx + 2..];
199 return Some((hash, rest));
200 }
201 None
202}
203
204pub(crate) fn hex_encode(bytes: &[u8]) -> String {
206 let mut s = String::with_capacity(bytes.len() * 2);
207 for &b in bytes {
208 s.push_str(&format!("{:02x}", b));
209 }
210 s
211}