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#l#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] [-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  Include file output format (comma-separated hex byte literals)
  27    -l n        Limit of n bytes before stopping (default is no limit)
  28    -p  Plain hexdump (30 bytes/line, no grouping)
  29    -r  Reverse operation: turn a hexdump into a binary file
  30    -s n        Skip to offset n
  31*/
  32
  33#define FOR_xxd
  34#include "toys.h"
  35
  36GLOBALS(
  37  long s;
  38  long g;
  39  long l;
  40  long c;
  41)
  42
  43static void do_xxd(int fd, char *name)
  44{
  45  long long pos = 0;
  46  long long limit = TT.l;
  47  int i, len, space;
  48
  49  if (toys.optflags&FLAG_s) {
  50    xlseek(fd, TT.s, SEEK_SET);
  51    pos = TT.s;
  52    if (limit) limit += TT.s;
  53  }
  54
  55  while (0<(len = readall(fd, toybuf,
  56                          (limit && limit-pos<TT.c)?limit-pos:TT.c))) {
  57    if (!(toys.optflags&FLAG_p)) printf("%08llx: ", pos);
  58    pos += len;
  59    space = 2*TT.c+TT.c/TT.g+1;
  60
  61    for (i=0; i<len;) {
  62      space -= printf("%02x", toybuf[i]);
  63      if (!(++i%TT.g)) {
  64        putchar(' ');
  65        space--;
  66      }
  67    }
  68
  69    if (!(toys.optflags&FLAG_p)) {
  70      printf("%*s", space, "");
  71      for (i=0; i<len; i++)
  72        putchar((toybuf[i]>=' ' && toybuf[i]<='~') ? toybuf[i] : '.');
  73    }
  74    putchar('\n');
  75  }
  76  if (len<0) perror_exit("read");
  77}
  78
  79static void do_xxd_include(int fd, char *name)
  80{
  81  long long total = 0;
  82  int c = 1, i, len;
  83
  84  // The original xxd outputs a header/footer if given a filename (not stdin).
  85  // We don't, which means that unlike the original we can implement -ri.
  86  while ((len = read(fd, toybuf, sizeof(toybuf))) > 0) {
  87    total += len;
  88    for (i = 0; i < len; ++i) {
  89      printf("%s%#.02x", c > 1 ? ", " : "  ", toybuf[i]);
  90      if (c++ == TT.c) {
  91        xprintf(",\n");
  92        c = 1;
  93      }
  94    }
  95  }
  96  if (len < 0) perror_msg_raw(name);
  97  if (c > 1) xputc('\n');
  98}
  99
 100static int dehex(char ch)
 101{
 102  if (ch >= '0' && ch <= '9') return ch - '0';
 103  if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
 104  if (ch >= 'A' && ch <= 'F') return ch - 'a' + 10;
 105  return (ch == '\n') ? -2 : -1;
 106}
 107
 108static void do_xxd_reverse(int fd, char *name)
 109{
 110  FILE *fp = xfdopen(fd, "r");
 111  int tmp;
 112
 113  if (toys.optflags&FLAG_i) {
 114    // -ri is a very easy special case.
 115    while (fscanf(fp, " 0x%02x,", &tmp) == 1) {
 116      fputc(tmp & 0xff, stdout);
 117    }
 118  } else {
 119    while (!feof(fp)) {
 120      int col = 0;
 121
 122      // Each line of a regular hexdump starts with an offset/address.
 123      // Each line of a plain hexdump just goes straight into the bytes.
 124      if (!(toys.optflags&FLAG_p)) {
 125        long long pos;
 126
 127        if (fscanf(fp, "%llx: ", &pos) == 1) {
 128          if (fseek(stdout, pos, SEEK_SET) != 0) {
 129            // TODO: just write out zeros if non-seekable?
 130            perror_exit("%s: seek failed", name);
 131          }
 132        }
 133      }
 134
 135      // A plain hexdump can have as many bytes per line as you like,
 136      // but a non-plain hexdump assumes garbage after it's seen the
 137      // specified number of bytes.
 138      while (toys.optflags&FLAG_p || col < TT.c) {
 139        int n1, n2;
 140
 141        // If we're at EOF or EOL or we read some non-hex...
 142        if ((n1 = n2 = dehex(fgetc(fp))) < 0 || (n2 = dehex(fgetc(fp))) < 0) {
 143          // If we're at EOL, start on that line.
 144          if (n1 == -2 || n2 == -2) continue;
 145          // Otherwise, skip to the next line.
 146          break;
 147        }
 148
 149        fputc((n1 << 4) | (n2 & 0xf), stdout);
 150        col++;
 151
 152        // Is there any grouping going on? Ignore a single space.
 153        tmp = fgetc(fp);
 154        if (tmp != ' ') ungetc(tmp, fp);
 155      }
 156
 157      // Skip anything else on this line (such as the ASCII dump).
 158      while ((tmp = fgetc(fp)) != EOF && tmp != '\n')
 159        ;
 160    }
 161  }
 162
 163  if (ferror(fp)) perror_msg_raw(name);
 164  fclose(fp);
 165}
 166
 167void xxd_main(void)
 168{
 169  if (TT.c < 0 || TT.c > 256) error_exit("invalid -c: %ld", TT.c);
 170  if (TT.c == 0) TT.c = (toys.optflags&FLAG_i)?12:16;
 171
 172  // Plain style is 30 bytes/line, no grouping.
 173  if (toys.optflags&FLAG_p) TT.c = TT.g = 30;
 174
 175  loopfiles(toys.optargs,
 176    toys.optflags&FLAG_r ? do_xxd_reverse
 177      : (toys.optflags&FLAG_i ? do_xxd_include : do_xxd));
 178}
 179