mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
checksum: Adapt checksum computation to hashsum
This commit is contained in:
@@ -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)?;
|
||||
|
||||
@@ -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
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, "\\");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user