sbase/od.c

333 lines
6.2 KiB
C
Raw Normal View History

/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "queue.h"
#include "util.h"
struct type {
unsigned char format;
unsigned int len;
TAILQ_ENTRY(type) entry;
};
static TAILQ_HEAD(head, type) head = TAILQ_HEAD_INITIALIZER(head);
static unsigned char addr_format = 'o';
static off_t skip = 0;
static off_t max = -1;
static size_t linelen = 1;
static int big_endian;
static void
printaddress(off_t addr)
{
char fmt[] = "%07j#";
if (addr_format == 'n') {
fputc(' ', stdout);
} else {
fmt[4] = addr_format;
printf(fmt, (intmax_t)addr);
}
}
static void
printchunk(const unsigned char *s, unsigned char format, size_t len)
2015-10-26 11:56:37 +00:00
{
long long res, basefac;
size_t i;
char fmt[] = " %#*ll#";
unsigned char c;
const char *namedict[] = {
"nul", "soh", "stx", "etx", "eot", "enq", "ack",
"bel", "bs", "ht", "nl", "vt", "ff", "cr",
"so", "si", "dle", "dc1", "dc2", "dc3", "dc4",
"nak", "syn", "etb", "can", "em", "sub", "esc",
"fs", "gs", "rs", "us", "sp",
};
const char *escdict[] = {
['\0'] = "\\0", ['\a'] = "\\a",
['\b'] = "\\b", ['\t'] = "\\t",
['\n'] = "\\n", ['\v'] = "\\v",
['\f'] = "\\f", ['\r'] = "\\r",
};
switch (format) {
case 'a':
c = *s & ~128; /* clear high bit as required by standard */
if (c < LEN(namedict) || c == 127) {
printf(" %3s", (c == 127) ? "del" : namedict[c]);
} else {
printf(" %3c", c);
}
break;
case 'c':
if (strchr("\a\b\t\n\v\f\r\0", *s)) {
printf(" %3s", escdict[*s]);
} else if (!isprint(*s)) {
printf(" %3o", *s);
} else {
printf(" %3c", *s);
}
break;
default:
if (big_endian) {
for (res = 0, basefac = 1, i = len; i; i--) {
res += s[i - 1] * basefac;
basefac <<= 8;
}
} else {
for (res = 0, basefac = 1, i = 0; i < len; i++) {
res += s[i] * basefac;
basefac <<= 8;
}
}
fmt[2] = big_endian ? '-' : ' ';
fmt[6] = format;
printf(fmt, (int)(3 * len + len - 1), res);
}
}
static void
printline(const unsigned char *line, size_t len, off_t addr)
{
struct type *t = NULL;
size_t i;
int first = 1;
unsigned char *tmp;
if (TAILQ_EMPTY(&head))
goto once;
TAILQ_FOREACH(t, &head, entry) {
once:
if (first) {
printaddress(addr);
first = 0;
} else {
printf("%*c", (addr_format == 'n') ? 1 : 7, ' ');
}
for (i = 0; i < len; i += MIN(len - i, t ? t->len : 4)) {
if (len - i < (t ? t->len : 4)) {
tmp = ecalloc(t ? t->len : 4, 1);
memcpy(tmp, line + i, len - i);
printchunk(tmp, t ? t->format : 'o',
t ? t->len : 4);
free(tmp);
} else {
printchunk(line + i, t ? t->format : 'o',
t ? t->len : 4);
}
}
fputc('\n', stdout);
if (TAILQ_EMPTY(&head) || (!len && !first))
break;
}
}
static int
od(int fd, char *fname, int last)
{
static unsigned char *line;
static size_t lineoff;
static off_t addr;
unsigned char buf[BUFSIZ];
size_t i, size = sizeof(buf);
ssize_t n;
while (skip - addr > 0) {
n = read(fd, buf, MIN(skip - addr, sizeof(buf)));
if (n < 0)
weprintf("read %s:", fname);
if (n <= 0)
return n;
addr += n;
}
if (!line)
line = emalloc(linelen);
for (;;) {
if (max >= 0)
size = MIN(max - (addr - skip), size);
if ((n = read(fd, buf, size)) <= 0)
break;
for (i = 0; i < n; i++, addr++) {
line[lineoff++] = buf[i];
if (lineoff == linelen) {
printline(line, lineoff, addr - lineoff + 1);
lineoff = 0;
}
}
}
if (n < 0) {
weprintf("read %s:", fname);
return n;
}
if (lineoff && last)
printline(line, lineoff, addr - lineoff);
if (last)
printline((unsigned char *)"", 0, addr);
return 0;
}
static int
lcm(unsigned int a, unsigned int b)
{
unsigned int c, d, e;
for (c = a, d = b; c ;) {
e = c;
c = d % c;
d = e;
}
return a / d * b;
}
2016-07-08 17:24:07 +00:00
static void
addtype(char format, int len)
{
struct type *t;
t = emalloc(sizeof(*t));
t->format = format;
t->len = len;
TAILQ_INSERT_TAIL(&head, t, entry);
}
static void
usage(void)
{
2016-07-08 17:24:07 +00:00
eprintf("usage: %s [-bdosvx] [-A addressformat] [-E | -e] [-j skip] "
"[-t outputformat] [file ...]\n", argv0);
}
int
main(int argc, char *argv[])
{
int fd;
struct type *t;
int ret = 0, len;
char *s;
big_endian = (*(uint16_t *)"\0\xff" == 0xff);
ARGBEGIN {
case 'A':
s = EARGF(usage());
if (strlen(s) != 1 || !strchr("doxn", s[0]))
usage();
addr_format = s[0];
break;
2016-07-08 17:24:07 +00:00
case 'b':
addtype('o', 1);
break;
case 'd':
addtype('u', 2);
break;
case 'E':
case 'e':
big_endian = (ARGC() == 'E');
break;
case 'j':
if ((skip = parseoffset(EARGF(usage()))) < 0)
usage();
break;
case 'N':
if ((max = parseoffset(EARGF(usage()))) < 0)
usage();
break;
2016-07-08 17:24:07 +00:00
case 'o':
addtype('o', 2);
break;
case 's':
addtype('d', 2);
break;
case 't':
s = EARGF(usage());
for (; *s; s++) {
switch (*s) {
case 'a':
case 'c':
2016-07-08 17:24:07 +00:00
addtype(*s, 1);
break;
case 'd':
case 'o':
case 'u':
case 'x':
/* todo: allow multiple digits */
if (*(s+1) > '0' && *(s+1) <= '9') {
len = *(s+1) - '0';
} else {
switch (*(s+1)) {
case 'C':
len = sizeof(char);
break;
case 'S':
len = sizeof(short);
break;
case 'I':
len = sizeof(int);
break;
case 'L':
len = sizeof(long);
break;
default:
len = sizeof(int);
}
}
addtype(*s++, len);
break;
default:
usage();
}
}
break;
Implement od(1) v-flag If this flag is not given, od(1) automatically replaces duplicate adjacent lines with an '*' for each reoccurence. If this flag is set, thus, no such filtering occurs. In this case this would mean having to somehow keep the last printed line in some backbuffer, building the next line and then doing the necessary comparisons. This basically means that we duplicate the functionality provided with uniq(1). So instead of $ od -t a > dump you'd rather do $ od -t a | uniq -f 1 -c > dump Skipping the first field is necessary, as the addresses obviously differ. Now, I was thinking hard why this flag even exists. If POSIX mandated to add the address before the asterisk, so we know the offset of duplicate occurrences, this would make sense. However, this is not the case. Using uniq(1) also gives nicer output: ~ $ echo "111111111111111111111111111111111111111111111111" | od -t a -v | uniq -f 1 -c 3 0000000 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0000060 nl 1 0000061 in comparison to $ echo "111111111111111111111111111111111111111111111111" | od -t a 0000000 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 * 0000060 nl 0000061 Before working on od(1), I didn't even know it would filter out duplicate adjacent lines like that. This is also a matter of predictability. Concluding, the v-flag is implicitly set and users urged to just use the existing tools provided by the system. I don't think we would break scripts either. Firstly, it's rather unlikely to have duplicate lines exactly matching the line-length of od(1). Secondly, even if a script did that specifically, in the worst case there would be a counting error or something. Given od(1) is mostly used interactively, we can safely assume this feature is for the benefit of the users. Ditch this legacy POSIX crap! Please enter the commit message for your changes. Lines starting
2015-09-30 10:54:24 +00:00
case 'v':
/* always set - use uniq(1) to handle duplicate lines */
Implement od(1) v-flag If this flag is not given, od(1) automatically replaces duplicate adjacent lines with an '*' for each reoccurence. If this flag is set, thus, no such filtering occurs. In this case this would mean having to somehow keep the last printed line in some backbuffer, building the next line and then doing the necessary comparisons. This basically means that we duplicate the functionality provided with uniq(1). So instead of $ od -t a > dump you'd rather do $ od -t a | uniq -f 1 -c > dump Skipping the first field is necessary, as the addresses obviously differ. Now, I was thinking hard why this flag even exists. If POSIX mandated to add the address before the asterisk, so we know the offset of duplicate occurrences, this would make sense. However, this is not the case. Using uniq(1) also gives nicer output: ~ $ echo "111111111111111111111111111111111111111111111111" | od -t a -v | uniq -f 1 -c 3 0000000 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0000060 nl 1 0000061 in comparison to $ echo "111111111111111111111111111111111111111111111111" | od -t a 0000000 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 * 0000060 nl 0000061 Before working on od(1), I didn't even know it would filter out duplicate adjacent lines like that. This is also a matter of predictability. Concluding, the v-flag is implicitly set and users urged to just use the existing tools provided by the system. I don't think we would break scripts either. Firstly, it's rather unlikely to have duplicate lines exactly matching the line-length of od(1). Secondly, even if a script did that specifically, in the worst case there would be a counting error or something. Given od(1) is mostly used interactively, we can safely assume this feature is for the benefit of the users. Ditch this legacy POSIX crap! Please enter the commit message for your changes. Lines starting
2015-09-30 10:54:24 +00:00
break;
2016-07-08 17:24:07 +00:00
case 'x':
addtype('x', 2);
break;
default:
usage();
} ARGEND
/* line length is lcm of type lengths and >= 16 by doubling */
TAILQ_FOREACH(t, &head, entry)
linelen = lcm(linelen, t->len);
if (TAILQ_EMPTY(&head))
linelen = 16;
while (linelen < 16)
linelen *= 2;
if (!argc) {
if (od(0, "<stdin>", 1) < 0)
ret = 1;
} else {
for (; *argv; argc--, argv++) {
if (!strcmp(*argv, "-")) {
*argv = "<stdin>";
fd = 0;
} else if ((fd = open(*argv, O_RDONLY)) < 0) {
weprintf("open %s:", *argv);
ret = 1;
continue;
}
if (od(fd, *argv, (!*(argv + 1))) < 0)
ret = 1;
if (fd != 0)
close(fd);
}
}
ret |= fshut(stdout, "<stdout>") | fshut(stderr, "<stderr>");
return ret;
}