/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: vmsbackup.c,v 1.4.2.1 92/06/24 13:44:51 marker Exp $"

/*
 *
 *  Title:
 *	Backup
 *
 *  Decription:
 *	Program to read VMS backup tape
 *
 *  Author:
 *	John Douglas CAREY.
 *	Sven-Ove Westberg    (version 3.0)
 *
 *  Net-addess:
 *	john%monu1.oz@seismo.ARPA
 *	luthcad!sow@enea.UUCP
 *
 *  History:
 *	Version 1.0 - September 1984
 *		Can only read variable length records
 *	Version 1.1
 *		Cleaned up the program from the original hack
 *		Can now read stream files
 *	Version 1.2
 *		Now convert filename from VMS to UNIX
 *			and creates sub-directories
 *	Version 1.3
 *		Works on the Pyramid if SWAP is defined
 *	Version 1.4
 *		Reads files spanning multiple tape blocks
 *	Version 1.5
 *		Always reset reclen = 0 on file open
 *		Now output fixed length records
 *
 *      Version 2.0 - July 1985
 *		VMS Version 4.0 causes a rethink !!
 *		Now use mtio operations instead of opening and closing file
 *		Blocksize now grabed from the label
 *
 *	Version 2.1 - September 1985
 *		Handle variable length records of zero length.
 *
 *	Version 2.2 - July 1986
 *		Handle FORTRAN records of zero length.
 *		Inserted exit(0) at end of program.
 *		Distributed program in aus.sources
 *
 *	Version 2.3 - August 1986
 *		Handle FORTRAN records with record length fields
 *		at the end of a block
 *		Put debug output to a file.
 *		Distributed program in net.sources
 *
 *	Version 3.0 - December 1986
 *		Handle multiple saveset 
 *		Remote tape
 *		Interactive mode
 *		File name selection with meta-characters
 *		Convert ; to : in VMS filenames
 *		Flag for usage of VMS directory structure
 *		Flag for "useless" files  eg. *.exe
 *		Flag for use VMS version in file names
 *		Flag for verbose mode
 *		Flag to list the contents of the tape
 *		Distributed to mod.sources
 *
 *
 *  Installation:
 *
 *	Computer Centre
 *	Monash University
 *	Wellington Road
 *	Clayton
 *	Victoria	3168
 *	AUSTRALIA
 *
 */
#include	<stdio.h>
#include	<ctype.h>
#include 	<fcntl.h>

#include	<sys/ioctl.h>
#include	<sys/types.h>
#ifdef REMOTE
#include	<local/rmt.h>
#include	<sys/stat.h>
#endif
#include	<sys/mtio.h>
#include	<sys/file.h>

#define bcopy(src,dst,n)	memcpy((dst), (src), (n))

#ifdef pyr
#define SWAP
#endif pyr

#ifdef sun
#define SWAP
#endif

#ifdef mips
#define SWAP
#endif

struct bbh {
	short	bbh_dol_w_size;
	short	bbh_dol_w_opsys;
	short	bbh_dol_w_subsys;
	short	bbh_dol_w_applic;
	long	bbh_dol_l_number;
	char	bbh_dol_t_spare_1[20];
	short	bbh_dol_w_struclev;
	short	bbh_dol_w_volnum;
	long	bbh_dol_l_crc;
	long	bbh_dol_l_blocksize;
	long	bbh_dol_l_flags;
	char	bbh_dol_t_ssname[32];
	short	bbh_dol_w_fid[3];
	short	bbh_dol_w_did[3];
	char	bbh_dol_t_filename[128];
	char	bbh_dol_b_rtype;
	char	bbh_dol_b_rattrib;
	short	bbh_dol_w_rsize;
	char	bbh_dol_b_bktsize;
	char	bbh_dol_b_vfcsize;
	short	bbh_dol_w_maxrec;
	long	bbh_dol_l_filesize;
	char	bbh_dol_t_spare_2[22];
	short	bbh_dol_w_checksum;
} *block_header;

struct brh {
	short	brh_dol_w_rsize;
	short	brh_dol_w_rtype;
	long	brh_dol_l_flags;
	long	brh_dol_l_address;
	long	brh_dol_l_spare;
} *record_header;

/* define record types */

#define	brh_dol_k_null	0
#define	brh_dol_k_summary	1
#define	brh_dol_k_volume	2
#define	brh_dol_k_file	3
#define	brh_dol_k_vbn	4
#define brh_dol_k_physvol	5
#define brh_dol_k_lbn	6
#define	brh_dol_k_fid	7

