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