#module  END_ANSI_TAPE_VOLUME  "V2.0"

/*++ENDTAPE.C

 Facility: 
	Fermilab Accelerator Control System - VAX/VMS ACNET
	General utility program.

 Abstract:
	Repair an ANSI magtape by terminating the volume after a specified
	file (with double tape marks).

 Environment:
	Foreign DCL command.
--*/

/*
 Modification History:
 Author: F. Nagy

 V1.0	06-Dec-83  FJN	Created to fix DL0002 Datalogger Archive tape
 V2.0	17-Dec-83  FJN	Flesh out into a foreign DCL command
*/

/******************
 * Include Files: *
 ******************/
#include  vaxtypes			/* VAX-11 C Type extensions */
#include  descrip			/* VAX-11 Descriptors */
#include  descrip2			/* Additional descriptor defs. */
#include  iodef				/* I/O definitions */
#include  ctype				/* ASCII character typing */

/********************************
 * Local definitions and types: *
 ********************************/
typedef unsigned long status;		/* Condition code status type */

#define  LABEL_SIZE	80		/* Size of label record in bytes */

					/* Status returned for tape I/O: */
globalvalue  SS$_ENDOFFILE;		/* EOF during tape read */
globalvalue  SS$_ENDOFTAPE;		/* EOT mark reached during tape I/O */
globalvalue  SS$_DATAOVERUN;		/* Tape record too larger for buffer */

					/* Signals for errors: */
globalvalue  ENDTAPE__VOLNAMLEN;	/* Volume id string too long */
globalvalue  ENDTAPE__NTHNOTPOS;	/* Occurrence number not > 0 */
globalvalue  ENDTAPE__ILLNEGVER;	/* Negative versions are illegal */
globalvalue  ENDTAPE__LBLMISMAT;	/* File ids in HDR1/EOF1 do not match */
globalvalue  ENDTAPE__LBLRECBIG;	/* Record too large for label */
globalvalue  ENDTAPE__LBLRECSML;	/* Record too small for label */
globalvalue  ENDTAPE__TAPERR;		/* General error reading tape */
globalvalue  ENDTAPE__PASTEOT;		/* EOF written past EOT mark */
globalvalue  ENDTAPE__UNEXPEOF;		/* Unexpected EOF */
globalvalue  ENDTAPE__UNEXPEOT;		/* Unexpected EOT */
globalvalue  ENDTAPE__WRONGLBL;		/* Wrong label record read */
globalvalue  ENDTAPE__WRONGVOL;		/* Wrong volume mounted */

/*****************************
 * Module Local Own Storage: *
 *****************************/
static int display_files = TRUE;	/* Display file names from tape */

static uword channel;			/* Channel assigned to the tape */

/**********************************************
 * Global Procedure and Function definitions: *
 **********************************************/

status SYS$QIOW();			/* VMS QIO system service */
int strncmp();				/* Bounded string comparison */


/*+ Main Program

 Functional Description:
	Accept and decode command line arguments and open channel to the
	magtape mounted /FOREIGN.  Rewind the tape and read the first
	record.  Check for VOL1 record and compare the volume id with that
	specified in the command.  Then enter the loop to find the "last"
	file:
		read record and check for HDR1, save file specification.
		skip 2 tape marks to get to EOF1 record.
		read EOF1 record, check and compare with HDR1 record.
		if file spec not that saved, skip tape mark and loop

	Once the EOF1 record of the "last" file has been reached, then
	2 records are read and checked for EOF2 and EOF3.  Then two
	tape marks are written to terminate the tape volume.

	If the VOL1 volume id does not match the specified volume name,
	the program exits with an error.  If a HDR1 or EOF1 record is
	not found where expected, the program aborts with an error.
	If the file specification of an EOF1 record does not match that
	of the preceesing HDR1 record, the program exits.  If the EOT
	is encountered before the tape mark write phase, the program exits.

 Calling Sequence:
	$ ENDTAPE := $FERMI$EXE:ENDTAPE
	$ ENDTAPE  device[:]  [-]volume-name  last-file-spec[;n]  [nth]

 Side Effects:
	If the volume name is preceeded by a minus (-), then the normal
	action of printing the file specifications (as encountered) will
	be bypassed.
-*/

main( argc, argv )
int argc;				/* Command line parameter count */
char *argv[];				/* Command line parameter strings */
{
status sts;				/* Completion status */
status SYS$ASSIGN();			/* Service to assign I/O channel */
status SYS$DASSGN();			/* Service to release I/O channel */
char device[64];			/* Magtape device name */
char volid[13];				/* Magtape volume name */
char file_spec[25];			/* Terminating file specification */
$DESCRIPTOR( d_device, device);		/* String descriptor for device */
int occurrence = 1;			/* End after 1st occurrence of file */
int version = 0;			/* Version number to match (ANY) */
char *strchr();				/* Point to a character in a string */
char *semi;				/* Pointer to ";" in file spec string */

/*
  Prompt for missing required arguments
*/
switch ((argc > 4) ? 4 : argc)
    {
case 1:
    do {
	printf("Device: ");
	argv[1] = gets( device);
	} while (*argv[1] == '\0');
case 2:
    do {
	printf("Volume id: ");
	argv[2] = gets( volid);
	} while (*argv[2] == '\0');
case 3:
    do {
	printf("Last file: ");
	argv[3] = gets( file_spec);
	} while (*argv[3] == '\0');
default:
    break;
    }

/*
  If optional 4th argument, then get the file occurrence count.
*/
if (argc > 4)
    {
    sscanf( argv[4], "%d", &occurrence);
    if (occurrence <= 0)
	LIB$SIGNAL( ENDTAPE__NTHNOTPOS);
    }

/*
  Get device name into uppercase and setup string descriptor.
*/
d_device.dsc$w_length = strcpy_upcase( argv[1], device);

/*
  Assign channel for magnetic tape volume.
*/
sts = SYS$ASSIGN( &d_device, &channel, 0, 0);
ABORT_ON_FAILURE( sts);

/*
  Get volume name in uppercase, checking for and stripping the leading
  optional "-" used to disable the printout of the encountered file names.
  Note that the volume name ca be at most 6 characters long (to which it
  is padded with spaces for the volume id check.
*/
if (strcpy_upcase(
	(*argv[2] == '-') ? (display_files = FALSE, ++argv[2]) : argv[2],
	volid) > 6)
    LIB$SIGNAL( ENDTAPE__VOLNAMLEN, 2, strlen(volid), volid);
else
    strpad( volid, 6);

/*
  Check for correct ANSI volume mounted.
*/
volume_check( volid);

/*
  Uppercase the file specification string, check for and remove the version
  number.  Especially check for the wildcard version ";*".
*/
if ((semi = strchr( argv[3], ';')) != 0)
    {
    *semi++ = '\0';			/* Terminate file id at ";" */
    if (*semi != '*')
	sscanf( semi, "%d", &version);	/* If not ";*" get binary version no. */
    if (version < 0)
	LIB$SIGNAL( ENDTAPE__ILLNEGVER);
    }
strcpy_upcase( argv[3], file_spec);	/* Uppercase the file id */
strpad( file_spec, 17);			/* And pad for testing. */

/*
  Find the "last" file and position tape past its EOF3 record.
*/
find_last_file( file_spec, version, occurrence);

/*
  Write two tape marks to end the volume here and rewind the tape.
*/
write_mark();
write_mark();
rewind_tape( FALSE);

/*
  Deassign I/O channel and exit.
*/
sts = SYS$DASSGN( channel);

SYS$EXIT( sts);
}

/*+/FIND_LAST_FILE

 Functional Description:
	Find the specified "last" file on the tape.

 Input Parameters:
	filespec - file id of the "last" file on the tape.
	version - VMS version number of the "last" file on the tape.
		  If zero then the version number is not checked against that
		  on the tape.
	nth - the nth match of the filespec/version number determines is the
	      "last" file (used when the same file/version occurs multiple
	      times on the tape).

 Implicit Inputs:
	NONE

 Output Parameters:
	NONE

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__WRONGLBL	wrong label record read
	ENDTAPE__LBLMISMAT	HDR1 and EOF1 file ids are mismatched

 Side Effects:
	Tape is left positioned after the EOF3 label of the "last" file.
-*/

