#include	<stdio.h>
#include	<ctype.h>

/*)BUILD
*/

#ifdef	DOCUMENTATION

title	untar	Read tar (tape archive) format tapes
index		Read tar (tape archive) format tapes

Synopsis

	untar [- options] [-d directory] [tar_file]

Description

	untar extracts the contents of a Unix tar format tape,
	simplifing transfer of files from Unix to VMS or other
	similar operating systems.  (Untar runs on VMS, RSTS/E,
	RSX, or RT11.)

	If the tar_file is missing, untar will read "tartap.dat"
	in your current directory.

Options

	Untar recognizes the following options:
	.lm +4
	.s.i -4;-a##Create all files in "ascii" (readable text) format.
	.s.i -4;-b##Create all files in "binary" format.
	.s.i -4;-d##(Requires a file name argument).  Scan the tar archive
	for directories, writing a VMS command file to create the
	directories.  (On-the-fly creation has not been implemented.)
	.s.i -4;-h##(Requires a string argument).  If specified, this
	string is prepended to all filenames before creation.
	For example, assume untar was invoked as

	    untar -h "dskz:"

	A filename "foo.bar" would be created as if it were
	written "dskz:foo.bar".  See also the "-n" option.
	.s.i -4;-n##Ignore directory path information in
	the archive filename, writing all files to the current
	directory.
	.s.i -4;-q##Query.  Inquire whether to create each archive member.
	.s.i -4;-t##List the contents of the archive.
	.s.i -4;-v##Verbose -- list each file as it is processed.  -v2
	lists more information.
	.s.lm -4
	If neither -a nor -b are given, untar decides on the output
	format by examining the filetype.  Files with no filetype
	or whose filetype is included in a built-in list are created
	in "binary" mode, others in ascii mode.  The -a/-b option
	is uninteresting on Unix, but important on RSTS/E and similar
	operating systems.

Reading the Archive

	The following procedure is reommended:
	.s.lm +4
	Read the tape onto a disk file, converting from the tape
	format (which may contain 10240 byte records) to "fixed-block,
	512-byte" records.  The Decus C distribution contains
	two Basic-Plus programs (rdmt.bas for RSTS/E and vrdmt.bas
	for VMS) that read tar files onto disk.  While the procedure
	is generally straight-forward, different operating systems
	have different requirements.

	Use untar to list all directories (-d option) and build an
	appropriate directory structure.

	Use untar to extract the files.
	.lm -4

Assumed Filetypes

	The following filetypes are assumed to be "binary":

	  tsk olb mlb sys sml ulb obj
	  stb bin lda exe sav   o   a

	Also, any file with no filetype will be assumed to be
	"binary".

Operating-system Notes

	Untar has not been tested on Unix.

	On RSTS/E and RT11, Untar will not create a file that already
	exists in your directory.

	If untar cannot create a file, it prompts for a replacement
	file name.  (If run interactively.)

#endif

/*
 *	D e f i n i t i o n s   a n d   G l o b a l s
 */

#define	FALSE		0
#define	TRUE		1
#define	EOS		'\0'		/* End of string marker	*/
#ifdef	decus
#define	OPENMODE	"rn"
#else
#define	OPENMODE	"r"
#endif

/*
 * Specify the operating system.
 */

#define	UNIX	0
#define	RSX	1
#define	VMS	2
#define	RT11	3

#ifdef	rsx
#define	OPSYS	RSX
#endif
#ifdef	rt11
#define	OPSYS	RT11
#endif
#ifdef	unix
#define	OPSYS	UNIX
#endif
#ifdef	vms
#define	OPSYS	VMS
#endif
#ifndef	OPSYS
	<< error, operating system wasn't defined >>
#endif

#if	OPSYS == UNIX
#define	IO_SUCCESS	0
#define	IO_ERROR	1
#endif

#if	OPSYS == RSX || OPSYS == RT11
extern int	$$rsts
#endif

#if	OPSYS == VMS
#include	ssdef		/* System status codes		*/
#include	iodef		/* I/O request codes		*/
#include	descrip		/* String descriptors		*/
#include	errno
#define	IO_SUCCESS	SS$_NORMAL
#define	IO_ERROR	errno	/* Use global value		*/
typedef struct dsc$descriptor_s STRING;	/* string descriptor	*/
/*
 * The following macro builds a descriptor from an argument string.
 */
#define	descrip(text, p)			\
	((p)->dsc$a_pointer = text,		\
	(p)->dsc$w_length = strlen(text),	\
	(p)->dsc$b_dtype = DSC$K_DTYPE_T,	\
	(p)->dsc$b_class = DSC$K_CLASS_S)

#endif

#ifdef	vms
/*
 * Create files in vanilla rms on vms.
 */
#define	CREATE(name, mode) (fdopen(creat(name, 0, "rat=cr", "rfm=var"), mode))
#else
#define	CREATE(name, mode) (fopen(name, mode))
#endif

#define	DEF_INPUT	"tartap.dat"
#define	RSIZE		512		/* Probably shouldn't change	*/
#define	FNSIZE		100		/* Filename string size		*/

/*
 * This structure defines the header of an archive file.
 * headers are always aligned on a 512 byte boundary.
 * All information in the header is in readable ascii.
 * All numbers are stored in octal.  The checksum is
 * calculated differently on different Unix systems.
 * The file create date is in Unix "time" format.
 *
 * If the last character of the name is '/', the
 * entry is a directory (there is no other data).
 * Similar magic (unimplemented here) is needed to
 * recognize "special files".
 */

typedef struct header {
    char	name[100];		/* Unix file name	*/
    char	protmode[8];		/* Protection bits	*/
    char	userid[8];		/* User i.d.		*/
    char	nlinks[8];		/* Number of links	*/
    char	filesize[11];		/* File size in bytes	*/
    char	date[13];		/* File create date	*/
    char	checksum[8];		/* Random number	*/
    char	linkflag[1];		/* '1' (one) if a link	*/
    char	linkpath[100];		/* Link to file		*/
} HEADER;

/*
 * Global variables
 */

int	debug;			/* Set for debug printouts	*/
int	verbose;		/* Log file			*/
int	nocreate;		/* Don't create files/dir's	*/
int	nopath;
int	list;			/* List tar contents		*/
int	ascii;			/* Ascii option (not binary)	*/
int	binary;			/* Binary output (not ascii)	*/
int	isbinary;		/* Mode of this file		*/
int	query;			/* Ask about each file.		*/
int	nooutput;		/* TRUE to skip this file	*/
FILE	*infd;			/* Tar input file		*/
FILE	*outfd;			/* Archive member written here	*/
FILE	*dirfd;			/* Directory command file	*/
char	*dirfile = NULL;	/* Set (-d file) to do direct.	*/
char	*header = "";		/* Set by -h to do header	*/
union {				/* Tar data record		*/
    HEADER	h;		/* Tar file header		*/
    char	b[RSIZE + 1];	/* Input record buffer		*/
} record;

char	arch_name[FNSIZE+1];		/* Filename in tar file	*/
char	filename[FNSIZE+1];		/* Filename to create	*/
char	*archivefilename = DEF_INPUT;	/* Tar file to process	*/
char	subdir[FNSIZE+1];		/* For vms [...] hacks	*/
char	work[FNSIZE+1];			/* String scratch area	*/
char	line[257];			/* For user prompts	*/
char	filetype[4];			/* For ascii test	*/
long	protmode;			/* For Unix create	*/
long	filesize;			/* File size (bytes)	*/
long	blocks;				/* File size (blocks)	*/
int	bytes_in_last;			/* left over bytes	*/
long	inrecords;			/* Read from tar input	*/

char	*types[] = {			/* Assumed binary	*/
	"tsk", "olb", "mlb", "sys", "sml", "ulb",
	"obj", "stb", "bin", "lda", "exe", "sav",
	"o",   "a",
	NULL
};

