strings.c (8424B)
1 #include <kit/core.h> 2 #include <stddef.h> 3 #include <stdint.h> 4 #include <stdio.h> 5 #include <string.h> 6 7 #include "driver.h" 8 #include "env.h" 9 10 /* `kit strings` — print sequences of printable characters found in a 11 * file. Unlike most tools here it is format-agnostic: it scans the raw 12 * bytes of any input (objects, archives, or arbitrary data), matching 13 * GNU strings' default whole-file behaviour. All display logic lives 14 * here; the driver env supplies the path/stdin byte loaders. */ 15 16 #define STRINGS_TOOL "strings" 17 18 #define STRINGS_DEFAULT_MIN 4 19 20 typedef enum StringsRadix { 21 STRINGS_RAD_NONE, /* default: no offset prefix */ 22 STRINGS_RAD_OCT, /* -t o */ 23 STRINGS_RAD_DEC, /* -t d */ 24 STRINGS_RAD_HEX, /* -t x */ 25 } StringsRadix; 26 27 typedef struct StringsOpts { 28 size_t min_len; /* -n N / --bytes=N / -N ; default STRINGS_DEFAULT_MIN */ 29 StringsRadix radix; /* -t {o,d,x} */ 30 int print_filename; /* -f / --print-file-name */ 31 } StringsOpts; 32 33 /* A byte is "printable" for the purposes of a run if it is a graphic 34 * ASCII character (0x20..0x7e) or a horizontal tab, matching GNU 35 * strings' default (non-locale) classification. */ 36 static int strings_is_printable(uint8_t c) { 37 return (c >= 0x20 && c <= 0x7e) || c == '\t'; 38 } 39 40 static void strings_emit(const char* path, const StringsOpts* opts, 41 size_t offset, const uint8_t* s, size_t len) { 42 if (opts->print_filename && path) driver_printf("%s: ", path); 43 switch (opts->radix) { 44 case STRINGS_RAD_OCT: 45 driver_printf("%7llo ", (unsigned long long)offset); 46 break; 47 case STRINGS_RAD_DEC: 48 driver_printf("%7llu ", (unsigned long long)offset); 49 break; 50 case STRINGS_RAD_HEX: 51 driver_printf("%7llx ", (unsigned long long)offset); 52 break; 53 default: 54 break; 55 } 56 driver_printf("%.*s\n", (int)len, (const char*)s); 57 } 58 59 /* Scan `data` for printable runs of at least opts->min_len bytes. */ 60 static void strings_scan(const char* path, const StringsOpts* opts, 61 const uint8_t* data, size_t size) { 62 size_t i = 0; 63 size_t run_start = 0; 64 size_t run_len = 0; 65 for (i = 0; i < size; ++i) { 66 if (strings_is_printable(data[i])) { 67 if (run_len == 0) run_start = i; 68 ++run_len; 69 continue; 70 } 71 if (run_len >= opts->min_len) { 72 strings_emit(path, opts, run_start, data + run_start, run_len); 73 } 74 run_len = 0; 75 } 76 if (run_len >= opts->min_len) { 77 strings_emit(path, opts, run_start, data + run_start, run_len); 78 } 79 } 80 81 void driver_help_strings(void) { 82 driver_printf( 83 "%.*s", 84 KIT_SLICE_ARG(KIT_SLICE_LIT( 85 "kit strings — print printable character sequences in a file\n" 86 "\n" 87 "USAGE\n" 88 " kit strings [OPTIONS] [FILE...]\n" 89 "\n" 90 "DESCRIPTION\n" 91 " Scans the raw bytes of each input and prints runs of at least\n" 92 " N printable characters (default 4). Works on any file, not\n" 93 " just object files. With no FILE, or with `-`, reads stdin.\n" 94 "\n" 95 "OPTIONS\n" 96 " -n N, --bytes=N minimum run length (default 4)\n" 97 " -N shorthand for -n N (e.g. -8)\n" 98 " -t {o,d,x} print the byte offset of each string in\n" 99 " octal / decimal / hexadecimal\n" 100 " -f, --print-file-name\n" 101 " print the input file name before each string\n" 102 " -h, --help show this help\n" 103 "\n" 104 " Use -- before a leading-dash file name.\n" 105 "\n" 106 "EXAMPLES\n" 107 " kit strings -n 8 firmware.bin\n" 108 " kit strings -t x -f app.o libfoo.a\n" 109 " kit strings -n 6 < firmware.bin\n" 110 " kit strings -- -firmware.bin\n" 111 "\n" 112 "EXIT CODES\n" 113 " 0 success 1 I/O error 2 bad usage\n"))); 114 } 115 116 /* Parse the argument of -n / --bytes=. Returns 0 and stores the value on 117 * success, or non-zero on a malformed/zero value. */ 118 static int strings_parse_min(const char* s, size_t* out) { 119 size_t v = 0; 120 if (!s || !*s) return 1; 121 for (; *s; ++s) { 122 if (*s < '0' || *s > '9') return 1; 123 v = v * 10u + (size_t)(*s - '0'); 124 } 125 if (v == 0) return 1; 126 *out = v; 127 return 0; 128 } 129 130 int driver_strings(int argc, char** argv) { 131 DriverEnv env; 132 StringsOpts opts; 133 int i, rc = 1, any_input = 0, options = 1; 134 135 if (driver_argv_wants_help(argc, argv, 1)) { 136 driver_help_strings(); 137 return 0; 138 } 139 140 memset(&opts, 0, sizeof opts); 141 opts.min_len = STRINGS_DEFAULT_MIN; 142 opts.radix = STRINGS_RAD_NONE; 143 driver_env_init(&env); 144 145 /* First pass: options. */ 146 for (i = 1; i < argc; ++i) { 147 const char* a = argv[i]; 148 if (options && driver_streq(a, "--")) { 149 options = 0; 150 continue; 151 } 152 if (!options) { 153 any_input = 1; 154 continue; 155 } 156 if (driver_streq(a, "-n") || driver_streq(a, "--bytes")) { 157 if (i + 1 >= argc || strings_parse_min(argv[++i], &opts.min_len) != 0) { 158 driver_errf(STRINGS_TOOL, "option %s requires a positive integer", a); 159 rc = 2; 160 goto done; 161 } 162 continue; 163 } 164 if (strncmp(a, "--bytes=", 8) == 0) { 165 if (strings_parse_min(a + 8, &opts.min_len) != 0) { 166 driver_errf(STRINGS_TOOL, "invalid --bytes value: %s", a + 8); 167 rc = 2; 168 goto done; 169 } 170 continue; 171 } 172 if (driver_streq(a, "-t")) { 173 const char* v; 174 if (i + 1 >= argc) { 175 driver_errf(STRINGS_TOOL, "option -t requires o, d, or x"); 176 rc = 2; 177 goto done; 178 } 179 v = argv[++i]; 180 if (driver_streq(v, "o")) { 181 opts.radix = STRINGS_RAD_OCT; 182 } else if (driver_streq(v, "d")) { 183 opts.radix = STRINGS_RAD_DEC; 184 } else if (driver_streq(v, "x")) { 185 opts.radix = STRINGS_RAD_HEX; 186 } else { 187 driver_errf(STRINGS_TOOL, "invalid -t radix: %s (want o, d, or x)", v); 188 rc = 2; 189 goto done; 190 } 191 continue; 192 } 193 if (driver_streq(a, "-f") || driver_streq(a, "--print-file-name")) { 194 opts.print_filename = 1; 195 continue; 196 } 197 /* `-<digits>` is GNU shorthand for `-n <digits>`. */ 198 if (a[0] == '-' && a[1] >= '0' && a[1] <= '9') { 199 if (strings_parse_min(a + 1, &opts.min_len) != 0) { 200 driver_errf(STRINGS_TOOL, "invalid length: %s", a); 201 rc = 2; 202 goto done; 203 } 204 continue; 205 } 206 if (driver_streq(a, "-")) { 207 any_input = 1; /* stdin, handled in second pass */ 208 continue; 209 } 210 if (a[0] == '-' && a[1] != '\0') { 211 driver_errf(STRINGS_TOOL, "unknown option: %s", a); 212 rc = 2; 213 goto done; 214 } 215 any_input = 1; 216 } 217 218 /* No file operands: scan stdin. */ 219 if (!any_input) { 220 uint8_t* buf = NULL; 221 size_t n = 0; 222 if (!driver_read_stdin(&env, &buf, &n)) { 223 driver_errf(STRINGS_TOOL, "failed to read stdin"); 224 rc = 1; 225 goto done; 226 } 227 strings_scan(NULL, &opts, buf, n); 228 driver_free(&env, buf, n); 229 rc = 0; 230 goto done; 231 } 232 233 /* Second pass: inputs, in argv order. */ 234 options = 1; 235 for (i = 1; i < argc; ++i) { 236 const char* a = argv[i]; 237 if (options && driver_streq(a, "--")) { 238 options = 0; 239 continue; 240 } 241 /* Skip the options consumed above (and their values). */ 242 if (options && (driver_streq(a, "-n") || 243 driver_streq(a, "--bytes") || driver_streq(a, "-t"))) { 244 ++i; 245 continue; 246 } 247 if (options && 248 (strncmp(a, "--bytes=", 8) == 0 || driver_streq(a, "-f") || 249 driver_streq(a, "--print-file-name") || 250 (a[0] == '-' && a[1] >= '0' && a[1] <= '9'))) { 251 continue; 252 } 253 if (driver_streq(a, "-")) { 254 uint8_t* buf = NULL; 255 size_t n = 0; 256 if (!driver_read_stdin(&env, &buf, &n)) { 257 driver_errf(STRINGS_TOOL, "failed to read stdin"); 258 rc = 1; 259 goto done; 260 } 261 strings_scan(opts.print_filename ? "{standard input}" : NULL, &opts, buf, 262 n); 263 driver_free(&env, buf, n); 264 continue; 265 } 266 { 267 DriverLoad ld = {0}; 268 KitSlice input; 269 if (driver_load_bytes(&env.file_io, STRINGS_TOOL, a, &ld, &input) != 0) { 270 rc = 1; 271 goto done; 272 } 273 strings_scan(a, &opts, input.data, input.len); 274 driver_release_bytes(&env.file_io, &ld); 275 } 276 } 277 278 rc = 0; 279 done: 280 driver_env_fini(&env); 281 return rc; 282 }