checksum: Support the '--check' option

This commit also updates 'checksum' to use the Core::Stream::File API.
This commit is contained in:
implicitfield 2022-10-06 17:29:05 +03:00 committed by Andrew Kaster
parent 340a2a96a4
commit 480e517f03
2 changed files with 82 additions and 18 deletions

View file

@ -4,12 +4,18 @@ checksum - helper program for calculating checksums
## Synopsis ## Synopsis
`$ md5sum <file>` `$ md5sum [options...] <file...>`
`$ sha1sum <file>` `$ sha1sum [options...] <file...>`
`$ sha256sum <file>` `$ sha256sum [options...] <file...>`
`$ sha512sum <file>` `$ sha512sum [options...] <file...>`
## Description ## Description
This program calculates and print specified checksum of files. It cannot be run directly, only This program calculates and print specified checksum of files. It cannot be run directly, only
as `md5sum`, `sha1sum`, `sha256sum` or `sha512sum`. as `md5sum`, `sha1sum`, `sha256sum` or `sha512sum`. A non-zero exit code is returned if the
input cannot be read. If the '--check' option is used, a non-zero exit code is also returned
if the checksums cannot be verified.
## Options
* `-c`, `--check`: Verify checksums against `file` or stdin.

View file

@ -6,7 +6,7 @@
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
#include <LibCore/ArgsParser.h> #include <LibCore/ArgsParser.h>
#include <LibCore/File.h> #include <LibCore/Stream.h>
#include <LibCore/System.h> #include <LibCore/System.h>
#include <LibCrypto/Hash/HashManager.h> #include <LibCrypto/Hash/HashManager.h>
#include <LibMain/Main.h> #include <LibMain/Main.h>
@ -36,9 +36,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
auto hash_name = program_name.substring_view(0, program_name.length() - 3).to_string().to_uppercase(); auto hash_name = program_name.substring_view(0, program_name.length() - 3).to_string().to_uppercase();
auto paths_help_string = String::formatted("File(s) to print {} checksum of", hash_name); auto paths_help_string = String::formatted("File(s) to print {} checksum of", hash_name);
bool verify_from_paths = false;
Vector<StringView> paths; Vector<StringView> paths;
Core::ArgsParser args_parser; Core::ArgsParser args_parser;
args_parser.add_option(verify_from_paths, "Verify checksums from file(s)", "check", 'c');
args_parser.add_positional_argument(paths, paths_help_string.characters(), "path", Core::ArgsParser::Required::No); args_parser.add_positional_argument(paths, paths_help_string.characters(), "path", Core::ArgsParser::Required::No);
args_parser.parse(arguments); args_parser.parse(arguments);
@ -48,23 +50,79 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
Crypto::Hash::Manager hash; Crypto::Hash::Manager hash;
hash.initialize(hash_kind); hash.initialize(hash_kind);
auto has_error = false; bool has_error = false;
int read_fail_count = 0;
int failed_verification_count = 0;
for (auto const& path : paths) { for (auto const& path : paths) {
NonnullRefPtr<Core::File> file = Core::File::standard_input(); auto file_or_error = Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read);
if (path != "-"sv) { if (file_or_error.is_error()) {
auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly); ++read_fail_count;
if (file_or_error.is_error()) { has_error = true;
warnln("{}: {}: {}", program_name, path, file->error_string()); warnln("{}: {}", path, file_or_error.release_error());
has_error = true; continue;
continue; }
auto file = file_or_error.release_value();
Array<u8, PAGE_SIZE> buffer;
if (!verify_from_paths) {
while (!file->is_eof())
hash.update(TRY(file->read(buffer)));
outln("{:hex-dump} {}", hash.digest().bytes(), path);
} else {
StringBuilder checksum_list_contents;
Array<u8, 1> checksum_list_buffer;
while (!file->is_eof())
checksum_list_contents.append(TRY(file->read(checksum_list_buffer)).data()[0]);
Vector<StringView> const lines = checksum_list_contents.string_view().split_view("\n"sv);
for (size_t i = 0; i < lines.size(); ++i) {
Vector<StringView> const line = lines[i].split_view(" "sv);
if (line.size() != 2) {
++read_fail_count;
// The real line number is greater than the iterator.
warnln("{}: {}: Failed to parse line {}", program_name, path, i + 1);
continue;
}
// line[0] = checksum
// line[1] = filename
StringView const filename = line[1];
auto file_from_filename_or_error = Core::Stream::File::open_file_or_standard_stream(filename, Core::Stream::OpenMode::Read);
if (file_from_filename_or_error.is_error()) {
++read_fail_count;
warnln("{}: {}", filename, file_from_filename_or_error.release_error());
continue;
}
auto file_from_filename = file_from_filename_or_error.release_value();
hash.reset();
while (!file_from_filename->is_eof())
hash.update(TRY(file_from_filename->read(buffer)));
if (String::formatted("{:hex-dump}", hash.digest().bytes()) == line[0])
outln("{}: OK", filename);
else {
++failed_verification_count;
warnln("{}: FAILED", filename);
}
} }
file = file_or_error.release_value(); }
}
// Print the warnings here in order to only print them once.
if (verify_from_paths) {
if (read_fail_count) {
if (read_fail_count == 1)
warnln("WARNING: 1 file could not be read");
else
warnln("WARNING: {} files could not be read", read_fail_count);
has_error = true;
} }
while (!file->eof() && !file->has_error()) if (failed_verification_count) {
hash.update(file->read(PAGE_SIZE)); if (failed_verification_count == 1)
outln("{:hex-dump} {}", hash.digest().bytes(), path); warnln("WARNING: 1 checksum did NOT match");
else
warnln("WARNING: {} checksums did NOT match", failed_verification_count);
has_error = true;
}
} }
return has_error ? 1 : 0; return has_error ? 1 : 0;
} }