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 fmt[2] = big_endian ? '-' : ' ';
97 fmt[6] = format;
98 printf(fmt, (int)(3 * len + len - 1), res);
99 }
100}
101
102static void
103printline(const unsigned char *line, size_t len, off_t addr)
104{
105 struct type *t = NULL;
106 size_t i;
107 int first = 1;
108 unsigned char *tmp;
109
110 if (TAILQ_EMPTY(&head))
111 goto once;
112 TAILQ_FOREACH(t, &head, entry)
113 {
114 once:
115 if (first) {
116 printaddress(addr);
117 first = 0;
118 } else {
119 printf("%*c", (addr_format == 'n') ? 1 : 7, ' ');
120 }
121 for (i = 0; i < len; i += MIN(len - i, t ? t->len : 4)) {
122 if (len - i < (t ? t->len : 4)) {
123 tmp = ecalloc(t ? t->len : 4, 1);
124 memcpy(tmp, line + i, len - i);
125 printchunk(tmp, t ? t->format : 'o', t ? t->len : 4);
126 free(tmp);
127 } else {
128 printchunk(line + i, t ? t->format : 'o', t ? t->len : 4);
129 }
130 }
131 fputc('\n', stdout);
132 if (TAILQ_EMPTY(&head) || (!len && !first))
133 break;
134 }
135}
136
137static int
138od(int fd, char *fname, int last)
139{
140 static unsigned char *line;
141 static size_t lineoff;
142 static off_t addr;
143 unsigned char buf[BUFSIZ];
144 size_t i, size = sizeof(buf);
145 ssize_t n;
146
147 while (skip - addr > 0) {
148 n = read(fd, buf, MIN((size_t)(skip - addr), sizeof(buf)));
149 if (n < 0)
150 weprintf("read %s:", fname);
151 if (n <= 0)
152 return n;
153 addr += n;
154 }
155 if (!line)
156 line = emalloc(linelen);
157
158 for (;;) {
159 if (max >= 0)
160 size = MIN((size_t)(max - (addr - skip)), size);
161 if ((n = read(fd, buf, size)) <= 0)
162 break;
163 for (i = 0; i < (size_t)n; i++, addr++) {
164 line[lineoff++] = buf[i];
165 if (lineoff == linelen) {
166 printline(line, lineoff, addr - lineoff + 1);
167 lineoff = 0;
168 }
169 }
170 }
171 if (n < 0) {
172 weprintf("read %s:", fname);
173 return n;
174 }
175 if (lineoff && last)
176 printline(line, lineoff, addr - lineoff);
177 if (last)
178 printline((unsigned char *)"", 0, addr);
179 return 0;
180}
181
182static int
183lcm(unsigned int a, unsigned int b)
184{
185 unsigned int c, d, e;
186
187 for (c = a, d = b; c;) {
188 e = c;
189 c = d % c;
190 d = e;
191 }
192
193 return a / d * b;
194}
195
196static void
197addtype(char format, int len)
198{
199 struct type *t;
200
201 t = emalloc(sizeof(*t));
202 t->format = format;
203 t->len = len;
204 TAILQ_INSERT_TAIL(&head, t, entry);
205}
206
207static void
208usage(void)
209{
210 eprintf(
211 "usage: %s [-bdosvx] [-A addressformat] "
212#if FEATURE_OD_ENDIAN
213 "[-E | -e] "
214#endif
215 "[-j skip] [-t outputformat] [file ...]\n",
216 argv0
217 );
218}
219
220// ?man od: dump files in formats
221// ?man display file contents in octal, hex, or other formats
222int
223main(int argc, char *argv[])
224{
225 struct type *t;
226 char *s, *end;
227 int fd, ret = 0, len, fmt_char;
228
229 big_endian = (*(uint16_t *)"\0\xff" == 0xff);
230
231 ARGBEGIN
232 {
233 // ?man -A:str: specify option flag
234 case 'A':
235 s = EARGF(usage());
236 if (strlen(s) != 1 || !strchr("doxn", s[0]))
237 usage();
238 addr_format = s[0];
239 break;
240 // ?man -b: specify block size or base directory
241 case 'b':
242 addtype('o', 1);
243 break;
244 // ?man -d: specify directory
245 case 'd':
246 addtype('u', 2);
247 break;
248#if FEATURE_OD_ENDIAN
249 // ?man -E: specify option flag
250 case 'E':
251 // ?man -e: specify expression or pattern
252 case 'e':
253 big_endian = (ARGC() == 'E');
254 break;
255#endif
256 // ?man -j:str: specify option flag
257 case 'j':
258 if ((skip = parseoffset(EARGF(usage()))) < 0)
259 usage();
260 break;
261 // ?man -N:str: specify option flag
262 case 'N':
263 if ((max = parseoffset(EARGF(usage()))) < 0)
264 usage();
265 break;
266 // ?man -o: specify output file
267 case 'o':
268 addtype('o', 2);
269 break;
270 // ?man -s: silent mode or print summary
271 case 's':
272 addtype('d', 2);
273 break;
274 // ?man -t:str: sort or specify timestamp
275 case 't':
276 s = EARGF(usage());
277 for (; *s; s++) {
278 switch (*s) {
279 // ?man -a: print or show all entries
280 case 'a':
281 // ?man -c: print count or perform stdout action
282 case 'c':
283 addtype(*s, 1);
284 break;
285 // ?man -d: specify directory
286 case 'd':
287 // ?man -o: specify output file
288 case 'o':
289 // ?man -u: unbuffered output
290 case 'u':
291 // ?man -x: hex format or match whole lines
292 case 'x':
293 fmt_char = *s;
294 if (isdigit((unsigned char)*(s + 1))) {
295 len = strtol(s + 1, &end, 10);
296 s = end - 1;
297 } else {
298 switch (*(s + 1)) {
299 // ?man -C: specify option flag
300 case 'C':
301 len = sizeof(char);
302 s++;
303 break;
304 // ?man -S: specify option flag
305 case 'S':
306 len = sizeof(short);
307 s++;
308 break;
309 // ?man -I: specify option flag
310 case 'I':
311 len = sizeof(int);
312 s++;
313 break;
314 // ?man -L: specify option flag
315 case 'L':
316 len = sizeof(long);
317 s++;
318 break;
319 default:
320 len = sizeof(int);
321 }
322 }
323 addtype(fmt_char, len);
324 break;
325 default:
326 usage();
327 }
328 }
329 break;
330 // ?man -v: verbose mode; show progress
331 case 'v':
332 /* always set, use uniq(1) to handle duplicate lines */
333 break;
334 // ?man -x: hex format or match whole lines
335 case 'x':
336 addtype('x', 2);
337 break;
338 default:
339 usage();
340 }
341 ARGEND
342
343 /* line length is lcm of type lengths and >= 16 by doubling */
344 TAILQ_FOREACH(t, &head, entry)
345 linelen = lcm(linelen, t->len);
346 if (TAILQ_EMPTY(&head))
347 linelen = 16;
348 while (linelen < 16)
349 linelen *= 2;
350
351 if (!argc) {
352 if (od(0, "<stdin>", 1) < 0)
353 ret = 1;
354 } else {
355 for (; *argv; argc--, argv++) {
356 if (!strcmp(*argv, "-")) {
357 *argv = "<stdin>";
358 fd = 0;
359 } else if ((fd = open(*argv, O_RDONLY)) < 0) {
360 weprintf("open %s:", *argv);
361 ret = 1;
362 continue;
363 }
364 if (od(fd, *argv, (!*(argv + 1))) < 0)
365 ret = 1;
366 if (fd != 0)
367 close(fd);
368 }
369 }
370
371 ret |= fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>");
372
373 return ret;
374}