1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 08:24:58 +00:00
serenity/Userland/Utilities/cksum.cpp
Kemal Zebari 09a053a723 cksum: Handle edge case when no file operands are given when printing
Given no file operands, POSIX wants us to read from stdin as well
as omit "the pathname and its leading <space>" when printing.
2024-01-15 23:08:28 -07:00

117 lines
4.3 KiB
C++

/*
* Copyright (c) 2021-2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/String.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCrypto/Checksum/Adler32.h>
#include <LibCrypto/Checksum/CRC32.h>
#include <LibMain/Main.h>
#include <string.h>
struct Data {
u32 checksum { 0 };
size_t file_size { 0 };
};
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
Vector<StringView> paths;
StringView opt_algorithm;
Core::ArgsParser args_parser;
args_parser.add_option(opt_algorithm, "Checksum algorithm (default 'crc32', use 'list' to list available algorithms)", "algorithm", '\0', nullptr);
args_parser.add_positional_argument(paths, "File", "file", Core::ArgsParser::Required::No);
args_parser.parse(arguments);
auto algorithm = opt_algorithm.is_empty() ? "crc32"sv : opt_algorithm;
auto available_algorithms = Vector<StringView> { "crc32"sv, "adler32"sv };
if (algorithm == "list") {
outln("Available algorithms:");
for (auto& available_algorithm : available_algorithms) {
outln(available_algorithm);
}
exit(0);
}
Array<u8, PAGE_SIZE> buffer;
bool fail = false;
Function<Data(Core::File*, StringView path)> build_checksum_data_using_file;
if (algorithm == "crc32") {
build_checksum_data_using_file = [&buffer, &arguments, &fail](Core::File* file, StringView path) {
Crypto::Checksum::CRC32 crc32;
size_t file_size = 0;
while (!file->is_eof()) {
auto data_or_error = file->read_some(buffer);
if (data_or_error.is_error()) {
warnln("{}: Failed to read {}: {}", arguments.strings[0], path, data_or_error.error());
fail = true;
continue;
}
file_size += data_or_error.value().size();
crc32.update(data_or_error.value());
}
return Data { .checksum = crc32.digest(), .file_size = file_size };
};
} else if (algorithm == "adler32") {
build_checksum_data_using_file = [&buffer, &arguments, &fail](Core::File* file, StringView path) {
Crypto::Checksum::Adler32 adler32;
size_t file_size = 0;
while (!file->is_eof()) {
auto data_or_error = file->read_some(buffer);
if (data_or_error.is_error()) {
warnln("{}: Failed to read {}: {}", arguments.strings[0], path, data_or_error.error());
fail = true;
continue;
}
file_size += data_or_error.value().size();
adler32.update(data_or_error.value());
}
return Data { .checksum = adler32.digest(), .file_size = file_size };
};
} else {
warnln("{}: Unknown checksum algorithm: {}", arguments.strings[0], algorithm);
exit(1);
}
if (paths.is_empty()) {
// The POSIX spec explains that when given no file operands, we should read from stdin and only print the checksum and file size. So let's do
// this here.
auto file_or_error = Core::File::open_file_or_standard_stream("-"sv, Core::File::OpenMode::Read);
auto filepath = "/dev/stdin"sv;
if (file_or_error.is_error()) {
warnln("{}: {}: {}", arguments.strings[0], filepath, file_or_error.error());
exit(1);
}
auto file = file_or_error.release_value();
auto data = build_checksum_data_using_file(file.ptr(), filepath);
outln("{:08x} {}", data.checksum, data.file_size);
// We return fail here since build_checksum_data_using_file() may set it to true, indicating problems have occurred.
return fail;
}
for (auto& path : paths) {
auto file_or_error = Core::File::open_file_or_standard_stream(path, Core::File::OpenMode::Read);
auto filepath = (path == "-"sv) ? "/dev/stdin"sv : path;
if (file_or_error.is_error()) {
warnln("{}: {}: {}", arguments.strings[0], filepath, file_or_error.error());
fail = true;
continue;
}
auto file = file_or_error.release_value();
auto data = build_checksum_data_using_file(file.ptr(), path);
outln("{:08x} {} {}", data.checksum, data.file_size, path);
}
return fail;
}