diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 0a246b8e9..7ff0243eb 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -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>( 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, @@ -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)?; diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 00eb152ed..ec382870b 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -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]] diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 61bd0f0ff..ebe3ac033 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -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, - 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::(options::FILE).map_or_else( + // No files given, read from stdin. + || Box::new(iter::once(OsStr::new("-"))) as Box>, + // At least one file given, read from them. + |files| Box::new(files.map(OsStr::new)) as Box>, + ); + // Show the hashsum of the input - match matches.get_many::(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, -{ - 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 - } 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 - }, - ); - - 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)), - } -} diff --git a/src/uucore/src/lib/features/checksum/compute.rs b/src/uucore/src/lib/features/checksum/compute.rs index 015e9bb0f..426b2633e 100644 --- a/src/uucore/src/lib/features/checksum/compute.rs +++ b/src/uucore/src/lib/features/checksum/compute.rs @@ -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 - } 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 - }); + let mut file = BufReader::with_capacity( + READ_BUFFER_SIZE, + if filename == "-" { + stdin_buf = io::stdin(); + Box::new(stdin_buf) as Box + } 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 + }, + ); 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, diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index a3b7e53c9..87c8836fd 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -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, &'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, "\\"); }