mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
Merge pull request #7192 from RenjiSann/cksum-fix-error-handling
cksum: Update error and flags handling to improver GNU's match
This commit is contained in:
commit
5129aba0f0
4 changed files with 545 additions and 91 deletions
|
@ -13,8 +13,9 @@ use std::iter;
|
|||
use std::path::Path;
|
||||
use uucore::checksum::{
|
||||
calculate_blake2b_length, detect_algo, digest_reader, perform_checksum_validation,
|
||||
ChecksumError, ChecksumOptions, ALGORITHM_OPTIONS_BLAKE2B, ALGORITHM_OPTIONS_BSD,
|
||||
ALGORITHM_OPTIONS_CRC, ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV, SUPPORTED_ALGORITHMS,
|
||||
ChecksumError, ChecksumOptions, ChecksumVerbose, ALGORITHM_OPTIONS_BLAKE2B,
|
||||
ALGORITHM_OPTIONS_BSD, ALGORITHM_OPTIONS_CRC, ALGORITHM_OPTIONS_CRC32B, ALGORITHM_OPTIONS_SYSV,
|
||||
SUPPORTED_ALGORITHMS,
|
||||
};
|
||||
use uucore::{
|
||||
encoding,
|
||||
|
@ -322,13 +323,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|| iter::once(OsStr::new("-")).collect::<Vec<_>>(),
|
||||
|files| files.map(OsStr::new).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let verbose = ChecksumVerbose::new(status, quiet, warn);
|
||||
|
||||
let opts = ChecksumOptions {
|
||||
binary: binary_flag,
|
||||
ignore_missing,
|
||||
quiet,
|
||||
status,
|
||||
strict,
|
||||
warn,
|
||||
verbose,
|
||||
};
|
||||
|
||||
return perform_checksum_validation(files.iter().copied(), algo_option, length, opts);
|
||||
|
@ -462,19 +464,22 @@ pub fn uu_app() -> Command {
|
|||
.short('w')
|
||||
.long("warn")
|
||||
.help("warn about improperly formatted checksum lines")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::STATUS, options::QUIET]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::STATUS)
|
||||
.long("status")
|
||||
.help("don't output anything, status code shows success")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::WARN, options::QUIET]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::QUIET)
|
||||
.long(options::QUIET)
|
||||
.help("don't print OK for each successfully verified file")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::WARN, options::STATUS]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::IGNORE_MISSING)
|
||||
|
|
|
@ -24,6 +24,7 @@ use uucore::checksum::escape_filename;
|
|||
use uucore::checksum::perform_checksum_validation;
|
||||
use uucore::checksum::ChecksumError;
|
||||
use uucore::checksum::ChecksumOptions;
|
||||
use uucore::checksum::ChecksumVerbose;
|
||||
use uucore::checksum::HashAlgorithm;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::sum::{Digest, Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256};
|
||||
|
@ -240,13 +241,14 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
|||
|| iter::once(OsStr::new("-")).collect::<Vec<_>>(),
|
||||
|files| files.map(OsStr::new).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let verbose = ChecksumVerbose::new(status, quiet, warn);
|
||||
|
||||
let opts = ChecksumOptions {
|
||||
binary,
|
||||
ignore_missing,
|
||||
quiet,
|
||||
status,
|
||||
strict,
|
||||
warn,
|
||||
verbose,
|
||||
};
|
||||
|
||||
// Execute the checksum validation
|
||||
|
@ -356,14 +358,16 @@ pub fn uu_app_common() -> Command {
|
|||
.short('q')
|
||||
.long(options::QUIET)
|
||||
.help("don't print OK for each successfully verified file")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::STATUS, options::WARN]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::STATUS)
|
||||
.short('s')
|
||||
.long("status")
|
||||
.help("don't output anything, status code shows success")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::QUIET, options::WARN]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::STRICT)
|
||||
|
@ -382,7 +386,8 @@ pub fn uu_app_common() -> Command {
|
|||
.short('w')
|
||||
.long("warn")
|
||||
.help("warn about improperly formatted checksum lines")
|
||||
.action(ArgAction::SetTrue),
|
||||
.action(ArgAction::SetTrue)
|
||||
.overrides_with_all([options::QUIET, options::STATUS]),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("zero")
|
||||
|
|
|
@ -19,7 +19,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
error::{set_exit_code, FromIo, UError, UResult, USimpleError},
|
||||
error::{FromIo, UError, UResult, USimpleError},
|
||||
os_str_as_bytes, os_str_from_bytes, read_os_string_lines, show, show_error, show_warning_caps,
|
||||
sum::{
|
||||
Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224,
|
||||
|
@ -130,10 +130,12 @@ impl From<ChecksumError> for LineCheckError {
|
|||
enum FileCheckError {
|
||||
/// a generic UError was encountered in sub-functions
|
||||
UError(Box<dyn UError>),
|
||||
/// the checksum file is improperly formatted.
|
||||
ImproperlyFormatted,
|
||||
/// reading of the checksum file failed
|
||||
CantOpenChecksumFile,
|
||||
/// processing of the file is considered as a failure regarding the
|
||||
/// provided flags. This however does not stop the processing of
|
||||
/// further files.
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl From<Box<dyn UError>> for FileCheckError {
|
||||
|
@ -148,15 +150,57 @@ impl From<ChecksumError> for FileCheckError {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)]
|
||||
pub enum ChecksumVerbose {
|
||||
Status,
|
||||
Quiet,
|
||||
Normal,
|
||||
Warning,
|
||||
}
|
||||
|
||||
impl ChecksumVerbose {
|
||||
pub fn new(status: bool, quiet: bool, warn: bool) -> Self {
|
||||
use ChecksumVerbose::*;
|
||||
|
||||
// Assume only one of the three booleans will be enabled at once.
|
||||
// This is ensured by clap's overriding arguments.
|
||||
match (status, quiet, warn) {
|
||||
(true, _, _) => Status,
|
||||
(_, true, _) => Quiet,
|
||||
(_, _, true) => Warning,
|
||||
_ => Normal,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn over_status(self) -> bool {
|
||||
self > Self::Status
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn over_quiet(self) -> bool {
|
||||
self > Self::Quiet
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn at_least_warning(self) -> bool {
|
||||
self >= Self::Warning
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChecksumVerbose {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct regroups CLI flags.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct ChecksumOptions {
|
||||
pub binary: bool,
|
||||
pub ignore_missing: bool,
|
||||
pub quiet: bool,
|
||||
pub status: bool,
|
||||
pub strict: bool,
|
||||
pub warn: bool,
|
||||
pub verbose: ChecksumVerbose,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -235,20 +279,19 @@ pub fn create_sha3(bits: Option<usize>) -> UResult<HashAlgorithm> {
|
|||
}
|
||||
|
||||
#[allow(clippy::comparison_chain)]
|
||||
fn cksum_output(res: &ChecksumResult, status: bool) {
|
||||
fn print_cksum_report(res: &ChecksumResult) {
|
||||
if res.bad_format == 1 {
|
||||
show_warning_caps!("{} line is improperly formatted", res.bad_format);
|
||||
} else if res.bad_format > 1 {
|
||||
show_warning_caps!("{} lines are improperly formatted", res.bad_format);
|
||||
}
|
||||
|
||||
if !status {
|
||||
if res.failed_cksum == 1 {
|
||||
show_warning_caps!("{} computed checksum did NOT match", res.failed_cksum);
|
||||
} else if res.failed_cksum > 1 {
|
||||
show_warning_caps!("{} computed checksums did NOT match", res.failed_cksum);
|
||||
}
|
||||
if res.failed_cksum == 1 {
|
||||
show_warning_caps!("{} computed checksum did NOT match", res.failed_cksum);
|
||||
} else if res.failed_cksum > 1 {
|
||||
show_warning_caps!("{} computed checksums did NOT match", res.failed_cksum);
|
||||
}
|
||||
|
||||
if res.failed_open_file == 1 {
|
||||
show_warning_caps!("{} listed file could not be read", res.failed_open_file);
|
||||
} else if res.failed_open_file > 1 {
|
||||
|
@ -284,10 +327,10 @@ impl FileChecksumResult {
|
|||
|
||||
/// The cli options might prevent to display on the outcome of the
|
||||
/// comparison on STDOUT.
|
||||
fn can_display(&self, opts: ChecksumOptions) -> bool {
|
||||
fn can_display(&self, verbose: ChecksumVerbose) -> bool {
|
||||
match self {
|
||||
FileChecksumResult::Ok => !opts.status && !opts.quiet,
|
||||
FileChecksumResult::Failed => !opts.status,
|
||||
FileChecksumResult::Ok => verbose.over_quiet(),
|
||||
FileChecksumResult::Failed => verbose.over_status(),
|
||||
FileChecksumResult::CantOpen => true,
|
||||
}
|
||||
}
|
||||
|
@ -310,9 +353,9 @@ fn print_file_report<W: Write>(
|
|||
filename: &[u8],
|
||||
result: FileChecksumResult,
|
||||
prefix: &str,
|
||||
opts: ChecksumOptions,
|
||||
verbose: ChecksumVerbose,
|
||||
) {
|
||||
if result.can_display(opts) {
|
||||
if result.can_display(verbose) {
|
||||
let _ = write!(w, "{prefix}");
|
||||
let _ = w.write_all(filename);
|
||||
let _ = writeln!(w, ": {result}");
|
||||
|
@ -589,7 +632,7 @@ fn get_file_to_check(
|
|||
filename_bytes,
|
||||
FileChecksumResult::CantOpen,
|
||||
"",
|
||||
opts,
|
||||
opts.verbose,
|
||||
);
|
||||
};
|
||||
match File::open(filename) {
|
||||
|
@ -648,12 +691,11 @@ fn get_input_file(filename: &OsStr) -> UResult<Box<dyn Read>> {
|
|||
fn identify_algo_name_and_length(
|
||||
line_info: &LineInfo,
|
||||
algo_name_input: Option<&str>,
|
||||
last_algo: &mut Option<String>,
|
||||
) -> Option<(String, Option<usize>)> {
|
||||
let algorithm = line_info
|
||||
.algo_name
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.to_lowercase();
|
||||
let algo_from_line = line_info.algo_name.clone().unwrap_or_default();
|
||||
let algorithm = algo_from_line.to_lowercase();
|
||||
*last_algo = Some(algo_from_line);
|
||||
|
||||
// check if we are called with XXXsum (example: md5sum) but we detected a different algo parsing the file
|
||||
// (for example SHA1 (f) = d...)
|
||||
|
@ -711,7 +753,7 @@ fn compute_and_check_digest_from_file(
|
|||
filename,
|
||||
FileChecksumResult::from_bool(checksum_correct),
|
||||
prefix,
|
||||
opts,
|
||||
opts.verbose,
|
||||
);
|
||||
|
||||
if checksum_correct {
|
||||
|
@ -726,11 +768,13 @@ fn process_algo_based_line(
|
|||
line_info: &LineInfo,
|
||||
cli_algo_name: Option<&str>,
|
||||
opts: ChecksumOptions,
|
||||
last_algo: &mut Option<String>,
|
||||
) -> Result<(), LineCheckError> {
|
||||
let filename_to_check = line_info.filename.as_slice();
|
||||
|
||||
let (algo_name, algo_byte_len) = identify_algo_name_and_length(line_info, cli_algo_name)
|
||||
.ok_or(LineCheckError::ImproperlyFormatted)?;
|
||||
let (algo_name, algo_byte_len) =
|
||||
identify_algo_name_and_length(line_info, cli_algo_name, last_algo)
|
||||
.ok_or(LineCheckError::ImproperlyFormatted)?;
|
||||
|
||||
// If the digest bitlen is known, we can check the format of the expected
|
||||
// checksum with it.
|
||||
|
@ -789,13 +833,13 @@ fn process_non_algo_based_line(
|
|||
/// matched the expected.
|
||||
/// If the comparison didn't happen, return a `LineChecksumError`.
|
||||
fn process_checksum_line(
|
||||
filename_input: &OsStr,
|
||||
line: &OsStr,
|
||||
i: usize,
|
||||
cli_algo_name: Option<&str>,
|
||||
cli_algo_length: Option<usize>,
|
||||
opts: ChecksumOptions,
|
||||
cached_regex: &mut Option<LineFormat>,
|
||||
last_algo: &mut Option<String>,
|
||||
) -> Result<(), LineCheckError> {
|
||||
let line_bytes = os_str_as_bytes(line)?;
|
||||
|
||||
|
@ -806,34 +850,19 @@ fn process_checksum_line(
|
|||
|
||||
// Use `LineInfo` to extract the data of a line.
|
||||
// Then, depending on its format, apply a different pre-treatment.
|
||||
if let Some(line_info) = LineInfo::parse(line, cached_regex) {
|
||||
if line_info.format == LineFormat::AlgoBased {
|
||||
process_algo_based_line(&line_info, cli_algo_name, opts)
|
||||
} else if let Some(cli_algo) = cli_algo_name {
|
||||
// If we match a non-algo based regex, we expect a cli argument
|
||||
// to give us the algorithm to use
|
||||
process_non_algo_based_line(i, &line_info, cli_algo, cli_algo_length, opts)
|
||||
} else {
|
||||
// We have no clue of what algorithm to use
|
||||
return Err(LineCheckError::ImproperlyFormatted);
|
||||
}
|
||||
} else {
|
||||
if opts.warn {
|
||||
let algo = if let Some(algo_name_input) = cli_algo_name {
|
||||
algo_name_input.to_uppercase()
|
||||
} else {
|
||||
"Unknown algorithm".to_string()
|
||||
};
|
||||
eprintln!(
|
||||
"{}: {}: {}: improperly formatted {} checksum line",
|
||||
util_name(),
|
||||
&filename_input.maybe_quote(),
|
||||
i + 1,
|
||||
algo
|
||||
);
|
||||
}
|
||||
let Some(line_info) = LineInfo::parse(line, cached_regex) else {
|
||||
return Err(LineCheckError::ImproperlyFormatted);
|
||||
};
|
||||
|
||||
Err(LineCheckError::ImproperlyFormatted)
|
||||
if line_info.format == LineFormat::AlgoBased {
|
||||
process_algo_based_line(&line_info, cli_algo_name, opts, last_algo)
|
||||
} else if let Some(cli_algo) = cli_algo_name {
|
||||
// If we match a non-algo based regex, we expect a cli argument
|
||||
// to give us the algorithm to use
|
||||
process_non_algo_based_line(i, &line_info, cli_algo, cli_algo_length, opts)
|
||||
} else {
|
||||
// We have no clue of what algorithm to use
|
||||
return Err(LineCheckError::ImproperlyFormatted);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -856,7 +885,6 @@ fn process_checksum_file(
|
|||
Err(e) => {
|
||||
// Could not read the file, show the error and continue to the next file
|
||||
show_error!("{e}");
|
||||
set_exit_code(1);
|
||||
return Err(FileCheckError::CantOpenChecksumFile);
|
||||
}
|
||||
}
|
||||
|
@ -868,16 +896,20 @@ fn process_checksum_file(
|
|||
// cached_regex is used to ensure that several non algo-based checksum line
|
||||
// will use the same regex.
|
||||
let mut cached_regex = None;
|
||||
// last_algo caches the algorithm used in the last line to print a warning
|
||||
// message for the current line if improperly formatted.
|
||||
// Behavior tested in gnu_cksum_c::test_warn
|
||||
let mut last_algo = None;
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
let line_result = process_checksum_line(
|
||||
filename_input,
|
||||
line,
|
||||
i,
|
||||
cli_algo_name,
|
||||
cli_algo_length,
|
||||
opts,
|
||||
&mut cached_regex,
|
||||
&mut last_algo,
|
||||
);
|
||||
|
||||
// Match a first time to elude critical UErrors, and increment the total
|
||||
|
@ -893,7 +925,26 @@ fn process_checksum_file(
|
|||
match line_result {
|
||||
Ok(()) => res.correct += 1,
|
||||
Err(DigestMismatch) => res.failed_cksum += 1,
|
||||
Err(ImproperlyFormatted) => res.bad_format += 1,
|
||||
Err(ImproperlyFormatted) => {
|
||||
res.bad_format += 1;
|
||||
|
||||
if opts.verbose.at_least_warning() {
|
||||
let algo = if let Some(algo_name_input) = cli_algo_name {
|
||||
Cow::Owned(algo_name_input.to_uppercase())
|
||||
} else if let Some(algo) = &last_algo {
|
||||
Cow::Borrowed(algo.as_str())
|
||||
} else {
|
||||
Cow::Borrowed("Unknown algorithm")
|
||||
};
|
||||
eprintln!(
|
||||
"{}: {}: {}: improperly formatted {} checksum line",
|
||||
util_name(),
|
||||
&filename_input.maybe_quote(),
|
||||
i + 1,
|
||||
algo
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(CantOpenFile | FileIsDirectory) => res.failed_open_file += 1,
|
||||
Err(FileNotFound) if !opts.ignore_missing => res.failed_open_file += 1,
|
||||
_ => continue,
|
||||
|
@ -903,36 +954,43 @@ fn process_checksum_file(
|
|||
// not a single line correctly formatted found
|
||||
// return an error
|
||||
if res.total_properly_formatted() == 0 {
|
||||
if !opts.status {
|
||||
if opts.verbose.over_status() {
|
||||
log_no_properly_formatted(get_filename_for_output(filename_input, input_is_stdin));
|
||||
}
|
||||
set_exit_code(1);
|
||||
return Err(FileCheckError::ImproperlyFormatted);
|
||||
return Err(FileCheckError::Failed);
|
||||
}
|
||||
|
||||
// if any incorrectly formatted line, show it
|
||||
cksum_output(&res, opts.status);
|
||||
if opts.verbose.over_status() {
|
||||
print_cksum_report(&res);
|
||||
}
|
||||
|
||||
if opts.ignore_missing && res.correct == 0 {
|
||||
// we have only bad format
|
||||
// and we had ignore-missing
|
||||
eprintln!(
|
||||
"{}: {}: no file was verified",
|
||||
util_name(),
|
||||
filename_input.maybe_quote(),
|
||||
);
|
||||
set_exit_code(1);
|
||||
if opts.verbose.over_status() {
|
||||
eprintln!(
|
||||
"{}: {}: no file was verified",
|
||||
util_name(),
|
||||
filename_input.maybe_quote(),
|
||||
);
|
||||
}
|
||||
return Err(FileCheckError::Failed);
|
||||
}
|
||||
|
||||
// strict means that we should have an exit code.
|
||||
if opts.strict && res.bad_format > 0 {
|
||||
set_exit_code(1);
|
||||
return Err(FileCheckError::Failed);
|
||||
}
|
||||
|
||||
// if we have any failed checksum verification, we set an exit code
|
||||
// except if we have ignore_missing
|
||||
if (res.failed_cksum > 0 || res.failed_open_file > 0) && !opts.ignore_missing {
|
||||
set_exit_code(1);
|
||||
// If a file was missing, return an error unless we explicitly ignore it.
|
||||
if res.failed_open_file > 0 && !opts.ignore_missing {
|
||||
return Err(FileCheckError::Failed);
|
||||
}
|
||||
|
||||
// Obviously, if a checksum failed at some point, report the error.
|
||||
if res.failed_cksum > 0 {
|
||||
return Err(FileCheckError::Failed);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -950,16 +1008,23 @@ pub fn perform_checksum_validation<'a, I>(
|
|||
where
|
||||
I: Iterator<Item = &'a OsStr>,
|
||||
{
|
||||
let mut failed = false;
|
||||
|
||||
// if cksum has several input files, it will print the result for each file
|
||||
for filename_input in files {
|
||||
use FileCheckError::*;
|
||||
match process_checksum_file(filename_input, algo_name_input, length_input, opts) {
|
||||
Err(UError(e)) => return Err(e),
|
||||
Err(CantOpenChecksumFile | ImproperlyFormatted) | Ok(_) => continue,
|
||||
Err(Failed | CantOpenChecksumFile) => failed = true,
|
||||
Ok(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if failed {
|
||||
Err(USimpleError::new(1, ""))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn digest_reader<T: Read>(
|
||||
|
@ -1416,7 +1481,7 @@ mod tests {
|
|||
|
||||
for (filename, result, prefix, expected) in cases {
|
||||
let mut buffer: Vec<u8> = vec![];
|
||||
print_file_report(&mut buffer, filename, *result, prefix, opts);
|
||||
print_file_report(&mut buffer, filename, *result, prefix, opts.verbose);
|
||||
assert_eq!(&buffer, expected)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb
|
||||
// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb dont
|
||||
|
||||
use crate::common::util::TestScenario;
|
||||
|
||||
|
@ -1284,6 +1284,18 @@ fn test_several_files_error_mgmt() {
|
|||
.stderr_contains("incorrect: no properly ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_unknown_checksum_file() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("missing")
|
||||
.fails()
|
||||
.stderr_only("cksum: missing: No such file or directory\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_comment_line() {
|
||||
// A comment in a checksum file shall be discarded unnoticed.
|
||||
|
@ -1811,6 +1823,373 @@ mod gnu_cksum_base64 {
|
|||
}
|
||||
}
|
||||
|
||||
/// This module reimplements the cksum-c.sh GNU test.
|
||||
mod gnu_cksum_c {
|
||||
use super::*;
|
||||
|
||||
const INVALID_SUM: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaafdb57c725157cb40b5aee8d937b8351477e";
|
||||
|
||||
fn make_scene() -> TestScenario {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.write("input", "9\n7\n1\n4\n2\n6\n3\n5\n8\n10\n");
|
||||
|
||||
let algos: &[&[&str]] = &[
|
||||
&["-a", "sha384"],
|
||||
&["-a", "blake2b"],
|
||||
&["-a", "blake2b", "-l", "384"],
|
||||
&["-a", "sm3"],
|
||||
];
|
||||
|
||||
for args in algos {
|
||||
let result = scene.ucmd().args(args).succeeds();
|
||||
let stdout = result.stdout();
|
||||
at.append_bytes("CHECKSUMS", stdout);
|
||||
}
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_signed_checksums() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_individual_digests_in_mixed_file() {
|
||||
let scene = make_scene();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("-a")
|
||||
.arg("sm3")
|
||||
.arg("CHECKSUMS")
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_against_older_non_hex_formats() {
|
||||
let scene = make_scene();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-c")
|
||||
.arg("-a")
|
||||
.arg("crc")
|
||||
.arg("CHECKSUMS")
|
||||
.fails();
|
||||
|
||||
let crc_cmd = scene.ucmd().arg("-a").arg("crc").arg("input").succeeds();
|
||||
let crc_cmd_out = crc_cmd.stdout();
|
||||
scene.fixtures.write_bytes("CHECKSUMS.crc", crc_cmd_out);
|
||||
|
||||
scene.ucmd().arg("-c").arg("CHECKSUMS.crc").fails();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status() {
|
||||
let scene = make_scene();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.succeeds()
|
||||
.no_output();
|
||||
}
|
||||
|
||||
fn make_scene_with_comment() -> TestScenario {
|
||||
let scene = make_scene();
|
||||
|
||||
scene
|
||||
.fixtures
|
||||
.append("CHECKSUMS", "# Very important comment\n");
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_with_comment() {
|
||||
let scene = make_scene_with_comment();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.succeeds()
|
||||
.no_output();
|
||||
}
|
||||
|
||||
fn make_scene_with_invalid_line() -> TestScenario {
|
||||
let scene = make_scene_with_comment();
|
||||
|
||||
scene.fixtures.append("CHECKSUMS", "invalid_line\n");
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_strict() {
|
||||
let scene = make_scene_with_invalid_line();
|
||||
|
||||
// without strict, succeeds
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.succeeds()
|
||||
.stderr_contains("1 line is improperly formatted");
|
||||
|
||||
// with strict, fails
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--strict")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stderr_contains("1 line is improperly formatted");
|
||||
}
|
||||
|
||||
fn make_scene_with_two_invalid_lines() -> TestScenario {
|
||||
let scene = make_scene_with_comment();
|
||||
|
||||
scene
|
||||
.fixtures
|
||||
.append("CHECKSUMS", "invalid_line\ninvalid_line\n");
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_strict_plural_checks() {
|
||||
let scene = make_scene_with_two_invalid_lines();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--strict")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stderr_contains("2 lines are improperly formatted");
|
||||
}
|
||||
|
||||
fn make_scene_with_incorrect_checksum() -> TestScenario {
|
||||
let scene = make_scene_with_two_invalid_lines();
|
||||
|
||||
scene
|
||||
.fixtures
|
||||
.append("CHECKSUMS", &format!("SM3 (input) = {INVALID_SUM}\n"));
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_with_incorrect_checksum() {
|
||||
let scene = make_scene_with_incorrect_checksum();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stdout_contains("input: FAILED")
|
||||
.stderr_contains("1 computed checksum did NOT match");
|
||||
|
||||
// also fails with strict
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--strict")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stdout_contains("input: FAILED")
|
||||
.stderr_contains("1 computed checksum did NOT match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_with_errors() {
|
||||
let scene = make_scene_with_incorrect_checksum();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.no_output();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_with_non_existing_file() {
|
||||
let scene = make_scene();
|
||||
scene
|
||||
.fixtures
|
||||
.write("CHECKSUMS2", &format!("SM3 (input2) = {INVALID_SUM}\n"));
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS2")
|
||||
.fails()
|
||||
.stdout_contains("input2: FAILED open or read")
|
||||
.stderr_contains("1 listed file could not be read");
|
||||
|
||||
// also fails with strict
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--strict")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS2")
|
||||
.fails()
|
||||
.stdout_contains("input2: FAILED open or read")
|
||||
.stderr_contains("1 listed file could not be read");
|
||||
}
|
||||
|
||||
fn make_scene_with_another_improperly_formatted() -> TestScenario {
|
||||
let scene = make_scene_with_incorrect_checksum();
|
||||
|
||||
scene.fixtures.append(
|
||||
"CHECKSUMS",
|
||||
&format!("BLAKE2b (missing-file) = {INVALID_SUM}\n"),
|
||||
);
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_warn() {
|
||||
let scene = make_scene_with_another_improperly_formatted();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--warn")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.run()
|
||||
.stderr_contains("CHECKSUMS: 6: improperly formatted SM3 checksum line")
|
||||
.stderr_contains("CHECKSUMS: 9: improperly formatted BLAKE2b checksum line");
|
||||
}
|
||||
|
||||
fn make_scene_with_checksum_missing() -> TestScenario {
|
||||
let scene = make_scene_with_another_improperly_formatted();
|
||||
|
||||
scene.fixtures.write(
|
||||
"CHECKSUMS-missing",
|
||||
&format!("SM3 (nonexistent) = {INVALID_SUM}\n"),
|
||||
);
|
||||
|
||||
scene
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_missing() {
|
||||
let scene = make_scene_with_checksum_missing();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--ignore-missing")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS-missing")
|
||||
.fails()
|
||||
.stdout_does_not_contain("nonexistent: No such file or directory")
|
||||
.stdout_does_not_contain("nonexistent: FAILED open or read")
|
||||
.stderr_contains("CHECKSUMS-missing: no file was verified");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_and_warn() {
|
||||
let scene = make_scene_with_checksum_missing();
|
||||
|
||||
// --status before --warn
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--warn")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stderr_contains("CHECKSUMS: 9: improperly formatted BLAKE2b checksum line")
|
||||
.stderr_contains("WARNING: 3 lines are improperly formatted")
|
||||
.stderr_contains("WARNING: 1 computed checksum did NOT match");
|
||||
|
||||
// --warn before --status (status hides the results)
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--warn")
|
||||
.arg("--status")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.stderr_does_not_contain("CHECKSUMS: 9: improperly formatted BLAKE2b checksum line")
|
||||
.stderr_does_not_contain("WARNING: 3 lines are improperly formatted")
|
||||
.stderr_does_not_contain("WARNING: 1 computed checksum did NOT match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_and_ignore_missing() {
|
||||
let scene = make_scene_with_checksum_missing();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--ignore-missing")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS")
|
||||
.fails()
|
||||
.no_output();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_warn_and_ignore_missing() {
|
||||
let scene = make_scene_with_checksum_missing();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--status")
|
||||
.arg("--warn")
|
||||
.arg("--ignore-missing")
|
||||
.arg("--check")
|
||||
.arg("CHECKSUMS-missing")
|
||||
.fails()
|
||||
.stderr_contains("CHECKSUMS-missing: no file was verified")
|
||||
.stdout_does_not_contain("nonexistent: No such file or directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_several_files_dont_exist() {
|
||||
let scene = make_scene();
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("non-existing-1")
|
||||
.arg("non-existing-2")
|
||||
.fails()
|
||||
.stderr_contains("non-existing-1: No such file or directory")
|
||||
.stderr_contains("non-existing-2: No such file or directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_several_files_empty() {
|
||||
let scene = make_scene();
|
||||
scene.fixtures.touch("empty-1");
|
||||
scene.fixtures.touch("empty-2");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--check")
|
||||
.arg("empty-1")
|
||||
.arg("empty-2")
|
||||
.fails()
|
||||
.stderr_contains("empty-1: no properly formatted checksum lines found")
|
||||
.stderr_contains("empty-2: no properly formatted checksum lines found");
|
||||
}
|
||||
}
|
||||
|
||||
/// The tests in this module check the behavior of cksum when given different
|
||||
/// checksum formats and algorithms in the same file, while specifying an
|
||||
/// algorithm on CLI or not.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue