/* TAPEDUMP.C   (c) Copyright Jay Moseley, CCP 2000                        */
/*              Dump contents of AWSTAPE image file                        */
/*                                                                         */
/* This program is placed in the public domain and may be freely used      */
/* and incorporated into derived works so long as attribution to the       */
/* original authorship remains in any distributed copies of the C source.  */
/* /Jay Moseley/ January, 2008                                             */

/*-------------------------------------------------------------------------*/
/* Reads an AWSTAPE image file and prints report to STDOUT showing the     */
/* information about datasets contained on the tape image and optionally   */
/* can dump contents of standard labels and data records.                  */
/*-------------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <time.h>

typedef unsigned char HWORD [2];
typedef unsigned char BYTE;

/*-------------------------------------------------------------------------*/
/* Structure definition for AWSTAPE block header                           */
/*-------------------------------------------------------------------------*/
typedef struct _AWSTAPE_BLKHDR {
	HWORD   curblkl;                /* Length of current block         */
	HWORD   prvblkl;                /* Length of previous block        */
	BYTE    flags1;                 /* Flags byte 1                    */
	BYTE    flags2;                 /* Flags byte 2                    */
    } AWSTAPE_BLKHDR;

/* Definitions for AWSTAPE_BLKHDR flags byte 1                             */
#define AWSTAPE_FLAG1_NEWREC    0x80    /* Start of new record             */
#define AWSTAPE_FLAG1_TAPEMARK  0x40    /* Tape mark                       */
#define AWSTAPE_FLAG1_ENDREC    0x20    /* End of record                   */

/*-------------------------------------------------------------------------*/
/* EBCDIC -> ASCII Translation table                                       */
/*-------------------------------------------------------------------------*/
static unsigned char
ebcdic_to_ascii[] = {
"\x00\x01\x02\x03\xA6\x09\xA7\x7F\xA9\xB0\xB1\x0B\x0C\x0D\x0E\x0F"
"\x10\x11\x12\x13\xB2\xB4\x08\xB7\x18\x19\x1A\xB8\xBA\x1D\xBB\x1F"
"\xBD\xC0\x1C\xC1\xC2\x0A\x17\x1B\xC3\xC4\xC5\xC6\xC7\x05\x06\x07"
"\xC8\xC9\x16\xCB\xCC\x1E\xCD\x04\xCE\xD0\xD1\xD2\x14\x15\xD3\xFC"
"\x20\xD4\x83\x84\x85\xA0\xD5\x86\x87\xA4\xD6\x2E\x3C\x28\x2B\xD7"
"\x26\x82\x88\x89\x8A\xA1\x8C\x8B\x8D\xD8\x21\x24\x2A\x29\x3B\x5E"
"\x2D\x2F\xD9\x8E\xDB\xDC\xDD\x8F\x80\xA5\x7C\x2C\x25\x5F\x3E\x3F"
"\xDE\x90\xDF\xE0\xE2\xE3\xE4\xE5\xE6\x60\x3A\x23\x40\x27\x3D\x22"
"\xE7\x61\x62\x63\x64\x65\x66\x67\x68\x69\xAE\xAF\xE8\xE9\xEA\xEC"
"\xF0\x6A\x6B\x6C\x6D\x6E\x6F\x70\x71\x72\xF1\xF2\x91\xF3\x92\xF4"
"\xF5\x7E\x73\x74\x75\x76\x77\x78\x79\x7A\xAD\xA8\xF6\x5B\xF7\xF8"
"\x9B\x9C\x9D\x9E\x9F\xB5\xB6\xAC\xAB\xB9\xAA\xB3\xBC\x5D\xBE\xBF"
"\x7B\x41\x42\x43\x44\x45\x46\x47\x48\x49\xCA\x93\x94\x95\xA2\xCF"
"\x7D\x4A\x4B\x4C\x4D\x4E\x4F\x50\x51\x52\xDA\x96\x81\x97\xA3\x98"
"\x5C\xE1\x53\x54\x55\x56\x57\x58\x59\x5A\xFD\xEB\x99\xED\xEE\xEF"
"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\xFE\xFB\x9A\xF9\xFA\xFF"
	};

/*-------------------------------------------------------------------------*/
/* global variables                                                        */
/*-------------------------------------------------------------------------*/
char *inFileID = NULL;             /* -> input file name                   */
#define printSummary 0             /* summary print option setting         */
#define printDetail -1             /* detail print option setting          */
int  printOption = printSummary;   /* summary/detail print option          */
FILE *inf;                         /* Input file                           */
AWSTAPE_BLKHDR  awshdr;            /* AWSTAPE block header                 */
int hcurblkl;                      /* converted header current block len   */
int hprvblkl;                      /* converted header previous block len  */
BYTE buf[65500];                   /* input/output buffer                  */

/*-------------------------------------------------------------------------*/
/* parse command line arguments and validate                               */
/*-------------------------------------------------------------------------*/
void parseCommand (int argc, char *argv[])
{                                  /* loop pointer                         */
int i;
  /* if no parameters supplied, display syntax and exit                    */
  if (argc == 1)
  {
    puts("Syntax: TAPEDUMP INPUT: | IN: | I: <input file name>");
    puts("                 [ DETAIL | D ]");
    exit(1);

  } /* if */

  /* copy parameter values into global variables                           */
  for (i = 1; i < argc; i++)
  {
     /* looking for variations on INPUT: keyword                           */
     if (!strcasecmp(argv[i], "input") ||
	 !strcasecmp(argv[i], "input:") ||
	 !strcasecmp(argv[i], "in") ||
	 !strcasecmp(argv[i], "in:") ||
	 !strcasecmp(argv[i], "i") ||
	 !strcasecmp(argv[i], "i:"))
     {
       if ((argc-1) == i)
	 printf("<input file name> missing following %s parameter\n",
		argv[i]);
       else
	 inFileID = argv[(i+1)];
       continue;

     } /* if */

     /* looking for variations on DETAIL keyword                           */
     if (!strcasecmp(argv[i], "detail") ||
	 !strcasecmp(argv[i], "d"))
     {
       printOption = printDetail;
       continue;

     } /* if */
  } /* for */

  /* ensure that required parameters have been supplied                    */
  if (inFileID == NULL)
  {
    printf("Required INPUT: <file name> parameter omitted\n");
    exit(2);

  } /* if */

  return;

} /* parseCommand */

/*-------------------------------------------------------------------------*/
/* read a block of data (awshdr.curblkl characters) from AWSTAPE image     */
/*-------------------------------------------------------------------------*/
void readDataBlock(void)
{
int len;
int i;

  len = fread(buf, 1, hcurblkl, inf);
  if (len < hcurblkl)
  {
    printf("Error reading data block from %s: %s\n",
	   inFileID, strerror(errno));
    exit(6);

  } /* if */

  /* convert data in i/o buffer from EBCDIC to ASCII                       */
  for (i=0; i < hcurblkl; i++)
    buf[i] = ebcdic_to_ascii[buf[i]];

  return;

} /* readDataBlock */

/*-------------------------------------------------------------------------*/
/* read a AWS header information block from input AWSTAPE image            */
/*-------------------------------------------------------------------------*/
void readHeader (void)
{
int len;

  len = fread(buf, 1, sizeof(AWSTAPE_BLKHDR), inf);
  if ((len > 0) && (len < sizeof(AWSTAPE_BLKHDR)))
  {
    printf("Incomplete block header read\n");
    exit(4);

  } /* if */

  /* copy header from i/o buffer into header structure variable            */
  memcpy(&awshdr, buf, sizeof(AWSTAPE_BLKHDR));

  hcurblkl = awshdr.curblkl[0] + (awshdr.curblkl[1] << 8);
  hprvblkl = awshdr.prvblkl[0] + (awshdr.prvblkl[1] << 8);

  /* if the header is followed by a data block, read it now as well        */
  if (hcurblkl > 0)
      readDataBlock();

  return;

} /* readHeader */

