[net.sources] vmstape - read/write VMS format magtapes under 4.{2,3} BSD

horton@harvard.ARPA (Nike Horton) (06/21/85)

'vmstape' is a Harvard utility to read and write files in a format
compatible with VMS systems.  It does not do particularly smart 
things with incompatible filenames.  It has a syntax reminiscent of
'tar'.  

Please send bugs to 'manager@harvard.ARPA', or 'harvard!manager'.

------------------------ cut here for best results -------------------------
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# READ_ME vmstape.1 Makefile vmstape.h field.c header3.c skiptm.c vmstape.c vt_append.c vt_extract.c vt_list.c vt_write.c

echo x - READ_ME
cat > "READ_ME" << '//E*O*F READ_ME//'
By default, the makefile will compile the vmstape assuming
	the new 4.2 directory structure emulation by libndir.a and ndir.h.

If these files do not exist, the makefile must be changed (as noted in the file)
	 to add and remove definitions for the variables DEFS and LIBS.


Authors: Glen Dudek and Steve Kaufer
  	 Harvard University Science Center
//E*O*F READ_ME//

echo x - vmstape.1
cat > "vmstape.1" << '//E*O*F vmstape.1//'
.TH VMSTAPE 1H
.SH NAME
vmstape \- manipulate tapes in accordance with VAX-VMS standards
.SH SYNOPSIS
vmstape [tcxrvfbdFRHV] [files]
.SH DESCRIPTION
.LP
.I Vmstape
is a program that manipulates the tape format that is the standard for
VMS systems.  The format is based on the ANSI standard for tapes.  The
switches to the
.I vmstape
program are as similar to the switches for
.I tar
as possible.
.SH SWITCHES
.LP
.I c
is used to
.I create
a new tape with the named files on it.
All old information on the tape is overwritten.
.LP
.I r
appends the named files to the end of the tape. All old information
is unchanged on the tape.
.LP
If a directory name is passed as an argument to the 
.I c
or
.I r
switches, all the files in the directory will be written on the tape.
.LP
.I t
reports a directory listing of the files on the tape.
.LP
.I x extracts 
the named files from the tape.  If no individual files are
specified, then all files are extracted from the tape.
.LP
All other switches are modifiers to these basic commands.  Not all modifiers
are applicable to all the commands.
.LP
.I v
causes a
.I verbose
output describing the programs actions on the tape.
.LP
.I f
is used to specify an alternate magtape device.  The default
assumes the tape is 1600 bpi density and will rewind the tape when the
program is done.
.LP
.I d
divides the tape into sections so that the VMS "directory" command will
list the tape in sections.  This is useful when writing subdirectories in
UNIX.
.LP
.I b
is used to change the default block size to the length specified.
.LP
.I V
is used to specify an alternate volume label.
.LP
.I F
is used to indicate that files are NOT textual, and should therefore be 
written in a fixed length format.
.LP
.I R
is used to change the default fixed length record size to the length 
specified.
.LP
.I H
provides a help screen.

.SH SEE ALSO
tar(1), tp(1), mtb(8), retrieve(8)
.SH DIAGNOSTICS
.LP
Improperly formatted tapes, not made in agreement with the VMS standard,
may generate missing tape mark errors.
.LP
Problems with the tape and/or magtape drive will generate messages to the
effect that the program was unable to read or write the tape.
.SH BUGS
.LP
As DEC changes the standard for VAX-VMS tapes,
the program will need alterations.
.LP
If the user attemps to write a non-textual file without the F (or R) switch,
the file will not be completely written.  
//E*O*F vmstape.1//

echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
CFILES	=	field.c	header3.c	skiptm.c	vmstape.c	vt_append.c \
	vt_extract.c	vt_list.c	vt_write.c

OBJECTS =	field.o	header3.o	skiptm.o	vmstape.o	vt_append.o \
	vt_extract.o	vt_list.o	vt_write.o

CMD	= vmstape
DESTIN	= /usr/local/bin
CFLAGS	= -O $(DEFS)
DEFS	= -DNEWDIR
# to compile for ver 4.2 BSD  OR any version that CANNOT emulate 
#		the 4.2 directory structure with the files ndir.h and libndir.a:
#	define in DEFS '-Dnewdir'.
#	do not define '-lndir' in LIBS.
# to compile for any version of unix WITH the 4.2 directory EMULATION package:
# 	do not define  '-Dnewdir' in DEFS.
#	define '-lndir' in LIBS.
LIBS	= 
OWNER	= 
MODE	= 0755

$(CMD)	: $(OBJECTS) /lib/libc.a 
	ld /lib/crt0.o $(OBJECTS) -o $(CMD) $(LIBS) -lc -lg

install	: $(DESTIN)/$(CMD)

$(DESTIN)/$(CMD) : $(CMD)
	cp $(CMD) $(DESTIN)/$(CMD)
	strip $(DESTIN)/$(CMD)
	chmod $(MODE) $(DESTIN)/$(CMD)

depend	:
	@makedepend $(CFILES)

clean	:
	-rm $(OBJECTS) $(CMD) 

print	:
	print -h $(CFILES) 

lint	:
	lint -h $(CFILES) $(DEFS)

# DO NOT DELETE THIS LINE -- make depends on it

header3.o	: vmstape.h

skiptm.o	: /usr/include/sys/types.h /usr/include/sys/mtio.h \
	 vmstape.h /usr/include/sys/ioctl.h

vmstape.o	: /usr/include/stdio.h vmstape.h

