checksum: Adapt checksum computation to hashsum

This commit is contained in:
Dorian Peron
2025-11-19 13:37:14 +01:00
parent 3849d91d69
commit c97ce1bb8e
5 changed files with 165 additions and 204 deletions
+2 -38
View File
@@ -10,7 +10,7 @@ use clap::{Arg, ArgAction, Command};
use std::ffi::{OsStr, OsString};
use std::iter;
use uucore::checksum::compute::{
ChecksumComputeOptions, DigestFormat, OutputFormat, ReadingMode, perform_checksum_computation,
ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation,
};
use uucore::checksum::validate::{
ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation,
@@ -84,43 +84,6 @@ fn handle_tag_text_binary_flags<S: AsRef<OsStr>>(
Ok((tag, binary))
}
/// Use already-processed arguments to decide the output format.
fn figure_out_output_format(
algo: SizedAlgoKind,
tag: bool,
binary: bool,
raw: bool,
base64: bool,
) -> OutputFormat {
// Raw output format takes precedence over anything else.
if raw {
return OutputFormat::Raw;
}
// Then, if the algo is legacy, takes precedence over the rest
if algo.is_legacy() {
return OutputFormat::Legacy;
}
let digest_format = if base64 {
DigestFormat::Base64
} else {
DigestFormat::Hexadecimal
};
// After that, decide between tagged and untagged output
if tag {
OutputFormat::Tagged(digest_format)
} else {
let reading_mode = if binary {
ReadingMode::Binary
} else {
ReadingMode::Text
};
OutputFormat::Untagged(digest_format, reading_mode)
}
}
/// Sanitize the `--length` argument depending on `--algorithm` and `--length`.
fn maybe_sanitize_length(
algo_cli: Option<AlgoKind>,
@@ -222,6 +185,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
algo_kind: algo,
output_format,
line_ending,
no_names: false,
};
perform_checksum_computation(opts, files)?;
+1 -1
View File
@@ -19,7 +19,7 @@ path = "src/hashsum.rs"
[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = ["checksum", "sum"] }
uucore = { workspace = true, features = ["checksum", "encoding", "sum"] }
fluent = { workspace = true }
[[bin]]
+32 -122
View File
@@ -5,47 +5,26 @@
// spell-checker:ignore (ToDO) algo, algoname, bitlen, regexes, nread, nonames
use clap::ArgAction;
use clap::builder::ValueParser;
use clap::value_parser;
use clap::{Arg, ArgMatches, Command};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{BufReader, Read, stdin};
use std::iter;
use std::num::ParseIntError;
use std::path::Path;
use clap::builder::ValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command, value_parser};
use uucore::checksum::compute::{
ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation,
};
use uucore::checksum::validate::{
ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation,
};
use uucore::checksum::{
AlgoKind, ChecksumError, SizedAlgoKind, calculate_blake2b_length, digest_reader,
escape_filename,
};
use uucore::error::{UResult, strip_errno};
use uucore::sum::Digest;
use uucore::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, calculate_blake2b_length};
use uucore::error::UResult;
use uucore::line_ending::LineEnding;
use uucore::{format_usage, translate};
const NAME: &str = "hashsum";
// Using the same read buffer size as GNU
const READ_BUFFER_SIZE: usize = 32 * 1024;
struct Options<'a> {
algo: SizedAlgoKind,
digest: Box<dyn Digest + 'static>,
binary: bool,
binary_name: &'a str,
//check: bool,
tag: bool,
nonames: bool,
//status: bool,
//quiet: bool,
//strict: bool,
//warn: bool,
zero: bool,
//ignore_missing: bool,
}
/// Creates a hasher instance based on the command-line flags.
///
@@ -186,9 +165,9 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
};
let check = matches.get_flag("check");
let status = matches.get_flag("status");
let quiet = matches.get_flag("quiet") || status;
let quiet = matches.get_flag("quiet");
let strict = matches.get_flag("strict");
let warn = matches.get_flag("warn") && !status;
let warn = matches.get_flag("warn");
let ignore_missing = matches.get_flag("ignore-missing");
if ignore_missing && !check {
@@ -232,33 +211,36 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
return Err(ChecksumError::StrictNotCheck.into());
}
let nonames = *matches
let no_names = *matches
.try_get_one("no-names")
.unwrap_or(None)
.unwrap_or(&false);
let zero = matches.get_flag("zero");
let line_ending = LineEnding::from_zero_flag(matches.get_flag("zero"));
let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
let opts = Options {
algo,
digest: algo.create_digest(),
binary,
binary_name: &binary_name,
tag: matches.get_flag("tag"),
nonames,
//status,
//quiet,
//warn,
zero,
//ignore_missing,
let opts = ChecksumComputeOptions {
algo_kind: algo,
output_format: figure_out_output_format(
algo,
matches.get_flag(options::TAG),
binary,
/* raw */ false,
/* base64: */ false,
),
line_ending,
no_names,
};
let files = matches.get_many::<OsString>(options::FILE).map_or_else(
// No files given, read from stdin.
|| Box::new(iter::once(OsStr::new("-"))) as Box<dyn Iterator<Item = &OsStr>>,
// At least one file given, read from them.
|files| Box::new(files.map(OsStr::new)) as Box<dyn Iterator<Item = &OsStr>>,
);
// Show the hashsum of the input
match matches.get_many::<OsString>(options::FILE) {
Some(files) => hashsum(opts, files.map(|f| f.as_os_str())),
None => hashsum(opts, iter::once(OsStr::new("-"))),
}
perform_checksum_computation(opts, files)
}
mod options {
@@ -489,75 +471,3 @@ fn uu_app(binary_name: &str) -> (Command, bool) {
(command, is_hashsum_bin)
}
#[allow(clippy::cognitive_complexity)]
fn hashsum<'a, I>(mut options: Options, files: I) -> UResult<()>
where
I: Iterator<Item = &'a OsStr>,
{
let binary_marker = if options.binary { "*" } else { " " };
let mut err_found = None;
for filename in files {
let filename = Path::new(filename);
let mut file = BufReader::with_capacity(
READ_BUFFER_SIZE,
if filename == OsStr::new("-") {
Box::new(stdin()) as Box<dyn Read>
} else {
let file_buf = match File::open(filename) {
Ok(f) => f,
Err(e) => {
eprintln!(
"{}: {}: {}",
options.binary_name,
filename.to_string_lossy(),
strip_errno(&e)
);
err_found = Some(ChecksumError::Io(e));
continue;
}
};
Box::new(file_buf) as Box<dyn Read>
},
);
let sum = match digest_reader(
&mut options.digest,
&mut file,
options.binary,
options.algo.bitlen(),
) {
Ok((sum, _)) => sum,
Err(e) => {
eprintln!(
"{}: {}: {}",
options.binary_name,
filename.to_string_lossy(),
strip_errno(&e)
);
err_found = Some(ChecksumError::Io(e));
continue;
}
};
let (escaped_filename, prefix) = escape_filename(filename);
if options.tag {
println!(
"{prefix}{} ({escaped_filename}) = {sum}",
options.algo.to_tag()
);
} else if options.nonames {
println!("{sum}");
} else if options.zero {
// with zero, we don't escape the filename
print!("{sum} {binary_marker}{}\0", filename.display());
} else {
println!("{prefix}{sum} {binary_marker}{escaped_filename}");
}
}
match err_found {
None => Ok(()),
Some(e) => Err(Box::new(e)),
}
}
+111 -22
View File
@@ -1,17 +1,36 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore bitlen
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufReader, Read, Write};
use std::path::Path;
use crate::checksum::{ChecksumError, SizedAlgoKind, digest_reader};
use crate::checksum::{ChecksumError, SizedAlgoKind, digest_reader, escape_filename};
use crate::error::{FromIo, UResult, USimpleError};
use crate::line_ending::LineEnding;
use crate::{encoding, os_str_as_bytes, show, translate};
use crate::{encoding, show, translate};
/// Use the same buffer size as GNU when reading a file to create a checksum
/// from it: 32 KiB.
const READ_BUFFER_SIZE: usize = 32 * 1024;
pub struct ChecksumComputeOptions {
/// Which algorithm to use to compute the digest.
pub algo_kind: SizedAlgoKind,
/// Printing format to use for each checksum.
pub output_format: OutputFormat,
/// Whether to finish lines with '\n' or '\0'.
pub line_ending: LineEnding,
/// (non-GNU option) Do not print file names
pub no_names: bool,
}
/// Reading mode used to compute digest.
@@ -77,6 +96,43 @@ impl OutputFormat {
}
}
/// Use already-processed arguments to decide the output format.
pub fn figure_out_output_format(
algo: SizedAlgoKind,
tag: bool,
binary: bool,
raw: bool,
base64: bool,
) -> OutputFormat {
// Raw output format takes precedence over anything else.
if raw {
return OutputFormat::Raw;
}
// Then, if the algo is legacy, takes precedence over the rest
if algo.is_legacy() {
return OutputFormat::Legacy;
}
let digest_format = if base64 {
DigestFormat::Base64
} else {
DigestFormat::Hexadecimal
};
// After that, decide between tagged and untagged output
if tag {
OutputFormat::Tagged(digest_format)
} else {
let reading_mode = if binary {
ReadingMode::Binary
} else {
ReadingMode::Text
};
OutputFormat::Untagged(digest_format, reading_mode)
}
}
fn print_legacy_checksum(
options: &ChecksumComputeOptions,
filename: &OsStr,
@@ -85,6 +141,14 @@ fn print_legacy_checksum(
) -> UResult<()> {
debug_assert!(options.algo_kind.is_legacy());
let (escaped_filename, prefix) = if options.line_ending == LineEnding::Nul {
(filename.to_string_lossy().to_string(), "")
} else {
escape_filename(filename)
};
print!("{prefix}");
// Print the sum
match options.algo_kind {
SizedAlgoKind::Sysv => print!(
@@ -108,9 +172,9 @@ fn print_legacy_checksum(
}
// Print the filename after a space if not stdin
if filename != "-" {
if escaped_filename != "-" {
print!(" ");
let _dropped_result = io::stdout().write_all(os_str_as_bytes(filename)?);
let _dropped_result = io::stdout().write_all(escaped_filename.as_bytes());
}
Ok(())
@@ -121,11 +185,17 @@ fn print_tagged_checksum(
filename: &OsStr,
sum: &String,
) -> UResult<()> {
let (escaped_filename, prefix) = if options.line_ending == LineEnding::Nul {
(filename.to_string_lossy().to_string(), "")
} else {
escape_filename(filename)
};
// Print algo name and opening parenthesis.
print!("{} (", options.algo_kind.to_tag());
print!("{prefix}{} (", options.algo_kind.to_tag());
// Print filename
let _dropped_result = io::stdout().write_all(os_str_as_bytes(filename)?);
let _dropped_result = io::stdout().write_all(escaped_filename.as_bytes());
// Print closing parenthesis and sum
print!(") = {sum}");
@@ -134,15 +204,28 @@ fn print_tagged_checksum(
}
fn print_untagged_checksum(
options: &ChecksumComputeOptions,
filename: &OsStr,
sum: &String,
reading_mode: ReadingMode,
) -> UResult<()> {
// early check for the "no-names" option
if options.no_names {
print!("{sum}");
return Ok(());
}
let (escaped_filename, prefix) = if options.line_ending == LineEnding::Nul {
(filename.to_string_lossy().to_string(), "")
} else {
escape_filename(filename)
};
// Print checksum and reading mode flag
print!("{sum} {}", reading_mode.as_char());
print!("{prefix}{sum} {}", reading_mode.as_char());
// Print filename
let _dropped_result = io::stdout().write_all(os_str_as_bytes(filename)?);
let _dropped_result = io::stdout().write_all(escaped_filename.as_bytes());
Ok(())
}
@@ -171,25 +254,30 @@ where
if filepath.is_dir() {
show!(USimpleError::new(
1,
translate!("cksum-error-is-directory", "file" => filepath.display())
// TODO: Rework translation, which is broken since this code moved to uucore
// translate!("cksum-error-is-directory", "file" => filepath.display())
format!("{}: Is a directory", filepath.display())
));
continue;
}
// Handle the file input
let mut file = BufReader::new(if filename == "-" {
stdin_buf = io::stdin();
Box::new(stdin_buf) as Box<dyn Read>
} else {
file_buf = match File::open(filepath) {
Ok(file) => file,
Err(err) => {
show!(err.map_err_context(|| filepath.to_string_lossy().to_string()));
continue;
}
};
Box::new(file_buf) as Box<dyn Read>
});
let mut file = BufReader::with_capacity(
READ_BUFFER_SIZE,
if filename == "-" {
stdin_buf = io::stdin();
Box::new(stdin_buf) as Box<dyn Read>
} else {
file_buf = match File::open(filepath) {
Ok(file) => file,
Err(err) => {
show!(err.map_err_context(|| filepath.to_string_lossy().into()));
continue;
}
};
Box::new(file_buf) as Box<dyn Read>
},
);
let mut digest = options.algo_kind.create_digest();
@@ -233,6 +321,7 @@ where
}
OutputFormat::Untagged(digest_format, reading_mode) => {
print_untagged_checksum(
&options,
filename,
&encode_sum(sum_hex, digest_format),
reading_mode,
+19 -21
View File
@@ -2,25 +2,23 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore anotherfile invalidchecksum JWZG FFFD xffname prefixfilename bytelen bitlen hexdigit rsplit
// spell-checker:ignore bitlen
use std::ffi::OsStr;
use std::io::{self, Read};
use std::num::IntErrorKind;
use os_display::Quotable;
use std::{
io::{self, Read},
num::IntErrorKind,
path::Path,
};
use crate::{
error::{UError, UResult},
show_error,
sum::{
Blake2b, Blake3, Bsd, CRC32B, Crc, Digest, DigestWriter, Md5, Sha1, Sha3_224, Sha3_256,
Sha3_384, Sha3_512, Sha224, Sha256, Sha384, Sha512, Shake128, Shake256, Sm3, SysV,
},
};
use thiserror::Error;
use crate::error::{UError, UResult};
use crate::show_error;
use crate::sum::{
Blake2b, Blake3, Bsd, CRC32B, Crc, Digest, DigestWriter, Md5, Sha1, Sha3_224, Sha3_256,
Sha3_384, Sha3_512, Sha224, Sha256, Sha384, Sha512, Shake128, Shake256, Sm3, SysV,
};
pub mod compute;
pub mod validate;
@@ -553,8 +551,8 @@ pub fn unescape_filename(filename: &[u8]) -> (Vec<u8>, &'static str) {
(unescaped, prefix)
}
pub fn escape_filename(filename: &Path) -> (String, &'static str) {
let original = filename.as_os_str().to_string_lossy();
pub fn escape_filename(filename: &OsStr) -> (String, &'static str) {
let original = filename.to_string_lossy();
let escaped = original
.replace('\\', "\\\\")
.replace('\n', "\\n")
@@ -587,19 +585,19 @@ mod tests {
#[test]
fn test_escape_filename() {
let (escaped, prefix) = escape_filename(Path::new("testfile.txt"));
let (escaped, prefix) = escape_filename(OsStr::new("testfile.txt"));
assert_eq!(escaped, "testfile.txt");
assert_eq!(prefix, "");
let (escaped, prefix) = escape_filename(Path::new("test\nfile.txt"));
let (escaped, prefix) = escape_filename(OsStr::new("test\nfile.txt"));
assert_eq!(escaped, "test\\nfile.txt");
assert_eq!(prefix, "\\");
let (escaped, prefix) = escape_filename(Path::new("test\rfile.txt"));
let (escaped, prefix) = escape_filename(OsStr::new("test\rfile.txt"));
assert_eq!(escaped, "test\\rfile.txt");
assert_eq!(prefix, "\\");
let (escaped, prefix) = escape_filename(Path::new("test\\file.txt"));
let (escaped, prefix) = escape_filename(OsStr::new("test\\file.txt"));
assert_eq!(escaped, "test\\\\file.txt");
assert_eq!(prefix, "\\");
}