/* $Header:od.c 12.0$ */
/* $ACIS:od.c 12.0$ */
/* $Source: /ibm/acis/usr/src/bin/RCS/od.c,v $ */

#ifndef lint
static char *rcsid = "$Header:od.c 12.0$";
#endif

static char *sccsid = "@(#)od.c	5.12 (Berkeley) 5/14/84";
/*
 * od -- octal, hex, decimal, character dump of data in a file.
 *
 * usage:  od [-abBcdDefFhHiIlLopPs[n]vw[n]xX] [file] [[+]offset[.][b] [label]]
 *
 * where the option flags have the following meaning:
 *   character	object	radix	signed?
 *	a	byte	(10)	(n.a.)	ASCII named byte stream
 *	b	byte	  8	 no	byte octal
 *	c	byte	 (8)	(no)	character with octal non-graphic bytes
 *	d	short	 10	 no
 *	D	long	 10	 no
 *	e,F	double	(10)		double precision floating pt.
 *	f	float	(10)		single precision floating pt.
 *	h,x	short	 16	 no
 *	H,X	long	 16	 no
 *	i	short	 10	yes
 *	I,l,L	long	 10	yes
 *	o,B	short	  8	 no	(default conversion)
 *	O	long	  8	 no
 *	s[n]	string	 (8)		ASCII graphic strings
 *
 *	p				indicate EVEN parity on 'a' conversion
 *	P				indicate ODD parity on 'a' conversion
 *	v				show all data - don't skip like lines.
 *	w[n]				bytes per display line
 *
 * More than one format character may be given.
 * If {file} is not specified, standard input is read.
 * If {file} is not specified, then {offset} must start with '+'.
 * {Offset} may be HEX (0xnnn), OCTAL (0nn), or decimal (nnn.).
 * The default is octal. The same radix will be used to display the address.
 */

#include <stdio.h>

#define DBUF_SIZE	BUFSIZ
#define BIG_DBUF	32
#define NO		0
#define YES		1
#define EVEN	       -1
#define ODD		1
#define UNSIGNED	0
#define SIGNED		1
#define PADDR		1
#define MIN_SLEN	3

int	a_put();
int	b_put();
int	c_put();
int	s_put();
int	us_put();
int	l_put();
int	f_put();
int	d_put();
int	st_put();

struct dfmt {
	int	df_field;	/* external field required for object */
	int	df_size;	/* size (bytes) of object */
	int	df_radix;	/* conversion radix */
	int	df_signed;	/* signed? flag */
	int	df_paddr;	/* "put address on each line?" flag */
	int	(*df_put)();	/* function to output object */
	char	*df_fmt;	/* output string format */
} *conv_vec[32];		/* vector of conversions to be done */

struct dfmt	ascii	= { 3, sizeof (char),   10,        0, PADDR,  a_put, 0};
struct dfmt	byte	= { 3, sizeof (char),    8, UNSIGNED, PADDR,  b_put, 0};
struct dfmt	cchar	= { 3, sizeof (char),    8, UNSIGNED, PADDR,  c_put, 0};
struct dfmt	u_s_oct	= { 6, sizeof (short),   8, UNSIGNED, PADDR, us_put, 0};
struct dfmt	u_s_dec	= { 5, sizeof (short),  10, UNSIGNED, PADDR, us_put, 0};
struct dfmt	u_s_hex	= { 4, sizeof (short),  16, UNSIGNED, PADDR, us_put, 0};
struct dfmt	u_l_oct	= {11, sizeof (long),    8, UNSIGNED, PADDR,  l_put, 0};
struct dfmt	u_l_dec	= {10, sizeof (long),   10, UNSIGNED, PADDR,  l_put, 0};
struct dfmt	u_l_hex	= { 8, sizeof (long),   16, UNSIGNED, PADDR,  l_put, 0};
struct dfmt	s_s_dec	= { 6, sizeof (short),  10,   SIGNED, PADDR,  s_put, 0};
struct dfmt	s_l_dec	= {11, sizeof (long),   10,   SIGNED, PADDR,  l_put, 0};
struct dfmt	flt	= {14, sizeof (float),  10,   SIGNED, PADDR,  f_put, 0};
struct dfmt	dble	= {21, sizeof (double), 10,   SIGNED, PADDR,  d_put, 0};
struct dfmt	string	= { 0,               0,  8,        0,    NO, st_put, 0};