struct bsa {
	short	bsa_dol_w_size;
	short	bsa_dol_w_type;
	char	bsa_dol_t_text[1];
} *data_item;

#ifdef	STREAM
char	*def_tapefile = "/dev/rts8";
#else
char	*def_tapefile = "/dev/rmt8";
#endif
char	*tapefile;

char	filename[128];
int	filesize;

char	*progname;

char	recfmt;		/* record format */

#define			FAB_dol_C_UDF	0	/* undefined */
#define			FAB_dol_C_FIX	1	/* fixed-length record */
#define			FAB_dol_C_VAR	2	/* variable-length record */
#define			FAB_dol_C_VFC	3	/* variable-length with fixed-length control record */
#define 		FAB_dol_C_STM	4	/* RMS-11 stream record (valid only for sequential org) */
#define			FAB_dol_C_STMLF	5	/* stream record delimited by LF (sequential org only) */
#define 		FAB_dol_C_STMCR	6	/* stream record delimited by CR (sequential org only) */
#define			FAB_dol_C_MAXRFM	6	/* maximum rfm supported */

char	recatt;		/* record attributes */

#define			FAB_dol_V_FTN	0	/* FORTRAN carriage control character */
#define			FAB_dol_V_CR	1	/* line feed - record -carriage return */
#define			FAB_dol_V_PRN	2	/* print-file carriage control */
#define			FAB_dol_V_BLK	3	/* records don't cross block boundaries */

#define	FANO	20

#ifdef	pyr
static struct	bsa	*file_table[FANO];
#else
struct	bsa	*file_table[FANO];
#endif

FILE	*f	= NULL;
int	file_count;
short	reclen;
short	fix;
short	recsize;
int	vfcsize;

#ifdef	NEWD
FILE	*lf;
#endif	NEWD

int	fd;		/* tape file descriptor */
int 	cflag, dflag, eflag, sflag, tflag, vflag, wflag, xflag;
int	setnr;
char	**gargv;
int 	goptind, gargc;

#define	LABEL_SIZE	80
char	label[LABEL_SIZE];

char	*block;
int	blocksize;

struct	mtop	op;

FILE *
openfile(fn)
char	*fn;
{
	char	ufn[256];
	char	ans[80];
	char	*p, *q, s, *ext;
	int	procf;

	procf = 1;
	/* copy fn to ufn and convert to lower case */
	p = fn;
	q = ufn;
	while (*p) {
		if (isupper(*p))
			*q = *p - 'A' + 'a';
		else
			*q = *p;
		p++;
		q++;
	}
	*q = '\0';

	/* convert the VMS to UNIX and make the directory path */
	p = ufn;
	q = ++p;
	while (*q) {
		if (*q == '.' || *q == ']') {
			s = *q;
			*q = '\0';
			if(procf && dflag) mkdir(p, 0777);
			*q = '/';
			if (s == ']')
				break;
		}
		*q++;
	}
	*q++;
	if(!dflag) p=q;
	/* strip off the version number */
	while (*q && *q != ';') {
		if( *q == '.') ext = q;
		q++;
	}
	if (cflag) {
		*q = ':';
	}
	else {
		*q = '\0';
	}
	if(!eflag && procf) procf = typecmp(++ext);
	if(procf && wflag) {
		printf("extract %s [ny]",filename);
		fflush(stdout);
		gets(ans);
		if(*ans != 'y') procf = NULL;
	}
	if(procf)
		/* open the file for writing */
		return(fopen(p, "w"));
	else
		return(NULL);
}

typecmp(str)    /* Compare the filename type in str with our list
                   of file type to be ignored.  Return 0 if the
                   file is to be ignored, return 1 if the
                   file is not in our list and should not be ignored. */
register char   *str;
{
        static char *type[] = {
                "exe",          /* vms executable image */
                "lib",          /* vms object library */
                "obj",          /* rsx object file */
                "odl",          /* rsx overlay description file */
                "olb",          /* rsx object library */
                "pmd",          /* rsx post mortem dump */
                "stb",          /* rsx symbol table */
                "sys",          /* rsx bootable system image */
                "tsk",          /* rsx executable image */
		"dir",
		"upd",
		"tlo",
		"tlb",
                ""              /* null string terminates list */
        };
        register int    i;

        i = -1;
        while (*type[++i])
                if (strncmp(str, type[i],3) == 0)
                        return(0);      /* found a match, file to be ignored */
        return(1);                      /* no match found */
}

process_file(buffer)
char	*buffer;
{
	int	i, n;
	char	*p, *q;
	short	dsize, nblk, lnch;

	int	c;
	short	*s;

	int 	procf;

	s = (short *) buffer;

	/* check the header word */
	if (*s != 257) {
		printf("%s: invalid data header\n", progname);
		exit(1);
	}

	c = 2;
	for (i = 0; i < FANO; i++) {
		file_table[i] = (struct bsa *) &buffer[c];
#ifndef	SWAP
		dsize = file_table[i]->bsa_dol_w_size;
#else
		swap(&file_table[i]->bsa_dol_w_size, &dsize, sizeof(short));
#endif
		c += dsize + 4;
	}

	/* extract file name */
#ifndef	SWAP
	dsize = file_table[0]->bsa_dol_w_size;
#else
	swap(&file_table[0]->bsa_dol_w_size, &dsize, sizeof(short));
#endif
	p = file_table[0]->bsa_dol_t_text;
	q = filename;
	for (i = 0; i < dsize; i++)
		*q++ = *p++;
	*q = '\0';

	/* extract file's record attributes */
#ifndef	SWAP
	dsize = file_table[5]->bsa_dol_w_size;
#else
	swap(&file_table[5]->bsa_dol_w_size, &dsize, sizeof(short));
#endif
	p = file_table[5]->bsa_dol_t_text;
	recfmt = p[0];
	recatt = p[1];
#ifndef	SWAP
	bcopy(&p[2], &recsize, sizeof(short));
#else
	swap(&p[2], &recsize, sizeof(short));
#endif
	vfcsize = p[15];
	if (vfcsize == 0)
		vfcsize = 2;
#ifdef	DEBUG
	printf("recfmt = %d\n", recfmt);
	printf("recatt = %d\n", recatt);
	printf("reclen = %d\n", recsize);
	printf("vfcsize = %d\n", vfcsize);
#endif
#ifndef	SWAP
	bcopy(&p[10], &nblk, sizeof(short));
	bcopy(&p[12], &lnch, sizeof(short));
#else
	swap(&p[10], &nblk, sizeof(short));
	swap(&p[12], &lnch, sizeof(short));
#endif
	filesize = (nblk-1)*512 + lnch;
#ifdef DEBUG
	printf("nbk = %d, lnch = %d\n", nblk, lnch);
	printf("filesize = 0x%x\n", filesize);
#endif

	/* open the file */
	if (f != NULL) {
		fclose(f);
		file_count = 0;
		reclen = 0;
	}
	procf = 0;
	if (goptind < gargc) 
		for(i=goptind; i < gargc; i++) {
			procf |= match(filename,gargv[i]);
		}
	else
		procf = 1;
	if (tflag && procf) 
		printf( " %-35s %8d \n",filename,filesize);
	if (xflag && procf) {
		/* open file */
		f = openfile(filename);
		if(f != NULL && vflag) printf("extracting %s\n", filename);
	}
}
/*
 *
 *  process a virtual block record (file record)
 *
 */
process_vbn(buffer, rsize)
char		*buffer;
unsigned short	rsize;
{
	int	c, i;

	if (f == NULL) {
		return;
	}
	i = 0;
	while (file_count+i < filesize && i < rsize) {
		switch (recfmt) {
		case FAB_dol_C_FIX:
			if (reclen == 0) {
				reclen = recsize;
			}
			fputc(buffer[i], f);
			i++;
			reclen--;
			break;

		case FAB_dol_C_VAR:
		case FAB_dol_C_VFC:
			if (reclen == 0) {
				reclen = *((short *) &buffer[i]);
#ifdef	SWAP
				swap(&reclen, &reclen, sizeof(short));
#endif
#ifdef	NEWD
				fprintf(lf, "---\n");
				fprintf(lf, "reclen = %d\n", reclen);
				fprintf(lf, "i = %d\n", i);
				fprintf(lf, "rsize = %d\n", rsize);
#endif	NEWD
				fix = reclen;
				i += 2;
				if (recfmt == FAB_dol_C_VFC) {
					i += vfcsize;
					reclen -= vfcsize;
				}
			} else if (reclen == fix
					&& recatt == (1 << FAB_dol_V_FTN)) {
					/****
					if (buffer[i] == '0')
						fputc('\n', f);
					else if (buffer[i] == '1')
						fputc('\f', f);
					*** sow ***/
					fputc(buffer[i],f); /** sow **/
					i++;
					reclen--;
			} else {
				fputc(buffer[i], f);
				i++;
				reclen--;
			}
			if (reclen == 0) {
				fputc('\n', f);
				if (i & 1)
					i++;
			}
			break;

		case FAB_dol_C_STM:
		case FAB_dol_C_STMLF:
			if (reclen < 0) {
				printf("SCREAM\n");
			}
			if (reclen == 0) {
				reclen = 512;
			}
			c = buffer[i++];
			reclen--;
			if (c == '\n') {
				reclen = 0;
			}
			fputc(c, f);
			break;

		case FAB_dol_C_STMCR:
			c = buffer[i++];
			if (c == '\r')
				fputc('\n', f);
			else
				fputc(c, f);
			break;

		default:
			fclose(f);
			unlink(filename);
			fprintf(stderr, "Invalid record format = %d\n", recfmt);
			return;
		}
	}
	file_count += i;
}
#ifdef	SWAP
/*
 *
 *  do swapping for Motorola type architectures
 *
 */
swap(from, to, nbytes)
char	*from, *to;
int	nbytes;
{
	int	i, j;
	char	temp[100];

	for (i = 0; i < nbytes; i++)
		temp[i] = from[i];
	for (i = 0, j = nbytes-1; i < nbytes; i++, j--)
		to[i] = temp[j];
}
#endif
/*
 *
 *  process a backup block
 *
 */
process_block(block, blocksize)
char	*block;
int	blocksize;
{

	unsigned short	bhsize, rsize, rtype;
	unsigned long	bsize, i;

	i = 0;

	/* read the backup block header */
	block_header = (struct bbh *) &block[i];
	i += sizeof(struct bbh);

	bhsize = block_header->bbh_dol_w_size;
	bsize = block_header->bbh_dol_l_blocksize;
#ifdef	SWAP
	swap(&bhsize, &bhsize, sizeof(short));
	swap(&bsize, &bsize, sizeof(long));
#endif

	/* check the validity of the header block */
	if (bhsize != sizeof(struct bbh)) {
		fprintf(stderr, "%s: Invalid header block size\n", progname);
		exit(1);
	}
	if (bsize != 0 && bsize != blocksize) {
		fprintf(stderr, "%s: Invalid block size\n", progname);
		exit(1);
	}
#ifdef	DEBUG
	printf("new block: i = %d, bsize = %d\n", i, bsize);
#endif

	/* read the records */
	while (i < bsize) {
		/* read the backup record header */
		record_header = (struct brh *) &block[i];
		i += sizeof(struct brh);

		rtype = record_header->brh_dol_w_rtype;
		rsize = record_header->brh_dol_w_rsize;
#ifdef	SWAP
		swap(&rtype, &rtype, sizeof(short));
		swap(&rsize, &rsize, sizeof(short));
#endif
#ifdef	DEBUG
		printf("rtype = %d\n", rtype);
		printf("rsize = %d\n", rsize);
		printf("flags = 0x%x\n", record_header->brh_dol_l_flags);
		printf("addr = 0x%x\n", record_header->brh_dol_l_address);
		printf("i = %d\n", i);
#endif

		switch (rtype) {

		case brh_dol_k_null:
#ifdef	DEBUG
			printf("rtype = null\n");
#endif
			break;

		case brh_dol_k_summary:
#ifdef	DEBUG
			printf("rtype = summary\n");
#endif
			break;

		case brh_dol_k_file:
#ifdef	DEBUG
			printf("rtype = file\n");
#endif
			process_file(&block[i]);
			break;

		case brh_dol_k_vbn:
#ifdef	DEBUG
			printf("rtype = vbn\n");
#endif
			process_vbn(&block[i], rsize);
			break;

		case brh_dol_k_physvol:
#ifdef	DEBUG
			printf("rtype = physvol\n");
#endif
			break;

		case brh_dol_k_lbn:
#ifdef	DEBUG
			printf("rtype = lbn\n");
#endif
			break;

		case brh_dol_k_fid:
#ifdef	DEBUG
			printf("rtype = fid\n");
#endif
			break;

		default:
			fprintf(stderr, " %s: invalid record type\n", progname);
			fprintf(stderr, " record type = %d\n", rtype);
			exit(1);
		}
#ifdef pyr
		i = i + rsize;
#else
		i += rsize;
#endif
	}
}

rdhead()
{
	int i, nfound;
	char name[80];
	nfound = 1;
	/* read the tape label - 4 records of 80 bytes */
	while ((i = read(fd, label, LABEL_SIZE)) != 0) {
		if (i != LABEL_SIZE) {
			fprintf(stderr, "%s: bad label record\n", progname);
			exit(1);
		}
		if (strncmp(label, "VOL1",4) == 0) {
			sscanf(label+4, "%14s", name);
			if(vflag || tflag) printf("Volume: %s\n",name);
		}
		if (strncmp(label, "HDR1",4) == 0) {
			sscanf(label+4, "%14s", name);
			sscanf(label+31, "%4d", &setnr);
		}
		/* get the block size */
		if (strncmp(label, "HDR2", 4) == 0) {
			nfound = 0;
			sscanf(label+5, "%5d", &blocksize);
#ifdef	DEBUG
			printf("blocksize = %d\n", blocksize);
#endif
		}
	}
	if((vflag || tflag) && !nfound) 
		printf("Saveset name: %s   number: %d\n",name,setnr);
	/* get the block buffer */
	block = (char *) malloc(blocksize);
	if (block == (char *) 0) {
		fprintf(stderr, "memory allocation for block failed\n");
		exit(1);
	}
	return(nfound);
}

rdtail()
{
	int i;
	char name[80];
	/* read the tape label - 4 records of 80 bytes */
	while ((i = read(fd, label, LABEL_SIZE)) != 0) {
		if (i != LABEL_SIZE) {
			fprintf(stderr, "%s: bad label record\n", progname);
			exit(1);
		}
		if (strncmp(label, "EOF1",4) == 0) {
			sscanf(label+4, "%14s", name);
			if(vflag || tflag)
				printf("End of saveset: %s\n\n\n",name);
		}
	}
}

usage(progname)
char	*progname;
{
	fprintf(stderr,
	  "Usage:  %s -{tx}[cdevw][-s setnumber][-f tapefile]\n",progname);
}
main(argc, argv)
int	argc;
char	*argv[];
{
	
	int	c, i, eoffl;
	int	selset;
	extern int optind;
	extern char *optarg;

	progname = argv[0];
	if(argc < 2){
		usage(progname);
		exit(1);
	}
	gargv = argv;
	gargc = argc;
	tapefile = def_tapefile;
	cflag=dflag=eflag=sflag=tflag=vflag=wflag=xflag=0;
	while((c=getopt(argc,argv,"cdef:s:tvwx")) != EOF)
		switch(c){
		case 'c':
			cflag++;
			break;
		case 'd':
			dflag++;
			break;
		case 'e':
			eflag++;
			break;
		case 'f':
			tapefile = optarg;
			break;
		case 's':
			sflag++;
			sscanf(optarg,"%d",&selset);
			break;
		case 't':
			tflag++;
			break;
		case 'v':
			vflag++;
			break;
		case 'w':
			wflag++;
			break;
		case 'x':
			xflag++;
			break;
		case '?':
			usage(progname);
			exit(1);
			break;
		};
	if(!tflag && !xflag) {
		usage(progname);
		exit(1);
	}
	goptind = optind;

#ifdef	NEWD
	/* open debug file */
	lf = fopen("log", "w");
	if (lf == NULL) {
		perror("log");
		exit(1);
	}
#endif

	/* open the tape file */
	fd = open(tapefile, O_RDONLY);
	if (fd < 0) {
		perror(tapefile);
		exit(1);
	}

	/* rewind the tape */
	op.mt_op = MTREW;
	op.mt_count = 1;
	i = ioctl(fd, MTIOCTOP, &op);
	if (i < 0) {
		perror(tapefile);
		exit(1);
	}

	eoffl = rdhead();
	/* read the backup tape blocks until end of tape */ 
	while (!eoffl) {
		if(sflag && setnr != selset) {
			op.mt_op = MTFSF;
			op.mt_count = 1;
			i = ioctl(fd, MTIOCTOP, &op);
			if (i < 0) {
				perror(tapefile);
				exit(1);
			}
			i = 0;
		}
		else
			i = read(fd, block, blocksize);
		if(i == 0) {
			rdtail();
			eoffl=rdhead();
		}
		else if (i != blocksize) {
			fprintf(stderr, "bad block read i = %d\n", i);
			exit(1);
		}
		else{
			eoffl = 0;
			process_block(block, blocksize);
		}
	}
	if(vflag || tflag) printf("End of tape\n");

	/* close the tape */
	close(fd);

#ifdef	NEWD
	/* close debug file */
	fclose(lf);
#endif	NEWD

	/* exit cleanly */
	exit(0);
}
