This commit is contained in:
Dorian Péron 2025-12-22 19:47:09 +00:00 committed by GitHub
commit f82d5780c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 1097 additions and 467 deletions

12
Cargo.lock generated
View file

@ -565,6 +565,7 @@ dependencies = [
"time",
"unindent",
"uu_arch",
"uu_b2sum",
"uu_base32",
"uu_base64",
"uu_basename",
@ -3012,6 +3013,17 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_b2sum"
version = "0.5.0"
dependencies = [
"clap",
"codspeed-divan-compat",
"fluent",
"tempfile",
"uucore",
]
[[package]]
name = "uu_base32"
version = "0.5.0"

View file

@ -76,6 +76,7 @@ feat_common_core = [
"basenc",
"cat",
"cksum",
"b2sum",
"comm",
"cp",
"csplit",
@ -431,6 +432,7 @@ chmod = { optional = true, version = "0.5.0", package = "uu_chmod", path = "src/
chown = { optional = true, version = "0.5.0", package = "uu_chown", path = "src/uu/chown" }
chroot = { optional = true, version = "0.5.0", package = "uu_chroot", path = "src/uu/chroot" }
cksum = { optional = true, version = "0.5.0", package = "uu_cksum", path = "src/uu/cksum" }
b2sum = { optional = true, version = "0.5.0", package = "uu_b2sum", path = "src/uu/b2sum" }
comm = { optional = true, version = "0.5.0", package = "uu_comm", path = "src/uu/comm" }
cp = { optional = true, version = "0.5.0", package = "uu_cp", path = "src/uu/cp" }
csplit = { optional = true, version = "0.5.0", package = "uu_csplit", path = "src/uu/csplit" }

View file

@ -84,6 +84,7 @@ PROGS := \
basename \
cat \
cksum \
b2sum \
comm \
cp \
csplit \
@ -185,7 +186,6 @@ SELINUX_PROGS := \
runcon
HASHSUM_PROGS := \
b2sum \
md5sum \
sha1sum \
sha224sum \
@ -223,6 +223,7 @@ TEST_PROGS := \
chmod \
chown \
cksum \
b2sum \
comm \
cp \
csplit \

View file

@ -85,7 +85,6 @@ pub fn main() {
phf_map.entry("sha256sum", map_value.clone());
phf_map.entry("sha384sum", map_value.clone());
phf_map.entry("sha512sum", map_value.clone());
phf_map.entry("b2sum", map_value.clone());
}
_ => {
phf_map.entry(krate, map_value.clone());

41
src/uu/b2sum/Cargo.toml Normal file
View file

@ -0,0 +1,41 @@
[package]
name = "uu_b2sum"
description = "b2sum ~ (uutils) Print or check the BLAKE2b checksums"
repository = "https://github.com/uutils/coreutils/tree/main/src/uu/b2sum"
version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
keywords.workspace = true
categories.workspace = true
edition.workspace = true
readme.workspace = true
[lints]
workspace = true
[lib]
path = "src/b2sum.rs"
[dependencies]
clap = { workspace = true }
uucore = { workspace = true, features = [
"checksum",
"encoding",
"sum",
"hardware",
] }
fluent = { workspace = true }
[dev-dependencies]
divan = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["benchmark"] }
[[bin]]
name = "b2sum"
path = "src/main.rs"
# [[bench]]
# name = "b2sum_bench"
# harness = false

1
src/uu/b2sum/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

View file

@ -0,0 +1,3 @@
b2sum-about = Print or check the BLAKE2b checksums
b2sum-usage = b2sum [OPTIONS] [FILE]...
b2sum-after-help = With no FILE or when FILE is -, read standard input

View file

@ -0,0 +1,2 @@
b2sum-about = Afficher le BLAKE2b et la taille de chaque fichier
b2sum-usage = b2sum [OPTION]... [FICHIER]...

36
src/uu/b2sum/src/b2sum.rs Normal file
View file

@ -0,0 +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 (ToDO) algo
use clap::Command;
use uucore::checksum::cli::{checksum_main, options, standalone_checksum_app_with_length};
use uucore::checksum::compute::OutputFormat;
use uucore::checksum::{AlgoKind, calculate_blake2b_length_str};
use uucore::error::UResult;
use uucore::translate;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let algo = Some(AlgoKind::Blake2b);
let length = matches
.get_one::<String>(options::LENGTH)
.map(String::as_str)
.map(calculate_blake2b_length_str)
.transpose()?
.flatten();
let format = OutputFormat::from_standalone(std::env::args_os().into_iter());
checksum_main(algo, length, matches, format?)
}
#[inline]
pub fn uu_app() -> Command {
standalone_checksum_app_with_length(translate!("b2sum-about"), translate!("b2sum-usage"))
.after_help(translate!("b2sum-after-help"))
}

1
src/uu/b2sum/src/main.rs Normal file
View file

@ -0,0 +1 @@
uucore::bin!(uu_b2sum);

View file

@ -12,19 +12,3 @@ cksum-after-help = DIGEST determines the digest algorithm and default output for
- sha3: (only available through cksum)
- blake2b: (equivalent to b2sum)
- sm3: (only available through cksum)
# Help messages
cksum-help-algorithm = select the digest type to use. See DIGEST below
cksum-help-untagged = create a reversed style checksum, without digest type
cksum-help-tag = create a BSD style checksum, undo --untagged (default)
cksum-help-length = digest length in bits; must not exceed the max for the blake2 algorithm and must be a multiple of 8
cksum-help-raw = emit a raw binary digest, not hexadecimal
cksum-help-strict = exit non-zero for improperly formatted checksum lines
cksum-help-check = read hashsums from the FILEs and check them
cksum-help-base64 = emit a base64 digest, not hexadecimal
cksum-help-warn = warn about improperly formatted checksum lines
cksum-help-status = don't output anything, status code shows success
cksum-help-quiet = don't print OK for each successfully verified file
cksum-help-ignore-missing = don't fail or report status for missing files
cksum-help-zero = end each output line with NUL, not newline, and disable file name escaping
cksum-help-debug = print CPU hardware capability detection info used by cksum

View file

@ -12,19 +12,3 @@ cksum-after-help = DIGEST détermine l'algorithme de condensé et le format de s
- sha3 : (disponible uniquement via cksum)
- blake2b : (équivalent à b2sum)
- sm3 : (disponible uniquement via cksum)
# Messages d'aide
cksum-help-algorithm = sélectionner le type de condensé à utiliser. Voir DIGEST ci-dessous
cksum-help-untagged = créer une somme de contrôle de style inversé, sans type de condensé
cksum-help-tag = créer une somme de contrôle de style BSD, annuler --untagged (par défaut)
cksum-help-length = longueur du condensé en bits ; ne doit pas dépasser le maximum pour l'algorithme blake2 et doit être un multiple de 8
cksum-help-raw = émettre un condensé binaire brut, pas hexadécimal
cksum-help-strict = sortir avec un code non-zéro pour les lignes de somme de contrôle mal formatées
cksum-help-check = lire les sommes de hachage des FICHIERs et les vérifier
cksum-help-base64 = émettre un condensé base64, pas hexadécimal
cksum-help-warn = avertir des lignes de somme de contrôle mal formatées
cksum-help-status = ne rien afficher, le code de statut indique le succès
cksum-help-quiet = ne pas afficher OK pour chaque fichier vérifié avec succès
cksum-help-ignore-missing = ne pas échouer ou signaler le statut pour les fichiers manquants
cksum-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne, et désactiver l'échappement des noms de fichiers
cksum-help-debug = afficher les informations de débogage sur la détection de la prise en charge matérielle du processeur

View file

@ -5,24 +5,18 @@
// spell-checker:ignore (ToDO) fname, algo, bitlen
use clap::builder::ValueParser;
use clap::{Arg, ArgAction, Command};
use std::ffi::{OsStr, OsString};
use std::iter;
use uucore::checksum::compute::{
ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation,
};
use uucore::checksum::validate::{
ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation,
};
use std::ffi::OsStr;
use clap::Command;
use uucore::checksum::cli::{ChecksumCommand, checksum_main, default_checksum_app};
use uucore::checksum::compute::OutputFormat;
use uucore::checksum::{
AlgoKind, ChecksumError, SUPPORTED_ALGORITHMS, SizedAlgoKind, calculate_blake2b_length_str,
AlgoKind, ChecksumError, calculate_blake2b_length_str, cli::options,
sanitize_sha2_sha3_length_str,
};
use uucore::error::UResult;
use uucore::hardware::{HasHardwareFeatures as _, SimdPolicy};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, show_error, translate};
use uucore::{show_error, translate};
/// Print CPU hardware capability detection information to stderr
/// This matches GNU cksum's --debug behavior
@ -48,24 +42,28 @@ fn print_cpu_debug_info() {
}
}
mod options {
pub const ALGORITHM: &str = "algorithm";
pub const FILE: &str = "file";
pub const UNTAGGED: &str = "untagged";
pub const TAG: &str = "tag";
pub const LENGTH: &str = "length";
pub const RAW: &str = "raw";
pub const BASE64: &str = "base64";
pub const CHECK: &str = "check";
pub const STRICT: &str = "strict";
pub const TEXT: &str = "text";
pub const BINARY: &str = "binary";
pub const STATUS: &str = "status";
pub const WARN: &str = "warn";
pub const IGNORE_MISSING: &str = "ignore-missing";
pub const QUIET: &str = "quiet";
pub const ZERO: &str = "zero";
pub const DEBUG: &str = "debug";
/// Sanitize the `--length` argument depending on `--algorithm` and `--length`.
fn maybe_sanitize_length(
algo_cli: Option<AlgoKind>,
input_length: Option<&str>,
) -> UResult<Option<usize>> {
match (algo_cli, input_length) {
// No provided length is not a problem so far.
(_, None) => Ok(None),
// For SHA2 and SHA3, if a length is provided, ensure it is correct.
(Some(algo @ (AlgoKind::Sha2 | AlgoKind::Sha3)), Some(s_len)) => {
sanitize_sha2_sha3_length_str(algo, s_len).map(Some)
}
// For BLAKE2b, if a length is provided, validate it.
(Some(AlgoKind::Blake2b), Some(len)) => calculate_blake2b_length_str(len),
// For any other provided algorithm, check if length is 0.
// Otherwise, this is an error.
(_, Some(len)) if len.parse::<u32>() == Ok(0_u32) => Ok(None),
(_, Some(_)) => Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into()),
}
}
/// cksum has a bunch of legacy behavior. We handle this in this function to
@ -110,50 +108,10 @@ fn handle_tag_text_binary_flags<S: AsRef<OsStr>>(
Ok((tag, binary))
}
/// Sanitize the `--length` argument depending on `--algorithm` and `--length`.
fn maybe_sanitize_length(
algo_cli: Option<AlgoKind>,
input_length: Option<&str>,
) -> UResult<Option<usize>> {
match (algo_cli, input_length) {
// No provided length is not a problem so far.
(_, None) => Ok(None),
// For SHA2 and SHA3, if a length is provided, ensure it is correct.
(Some(algo @ (AlgoKind::Sha2 | AlgoKind::Sha3)), Some(s_len)) => {
sanitize_sha2_sha3_length_str(algo, s_len).map(Some)
}
// For BLAKE2b, if a length is provided, validate it.
(Some(AlgoKind::Blake2b), Some(len)) => calculate_blake2b_length_str(len),
// For any other provided algorithm, check if length is 0.
// Otherwise, this is an error.
(_, Some(len)) if len.parse::<u32>() == Ok(0_u32) => Ok(None),
(_, Some(_)) => Err(ChecksumError::LengthOnlyForBlake2bSha2Sha3.into()),
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let check = matches.get_flag(options::CHECK);
let check_flag = |flag| match (check, matches.get_flag(flag)) {
(_, false) => Ok(false),
(true, true) => Ok(true),
(false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())),
};
// Each of the following flags are only expected in --check mode.
// If we encounter them otherwise, end with an error.
let ignore_missing = check_flag(options::IGNORE_MISSING)?;
let warn = check_flag(options::WARN)?;
let quiet = check_flag(options::QUIET)?;
let strict = check_flag(options::STRICT)?;
let status = check_flag(options::STATUS)?;
let algo_cli = matches
.get_one::<String>(options::ALGORITHM)
.map(AlgoKind::from_cksum)
@ -165,199 +123,38 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let length = maybe_sanitize_length(algo_cli, input_length)?;
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>>,
let (tag, binary) = handle_tag_text_binary_flags(std::env::args_os())?;
let output_format = OutputFormat::from_cksum(
algo_cli.unwrap_or(AlgoKind::Crc),
tag,
binary,
/* raw: */
matches.get_flag(options::RAW),
/* base64: */
matches.get_flag(options::BASE64),
);
if check {
// cksum does not support '--check'ing legacy algorithms
if algo_cli.is_some_and(AlgoKind::is_legacy) {
return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into());
}
let text_flag = matches.get_flag(options::TEXT);
let binary_flag = matches.get_flag(options::BINARY);
let tag = matches.get_flag(options::TAG);
if tag || binary_flag || text_flag {
return Err(ChecksumError::BinaryTextConflict.into());
}
// Execute the checksum validation based on the presence of files or the use of stdin
let verbose = ChecksumVerbose::new(status, quiet, warn);
let opts = ChecksumValidateOptions {
ignore_missing,
strict,
verbose,
};
return perform_checksum_validation(files, algo_cli, length, opts);
}
// Not --check
// Print hardware debug info if requested
if matches.get_flag(options::DEBUG) {
print_cpu_debug_info();
}
// Set the default algorithm to CRC when not '--check'ing.
let algo_kind = algo_cli.unwrap_or(AlgoKind::Crc);
let (tag, binary) = handle_tag_text_binary_flags(std::env::args_os())?;
let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
let opts = ChecksumComputeOptions {
algo_kind: algo,
output_format: figure_out_output_format(
algo,
tag,
binary,
matches.get_flag(options::RAW),
matches.get_flag(options::BASE64),
),
line_ending,
};
perform_checksum_computation(opts, files)?;
Ok(())
checksum_main(algo_cli, length, matches, output_format)
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template(uucore::util_name()))
.about(translate!("cksum-about"))
.override_usage(format_usage(&translate!("cksum-usage")))
.infer_long_args(true)
.args_override_self(true)
.arg(
Arg::new(options::FILE)
.hide(true)
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::FilePath),
)
.arg(
Arg::new(options::ALGORITHM)
.long(options::ALGORITHM)
.short('a')
.help(translate!("cksum-help-algorithm"))
.value_name("ALGORITHM")
.value_parser(SUPPORTED_ALGORITHMS),
)
.arg(
Arg::new(options::UNTAGGED)
.long(options::UNTAGGED)
.help(translate!("cksum-help-untagged"))
.action(ArgAction::SetTrue)
.overrides_with(options::TAG),
)
.arg(
Arg::new(options::TAG)
.long(options::TAG)
.help(translate!("cksum-help-tag"))
.action(ArgAction::SetTrue)
.overrides_with(options::UNTAGGED),
)
.arg(
Arg::new(options::LENGTH)
.long(options::LENGTH)
.short('l')
.help(translate!("cksum-help-length"))
.action(ArgAction::Set),
)
.arg(
Arg::new(options::RAW)
.long(options::RAW)
.help(translate!("cksum-help-raw"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::STRICT)
.long(options::STRICT)
.help(translate!("cksum-help-strict"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::CHECK)
.short('c')
.long(options::CHECK)
.help(translate!("cksum-help-check"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::BASE64)
.long(options::BASE64)
.help(translate!("cksum-help-base64"))
.action(ArgAction::SetTrue)
// Even though this could easily just override an earlier '--raw',
// GNU cksum does not permit these flags to be combined:
.conflicts_with(options::RAW),
)
.arg(
Arg::new(options::TEXT)
.long(options::TEXT)
.short('t')
.hide(true)
.overrides_with(options::BINARY)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::BINARY)
.long(options::BINARY)
.short('b')
.hide(true)
.overrides_with(options::TEXT)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::WARN)
.short('w')
.long("warn")
.help(translate!("cksum-help-warn"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::STATUS, options::QUIET]),
)
.arg(
Arg::new(options::STATUS)
.long("status")
.help(translate!("cksum-help-status"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::WARN, options::QUIET]),
)
.arg(
Arg::new(options::QUIET)
.long(options::QUIET)
.help(translate!("cksum-help-quiet"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::WARN, options::STATUS]),
)
.arg(
Arg::new(options::IGNORE_MISSING)
.long(options::IGNORE_MISSING)
.help(translate!("cksum-help-ignore-missing"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ZERO)
.long(options::ZERO)
.short('z')
.help(translate!("cksum-help-zero"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::DEBUG)
.long(options::DEBUG)
.help(translate!("cksum-help-debug"))
.action(ArgAction::SetTrue),
)
default_checksum_app(translate!("cksum-about"), translate!("cksum-usage"))
.with_algo()
.with_length()
.with_check()
.with_untagged()
.with_tag(true)
.with_raw()
.with_base64()
.with_text(false)
.with_binary()
.with_zero()
.with_debug()
.after_help(translate!("cksum-after-help"))
}

View file

@ -7,19 +7,21 @@
use std::ffi::{OsStr, OsString};
use std::iter;
use std::num::ParseIntError;
use std::path::Path;
use clap::builder::ValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command};
use uucore::checksum::compute::{
ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation,
ChecksumComputeOptions, OutputFormat, perform_checksum_computation,
};
use uucore::checksum::validate::{
ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation,
};
use uucore::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, calculate_blake2b_length_str};
use uucore::checksum::{
AlgoKind, ChecksumError, SizedAlgoKind, calculate_blake2b_length_str,
sanitize_sha2_sha3_length_str,
};
use uucore::error::UResult;
use uucore::line_ending::LineEnding;
use uucore::{format_usage, translate};
@ -74,9 +76,11 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<(AlgoKind, Optio
set_or_err((AlgoKind::Blake3, None))?;
}
if matches.get_flag("sha3") {
match matches.get_one::<usize>("bits") {
Some(bits @ (224 | 256 | 384 | 512)) => set_or_err((AlgoKind::Sha3, Some(*bits)))?,
Some(bits) => return Err(ChecksumError::InvalidLengthForSha(bits.to_string()).into()),
match matches.get_one::<String>(options::LENGTH) {
Some(len) => set_or_err((
AlgoKind::Sha3,
Some(sanitize_sha2_sha3_length_str(AlgoKind::Sha3, len)?),
))?,
None => return Err(ChecksumError::LengthRequired("SHA3".into()).into()),
}
}
@ -93,16 +97,10 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<(AlgoKind, Optio
set_or_err((AlgoKind::Sha3, Some(512)))?;
}
if matches.get_flag("shake128") {
match matches.get_one::<usize>("bits") {
Some(bits) => set_or_err((AlgoKind::Shake128, Some(*bits)))?,
None => return Err(ChecksumError::LengthRequired("SHAKE128".into()).into()),
}
set_or_err((AlgoKind::Shake128, Some(128)))?;
}
if matches.get_flag("shake256") {
match matches.get_one::<usize>("bits") {
Some(bits) => set_or_err((AlgoKind::Shake256, Some(*bits)))?,
None => return Err(ChecksumError::LengthRequired("SHAKE256".into()).into()),
}
set_or_err((AlgoKind::Shake256, Some(256)))?;
}
if alg.is_none() {
@ -112,11 +110,6 @@ fn create_algorithm_from_flags(matches: &ArgMatches) -> UResult<(AlgoKind, Optio
Ok(alg.unwrap())
}
// TODO: return custom error type
fn parse_bit_num(arg: &str) -> Result<usize, ParseIntError> {
arg.parse()
}
#[uucore::main]
pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
// if there is no program name for some reason, default to "hashsum"
@ -128,9 +121,6 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
let args = iter::once(program.clone()).chain(args);
// Default binary in Windows, text mode otherwise
let binary_flag_default = cfg!(windows);
let (command, is_hashsum_bin) = uu_app(&binary_name);
// FIXME: this should use try_get_matches_from() and crash!(), but at the moment that just
@ -139,30 +129,22 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
// least somewhat better from a user's perspective.
let matches = uucore::clap_localization::handle_clap_result(command, args)?;
let input_length: Option<&String> = if binary_name == "b2sum" {
matches.get_one::<String>(options::LENGTH)
let length: Option<usize> = if binary_name == "b2sum" {
if let Some(len) = matches.get_one::<String>(options::LENGTH) {
calculate_blake2b_length_str(len)?
} else {
None
}
} else {
None
};
let length = match input_length {
Some(length) => calculate_blake2b_length_str(length)?,
None => None,
};
let (algo_kind, length) = if is_hashsum_bin {
create_algorithm_from_flags(&matches)?
} else {
(AlgoKind::from_bin_name(&binary_name)?, length)
};
let binary = if matches.get_flag("binary") {
true
} else if matches.get_flag("text") {
false
} else {
binary_flag_default
};
let check = matches.get_flag("check");
let check_flag = |flag| match (check, matches.get_flag(flag)) {
@ -216,13 +198,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
let opts = ChecksumComputeOptions {
algo_kind: algo,
output_format: figure_out_output_format(
algo,
matches.get_flag(options::TAG),
binary,
/* raw */ false,
/* base64: */ false,
),
output_format: OutputFormat::from_standalone(std::env::args_os().into_iter())?,
line_ending,
};
@ -371,24 +347,8 @@ fn uu_app_opt_length(command: Command) -> Command {
)
}
pub fn uu_app_bits() -> Command {
uu_app_opt_bits(uu_app_common())
}
fn uu_app_opt_bits(command: Command) -> Command {
// Needed for variable-length output sums (e.g. SHAKE)
command.arg(
Arg::new("bits")
.long("bits")
.help(translate!("hashsum-help-bits"))
.value_name("BITS")
// XXX: should we actually use validators? they're not particularly efficient
.value_parser(parse_bit_num),
)
}
pub fn uu_app_custom() -> Command {
let mut command = uu_app_opt_bits(uu_app_common());
let mut command = uu_app_opt_length(uu_app_common());
let algorithms = &[
("md5", translate!("hashsum-help-md5")),
("sha1", translate!("hashsum-help-sha1")),

View file

@ -73,3 +73,23 @@ checksum-failed-open-file = { $count ->
*[other] { $count } listed files could not be read
}
checksum-error-algo-bad-format = { $file }: { $line }: improperly formatted { $algo } checksum line
# checksum argument help messages
checksum-help-algorithm = select the digest type to use. See DIGEST below
checksum-help-untagged = create a reversed style checksum, without digest type
checksum-help-tag-default = create a BSD style checksum (default)
checksum-help-tag = create a BSD style checksum
checksum-help-text = read in text mode (default)
checksum-help-length = digest length in bits; must not exceed the max size and must be a multiple of 8 for blake2b; must be 224, 256, 384, or 512 for sha2 or sha3
checksum-help-check = read checksums from the FILEs and check them
checksum-help-base64 = emit base64-encoded digests, not hexadecimal
checksum-help-raw = emit a raw binary digest, not hexadecimal
checksum-help-zero = end each output line with NUL, not newline, and disable file name escaping
checksum-help-strict = exit non-zero for improperly formatted checksum lines
checksum-help-warn = warn about improperly formatted checksum lines
checksum-help-status = don't output anything, status code shows success
checksum-help-quiet = don't print OK for each successfully verified file
checksum-help-ignore-missing = don't fail or report status for missing files
checksum-help-debug = print CPU hardware capability detection info used by cksum

View file

@ -73,3 +73,21 @@ checksum-failed-open-file = { $count ->
*[other] { $count } fichiers passés n'ont pas pu être lu
}
checksum-error-algo-bad-format = { $file }: { $line }: ligne invalide pour { $algo }
# Messages d'aide d'arguments checksum
checksum-help-algorithm = sélectionner le type de condensé à utiliser. Voir DIGEST ci-dessous
checksum-help-untagged = créer une somme de contrôle de style inversé, sans type de condensé
checksum-help-tag-default = créer une somme de contrôle de style BSD (par défaut)
checksum-help-tag = créer une somme de contrôle de style BSD
checksum-help-text = lire en mode texte (par défaut)
checksum-help-length = longueur du condensé en bits ; ne doit pas dépasser le maximum pour l'algorithme blake2 et doit être un multiple de 8
checksum-help-raw = émettre un condensé binaire brut, pas hexadécimal
checksum-help-strict = sortir avec un code non-zéro pour les lignes de somme de contrôle mal formatées
checksum-help-check = lire les sommes de hachage des FICHIERs et les vérifier
checksum-help-base64 = émettre un condensé base64, pas hexadécimal
checksum-help-warn = avertir des lignes de somme de contrôle mal formatées
checksum-help-status = ne rien afficher, le code de statut indique le succès
checksum-help-quiet = ne pas afficher OK pour chaque fichier vérifié avec succès
checksum-help-ignore-missing = ne pas échouer ou signaler le statut pour les fichiers manquants
checksum-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne, et désactiver l'échappement des noms de fichiers
checksum-help-debug = afficher les informations de débogage sur la détection de la prise en charge matérielle du processeur

View file

@ -0,0 +1,326 @@
use std::ffi::{OsStr, OsString};
use clap::builder::ValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint};
use crate::checksum::compute::{
ChecksumComputeOptions, OutputFormat, perform_checksum_computation,
};
use crate::checksum::validate::{ChecksumValidateOptions, ChecksumVerbose};
use crate::checksum::{AlgoKind, ChecksumError, SUPPORTED_ALGORITHMS, SizedAlgoKind};
use crate::error::UResult;
use crate::line_ending::LineEnding;
use crate::{crate_version, format_usage, localized_help_template, translate, util_name};
pub mod options {
// cksum-specific
pub const ALGORITHM: &str = "algorithm";
pub const DEBUG: &str = "debug";
pub const FILE: &str = "file";
pub const UNTAGGED: &str = "untagged";
pub const TAG: &str = "tag";
pub const LENGTH: &str = "length";
pub const RAW: &str = "raw";
pub const BASE64: &str = "base64";
pub const CHECK: &str = "check";
pub const TEXT: &str = "text";
pub const BINARY: &str = "binary";
pub const ZERO: &str = "zero";
// check-specific
pub const STRICT: &str = "strict";
pub const STATUS: &str = "status";
pub const WARN: &str = "warn";
pub const IGNORE_MISSING: &str = "ignore-missing";
pub const QUIET: &str = "quiet";
}
pub trait ChecksumCommand {
fn with_algo(self) -> Self;
fn with_length(self) -> Self;
fn with_check(self) -> Self;
fn with_binary(self) -> Self;
fn with_text(self, default: bool) -> Self;
fn with_tag(self, default: bool) -> Self;
fn with_untagged(self) -> Self;
fn with_raw(self) -> Self;
fn with_base64(self) -> Self;
fn with_zero(self) -> Self;
fn with_debug(self) -> Self;
}
impl ChecksumCommand for Command {
fn with_algo(self) -> Self {
self.arg(
Arg::new(options::ALGORITHM)
.long(options::ALGORITHM)
.short('a')
.help(translate!("checksum-help-algorithm"))
.value_name("ALGORITHM")
.value_parser(SUPPORTED_ALGORITHMS),
)
}
fn with_length(self) -> Self {
self.arg(
Arg::new(options::LENGTH)
.long(options::LENGTH)
.short('l')
.help(translate!("checksum-help-length"))
.action(ArgAction::Set),
)
}
fn with_check(self) -> Self {
self.arg(
Arg::new(options::CHECK)
.short('c')
.long(options::CHECK)
.help(translate!("checksum-help-check"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::WARN)
.short('w')
.long("warn")
.help(translate!("checksum-help-warn"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::STATUS, options::QUIET]),
)
.arg(
Arg::new(options::STATUS)
.long("status")
.help(translate!("checksum-help-status"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::WARN, options::QUIET]),
)
.arg(
Arg::new(options::QUIET)
.long(options::QUIET)
.help(translate!("checksum-help-quiet"))
.action(ArgAction::SetTrue)
.overrides_with_all([options::WARN, options::STATUS]),
)
.arg(
Arg::new(options::IGNORE_MISSING)
.long(options::IGNORE_MISSING)
.help(translate!("checksum-help-ignore-missing"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::STRICT)
.long(options::STRICT)
.help(translate!("checksum-help-strict"))
.action(ArgAction::SetTrue),
)
}
fn with_binary(self) -> Self {
self.arg(
Arg::new(options::BINARY)
.long(options::BINARY)
.short('b')
.hide(true)
.overrides_with(options::TEXT)
.action(ArgAction::SetTrue),
)
}
fn with_text(self, default: bool) -> Self {
let mut arg = Arg::new(options::TEXT)
.long(options::TEXT)
.short('t')
.action(ArgAction::SetTrue);
if default {
arg = arg.help(translate!("checksum-help-text"));
} else {
arg = arg.hide(true);
}
self.arg(arg)
}
fn with_tag(self, default: bool) -> Self {
self.arg(
Arg::new(options::TAG)
.long(options::TAG)
.help(if default {
translate!("checksum-help-tag-default")
} else {
translate!("checksum-help-tag")
})
.action(ArgAction::SetTrue),
)
}
fn with_untagged(self) -> Self {
self.arg(
Arg::new(options::UNTAGGED)
.long(options::UNTAGGED)
.help(translate!("checksum-help-untagged"))
.action(ArgAction::SetTrue)
.overrides_with(options::TAG),
)
}
fn with_raw(self) -> Self {
self.arg(
Arg::new(options::RAW)
.long(options::RAW)
.help(translate!("checksum-help-raw"))
.action(ArgAction::SetTrue),
)
}
fn with_base64(self) -> Self {
self.arg(
Arg::new(options::BASE64)
.long(options::BASE64)
.help(translate!("checksum-help-base64"))
.action(ArgAction::SetTrue)
// Even though this could easily just override an earlier '--raw',
// GNU cksum does not permit these flags to be combined:
.conflicts_with(options::RAW),
)
}
fn with_zero(self) -> Self {
self.arg(
Arg::new(options::ZERO)
.long(options::ZERO)
.short('z')
.help(translate!("checksum-help-zero"))
.action(ArgAction::SetTrue),
)
}
fn with_debug(self) -> Self {
self.arg(
Arg::new(options::DEBUG)
.long(options::DEBUG)
.help(translate!("checksum-help-debug"))
.action(ArgAction::SetTrue),
)
}
}
pub fn default_checksum_app(about: String, usage: String) -> Command {
Command::new(util_name())
.version(crate_version!())
.help_template(localized_help_template(util_name()))
.about(about)
.override_usage(format_usage(&usage))
.infer_long_args(true)
.args_override_self(true)
.arg(
Arg::new(options::FILE)
.hide(true)
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.value_hint(ValueHint::FilePath),
)
}
pub fn standalone_checksum_app(about: String, usage: String) -> Command {
default_checksum_app(about, usage)
.with_binary()
.with_check()
.with_tag(false)
.with_text(true)
.with_zero()
}
pub fn standalone_checksum_app_with_length(about: String, usage: String) -> Command {
default_checksum_app(about, usage)
.with_binary()
.with_check()
.with_length()
.with_tag(false)
.with_text(true)
.with_zero()
}
pub fn checksum_main(
algo: Option<AlgoKind>,
length: Option<usize>,
matches: ArgMatches,
output_format: OutputFormat,
) -> UResult<()> {
let check = matches.get_flag(options::CHECK);
let check_flag = |flag| match (check, matches.get_flag(flag)) {
(_, false) => Ok(false),
(true, true) => Ok(true),
(false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())),
};
// Each of the following flags are only expected in --check mode.
// If we encounter them otherwise, end with an error.
let ignore_missing = check_flag(options::IGNORE_MISSING)?;
let warn = check_flag(options::WARN)?;
let quiet = check_flag(options::QUIET)?;
let strict = check_flag(options::STRICT)?;
let status = check_flag(options::STATUS)?;
let files = matches.get_many::<OsString>(options::FILE).map_or_else(
// No files given, read from stdin.
|| Box::new(std::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>>,
);
if check {
// cksum does not support '--check'ing legacy algorithms
if algo.is_some_and(AlgoKind::is_legacy) {
return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into());
}
let text_flag = matches.get_flag(options::TEXT);
let binary_flag = matches.get_flag(options::BINARY);
let tag = matches.get_flag(options::TAG);
if tag || binary_flag || text_flag {
return Err(ChecksumError::BinaryTextConflict.into());
}
// Execute the checksum validation based on the presence of files or the use of stdin
let verbose = ChecksumVerbose::new(status, quiet, warn);
let opts = ChecksumValidateOptions {
ignore_missing,
strict,
verbose,
};
return super::validate::perform_checksum_validation(files, algo, length, opts);
}
// Not --check
// Set the default algorithm to CRC when not '--check'ing.
let algo_kind = algo.unwrap_or(AlgoKind::Crc);
let algo = SizedAlgoKind::from_unsized(algo_kind, length)?;
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
let opts = ChecksumComputeOptions {
algo_kind: algo,
output_format,
line_ending,
};
perform_checksum_computation(opts, files)?;
Ok(())
}

View file

@ -5,12 +5,12 @@
// spell-checker:ignore bitlen
use std::ffi::OsStr;
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{self, BufReader, Read, Write};
use std::path::Path;
use crate::checksum::{ChecksumError, SizedAlgoKind, digest_reader, escape_filename};
use crate::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, digest_reader, escape_filename};
use crate::error::{FromIo, UResult, USimpleError};
use crate::line_ending::LineEnding;
use crate::sum::DigestOutput;
@ -103,42 +103,76 @@ impl OutputFormat {
fn is_raw(&self) -> bool {
*self == Self::Raw
}
}
/// 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;
}
/// Find the correct output format for cksum.
pub fn from_cksum(algo: AlgoKind, tag: bool, binary: bool, raw: bool, base64: bool) -> Self {
// 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;
}
// 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
let digest_format = if base64 {
DigestFormat::Base64
} else {
ReadingMode::Text
DigestFormat::Hexadecimal
};
OutputFormat::Untagged(digest_format, reading_mode)
// 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)
}
}
/// Find the correct output format for a standalone checksum util (b2sum,
/// md5sum, etc)
///
/// Since standalone utils can't use the Raw or Legacy output format, it is
/// decided only using the --tag, --binary and --text arguments.
pub fn from_standalone<'a, I: Iterator<Item = OsString>>(args: I) -> UResult<Self> {
let mut text = true;
let mut tag = false;
for arg in args {
if arg == "--" {
break;
} else if arg == "--tag" {
tag = true;
text = false;
} else if arg == "--binary" || arg == "-b" {
text = false;
} else if arg == "--text" || arg == "-t" {
// Finding a `--text` after `--tag` is an error.
if tag {
return Err(ChecksumError::TextAfterTag.into());
}
text = true;
}
}
if tag {
Ok(OutputFormat::Tagged(DigestFormat::Hexadecimal))
} else {
Ok(OutputFormat::Untagged(
DigestFormat::Hexadecimal,
if text {
ReadingMode::Text
} else {
ReadingMode::Binary
},
))
}
}
}

