Serenity Operating System
at master 139 lines 4.7 kB view raw
1/* 2 * Copyright (c) 2022, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/CharacterTypes.h> 8#include <AK/Forward.h> 9#include <LibCore/ArgsParser.h> 10#include <LibCore/File.h> 11#include <LibCore/System.h> 12#include <LibMain/Main.h> 13#include <unistd.h> 14 15enum class StringOffsetFormat { 16 None = 0, 17 Decimal, 18 Octal, 19 Hexadecimal 20}; 21 22// NOTE: This is similar to how the cat utility works in the sense of aggregating 23// data in 32K buffer. 24static constexpr size_t buffer_read_size = 32768; 25 26static bool should_print_characters(Vector<u8> const& characters) 27{ 28 for (u8 ch : characters) { 29 if (is_ascii_printable(ch) && !is_ascii_space(ch)) 30 return true; 31 } 32 return false; 33} 34 35static void print_characters(Vector<u8> const& characters, StringOffsetFormat string_offset_format, size_t string_offset_position) 36{ 37 switch (string_offset_format) { 38 case StringOffsetFormat::Decimal: 39 out("{:>7d} ", string_offset_position); 40 break; 41 case StringOffsetFormat::Octal: 42 out("{:>7o} ", string_offset_position); 43 break; 44 case StringOffsetFormat::Hexadecimal: 45 out("{:>7x} ", string_offset_position); 46 break; 47 default: 48 break; 49 } 50 outln("{:s}", characters.span()); 51} 52 53static int process_characters_in_span(Vector<u8>& characters, ReadonlyBytes span) 54{ 55 int processed_characters = 0; 56 for (u8 ch : span) { 57 ++processed_characters; 58 if (is_ascii_printable(ch) || ch == '\t') 59 characters.append(ch); 60 else 61 break; 62 } 63 return processed_characters; 64} 65 66static ErrorOr<void> process_strings_in_file(StringView path, bool show_paths, StringOffsetFormat string_offset_format, size_t minimum_string_length) 67{ 68 Array<u8, buffer_read_size> buffer; 69 Vector<u8> output_characters; 70 auto file = TRY(Core::File::open_file_or_standard_stream(path, Core::File::OpenMode::Read)); 71 size_t processed_characters = 0; 72 size_t string_offset_position = 0; 73 bool did_show_path = false; 74 while (!file->is_eof()) { 75 auto buffer_span = TRY(file->read_some(buffer)); 76 while (!buffer_span.is_empty()) { 77 string_offset_position += processed_characters; 78 processed_characters = process_characters_in_span(output_characters, buffer_span); 79 if (show_paths && !did_show_path) { 80 outln("path {}:", path); 81 did_show_path = true; 82 } 83 if (output_characters.size() >= minimum_string_length && should_print_characters(output_characters)) { 84 print_characters(output_characters, string_offset_format, string_offset_position); 85 } 86 buffer_span = buffer_span.slice(processed_characters); 87 output_characters.clear(); 88 } 89 } 90 return {}; 91} 92 93ErrorOr<int> serenity_main(Main::Arguments arguments) 94{ 95 TRY(Core::System::pledge("stdio rpath")); 96 97 Vector<StringView> paths; 98 size_t minimum_string_length = 4; 99 bool show_paths = false; 100 101 StringOffsetFormat string_offset_format { StringOffsetFormat::None }; 102 103 Core::ArgsParser args_parser; 104 args_parser.add_option(minimum_string_length, "Specify the minimum string length.", nullptr, 'n', "number"); 105 args_parser.add_option(show_paths, "Display the path for each matched file.", nullptr, 'p'); 106 args_parser.add_option({ Core::ArgsParser::OptionArgumentMode::Required, 107 "Write offset relative to start of each file in (d)ec, (o)ct, or he(x) format.", 108 nullptr, 109 't', 110 "format", 111 [&string_offset_format](StringView value) { 112 if (value == "d") { 113 string_offset_format = StringOffsetFormat::Decimal; 114 } else if (value == "o") { 115 string_offset_format = StringOffsetFormat::Octal; 116 } else if (value == "x") { 117 string_offset_format = StringOffsetFormat::Hexadecimal; 118 } else { 119 return false; 120 } 121 return true; 122 } }); 123 args_parser.set_general_help("Write the sequences of printable characters in files or pipes to stdout."); 124 args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No); 125 args_parser.parse(arguments); 126 127 if (minimum_string_length < 1) { 128 warnln("Invalid minimum string length {}", minimum_string_length); 129 return 1; 130 } 131 132 if (paths.is_empty()) 133 paths.append("-"sv); 134 135 for (auto const& path : paths) 136 TRY(process_strings_in_file(path, show_paths, string_offset_format, minimum_string_length)); 137 138 return 0; 139}