toybox/toys/other/xxd.c
<<
>>
Prefs
   1/* xxd.c - hexdump.
   2 *
   3 * Copyright 2015 The Android Open Source Project
   4 *
   5 * No obvious standard.
   6 * Regular output:
   7 *   "00000000: 4c69 6e75 7820 7665 7273 696f 6e20 342e  Linux version 4."
   8 * xxd -i "include" or "initializer" output:
   9 *   "  0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,"
  10 * xxd -p "plain" output:
  11 *   "4c696e75782076657273696f6e20342e392e302d342d616d643634202864"
  12
  13USE_XXD(NEWTOY(xxd, ">1c#<0>256l#o#g#<1=2iprs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
  14
  15config XXD
  16  bool "xxd"
  17  default y
  18  help
  19    usage: xxd [-c n] [-g n] [-i] [-l n] [-o n] [-p] [-r] [-s n] [file]
  20
  21    Hexdump a file to stdout. If no file is listed, copy from stdin.
  22    Filename "-" is a synonym for stdin.
  23
  24    -c n        Show n bytes per line (default 16)
  25    -g n        Group bytes by adding a ' ' every n bytes (default 2)
  26    -i  Output include file (CSV hex bytes, plus C header/footer if not stdin)
  27    -l n        Limit of n bytes before stopping (default is no limit)
  28    -o n        Add n to display offset
  29    -p  Plain hexdump (30 bytes/line, no grouping)
  30    -r  Reverse operation: turn a hexdump into a binary file
  31    -s n        Skip to offset n
  32*/
  33
  34#define FOR_xxd
  35#include "toys.h"
  36
  37GLOBALS(
  38  long s, g, o, l, c;
  39)
  40
  41static void do_xxd(int fd, char *name)
  42{
  43  long long pos = 0;
  44  long long limit = TT.l;
  45  int i, len, space;
  46
  47  if (FLAG(s)) {
  48    xlseek(fd, TT.s, SEEK_SET);
  49    pos = TT.s;
  50    if (limit) limit += TT.s;
  51  }
  52
  53  while (0<(len = readall(fd, toybuf,
  54                          (limit && limit-pos<TT.c)?limit-pos:TT.c)))
  55  {
  56    if (!FLAG(p)) printf("%08llx: ", TT.o + pos);
  57    pos += len;
  58    space = 2*TT.c+(TT.c+TT.g-1)/TT.g+1;
  59
  60    for (i=0; i<len;) {
  61      space -= printf("%02x", toybuf[i]);
  62      if (!(++i%TT.g)) {
  63        putchar(' ');
  64        space--;
  65      }
  66    }
  67
  68    if (!FLAG(p)) {
  69      printf("%*s", space, "");
  70      for (i = 0; i<len; i++)
  71        putchar((toybuf[i]>=' ' && toybuf[i]<='~') ? toybuf[i] : '.');
  72    }
  73    putchar('\n');
  74  }
  75  if (len<0) perror_exit("read");
  76}
  77
  78static void do_xxd_include(int fd, char *name)
  79{
  80  int c = 1, i, len;
  81
  82  // The original xxd outputs a header/footer if given a filename (not stdin).
  83  // We don't, which means that unlike the original we can implement -ri.
  84  while ((len = read(fd, toybuf, sizeof(toybuf))) > 0) {
  85    for (i = 0; i < len; ++i) {
  86      printf("%s%#.02x", c > 1 ? ", " : "  ", toybuf[i]);
  87      if (c++ == TT.c) {
  88        xprintf(",\n");
  89        c = 1;
  90      }
  91    }
  92  }
  93  if (len < 0) perror_msg_raw(name);
  94  if (c > 1) xputc('\n');
  95}
  96
  97static int dehex(char ch)
  98{
  99  if (ch >= '0' && ch <= '9') return ch - '0';
 100  if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
 101  if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
 102  return (ch == '\n') ? -2 : -1;
 103}
 104
 105static void do_xxd_reverse(int fd, char *name)
 106{
 107  FILE *fp = xfdopen(xdup(fd), "r");
 108  long long current_pos = 0;
 109  int tmp;
 110
 111  // -ri is a very easy special case.
 112  if (FLAG(i)) while (fscanf(fp, " 0x%02x,", &tmp) == 1) xputc(tmp);
 113  else while (!feof(fp)) {
 114    int col = 0;
 115
 116    // Each line of a regular hexdump starts with an offset/address.
 117    // Each line of a plain hexdump just goes straight into the bytes.
 118    if (!FLAG(p)) {
 119      long long pos;
 120
 121      if (fscanf(fp, "%llx: ", &pos) == 1) {
 122        if (pos != current_pos && fseek(stdout, pos, SEEK_SET) != 0) {
 123          // TODO: just write out zeros if non-seekable?
 124          perror_exit("%s: seek failed", name);
 125        }
 126      }
 127    }
 128
 129    // A plain hexdump can have as many bytes per line as you like,
 130    // but a non-plain hexdump assumes garbage after it's seen the
 131    // specified number of bytes.
 132    while (FLAG(p) || col < TT.c) {
 133      int n1, n2;
 134
 135      // If we're at EOF or EOL or we read some non-hex...
 136      if ((n1 = n2 = dehex(fgetc(fp))) < 0 || (n2 = dehex(fgetc(fp))) < 0) {
 137        // If we're at EOL, start on that line.
 138        if (n1 == -2 || n2 == -2) continue;
 139        // Otherwise, skip to the next line.
 140        break;
 141      }
 142
 143      fputc((n1 << 4) | (n2 & 0xf), stdout);
 144      col++;
 145      current_pos++;
 146
 147      // Is there any grouping going on? Ignore a single space.
 148      tmp = fgetc(fp);
 149      if (tmp != ' ') ungetc(tmp, fp);
 150    }
 151
 152    // Skip anything else on this line (such as the ASCII dump).
 153    while ((tmp = fgetc(fp)) != EOF && tmp != '\n');
 154  }
 155
 156  if (ferror(fp)) perror_msg_raw(name);
 157  fclose(fp);
 158}
 159
 160void xxd_main(void)
 161{
 162  if (!TT.c) TT.c = FLAG(i) ? 12 : 16;
 163
 164  // Plain style is 30 bytes/line, no grouping.
 165  if (FLAG(p)) TT.c = TT.g = 30;
 166
 167  loopfiles(toys.optargs,
 168    FLAG(r) ? do_xxd_reverse : (FLAG(i) ? do_xxd_include : do_xxd));
 169}
 170