master xplshn/aruu / cmd / posix / od.c
  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}