find_last_file( filespec, version, nth )
char filespec[17];			/* File id of the "last" file */
int version;				/* VMS version number of "last" file */
int nth;				/* "last" is # of same file/version */
{
struct lbl1 {				/* Structure of HDR1/EOF1 labels */
    char label_id[4];			/* Label type identification */
    char file_id[17];			/* File identification */
    char fileset_id[6];			/* Volume set name */
    char file_section_no[4];
    char file_sequence_no[4];
    char generation_no[4];		/* File generation number */
    char generation_version[2];		/* Version-in-generation number */
    char creation_date[6];		/* File creation date */
    char expiration_date[6];		/* File expiration date */
    char file_accessibility;
    char block_count[6];
    char system_code[13];
    char l_reserved[7];			/* Reserved field */
    };
struct lbl1 hdr1;			/* Storage for HDR1 record */
struct lbl1 eofs;			/* Storage for EOFn records */
int not_last = TRUE;			/* Flag for "last" file found */
int vn, gn, gvn;			/* Binary version, generation and
					   generation-version numbers */
int atoi();				/* ASCII to binary conversion */

/*
  Loop over all tape files until the "last" file.
*/
while (not_last) {
    /*
	Read and check what should be the HDR1 label.
    */
    read_label( &hdr1);
    if (strncmp( hdr1.label_id, "HDR1", 4) != 0)
	LIB$SIGNAL( ENDTAPE__WRONGLBL, 4, 4, "HDR1", 4, hdr1.label_id);

    /*
	Skip 2 tape marks to skip HDR2/HDR3 labels and the data area.
    */
    skip_marks( 2);

    /*
	Read and check what should be the EOF1 label.
	Also compare EOF1 file id to the HDR1 file id.
    */
    read_label( &eofs);
    if (strncmp( eofs.label_id, "EOF1", 4) != 0)
	LIB$SIGNAL( ENDTAPE__WRONGLBL, 4, 4, "EOF1", 4, eofs.label_id);
    if (strncmp( hdr1.file_id, eofs.file_id, 17) != 0)
	LIB$SIGNAL( ENDTAPE__LBLMISMAT, 4, 17, hdr1.file_id, 17, eofs.file_id);

    /*
	Convert generation and generation-version numbers to binary to
	form pseudo-VMS file version.  The conversion formula is from the
	"Magnetic Tape User's Guide".
    */
    strtrunc( eofs.generation_version, 2);
    gvn = atoi( eofs.generation_version);
    strtrunc( eofs.generation_no, 4);
    gn = atoi( eofs.generation_no);
    vn = (gn - 1)*100 + gvn + 1;

    /*
	Check for "last" file and identify the just-processed file.
	The "last" file is found whenever:
	  1. the file identification strings match AND
	  2. Either the version number is 0 (match all versions) OR
	     the tape version number specifies the input version number
	  3. AND the occurrence counter is decremented to 0.
    */
    not_last = strncmp( eofs.file_id, filespec, 17) != 0;
    if ( !not_last)
	{
	/*
	  The following statements come from the boolean conditions:
		last := v=0 OR (v<>0 AND v=v');
		last := last AND nth=0;
		not_last := NOT last;
	  which leads to:
		not_last := (v<>0 AND v=0) OR (v<>0 AND v=v');
		not_last := not_last AND nth>0;
	  where the term (v<>0 AND v=0) is equivalent to FALSE.
	*/
	not_last = (version != 0) && (vn != version);
	if ( !not_last)
	    not_last = --nth > 0;
	}

    if (display_files)
	{
	strtrunc( eofs.file_id, 17);
	printf( "File %s;%d end.\n", eofs.file_id, vn);
	}

    /*
	If not the "last" file skip past EOF2/EOF3 records and the tape mark.
    */
    if (not_last)
	skip_marks( 1);
    }

/*
  Read and check for EOF2 and EOF3 labels of the last file.
*/
read_label( &eofs);
if (strncmp( eofs.label_id, "EOF2", 4) != 0)
    LIB$SIGNAL( ENDTAPE__WRONGLBL, 4, 4, "EOF2", 4, eofs.label_id);

read_label( &eofs);
if (strncmp( eofs.label_id, "EOF3", 4) != 0)
    LIB$SIGNAL( ENDTAPE__WRONGLBL, 4, 4, "EOF3", 4, eofs.label_id);

}

/*+/READ_LABEL

 Functional Description:
	Read a label record from the magnetic tape.

 Input Parameters:
	NONE

 Implicit Inputs:
	channel - channel number on which the tape is open.

 Output Parameters:
	label - buffer into which the label record is read.

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__LBLRECBIG	not label record, >80 bytes
	ENDTAPE__LBLRECSML	not label record, <80 bytes
	ENDTAPE__TAPERR		general tape I/O error
	ENDTAPE__UNEXPEOF	unexpected EOF reading label record
	ENDTAPE__UNEXPEOT	unexpected EOT reading label record

 Side Effects:
	NONE
-*/