char	usage[]	="usage: %s [-abcdfhilopswvx] [file] [[+]offset[.][b] [label]]\n";
char	dbuf[DBUF_SIZE];
char	lastdbuf[DBUF_SIZE];
int	addr_base	= 8;		/* default address base is OCTAL */
long	addr		= 0L;		/* current file offset */
long	label		= -1L;		/* current label; -1 is "off" */
int	dbuf_size	= 16;		/* file bytes / display line */
int	_parity		= NO;		/* show parity on ascii bytes */
char	fmt[]	= "            %s";	/* 12 blanks */
char	*icvt();
char	*scvt();
char	*underline();
long	get_addr();


/*
 * special form of _ctype
 */

#define A	01
#define G	02
#define D	04
#define P	010
#define X	020
#define isdigit(c)	(_ctype[c] & D)
#define isascii(c)	(_ctype[c] & A)
#define isgraphic(c)	(_ctype[c] & G)
#define isprint(c)	(_ctype[c] & P)
#define ishex(c)	(_ctype[c] & (X|D))

char	_ctype[256] = {
/* 000 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 010 */	A,	A,	A,	0,	A,	A,	0,	0,
/* 020 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 030 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 040 */     P|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 050 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 060 */ P|G|D|A,P|G|D|A,P|G|D|A,P|G|D|A,P|G|D|A,P|G|D|A,P|G|D|A,P|G|D|A,
/* 070 */ P|G|D|A,P|G|D|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 100 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 110 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 120 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 130 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 140 */   P|G|A,X|P|G|A,X|P|G|A,X|P|G|A,X|P|G|A,X|P|G|A,X|P|G|A,  P|G|A,
/* 150 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 160 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,
/* 170 */   P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,  P|G|A,	0,
/* 200 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 210 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 220 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 230 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 240 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 250 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 260 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 270 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 300 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 310 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 320 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 330 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 340 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 350 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 360 */	0,	0,	0,	0,	0,	0,	0,	0,
/* 370 */	0,	0,	0,	0,	0,	0,	0,	0,
};

char *progname;

main(argc, argv)
int	argc;
char	**argv;
{
	register char *p;
	register char *l;
	register n, same;
	struct dfmt	*d;
	struct dfmt	**cv = conv_vec;
	int	showall = NO;
	int	field, llen, nelm;
	int	max_llen = 0;
	progname = argv[0];

	argv++;
	argc--;

	if(argc > 0)
	{
		p = *argv;
		if(*p == '-')
		{
			while(*++p != '\0')
			{
				switch(*p)
				{
				case 'a':
					d = &ascii;
					break;
				case 'b':
					d = &byte;
					break;
				case 'c':
					d = &cchar;
					break;
				case 'd':
					d = &u_s_dec;
					break;
				case 'D':
					d = &u_l_dec;
					break;
				case 'e':
				case 'F':
					d = &dble;
					break;
				case 'f':
					d = &flt;
					break;
				case 'h':
				case 'x':
					d = &u_s_hex;
					break;
				case 'H':
				case 'X':
					d = &u_l_hex;
					break;
				case 'i':
					d = &s_s_dec;
					break;
				case 'I':
				case 'l':
				case 'L':
					d = &s_l_dec;
					break;
				case 'o':
				case 'B':
					d = &u_s_oct;
					break;
				case 'O':
					d = &u_l_oct;
					break;
				case 'p':
					_parity = EVEN;
					continue;
				case 'P':
					_parity = ODD;
					continue;
				case 's':
					d = &string;
					*(cv++) = d;
					while (isdigit(p[1]))
						d->df_size = (10 * d->df_size) + (*++p - '0');
					if (d->df_size <= 0)
						d->df_size = MIN_SLEN;
					showall = YES;
					continue;
				case 'w':
					dbuf_size = 0;
					while (isdigit(p[1]))
						dbuf_size = (10 * dbuf_size) + (*++p - '0');
					if (dbuf_size == 0)
						dbuf_size = BIG_DBUF;
					continue;
				case 'v':
					showall = YES;
					continue;
				default:
					printf("%s: bad flag -%c\n", progname, *p);
					fprintf(stderr, usage,progname);
					exit(1);
				}
				*(cv++) = d;
			}
			argc--;
			argv++;
		}
	}

	/*
	 * if nothing spec'd, setup default conversion.
	 */
	if(cv == conv_vec)
		*(cv++) = &u_s_oct;

	*cv = (struct dfmt *)0;

	/*
	 * calculate display parameters
	 */
	for (cv = conv_vec; d = *cv; cv++)
	{
		nelm = (dbuf_size + d->df_size - 1) / d->df_size;
		llen = nelm * (d->df_field + 1);
		if (llen > max_llen)
			max_llen = llen;
	}

	/*
	 * setup df_fmt to point to uniform output fields.
	 */
	for (cv = conv_vec; d = *cv; cv++)
	{
		if (d->df_field)	/* only if external field is known */
		{
			nelm = (dbuf_size + d->df_size - 1) / d->df_size;
			field = max_llen / nelm;
			d->df_fmt = fmt + 12 - (field - d->df_field);
		}
	}

	/*
	 * input file specified ?
	 */
	if(argc > 0 && **argv != '+')
	{
		if (freopen(*argv, "r", stdin) == NULL)
		{
			perror(*argv);
			exit(1);
		}
		argv++;
		argc--;
	}

	/*
	 * check for possible offset [label]
	 */
	if (argc > 0)
	{
		addr = get_addr(*argv);
		offset(addr);
		argv++;
		argc--;

		if (argc > 0)
			label = get_addr(*argv);
	}

	/*
	 * main dump loop
	 */
	same = -1;
	while ((n = fread(dbuf, 1, dbuf_size, stdin)) > 0)
	{
		if (same>=0 && bcmp(dbuf, lastdbuf, dbuf_size) == 0 && !showall)
		{
			if (same==0)
			{
				printf("*\n");
				same = 1;
			}
		}
		else
		{
			line(n);
			same = 0;
			p = dbuf;
			l = lastdbuf;
			for (nelm = 0; nelm < dbuf_size; nelm++)
			{
				*l++ = *p;
				*p++ = '\0';
			}
		}
		addr += n;
		if (label >= 0)
			label += n;
	}

	/*
	 * Some conversions require "flushing".
	 */
	n = 0;
	for (cv = conv_vec; *cv; cv++)
	{
		if ((*cv)->df_paddr)
		{
			if (n++ == 0)
				put_addr(addr, label, '\n');
		}
		else
			(*((*cv)->df_put))(0, *cv);
	}
}

put_addr(a, l, c)
long	a;
long	l;
char	c;
{
	fputs(icvt(a, addr_base, UNSIGNED, 7), stdout);
	if (l >= 0)
		printf(" (%s)", icvt(l, addr_base, UNSIGNED, 7));
	putchar(c);
}

line(n)
int	n;
{
	register i, first;
	register struct dfmt *c;
	register struct dfmt **cv = conv_vec;

	first = YES;
	while (c = *cv++)
	{
		if (c->df_paddr)
		{
			if (first)
			{
				put_addr(addr, label, ' ');
				first = NO;
			}
			else
			{
				putchar('\t');
				if (label >= 0)
					fputs("\t  ", stdout);
			}
		}
		i = 0;
		while (i < n)
			i += (*(c->df_put))(dbuf+i, c);
		if (c->df_paddr)
			putchar('\n');
	}
}

s_put(n, d)
short	*n;
struct dfmt	*d;
{
	printf(d->df_fmt, icvt((long)*n, d->df_radix, d->df_signed, d->df_field));
	return(d->df_size);
}

us_put(n, d)
unsigned short	*n;
struct dfmt	*d;
{
	printf(d->df_fmt, icvt((long)*n, d->df_radix, d->df_signed, d->df_field));
	return(d->df_size);
}

l_put(n, d)
long	*n;
struct dfmt	*d;
{
	printf(d->df_fmt, icvt(*n, d->df_radix, d->df_signed, d->df_field));
	return(d->df_size);
}

d_put(f, d)
double	*f;
struct dfmt *d;
{
	char fbuf[24];
	struct l { long n[2]; };

#if	vax
	if ((((struct l *)f)->n[0] & 0xff00) == 0x8000)	/* Vax illegal f.p. */
		sprintf(fbuf, "    %08x %08x",
			((struct l *)f)->n[0], ((struct l *)f)->n[1]);
	else
#endif

		sprintf(fbuf, "%21.14e", *f);
	printf(d->df_fmt, fbuf);
	return(d->df_size);
}

f_put(f, d)
float	*f;
struct dfmt *d;
{
	char fbuf[16];

#if	vax
	if ((*(long *)f & 0xff00) == 0x8000)	/* Vax illegal f.p. form */
		sprintf(fbuf, "      %08x", *(long *)f);
	else
#endif
		sprintf(fbuf, "%14.7e", *f);
	printf(d->df_fmt, fbuf);
	return(d->df_size);
}


char	asc_name[34][4] = {
/* 000 */	"nul",	"soh",	"stx",	"etx",	"eot",	"enq",	"ack",	"bel",
/* 010 */	" bs",	" ht",	" nl",	" vt",	" ff",	" cr",	" so",	" si",
/* 020 */	"dle",	"dc1",	"dc2",	"dc3",	"dc4",	"nak",	"syn",	"etb",
/* 030 */	"can",	" em",	"sub",	"esc",	" fs",	" gs",	" rs",	" us",
/* 040 */	" sp",	"del"
};

a_put(cc, d)
char	*cc;
struct dfmt *d;
{
	int c = *cc;
	register char *s = "   ";
	register pbit = parity((int)c & 0377);

	c &= 0177;
	if (isgraphic(c))
	{
		s[2] = c;
		if (pbit == _parity)
			printf(d->df_fmt, underline(s));
		else
			printf(d->df_fmt, s);
	}
	else
	{
		if (c == 0177)
			c = ' ' + 1;
		if (pbit == _parity)
			printf(d->df_fmt, underline(asc_name[c]));
		else
			printf(d->df_fmt, asc_name[c]);
	}
	return(1);
}

parity(word)
int	word;
{
	register int p = 0;
	register int w = word;

	if (w)
		do
		{
			p ^= 1;
		} while(w &= (~(-w)));
	return (p? ODD:EVEN);
}

char *
underline(s)
char	*s;
{
	static char ulbuf[16];
	register char *u = ulbuf;

	while (*s)
	{
		if (*s != ' ')
		{
			*u++ = '_';
			*u++ = '\b';
		}
		*u++ = *s++;
	}
	*u = '\0';
	return(ulbuf);
}

b_put(b, d)
char	*b;
struct dfmt *d;
{
	printf(d->df_fmt, icvt((long)*b & 0377, d->df_radix, d->df_signed, d->df_field));
	return(1);
}

c_put(cc, d)
char	*cc;
struct dfmt *d;
{
	register char	*s;
	register int	n;
	register int	c = *cc & 0377;

	s = scvt(c, d);
	for (n = d->df_field - strlen(s); n > 0; n--)
		putchar(' ');
	printf(d->df_fmt, s);
	return(1);
}

char *scvt(c, d)
int	c;
struct dfmt	*d;
{
	static char s[2];

	switch(c)
	{
		case '\0':
			return("\\0");

		case '\b':
			return("\\b");

		case '\f':
			return("\\f");

		case '\n':
			return("\\n");

		case '\r':
			return("\\r");

		case '\t':
			return("\\t");

		default:
			if (isprint(c))
			{
				s[0] = c;
				return(s);
			}
			return(icvt((long)c, d->df_radix, d->df_signed, d->df_field));
	}
}

/*
 * Look for strings.
 * A string contains bytes > 037 && < 177, and ends with a null.
 * The minimum length is given in the dfmt structure.
 */

#define CNULL		'\0'
#define S_EMPTY	0
#define S_FILL	1
#define	S_CONT	2
#define SBUFSIZE	1024

static char	str_buf[SBUFSIZE];
static int	str_mode = S_EMPTY;
static char	*str_ptr;
static long	str_addr;
static long	str_label;

st_put(cc, d)
char	*cc;
struct dfmt	*d;
{
	register int	c;

	if (cc == 0)
	{
		pr_sbuf(d, YES);
		return(1);
	}

	c = (*cc & 0377);

	if (str_mode & S_FILL)
	{
		if (isascii(c))
			put_sbuf(c, d);
		else
		{
			*str_ptr = CNULL;
			if (c == NULL)
				pr_sbuf(d, YES);
			str_mode = S_EMPTY;
		}
	}
	else if (isascii(c))
	{
		str_mode = S_FILL;
		str_addr = addr + (cc - dbuf);	  /* ugly */
		if ((str_label = label) >= 0)
			str_label += (cc - dbuf); /*  ''  */
		str_ptr = str_buf;
		put_sbuf(c, d);
	}

	return(1);
}

put_sbuf(c, d)
int	c;
struct dfmt	*d;
{
	*str_ptr++ = c;
	if (str_ptr >= (str_buf + SBUFSIZE))
	{
		pr_sbuf(d, NO);
		str_ptr = str_buf;
		str_mode |= S_CONT;
	}
}

pr_sbuf(d, end)
struct dfmt	*d;
int	end;
{
	register char	*p = str_buf;

	if (str_mode == S_EMPTY
	    || (!(str_mode & S_CONT) && (str_ptr - str_buf) < d->df_size))
		return;

	if (!(str_mode & S_CONT))
		put_addr(str_addr, str_label, ' ');

	while (p < str_ptr)
		fputs(scvt(*p++, d), stdout);

	if (end)
		putchar('\n');
}

/*
 * integer to ascii conversion
 *
 * This code has been rearranged to produce optimized runtime code.
 */

#define MAXINTLENGTH	32
static char	_digit[] = "0123456789abcdef";
static char	_icv_buf[MAXINTLENGTH+1];
static long	_mask = 0x7fffffff;

char *
icvt (value, radix, issigned, ndigits)
long	value;
int	radix;
int	issigned;
int	ndigits;
{
	register long	val = value;
	register long	rad = radix;
	register char	*b = &_icv_buf[MAXINTLENGTH];
	register char	*d = _digit;
	register long	tmp1;
	register long	tmp2;
	long	rem;
	long	kludge;
	int	sign;

	if (val == 0)
	{
		*--b = '0';
		sign = 0;
		goto done; /*return(b);*/
	}

	if (issigned && (sign = (val < 0)))	/* signed conversion */
	{
		/*
		 * It is necessary to do the first divide
		 * before the absolute value, for the case -2^31
		 *
		 * This is actually what is being done...
		 * tmp1 = (int)(val % rad);
		 * val /= rad;
		 * val = -val
		 * *--b = d[-tmp1];
		 */
		tmp1 = val / rad;
		*--b = d[(tmp1 * rad) - val];
		val = -tmp1;
	}
	else				/* unsigned conversion */
	{
		sign = 0;
		if (val < 0)
		{	/* ALL THIS IS TO SIMULATE UNSIGNED LONG MOD & DIV */
			kludge = _mask - (rad - 1);
			val &= _mask;
			/*
			 * This is really what's being done...
			 * rem = (kludge % rad) + (val % rad);
			 * val = (kludge / rad) + (val / rad) + (rem / rad) + 1;
			 * *--b = d[rem % rad];
			 */
			tmp1 = kludge / rad;
			tmp2 = val / rad;
			rem = (kludge - (tmp1 * rad)) + (val - (tmp2 * rad));
			val = ++tmp1 + tmp2;
			tmp1 = rem / rad;
			val += tmp1;
			*--b = d[rem - (tmp1 * rad)];
		}
	}

	while (val)
	{
		/*
		 * This is really what's being done ...
		 * *--b = d[val % rad];
		 * val /= rad;
		 */
		tmp1 = val / rad;
		*--b = d[val - (tmp1 * rad)];
		val = tmp1;
	}

done:
	if (sign)
		*--b = '-';

	tmp1 = ndigits - (&_icv_buf[MAXINTLENGTH] - b);
	tmp2 = issigned? ' ':'0';
	while (tmp1 > 0)
	{
		*--b = tmp2;
		tmp1--;
	}

	return(b);
}

long get_addr(s)
register char *s;
{
	register char *p;
	register long a;
	register int d;

	if (*s=='+')
		s++;
	if (*s=='x')
	{
		s++;
		addr_base = 16;
	}
	else if (*s=='0' && s[1]=='x')
	{
		s += 2;
		addr_base = 16;
	}
	else if (*s == '0')
		addr_base = 8;
	p = s;
	while(*p)
	{
		if (*p++=='.')
			addr_base = 10;
	}
	for (a=0; *s; s++)
	{
		d = *s;
		if(isdigit(d))
			a = a*addr_base + d - '0';
		else if (ishex(d) && addr_base==16)
			a = a*addr_base + d + 10 - 'a';
		else
			break;
	}

	if (*s == '.')
		s++;
	if(*s=='b')
		a *= 512;
	if(*s=='B')
		a *= 1024;

	return(a);
}

offset(a)
long	a;
{
	if (canseek(stdin))
	{
		/*
		 * in case we're accessing a raw disk,
		 * we have to seek in multiples of a physical block.
		 */
		fseek(stdin, a & 0xfffffe00L, 0);
		a &= 0x1ffL;
	}
	dumbseek(stdin, a);
}

dumbseek(s, offset)
FILE	*s;
long	offset;
{
	char	buf[BUFSIZ];
	int	n;
	int	nr;

	while (offset > 0)
	{
		nr = (offset > BUFSIZ) ? BUFSIZ : (int)offset;
		if ((n = fread(buf, 1, nr, s)) != nr)
		{
			fprintf(stderr, "EOF\n");
			exit(1);
		}
		offset -= n;
	}
}

#include <sys/types.h>
#include <sys/stat.h>

canseek(f)
FILE	*f;
{
	struct stat statb;

	return( (fstat(fileno(f),&statb)==0) &&
		(statb.st_nlink > 0) &&		/*!pipe*/
		(!isatty(fileno(f))) );
}
