/* MAKETAPE.C   (c) Copyright Jay Moseley, CCP 2000                        */
/*              Create 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 one or more line sequential ASCII files and creates one or more   */
/* datasets contained on an AWSTAPE image file.                            */
/*-------------------------------------------------------------------------*/

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

#define MAXFILES 50
#define MAXLRECL 32767

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                   */

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

/*-------------------------------------------------------------------------*/
/* global variables                                                        */
/*-------------------------------------------------------------------------*/
char *pInFileID = NULL;            /* -> input file name                   */
char *volSer = NULL;               /* -> volume serial number              */
char *datasetName = NULL;          /* -> dataset name                      */
char *outFileID = NULL;            /* -> output file name                  */
int pLRECL = 80;                   /* input/output LRECL                   */
int pBLKFACTOR = 1;                /* output blocking factor               */
int blkSize;                       /* physical block size                  */
#define outputGLOB 0               /* output single dataset                */
#define outputUNIQUE -1            /* each input becomes separate output   */
int outGLOBBING = outputGLOB;      /* output single dataset                */
int inFileSeq;                     /* used to index inFileID               */
FILE *inMeta;                      /* pointer to meta file stream          */
FILE *inData;                      /* pointer to input data file stream    */
FILE *outf;                        /* output file                          */
AWSTAPE_BLKHDR  awshdr;            /* AWSTAPE block header                 */
BYTE buf[65500];                   /* output AWSTAPE buffer                */
int blkCount;                      /* block count for EOF1                 */
char julianToday[5];               /* today's date in julian format        */
char *mode = "r";                  /* mode for input files to be opened in */
int binary = 0;                    /* whether input files are binary       */
int nltape = 0;                    /* whether this is a non-labelled tape  */
int ansi = 0;                      /* whether this is an ANSI-label tape   */
char inFileID[MAXFILES][FILENAME_MAX+1];       
    /* array of MAXFILES files to read/include    */

/*-------------------------------------------------------------------------*/
/* build julian date from system date for standard label                   */
/*-------------------------------------------------------------------------*/
void julianDate (void)
{
time_t timer;
struct tm *tblock;
char year[3];                      /* y2k compensation                     */

  timer = time(NULL);              /* get time of day from system          */
  tzset();                         /* read TZ environment variable         */
  tblock = localtime(&timer);      /* convert to date/time block           */

  sprintf(year, "%3.3i", tblock->tm_year);
  strncpy(julianToday, year+1, 2);
  sprintf(julianToday+2, "%3.3i", tblock->tm_yday);

  return;

} /* julianDate */