read_label( label )
char label[LABEL_SIZE];			/* Buffer for label record */
{
status sts;				/* Completion status */
struct {				/* I/O status block */
    uword iosts;			/* I/O operation status */
    uword count;			/* Byte count of record */
    ulong device;			/* Device-dependent status */
    } iosb;

sts = SYS$QIOW( 0, channel, IO$_READLBLK, &iosb, 0, 0,
		label, LABEL_SIZE, 0, 0, 0, 0);

if ($VMS_FAILURE( sts))
    if (sts == SS$_ENDOFFILE)
	LIB$SIGNAL( ENDTAPE__UNEXPEOF);
    else if (sts == SS$_ENDOFTAPE)
	LIB$SIGNAL( ENDTAPE__UNEXPEOT);
    else if (sts == SS$_DATAOVERUN)
	LIB$SIGNAL( ENDTAPE__LBLRECBIG, 1, iosb.count);
    else
	LIB$SIGNAL( ENDTAPE__TAPERR, 0, sts);
else if (iosb.count < LABEL_SIZE)
    LIB$SIGNAL( ENDTAPE__LBLRECSML, 1, iosb.count);

}

/*+/REWIND_TAPE

 Functional Description:
	Rewind the tape.

 Input Parameters:
	wait - if TRUE, wait for the tape to rewind.  If FALSE, return
	       immediately after starting the tape rewinding.

 Implicit Inputs:
	channel - channel number on which the tape is open.

 Output Parameters:
	NONE

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__TAPERR		tape I/O error

 Side Effects:
	NONE
-*/

rewind_tape( wait )
int wait;				/* wait-for-rewind flag */
{
status sts;				/* Completion status */
uword iofcn = IO$_REWIND | IO$M_NOWAIT;	/* No-wait rewind assumed */

if (wait)
    iofcn = IO$_REWIND;			/* Rewind and wait */

sts = SYS$QIOW( 0, channel, iofcn, 0, 0, 0, 0, 0, 0, 0, 0, 0);

if ($VMS_FAILURE( sts))
	LIB$SIGNAL( ENDTAPE__TAPERR, 0, sts);

}

/*+/SKIP_MARKS

 Functional Description:
	Skip over one or more tape marks.

 Input Parameters:
	nn - number of tape marks to skip over.

 Implicit Inputs:
	channel - channel number on which the tape is open.

 Output Parameters:
	NONE

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__TAPERR		tape I/O error
	ENDTAPE__UNEXPEOT	unexpected end-of-tape

 Side Effects:
	NONE
-*/

skip_marks( nn )
int nn;					/* Number of tape marks to skip */
{
status sts;				/* Completion status */

sts = SYS$QIOW( 0, channel, IO$_SKIPFILE, 0, 0, 0,
		nn, 0, 0, 0, 0, 0);

if ($VMS_FAILURE( sts))
    if (sts == SS$_ENDOFTAPE)
	LIB$SIGNAL( ENDTAPE__UNEXPEOT);
    else
	LIB$SIGNAL( ENDTAPE__TAPERR, 0, sts);

}

/*+/STRCPY_UPCASE

 Functional Description:
	Copy from string to string and convert all characters to uppercase.

 Input Parameters:
	in - pointer to input (NUL-terminated) string.

 Implicit Inputs:
	NONE

 Output Parameters:
	Returns the number of characters (not including the NUL) in the string.
	out - pointer to output string buffer.

 Implicit Outputs:
	NONE

 Condition Codes:
	NONE

 Side Effects:
	NONE
-*/

int strcpy_upcase( in, out )
char *in;				/* Input string pointer */
char *out;				/* Output string pointer */
{
char tc;				/* Temporary character holder */
int nc = 0;				/* Character counter */

while ((tc = *in++) != '\0')
    {
    nc++;				/* Count string length */
    *out++ = _toupper( tc);		/* Uppercase the characters */
    }

*out = '\0';				/* Terminate output string */

return nc;
}

/*+/STRPAD

 Functional Description:
	Pad (in place) a string with blanks until there are n characters
	in the string.

 Input Parameters:
	string - string pointer.
	nwant - desired length of padded string.

 Implicit Inputs:
	NONE

 Output Parameters:
	string - string pointer.

 Implicit Outputs:
	NONE

 Condition Codes:
	NONE

 Side Effects:
	Padding is done in place.
-*/

