1/* xxd.c - hexdump.
2 *
3 * Copyright 2015 The Android Open Source Project
4 *
5 * No obvious standard, output looks like:
6 * 0000000: 4c69 6e75 7820 7665 7273 696f 6e20 332e  Linux version 3.
7
8USE_XXD(NEWTOY(xxd, ">1c#<1>4096=16l#g#<1=2prs#[!rs]", TOYFLAG_USR|TOYFLAG_BIN))
9
10config XXD
11  bool "xxd"
12  default y
13  help
14    usage: xxd [-c n] [-g n] [-l n] [-p] [-r] [-s n] [file]
15
16    Hexdump a file to stdout.  If no file is listed, copy from stdin.
17    Filename "-" is a synonym for stdin.
18
19    -c n	Show n bytes per line (default 16).
20    -g n	Group bytes by adding a ' ' every n bytes (default 2).
21    -l n	Limit of n bytes before stopping (default is no limit).
22    -p	Plain hexdump (30 bytes/line, no grouping).
23    -r	Reverse operation: turn a hexdump into a binary file.
24    -s n	Skip to offset n.
25*/
26
27#define FOR_xxd
28#include "toys.h"
29
30GLOBALS(
31  long s;
32  long g;
33  long l;
34  long c;
35)
36
37static void do_xxd(int fd, char *name)
38{
39  long long pos = 0;
40  long long limit = TT.l;
41  int i, len, space;
42
43  if (toys.optflags&FLAG_s) {
44    xlseek(fd, TT.s, SEEK_SET);
45    pos = TT.s;
46    if (limit) limit += TT.s;
47  }
48
49  while (0<(len = readall(fd, toybuf,
50                          (limit && limit-pos<TT.c)?limit-pos:TT.c))) {
51    if (!(toys.optflags&FLAG_p)) printf("%08llx: ", pos);
52    pos += len;
53    space = 2*TT.c+TT.c/TT.g+1;
54
55    for (i=0; i<len;) {
56      space -= printf("%02x", toybuf[i]);
57      if (!(++i%TT.g)) {
58        putchar(' ');
59        space--;
60      }
61    }
62
63    if (!(toys.optflags&FLAG_p)) {
64      printf("%*s", space, "");
65      for (i=0; i<len; i++)
66        putchar((toybuf[i]>=' ' && toybuf[i]<='~') ? toybuf[i] : '.');
67    }
68    putchar('\n');
69  }
70  if (len<0) perror_exit("read");
71}
72
73static int dehex(char ch)
74{
75  if (ch >= '0' && ch <= '9') return ch - '0';
76  if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
77  if (ch >= 'A' && ch <= 'F') return ch - 'a' + 10;
78  return (ch == '\n') ? -2 : -1;
79}
80
81static void do_xxd_reverse(int fd, char *name)
82{
83  FILE *fp = xfdopen(fd, "r");
84
85  while (!feof(fp)) {
86    int col = 0;
87    int tmp;
88
89    // Each line of a non-plain hexdump starts with an offset/address.
90    if (!(toys.optflags&FLAG_p)) {
91      long long pos;
92
93      if (fscanf(fp, "%llx: ", &pos) == 1) {
94        if (fseek(stdout, pos, SEEK_SET) != 0) {
95          // TODO: just write out zeros if non-seekable?
96          perror_exit("%s: seek failed", name);
97        }
98      }
99    }
100
101    // A plain hexdump can have as many bytes per line as you like,
102    // but a non-plain hexdump assumes garbage after it's seen the
103    // specified number of bytes.
104    while (toys.optflags&FLAG_p || col < TT.c) {
105      int n1, n2;
106
107      // If we're at EOF or EOL or we read some non-hex...
108      if ((n1 = n2 = dehex(fgetc(fp))) < 0 || (n2 = dehex(fgetc(fp))) < 0) {
109        // If we're at EOL, start on that line.
110        if (n1 == -2 || n2 == -2) continue;
111        // Otherwise, skip to the next line.
112        break;
113      }
114
115      fputc((n1 << 4) | (n2 & 0xf), stdout);
116      col++;
117
118      // Is there any grouping going on? Ignore a single space.
119      tmp = fgetc(fp);
120      if (tmp != ' ') ungetc(tmp, fp);
121    }
122
123    // Skip anything else on this line (such as the ASCII dump).
124    while ((tmp = fgetc(fp)) != EOF && tmp != '\n')
125      ;
126  }
127  if (ferror(fp)) perror_msg_raw(name);
128
129  fclose(fp);
130}
131
132void xxd_main(void)
133{
134  // Plain style is 30 bytes/line, no grouping.
135  if (toys.optflags&FLAG_p) TT.c = TT.g = 30;
136
137  loopfiles(toys.optargs, toys.optflags&FLAG_r ? do_xxd_reverse : do_xxd);
138}
139