vt_append.o	: /usr/include/stdio.h /usr/include/sys/types.h \
	 /usr/include/sys/stat.h /usr/include/sys/mtio.h vmstape.h \
	/usr/include/sys/ioctl.h

vt_extract.o	: /usr/include/stdio.h /usr/include/ctype.h \
	 /usr/include/sys/types.h /usr/include/sys/stat.h vmstape.h

vt_list.o	: vmstape.h

vt_write.o	: /usr/include/stdio.h /usr/include/sys/types.h \
	 /usr/include/sys/stat.h /usr/include/sys/dir.h vmstape.h
//E*O*F Makefile//

echo x - vmstape.h
cat > "vmstape.h" << '//E*O*F vmstape.h//'
#ifndef MAXNAMLEN /* maxnamlen is defined in the new directory package, but
			not the old.  This is our way of telling if we
			are using the new package or not */
#define MAXNAMLEN 14 /* This will set maxnamlen for old dir structs*/
#define OLD_DIR_STRUCT 1 /* for compilation of proper subrs */
#endif

#define TRUE    1
#define FALSE   0

#define	streq(a,b)	(!strcmp(a,b))

/* exit status codes for program
 */
#define	SUCCESS	0
#define	FAILURE (-1)

#define MAGTAPE "/dev/rmt8" 

int blocksz;
#define BLOCKSZ       2048		
#define MAXBLOCKSZ 	2048		/* random choice */
int fixreclen;
#define FIXRECLEN	128		/* should be multiple of BLOCKSZ */
#define MAXFIXRECLEN	MAXBLOCKSZ

#define	RECSIZE		80		/* size of Header and Trailer
					 * records
					 */

/*
 * The ANSI Standard Magtape produced by a VAX looks like
 *
 *      Volume Label            80 bytes
 *      --------------------------------
 *      Header 1                80 bytes
 *      Header 2                80 bytes
 *     (Header  3)	       (80 bytes)   ... optional
 *      TAPE MARK
 *      Data Block              2048 bytes
 *      ....
 *      TAPE MARK
 *      Trailer 1               80 bytes
 *      Trailer 2               80 bytes
 *     (Trailer 3)	       (80 bytes)   ... optional
 *      TAPE MARK
 *      ---------------------------------
 *      TAPE MARK
 *
 * The information between the dashed lines is repeated for
 * every file on the tape. See appendix B of VAX-11 RMS Reference
 * for additional detailed information.
 */

/* The definitions used here reflect the fact that in C, counting starts
 * at 0 and not 1
 */

/*
 * fields of Volume Label
 */
#define __XXX		0,2	/* alphabetic characters "VOL"	*/
#define __YYY		3,3	/* numberic character "1"	*/
#define	VLABEL		4,9
#define	VPROT		10,10	/* protection on volume		*/
#define	VRES1		11,36
#define	VOWNER		37,49
#define	VDSV		50,50	/* DIGITAL standard version	*/
#define	VRES2		51,78
#define	VLSV		79,79	/* Label standard version	*/

/*
 * fields of Header 1
 */
#define	__AAA		0,2	/* alphabetic characters "HDR"	*/
#define __BBB		3,3	/* numeric character "1"	*/
#define H1_FNAME        4, 20	/* filename			*/
#define __CCC		21,26	/* file set identifier		*/
#define __DDD		27,30	/* file section number		*/
#define	__EEE		31,34	/* file sequence number		*/
#define __FFF		35,38	/* generation number		*/
#define	__GGG		39,40	/* generation version		*/
#define	H1_CDATE	41,46	/* creation date		*/
#define	H1_XDATE	47,52	/* expiration date		*/
#define	H1_ACCESS	53,53	/* access security		*/
#define	H1_BCOUNT	54,59	/* block count (always 0)	*/
#define	H1_SYSTEM	60,72	/* system that produced it	*/
#define __HHH		73,79	/* reserved			*/

/*
 * fields of Header 2
 */
#define	__III		0,2	/* alphabetic characters "HDR"	*/
#define	__JJJ		3,3	/* numeric character "2"	*/
#define	H2_FMT		4,4	/* record format		*/
#define	H2_BSZ		5,9	/* # characters per block	*/
#define	H2_RECLEN	10,14	/* record length for fixed length records */
#define	__KKK		15,35	/* system dependent info	*/
#define	H2_FORMC	36,36	/* form control
				 *	'A' : first byte of record contains
				 *		fortran control characters
				 *	'M' : record contains all form control
				 *		info
				 *	' ' : lf/cr to be inserted between
				 *		records.
				 */
#define	__LLL		37,49	/* system dependent info	*/
#define	__MMM		50,51	/* buffer offset = "00"		*/
#define	__NNN		52,79	/* reserved			*/

/*
 * fields of Header 3
 */
#define	__OOO		0,2	/* alphabetic characters "HDR"	*/
#define	__PPP		3,3	/* numeric character "3"	*/
#define	__QQQ		4,67	/* FILES-11 attributes if made on VMS system */
#define	__RRR		68,79	/* system dependent info. spaces for VMS */

/*
 * DEFINITIONS FOR WRITING TAPES
 */

#define VOL_LABEL       "UNIX"		/* default volume label */
#define CREATION        " 70001"        /* Julian date: yyddd */
#define EXPIRATION      " 99365"        /* Julian date: yyddd */
#define FILLCHAR        '^'

int     magtape;

char    databuf[MAXBLOCKSZ+1];
char	vol_label[80];


 

/*
 * These flags represent which operation is to be performed
 */

int     createflag;             /* Create a new tape            */
int     listflag;               /* List the tape contents       */
int     extractflag;            /* Extract the files from tape  */

/*
 * These flags represent modifiers to the basic operations
 */

int     rawflag;
int     binflag;
int	verbose;
int 	fixed_length_flag;
int 	section_flag;

char    *field();

char pad_record[MAXFIXRECLEN];
//E*O*F vmstape.h//

echo x - field.c
cat > "field.c" << '//E*O*F field.c//'
#define	SIZE	80

char    *
field(p, begin, end)
	char    *p;
	int     begin;
	int     end;
{
	register        int     i;
	register        int     j;
	static          char    buffer[SIZE];

	j = 0;
	for(i = begin ; i <= end ; i++)
		buffer[j++] = p[i];
	buffer[j] = '\0';

	if( j >= SIZE )
		printf("field: buffer overflow.  j = %d\n", j);

	return(buffer);
}
//E*O*F field.c//

echo x - header3.c
cat > "header3.c" << '//E*O*F header3.c//'
#include "vmstape.h"

/* these functions really simulate r_record, but with these names
 * make reading the code easier
 */

/* is there a Header 3 area on the tape?
 */

header3()
{
	char	buf[RECSIZE];

	if( read(magtape, buf, RECSIZE) != 0 )
		return(1);
	return(0);
}

trailer3()
{
	char	buf[RECSIZE];

	if( read(magtape, buf, RECSIZE) != 0 )
		return(1);
	return(0);
}
//E*O*F header3.c//

echo x - skiptm.c
cat > "skiptm.c" << '//E*O*F skiptm.c//'
#include <sys/types.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>
#include "vmstape.h"

skiptm(n)
	int	n;
{
	struct	mtop	m;

	m.mt_count	= n;
	m.mt_op		= MTFSF;

	ioctl(magtape, MTIOCTOP, &m);
}
w_tapemark()
{
	struct	mtop	m;

	m.mt_count	= 1;
	m.mt_op		= MTWEOF;

	ioctl(magtape, MTIOCTOP, &m);
}
//E*O*F skiptm.c//

echo x - vmstape.c
cat > "vmstape.c" << '//E*O*F vmstape.c//'
#include	<stdio.h>
#include        "vmstape.h"

/* NOTE -- FILES MUST BE TEXTUAL (NOT CONTAINING A NULL BYTE) */

/* arguments to open system call
 */
#define	READ	0
#define	WRITE	1
#define RDWT	2

#define	CREATE	1
#define	LIST	2
#define	EXTRACT 3
#define APPEND  4

int open_file();

main(argc, argv)
	int     argc;
	char    **argv;
{
	register        char    *p;
			int     function;
			int     func_count;
			int     mode;
			char	device[100];

	if(argc < 2) {
		fprintf(stderr, "Usage (H = HELP): vmstape [crtxvfdbFRVH] [ filename ]\n");
		exit(FAILURE);
		}

	strcpy(device, MAGTAPE);
	strcpy(vol_label, VOL_LABEL);
	blocksz = BLOCKSZ ; /* default */
	fixreclen = FIXRECLEN; /* default */
	func_count = 0;
	for(p = argv[1]; *p != '\0' ; p++)
		switch(*p) {
			case 'H':
				fprintf(stdout,"Help option specified. All others ignored.\n");
				fprintf(stdout,"OPTIONS: c = create tape\n");
				fprintf(stdout,"         r = append to tape\n");
				fprintf(stdout,"         t = list tape\n");
				fprintf(stdout,"         x = extract from tape specified file name\n");
				fprintf(stdout,"         v = verbose messages\n");
				fprintf(stdout,"         f = specify new device\n");
				fprintf(stdout,"         d = if appending, make new section\n");
				fprintf(stdout,"         b = specify new blocksize.(default:2048b/block.)\n");
				fprintf(stdout,"         F = write fixed length records (non text files).\n");
				fprintf(stdout,"         R = specify new fixed record length.(default:128b/rec.)\n");
				fprintf(stdout,"         V = specify new volume label\n");
				fprintf(stdout,"         H = see this help screen\n");
				exit(FAILURE);
			case 'R':
		    		fixreclen = atoi(argv[2]);
				if (fixreclen > MAXFIXRECLEN) 
				{
				  fprintf(stderr,"ERROR:Fixed record length cannot be greater than %d\n",MAXFIXRECLEN);
				  exit(FAILURE);
				}
			        argv++;argc--;
				/* Fall through to writing fixed length recs */;
		    	case 'F':
				fixed_length_flag = 1;
				break;
			case 'b':
		    		blocksz = atoi(argv[2]);
				if (blocksz > MAXBLOCKSZ) 
				{
				  fprintf(stderr,"ERROR:Blocksz cannot be greater than %d\n",MAXBLOCKSZ);
				  exit(FAILURE);
				}
			        argv++;argc--;
				break;
			case 'c':
				function = CREATE;
				func_count++;
				mode = WRITE;
				break;

			case 't':
				function = LIST;
				func_count++;
				mode = READ;
				break;

			case 'x':
				function = EXTRACT;
				func_count++;
				mode = READ;
				break;

			case 'r':
				function = APPEND;
				func_count++;
				mode = RDWT;
				break;

		    	case 'd':
			/* appending , but starting the file offset count
			all over again so that a VMS dir command will split
			the tape directory into sections. */
				section_flag = 1;
				break;
			case 'f':
				strcpy(device, argv[2]);
				argv++ ; argc-- ; 
				break;

#ifdef OLD
/* could use these for writing tapes.
 */
			case 'B':
				binflag = TRUE;
				break;

			case 'R':
				rawflag = TRUE;
				break;
#endif

			case 'V':
				strcpy(vol_label, argv[2]);
				argv++ ; argc--;
				break;

			case 'v':
				verbose = TRUE;
				break;

			
			default:
				printf("Unknown key: %c\n", *p);
				exit(FAILURE);
		}

	if (blocksz % fixreclen ) {
		fprintf(stderr,"ERROR:The blocksize must be a multiple of the fixed record length.\n");
		exit (FAILURE);
		}
	if(func_count == 0) {
		fprintf(stderr,"No function specified\n");
		exit(FAILURE);
		}
	if(func_count > 1) {
		fprintf(stderr,"Too many functions specified\n");
		exit(FAILURE);
		}
#ifdef OLD
	magtape = open(device, mode);
	if(magtape < 0) {
		fprintf(stderr, "Cannot open %s\n", device);
		exit(FAILURE);
		}
#endif
	switch(function) {
		case CREATE:
			vt_create(&argv[2], device, mode);
			break;

		case LIST:
			vt_list(device, mode);
			break;

		case EXTRACT:
			vt_extract(&argv[2], device, mode);
			break;

		case APPEND:
			vt_append(&argv[2], device, mode);
			 break;

		default:
			printf("This can never happen!\n");
			break;
		}

	close(magtape);
	exit(SUCCESS);
}

make_pad_record()
{
int i;
for (i=0;i<MAXFIXRECLEN;i++) pad_record[i] = '^';
}

int open_file (device, mode)

char device[];
int mode;

{
	int descrip;

	descrip = open(device, mode);
	if(descrip < 0) {
		fprintf(stderr, "Cannot open %s\n", device);
		exit(FAILURE);
	}
	return (descrip);
}
//E*O*F vmstape.c//

echo x - vt_append.c
cat > "vt_append.c" << '//E*O*F vt_append.c//'
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>
#include "vmstape.h"

int open_file();

vt_append(argv, device, mode)

char **argv, *device;
int mode;

{
	char buf[RECSIZE];
	int  i,offset;
	int  nblocks;

	if ( !(*argv) ) {
		printf("r: no arguments??\n");
		return;
	}

	/* open tape file */

	magtape = open_file (device, mode);

	/* read the volume label */
	r_record(buf);

 	offset = 1;
	while (r_record(buf)) 
	 {
		++offset;	/* counting files */
		skiptm(3);
	 }

	if (section_flag) offset = 1; /* if we want to make sections ... */
	/* back up one tape mark */
	backtm(); 

	for(i = 0 ; argv[i] ; i++) {
		if(verbose)
			printf("r %s\n", argv[i]);
		if( subdir(argv[i]) ) 
			continue;
		if( size0(argv[i]) )
			continue;
		if ( isdir(argv[i]) ) {
		    printf("'%s' is a directory...writing all files within\n", argv[i]);
		    w_dir(argv[i],  &offset);
		}
		else {
			w_file(argv[i], offset);
			++offset;
		}
	   }
	w_tapemark();
}

backtm()
{
	int i;
	struct mtop 	m;

	m.mt_count	= 1;
	m.mt_op		= MTBSF;

	i = ioctl(magtape, MTIOCTOP, &m);
}
//E*O*F vt_append.c//

echo x - vt_extract.c
cat > "vt_extract.c" << '//E*O*F vt_extract.c//'
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "vmstape.h"

static	int	nomore = 0;
int open_file();

vt_extract(files, device, mode)

char	**files, *device;
int mode;

{
	register        int     blocksize;
			int     nblocks;
			int	i;
			char    buf[RECSIZE];
			char    filename[RECSIZE];
			char	recformat;
			char	formc;
	/* open tape file */

	magtape = open_file (device, mode);

	/* get volume label */
	r_record(buf);

	while( r_record(buf) ){
		rawflag	= 0;
		binflag	= 0;

		strcpy(filename, field(buf, H1_FNAME));
		strip(filename);
		r_record(buf);
		blocksize = atoi(field(buf, H2_BSZ));

		/* set flags for extraction of the file.
		 *
		 * variable length crlf form control --> no flags set
		 *	this is your normal text file on VMS
		 *
		 *	|length|record|  --> |record|'\n'
		 *
		 * fixed length --> binflag set
		 *	file records extracted as is.  no interpretation
		 *	done on blocks of file.  (ie, file is full of records
		 *	and no information about record length).  This type
		 *	of file is an array of records which have no control
		 *	(record length) info).
		 *
		 *	|record|  --> |record|
		 *
		 * variable length no form control --> raw flag set
		 *
		 *	|length|record|  --> |length|record|
		 */
		recformat = *field(buf, H2_FMT);
		fixreclen = atoi(field(buf, H2_RECLEN));
		if( recformat == 'F' )	/* is fixed length */
			binflag	= 1;
		else {
			if( recformat != 'D' ){
				printf("UNKNOWN RECORD FORMAT <%c>\n",
					recformat);
				exit(FAILURE);
				}
			/* if recformat == 'D', then
			 * is variable length.  set no flags */
			}

		formc	= *field(buf, H2_FORMC);
		if( formc == 'M')
			rawflag	= 1;	/* stuff record onto disk with
					 * its control area defining its
					 * length.
					 */
		else {
			if( formc != ' '){
				printf("UNKNOWN FORM CONTROL <%c>\n",
					formc);
				exit(FAILURE);
				}
			/* is the default whith crlf
			 * stuff done at the end of each record.
			 */
			}

		if( header3() )
			r_tapemark();

		if( isarg(filename, files) )
			r_data(blocksize, filename, files);
		else{
			/* skip tape mark at end of data area and
			 * after Trailers.
			 */
			skiptm(2);
			continue;
			}

		if( nomore )	/* got all files wanted */
			break;

		/* grap Trailer areas and tape mark separating files
		 */
		r_record(buf);
		r_record(buf);
		if( trailer3() )
			r_tapemark();
	}

	for( i=0; files[i]; i++)
		if (files[i] != (char *)(-1))
		    printf("'%s' not found on tape\n", files[i]);

}

r_data(blksize,  filename, argv)
register        int     blksize;
		char    *filename;
		char    **argv;
{
	register        int     i;
			FILE    *outfile;
			char	*pathname;

	if(blksize > MAXBLOCKSZ) {
		fprintf("BUFFER SIZE EXCEEDED\n");
		exit(FAILURE);
		}

	if(exists(filename)) {
		fprintf(stderr, "x: %s already exists.  Not extracted.\n",
			filename);
		while( read(magtape, databuf, blksize) > 0)
			continue;
		return;
	}

	outfile = fopen(filename, "w");
	if(outfile == NULL) {
		fprintf(stderr, "x: cannot create %s\n", filename);
		while( read(magtape, databuf, blksize) > 0)
			continue;
		return;
	}

	if (verbose) printf("Extracting %s\n", filename);

	while( (i = read(magtape, databuf, blksize)) > 0 )
		ext(databuf, i, outfile);

	fclose(outfile);
}

exists(filename)
	char    *filename;
{
	struct	stat	statbuf;

	if( stat(filename, &statbuf) < 0 )
		return(FALSE);

	return(TRUE);
}

ext(p, blksize, outfile)
register        char    *p;
		int     blksize;
register        FILE    *outfile;
{
	register        int     i;
	register        int     seen;
			int     thisline;
			char    nbuf[5];
			char	outbuf[MAXFIXRECLEN];
			int	filler = 1;	

/* if binflag, then global fixreclen is set to record size */
	if(binflag) {
	   while (blksize>0) {
		for(i = 0 ; i < fixreclen ; i++) {
			outbuf[i] = *p;
			if (*p++ != FILLCHAR) filler = 0;
			}
		if (filler) return;  
		filler = 1;
		for(i = 0;i<fixreclen;i++) {	
			putc(outbuf[i], outfile);
			}
		blksize -= fixreclen;
		}
	   }
	else
		for(seen = 0 ; seen < blksize ; ) {
			for(i = 0 ; i < 4 ; i++)
				nbuf[i] = *p++;
			nbuf[4] = '\0';
			seen += 4;
			if(!isdigit(nbuf[0]))
				break;
			thisline = atoi(nbuf) - 4;

			if(rawflag)
				putw(thisline, outfile);

			for(i = 0 ; i < thisline ; i++)
				fputc(*p++, outfile);

			seen += thisline;

			if(!rawflag)
				putc('\n', outfile);
			}
}

r_record(p)
	char    *p;
{
	int     n_read;

	n_read = read(magtape, p, RECSIZE);
	return(n_read);
}

isarg(str, argv)
		char    *str;
		char    **argv;
{
	int     i;
	int	count;	/* of number left */
	int	retval;

	/* argv vector is null terminated.
	 * if no files listed, then extract all
	 */
	if( !(*argv) )
		return(1);

	count	= 0;
	retval	= 0;

	for(i = 0 ; argv[i] ; i++){
		if( argv[i] == ((char *)(-1)) )
			continue;
		if( streq(str, argv[i]) ) {
			argv[i] = ((char *)(-1));
			retval	= 1;
			}
		count++;
		}

	if( (!count) && (!retval) )
		nomore	= 1;	/* all done */
	return(retval);
}

strip(p)
register        char    *p;
{
	if(*p == '\0')
		return;
	while(*p != '\0')
		p++;
	p--;
	while(*p == ' ')
		p--;
	*++p = '\0';
}

r_tapemark()
{
	if(read(magtape, databuf, MAXBLOCKSZ) != 0) {
		fprintf(stderr, "MISSING TAPE MARK??\n");
		exit(FAILURE);
		}
}
//E*O*F vt_extract.c//

echo x - vt_list.c
cat > "vt_list.c" << '//E*O*F vt_list.c//'
#include "vmstape.h"

int open_file();

vt_list(device, mode)

char *device;
int mode;

{
	register        int     blocksize;
	register        int     recformat;
	register        int     formcntrl;
			int     nblocks;
			char    buf[RECSIZE];
			char    filename[RECSIZE];
			char	format[40];
			char	formc[40];
			int	n_read;
			int	reclen;
	/* open tape file */

	magtape = open_file (device, mode);

	/* get volume label */

	r_record(buf);
	printf("\nVolume Label: %s\n\n", field(buf, VLABEL));
	if( verbose )
		printf("%-20s%-15s %5s %6s %7s %7s\n",
			"filename", "record format", "bsize",
			"reclen", "formc", "nblocks"
			);
	else
		printf("%-20s\n",
			"filename"
			);

	while( r_record(buf) ){

		strcpy(filename, field(buf, H1_FNAME));
		strip(filename);

		r_record(buf);
		blocksize = atoi(field(buf, H2_BSZ));
		if( blocksize > MAXBLOCKSZ ){
			printf("blocksize %d too large for program !\n",
				blocksize);
			exit(-1);
			}

		recformat = *field(buf, H2_FMT);
		if( recformat == 'D' )
			strcpy(format,"var length");
		else if( recformat == 'F' )
			strcpy(format,"fixed length");
		else
			strcpy(format,"unknown");

		reclen	= atoi(field(buf, H2_RECLEN));
		if( recformat == 'D' )
			reclen -= 4;	/* 4 = size of control area
					 * of for variable length records
					 *
					 * the control area defines the length
					 * of the record, including the control
					 * area.
					 */

		formcntrl = *field(buf, H2_FORMC);
		if( formcntrl == 'A' )
			strcpy(formc, "fortran");
		else if( formcntrl == 'M' )
			strcpy(formc, "none");
		else if( formcntrl == ' ' )
			strcpy(formc, "crlf"); /* to be put between records */
		else
			strcpy(formc,"unknown");


		if( header3() )
			r_tapemark();

		if( verbose )
			printf("%-20s%-15s %-5d %-6d %7s ",
				filename, format, blocksize, reclen, formc);
		else
			printf("%-20s\n",
				filename);
		nblocks = 0;
		while( read(magtape, databuf, blocksize) > 0 )
			nblocks++;

/*
		if (recformat == 'F') nblocks = (nblocks % (blocksize/reclen))
						? nblocks/(blocksize/reclen)+1
						: nblocks/(blocksize/reclen);
*/
				/* Because when we were reading in 
				blocksize chunks of data, we
				were only getting reclen size peices. */

		if( verbose )
			printf("%-7d\n", nblocks);

		/* the condition that causes the exit from the while loop
		 * reads in the tape mark after the data area
		 */
		r_record(buf);
		r_record(buf);
		if( trailer3() )
			r_tapemark();
	}
}
//E*O*F vt_list.c//

echo x - vt_write.c
cat > "vt_write.c" << '//E*O*F vt_write.c//'
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

 /* for either the old directory struct or new, but not emulation*/

#include <sys/dir.h>

#include "vmstape.h"

struct dirlis {
	char *names;	/* newline sep'd list of files */
	int n;		/* number of files there */
};

struct carray {
	char *block;	/* block w/text stored in it */
	char **array;	/* block of pointers into above */
	int num;	/* number of 'valid' pointers */
};

int open_file();

/* makerec assumes normal textual files
 *	note the check for zero byte means such is not valid to have
 *	in textual files.  All material following the zero byte is
 *	lost.
 *
 *	fgets has filled in the variable "in" with an asciz string
 *	and so the variable "in" is null-terminated.
 */

makerec(in, out, max)
	char    *in;
	char    *out;
{
	register        char    *p;
	register	int	count;

	count	= 0;
	for(p = in ; *p != '\n' ; p++){
		count++;
		if( count + 4> max ){  /* we need the 4 to have control info*/
			printf("GASP!  More than %d characters in the file not seperated by a newline.\nGASP!  File Incomplete.\n",max-4);
			return 0; /* error condition */
			}
		if( !(*p) )
			break;
		}

	*p = '\0';
	sprintf(out, "%04d%s", strlen(in) + 4, in);
}

bflush(seen)
register        int     seen;
{
	if(seen == 0)
		return;
	for( ; seen < blocksz ; seen++)
		databuf[seen] = FILLCHAR;
	if(write(magtape, databuf, blocksz) != blocksz) {
		fprintf(stderr, "FATAL WRITE ERROR\n");
		exit(FAILURE);
		}
	databuf[0] = '\0';              /* for strcat() */
}

flush_blk(buf,charcount)
char *buf;
long charcount;
{
	int i;
	if (charcount)
		for (i = charcount;i<blocksz;i++) buf[i] = FILLCHAR;
	if (write(magtape,buf,blocksz) != blocksz)
	 {
		fprintf(stderr,"FATAL WRITE ERROR\n");
		exit (FAILURE);
	 }
}

vt_create(argv, device, mode)

char    **argv, *device;
int mode;

{
	int     i;
	int	offset;	/* offset for filenumbers if any arguments */
			/* are directory names instead of just files */

	if( !(*argv) ){
		printf("c: no args?\n");
		return;
		};

	/* open tape file */

	magtape = open_file (device, mode);

	w_label();	 /* volume label */

	offset = 1; /* first file sequence number */

	for(i = 0 ; argv[i] ; i++) {
		if(verbose)
			printf("c %s\n", argv[i]);
		if( subdir(argv[i]) ) 
			continue;
		if( size0(argv[i]) )
			continue;
		if ( isdir(argv[i]) ) {
		    printf("'%s' is a directory...writing all files within\n", argv[i]);
		    w_dir(argv[i],  &offset);
		}
		else {
			w_file(argv[i], offset);
			++offset;
		 }
	   }
	w_tapemark();
}

w_dir(dirname,  offset)
char *dirname;
int *offset;
{
	struct carray files, gath();
	char *p, *malloc();
	int i;

	p = malloc(256);

	files = gath(dirname);
	
	for(i = 0; i < files.num; i++) {
	    if(*files.array[i] == '.')
		continue;

	    if(verbose)
		printf("%s: %s\n", dirname, files.array[i]);

	    strcpy(p, dirname);
	    strcat(p, "/");
	    strcat(p, files.array[i]);

	    if ( isdir(p) ) {
		printf("%s is a subdirectory. Not added to tape\n", p);
		continue;
	    }
	    if( size0(p) )
		continue;
	    w_file(p, *offset );
	    ++*offset;	/* inc file sequence number */
	}
}

int w_data(filename)
char    *filename;
{
	FILE    *ioptr;
	char    c,buf[MAXBLOCKSZ+1];
	struct stat filestat;
	long totalchar=0,charcount=0;
	long nrecs;
	int nblocks = 0;
	int length,outcount;
	char rec[MAXBLOCKSZ+1];

	ioptr = fopen(filename, "r");
	if(ioptr == NULL) {
		fprintf(stderr, "Cannot open %s\n", filename);
		}
	else {

	if (fixed_length_flag) {
		stat(filename,&filestat);
		nrecs = (filestat.st_size%fixreclen ) 
					? filestat.st_size/fixreclen +1
					: filestat.st_size/fixreclen ;
		totalchar = fixreclen * nrecs; /*this will be an even rec size*/
		while (charcount < totalchar)
		 {
			c = getc(ioptr);
			buf[charcount++ % blocksz] = c;
			if ((charcount % blocksz) == 0 ) flush_blk(buf,0);
		 }
		if ((charcount % blocksz) != 0 ) 
				flush_blk(buf,charcount % blocksz);
		fclose(ioptr);
	nblocks = nrecs/(blocksz/fixreclen);
	if (nrecs % (blocksz/fixreclen)) nblocks++;
	}
	else {  /* variable length records */

		outcount = 0;
		for(;;){
			length = (int)fgets(buf, blocksz, ioptr);
			/* scratch variable for the moment */

			if(length == NULL)
				break;

			if (makerec(buf, rec, blocksz) == 0) 
			 {
				nblocks = 0;
				break; /* error */
			 }
			length = strlen(rec);
			if(outcount + length > blocksz) {
				bflush(outcount);
				nblocks++;
				outcount = 0;
				}
			strcat(databuf, rec);
			outcount += length;
			}

		if( outcount ){
			bflush(outcount);
			nblocks++;
			}

		fclose(ioptr);
		}
}
	return(nblocks);
}


w_file(path, number)
char *path;
int number;
{
	int nblocks;
	char *file, *malloc();

	file = malloc(256);
	strcpy(file, path);

	while(*file != '\0' && *file !='/') file++;
	if (*file == '\0') file = path;
	else file++;
	w_hdr1(file, number);
	w_hdr2();
	w_tapemark();
	nblocks = w_data(path);
	w_tapemark();
	w_eof1(file, number, nblocks);
	w_eof2();
	w_tapemark();
	if( nblocks == 0 ){
	    fprintf(stderr,"%s caused no data area to be written on tape??\n",
		file);
	    w_tapemark(); /* make greaceful crash (all other files are okay )*/
	    exit(FAILURE);
	}
}

w_eof1(filename, filenum, blkcount)
char    *filename;
int     filenum;
int	blkcount;
{
	char    buf[81];

	sprintf(buf,
		"EOF1%-17s%-6s0001%04d000101%-6s%-6s %06dDECFILE11A          ",
		filename, vol_label, filenum, CREATION, EXPIRATION, blkcount);

	if(write(magtape, buf, 80) != 80) {
		fprintf(stderr, "WRITE -- FATAL ERROR\n");
		exit(FAILURE);
		}
}

w_eof2()
{
	char buf[82];
	sprintf(buf,
		 "EOF2%c%05d%05d                     %c             00                             ",(fixed_length_flag)?'F':'D',
			blocksz,(fixed_length_flag)?fixreclen:blocksz-4,
			(fixed_length_flag) ? 'M' : ' ');
	if(write(magtape, buf, 80) != 80) {
		fprintf(stderr, "WRITE -- FATAL ERROR\n");
		exit(FAILURE);
		}
}

w_hdr1(filename, filenum)
	char    *filename;
	int     filenum;
{
	char    buf[81];
	
	sprintf(buf,
	    "HDR1%-17s%-6s0001%04d000101%-6s%-6s 000000DECFILE11A          ",
	    filename, vol_label, filenum, CREATION, EXPIRATION);

	if(write(magtape, buf, 80) != 80) {
		fprintf(stderr, "WRITE -- FATAL ERROR\n");
		exit(FAILURE);
		}
}

w_hdr2()
{
	char	hdr2_label[RECSIZE];
	int	i;

	sprintf( hdr2_label, "HDR2%c%05d%05d",(fixed_length_flag)?'F':'D',blocksz,(fixed_length_flag) ? fixreclen: MAXFIXRECLEN);
	i	= strlen(hdr2_label);
	for( ; i<RECSIZE ; i++){

		hdr2_label[i]	= ' '; /* By default */

		/* Form control info */
		if (i == 36 && (fixed_length_flag)) hdr2_label[i] = 'M';

		/* buffer offset = "00" */
		if( i == 50 || i == 51 ) hdr2_label[i]	= '0';

		}

	if(write(magtape, hdr2_label, RECSIZE) != RECSIZE) {
		fprintf(stderr, "WRITE -- FATAL ERROR\n");
		exit(FAILURE);
		}
}

w_label()
{
	char    buf[RECSIZE];
	int	i;

	sprintf(buf,"VOL1%-6s", vol_label);

	i	= strlen(buf);
	for( ; i<RECSIZE ; i++){

		/* DIGITAL standard version */
		if( i == 50 ){
			buf[i]	= '1';
			continue;
			}

		/* label standard version */
		if( i == 79 ){
			buf[i]	= '3';
			continue;
			}

		buf[i]	= ' ';
		}

	if(write(magtape, buf, RECSIZE) != RECSIZE) {
		fprintf(stderr, "WRITE -- FATAL ERROR\n");
		exit(FAILURE);
		}
}
size0(filename)
	char	*filename;
{
	struct	stat	statbuf;

	if( stat(filename, &statbuf) < 0 ){
		printf("'%s' does not exist...not added to tape\n", filename);
		return(1);
		}
	if( statbuf.st_size == 0 ){
		printf("'%s' is of 0 size...skipped\n", filename);
		return(1);
		}

	return(0);
}

subdir(filename)
char *filename;
{
	register char *p;

	for(p = filename; *p != '\0'; p++) 
	    if (*p == '/') {
		printf("File '%s' from a subdirectory...not added to tape\n", filename);
		return(1);
	    }
	return(0);
}
	
isdir(file) 
char *file;
{
	struct stat sb;
	register int t;

	stat(file, &sb);
	t = sb.st_mode & S_IFMT;

	return(t == S_IFDIR);
}

/*	DIRSIZ, to return the size of the (directory) passed it.
 *	used as an estimate for the amount of space to keep for 
 *	matches.  ERROR if longer than 16 bit's worth.
 *      From Dave Brownell's help program
 */

int dirsiz(dir) char *dir;
{
	struct stat entry;

	if (stat(dir,&entry) == EOF) {
		printf("Can't stat() %s\n", dir);
		exit(1);
	}
	if (entry.st_size > 65000L) {	/* lazy */
		printf("Directory exceeds 64K bytes in length\n");
		exit(1);
	}
	else return((int) entry.st_size);
}

/*	NAMGET, to get the names in a directory and put them in
 *	a string, separated by newlines.  will also count the number
 *	of files. From Dave Brownell's help program.
 *	Modified to reflect 4.2 file changes as of 6/13/84. -- S.K.
 */

struct dirlis namget(dir) char *dir;
{	
#ifdef OLD_DIR_STRUCT
	struct direct entry;
#else
	struct direct *entry;
	DIR *dp;
#endif
	struct dirlis ret;
	register int i;
	register char *t;
	register FILE *file;
	extern char *malloc();

	ret.n = 0;

	t = ret.names = malloc(dirsiz(dir));
#ifdef OLD_DIR_STRUCT
	if ((file = fopen(dir,"r")) == NULL) return (ret);
	while (read(file,&entry,sizeof(struct direct))) {
	    if (entry.d_ino != 0) {
		for (i = 0 ; entry.d_name[i] != 0 && i < MAXNAMLEN ; i++)
			*t++ = entry.d_name[i]; /* copy the name */
		*t++ = '\n';
		ret.n++;
		}
	 }
	*t++ = '\0';
	fclose(file);
	return(ret);
}
#else
	if ((dp = opendir(dir)) == NULL) return (ret);
	while( entry = readdir(dp)) {
	    if (entry->d_ino != 0) {
		for (i = 0 ; entry->d_name[i] != 0 && i < MAXNAMLEN ; i++)
			*t++ = entry->d_name[i]; /* copy the name */
		*t++ = '\n';
		ret.n++;
	    }
	    
	}

	*t++ = '\0';
	closedir(dp);
	return(ret);
}
#endif

/*	GATH, to return an array of strings representing the names
 *	of the files in the directory.  This is not sorted.
 *	Note that a 'struct carray' has two pointers to blocks which
 *	must be free()ed.
 *	The newlines in the block returned by namget() are changed
 *	to nulls. From Dave Brownell's help program.
 */

struct carray gath(dir) char *dir;
{
	struct carray ret;
	struct dirlis names;
	char *calloc();

	names = namget(dir);
	ret.block = names.names;
	ret.array = (char **) calloc(names.n + 1, sizeof(char *));
	for (ret.num = 0; ret.num < names.n; ret.num++) {
		(ret.array)[ret.num] = names.names;
		while (*(names.names) != '\n') (names.names)++;
		*(names.names)++ = '\0';
	}
	ret.array[ret.num] = NULL;
	return(ret);
}
//E*O*F vt_write.c//

echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
       9      56     351 READ_ME
      91     437    2391 vmstape.1
      63     204    1626 Makefile
     156     686    4467 vmstape.h
      22      56     359 field.c
      26      58     373 header3.c
      24      40     300 skiptm.c
     204     514    4290 vmstape.c
      71     155    1118 vt_append.c
     279     744    5247 vt_extract.c
     114     303    2440 vt_list.c
     504    1433   10252 vt_write.c
    1563    4686   33214 total
!!!
wc  READ_ME vmstape.1 Makefile vmstape.h field.c header3.c skiptm.c vmstape.c vt_append.c vt_extract.c vt_list.c vt_write.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0
-- 
Nicholas Horton	    	    System Manager
Cambridge, MA               Aiken Computation Lab
UUCP:  decvax!genrad!wjh12!horton   
       {seismo,ihnp4,allegra,ut-sally}!harvard!horton
ARPA:  horton@harvard 	BITNET:  HORTON@HARVUNXH