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