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      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}