View file

@ -19,6 +19,7 @@ use crate::sum::{
Sha3_256, Sha3_384, Sha3_512, Sha224, Sha256, Sha384, Sha512, Shake128, Shake256, Sm3, SysV,
};
pub mod cli;
pub mod compute;
pub mod validate;
@ -397,6 +398,8 @@ pub enum ChecksumError {
BinaryTextConflict,
#[error("--text mode is only supported with --untagged")]
TextWithoutUntagged,
#[error("--tag does not support --text mode")]
TextAfterTag,
#[error("--check is not supported with --algorithm={{bsd,sysv,crc,crc32b}}")]
AlgorithmNotSupportedWithCheck,
#[error("You cannot combine multiple hash algorithms!")]

View file

@ -170,9 +170,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str {
"[" => "test",
// hashsum aliases - all these hash commands are aliases for hashsum
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => {
"hashsum"
}
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum",
"dir" => "ls", // dir is an alias for ls

289
tests/by-util/test_b2sum.rs Normal file
View file

@ -0,0 +1,289 @@
// 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.
use rstest::rstest;
use uutests::util::TestScenario;
use uutests::{new_ucmd, util_name};
// spell-checker:ignore checkfile, nonames, testf, ntestf
macro_rules! get_hash(
($str:expr) => (
$str.split(' ').collect::<Vec<&str>>()[0]
);
);
macro_rules! test_digest_with_len {
($id:ident, $t:ident, $size:expr) => {
mod $id {
use uutests::util::*;
use uutests::util_name;
static LENGTH_ARG: &'static str = concat!("--length=", stringify!($size));
static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected");
static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile");
static INPUT_FILE: &'static str = "input.txt";
#[test]
fn test_single_file() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(LENGTH_ARG)
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_stdin() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(LENGTH_ARG)
.pipe_in_fixture(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_check() {
let ts = TestScenario::new(util_name!());
println!("File content='{}'", ts.fixtures.read(INPUT_FILE));
println!("Check file='{}'", ts.fixtures.read(CHECK_FILE));
ts.ucmd()
.args(&[LENGTH_ARG, "--check", CHECK_FILE])
.succeeds()
.no_stderr()
.stdout_is("input.txt: OK\n");
}
#[test]
fn test_zero() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(LENGTH_ARG)
.arg("--zero")
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_missing_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "file1\n");
at.write("c", "file3\n");
ts.ucmd()
.args(&[LENGTH_ARG, "a", "b", "c"])
.fails()
.stdout_contains("a\n")
.stdout_contains("c\n")
.stderr_contains("b: No such file or directory");
}
}
};
}
test_digest_with_len! {b2sum, b2sum, 512}
#[test]
fn test_check_b2sum_length_option_0() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("testf", "foobar\n");
at.write("testf.b2sum", "9e2bf63e933e610efee4a8d6cd4a9387e80860edee97e27db3b37a828d226ab1eb92a9cdd8ca9ca67a753edaf8bd89a0558496f67a30af6f766943839acf0110 testf\n");
scene
.ccmd("b2sum")
.arg("--length=0")
.arg("-c")
.arg(at.subdir.join("testf.b2sum"))
.succeeds()
.stdout_only("testf: OK\n");
}
#[test]
fn test_check_b2sum_length_duplicate() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("testf", "foobar\n");
scene
.ccmd("b2sum")
.arg("--length=123")
.arg("--length=128")
.arg("testf")
.succeeds()
.stdout_contains("d6d45901dec53e65d2b55fb6e2ab67b0");
}
#[test]
fn test_check_b2sum_length_option_8() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("testf", "foobar\n");
at.write("testf.b2sum", "6a testf\n");
scene
.ccmd("b2sum")
.arg("--length=8")
.arg("-c")
.arg(at.subdir.join("testf.b2sum"))
.succeeds()
.stdout_only("testf: OK\n");
}
#[test]
fn test_invalid_b2sum_length_option_not_multiple_of_8() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("testf", "foobar\n");
scene
.ccmd("b2sum")
.arg("--length=9")
.arg(at.subdir.join("testf"))
.fails_with_code(1)
.stderr_contains("b2sum: invalid length: '9'")
.stderr_contains("b2sum: length is not a multiple of 8");
}
#[rstest]
#[case("513")]
#[case("1024")]
#[case("18446744073709552000")]
fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("testf", "foobar\n");
scene
.ccmd("b2sum")
.arg("--length")
.arg(len)
.arg(at.subdir.join("testf"))
.fails_with_code(1)
.no_stdout()
.stderr_contains(format!("b2sum: invalid length: '{len}'"))
.stderr_contains("b2sum: maximum digest length for 'BLAKE2b' is 512 bits");
}
#[test]
fn test_check_b2sum_tag_output() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("f");
scene
.ccmd("b2sum")
.arg("--length=0")
.arg("--tag")
.arg("f")
.succeeds()
.stdout_only("BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n");
scene
.ccmd("b2sum")
.arg("--length=128")
.arg("--tag")
.arg("f")
.succeeds()
.stdout_only("BLAKE2b-128 (f) = cae66941d9efbd404e4d88758ea67670\n");
}
#[test]
fn test_check_b2sum_verify() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("a", "a\n");
scene
.ccmd("b2sum")
.arg("--tag")
.arg("a")
.succeeds()
.stdout_only("BLAKE2b (a) = bedfbb90d858c2d67b7ee8f7523be3d3b54004ef9e4f02f2ad79a1d05bfdfe49b81e3c92ebf99b504102b6bf003fa342587f5b3124c205f55204e8c4b4ce7d7c\n");
scene
.ccmd("b2sum")
.arg("--tag")
.arg("-l")
.arg("128")
.arg("a")
.succeeds()
.stdout_only("BLAKE2b-128 (a) = b93e0fc7bb21633c08bba07c5e71dc00\n");
}
#[test]
fn test_check_b2sum_strict_check() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("f");
let checksums = [
"2e f\n",
"e4a6a0577479b2b4 f\n",
"cae66941d9efbd404e4d88758ea67670 f\n",
"246c0442cd564aced8145b8b60f1370aa7 f\n",
"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8 f\n",
"4ded8c5fc8b12f3273f877ca585a44ad6503249a2b345d6d9c0e67d85bcb700db4178c0303e93b8f4ad758b8e2c9fd8b3d0c28e585f1928334bb77d36782e8 f\n",
"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce f\n",
];
at.write("ck", &checksums.join(""));
let output = "f: OK\n".to_string().repeat(checksums.len());
scene
.ccmd("b2sum")
.arg("-c")
.arg(at.subdir.join("ck"))
.succeeds()
.stdout_only(&output);
scene
.ccmd("b2sum")
.arg("--strict")
.arg("-c")
.arg(at.subdir.join("ck"))
.succeeds()
.stdout_only(&output);
}
#[test]
fn test_help_shows_correct_utility_name() {
// Test b2sum
new_ucmd!()
.arg("--help")
.succeeds()
.stdout_contains("Usage: b2sum")
.stdout_does_not_contain("Usage: hashsum");
}

