Serenity Operating System
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}