Serenity Operating System
at master 128 lines 5.1 kB view raw
1/* 2 * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/LexicalPath.h> 8#include <LibCore/ArgsParser.h> 9#include <LibCore/File.h> 10#include <LibCore/System.h> 11#include <LibCrypto/Hash/HashManager.h> 12#include <LibMain/Main.h> 13#include <unistd.h> 14 15ErrorOr<int> serenity_main(Main::Arguments arguments) 16{ 17 TRY(Core::System::pledge("stdio rpath")); 18 19 auto program_name = LexicalPath::basename(arguments.strings[0]); 20 auto hash_kind = Crypto::Hash::HashKind::None; 21 22 if (program_name == "md5sum") 23 hash_kind = Crypto::Hash::HashKind::MD5; 24 else if (program_name == "sha1sum") 25 hash_kind = Crypto::Hash::HashKind::SHA1; 26 else if (program_name == "sha256sum") 27 hash_kind = Crypto::Hash::HashKind::SHA256; 28 else if (program_name == "sha512sum") 29 hash_kind = Crypto::Hash::HashKind::SHA512; 30 31 if (hash_kind == Crypto::Hash::HashKind::None) { 32 warnln("Error: program must be executed as 'md5sum', 'sha1sum', 'sha256sum' or 'sha512sum'; got '{}'", program_name); 33 exit(1); 34 } 35 36 auto hash_name = program_name.substring_view(0, program_name.length() - 3).to_deprecated_string().to_uppercase(); 37 auto paths_help_string = DeprecatedString::formatted("File(s) to print {} checksum of", hash_name); 38 39 bool verify_from_paths = false; 40 Vector<StringView> paths; 41 42 Core::ArgsParser args_parser; 43 args_parser.add_option(verify_from_paths, "Verify checksums from file(s)", "check", 'c'); 44 args_parser.add_positional_argument(paths, paths_help_string.characters(), "path", Core::ArgsParser::Required::No); 45 args_parser.parse(arguments); 46 47 if (paths.is_empty()) 48 paths.append("-"sv); 49 50 Crypto::Hash::Manager hash; 51 hash.initialize(hash_kind); 52 53 bool has_error = false; 54 int read_fail_count = 0; 55 int failed_verification_count = 0; 56 57 for (auto const& path : paths) { 58 auto file_or_error = Core::File::open_file_or_standard_stream(path, Core::File::OpenMode::Read); 59 if (file_or_error.is_error()) { 60 ++read_fail_count; 61 has_error = true; 62 warnln("{}: {}", path, file_or_error.release_error()); 63 continue; 64 } 65 auto file = file_or_error.release_value(); 66 Array<u8, PAGE_SIZE> buffer; 67 if (!verify_from_paths) { 68 while (!file->is_eof()) 69 hash.update(TRY(file->read_some(buffer))); 70 outln("{:hex-dump} {}", hash.digest().bytes(), path); 71 } else { 72 StringBuilder checksum_list_contents; 73 Array<u8, 1> checksum_list_buffer; 74 while (!file->is_eof()) 75 checksum_list_contents.append(TRY(file->read_some(checksum_list_buffer)).data()[0]); 76 Vector<StringView> const lines = checksum_list_contents.string_view().split_view("\n"sv); 77 78 for (size_t i = 0; i < lines.size(); ++i) { 79 Vector<StringView> const line = lines[i].split_view(" "sv); 80 if (line.size() != 2) { 81 ++read_fail_count; 82 // The real line number is greater than the iterator. 83 warnln("{}: {}: Failed to parse line {}", program_name, path, i + 1); 84 continue; 85 } 86 87 // line[0] = checksum 88 // line[1] = filename 89 StringView const filename = line[1]; 90 auto file_from_filename_or_error = Core::File::open_file_or_standard_stream(filename, Core::File::OpenMode::Read); 91 if (file_from_filename_or_error.is_error()) { 92 ++read_fail_count; 93 warnln("{}: {}", filename, file_from_filename_or_error.release_error()); 94 continue; 95 } 96 auto file_from_filename = file_from_filename_or_error.release_value(); 97 hash.reset(); 98 while (!file_from_filename->is_eof()) 99 hash.update(TRY(file_from_filename->read_some(buffer))); 100 if (DeprecatedString::formatted("{:hex-dump}", hash.digest().bytes()) == line[0]) 101 outln("{}: OK", filename); 102 else { 103 ++failed_verification_count; 104 warnln("{}: FAILED", filename); 105 } 106 } 107 } 108 } 109 // Print the warnings here in order to only print them once. 110 if (verify_from_paths) { 111 if (read_fail_count) { 112 if (read_fail_count == 1) 113 warnln("WARNING: 1 file could not be read"); 114 else 115 warnln("WARNING: {} files could not be read", read_fail_count); 116 has_error = true; 117 } 118 119 if (failed_verification_count) { 120 if (failed_verification_count == 1) 121 warnln("WARNING: 1 checksum did NOT match"); 122 else 123 warnln("WARNING: {} checksums did NOT match", failed_verification_count); 124 has_error = true; 125 } 126 } 127 return has_error ? 1 : 0; 128}