/*-------------------------------------------------------------------------*/
/* parse command line arguments and validate                               */
/*-------------------------------------------------------------------------*/
void parseCommand (int argc, char *argv[])
{
int i;                             /* index to parse arguments             */
int termFlag;                      /* indicate missing/invalid arguments   */

  /* if no parameters supplied, display syntax and exit                    */
  if (argc == 1)
  {
    printf("Syntax: MAKETAPE \n");
    printf("           INPUT: <input file name> | ");
    printf("@<file containing file list>\n");
    printf("           VOLSER: <1 to 6 character volume serial number>\n");
    printf("           DATASET: <1 to 17 character dataset label>\n");
    printf("           OUTPUT: <output file name>\n");
    printf("           [ LRECL: <record length> ]\n");
    printf("           [ BLOCK: <blocking factor> ]\n");
    printf("           [ UNIQUE ]\n");
    printf("           [ BINARY ]\n");
    printf("           [ NLTAPE ]\n");
    printf("           [ ANSI ]\n");
    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
         pInFileID = argv[(i+1)];
       continue;

     } /* if */

     /* looking for variations on OUTPUT: keyword                          */
     if (!strcasecmp(argv[i], "output") ||
         !strcasecmp(argv[i], "output:") ||
         !strcasecmp(argv[i], "out") ||
         !strcasecmp(argv[i], "out:") ||
         !strcasecmp(argv[i], "o") ||
         !strcasecmp(argv[i], "o:"))
     {
       if ((argc-1) == i)
         printf("<output file name> missing following %s parameter\n",
                argv[i]);
       else
         outFileID = argv[(i+1)];
       continue;

     } /* if */

     /* looking for variations on VOLSER: keyword                          */
     if (!strcasecmp(argv[i], "volser") ||
         !strcasecmp(argv[i], "volser:") ||
         !strcasecmp(argv[i], "vol") ||
         !strcasecmp(argv[i], "vol:") ||
         !strcasecmp(argv[i], "v") ||
         !strcasecmp(argv[i], "v:"))
     {
       if ((argc-1) == i)
         printf("<serial number> missing following %s parameter\n",
                argv[i]);
       else
         volSer = argv[(i+1)];
       continue;

     } /* if */

     /* looking for variations on DATASET: keyword                         */
     if (!strcasecmp(argv[i], "dataset") ||
         !strcasecmp(argv[i], "dataset:") ||
         !strcasecmp(argv[i], "data") ||
         !strcasecmp(argv[i], "data:") ||
         !strcasecmp(argv[i], "d") ||
         !strcasecmp(argv[i], "d:"))
     {
       if ((argc-1) == i)
         printf("<dataset name> missing following %s parameter\n",
                argv[i]);
       else
         datasetName = argv[(i+1)];
       continue;

     } /* if */

     /* looking for variations on LRECL: keyword                           */
     if (!strcasecmp(argv[i], "lrecl") ||
         !strcasecmp(argv[i], "lrecl:") ||
         !strcasecmp(argv[i], "l") ||
         !strcasecmp(argv[i], "l:"))
     {
       if ((argc-1) == i)
         printf("<record length> missing following %s parameter\n",
                argv[i]);
       else
         sscanf(argv[(i+1)], "%5u", &pLRECL);
       continue;

     } /* if */

     /* looking for variations on BLOCK: keyword                           */
     if (!strcasecmp(argv[i], "block") ||
         !strcasecmp(argv[i], "block:") ||
         !strcasecmp(argv[i], "b") ||
         !strcasecmp(argv[i], "b:"))
     {
       if ((argc-1) == i)
         printf("<blocking factor> missing following %s parameter\n",
                argv[i]);
       else
         sscanf(argv[(i+1)], "%3u", &pBLKFACTOR);
       continue;

     } /* if */

     /* looking for variations on UNIQUE keyword                           */
     if (!strcasecmp(argv[i], "unique") ||
         !strcasecmp(argv[i], "u"))
     {
       outGLOBBING = outputUNIQUE;
       continue;

     } /* if */

     /* looking for variations on BINARY keyword                           */
     if (!strcasecmp(argv[i], "binary"))
     {
       mode = "rb";
       binary = 1;
       continue;

     } /* if */

     /* looking for variations on NLTAPE keyword                           */
     if (!strcasecmp(argv[i], "nltape"))
     {
       nltape = 1;
       continue;

     } /* if */

     /* looking for variations on ANSI keyword                             */
     if (!strcasecmp(argv[i], "ansi"))
     {
       ansi = 1;
       continue;

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

  /* initialize input file path table to null values                       */
  for (inFileSeq = 0; inFileSeq < MAXFILES; inFileSeq++)
    strcpy(inFileID[inFileSeq], "");

  /* if single input file specified                                        */
  if (strncasecmp(pInFileID, "@", 1))
  {
    if (((inData = fopen(pInFileID, "r")) == NULL))
    {
      printf("Error occurred opening input file %s: %s\n",
             pInFileID, strerror(errno));
    } /* if (single input file open test failed) */
    else
    {
      fclose(inData);
      inData = NULL;
      strncpy(inFileID[0], pInFileID, FILENAME_MAX);
    } /* else (single input file open test succeeded) */
  }
  else
  /* specified file is a meta file                                         */
  {
    if (((inMeta = fopen(pInFileID+1, "r")) == NULL))
    {
      printf("Error occurred opening input (meta) file %s: %s\n",
             pInFileID, strerror(errno));
    } /* if (meta file open failed) */
    else
    {
      i = 0;
      inFileSeq = 0;

      /* read meta file, validate file names and copy to table             */
      while (1)
      {
        i++;
        /* exit while when EOF reached on META file                        */
        if (fgets(buf, 255, inMeta) == NULL)
          break;

        *strchr(buf, '\n') = '\0'; /* replace newline character            */
        if (((inData = fopen(buf, "r")) == NULL))
        {
          printf("Error occurred opening included input file %i-%s: %s\n",
                 i, buf,  strerror(errno));
        } /* if (included file open test failed) */
        else
        {
          fclose(inData);
          inData = NULL;
          if (inFileSeq == MAXFILES)
          {
            printf("number of files specified in %s exceeds %d\n",
                   pInFileID, MAXFILES);
            printf("excess files ignored\n");
            break;
          } /* if (maximum included files exceeded) */
          else
          {
            strncpy(inFileID[inFileSeq], buf, FILENAME_MAX);
            inFileSeq++;
          } /* else (maximum included files not exceeded */
        } /* else (included file open test failed */
      } /* while (reading through meta file) */
      fclose(inMeta);
      inMeta = NULL;
    } /* else (meta file open succeeded) */
  } /* else (is a meta file) */

  termFlag = 0;
  if (strlen(inFileID[0]) == 0)
  {
    printf("required INPUT: <input file name> omitted or not found\n");
    termFlag = 1;
  }
  if (volSer == NULL)
  {
    printf("required VOLSER: <volume serial number> omitted\n");
    termFlag = 1;
  }
  if (datasetName == NULL)
  {
    printf("required DATASET: <dataset name> omitted\n");
    termFlag = 1;
  }

  if (outFileID == NULL)
  {
    printf("required OUTPUT: <output file name> omitted\n");
    termFlag = 1;
  }
  if (termFlag == 1)
    exit(2);

  return;
} /* parseCommand */

/*-------------------------------------------------------------------------*/
/* write data buffer to output file preceeded by AWS header block          */
/*-------------------------------------------------------------------------*/
void writeBuffer (int bufferLen)
{
int i;                         /* index buffer for ASCII -> EBCDIC convert */
unsigned int len;              /* length of record to write/written        */
char hbuf[6];                  /* header block output buffer               */

  /* set up fields in header block                                         */
  awshdr.prvblkl[0] = awshdr.curblkl[0];
  awshdr.prvblkl[1] = awshdr.curblkl[1];
  awshdr.curblkl[0] = bufferLen & 0xFF;
  awshdr.curblkl[1] = (bufferLen >> 8) & 0xFF;
  awshdr.flags1 = '\xA0';
  awshdr.flags2 = '\0';

  /* copy header information to output buffer for writing                  */
  memcpy(hbuf, &awshdr, 6);

  /* write header block to AWSTAPE image file                              */
  len = 6;
  if ((len = fwrite(hbuf, 1, len, outf)) != 6)
  {
    printf("Error occurred writing header to output file %s: %s\n",
         outFileID, strerror(errno));
    exit(4);

  } /* if */

  /* convert data in i/o buffer from ASCII to EBCDIC                       */
  if (!binary)
  {
    for (i=0; i < bufferLen; i++)
      buf[i] = ascii_to_ebcdic[buf[i]];
  }

  /* write data block to AWSTAPE image file                                */
  if ((fwrite(buf, 1, bufferLen, outf) != bufferLen))
  {
    printf("Error occurred writing data record to output file %s: %s\n",
         outFileID, strerror(errno));
    exit(5);

  } /* if */

  return;

} /* writeBuffer */

/*-------------------------------------------------------------------------*/
/* build standard labels and call writeBuffer to write                     */
/*-------------------------------------------------------------------------*/
void writeLabel (char ltype, char lseq)
{
char dsn[18];                      /* temporary dataset name variable      */
char fseq[5];                      /* temporary file sequence variable     */
int i, j;                          /* used to index through string vars    */

  /* clear buffer to spaces to build label                                 */
  for (i = 0; i < 80; i++)
    buf[i] = ' ';

  if (ltype == 'H')
    strncpy(buf, "HDR", 3);        /* HeaDeR label requested               */
  else
    strncpy(buf, "EOF", 3);        /* EndOfFile label requested            */

  buf[3] = lseq;                   /* insert passed number into label      */

  /* copy dataset name parameter to work variable, at conclusion j will
     contain the index to the first blank past the end of the label text   */
  strncpy(dsn, datasetName, 17);
  for (i = j = 0; i < 17; i++)
    if (dsn[i] == '\0')
    {
      if (j == 0)
        j = i;
      dsn[i] = ' ';
    }
  dsn[18] = '\0';

  /* if each file is to be output discretely, manufacture unique dsn       */
  if (outGLOBBING == outputUNIQUE)
  {
    /* copy input file sequence number to work variable                    */
    sprintf(fseq, "%4.4i", (inFileSeq+1));
    fseq[5] = '\0';

    if (j == 0 || j > 11)
    {
      dsn[11] = '.';
      dsn[12] = 'F';
      strncpy(dsn+13, fseq, 4);
    } /* if (dsn length > 11) */
    else
    {
     dsn[j] = '.';
     j++;
     dsn[j] = 'F';
     j++;
      strncpy(dsn+j, fseq, 4);
    } /* else (dsn length < 11) */
  } /* if (outGLOBBING) */
  else
    strncpy(fseq, "0001", 4);      /* only 1 file on output tape           */

  /* format label based upon passed sequence number                        */
  if (lseq == '1')                 /* label #1                             */
  {
    strncpy(buf+4, dsn, 17);         /* dataset name                       */
    strncpy(buf+21, volSer, 6);      /* volume serial number               */
    strncpy(buf+27, "0001", 4);      /* volume sequence number             */
    strncpy(buf+31, fseq, 4);        /* file number                        */
    strncpy(buf+35, "    ", 4);      /* generation number                  */
    strncpy(buf+39, "  ", 2);        /* version number                     */
    strncpy(buf+42, julianToday, 5); /* creation date                      */
    strncpy(buf+48, "99365", 5);     /* expiration date                    */
    buf[53] = '0';                   /* security - 0 = none                */
    if (ansi) buf[53] = ' ';
    if (ltype == 'H')
      strncpy(buf+54, "000000", 6);  /* no block count in header           */
    else
      sprintf(buf+54, "%6.6i", blkCount); /* count of blocks written      */
  } /* if (label #1) */
  else
  {
    buf[4] = 'F';                    /* record format - F = fixed          */
    sprintf(buf+5, "%5.5i", blkSize); /* physical block size               */
    sprintf(buf+10, "%5.5i", pLRECL); /* physical record size              */
    buf[15] = '3';                   /* density                            */
    buf[16] = '0';                   /* dataset position                   */
    strncpy(buf+17, "EXTERNAL/EXTERNAL", 17); /* job/step creating         */
    if (pBLKFACTOR != 1)
      buf[38] = 'B';                 /* blocked records                    */
  } /* else (label #2) */

  /* ensure that label fields only contain upper case                      */
  for (i = 0; i < 80; i++)
    buf[i] = toupper(buf[i]);

  if (!nltape)
  {
      int tmpbin = binary;
      
      binary = ansi;
      writeBuffer(80);
      binary = tmpbin;
  }

  /* show dataset info when last end label written for each file           */
  if (ltype == 'E' && lseq == '2')
    printf("Wrote %i blocks to AWSTAPE file: %s (Seq #%.4s Dataset:%.17s)\n",
           blkCount, outFileID, fseq, dsn);

  return;

} /* writeLabel */

/*-------------------------------------------------------------------------*/
/* write tape mark record to AWS image file                                */
/*-------------------------------------------------------------------------*/
void writeTapeMark (void)
{
unsigned int len;                  /* length of header block written       */
char hbuf[6];                      /* output buffer for header block       */

  /* set fields in header block for tape mark                              */
  awshdr.prvblkl[0] = awshdr.curblkl[0];
  awshdr.prvblkl[1] = awshdr.curblkl[1];
  awshdr.curblkl[0] = 0;
  awshdr.curblkl[1] = 0;
  awshdr.flags1 = '\x40';
  awshdr.flags2 = '\0';

  /* copy header block to output buffer                                    */
  memcpy(hbuf, &awshdr, 6);

  /* write header block to AWSTAPE image file                              */
  len = 6;
  if ((len = fwrite(hbuf, 1, len, outf)) != 6)
  {
    printf("Error occurred writing tape mark to output file %s: %s\n",
         outFileID, strerror(errno));
    exit(6);
  }

  return;

} /* writeTapeMark */

/*-------------------------------------------------------------------------*/
/* main process loop of MAKETAPE                                           */
/*-------------------------------------------------------------------------*/
int main (int argc, char *argv[])
{
int i;                             /* used to index string vars            */
int offset;                        /* used for building blocked records    */

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

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

  /* build julian date for standard labels                                 */
  julianDate();

  /* open the AWSTAPE image file                                           */
  if ((outf = fopen(outFileID, "wb")) == NULL)
  {
    printf("Error occurred opening output file %s: %s\n",
         outFileID, strerror(errno));
    exit(3);

  } /* if */

  /* build and write VOLume 1 label                                        */
  for (i = 0; i < 80; i++)
    buf[i] = ' ';
  strncpy(buf, "VOL1", 4);
  strncpy(buf+4, volSer, 6);
  if (!ansi) buf[10] = '0';

  /* ensure that label fields only contain upper case                      */
  for (i = 0; i < 80; i++)
    buf[i] = toupper(buf[i]);
  
  if (ansi)
  {
    buf[79] = '1';
  }

  if (!nltape)
  {
      int tmpbin = binary;
      
      binary = ansi;
      writeBuffer(80);
      binary = tmpbin;
  }

  inFileSeq = 0;                   /* index of input files                 */
  blkSize = pLRECL * pBLKFACTOR;   /* compute physical block size          */
  offset = 0;                      /* intrablock pointer                   */

  /* if single output file specified, write header labels                  */
  if ((outGLOBBING == outputGLOB) && !nltape)
  {
    writeLabel('H', '1');
    writeLabel('H', '2');
    writeTapeMark();
  } /* if (outGLOBBING) */

  /* loop for processing all specified input files                         */
  while (1)
  {
    printf("Processing input from: %s\n", inFileID[inFileSeq]);

    if (((inData = fopen(inFileID[inFileSeq], mode)) == NULL))
    {
      printf("Error occurred opening input file %i-%s: %s\n",
             inFileSeq, inFileID[inFileSeq],  strerror(errno));
    } /* if (input file open test failed) */

    /* if multiple output files specified, write header labels             */
    if ((outGLOBBING == outputUNIQUE) && !nltape)
    {
      writeLabel('H', '1');
      writeLabel('H', '2');
      writeTapeMark();
    } /* if (outGLOBBING) */

    /* loop for processing records from current input file                 */
    while (1)
    {
      if (binary)
      {
        i = fread(&buf[offset], 1, pLRECL, inData);
        if (i == 0)
          break;
        /* auto-pad to LRECL */
        if (i < pLRECL)
        {
          memset(&buf[offset + i], '\0', pLRECL - i);
        }
      }
      else
      {
        /* exit inner loop when EOF reached on data file                   */
        if (fgets(&buf[offset], MAXLRECL, inData) == NULL)
          break;

        /* copy input to output buffer until newline or LRECL reached      */
        for (i = 0; i < pLRECL ; i++)
        {
          if (buf[offset+i] == '\n')
            break;
        }
        /* if input record length < LRECL, pad output buffer with spaces   */
        for ( ; i < pLRECL ; i++)
          buf[offset+i] = ' ';
      }

      /* update block pointer and check for end of block                   */
      offset+=pLRECL;
      if (offset == blkSize)
      {
        writeBuffer (offset);
        blkCount++;
        offset=0;
      } /* if (offset = blksize [block full]) */
    } /* while (processing input data file) */

    fclose(inData);                /* close input data file                */

    /* write trailing labels for multiple output files                     */
    if (outGLOBBING == outputUNIQUE)
    {
      /* if partial block at end of input data file, write it              */
      if (offset > 0)
      {
        writeBuffer (offset);
        blkCount++;
        offset = 0;
      }
      if (!nltape)
      {
        writeTapeMark();
        writeLabel('E', '1');
        writeLabel('E', '2');
      }
      writeTapeMark();
      blkCount = 0;
    } /* if (writing separate files) */

    inFileSeq++;                   /* increment to next input file ID      */

    /* exit while if file ID blank or max input files processed            */
    if ((inFileSeq == MAXFILES) ||
        (strlen(inFileID[inFileSeq]) == 0))
      break;

  } /* while (loop processing all input data files                         */

  /* if partial block at end of last input file, write it                  */
  if (offset > 0)
  {
    writeBuffer (offset);
    blkCount++;
  }

  /* write trailing labels for single output file                          */
  if (outGLOBBING == outputGLOB)
  {
    writeTapeMark();
    if (!nltape)
    {
      writeLabel('E', '1');
      writeLabel('E', '2');
      writeTapeMark();
    }
  } /* if (writing single output file) */

  /* write closing tape mark for end of volume indication                  */
  writeTapeMark();

  /* Close output file and exit */
  fclose (outf);

  return 0;
} /* end main */


