xxd.c (13218B)
1 #include <kit/core.h> 2 #include <stddef.h> 3 #include <stdint.h> 4 #include <string.h> 5 6 #include "driver.h" 7 #include "env.h" 8 9 /* `kit xxd` — hex dump any file, and reverse a dump back to binary. A GNU 10 * xxd subset: default hex+ASCII dump, plus -r (reverse), -p (plain hex), 11 * -i (C array), -c/-g (columns/grouping), -s/-l (seek/length), -u (uppercase). 12 * Unlike `objdump -s` it is format-agnostic: it dumps raw bytes of any input. 13 * With no FILE, or `-`, reads stdin; output goes to stdout or -o FILE. */ 14 15 #define XXD_TOOL "xxd" 16 17 #define XXD_COLS_DUMP 16u 18 #define XXD_COLS_PLAIN 30u 19 #define XXD_COLS_INCLUDE 12u 20 21 typedef enum XxdMode { 22 XXD_DUMP = 0, /* default hex+ASCII */ 23 XXD_PLAIN, /* -p: continuous hex */ 24 XXD_INCLUDE, /* -i: C array */ 25 } XxdMode; 26 27 typedef struct XxdOpts { 28 XxdMode mode; 29 int reverse; /* -r */ 30 int uppercase; /* -u */ 31 size_t cols; /* -c; 0 = mode default */ 32 size_t group; /* -g; 0 = default (2) */ 33 uint64_t seek; /* -s */ 34 uint64_t limit; /* -l; 0 = no limit */ 35 int have_limit; /* whether -l was given */ 36 const char* in; /* input path, or NULL = stdin */ 37 const char* out; /* output path, or NULL = stdout */ 38 } XxdOpts; 39 40 /* Streaming output buffer over a KitWriter; flushes when full so cols can be 41 * arbitrarily large. */ 42 typedef struct Xb { 43 char buf[8192]; 44 size_t n; 45 KitWriter* w; 46 int err; 47 } Xb; 48 49 static void xb_flush(Xb* b) { 50 if (b->n) { 51 if (kit_writer_write(b->w, b->buf, b->n) != KIT_OK) b->err = 1; 52 b->n = 0; 53 } 54 } 55 static void xb_c(Xb* b, char c) { 56 if (b->n == sizeof b->buf) xb_flush(b); 57 b->buf[b->n++] = c; 58 } 59 static void xb_s(Xb* b, const char* s) { 60 while (*s) xb_c(b, *s++); 61 } 62 static void xb_hex2(Xb* b, uint8_t v, const char* hx) { 63 xb_c(b, hx[v >> 4]); 64 xb_c(b, hx[v & 0x0f]); 65 } 66 static void xb_hexnum(Xb* b, uint64_t v, int width, const char* hx) { 67 char t[16]; 68 int i; 69 if (width > 16) width = 16; 70 for (i = width - 1; i >= 0; --i) { 71 t[i] = hx[v & 0x0f]; 72 v >>= 4; 73 } 74 for (i = 0; i < width; ++i) xb_c(b, t[i]); 75 } 76 static void xb_dec(Xb* b, uint64_t v) { 77 char t[24]; 78 int i = 0; 79 if (v == 0) { 80 xb_c(b, '0'); 81 return; 82 } 83 while (v) { 84 t[i++] = (char)('0' + (int)(v % 10)); 85 v /= 10; 86 } 87 while (i) xb_c(b, t[--i]); 88 } 89 90 void driver_help_xxd(void) { 91 driver_printf( 92 "%.*s", 93 KIT_SLICE_ARG(KIT_SLICE_LIT( 94 "kit xxd — hex dump a file (and reverse a dump back to binary)\n" 95 "\n" 96 "USAGE\n" 97 " kit xxd [OPTIONS] [INFILE [OUTFILE]]\n" 98 "\n" 99 "DESCRIPTION\n" 100 " Dumps the raw bytes of INFILE (or stdin) as a hex+ASCII table.\n" 101 " Works on any file, not just objects. `xxd f | xxd -r` " 102 "round-trips\n" 103 " contiguous data back to the original bytes.\n" 104 "\n" 105 "OPTIONS\n" 106 " -r reverse: read a hex dump, write binary\n" 107 " -p plain hex dump (continuous, no offsets/ASCII)\n" 108 " -i output a C `unsigned char[]` array\n" 109 " -c N N bytes per line (default 16; -p 30; -i 12)\n" 110 " -g N group hex into N-byte columns (default 2)\n" 111 " -s OFF start at byte OFF (decimal or 0x-hex)\n" 112 " -l LEN dump at most LEN bytes\n" 113 " -u uppercase hex digits\n" 114 " -o FILE write output to FILE instead of stdout\n" 115 " -h, --help show this help\n" 116 "\n" 117 "NOTE\n" 118 " -r reconstructs contiguous data; leading offsets are read for\n" 119 " context but not used to seek/pad sparse output.\n" 120 " Use -- before a leading-dash input.\n" 121 "\n" 122 "EXAMPLES\n" 123 " kit xxd firmware.bin\n" 124 " kit xxd -p -u firmware.bin > firmware.hex\n" 125 " kit xxd -i -o firmware.inc firmware.bin\n" 126 " kit xxd firmware.bin | kit xxd -r -o firmware.copy\n" 127 " printf '4142430a\\n' | kit xxd -r -p > abc.txt\n" 128 "\n" 129 "EXIT CODES\n" 130 " 0 success 1 I/O error 2 bad usage\n"))); 131 } 132 133 static int xxd_is_hex(int c) { 134 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || 135 (c >= 'A' && c <= 'F'); 136 } 137 138 /* Derive a C identifier from a file path: basename, non-alnum -> '_', a leading 139 * digit gets an '_' prefix. Writes into out (cap bytes). */ 140 static void xxd_ident(const char* path, char* out, size_t cap) { 141 const char* base = driver_basename(path); 142 size_t i = 0; 143 if (cap == 0) return; 144 if (base[0] >= '0' && base[0] <= '9' && i + 1 < cap) out[i++] = '_'; 145 for (; *base && i + 1 < cap; ++base) { 146 char c = *base; 147 int ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || 148 (c >= '0' && c <= '9'); 149 out[i++] = ok ? c : '_'; 150 } 151 out[i] = '\0'; 152 } 153 154 static void xxd_emit_dump(Xb* b, const uint8_t* data, size_t len, uint64_t base, 155 size_t cols, size_t group, const char* hx) { 156 size_t off; 157 for (off = 0; off < len; off += cols) { 158 size_t linelen = (len - off < cols) ? (len - off) : cols; 159 size_t j; 160 xb_hexnum(b, base + off, 8, hx); 161 xb_s(b, ": "); 162 for (j = 0; j < cols; ++j) { 163 if (j < linelen) 164 xb_hex2(b, data[off + j], hx); 165 else { 166 xb_c(b, ' '); 167 xb_c(b, ' '); 168 } 169 if ((j + 1) % group == 0) xb_c(b, ' '); 170 } 171 if (cols % group != 0) xb_c(b, ' '); 172 xb_c(b, ' '); 173 for (j = 0; j < linelen; ++j) { 174 uint8_t c = data[off + j]; 175 xb_c(b, (c >= 0x20 && c <= 0x7e) ? (char)c : '.'); 176 } 177 xb_c(b, '\n'); 178 } 179 } 180 181 static void xxd_emit_plain(Xb* b, const uint8_t* data, size_t len, size_t cols, 182 const char* hx) { 183 size_t i; 184 for (i = 0; i < len; ++i) { 185 xb_hex2(b, data[i], hx); 186 if ((i + 1) % cols == 0) xb_c(b, '\n'); 187 } 188 if (len == 0 || len % cols != 0) xb_c(b, '\n'); 189 } 190 191 static void xxd_emit_include(Xb* b, const uint8_t* data, size_t len, 192 const char* ident, size_t cols, const char* hx) { 193 size_t i; 194 xb_s(b, "unsigned char "); 195 if (ident && ident[0]) xb_s(b, ident); 196 xb_s(b, "[] = {\n"); 197 for (i = 0; i < len; ++i) { 198 if (i % cols == 0) xb_s(b, " "); 199 xb_s(b, "0x"); 200 xb_hex2(b, data[i], hx); 201 if (i + 1 < len) xb_c(b, ','); 202 if ((i + 1) % cols == 0 || i + 1 == len) 203 xb_c(b, '\n'); 204 else 205 xb_c(b, ' '); 206 } 207 xb_s(b, "};\n"); 208 xb_s(b, "unsigned int "); 209 if (ident && ident[0]) xb_s(b, ident); 210 xb_s(b, "_len = "); 211 xb_dec(b, len); 212 xb_s(b, ";\n"); 213 } 214 215 /* Reverse a hex dump back to raw bytes. plain=1 treats the whole input as 216 * continuous hex; otherwise each line is `[offset:] hex... ascii` and parsing 217 * stops at the double-space gutter. */ 218 static void xxd_reverse(Xb* b, const uint8_t* data, size_t len, int plain) { 219 size_t i = 0; 220 while (i < len) { 221 size_t eol = i; 222 size_t s, p; 223 int hi = -1; 224 while (eol < len && data[eol] != '\n') ++eol; 225 s = i; 226 if (!plain) { 227 size_t c; 228 for (c = i; c < eol; ++c) { 229 if (data[c] == ':') { 230 s = c + 1; 231 break; 232 } 233 } 234 } 235 for (p = s; p < eol; ++p) { 236 unsigned char ch = data[p]; 237 if (ch == ' ' || ch == '\t') { 238 if (!plain && p + 1 < eol && 239 (data[p + 1] == ' ' || data[p + 1] == '\t')) 240 break; /* gutter before the ASCII column */ 241 continue; 242 } 243 if (xxd_is_hex(ch)) { 244 if (hi < 0) { 245 hi = driver_hex_nibble((char)ch); 246 } else { 247 xb_c(b, (char)((hi << 4) | driver_hex_nibble((char)ch))); 248 hi = -1; 249 } 250 continue; 251 } 252 break; /* non-hex, non-space: rest of line is ASCII/junk */ 253 } 254 i = (eol < len) ? eol + 1 : eol; 255 } 256 } 257 258 /* Extract the value for a short option that may be attached (-c16) or separate 259 * (-c 16). On success advances *i past a consumed separate arg and returns the 260 * value string; returns NULL on a missing argument. */ 261 static const char* xxd_optval(const char* a, int argc, char** argv, int* i) { 262 if (a[2] != '\0') return a + 2; 263 if (*i + 1 >= argc) return NULL; 264 return argv[++(*i)]; 265 } 266 267 int driver_xxd(int argc, char** argv) { 268 DriverEnv env; 269 KitContext ctx; 270 XxdOpts o; 271 Xb b; 272 KitWriter* w = NULL; 273 const uint8_t* data = NULL; 274 size_t len = 0; 275 DriverLoad ld = {0}; 276 uint8_t* sbuf = NULL; 277 size_t sbuf_len = 0; 278 int loaded = 0, npos = 0, rc = 2, owned_writer = 0, options = 1; 279 int i; 280 size_t cols, group; 281 const char* hx; 282 char ident[256]; 283 284 if (driver_argv_wants_help(argc, argv, 1)) { 285 driver_help_xxd(); 286 return 0; 287 } 288 289 memset(&o, 0, sizeof o); 290 driver_env_init(&env); 291 ctx = driver_env_to_context(&env); 292 293 for (i = 1; i < argc; ++i) { 294 const char* a = argv[i]; 295 if (options && driver_streq(a, "--")) { 296 options = 0; 297 continue; 298 } 299 if (!options) { 300 if (npos == 0) 301 o.in = a; 302 else if (npos == 1 && !o.out) 303 o.out = a; 304 else { 305 driver_errf(XXD_TOOL, "too many operands: %s", a); 306 goto done; 307 } 308 ++npos; 309 continue; 310 } 311 if (driver_streq(a, "-r")) { 312 o.reverse = 1; 313 continue; 314 } 315 if (driver_streq(a, "-p")) { 316 o.mode = XXD_PLAIN; 317 continue; 318 } 319 if (driver_streq(a, "-i")) { 320 o.mode = XXD_INCLUDE; 321 continue; 322 } 323 if (driver_streq(a, "-u")) { 324 o.uppercase = 1; 325 continue; 326 } 327 if (a[0] == '-' && a[1] == 'c') { 328 const char* v = xxd_optval(a, argc, argv, &i); 329 uint64_t n; 330 if (!v || driver_parse_u64(v, &n) != 0 || n == 0) { 331 driver_errf(XXD_TOOL, "-c requires a positive integer"); 332 goto done; 333 } 334 o.cols = (size_t)n; 335 continue; 336 } 337 if (a[0] == '-' && a[1] == 'g') { 338 const char* v = xxd_optval(a, argc, argv, &i); 339 uint64_t n; 340 if (!v || driver_parse_u64(v, &n) != 0 || n == 0) { 341 driver_errf(XXD_TOOL, "-g requires a positive integer"); 342 goto done; 343 } 344 o.group = (size_t)n; 345 continue; 346 } 347 if (a[0] == '-' && a[1] == 's') { 348 const char* v = xxd_optval(a, argc, argv, &i); 349 if (!v || driver_parse_u64(v, &o.seek) != 0) { 350 driver_errf(XXD_TOOL, "-s requires a byte offset"); 351 goto done; 352 } 353 continue; 354 } 355 if (a[0] == '-' && a[1] == 'l') { 356 const char* v = xxd_optval(a, argc, argv, &i); 357 if (!v || driver_parse_u64(v, &o.limit) != 0) { 358 driver_errf(XXD_TOOL, "-l requires a length"); 359 goto done; 360 } 361 o.have_limit = 1; 362 continue; 363 } 364 if (driver_streq(a, "-o")) { 365 if (i + 1 >= argc) { 366 driver_errf(XXD_TOOL, "-o requires a path"); 367 goto done; 368 } 369 o.out = argv[++i]; 370 continue; 371 } 372 if (driver_streq(a, "-")) { 373 if (npos == 0) o.in = NULL; /* stdin */ 374 ++npos; 375 continue; 376 } 377 if (a[0] == '-' && a[1] != '\0') { 378 driver_errf(XXD_TOOL, "unknown option: %s", a); 379 goto done; 380 } 381 if (npos == 0) 382 o.in = a; 383 else if (npos == 1 && !o.out) 384 o.out = a; 385 else { 386 driver_errf(XXD_TOOL, "too many operands: %s", a); 387 goto done; 388 } 389 ++npos; 390 } 391 392 /* Resolve mode-dependent defaults. */ 393 cols = o.cols ? o.cols 394 : (o.mode == XXD_PLAIN ? XXD_COLS_PLAIN 395 : o.mode == XXD_INCLUDE ? XXD_COLS_INCLUDE 396 : XXD_COLS_DUMP); 397 group = o.group ? o.group : 2u; 398 hx = o.uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 399 400 /* Load input. */ 401 if (o.in) { 402 KitSlice in; 403 if (driver_load_bytes(&env.file_io, XXD_TOOL, o.in, &ld, &in) != 0) { 404 rc = 1; 405 goto done; 406 } 407 loaded = 1; 408 data = in.data; 409 len = in.len; 410 } else { 411 if (!driver_read_stdin(&env, &sbuf, &sbuf_len)) { 412 driver_errf(XXD_TOOL, "failed to read stdin"); 413 rc = 1; 414 goto done; 415 } 416 data = sbuf; 417 len = sbuf_len; 418 } 419 420 /* Apply seek/length (forward modes only; reverse consumes the whole text). */ 421 if (!o.reverse) { 422 if (o.seek < len) { 423 data += o.seek; 424 len -= (size_t)o.seek; 425 } else { 426 data += len; 427 len = 0; 428 } 429 if (o.have_limit && o.limit < (uint64_t)len) len = (size_t)o.limit; 430 } 431 432 /* Open output. */ 433 if (o.out) { 434 if (ctx.file_io->open_writer(ctx.file_io->user, o.out, &w) != KIT_OK) { 435 driver_errf(XXD_TOOL, "failed to open output: %s", o.out); 436 rc = 1; 437 goto done; 438 } 439 owned_writer = 1; 440 } else { 441 w = driver_stdout_writer(&env); 442 owned_writer = 1; 443 } 444 445 memset(&b, 0, sizeof b); 446 b.w = w; 447 448 if (o.reverse) { 449 xxd_reverse(&b, data, len, o.mode == XXD_PLAIN); 450 } else if (o.mode == XXD_PLAIN) { 451 xxd_emit_plain(&b, data, len, cols, hx); 452 } else if (o.mode == XXD_INCLUDE) { 453 ident[0] = '\0'; 454 if (o.in) xxd_ident(o.in, ident, sizeof ident); 455 xxd_emit_include(&b, data, len, ident, cols, hx); 456 } else { 457 xxd_emit_dump(&b, data, len, o.seek, cols, group, hx); 458 } 459 xb_flush(&b); 460 rc = b.err ? 1 : 0; 461 462 done: 463 if (owned_writer && w) kit_writer_close(w); 464 if (sbuf) driver_free(&env, sbuf, sbuf_len); 465 if (loaded) driver_release_bytes(&env.file_io, &ld); 466 driver_env_fini(&env); 467 return rc; 468 }