/*-------------------------------------------------------------------------*/
/* main process of TAPEDUMP                                                */
/*-------------------------------------------------------------------------*/
int main (int argc, char*argv[])
{
BYTE prevFlags;                    /* previous header flag value           */
char labelFileName[17];            /* MVT dataset name from HDR1           */
char labelFileNumber[4];           /* MVT file number from HDR1            */
char labelRECFM;                   /* MVT RECFM from HDR2                  */
unsigned int labelBLKSIZE = 0;     /* MVT BLKSIZE from HDR2                */
unsigned int labelLRECL = 0;       /* MVT LRECL from HDR2                  */
unsigned int labelBLKCOUNT = 0;    /* MVT block count from EOF1            */
unsigned int auditBLKCOUNT = 0;    /* my count of blocks read              */
unsigned int offset;               /* used to deblock blocked records      */
int cptr;                          /* used to print data records           */
int firstSwitch = -1;              /* first pass indicator                 */

  printf("TAPEDUMP v1.x (C) copyright Jay Moseley, CCP 2000\n");

  /* validate and extract submitted parameter values                       */
  parseCommand(argc, argv);

  /* open the AWSTAPE image file                                           */
  inf = fopen(inFileID, "rb");
  if (inf == NULL)
  {
    printf("Error occurred opening input file %s: %s\n",
	 inFileID, strerror(errno));
    exit(3);

  } /* if */

  printf("Processing AWSTAPE file: %s\n", inFileID);

  /* this loop processes the entire contents of the AWSTAPE image file     */
  while (1)
  {
    readHeader();		   /* read header and associated data      */

    /* the first header should contain known values, so test for those as a
       means of determining if the specified file is a valid AWSTAPE image */
    if (firstSwitch < 0)
    {
      firstSwitch = 1;
      prevFlags = awshdr.flags1;
      
      if ((hcurblkl != 80) ||
	  (hprvblkl != 0) ||
	  (awshdr.flags1 != 160))
      {
        printf("Data for first header block seems to indicate that the file\n");
        printf("specified for input (%s) is not an AWSTAPE dataset.\n",
               inFileID);
        exit(5);

      } /* if (header validation) */
    } /* if (firstSwitch) */

    /* if detail print requested, print header information fields          */
    if (printOption == printDetail)
    {
      printf("Header: This: %i Prev: %i Flags: ",
	     hcurblkl, hprvblkl);
      if ((awshdr.flags1 & AWSTAPE_FLAG1_NEWREC) != 0)
	printf("x'80' ");
      if ((awshdr.flags1 & AWSTAPE_FLAG1_TAPEMARK) != 0)
	printf("x'40' ");
      if ((awshdr.flags1 & AWSTAPE_FLAG1_ENDREC) != 0)
	printf("x'20' ");
      printf("\n");

    } /* if */

    /* if there was a data block following the header, process it          */
    if (hcurblkl > 0)
    {
      /* if the data block contained a standard VOLume 1 label             */
      if (!strncasecmp(buf, "VOL1", 4))
      {
	if (printOption == printDetail)
	  printf("%.80s\n", buf);
	else
	{
	  printf("Serial Number from VOLume 1 label: %.6s\n\n",
		 buf+4);
	  printf("File  MVT Dataset       RECFM BLKSIZE LRECL ");
	  printf("BlocksExpected BlocksRead\n");

	} /* else (printOption == printDetail) */
      } /* end if (VOL1) */
      else
      /* if the data block contained a standard HeaDeR 1 label             */
      if (!strncasecmp(buf, "HDR1", 4))
      {
	memcpy(&labelFileName, buf+4, 17);
	memcpy(&labelFileNumber, buf+31, 4);

	if (printOption == printDetail)
	  printf("%.80s\n", buf);
	auditBLKCOUNT = 0;

      } /* if (HDR1) */
      else
      /* if the data block contained a standard HeaDeR 2 label             */
      if (!strncasecmp(buf, "HDR2", 4))
      {
	labelRECFM = buf[4];
	sscanf(buf+5, "%5u", &labelBLKSIZE);
	sscanf(buf+10, "%5u", &labelLRECL);

	if (printOption == printDetail)
	  printf("%.80s\n", buf);

      } /* if (HDR2) */
      else
      /* if the data block contained a standard EndOfFile 1 label          */
      if (!strncasecmp(buf, "EOF1", 4))
      {
	sscanf(buf+54, "%6u", &labelBLKCOUNT);

	if (printOption == printDetail)
	  printf("%.80s\n", buf);

      } /* if (EOF1) */
      else
      /* if the data block contained a standard EndOfFile 2 label          */
      if (!strncasecmp(buf, "EOF2", 4))
      {
	if (printOption == printDetail)
	  printf("%.80s\n", buf);
	else
	{
	  printf("%.4s  %.17s   ", labelFileNumber, labelFileName);
	  printf("%c   %7u ", labelRECFM, labelBLKSIZE);
	  printf("%5u       %7u", labelLRECL, labelBLKCOUNT);
	  printf("   %7u\n", auditBLKCOUNT);

	} /* else (printOption == printDetail) */
      }
      else
      /* the data block must have contained user data                      */
      {
	/* count the block to audit the expected (EOF1) block count        */
	auditBLKCOUNT++;

	if (printOption == printDetail)
	{
	  /* are the user data records unblocked?                          */
	  if (labelBLKSIZE == labelLRECL)
	  {
	    /* just print the block as a single logical record             */
	    for (cptr = 0; cptr < labelLRECL; cptr++)
	      printf("%c", buf[cptr]);
	    printf("\n");

	  } /* if (labelBLKSIZE == labelLRECL) */
	  else
	  /* the user records are blocked                                  */
	  {
	    /* loop through the data record offsetting for LRECL size      */
	    for (offset=0; offset < hcurblkl; offset+=labelLRECL)
	    {
	      /* printing the logical records from the block               */
	      for (cptr=0; cptr < labelLRECL; cptr++)
		printf("%c", buf[(offset+cptr)]);
	      printf("\n");

	    } /* for (record inside of block) */
	  } /* else (blocked records) */
	} /* if (printOption == printDetail) */
      } /* else (detail data record) */
    } /* if (block with non-zero length) */

    /* was this a TAPE MARK?                                               */
    if ((awshdr.flags1 & AWSTAPE_FLAG1_TAPEMARK))
    {
      if (printOption == printDetail)
	puts("TAPE MARK");

      /* two consecutive tape marks signify the end of the AWSTAPE image   */
      if (prevFlags == awshdr.flags1)
	break;

    } /* if (tape mark read) */

    prevFlags = awshdr.flags1;

  } /* while (1) */

  /* Close files and exit */
  fclose (inf);

  return 0;

} /* main process */

