serenity/Userland/Utilities/hexdump.cpp
Eli Youngs 7cd43deb28 hexdump: Replace Core::File with Core::Stream::File
Previously, hexdump used Core::File to read input into a fixed buffer.
This PR rewrites the file handling to use the more modern
Core::Stream::File, which reads data into spans. By using spans, we
can also simplify the rest of the code, which previously used memcpy
for array manipulation and relied on manual bookkeeping to keep track of
offsets.
2022-11-26 11:07:00 +01:00

129 lines
3.5 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Eli Youngs <eli.m.youngs@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/Stream.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <ctype.h>
#include <string.h>
static constexpr size_t LINE_LENGTH_BYTES = 16;
enum class State {
Print,
PrintFiller,
SkipPrint
};
ErrorOr<int> serenity_main(Main::Arguments args)
{
TRY(Core::System::pledge("stdio rpath"));
Core::ArgsParser args_parser;
StringView path;
bool verbose = false;
Optional<size_t> max_bytes;
args_parser.add_positional_argument(path, "Input", "input", Core::ArgsParser::Required::No);
args_parser.add_option(verbose, "Display all input data", "verbose", 'v');
args_parser.add_option(max_bytes, "Truncate to a fixed number of bytes", nullptr, 'n', "bytes");
args_parser.parse(args);
auto file = TRY(Core::Stream::File::open_file_or_standard_stream(path, Core::Stream::OpenMode::Read));
auto print_line = [](Bytes line) {
VERIFY(line.size() <= LINE_LENGTH_BYTES);
for (size_t i = 0; i < LINE_LENGTH_BYTES; ++i) {
if (i < line.size())
out("{:02x} ", line[i]);
else
out(" ");
if (i == 7)
out(" ");
}
out(" |");
for (auto const& byte : line) {
if (isprint(byte))
putchar(byte);
else
putchar('.');
}
putchar('|');
putchar('\n');
};
Array<u8, BUFSIZ> contents;
Bytes bytes;
Bytes previous_line;
static_assert(LINE_LENGTH_BYTES * 2 <= contents.size(), "Buffer is too small?!");
size_t total_bytes_read = 0;
auto state = State::Print;
bool is_input_remaining = true;
while (is_input_remaining) {
auto bytes_to_read = contents.size() - bytes.size();
if (max_bytes.has_value()) {
auto bytes_remaining = max_bytes.value() - total_bytes_read;
if (bytes_remaining < bytes_to_read) {
bytes_to_read = bytes_remaining;
is_input_remaining = false;
}
}
bytes = contents.span().slice(0, bytes_to_read);
bytes = TRY(file->read(bytes));
total_bytes_read += bytes.size();
if (bytes.size() < bytes_to_read) {
is_input_remaining = false;
}
while (bytes.size() > LINE_LENGTH_BYTES) {
auto current_line = bytes.slice(0, LINE_LENGTH_BYTES);
bytes = bytes.slice(LINE_LENGTH_BYTES);
if (verbose) {
print_line(current_line);
continue;
}
bool is_same_contents = (current_line == previous_line);
if (!is_same_contents)
state = State::Print;
else if (is_same_contents && (state != State::SkipPrint))
state = State::PrintFiller;
// Coalesce repeating lines
switch (state) {
case State::Print:
print_line(current_line);
break;
case State::PrintFiller:
outln("*");
state = State::SkipPrint;
break;
case State::SkipPrint:
break;
}
previous_line = current_line;
}
}
if (bytes.size() > 0)
print_line(bytes);
return 0;
}