1/* see LICENSE file for copyright and license details */
2
3#include "queue.h"
4#include "util.h"
5
6#include <ctype.h>
7#include <fcntl.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13
14struct type {
15 unsigned char format;
16 unsigned int len;
17 TAILQ_ENTRY(type) entry;
18};
19
20static TAILQ_HEAD(head, type) head = TAILQ_HEAD_INITIALIZER(head);
21static unsigned char addr_format = 'o';
22static off_t skip = 0;
23static off_t max = -1;
24static size_t linelen = 1;
25static int big_endian;
26
27static void
28printaddress(off_t addr)
29{
30 char fmt[] = "%07j#";
31
32 if (addr_format == 'n') {
33 fputc(' ', stdout);
34 } else {
35 fmt[4] = addr_format;
36 printf(fmt, (intmax_t)addr);
37 }
38}
39
40static void
41printchunk(const unsigned char *s, unsigned char format, size_t len)
42{
43 long long res, basefac;
44 size_t i;
45 char fmt[] = " %#*ll#";
46 unsigned char c;
47
48 const char *namedict[] = {
49 "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", "bs", "ht", "nl",
50 "vt", "ff", "cr", "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", "nak",
51 "syn", "etb", "can", "em", "sub", "esc", "fs", "gs", "rs", "us", "sp",
52 };
53 const char *escdict[] = {
54 ['\0'] = "\\0",
55 ['\a'] = "\\a",
56 ['\b'] = "\\b",
57 ['\t'] = "\\t",
58 ['\n'] = "\\n",
59 ['\v'] = "\\v",
60 ['\f'] = "\\f",
61 ['\r'] = "\\r",
62 };
63
64 switch (format) {
65 // ?man -a: print or show all entries
66 case 'a':
67 c = *s & ~128; /* clear high bit as required by standard */
68 if (c < LEN(namedict) || c == 127) {
69 printf(" %3s", (c == 127) ? "del" : namedict[c]);
70 } else {
71 printf(" %3c", c);
72 }
73 break;
74 // ?man -c: print count or perform stdout action
75 case 'c':
76 if (strchr("\a\b\t\n\v\f\r\0", *s)) {
77 printf(" %3s", escdict[*s]);
78 } else if (!isprint(*s)) {
79 printf(" %3o", *s);
80 } else {
81 printf(" %3c", *s);
82 }
83 break;
84 default:
85 if (big_endian) {
86 for (res = 0, basefac = 1, i = len; i; i--) {
87 res += s[i - 1] * basefac;
88 basefac <<= 8;
89 }
90 } else {
91 for (res = 0, basefac = 1, i = 0; i < len; i++) {
92 res += s[i] * basefac;
93 basefac <<= 8;
94 }
95 }
96 /* x/o print a fixed-width, zero-padded field: 2 hex digits per
97 * byte, or ceil(8*len/3) octal digits, the exact digit count
98 * needed for len bytes' worth of bits, not the old 4*len-1
99 * estimate (right for octal/decimal at len==1 only by
100 * coincidence, and short for hex at every len). d/u stay right-
101 * justified with spaces at that same estimate, same as every
102 * other od: an oversized width there is just extra leading
103 * whitespace, harmless under a space fill */
104 fmt[2] = (format == 'x' || format == 'o') ? '0' : ' ';
105 fmt[6] = format;
106 switch (format) {
107 case 'x': printf(fmt, (int)(2 * len), res); break;
108 case 'o': printf(fmt, (int)((8 * len + 2) / 3), res); break;
109 default: printf(fmt, (int)(3 * len + len - 1), res); break;
110 }
111 }
112}
113
114static void
115printline(const unsigned char *line, size_t len, off_t addr)
116{
117 struct type *t = NULL;
118 size_t i;
119 int first = 1;
120 unsigned char *tmp;
121
122 if (TAILQ_EMPTY(&head))
123 goto once;
124 TAILQ_FOREACH(t, &head, entry)
125 {
126 once:
127 if (first) {
128 printaddress(addr);
129 first = 0;
130 } else {
131 printf("%*c", (addr_format == 'n') ? 1 : 7, ' ');
132 }
133 for (i = 0; i < len; i += MIN(len - i, t ? t->len : 4)) {
134 if (len - i < (t ? t->len : 4)) {
135 tmp = ecalloc(t ? t->len : 4, 1);
136 memcpy(tmp, line + i, len - i);
137 printchunk(tmp, t ? t->format : 'o', t ? t->len : 4);
138 free(tmp);
139 } else {
140 printchunk(line + i, t ? t->format : 'o', t ? t->len : 4);
141 }
142 }
143 fputc('\n', stdout);
144 if (TAILQ_EMPTY(&head) || (!len && !first))
145 break;
146 }
147}
148
149static int
150od(int fd, char *fname, int last)
151{
152 static unsigned char *line;
153 static size_t lineoff;
154 static off_t addr;
155 unsigned char buf[BUFSIZ];
156 size_t i, size = sizeof(buf);
157 ssize_t n;
158
159 while (skip - addr > 0) {
160 n = read(fd, buf, MIN((size_t)(skip - addr), sizeof(buf)));
161 if (n < 0)
162 weprintf("read %s:", fname);
163 if (n <= 0)
164 return n;
165 addr += n;
166 }
167 if (!line)
168 line = emalloc(linelen);
169
170 for (;;) {
171 if (max >= 0)
172 size = MIN((size_t)(max - (addr - skip)), size);
173 if ((n = read(fd, buf, size)) <= 0)
174 break;
175 for (i = 0; i < (size_t)n; i++, addr++) {
176 line[lineoff++] = buf[i];
177 if (lineoff == linelen) {
178 printline(line, lineoff, addr - lineoff + 1);
179 lineoff = 0;
180 }
181 }
182 }
183 if (n < 0) {
184 weprintf("read %s:", fname);
185 return n;
186 }
187 if (lineoff && last)
188 printline(line, lineoff, addr - lineoff);
189 /* the trailing all-data-consumed line exists to show the final
190 * address (the file's total size); with -an there is no address to
191 * show, so it degrades to a content-free, address-column-only blank
192 * line nothing after it can use, unlike every other od */
193 if (last && addr_format != 'n')
194 printline((unsigned char *)"", 0, addr);
195 return 0;
196}
197
198static int
199lcm(unsigned int a, unsigned int b)
200{
201 unsigned int c, d, e;
202
203 for (c = a, d = b; c;) {
204 e = c;
205 c = d % c;
206 d = e;
207 }
208
209 return a / d * b;
210}
211
212static void
213addtype(char format, int len)
214{
215 struct type *t;
216
217 t = emalloc(sizeof(*t));
218 t->format = format;
219 t->len = len;
220 TAILQ_INSERT_TAIL(&head, t, entry);
221}
222
223static void
224usage(void)
225{
226 eprintf(
227 "usage: %s [-bdosvx] [-A addressformat] "
228#if FEATURE_OD_ENDIAN
229 "[-E | -e] "
230#endif
231 "[-j skip] [-t outputformat] [file ...]\n",
232 argv0
233 );
234}
235
236// ?man od: dump files in formats
237// ?man display file contents in octal, hex, or other formats
238int
239main(int argc, char *argv[])
240{
241 struct type *t;
242 char *s, *end;
243 int fd, ret = 0, len, fmt_char;
244
245 big_endian = (*(uint16_t *)"\0\xff" == 0xff);
246
247 ARGBEGIN
248 {
249 // ?man -A:str: specify option flag
250 case 'A':
251 s = EARGF(usage());
252 if (strlen(s) != 1 || !strchr("doxn", s[0]))
253 usage();
254 addr_format = s[0];
255 break;
256 // ?man -b: specify block size or base directory
257 case 'b':
258 addtype('o', 1);
259 break;
260 // ?man -d: specify directory
261 case 'd':
262 addtype('u', 2);
263 break;
264#if FEATURE_OD_ENDIAN
265 // ?man -E: specify option flag
266 case 'E':
267 // ?man -e: specify expression or pattern
268 case 'e':
269 big_endian = (ARGC() == 'E');
270 break;
271#endif
272 // ?man -j:str: specify option flag
273 case 'j':
274 if ((skip = parseoffset(EARGF(usage()))) < 0)
275 usage();
276 break;
277 // ?man -N:str: specify option flag
278 case 'N':
279 if ((max = parseoffset(EARGF(usage()))) < 0)
280 usage();
281 break;
282 // ?man -o: specify output file
283 case 'o':
284 addtype('o', 2);
285 break;
286 // ?man -s: silent mode or print summary
287 case 's':
288 addtype('d', 2);
289 break;
290 // ?man -t:str: sort or specify timestamp
291 case 't':
292 s = EARGF(usage());
293 for (; *s; s++) {
294 switch (*s) {
295 // ?man -a: print or show all entries
296 case 'a':
297 // ?man -c: print count or perform stdout action
298 case 'c':
299 addtype(*s, 1);
300 break;
301 // ?man -d: specify directory
302 case 'd':
303 // ?man -o: specify output file
304 case 'o':
305 // ?man -u: unbuffered output
306 case 'u':
307 // ?man -x: hex format or match whole lines
308 case 'x':
309 fmt_char = *s;
310 if (isdigit((unsigned char)*(s + 1))) {
311 len = strtol(s + 1, &end, 10);
312 s = end - 1;
313 } else {
314 switch (*(s + 1)) {
315 // ?man -C: specify option flag
316 case 'C':
317 len = sizeof(char);
318 s++;
319 break;
320 // ?man -S: specify option flag
321 case 'S':
322 len = sizeof(short);
323 s++;
324 break;
325 // ?man -I: specify option flag
326 case 'I':
327 len = sizeof(int);
328 s++;
329 break;
330 // ?man -L: specify option flag
331 case 'L':
332 len = sizeof(long);
333 s++;
334 break;
335 default:
336 len = sizeof(int);
337 }
338 }
339 addtype(fmt_char, len);
340 break;
341 default:
342 usage();
343 }
344 }
345 break;
346 // ?man -v: verbose mode; show progress
347 case 'v':
348 /* always set, use uniq(1) to handle duplicate lines */
349 break;
350 // ?man -x: hex format or match whole lines
351 case 'x':
352 addtype('x', 2);
353 break;
354 default:
355 usage();
356 }
357 ARGEND
358
359 /* line length is lcm of type lengths and >= 16 by doubling */
360 TAILQ_FOREACH(t, &head, entry)
361 linelen = lcm(linelen, t->len);
362 if (TAILQ_EMPTY(&head))
363 linelen = 16;
364 while (linelen < 16)
365 linelen *= 2;
366
367 if (!argc) {
368 if (od(0, "<stdin>", 1) < 0)
369 ret = 1;
370 } else {
371 for (; *argv; argc--, argv++) {
372 if (!strcmp(*argv, "-")) {
373 *argv = "<stdin>";
374 fd = 0;
375 } else if ((fd = open(*argv, O_RDONLY)) < 0) {
376 weprintf("open %s:", *argv);
377 ret = 1;
378 continue;
379 }
380 if (od(fd, *argv, (!*(argv + 1))) < 0)
381 ret = 1;
382 if (fd != 0)
383 close(fd);
384 }
385 }
386
387 ret |= fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>");
388
389 return ret;
390}