strpad( string, nwant )
char *string;				/* String pointer */
int nwant;				/* Desired length of padded string */
{
int strlen();				/* Length of string to NUL */
int nspaces = nwant - strlen( string);	/* Number of padding blanks to add */
char *strpterm();			/* Returns pointer to the NUL */
char *end;				/* Pointer to string terminator */

end = strpterm( string);		/* Point to string terminator */

while (nspaces-- > 0)
    *end++ = ' ';			/* Add blanks at end */

*end = '\0';				/* Terminate string with NUL */

}

/*+/STRPTERM

 Functional Description:
	Return pointer to the NUL terminator of the input string.

 Input Parameters:
	string - input string pointer.

 Implicit Inputs:
	NONE

 Output Parameters:
	Returns a character pointer to the NUL character terminating the
	input string.

 Implicit Outputs:
	NONE

 Condition Codes:
	NONE

 Side Effects:
	NONE
-*/

char *strpterm( string )
char *string;				/* Input string pointer */
{

while (*string++ != '\0');		/* Slide pointer over non-NUL chars. */

return --string;			/* Return pointer to NUL character */
}

/*+/STRTRUNC

 Functional Description:
	Truncate a character string by inserting a NUL terminator after
	a specified maximum number of characters or by replacing the first
	blank in the string by the NUL.

 Input Parameters:
	string - string pointer.
	nmax - maximum number of characters in the string.

 Implicit Inputs:
	NONE

 Output Parameters:
	string - string pointer.

 Implicit Outputs:
	NONE

 Condition Codes:
	NONE

 Side Effects:
	Modifies the string in place.
-*/

strtrunc( string, nmax )
char *string;				/* String pointer */
int nmax;				/* Maximum non-blank length of string */
{
char ch;				/* Temporary for tests */

/*
  Skip over non-blank characters to first blank (i.e. end of text).
*/
while (((ch = *string++) != ' ') && (ch != '\0') && (nmax-- > 0));

*--string = '\0';			/* Truncate string in place */

}

/*+/VOLUME_CHECK

 Functional Description:
	Read first record on tape, should be VOL1 label.
	Check for expected volume id.

 Input Parameters:
	label - expected volume name (6 characters).

 Implicit Inputs:
	NONE

 Output Parameters:
	NONE

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__WRONGLBL	record is not VOL1 label record
	ENDTAPE__WRONGVOL	wrong volume id found

 Side Effects:
	The tape is initially rewound.
-*/

volume_check( label )
char label[6];				/* Expected volume label */
{
struct {				/* VOL1 label record */
    char label_id[4];			/* Label record type: VOL1 */
    char volume_id[6];			/* Volume identification */
    char vol_accessibility;		/* Volume accessibility */
    char v_reserved_1[26];		/* Reserved field */
    char owner_id[13];			/* Volume owner identification */
    char digital_version;		/* Digital standard version */
    char v_reserved_2[28];		/* Reserved field */
    char label_version;			/* Label record version */
    } vol1;

/*
  Rewind, waiting for completion
*/
rewind_tape( TRUE);

/*
  Read label record
*/
read_label( &vol1);

/*
  Check for VOL1 label record.
*/
if (strncmp( vol1.label_id, "VOL1", 4) != 0)
    LIB$SIGNAL( ENDTAPE__WRONGLBL, 4, 4, "VOL1", 4, vol1.label_id);

/*
  Check for expected volume name in the VOL1 label.
*/
if (strncmp( vol1.volume_id, label, 6) != 0)
    LIB$SIGNAL( ENDTAPE__WRONGVOL, 4, 6, label, 6, vol1.volume_id);

if (display_files)
    {
    strtrunc( vol1.volume_id, 6);
    printf( "Mounted tape volume %s for terminating.\n", vol1.volume_id);
    }

}

/*+/WRITE_MARK

 Functional Description:
	Write a tape mark.

 Input Parameters:
	NONE

 Implicit Inputs:
	channel - channel number on which the tape is open.

 Output Parameters:
	NONE

 Implicit Outputs:
	NONE

 Condition Codes Signalled:
	ENDTAPE__PASTEOT	EOF written past EOT mark
	ENTTAPE__TAPERR		tape I/O error

 Side Effects:
	NONE
-*/

write_mark()
{
status sts;				/* Completion status */

sts = SYS$QIOW( 0, channel, IO$_WRITEOF, 0, 0, 0, 0, 0, 0, 0, 0, 0);

if ($VMS_FAILURE( sts))
    if (sts == SS$_ENDOFTAPE)
	LIB$SIGNAL( ENDTAPE__PASTEOT);
    else
	LIB$SIGNAL( ENDTAPE__TAPERR, 0, sts);

}
