From 480e517f03519fde6e1f0c589cec6959d480ce96 Mon Sep 17 00:00:00 2001 From: implicitfield <114500360+implicitfield@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:29:05 +0300 Subject: [PATCH] checksum: Support the '--check' option This commit also updates 'checksum' to use the Core::Stream::File API. --- Base/usr/share/man/man1/checksum.md | 16 ++++-- Userland/Utilities/checksum.cpp | 84 ++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/Base/usr/share/man/man1/checksum.md b/Base/usr/share/man/man1/checksum.md index 14f27bda039..befa8cfeabb 100644 --- a/Base/usr/share/man/man1/checksum.md +++ b/Base/usr/share/man/man1/checksum.md @@ -4,12 +4,18 @@ checksum - helper program for calculating checksums ## Synopsis -`$ md5sum ` -`$ sha1sum ` -`$ sha256sum ` -`$ sha512sum ` +`$ md5sum [options...] ` +`$ sha1sum [options...] ` +`$ sha256sum [options...] ` +`$ sha512sum [options...] ` ## Description 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. diff --git a/Userland/Utilities/checksum.cpp b/Userland/Utilities/checksum.cpp index bbe0f205e05..d69a0edfb49 100644 --- a/Userland/Utilities/checksum.cpp +++ b/Userland/Utilities/checksum.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -36,9 +36,11 @@ ErrorOr serenity_main(Main::Arguments arguments) 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); + bool verify_from_paths = false; Vector paths; 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.parse(arguments); @@ -48,23 +50,79 @@ ErrorOr serenity_main(Main::Arguments arguments) Crypto::Hash::Manager hash; 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) { - NonnullRefPtr file = Core::File::standard_input(); - if (path != "-"sv) { - auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly); - if (file_or_error.is_error()) { - warnln("{}: {}: {}", program_name, path, file->error_string()); - has_error = true; - continue; + auto file_or_error = Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read); + if (file_or_error.is_error()) { + ++read_fail_count; + has_error = true; + warnln("{}: {}", path, file_or_error.release_error()); + continue; + } + auto file = file_or_error.release_value(); + Array 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 checksum_list_buffer; + while (!file->is_eof()) + checksum_list_contents.append(TRY(file->read(checksum_list_buffer)).data()[0]); + Vector const lines = checksum_list_contents.string_view().split_view("\n"sv); + + for (size_t i = 0; i < lines.size(); ++i) { + Vector 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()) - hash.update(file->read(PAGE_SIZE)); - outln("{:hex-dump} {}", hash.digest().bytes(), path); + if (failed_verification_count) { + if (failed_verification_count == 1) + 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; }