mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
chksum: add support of --check --algorithm=xxx
This commit is contained in:
parent
0b04bcaf9a
commit
1aec8b407d
2 changed files with 254 additions and 56 deletions
|
@ -75,10 +75,10 @@ impl Display for CkSumError {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_algo(
|
fn detect_algo(
|
||||||
program: &str,
|
algo: &str,
|
||||||
length: Option<usize>,
|
length: Option<usize>,
|
||||||
) -> (&'static str, Box<dyn Digest + 'static>, usize) {
|
) -> (&'static str, Box<dyn Digest + 'static>, usize) {
|
||||||
match program {
|
match algo {
|
||||||
ALGORITHM_OPTIONS_SYSV => (
|
ALGORITHM_OPTIONS_SYSV => (
|
||||||
ALGORITHM_OPTIONS_SYSV,
|
ALGORITHM_OPTIONS_SYSV,
|
||||||
Box::new(SYSV::new()) as Box<dyn Digest>,
|
Box::new(SYSV::new()) as Box<dyn Digest>,
|
||||||
|
@ -411,48 +411,99 @@ fn handle_tag_text_binary_flags(matches: &clap::ArgMatches) -> UResult<(bool, bo
|
||||||
/***
|
/***
|
||||||
* Do the checksum validation (can be strict or not)
|
* Do the checksum validation (can be strict or not)
|
||||||
*/
|
*/
|
||||||
fn perform_checksum_validation<'a, I>(files: I, strict: bool) -> UResult<()>
|
fn perform_checksum_validation<'a, I>(
|
||||||
|
files: I,
|
||||||
|
strict: bool,
|
||||||
|
algo_name_input: Option<&str>,
|
||||||
|
) -> UResult<()>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a OsStr>,
|
I: Iterator<Item = &'a OsStr>,
|
||||||
{
|
{
|
||||||
let re = Regex::new(r"(?P<algo>\w+)(-(?P<bits>\d+))? \((?P<filename>.*)\) = (?P<checksum>.*)")
|
// Regexp to handle the two input formats:
|
||||||
.unwrap();
|
// 1. <algo>[-<bits>] (<filename>) = <checksum>
|
||||||
let mut properly_formatted = false;
|
// 2. <checksum> [* ]<filename>
|
||||||
let mut bad_format = 0;
|
let regex_pattern = r"^(?P<algo>\w+)(-(?P<bits>\d+))?\s?\((?P<filename1>.*)\) = (?P<checksum1>[a-fA-F0-9]+)$|^(?P<checksum2>[a-fA-F0-9]+)\s[* ](?P<filename2>.*)";
|
||||||
let mut failed_cksum = 0;
|
let re = Regex::new(regex_pattern).unwrap();
|
||||||
let mut failed_open_file = 0;
|
|
||||||
|
// if cksum has several input files, it will print the result for each file
|
||||||
for filename_input in files {
|
for filename_input in files {
|
||||||
|
let mut bad_format = 0;
|
||||||
|
let mut failed_cksum = 0;
|
||||||
|
let mut failed_open_file = 0;
|
||||||
|
let mut properly_formatted = false;
|
||||||
let input_is_stdin = filename_input == OsStr::new("-");
|
let input_is_stdin = filename_input == OsStr::new("-");
|
||||||
|
|
||||||
let file: Box<dyn Read> = if input_is_stdin {
|
let file: Box<dyn Read> = if input_is_stdin {
|
||||||
Box::new(stdin()) // Use stdin if "-" is specified
|
Box::new(stdin()) // Use stdin if "-" is specified
|
||||||
} else {
|
} else {
|
||||||
match File::open(filename_input) {
|
match File::open(filename_input) {
|
||||||
Ok(f) => Box::new(f),
|
Ok(f) => Box::new(f),
|
||||||
Err(err) => {
|
Err(_) => {
|
||||||
show!(err.map_err_context(|| format!(
|
return Err(io::Error::new(
|
||||||
"Failed to open file: {}",
|
io::ErrorKind::Other,
|
||||||
filename_input.to_string_lossy()
|
format!(
|
||||||
)));
|
"{}: No such file or directory",
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Failed to open file").into());
|
filename_input.to_string_lossy()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
||||||
|
// for each line in the input, check if it is a valid checksum line
|
||||||
for line in reader.lines() {
|
for line in reader.lines() {
|
||||||
let line = line?;
|
let line = line.unwrap_or_else(|_| String::new());
|
||||||
|
|
||||||
if let Some(caps) = re.captures(&line) {
|
if let Some(caps) = re.captures(&line) {
|
||||||
properly_formatted = true;
|
properly_formatted = true;
|
||||||
let algo_name = caps.name("algo").unwrap().as_str().to_lowercase();
|
|
||||||
let filename_to_check = caps.name("filename").unwrap().as_str();
|
|
||||||
let expected_checksum = caps.name("checksum").unwrap().as_str();
|
|
||||||
|
|
||||||
let length = caps
|
// Determine what kind of file input we had
|
||||||
.name("bits")
|
// we need it for case "--check -a sm3 <file>" when <file> is
|
||||||
.map(|m| m.as_str().parse::<usize>().unwrap() / 8);
|
// <algo>[-<bits>] (<filename>) = <checksum>
|
||||||
let (_, mut algo, bits) = detect_algo(&algo_name, length);
|
let algo_based_format =
|
||||||
|
caps.name("filename1").is_some() && caps.name("checksum1").is_some();
|
||||||
|
|
||||||
|
let filename_to_check = caps
|
||||||
|
.name("filename1")
|
||||||
|
.or(caps.name("filename2"))
|
||||||
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
let expected_checksum = caps
|
||||||
|
.name("checksum1")
|
||||||
|
.or(caps.name("checksum2"))
|
||||||
|
.unwrap()
|
||||||
|
.as_str();
|
||||||
|
|
||||||
|
// If the algo_name is provided, we use it, otherwise we try to detect it
|
||||||
|
let algo_details = if algo_based_format {
|
||||||
|
// When the algo-based format is matched, extract details from regex captures
|
||||||
|
let algorithm = caps.name("algo").map_or("", |m| m.as_str()).to_lowercase();
|
||||||
|
let bits = caps
|
||||||
|
.name("bits")
|
||||||
|
.map(|m| m.as_str().parse::<usize>().unwrap() / 8);
|
||||||
|
(algorithm, bits)
|
||||||
|
} else if let Some(a) = algo_name_input {
|
||||||
|
// When a specific algorithm name is input, use it and default bits to None
|
||||||
|
(a.to_lowercase(), None)
|
||||||
|
} else {
|
||||||
|
// Default case if no algorithm is specified and non-algo based format is matched
|
||||||
|
(String::new(), None)
|
||||||
|
};
|
||||||
|
if algo_based_format
|
||||||
|
&& algo_name_input.map_or(false, |input| algo_details.0 != input)
|
||||||
|
{
|
||||||
|
bad_format += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if algo_details.0.is_empty() {
|
||||||
|
// we haven't been able to detect the algo name. No point to continue
|
||||||
|
properly_formatted = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (_, mut algo, bits) = detect_algo(&algo_details.0, algo_details.1);
|
||||||
|
|
||||||
|
// manage the input file
|
||||||
let file_to_check: Box<dyn Read> = if filename_to_check == "-" {
|
let file_to_check: Box<dyn Read> = if filename_to_check == "-" {
|
||||||
Box::new(stdin()) // Use stdin if "-" is specified in the checksum file
|
Box::new(stdin()) // Use stdin if "-" is specified in the checksum file
|
||||||
} else {
|
} else {
|
||||||
|
@ -470,9 +521,11 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut file_reader = BufReader::new(file_to_check);
|
let mut file_reader = BufReader::new(file_to_check);
|
||||||
|
// Read the file and calculate the checksum
|
||||||
|
let (calculated_checksum, _) =
|
||||||
|
digest_read(&mut algo, &mut file_reader, bits).unwrap();
|
||||||
|
|
||||||
let (calculated_checksum, _) = digest_read(&mut algo, &mut file_reader, bits)
|
// Do the checksum validation
|
||||||
.map_err_context(|| "failed to read input".to_string())?;
|
|
||||||
if expected_checksum == calculated_checksum {
|
if expected_checksum == calculated_checksum {
|
||||||
println!("{}: OK", filename_to_check);
|
println!("{}: OK", filename_to_check);
|
||||||
} else {
|
} else {
|
||||||
|
@ -483,31 +536,29 @@ where
|
||||||
bad_format += 1;
|
bad_format += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not a single line correctly formatted found
|
||||||
|
// return an error
|
||||||
|
if !properly_formatted {
|
||||||
|
uucore::show_error!(
|
||||||
|
"{}: no properly formatted checksum lines found",
|
||||||
|
filename_input.to_string_lossy()
|
||||||
|
);
|
||||||
|
set_exit_code(1);
|
||||||
|
}
|
||||||
|
// strict means that we should have an exit code.
|
||||||
|
if strict && bad_format > 0 {
|
||||||
|
set_exit_code(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have any failed checksum verification, we set an exit code
|
||||||
|
if failed_cksum > 0 || failed_open_file > 0 {
|
||||||
|
set_exit_code(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any incorrectly formatted line, show it
|
||||||
|
cksum_output(bad_format, failed_cksum, failed_open_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not a single line correctly formatted found
|
|
||||||
// return an error
|
|
||||||
if !properly_formatted {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"no properly formatted checksum lines found",
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// strict means that we should have an exit code.
|
|
||||||
if strict && bad_format > 0 {
|
|
||||||
set_exit_code(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we have any failed checksum verification, we set an exit code
|
|
||||||
if failed_cksum > 0 || failed_open_file > 0 {
|
|
||||||
set_exit_code(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if any incorrectly formatted line, show it
|
|
||||||
cksum_output(bad_format, failed_cksum, failed_open_file);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,10 +600,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
// Determine the appropriate algorithm option to pass
|
||||||
|
let algo_option = if algo_name.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(algo_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Execute the checksum validation based on the presence of files or the use of stdin
|
||||||
return match matches.get_many::<String>(options::FILE) {
|
return match matches.get_many::<String>(options::FILE) {
|
||||||
Some(files) => perform_checksum_validation(files.map(OsStr::new), strict),
|
Some(files) => perform_checksum_validation(files.map(OsStr::new), strict, algo_option),
|
||||||
None => perform_checksum_validation(iter::once(OsStr::new("-")), strict),
|
None => perform_checksum_validation(iter::once(OsStr::new("-")), strict, algo_option),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -472,6 +472,21 @@ fn test_all_algorithms_fail_on_folder() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_check_error_incorrect_format() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.write("checksum", "e5773576fc75ff0f8eba14f61587ae28 README.md");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-c")
|
||||||
|
.arg("checksum")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("no properly formatted checksum lines found");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dev_null() {
|
fn test_dev_null() {
|
||||||
|
@ -674,13 +689,13 @@ fn test_check_algo_err() {
|
||||||
|
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--a")
|
.arg("-a")
|
||||||
.arg("sm3")
|
.arg("sm3")
|
||||||
.arg("--check")
|
.arg("--check")
|
||||||
.arg("f")
|
.arg("f")
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("cksum: no properly formatted checksum lines found")
|
.stderr_contains("cksum: f: no properly formatted checksum lines found")
|
||||||
.code_is(1);
|
.code_is(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -705,14 +720,16 @@ fn test_cksum_check() {
|
||||||
.arg("--check")
|
.arg("--check")
|
||||||
.arg("CHECKSUM")
|
.arg("CHECKSUM")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n");
|
.stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n")
|
||||||
|
.stderr_does_not_contain("line is improperly formatted");
|
||||||
scene
|
scene
|
||||||
.ucmd()
|
.ucmd()
|
||||||
.arg("--check")
|
.arg("--check")
|
||||||
.arg("--strict")
|
.arg("--strict")
|
||||||
.arg("CHECKSUM")
|
.arg("CHECKSUM")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n");
|
.stdout_contains("f: OK\nf: OK\nf: OK\nf: OK\n")
|
||||||
|
.stderr_does_not_contain("line is improperly formatted");
|
||||||
// inject invalid content
|
// inject invalid content
|
||||||
at.append("CHECKSUM", "incorrect data");
|
at.append("CHECKSUM", "incorrect data");
|
||||||
scene
|
scene
|
||||||
|
@ -805,4 +822,127 @@ fn test_cksum_check_failed() {
|
||||||
.stderr_str()
|
.stderr_str()
|
||||||
.contains("1 listed file could not be read\n"));
|
.contains("1 listed file could not be read\n"));
|
||||||
assert!(result.stdout_str().contains("f: OK\n"));
|
assert!(result.stdout_str().contains("f: OK\n"));
|
||||||
|
|
||||||
|
// tests with two files
|
||||||
|
at.touch("CHECKSUM2");
|
||||||
|
at.write("f2", "42");
|
||||||
|
for command in &commands {
|
||||||
|
let result = scene.ucmd().args(command).arg("f2").succeeds();
|
||||||
|
at.append("CHECKSUM2", result.stdout_str());
|
||||||
|
}
|
||||||
|
// inject invalid content
|
||||||
|
at.append("CHECKSUM2", "again incorrect data\naze\nSM3 (input2) = 7cfb120d4fabea2a904948538a438fdb57c725157cb40b5aee8d937b8351477e\n");
|
||||||
|
at.append("CHECKSUM2", "again incorrect data\naze\nSM3 (input2) = 7cfb120d4fabea2a904948538a438fdb57c725157cb40b5aee8d937b8351477e\n");
|
||||||
|
|
||||||
|
let result = scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--check")
|
||||||
|
.arg("CHECKSUM")
|
||||||
|
.arg("CHECKSUM2")
|
||||||
|
.fails();
|
||||||
|
println!("result.stderr_str() {}", result.stderr_str());
|
||||||
|
println!("result.stdout_str() {}", result.stdout_str());
|
||||||
|
assert!(result.stderr_str().contains("Failed to open file: input2"));
|
||||||
|
assert!(result
|
||||||
|
.stderr_str()
|
||||||
|
.contains("4 lines are improperly formatted\n"));
|
||||||
|
assert!(result
|
||||||
|
.stderr_str()
|
||||||
|
.contains("2 listed files could not be read\n"));
|
||||||
|
assert!(result.stdout_str().contains("f: OK\n"));
|
||||||
|
assert!(result.stdout_str().contains("2: OK\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_md5_format() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch("empty");
|
||||||
|
at.write("f", "d41d8cd98f00b204e9800998ecf8427e *empty\n");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-a")
|
||||||
|
.arg("md5")
|
||||||
|
.arg("--check")
|
||||||
|
.arg("f")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains("empty: OK");
|
||||||
|
|
||||||
|
// with a second file
|
||||||
|
at.write("not-empty", "42");
|
||||||
|
at.write("f2", "a1d0c6e83f027327d8461063f4ac58a6 *not-empty\n");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-a")
|
||||||
|
.arg("md5")
|
||||||
|
.arg("--check")
|
||||||
|
.arg("f")
|
||||||
|
.arg("f2")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains("empty: OK")
|
||||||
|
.stdout_contains("not-empty: OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage the mixed behavior
|
||||||
|
// cksum --check -a sm3 CHECKSUMS
|
||||||
|
// when CHECKSUM contains among other lines:
|
||||||
|
// SHA384 (input) = f392fd0ae43879ced890c665a1d47179116b5eddf6fb5b49f4982746418afdcbd54ba5eedcd422af3592f57f666da285
|
||||||
|
#[test]
|
||||||
|
fn test_cksum_mixed() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
let commands = [
|
||||||
|
vec!["-a", "sha384"],
|
||||||
|
vec!["-a", "blake2b"],
|
||||||
|
vec!["-a", "blake2b", "-l", "384"],
|
||||||
|
vec!["-a", "sm3"],
|
||||||
|
];
|
||||||
|
at.touch("f");
|
||||||
|
at.touch("CHECKSUM");
|
||||||
|
for command in &commands {
|
||||||
|
let result = scene.ucmd().args(command).arg("f").succeeds();
|
||||||
|
at.append("CHECKSUM", result.stdout_str());
|
||||||
|
}
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--check")
|
||||||
|
.arg("-a")
|
||||||
|
.arg("sm3")
|
||||||
|
.arg("CHECKSUM")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_contains("f: OK")
|
||||||
|
.stderr_contains("3 lines are improperly formatted");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cksum_garbage() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
// Incorrect data at the start
|
||||||
|
at.write(
|
||||||
|
"check-file",
|
||||||
|
"garbage MD5 (README.md) = e5773576fc75ff0f8eba14f61587ae28",
|
||||||
|
);
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--check")
|
||||||
|
.arg("check-file")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("check-file: no properly formatted checksum lines found");
|
||||||
|
|
||||||
|
// Incorrect data at the end
|
||||||
|
at.write(
|
||||||
|
"check-file",
|
||||||
|
"MD5 (README.md) = e5773576fc75ff0f8eba14f61587ae28 garbage",
|
||||||
|
);
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("--check")
|
||||||
|
.arg("check-file")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("check-file: no properly formatted checksum lines found");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue