/* Octal (and other formats) dump of contents of a file.
   Copyright 1984 (C) Richard Stallman

   Permission is granted to anyone to make or distribute
   verbatim copies of this program
   provided that the copyright notice and this permission notice are preserved;
   and provided that the recipient is not asked to waive or limit his right to
   redistribute copies as permitted by this permission notice;
   and provided that anyone possessing a machine-executable copy
   is granted access to copy the source code, in machine-readable form,
   in some reasonable manner.

   Permission is granted to distribute derived works or enhanced versions of
   this program under the above conditions with the additional condition
   that the entire derivative or enhanced work
   must be covered by a permission notice identical to this one.

   Anything distributed as part of a package containing portions derived
   from this program, which cannot in current practice perform its function
   usefully in the absence of what was derived directly from this program,
   is to be considered as forming, together with the latter,
   a single work derived from this program,
   which must be entirely covered by a permission notice identical to this one
   in order for distribution of the package to be permitted.

 In other words, you are welcome to use, share and improve this program.
 You are forbidden to forbid anyone else to use, share and improve
 what you give them.   Help stamp out software-hoarding!  */

#include <stdio.h>

/* Format to be used: */

#define OCTAL 1
#define DECIMAL 2
#define SIGNED_DECIMAL 3
#define HEX 4
#define FLOAT 5
#define ASCII 6
#define CHARNAME 7
#define STRINGS 8

int format;	/* Value is one of the codes listed above */

/* Size of unit of dumping, in bytes.  */

int size;

/* What to do about bit 7 of each byte, in CHARNAME mode.
  1 => flag odd parity.  2 => flag even parity.  0 => ignore it. */

int parity;

/* Number of bytes to display on each line of output.  */

int bytesperline;

/* Nonzero means elide an output line identical to the previous one.  */

int elide;

/* Radix for printing addresses (must be 8, 10 or 16) */

int address_radix;

/* For STRINGS output format, this is minimum length of sequence
  of graphic chars to trigger output.  */

int stringmin;

/* Name of file to be dumped.  */

char *filename;		/* 0 means dump fropm stdin.  */

/* Starting offset in the file, in bytes.  */

long start_point;

/* Stopping point in the file, in bytes.  */

long stop_point;

/* Base of pseudo-address to use for printed lines.
  This address is printed for the first line of data
  and appropriately greater addresses for later lines.
  Pseudo-addresses are printed if `labelflag' is nonzero.  */

long label;
int labelflag;

char *concat ();
long integer_arg ();

main (argc, argv)
     int argc;
     char **argv;
{
  FILE *stream;

  /* Initialize all parameters to their defaults */

  format = OCTAL;
  size = 2;
  bytesperline = 16;
  elide = 1;
  start_point = 0;
  stop_point = -1;
  filename = 0;
  labelflag = 0;
  label = 0;
  address_radix = 8;
  parity = 0;		/* Relevant only for CHARNAMES mode */
  stringmin = 3;	/* Relevant only if STRINGS mode selected */

  {
    int i;
    for (i = 1; i < argc; i++)
      {
	if (argv[i][0] == '-')
	  {    /* this arg is a switch */
	    char *p = &argv[i][1];
	    char c;

	    if (!strcmp (argv[i], "-B"))
	      {
		if (++i == argc)
		  fatal ("-B used without argument", 0);
		start_point = integer_arg (argv[i]);
		continue;
	      }
	    if (!strcmp (argv[i], "-E"))
	      {
		if (++i == argc)
		  fatal ("-E used without argument", 0);
		stop_point = integer_arg (argv[i]);
		continue;
	      }
	    if (!strcmp (argv[i], "-R"))
	      {
		if (++i == argc)
		  fatal ("-R used without argument", 0);
		address_radix = integer_arg (argv[i]);
		continue;
	      }
	    if (!strcmp (argv[i], "-L"))
	      {
		if (++i == argc)
		  fatal ("-L used without argument", 0);
		labelflag = 1;
		label = integer_arg (argv[i]);
		continue;
	      }

	    while (c = *p++)
	      switch (c)
		{
		case 'a':
		  format = CHARNAME;
		  size = 1;
		  break;

		case 'b':
		  format = OCTAL;
		  size = 1;
		  break;

		case 'c':
		  format = ASCII;
		  size = 1;
		  break;

		case 'd':
		  format = DECIMAL;
		  size = 2;
		  break;

		case 'D':
		  format = DECIMAL;
		  size = 4;
		  break;

		case 'f':
		  format = FLOAT;
		  size = 4;
		  break;

		case 'F':
		  format = FLOAT;
		  size = 8;
		  break;

		case 'h':
		  format = HEX;
		  size = 2;
		  break;

		case 'H':
		  format = HEX;
		  size = 4;
		  break;

		case 'i':
		  format = SIGNED_DECIMAL;
		  size = 2;
		  break;

		case 'l':
		case 'I':
		  format = SIGNED_DECIMAL;
		  size = 4;
		  break;

		case 'o':
		  format = OCTAL;
		  size = 2;
		  break;

		case 'O':
		  format = OCTAL;
		  size = 4;
		  break;

		case 'p':
		  parity = 1;
		  break;

		case 'P':
		  parity = 2;
		  break;

		case 's':
		  format = STRINGS;
		  if (*p >= '0' && *p <= '9')
		    stringmin = parse_int (&p);
		  break;

		case 'v':
		  elide = 0;
		  break;

		case 'w':
		  if (*p >= '0' && *p <= '9')
		    bytesperline = parse_int (&p);
		  else
		    bytesperline = 32;
		  break;

		case 'x':
		  format = HEX;
		  size = 2;
		  break;

		case 'X':
		  format = HEX;
		  size = 4;
		  break;
		}
	  }
	else
	  break;	/* We have reached a non-switch. */
      }
    if (i < argc)
      filename = argv[i++];
    if (i < argc)
      error ("too many arguments; extra arguments being ignored");
  }

  /* Command args have been decoded; do the work */

  if (filename)
    stream = fopen (filename, "r");
  else
    stream = stdin;

  if (!stream)
    pfatal_with_name (filename);

  /* Seek or skip to specified start point in the file */

  if (fseek (stream, start_point, 0))
    {
      /* fseek did not work; do it the slow way. */
      char *buf[1024];
      long i;
      for (i = start_point / 1024; i > 0; i--)
	fread (buf, 1, 1024, stream);
      fread (buf, 1, start_point % 1024, stream);
    }

  if (format == STRINGS)
    dump_strings (stream);
  else
    {
      long address = start_point;
      /* buffer holds data for this line, buffer1 for prev line */
      char *buffer = (char *) malloc (bytesperline);
      char *buffer1 = (char *) malloc (bytesperline);
      int linecount = 0;
      int elidecount = 0;

      /* Loop, a line's worth of data at a time.  */

      while (stop_point == -1 || address < (unsigned) stop_point)
        {
	  /* Read the data */

	  int nread = fread (buffer, 1, bytesperline, stream);
	  if (!nread) break;

	  /* If this line matches the previous, elide it.  */

	  if (elide && linecount++ && !bcmp (buffer, buffer1, bytesperline))
	    {
	      if (!elidecount++)
	        printf ("*\n");
	    }
	  else
	    {
	      char *tem;

	      /* Not eliding => print the line of data  */

	      elidecount = 0;
              dumpline (buffer, address, nread);
	      putchar ('\n');

	      /* the line just dumped becomes "previous" by pointer-shuffle */

	      tem = buffer; buffer = buffer1; buffer1 = tem;
	    }
          address += nread;
        }
    }
  fclose (stream);
}

/* Dump the data in `buffer' as one line of text.
  There are `nbytes' bytes actually available.
  The data came from address `address' within the input file.
 */

dumpline (buffer, address, nbytes)
     char *buffer;
     long address;
     int nbytes;
{
  /* If specified stop point is within this line, don't go past it.  */

  if (stop_point != -1 && (unsigned) stop_point - address < nbytes)
    nbytes = (unsigned) stop_point - address;

  print_address (address);

  {
    char *p = buffer;
    int units = (nbytes + size - 1) / size;
    int i;

    for (i = 0; i < units; i++)
      {
	if (format == FLOAT)
	  {
	    if (size == 4)
	      printf ("%15.7#e", *(float *) p);
	    else
	      printf ("%22.14#e", *(double *) p);
	  }
	else
	  {
	    int unit;

	    /* Fetch the next unit - `size' says how long it is.  */
	    switch (size)
	      {
	      case 1:
	        unit = *(unsigned char *) p;
	        break;
	      case 2:
		if (format == SIGNED_DECIMAL)
		  unit = *(short *) p;
		else
	          unit = *(unsigned short *)p;
	        break;
	      case 4:
	        unit = *(int *)p;
	        break;
	      }
	    /* Print the unit.  */
	    switch (format)
	      {
	      case DECIMAL:
		printf (" %0*u", 5 * (size >> 1), unit);
		break;

	      case SIGNED_DECIMAL:
		printf (" %*d", 1 + 5 * (size >> 1), unit);
		break;

	      case OCTAL:
		printf (" %0*o", (size == 4) ? 11 : size * 3, unit);
		break;

	      case HEX:
		printf (" %0*x", size * 2, unit);
		break;

	      case ASCII:
		dump_char (unit & 0377);
		break;

	      case CHARNAME:
		dump_charname (unit);
		break;

	      default:
		printf (" xxx", unit);
	      }
	  }
	p += size;
      }
  }
}

/* Print the address of a line of output data.
  This is done at the beginning of each line.
  A space is printed after the address.  */

print_address (address)
     long address;
{
  print_address_1 (address);
  putchar (' ');
  if (labelflag)
    {
      putchar ('(');
      print_address_1 (address - start_point + label);
      putchar (')');
      putchar (' ');
    }
}

print_address_1 (address)
     long address;
{
  switch (address_radix)
    {
      case 8:
	printf ("%07o", address);
	break;

      case 10:
	printf ("%7d", address);
	break;

      case 16:
	printf ("0x%06x", address);
	break;

      default: ;
    }
}

/* Dump a single character, as a character, right-justified in four columns */

dump_char (c)
     unsigned char c;
{
  switch (c)
    {
    case 0:
      printf ("  \\0");
      break;

    case '\n':
      printf ("  \\n");
      break;

    case '\t':
      printf ("  \\t");
      break;

    case '\r':
      printf ("  \\r");
      break;

    case '\b':
      printf ("  \\b");
      break;

    case '\f':
      printf ("  \\f");
      break;

    default:
      if (c < 040 || c >= 0200)
	printf (" %03o", c);
      else
	printf ("   %c", c);
    }
}

/* Print the name of character c, and indicate its parity
 in the way specified by `parity'.  */
char *charname[33] =
  {
    "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
    "bs", "ht", "lf", "vt", "ff", "cr", "so", "si",
    "dle", "dc1", "dc2", "dc3", "nak", "syn", "etb",
    "can", "em", "sub", "esc", "fs", "gs", "rs", "us",
    "sp"
  };


dump_charname (c)
     char c;
{
  char c1 = c & 127;

  if (c1 == 127)
    printf (" del");
  else if (c1 > 040)
    printf ("   %c", c1);
  else printf ("%4s", charname[c1]);

  if (parity)
    {
      if ((parity == 1 && char_parity (c) == 0) ||
	  (parity == 2 && char_parity (c) == 1))
	putchar ('*');
      else
	putchar (' ');
    }
}

/* Return 1 if character `c' has odd parity.  */

char_parity (c)
     char c;
{
  return !!(c & 1) ^ !!(c & 2) ^ !!(c & 4) ^ !!(c & 8) ^
	 !!(c & 16) ^ !!(c & 32) ^ !!(c & 64) ^ !!(c & 128);
}

/* STRINGS mode.  Find each "string constant" in the file.
A string constant is a run of at least `stringmin' ASCII graphic (or formatting) characters
terminated by a null.  */

dump_strings (stream)
     FILE *stream;
{
  int bufsize = 100;
  char *buf = (char *) malloc (bufsize);
  long address = start_point;

  while (1)
    {
      int i;
      int c;

      /* See if the next `stringmin' chars are all graphic chars.  */
    tryline:
      if (stop_point != -1 && address >= (unsigned) stop_point)
	break;
      for (i = 0; i < stringmin; i++)
	{
	  c = getc (stream);
	  address++;
	  if (c < 0) return;
	  if (!graphicp (c))
	    /* Found a non-graphic.  Try again starting with next char.  */
	    goto tryline;
	  buf [i] = c;
	}
      /* We found a run of `stringmin' graphic characters */
      /* Now see if it is terminated with a null byte.   */
      while (1)
	{
	  if (i == bufsize)
	    buf = (char *) xrealloc (buf, bufsize *= 2);
	  c = getc (stream);
	  address++;
	  if (c < 0) return;
	  if (c == 0) break;  /* It is; print this string */
	  if (!graphicp (c))
	    goto tryline;     /* It isn't; give up on this string */
	  buf[i++] = c;       /* String continues; store it all. */
	}
      /* If we get here, the string is all graphics and null-terminated,
	 so print it.  It is all in `buf' and `i' is its length.  */
      buf[i] = 0;
      print_address (address - i - 1);
      for (i = 0; c = buf[i]; i++)
	switch (c)
	  {
	  case '\n':
	    printf ("\\n");
	    break;
	  case '\t':
	    printf ("\\t");
	    break;
	  case '\f':
	    printf ("\\f");
	    break;
	  case '\b':
	    printf ("\\b");
	    break;
	  case '\r':
	    printf ("\\r");
	    break;
	  default:
	    putchar (c);
	  }
      putchar ('\n');
    }
}

/* Return 1 if `c' is an ASCII graphic character */

int
graphicp (c)
     unsigned char c;
{
  return (c >= 040 && c < 0177) ||
      c == '\n' || c == '\t' || c == '\b' || c == '\f' || c == '\r';
}

/* Print error message and exit.  */

fatal (s1, s2)
     char *s1, *s2;
{
  error (s1, s2);
  exit (1);
}

/* Print error message.  `s1' is printf control string, `s2' is arg for it. */

error (s1, s2)
     char *s1, *s2;
{
  printf ("od: ");
  printf (s1, s2);
  printf ("\n");
}

pfatal_with_name (name)
     char *name;
{
  extern int errno, sys_nerr;
  extern char *sys_errlist[];
  char *s;

  if (errno < sys_nerr)
    s = concat ("", sys_errlist[errno], " for %s");
  else
    s = "cannot open %s";
  fatal (s, name);
}

/* Return a newly-allocated string whose contents concatenate those of s1, s2, s3.  */

char *
concat (s1, s2, s3)
     char *s1, *s2, *s3;
{
  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
  char *result = (char *) xmalloc (len1 + len2 + len3 + 1);

  strcpy (result, s1);
  strcpy (result + len1, s2);
  strcpy (result + len1 + len2, s3);
  *(result + len1 + len2 + len3) = 0;

  return result;
}

/* Like malloc but get fatal error if memory is exhausted.  */

int
xmalloc (size)
     int size;
{
  int result = malloc (size);
  if (!result)
    fatal ("virtual memory exhausted", 0);
  return result;
}


int
xrealloc (ptr, size)
     char *ptr;
     int size;
{
  int result = realloc (ptr, size);
  if (!result)
    fatal ("virtual memory exhausted");
  return result;
}

/* Parse string `s' as an integer, using decimal radix by default,
 but allowing octal and hex numbers as in C.  */

long
integer_arg (s)
     char *s;
{
  long value;
  int radix = 10;
  char *p = s;
  int c;

  if (*p != '0')
    radix = 10;
  else if (*++p == 'x')
    {
      radix = 16;
      p++;
    }
  else
    radix = 8;

  value = 0;
  while (((c = *p++) >= '0' && c <= '9')
	 || (radix == 16 && (c & ~40) >= 'A' && (c & ~40) <= 'Z'))
    {
      value *= radix;
      if (c >= '0' && c <= '9')
	value += c - '0';
      else
	value += (c & ~40) - 'A';
    }

  if (c == 'b')
    value *= 512;
  else if (c == 'B')
    value *= 1024;
  else
    p--;

  if (*p)
    fatal ("invalid integer argument %s", s);
  return value;
}

/* Parse an integer in decimal off string `*p' points to,
 and update *p to point at the terminating character.
 The integer is returned as the value.  */

parse_int (p)
     char **p;
{
  int val = atoi (*p);

  while (**p >= '0' && **p <= '9') (*p)++;

  return val;
}