View file

@ -16,87 +16,206 @@ macro_rules! get_hash(
);
macro_rules! test_digest {
($($id:ident $t:ident $size:expr)*) => ($(
($id:ident, $t:ident) => {
mod $id {
use uutests::util::*;
use uutests::util_name;
static DIGEST_ARG: &'static str = concat!("--", stringify!($t));
static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected");
static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile");
static INPUT_FILE: &'static str = "input.txt";
mod $id {
use uutests::util::*;
use uutests::util_name;
static DIGEST_ARG: &'static str = concat!("--", stringify!($t));
static BITS_ARG: &'static str = concat!("--bits=", stringify!($size));
static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected");
static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile");
static INPUT_FILE: &'static str = "input.txt";
#[test]
fn test_single_file() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_single_file() {
let ts = TestScenario::new(util_name!());
assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg(INPUT_FILE).succeeds().no_stderr().stdout_str()));
#[test]
fn test_stdin() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.pipe_in_fixture(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_check() {
let ts = TestScenario::new(util_name!());
println!("File content='{}'", ts.fixtures.read(INPUT_FILE));
println!("Check file='{}'", ts.fixtures.read(CHECK_FILE));
ts.ucmd()
.args(&[DIGEST_ARG, "--check", CHECK_FILE])
.succeeds()
.no_stderr()
.stdout_is("input.txt: OK\n");
}
#[test]
fn test_zero() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.arg("--zero")
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_missing_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "file1\n");
at.write("c", "file3\n");
ts.ucmd()
.args(&[DIGEST_ARG, "a", "b", "c"])
.fails()
.stdout_contains("a\n")
.stdout_contains("c\n")
.stderr_contains("b: No such file or directory");
}
}
#[test]
fn test_stdin() {
let ts = TestScenario::new(util_name!());
assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture(INPUT_FILE).succeeds().no_stderr().stdout_str()));
}
#[test]
fn test_check() {
let ts = TestScenario::new(util_name!());
println!("File content='{}'", ts.fixtures.read(INPUT_FILE));
println!("Check file='{}'", ts.fixtures.read(CHECK_FILE));
ts.ucmd()
.args(&[DIGEST_ARG, BITS_ARG, "--check", CHECK_FILE])
.succeeds()
.no_stderr()
.stdout_is("input.txt: OK\n");
}
#[test]
fn test_zero() {
let ts = TestScenario::new(util_name!());
assert_eq!(ts.fixtures.read(EXPECTED_FILE),
get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).arg("--zero").arg(INPUT_FILE).succeeds().no_stderr().stdout_str()));
}
#[test]
fn test_missing_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "file1\n");
at.write("c", "file3\n");
ts.ucmd()
.args(&[DIGEST_ARG, BITS_ARG, "a", "b", "c"])
.fails()
.stdout_contains("a\n")
.stdout_contains("c\n")
.stderr_contains("b: No such file or directory");
}
}
)*)
};
}
test_digest! {
md5 md5 128
sha1 sha1 160
sha224 sha224 224
sha256 sha256 256
sha384 sha384 384
sha512 sha512 512
sha3_224 sha3 224
sha3_256 sha3 256
sha3_384 sha3 384
sha3_512 sha3 512
shake128_256 shake128 256
shake256_512 shake256 512
b2sum b2sum 512
b3sum b3sum 256
macro_rules! test_digest_with_len {
($id:ident, $t:ident, $size:expr) => {
mod $id {
use uutests::util::*;
use uutests::util_name;
static DIGEST_ARG: &'static str = concat!("--", stringify!($t));
static LENGTH_ARG: &'static str = concat!("--length=", stringify!($size));
static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected");
static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile");
static INPUT_FILE: &'static str = "input.txt";
#[test]
fn test_single_file() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.arg(LENGTH_ARG)
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_stdin() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.arg(LENGTH_ARG)
.pipe_in_fixture(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_check() {
let ts = TestScenario::new(util_name!());
println!("File content='{}'", ts.fixtures.read(INPUT_FILE));
println!("Check file='{}'", ts.fixtures.read(CHECK_FILE));
ts.ucmd()
.args(&[DIGEST_ARG, LENGTH_ARG, "--check", CHECK_FILE])
.succeeds()
.no_stderr()
.stdout_is("input.txt: OK\n");
}
#[test]
fn test_zero() {
let ts = TestScenario::new(util_name!());
assert_eq!(
ts.fixtures.read(EXPECTED_FILE),
get_hash!(
ts.ucmd()
.arg(DIGEST_ARG)
.arg(LENGTH_ARG)
.arg("--zero")
.arg(INPUT_FILE)
.succeeds()
.no_stderr()
.stdout_str()
)
);
}
#[test]
fn test_missing_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "file1\n");
at.write("c", "file3\n");
ts.ucmd()
.args(&[DIGEST_ARG, LENGTH_ARG, "a", "b", "c"])
.fails()
.stdout_contains("a\n")
.stdout_contains("c\n")
.stderr_contains("b: No such file or directory");
}
}
};
}
test_digest! {md5, md5}
test_digest! {sha1, sha1}
test_digest! {b3sum, b3sum}
test_digest! {shake128, shake128}
test_digest! {shake256, shake256}
test_digest_with_len! {sha224, sha224, 224}
test_digest_with_len! {sha256, sha256, 256}
test_digest_with_len! {sha384, sha384, 384}
test_digest_with_len! {sha512, sha512, 512}
test_digest_with_len! {sha3_224, sha3, 224}
test_digest_with_len! {sha3_256, sha3, 256}
test_digest_with_len! {sha3_384, sha3, 384}
test_digest_with_len! {sha3_512, sha3, 512}
#[test]
fn test_check_sha1() {
// To make sure that #3815 doesn't happen again
@ -1037,7 +1156,6 @@ fn test_sha256_binary() {
get_hash!(
ts.ucmd()
.arg("--sha256")
.arg("--bits=256")
.arg("binary.png")
.succeeds()
.no_stderr()
@ -1054,7 +1172,6 @@ fn test_sha256_stdin_binary() {
get_hash!(
ts.ucmd()
.arg("--sha256")
.arg("--bits=256")
.pipe_in_fixture("binary.png")
.succeeds()
.no_stderr()
@ -1068,12 +1185,7 @@ fn test_sha256_stdin_binary() {
#[cfg_attr(windows, ignore = "Discussion is in #9168")]
fn test_check_sha256_binary() {
new_ucmd!()
.args(&[
"--sha256",
"--bits=256",
"--check",
"binary.sha256.checkfile",
])
.args(&["--sha256", "--check", "binary.sha256.checkfile"])
.succeeds()
.no_stderr()
.stdout_is("binary.png: OK\n");

1
tests/fixtures/b2sum/b2sum.checkfile vendored Normal file
View file

@ -0,0 +1 @@
7355dd5276c21cfe0c593b5063b96af3f96a454b33216f58314f44c3ade92e9cd6cec4210a0836246780e9baf927cc50b9a3d7073e8f9bd12780fddbcb930c6d input.txt

1
tests/fixtures/b2sum/b2sum.expected vendored Normal file
View file

@ -0,0 +1 @@
7355dd5276c21cfe0c593b5063b96af3f96a454b33216f58314f44c3ade92e9cd6cec4210a0836246780e9baf927cc50b9a3d7073e8f9bd12780fddbcb930c6d

BIN
tests/fixtures/b2sum/binary.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

1
tests/fixtures/b2sum/input.txt vendored Normal file
View file

@ -0,0 +1 @@
hello, world

View file

@ -64,6 +64,10 @@ mod test_chroot;
#[path = "by-util/test_cksum.rs"]
mod test_cksum;
#[cfg(feature = "b2sum")]
#[path = "by-util/test_b2sum.rs"]
mod test_b2sum;
#[cfg(feature = "comm")]
#[path = "by-util/test_comm.rs"]
mod test_comm;