main(argc, argv)
int		argc;
char		*argv[];
/*
 * Untar main program.
 */
{
	register int		i;

	getoptions(argc, argv);
	for (i = 1; i < argc; i++) {
	    if (argv[i] != NULL) {
		archivefilename = argv[i];
		break;
	    }
	}
	/*
	 * Process the file.
	 */
	if ((infd = fopen(archivefilename, OPENMODE)) == NULL) {
	    perror(archivefilename);
	    fprintf(stderr, "Can't open tar format input file.\n");
	    exit(IO_ERROR);
	}
	if (dirfile != NULL) {
	    if ((dirfd = CREATE(dirfile, "w")) == NULL) {
		perror(dirfile);
		fprintf(stderr,
		    "Can't open directory command output file.\n");
		exit(IO_ERROR);
	    }
	    nocreate = TRUE;
	}
	if (list)
	    nocreate = TRUE;
	inrecords = 0L;
	while (feof(infd) == 0 && ferror(infd) == 0) {
	    if (getheader())
		getfile();
	}
	if (ferror(infd)) {
	    perror(archivefilename);
	    exit(IO_ERROR);
	}
}

int
getheader()
/*
 * Read and parse a tar header.  Return TRUE if ok, FALSE to skip.
 * getheader() sets up lots of global stuff.
 */
{
	extern long	octal_to_long();

	/*
	 * Read the next record -- it is a tar member header.
	 */
	if (!readrecord())
	    return (FALSE);
	/*
	 * Process the archive name field.
	 */
	copystring(arch_name, record.h.name, sizeof (record.h.name));
	if (arch_name[0] == EOS)		/* "end of tape"	*/
	    return (FALSE);
	if (arch_name[strlen(arch_name) - 1] == '/') {
	    arch_name[strlen(arch_name) - 1] = EOS;	/* Drop signal	*/
	    if (list || verbose > 1)
		printf("\"%s\" is a directory\n", arch_name);
	    if (!nocreate)
		getdirectory();
	    if (dirfd != NULL)
		dircomfile();
	    return (FALSE);
	}
	/*
	 * Process the link flag
	 */
	if (record.h.linkflag[0] == '1') {
#if	OPSYS == UNIX
	    /*
	     * Do links someday
	     */
	    copystring(work, record.h.linkpath, sizeof (record.h.linkpath));
	    if (list || verbose) {
		printf("\"%s\" links to \"%s\", ignored.\n",
		    arch_name, work);
	    }
#else
	    copystring(work, record.h.linkpath, sizeof (record.h.linkpath));
	    if (list || verbose) {
		printf("\"%s\" links to \"%s\", ignored.\n",
		    arch_name, work);
	    }
#endif
	    return (FALSE);
	}

	/*
	 * Save the protection mode for Unix and
	 * process the file size field.
	 */
	copystring(work, record.h.protmode, sizeof (record.h.protmode));
	protmode = octal_to_long(work);
	copystring(work, record.h.filesize, sizeof (record.h.filesize));
	if ((filesize = octal_to_long(work)) == 0L) {
	    if (list || verbose)
		printf("\"%s\" has zero length, skipped.\n", arch_name);
	    return (FALSE);
	}
	blocks = filesize / RSIZE;
	bytes_in_last = filesize % RSIZE;
	if (list || verbose > 1) {
	    printf("\"%s\", %ld bytes (%ld blocks, %d bytes in last)\n",
		arch_name, filesize, blocks, bytes_in_last);
	}
	/*
	 * All processed.  Try to create the file.
	 */
	if (nocreate || !open_output())
	    nooutput = TRUE;
	else nooutput = FALSE;
	return (TRUE);
}

getdirectory()
/*
 * Create a directory entry.
 */
{
#if	OPSYS == UNIX
	int		status;		/* fork/wait status	*/
	int		cstatus;	/* Child status		*/

	strcpy(filename, arch_name);
	filename[strlen(filename) - 1] = EOS;
	/*
	 * How does a mundane create a directory?
	 * Maybe this will work:
	 */
	if ((status = vfork()) != 0) {
	    /*
	     * Parent -- wait for the child.
	     */
	    if (status = wait(&cstatus)) < 0)
		perror("make directory: child went away");
	    else if (cstatus != 0) {
		fprintf(stderr,
			"error creating directory \"%s\"\n",
			filename);
	    }
	}
	else {
	    status = execl("/bin/mkdir", "mkdir", filename, NULL);
	    if (status == -1)
		perror("execl of mkdir");
	}
#else
#if	OPSYS == VMS && FALSE
	/*
	 * Note: this doesn't seem to work.
	 */
	int		status;		/* fork/wait status	*/
	int		cstatus;	/* Child status		*/
	register char	*np;
	extern char	*vms_etext();

	strcpy(filename, arch_name);
	squishfilename(filename);
	while ((np = strchr(filename, '/')) != NULL) {
	    *np = '.';
	    if (np[1] == EOS)
		*np = EOS;
	}
	if (filename[0] == EOS)
	    return;
	sprintf(work, "[.%s]", filename);
	strcpy(filename, work);
	/*
	 * How does a mundane create a directory?
	 * Maybe this will work:
	 */
	if (verbose > 1)
	    printf("Attempting to create directory \"%s\"\n", filename);
	if ((status = vfork()) != 0) {
	    /*
	     * Parent -- wait for the child.
	     */
	    if ((status = wait(&cstatus)) < 0)
		perror("make directory: child went away");
	    else if (cstatus != 0) {
		fprintf(stderr,
			"error (%d) creating directory \"%s\"\n",
			cstatus, filename);
		fprintf(stderr, "%s\n", vms_etext(cstatus));
	    }
	}
	else {
	    if ((status = execl("$create", "/dir", filename, NULL)) == -1)
		perror("execl of create/dir");
	}
#else
	/*
	 * Can't create directories on RSTS/E, RSX, or RT11.
	 */
	if (verbose)
	    printf("\"%s\" is a directory, skipped.\n", arch_name);
#endif
#endif
}

dircomfile()
/*
 * Create a vax command file to build a directory.
 */
{
	register char	*np;

	strcpy(filename, arch_name);
	squishfilename(filename);
	while ((np = strchr(filename, '/')) != NULL)
	    *np = '.';
	fprintf(dirfd, "$ cre/dir [.%s]\n", filename);
}

getfile()
/*
 * Process the current archive member
 */
{
	register long	count;

	for (count = 1; count <= blocks; count++) {
	    if (readrecord())
		output(RSIZE);
	    else {
		perror(archivefilename);
		fprintf(stderr, "reading \"%s\"\n", arch_name);
		filesize = 0L;
	    }
	}
	if (bytes_in_last > 0) {
	    if (readrecord())
		output(bytes_in_last);
	    else {
		perror(archivefilename);
		fprintf(stderr, "reading last block of \"%s\"\n",
		    arch_name);
	    }
	}
	if (!nooutput)
	    fclose(outfd);
}

int
open_output()
/*
 * Create the current tar member.  Munging it as necessary for the
 * opeating system.  TRUE if ok, FALSE to skip the file.
 */
{
	register char	*np;
	register int	fileid;

	if (query) {
	    sprintf(work, "Create \"%s\"", arch_name);
	    if (!getyesno(work, "Yes"))
		return (FALSE);
	}
	strcpy(filename, arch_name);
#if	OPSYS == UNIX
	/*
	 * Create the file on Unix.
	 */
	if (nopath) {
	    while ((np = strchr(filename, '/')) != NULL)
		strcpy(filename, np + 1);
	}
	sprintf(work, "%s%s", header, filename);	/* Add header	*/
	strcpy(filename, work);
	for (;;) {
	    if (verbose)
		printf("\"%s\" => \"%s\"\n", arch_name, filename);
	    if (outfd = fdopen(creat(filename, protmode), "w")) != NULL)
		break;
	    perror(filename);
	    fprintf(stderr, "Can't create \"%s\" (archive is \"%s\")\n",
		filename, arch_name);
	    if (!isatty(fileno(stdin)) || feof(stdin)) {
		printf("Skipping archive \"%s\"\n", arch_name);
		return (FALSE);
	    }
	    fprintf(stderr, "New file, <return> to skip file: ");
	    if (!getcommand() || line[0] == EOS) {
		return (FALSE);
	    }
	    strcpy(filename, line);
	    protmode = 0666;			/* Get default mode	*/
	}
#endif

#if	OPSYS == VMS
	/*
	 * Create the file on VMS
	 *
	 * First, convert any directory paths to VMS flavor:
	 *	"foo/bar/file_name.extension" => "[.foo.bar]filename.ext"
	 */
	squishfilename(filename);	/* Remove non-filename stuff	*/
	testbinary(filename);		/* Check (fix) .ext filetype	*/
	subdir[0] = EOS;		/* Build subdirectory strings	*/
	while ((np = strchr(filename, '/')) != NULL) {
	    *np = EOS;
	    sprintf(work, "%s.%s", subdir, filename);
	    strcpy(subdir, work);
	    strcpy(filename, np + 1);
	}
	if (!nopath && subdir[0] != EOS) {
	    sprintf(work, "[%s]%s", subdir, filename);
	    strcpy(filename, work);
	}
	sprintf(work, "%s%s", header, filename);	/* Add header	*/
	strcpy(filename, work);
	for (;;) {
	    if (verbose)
		printf("\"%s\" => \"%s\"\n", arch_name, filename);
	    if (isbinary)
		outfd = fopen(filename, "w");
	    else {
		fileid = creat(filename, 0, "rat=cr", "rfm=var");
		outfd = (fileid == -1) ? NULL : fdopen(fileid, "w");
	    }
	    if (outfd != NULL)
		break;
	    perror(filename);
	    fprintf(stderr, "Can't create \"%s\" (archive is \"%s\")\n",
		filename, arch_name);
	    if (!isatty(fileno(stdin)) || feof(stdin)) {
		printf("Skipping archive \"%s\"\n", arch_name);
		return (FALSE);
	    }
	    fprintf(stderr, "New file, <return> to skip file: ");
	    if (!getcommand() || line[0] == EOS) {
		return (FALSE);
	    }
	    strcpy(filename, line);
	}
#endif

#if	OPSYS == RSX || OPSYS == RT11
	/*
	 * Create the file on RSTS or RSX.
	 *
	 * Strip out any directory paths.  (Note that this
	 * isn't quite correct as paths are legal on P/OS.)
	 */
	squishfilename(filename);		/* Remove funny text	*/
	while ((np = strchr(filename, '/')) != NULL)
	    strcpy(filename, np + 1);
	sprintf(work, "%s%s", header, filename);	/* Add header	*/
	strcpy(filename, work);
	for (;;) {
	    /*
	     * RSTS and RT11 don't have file version numbers.
	     * Make sure the file doesn't exist already.
	     */
	    testbinary(filename);
	    if (verbose)
		printf("\"%s\" => \"%s\"\n", arch_name, filename);
#if	OPSYS == RT11
	    outfd = fopen(filename, "r");
#else
	    if ($$rsts)
		outfd = fopen(filename, "r");
	    else
		outfd = NULL;
#endif
	    if (outfd == NULL)
		outfd = fopen(filename, (isbinary) ? "wn" : "w");
	    else {
		fclose(outfd);
		fprintf(stderr, "File already exists.  ");
		outfd = NULL;
	    }
	    if (outfd != NULL)
		break;			/* File is open correctly	*/
	    else {
		fprintf(stderr,
		    "Can't create \"%s\", (archive is \"%s\").\n",
		    filename, arch_name);
		if (!isatty(fileno(stdin)) || feof(stdin)) {
		    printf("Skipping archive \"%s\"\n", arch_name);
		    return (FALSE);
		}
		fprintf(stderr, "New file, <return> to skip file: ");
		if (!getcommand() || line[0] == EOS)
		    return (FALSE);
		strcpy(filename, line);
	    }
	}
#endif
	return (TRUE);
}

output(nbytes)
int		nbytes;
/*
 * Output the first nbytes from the record.
 */
{
	if (nooutput)
	    return;
	if (isbinary)
	    fwrite(record.b, sizeof (char), nbytes, outfd);
	else {
	    /*
	     * On RSTS/E, RSX, RT11, and VMS, this
	     * outputs "vanilla ascii" records.
	     */
	    record.b[nbytes] = EOS;
	    fputs(record.b, outfd);
	}
}

int
readrecord()
/*
 * Read one 512 byte record from the archive.  TRUE if ok, FALSE if error.
 */
{
	if (fread(record.b, sizeof (char), RSIZE, infd) != RSIZE)
	    return (FALSE);
	else {
	    inrecords++;
	    return (TRUE);
	}
}

testbinary(name)
char		*name;
/*
 * Set isbinary flag.
 */
{
	register char	*np;
	register char	**tp;

	if (ascii)				/* Always ascii		*/
	    isbinary = FALSE;
	else if (binary				/* Always binary	*/
	      || (np = strchr(name, '.')) == NULL	/* no dot	*/
	      || *++np == EOS)				/* no type	*/
	    isbinary = TRUE;			/* is binary		*/
	else {
	    if (strlen(np) > 3)			/* Long filetype?	*/
		np[3] = EOS;			/* Trim filetype	*/
	    strcpy(filetype, np);		/* Get a copy		*/
	    for (np = filetype; *np != EOS; np++) {
		if (isupper(*np))
		    *np = tolower(*np);
	    }
	    for (tp = types; *tp != NULL; tp++) {
		if (strcmp(*tp, filetype) == 0)
		    break;			/* Found a type		*/
	    }
	    if (*tp != NULL)
		isbinary = TRUE;		/* Known type		*/
	    else
		isbinary = FALSE;		/* Others are ascii	*/
	}
}

long
octal_to_long(in)
register char	*in;
/*
 * Convert octal string to a long result.
 */
{
	long		result;

	result = 0L;
	while (*in >= '0' && *in <= '7') {
	    result <<= 3;
	    result += (*in++ - '0');
	}
	return (result);
}

copystring(out, in, size)
register char	*out;
register char	*in;
int		size;
/*
 * Does Basic-Plush cvt$$(in, 1+2+4) which drops garbage,
 * blanks, and tabs.
 */
{
	char		*inend;
	int		c;

	inend = &in[size];
	while (in < inend) {
	    c = *in++ & 0177;			/* Eat parity	*/
	    if (isprint(c) && c != ' ')
		*out++ = c;
	}
	*out = EOS;
}

squishfilename(string)
char		*string;
/*
 * Removes non rad-50 characters (except '/' and all but the last '.')
 * from the argument. 
 */
{
	register char	*inp;
	register char	*outp;

	for (inp = outp = string; *inp != EOS; *inp++) {
	    if (isalpha(*inp)
	     || isdigit(*inp)
	     || *inp == '/'
	     || *inp == '.')
		*outp++ = *inp;
	}
	*outp = EOS;
	/*
	 * Remove all but the last '.'
	 */
	if ((inp = strrchr(string, '.')) != NULL)
	    *inp = '~';			/* Remember last '.'	*/
	for (inp = string; (inp = strchr(inp, '.')) != NULL;)
	    strcpy(inp, inp + 1);	/* Erase other dots	*/
	if ((inp = strchr(string, '~')) != NULL)
	    *inp = '.';			/* Fix last one again	*/
}

/*
 *			G E T O P T I O N S
 *
 * Generalized command line argument processor.  The following
 * types of arguments are parsed:
 *	flags		The associated int global is incremented:
 *			-f	f-flag set to 1
 *			-f123	f-flag set to 123 (no separator)
 *			-fg	f-flag and g-flag incremented.
 *	values		A value must be present.  The associated
 *			int global receives the value:
 *			-v123	value set to 123
 *			-v 123	value set to 123
 *	arguments	The associated global (a char *) is
 *			set to the next argument:
 *			-f foo	argument set to "foo"
 */

#define	FLAG	0
#define	VALUE	1
#define	ARG	2
#define	ERROR	3

typedef struct argstruct {
	char	opt;		/* Option byte			*/
	char	type;		/* FLAG/VALUE/ARG		*/
	int	*name;		/* What to set if option seen	*/
	char	*what;		/* String for error message	*/
} ARGSTRUCT;

static ARGSTRUCT arginfo[] = {
	{	'a',	FLAG,	&ascii,		"always ascii output"	},
	{	'b',	FLAG,	&binary,	"always binary output"	},
	{	'd',	ARG,	&dirfile,	"directory command"	},
	{	'h',	ARG,	&header,	"file name header"	},
	{	'n',	FLAG,	&nopath,	"ignore archive paths"	},
	{	'q',	FLAG,	&query,		"query each file"	},
	{	't',	FLAG,	&list,		"list entries only"	},
	{	'v',	FLAG,	&verbose,	"verbose"		},
	{	EOS,	ERROR,	NULL,		NULL			},
};

static char *argtype[] = {
	"flag", "takes value", "takes argument"
};

static
getoptions(argc, argv)
int		argc;
char		**argv;
/*
 * Process arg's
 */
{
	register char		*ap;
	register int		c;
	register ARGSTRUCT	*sp;
	int			i;
	int			helpneeded;

	getredirection(argc, argv);
	helpneeded = FALSE;
	for (i = 1; i < argc; i++) {
	    if ((ap = argv[i]) != NULL && *ap == '-') {
		argv[i] = NULL;
		for (ap++; (c = *ap++) != EOS;) {
		    c = tolower(c);
		    sp = arginfo;
		    while (sp->opt != EOS && sp->opt != c)
			sp++;
		    switch (sp->type) {
		    case FLAG:			/* Set the flag	*/
			if (!isdigit(*ap)) {
			    ++(*sp->name);
			    break;
			}
		    case VALUE:			/* -x123	*/
		        if (isdigit(*ap)) {
			    *((int **)sp->name) = atoi(ap);
			    *ap = EOS;
			}
			else if (*ap == EOS && ++i < argc) {
			    *((int **)sp->name) = atoi(argv[i]);
			    argv[i] = NULL;
			}
			else {
			    fprintf(stderr,
				"Bad option '%c%s' (%s)",
				c, ap, sp->what);
			    fprintf(stderr, ", ignored\n");
			    helpneeded++;
			}
			break;

		    case ARG:			/* -x foo	*/
			if (++i < argc) {
			    *((char **) sp->name) = argv[i];
			    argv[i] = NULL;
			}
			else {
			    fprintf(stderr,
				"Argument needed for '%c' (%s)",
				c, sp->what);
			    fprintf(stderr, ", ignored\n");
			    helpneeded++;
			}
			break;

		    case ERROR:
			fprintf(stderr,
			    "Unknown option '%c', ignored\n", c);
			helpneeded++;
			break;
		    }
		}
	    }
	}
	if (helpneeded > 0) {
	    for (sp = arginfo; sp->opt != EOS; sp++) {
		fprintf(stderr, "'%c' -- %s (%s)\n",
		    sp->opt, sp->what, argtype[sp->type]);
	    }
	}
}

int
getyesno(prompt, normal)
char	*prompt;		/* Prompt string			*/
char	*normal;		/* Default answer "Yes" or "No"		*/
/*
 * Asks a yes/no question.
 */
{
	register char	*lp;

	for (;;) {
	    fprintf(stderr, "%s? (Yes/No) <%s>: ", prompt, normal);
	    if (!getcommand())
		return(FALSE);	/* End of file is very false		*/
	    if (line[0] == EOS)
		strcpy(line, normal);
	    for (lp = line; *lp != EOS; lp++) {
		if (isupper(*lp))
		    *lp = tolower(*lp);
	    }
	    if (strcmp(line, "yes") == 0
	     || strcmp(line, "ye")  == 0
	     || strcmp(line, "y")   == 0)
		return (TRUE);
	    else if (strcmp(line, "no") == 0
	     || strcmp(line, "n")  == 0)
		return (FALSE);
	    fprintf(stderr, "Please answer 'yes' or 'no'.\n");
	}
}

int
getcommand()
/*
 * Read text from keyboard to global line[].
 * Return FALSE on end of file.  Note: rt11 probably trashes the
 * job so you'll never see the end of file.
 */
{
	register char	*lp;
	register char	c;

	lp = line;
	while ((c = getchar()) != EOF) {
	    if (c == EOS || c == '\r')		/* Skip CR or NULL	*/
		continue;			/* Rt11 last block	*/
	    if (c == '\n') {
		/*
		 * Squeeze out trailing blanks
		 */
		while (lp > line && lp[-1] == ' ') lp--;
		*lp = 0;
		return(TRUE);
	    }
	    *lp++ = c;
	}
	line[0] = EOS;
	return(FALSE);
}



/*
 * getredirection() is intended to aid in porting C programs
 * to VMS (Vax-11 C) which does not support '>' and '<'
 * I/O redirection.  With suitable modification, it may
 * useful for other portability problems as well.
 */

#include	<stdio.h>

getredirection(argc, argv)
int		argc;
char		**argv;
/*
 * Process vms redirection arg's.  Exit if any error is seen.
 * If getredirection() processes an argument, argv[i], it is changed
 * to NULL.
 *
 * Warning: do not try to simplify the code for vms.  The code
 * presupposes that getredirection() is called before any data is
 * read from stdin or written to stdout.
 *
 * Normal usage is as follows:
 *
 *	main(argc, argv)
 *	int		argc;
 *	char		*argv[];
 *	{
 *		register int		i;
 *		int			nargs;
 *
 *		getredirection(argc, argv);	** setup redirection
 *		for (nargs = 0, i = 1; i < argc, i++) {
 *		    if (argv[i] == NULL)	** skip if processed
 *			continue;		** by getredirection()
 *		    nargs++;			** here is an argument
 *		    ...				** process argv[i]
 *		}
 *		if (nargs == 0) {		** no arguments given
 *		    ...
 *		}
 *	}
 */
{
#ifdef	vms
	register char		*ap;	/* Argument pointer	*/
	int			i;	/* argv[] index		*/
	int			file;	/* File_descriptor 	*/
	extern int		errno;	/* Last vms i/o error 	*/

	for (i = 1; i < argc; i++) {	/* Do all arguments	*/
	    if (*(ap = argv[i]) == '<') {  /* <file		*/
		if (freopen(++ap, "r", stdin) == NULL) {
		    perror(ap);		/* Can't find file	*/
		    exit(errno);	/* Is a fatal error	*/
		}
		goto erase_arg;		/* Ok, erase argument	*/
	    }
	    else if (*ap++ == '>') {	/* >file or >>file	*/
		if (*ap == '>') {	/* >>file		*/
		    /*
		     * If the file exists, and is writable by us,
		     * call freopen to append to the file (using the
		     * file's current attributes).  Otherwise, create
		     * a new file with "vanilla" attributes as if
		     * the argument was given as ">filename".
		     * access(name, 2) is TRUE if we can write on
		     * the specified file.
		     */
		    if (access(++ap, 2) == 0) {
			if (freopen(ap, "a", stdout) == NULL) {
			    perror(ap);
			    exit(errno);
			}
			else goto erase_arg;
		    }			/* If file accessable	*/
		    else ;		/* Else it's just >file	*/
		}
		/*
		 * On vms, we want to create the file using "standard"
		 * record attributes.  create(...) creates the file
		 * using the caller's default protection mask and
		 * "variable length, implied carriage return"
		 * attributes. dup2() associates the file with stdout.
		 */
		if ((file = creat(ap, 0, "rat=cr", "rfm=var")) == -1
		 || dup2(file, fileno(stdout)) == -1) {
		    perror(ap);		/* Can't create file	*/
		    exit(errno);	/* is a fatal error	*/
		}			/* If '>' creation	*/
erase_arg:	argv[i] = NULL;		/* red. erases argument	*/
	    }				/* If redirection	*/
	}				/* For all arguments	*/
#endif
}

#ifdef	vms
/*
 * VMS error formatter
 */

static char		errname[257];	/* Error text stored here	*/
static $DESCRIPTOR(err, errname);	/* descriptor for error text	*/


char *
vms_etext(errorcode)
int		errorcode;
{
	char		*bp;
	short		errlen;		/* Actual text length		*/

	lib$sys_getmsg(&errorcode, &errlen, &err, &15);
	/*
	 * Trim trailing junk.
	 */
	for (bp = &errname[errlen]; --bp >= errname;) {
	    if (isgraph(*bp) && *bp != ' ')
		break;
	}
	bp[1] = EOS;
	return(errname);
}
#endif

