/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident "$Header: tar.c 1.33 90/08/14 $@(#)tar:tar.c	1.18"

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */


/*
 * This tar program will read/write POSIX tapes of type USTAR.
 * (Unix Standard TAR).  
 *
 * It can be used to write BSD tar tapes by defining BSD_TAR.
 * It can be used to write ATT tar tapes by defining ATT_TAR.
 */

/*
 * Modified for POSIX compliance -- supports mult-volume tar files.
 *
 * This program will support multiple volume tar files.  The files
 * must have an even number of blocks in each file, though the
 * ending block count may be different than the blocking factor
 * used on the rest of the tape.  Note the two types of tapes:
 *
 *  1/2 inch reel to reel tapes:
 *	If a write operation returns a number of bytes written which is
 *	not an even multiple of TBLOCK (512), we back space the tape
 *	over that record and write two file marks.  The next tape
 *	will continue with the standard blocking size.
 *
 *  Cartridge tapes:
 *	The tape drive will insure that we always write in 512 byte
 *	blocks.  The final block of the tape may not be the same blocksize
 *	as the rest of the tape, but this will not be considered a fatal
 *	error.
 *
 * The tapes produced by this tar program should be compatible with
 * other tar programs modified for POSIX conformace.  This should read
 * other POSIX tar tapes (1/2 inch) which write records containing integer
 * 512 byte blocks.  It will not accept records which are not in 512 byte
 * blocks.
 *
 */

/* #define BSD_TAR	1	/* write BSD format tar tapes */
/* #define ATT_TAR	1	/* write ATT format tar tapes */

#ifdef BSD_TAR
#define	ATT_BSD_TAR	1
#endif

#ifdef ATT_TAR
# ifndef ATT_BSD_TAR
# define ATT_BSD_TAR	1
# endif
#endif

#ifdef ATT_TAR
# ifdef BSD_TAR
You cannot build tar with both of these defined.
# endif
#endif

#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/statfs.h>
#include <sys/dir.h>
#include <dirent.h>
#include <sys/errno.h>
#include <tar.h>
#include <memory.h>
#include <string.h>
#include <ctype.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>

/*
 * Next block of stuff is taken from ls after it was modified by Sequent
 * to hash user names and passwords.
 */

#include <utmp.h>
#include <pwd.h>
#include <shadow.h>
#include <grp.h>

#define NMAX    32      /* Max length of user or group name */

#define SCPYN(a, b)     strncpy(a, b, NMAX)

/* Note: MAX_UID and MAX_GID are NOT the max values!!  */

#define MAX_UID         2048    /* size of hash table for user ids */
#define MAX_GID         300     /* size of hash table for group ids */
#define BUCKET_LEN      5       /* how many buckets to walk through */

char u_names[MAX_UID][NMAX + 1];      /* hash buffer for names */
uid_t u_vals[MAX_UID];
char g_names[MAX_GID][NMAX + 1];      /* hash buffer for groups */
gid_t g_vals[MAX_GID];

int   lastu_index;      /* where we cached last uid */
uid_t lastu = 0xfff0;	/* value of last uid */
int   lastg_index;      /* where we cached last gid */
gid_t lastg = 0xfff0;	/* value of last gid */

char *getname_uid();    /* get name given a uid */
char *getname_gid();    /* get group given a gid */
char *make_long_name();	/* build full name from prefix and name */
uid_t getuid_name();	/* get a uid given a name */
gid_t getgid_name();	/* get a gid given a name */
extern struct passwd *getpwuid(), *getpwnam();
extern struct group *getgrgid(), *getgrnam();

/* End of support for hashed user/group names. */

extern void	exit();
extern FILE	*fopen();

extern char	*getcwd();
extern long	lseek();
extern char	*malloc();
extern char	*mktemp();
extern char	*strcat();
extern char	*strcpy();
extern time_t	time();
extern int	errno;
extern char	*sys_errlist[];

#define copy(to, from)	memcpy((to), (from), TBLOCK)


/* major part of a device */
#define	major(x)	((int)(((unsigned)(x)>>16)&0xFFFF))

/* minor part of a device */
#define	minor(x)	((int)((x)&0xFFFF))

/* convert major/minor back into a dev_t */

#define	makedev(x,y)	((dev_t)(((x)<<16) | (y)))

#define writetape(b)	writebuf(b, 1)
#define min(a,b)	((a) < (b) ? (a) : (b))
#define max(a,b)	((a) > (b) ? (a) : (b))

#define INPUT	1
#define OUTPUT	2
int	name_flag;
char	*name_ptr;
char	ttyname[]="/dev/tty";

daddr_t bsrch();
#define TBLOCK		512
#define NBLOCK		20
#define NAMSIZ		100
#define PREFIXLEN	155
union hblock {
	char dummy[TBLOCK];
	struct header {
		char name[NAMSIZ];
		char mode[8];
		char uid[8];
		char gid[8];
		char size[12];
		char mtime[12];
		char chksum[8];
		char typeflag;
		char linkname[NAMSIZ];
		char magic[TMAGLEN];
		char version[TVERSLEN];
		char uname[32];
		char gname[32];
		char devmajor[8];
		char devminor[8];
		char prefix[PREFIXLEN];
	} dbuf;
};

union hblock dblock;
union hblock *tbuf;

struct linkbuf {
	ino_t	inum;
	dev_t	devnum;
	int	count;
	char	pathname[NAMSIZ];
	struct	linkbuf *nextp;
	int	toobig;			/* pathname didn't fit */
} *ihead;

struct stat stbuf;

int	rflag, xflag, vflag, tflag, mt, cflag, mflag, oflag, chrflg;
int	term, chksum, wflag, hflag, recno, first, linkerrok;
int	Bflag;
int	bflag;
int	freemem = 1;
int	nblock = 1;
int	my_uid, did_warn;
int	got_buf;

daddr_t	low;
daddr_t	high;

FILE	*tfile;
char	tname[] = "/tmp/tarXXXXXX";

char	*usefile;
char	*usefil1;
char	magtape[]	= "/dev/rmt/xt0\0\0\0\0";
char	magtap1[]	= "/dev/rmt/ts0\0\0\0\0";

main(argc, argv)
int	argc;
char	*argv[];
{
	char *cp;
	char er_strng[64];			/* old copy of sys_errlist[errno] */
	int speed;
	void onintr(), onquit(), onhup();
						/* fflg & uflg used to reject     */
	int fflg = 0, uflg = 0;			/* multiple keys		  */
	int bflg = 0;				/* bflg used to insure b function */
						/* modifier is specified for read */
						/* of char special device	  */
	if (argc < 2)
		usage();

	tfile = NULL;
	usefile =  magtape;
	usefil1 =  magtap1;
	argv[argc] = 0;
	argv++;
	for (cp = *argv++, speed = 0; *cp; cp++, speed--)
		switch(*cp) {
		case 'f':
			if (bflg) {
				fprintf(stderr, gettxt("tar:1", "tar: function modifier f must precede function modifier b\n"));
				usage();
			}
			fflg++;
			usefile = *argv++;
			if (nblock == 1)
				nblock = 0;
			break;
		case 'c':
			cflag++;
			rflag++;
			break;
		case 'u':
			if (++uflg > 1) {
				fprintf(stderr, gettxt("tar:2", "tar: Only one instance of the u key is allowed\n"));
				usage();
			}
			mktemp(tname);
			if ((tfile = fopen(tname, "w")) == NULL) {
				fprintf(stderr, gettxt("tar:3", "tar: cannot create temporary file (%s)\n"), tname);
				done(1);
			}
			fprintf(tfile, "!!!!!/!/!/!/!/!/!/! 000\n");
			/* FALL THROUGH */
		case 'r':
			rflag++;
			if (nblock != 1 && cflag == 0) {
noupdate:
				fprintf(stderr, gettxt("tar:4", "tar: Blocked tapes cannot be updated (yet)\n"));
				done(1);
			}
			break;
		case 'v':
			vflag++;
			break;
		case 'w':
			wflag++;
			break;
		case 'x':
			xflag++;
			break;
		case 't':
			tflag++;
			break;
		case 'm':
			if (speed != 1)		/* if speed=1 'm' means */
				mflag++;	/* medium speed		*/
			break;
		case '-':
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
			magtape[11] = *cp;
			magtap1[8] = *cp;
			speed = 2;		/* next time will be 1 */

			usefile = magtape;
			usefil1 = magtap1;
			break;
		case 'b':
			bflg++;
			bflag++;
			nblock = atoi(*argv++);
			if (nblock > NBLOCK || nblock <= 0) {
				fprintf(stderr, gettxt("tar:5", "tar: invalid blocksize, %s. (Max %d)\n"),
				*(argv - 1), NBLOCK);
				done(1);
			}
			if (rflag && !cflag)
				goto noupdate;
			break;
		case 'l':
			if (speed == 1) {
				magtape[12] = *cp;
				usefile = magtape;
			}
			else
				linkerrok++;
			break;
		case 'o':
			oflag++;
			break;
		case 'h':
			if (speed == 1) {
				magtape[12] = *cp;
				usefile = magtape;
			} else {
				hflag++;
			}
			break;
		case 'B':
			Bflag++;
			break;
		default:
			fprintf(stderr, gettxt("tar:6", "tar: %c: unknown option\n"), *cp);
			usage();
		}

	if ((xflag + tflag + rflag) > 1 || (uflg + rflag) > 2 || (cflag + rflag) > 2) {
		fprintf(stderr, gettxt("tar:7", "tar: Only one of the c, x, t, u or r keys is allowed\n"));
		usage();
	}
	if (bflg  > 1 || fflg  > 1 || linkerrok > 1 ||
	    mflag > 1 || oflag > 1 || vflag     > 1 || wflag > 1) {
		fprintf(stderr, gettxt("tar:8", "tar: Only one instance each of the b, f, l, m, o, v and w function modifiers is allowed\n"));
		usage();
	}
	if ((linkerrok && xflag) || (linkerrok && tflag)) {
		fprintf(stderr, gettxt("tar:9", "tar: Function modifier l valid only with c, r and u keys\n"));
		usage();
	}
	if (mflag && !xflag) {
		fprintf(stderr, gettxt("tar:10", "tar: Function modifier m valid only with x key\n"));
		usage();
	}
	if (wflag && tflag) {
		fprintf(stderr, gettxt("tar:11", "tar: Function modifier w invalid with t key\n"));
		usage();
	}
	if (oflag && !xflag) {
		fprintf(stderr, gettxt("tar:12", "tar: Function modifier o valid only with x key\n"));
		usage();
	}
	if ((xflag || tflag || (rflag && !cflag)) && fflg && !bflg &&
						(strcmp(usefile, "-") != 0)) {
		if (stat(usefile, &stbuf) == -1) {
			fprintf(stderr, gettxt("tar:13", "tar: stat() of %s failed, %s\n"), usefile, sys_errlist[errno]);
			done(1);
		}
		if ((stbuf.st_mode&S_IFMT) == S_IFCHR)
			chrflg++;
	}

	if (rflag) {
		if (signal(SIGINT, SIG_IGN) != SIG_IGN)
			(void) signal(SIGINT, onintr);
		if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
			(void) signal(SIGHUP, onhup);
		if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
			(void) signal(SIGQUIT, onquit);

		if (strcmp(usefile, "-") == 0) {
			if (cflag == 0) {
				fprintf(stderr, gettxt("tar:14", "tar: Standard output archives available only for the c key\n"));
				done(1);
			}
			mt = dup(1);
			nblock = 1;
		}
		else
		if ((mt = open(usefile,O_RDWR | O_NDELAY)) == -1) {
			strcpy(er_strng, sys_errlist[errno]);
			if (fflg) {
				if (cflag) {	/* rflag && fflg && cflag */
					if ((mt = creat(usefile, 0666)) == -1) {
						fprintf(stderr, gettxt("tar:15", "tar: cannot create %s\ntar: %s\n"),
							usefile, sys_errlist[errno]);
						done(1);
					}
				}
				else {		/* rflag && fflg && !cflag */
					fprintf(stderr, gettxt("tar:16", "tar: cannot open %s\ntar: %s\n"),
						usefile, sys_errlist[errno]);
					done(1);
				}
			}
			else {			/* rflag && !fflg */
				if ((mt = open(usefil1, O_RDWR | O_NDELAY)) == -1) {
					fprintf(stderr, gettxt("tar:17", "tar: cannot open %s, %s\n"),
						usefile, er_strng);
					fprintf(stderr, gettxt("tar:17", "tar: cannot open %s, %s\n"),
						usefil1, sys_errlist[errno]);
					done(1);
				}
				fprintf(stderr, gettxt("tar:67", "using %s\n"), usefil1);
			}
		}
		if (strcmp(usefile, "-") != 0) {
			name_ptr = usefile;
			name_flag = 1;
		}

		if (cflag == 0 && nblock == 0)
			nblock = 1;
		dorep(argv);
	}
	else if (xflag)  {
		if (strcmp(usefile, "-") == 0) {
			mt = dup(0);
			nblock = 1;
			Bflag++;
		}
		else
		if ((mt = open(usefile,O_RDONLY | O_NDELAY)) < 0) {
			if ((mt = open(usefil1,O_RDONLY | O_NDELAY)) < 0) {
				fprintf(stderr, gettxt("tar:18", "tar: cannot open %s\n"), usefile);
				done(1);
			}
			fprintf(stderr, gettxt("tar:67", "using %s\n"), usefil1);
		}
		if (strcmp(usefile, "-") != 0) {
			name_ptr = usefile;
			name_flag = 1;
		}
		doxtract(argv);
	}
	else if (tflag) {
		if (strcmp(usefile, "-") == 0) {
			mt = dup(0);
			nblock = 1;
			Bflag++;
		}
		else
		if ((mt = open(usefile,O_RDONLY | O_NDELAY)) < 0) {
			if ((mt = open(usefil1,O_RDONLY | O_NDELAY)) < 0) {
				fprintf(stderr, gettxt("tar:18", "tar: cannot open %s\n"), usefile);
				done(1);
			}
			fprintf(stderr, gettxt("tar:67", "using %s\n"), usefil1);
		}
		if (strcmp(usefile, "-") != 0) {
			name_ptr = usefile;
			name_flag = 1;
		}
		dotable();
	}
	else
		usage();
	done(0);
		/*NOTREACHED*/
}

usage()
{
/*	fprintf(stderr, "tar: usage  tar -{txruc}[vwfblmh] [tapefile] [blocksize] file1 file2...\n");	*/
	fprintf(stderr, gettxt("tar:19", "tar: usage  tar [-]{txruc}[vwfblm][0-7[lmh]] [tapefile] [blocksize] file1 file2...\n"));	/* For version 1.2 of tar */
	done(1);
}

dorep(argv)
char	*argv[];
{
	register char *cp, *cp2;
        char wdir[MAXPATHLEN], tempdir[MAXPATHLEN], *parent;


	if (!cflag) {
		getdir();
		do {
			passtape();
			if (term)
				done(0);
			getdir();
		} while (!endtape());
		if (tfile != NULL) {
			char buf[200];

			strcat(buf, "sort +0 -1 +1nr ");
			strcat(buf, tname);
			strcat(buf, " -o ");
			strcat(buf, tname);
			(void) sprintf(buf, gettxt("tar:51", "sort +0 -1 +1nr %s -o %s; awk '$1 != prev {print; prev=$1}' %s >%sX;mv %sX %s"),
				tname, tname, tname, tname, tname, tname);
			fflush(tfile);
			system(buf);
			freopen(tname, "r", tfile);
			fstat(fileno(tfile), &stbuf);
			high = stbuf.st_size;
		}
	}

	if (getcwd(wdir, MAXPATHLEN) == (char *) NULL) {
		fprintf(stderr, gettxt("tar:64", "tar: cannot save current directory name\n"));
		done(1);
	}
	while (*argv && ! term) {
		cp2 = *argv;
		parent = wdir;
		for (cp = *argv; *cp; cp++)
			if (*cp == '/')
				cp2 = cp;
		if (cp2 != *argv) {
			*cp2 = '\0';
			if (chdir(*argv)) {
				fprintf(stderr, gettxt("tar:61", "tar: can't change directories to "));
				perror(*argv++);
				continue;
			}
			if (getcwd(tempdir, MAXPATHLEN) == (char *) NULL) {
				fprintf(stderr, gettxt("tar:64", "tar: cannot save current directory name\n"));
			done(1);
			}
			parent = tempdir;
			*cp2 = '/';
			cp2++;
		}
		putfile(*argv++, cp2, parent);
		if (chdir(wdir)) {
			fprintf(stderr, gettxt("tar:50", "tar: cannot change back?: "));
			perror(wdir);
		}
	}
	putempty();
	putempty();
	flushtape();
	if (linkerrok == 1)
		for (; ihead != NULL; ihead = ihead->nextp)
			if (ihead->count != 0)
				fprintf(stderr, gettxt("tar:20", "Missing links to %s\n"), ihead->pathname);
}

endtape()
{
	if (dblock.dbuf.name[0] == '\0') {
		backtape();
		return(1);
	}
	else
		return(0);
}

getdir()
{
	register struct stat *sp;
	int i;

	readtape( (char *) &dblock);
	if (dblock.dbuf.name[0] == '\0')
		return;
	sp = &stbuf;
	sscanf(dblock.dbuf.mode, "%o", &i);
	sp->st_mode = i;
	sscanf(dblock.dbuf.uid, "%o", &i);
	sp->st_uid = i;
	sscanf(dblock.dbuf.gid, "%o", &i);
	sp->st_gid = i;
	sscanf(dblock.dbuf.size, "%lo", &sp->st_size);
	sscanf(dblock.dbuf.mtime, "%lo", &sp->st_mtime);
	sscanf(dblock.dbuf.chksum, "%o", &chksum);
	if (chksum != checksum()) {
		fprintf(stderr, gettxt("tar:21", "directory checksum error\n"));
		done(2);
	}
	if (tfile != NULL)
		fprintf(tfile, "%s %s\n", dblock.dbuf.name, dblock.dbuf.mtime);
}

passtape()
{
	long blocks;
	char buf[TBLOCK];

	if (dblock.dbuf.typeflag == LNKTYPE)
		return;
	blocks = stbuf.st_size;
	blocks += TBLOCK-1;
	blocks /= TBLOCK;

	while (blocks-- > 0)
		readtape(buf);
}

putfile(longname, shortname, parent)
char *longname;
char *shortname;
char *parent;
{
	int infile;
	long blocks;
	char buf[TBLOCK];
	register char *cp, *cp2, *cp3;
	struct direct dbuf;
	int i, j;

	struct stat Statb;
	struct statfs fsb;
	DIR *dir = NULL;
	long savedirloc;
	struct dirent *dp;
        char newparent[NAMSIZ+64];
	char *bigbuf;
	int maxread;
	int hint;

	/*
	 * This routine was modified to use opendir/readdir when a file
	 * is a directory.  This required a minor change in the code
	 * sequence such that a stat may be done on "shortname" before
	 * an open.  This is done to catch the case of a directory.
	 * The loop for copying a directory is much as it was before,
	 * just using readdir to pick up the names.  A change that should
	 * be made is to not close the directory across future calls
	 * to putfile if file descriptors can be kept open.  For a
	 * model of this take a look at "descend()" in "du.c".
	 */

	if (!hflag)
		i = lstat(shortname, &Statb);
	else
		i = stat(shortname, &Statb);
	if (statfs(shortname, &fsb, sizeof(struct statfs), 0) != 0)
		fsb.f_bsize = 0;
	if (i < 0) {
		fprintf(stderr, gettxt("tar:55", "tar: %s: cannot stat file\n"), longname);
		return;
	}

#ifdef DEBUG
	printf("putfile('%s', '%s')\n", longname, shortname);
#endif
	/* Zero the buffer so strings are all null padded to the end. */

	memset(&dblock.dbuf, '\0', sizeof(struct header));

#ifndef ATT_BSD_TAR
	/* Add info to identify as a POSIX (USTAR) tape. */

	strncpy(dblock.dbuf.magic, TMAGIC, TMAGLEN);
	strncpy(dblock.dbuf.version, TVERSION, TVERSLEN);
	/*
	 * initialise numeric fields to leading zero filled octal numbers
	 * in ascii terminated by a null.
	 */
	strcpy(dblock.dbuf.devmajor, "0000000");
	strcpy(dblock.dbuf.devminor, "0000000");
	strcpy(dblock.dbuf.size, "00000000000");
	strcpy(dblock.dbuf.chksum, "000000");
	strcpy(dblock.dbuf.mtime, "00000000000");
	strcpy(dblock.dbuf.uid, "0000000");
	strcpy(dblock.dbuf.gid, "0000000");
#endif /* ! ATT or BSD */

	/* Process directories. */

	if (S_ISDIR(Statb.st_mode)) {
#ifdef DEBUG
		printf("placing DIRTYPE on tape\n");
#endif
		/* Write the directory on the tape. */
#ifndef ATT_BSD_TAR
		/* Insert user and group name. */
		SCPYN(dblock.dbuf.uname, getname_uid(Statb.st_uid));
		SCPYN(dblock.dbuf.gname, getname_gid(Statb.st_gid));
		dblock.dbuf.typeflag = DIRTYPE;
#endif /* ! BSD or ATT */
#ifdef BSD_TAR
		dblock.dbuf.typeflag = AREGTYPE;
#endif
		for (i = 0, cp = buf; *cp++ = longname[i++];)
			;	/* continue */
		*--cp = '/';
		*++cp = 0; 
#ifndef ATT_TAR
		tomodes(&Statb);
		if (putname(buf)) {
			fprintf(stderr, gettxt("tar:56", "tar: %s: file name too long\n"), buf);
			return;
		}
		sprintf(dblock.dbuf.size, "%011lo", 0L);
		sprintf(dblock.dbuf.chksum, "%06o", checksum());
#ifdef DEBUG
		printf("wrote DIRTYPE record with name '%s'\n", buf);
#endif
		(void) writetape((char *)&dblock);
#endif /* ATT */
		/* Now process sub directories. */
		dir = opendir(shortname);
		if (dir == NULL) {
			fprintf(stderr, gettxt("tar:22", "tar: %s: cannot open file\n"), longname);
			return;
		}
		i = 0;
                (void)sprintf(newparent, "%s/%s", parent, shortname);
		if (chdir(shortname)) {
			fprintf(stderr, gettxt("tar:63", "tar: cannot change directories to "));
			perror(shortname);
                        if (chdir(parent) < 0) {
				fprintf(stderr, gettxt("tar:50", "tar: cannot change back?: "));
                                perror(parent);
                        }
			return;
		}
		for (dp = readdir(dir); dp != NULL && !term; dp = readdir(dir)) {
			if (dp->d_ino == 0) {
				i++;
				continue;
			}
			if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) {
				i++;
				continue;
			}
			cp2 = cp;
			cp3 = dp->d_name;
			while (*cp3) {
				*cp2++ = *cp3++;
			}
			*cp2 = '\0';
			savedirloc = telldir(dir);
			closedir(dir);
#ifdef DEBUG
			printf("Calling putfile from %s dir with '%s'\n", shortname, cp);
#endif
                        putfile(buf, cp, newparent);
			if ((dir = opendir(".")) == NULL) {
				fprintf(stderr, gettxt("tar:52", "tar: %s cannot reopen file\n"), longname);
				return;
			}
			seekdir(dir, savedirloc);
		}
		closedir(dir);
		if (chdir(parent)) {
			fprintf(stderr, gettxt("tar:50", "tar: cannot change back?: "));
			perror(parent);
		}
		return;
	}

#ifndef ATT_BSD_TAR
	/* Insert user and group name. */

	SCPYN(dblock.dbuf.uname, getname_uid(Statb.st_uid));
	SCPYN(dblock.dbuf.gname, getname_gid(Statb.st_gid));
#endif /* BSD or ATT */

	/* Take care of symbolic links. */
#ifdef ATT_TAR
	if (S_ISLNK(Statb.st_mode))
		return;
#endif /* ATT */

	if (S_ISLNK(Statb.st_mode)) {
#ifdef DEBUG
		printf("placing SYMTYPE on tape\n");
#endif
		tomodes(&Statb);
		if (putname(longname)) {
			fprintf(stderr, gettxt("tar:56", "tar: %s: file name too long\n"),
		    	longname);
			return;
		}
		/* Require terminating NULL byte. */
		if (Statb.st_size + 1 > NAMSIZ) {
			fprintf(stderr, gettxt("tar:58", "tar: %s: symbolic link too long\n"),
			    	longname);
			return;
		}
		i = readlink(shortname, dblock.dbuf.linkname, NAMSIZ);
		if (i < 0) {
			fprintf(stderr, gettxt("tar:62", "tar: can't read symbolic link "));
			perror(longname);
			return;
		}
		dblock.dbuf.typeflag = SYMTYPE;
		if (vflag)
			fprintf(stderr, gettxt("tar:49", "a %s symbolic link to %s\n"),
		    	longname, dblock.dbuf.linkname);
		sprintf(dblock.dbuf.size, "%011lo", 0L);
		sprintf(dblock.dbuf.chksum, "%06o", checksum());
		(void) writetape((char *)&dblock);
		return;
	}

	/* Take care of block and character special files. */

#ifdef ATT_BSD_TAR
	if (S_ISBLK(Statb.st_mode) || S_ISCHR(Statb.st_mode))
		return;
#endif /* BSD or ATT */

	if (S_ISBLK(Statb.st_mode) || S_ISCHR(Statb.st_mode)) {
		if (S_ISBLK(Statb.st_mode)) {
#ifdef DEBUG
			printf("placing BLKTYPE on tape\n");
#endif
			dblock.dbuf.typeflag = BLKTYPE;
			if (vflag)
				fprintf(stderr, gettxt("tar:46", "a %s block special file\n"),
				    longname);
		} else {
#ifdef DEBUG
			printf("placing CHRTYPE on tape\n");
#endif
			dblock.dbuf.typeflag = CHRTYPE;
			if (vflag)
				fprintf(stderr, gettxt("tar:47", "a %s character special file\n"),
				    longname);
		}
		tomodes(&Statb);
		if (putname(longname)) {
			fprintf(stderr, gettxt("tar:56", "tar: %s: file name too long\n"),
		    	longname);
			return;
		}
		sprintf(dblock.dbuf.devmajor, "%07o", major(Statb.st_rdev));
		sprintf(dblock.dbuf.devminor, "%07o", minor(Statb.st_rdev));
		sprintf(dblock.dbuf.size, "%011lo", 0L);
		sprintf(dblock.dbuf.chksum, "%06o", checksum());
		(void) writetape((char *)&dblock);
		return;
	}

	/* Take care of FIFOs. */
#ifdef ATT_BSD_TAR
	if (S_ISFIFO(Statb.st_mode))
		return;
#endif	/* BSD or ATT */

	if (S_ISFIFO(Statb.st_mode)) {
#ifdef DEBUG
		printf("placing FIFOTYPE on tape\n");
#endif
		dblock.dbuf.typeflag = FIFOTYPE;
		tomodes(&Statb);
		if (putname(longname)) {
			fprintf(stderr, gettxt("tar:56", "tar: %s: file name too long\n"),
		    	longname);
			return;
		}
		sprintf(dblock.dbuf.size, "%011lo", 0L);
		sprintf(dblock.dbuf.chksum, "%06o", checksum());
		if (vflag)
			fprintf(stderr, gettxt("tar:48", "a %s fifo\n"), longname);
		(void) writetape((char *)&dblock);
		return;
	}

	/* Must be regular file. */

	infile = open(shortname,O_RDONLY | O_NDELAY);
	if (infile < 0) {
		fprintf(stderr, gettxt("tar:22", "tar: %s: cannot open file\n"), longname);
		return;
	}

	fstat(infile, &stbuf);

	if (tfile != NULL && checkupdate(longname) == 0) {
		close(infile);
		return;
	}
	if (checkw('r', longname) == 0) {
		close(infile);
		return;
	}

	if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
		fprintf(stderr, gettxt("tar:23", "tar: %s is not a regular file. Not archived\n"), longname);
		close(infile);
		return;
	}

#ifdef ATT_BSD_TAR
	dblock.dbuf.typeflag = AREGTYPE;
#else
	dblock.dbuf.typeflag = REGTYPE;
#endif
	tomodes(&stbuf);

	if (putname(longname)) {
		fprintf(stderr, gettxt("tar:24", "%s: file name too long\n"), longname);
		close(infile);
		return;
	}

	if (stbuf.st_nlink > 1) {
		struct linkbuf *lp;
		int found = 0;

#ifdef DEBUG
		printf("file link count is %d, checking in mem list of links\n",
		    stbuf.st_nlink);
#endif
		for (lp = ihead; lp != NULL; lp = lp->nextp) {
			if (lp->inum == stbuf.st_ino && lp->devnum == stbuf.st_dev) {
				found++;
				break;
			}
		}
		if (found) {
			if (lp->toobig) {
				fprintf(stderr, gettxt("tar:79", "%s: link name too long\n"), longname);
				close(infile);
				return;
			}
			strncpy(dblock.dbuf.linkname, lp->pathname, NAMSIZ);
			dblock.dbuf.typeflag = LNKTYPE;
#ifdef DEBUG
			printf("placing LNKTYPE on tape\n");
#endif
			strcpy(dblock.dbuf.size, "00000000000");

			(void) sprintf(dblock.dbuf.chksum, "%06o", checksum());
			writetape( (char *) &dblock);
			if (vflag) {
				fprintf(stderr, gettxt("tar:45", "a %s "), longname);
				fprintf(stderr, gettxt("tar:25", "link to %s\n"), lp->pathname);
			}
			lp->count--;
			close(infile);
			return;
		}
		else {
#ifdef DEBUG
			printf("adding link to in memory list\n");
#endif
			lp = (struct linkbuf *) malloc(sizeof(*lp));
			if (lp == NULL) {
				if (freemem) {
					fprintf(stderr, gettxt("tar:26", "Out of memory. Link information lost\n"));
					freemem = 0;
				}
			}
			else {
				lp->nextp = ihead;
				ihead = lp;
				lp->inum = stbuf.st_ino;
				lp->devnum = stbuf.st_dev;
				lp->count = stbuf.st_nlink - 1;
				if (strlen(longname) > NAMSIZ)
					lp->toobig = 1;
				else
					lp->toobig = 0;
				strncpy(lp->pathname, longname, NAMSIZ);
			}
		}
	}

#ifdef DEBUG
	if (dblock.dbuf.typeflag == REGTYPE)
		printf("placing REGTYPE on tape\n");
	else if (dblock.dbuf.typeflag == LNKTYPE)
		printf("placing LNKTYPE on tape\n");
	else
		printf("ERROR: unexpected typeflag '%c' on tape\n",
		    dblock.dbuf.typeflag);
#endif
	blocks = (stbuf.st_size + (TBLOCK-1)) / TBLOCK;
	if (vflag) {
		fprintf(stderr, gettxt("tar:45", "a %s "), longname);
		fprintf(stderr, gettxt("tar:27", "%ld blocks\n"), blocks);
	}
	(void) sprintf(dblock.dbuf.chksum, "%06o", checksum());
	hint = writetape( (char *) &dblock);
	if (fsb.f_bsize != 0) {
		maxread = max(fsb.f_bsize, (nblock * TBLOCK));
		if ((bigbuf = malloc((unsigned)maxread)) == 0) {
			maxread = TBLOCK;
			bigbuf = buf;
		}
	} else {
		maxread = TBLOCK;
		bigbuf = buf;
	}

	while ((i = read(infile, bigbuf, min((hint*TBLOCK), maxread))) > 0
	  && blocks > 0) {
		register int nblks;
		nblks = ((i - 1) / TBLOCK) + 1;
		if (nblks > blocks)
			nblks = blocks;
		hint = writebuf(bigbuf, nblks);
		blocks -= nblks;
	}
	close(infile);
	if (bigbuf != buf)
		free(bigbuf);
	if (blocks != 0 || i != 0)
		fprintf(stderr, gettxt("tar:28", "%s: file changed size\n"), longname);
	while (blocks-- >  0)
		putempty();
}



doxtract(argv)
char	*argv[];
{
	long blocks, bytes;
	char buf[TBLOCK];
	char **cp;
	int ofile;
	char *np;
	char *longname;		/* full path name on tape. */
	int is_ustar = 0;	/* is this a POSIX USTAR format tape? */
	mode_t mode;		/* used to build mode for mknod call */
	int m_major;		/* used to pick up mode for mknod call */
	int m_minor;		/* used to pick up mode for mknod call */
	dev_t m_dev;		/* device for mknod call */
	uid_t owner;		/* for chown call */
	gid_t group;		/* for chown call */
	mode_t my_umask;

    my_uid = geteuid();
    if (my_uid == 0)
    	my_umask = umask(0);

    for (;;) {
	getdir();
	if (endtape())
		break;

	/*
	 * See if this is a POSIX tape.
	 * Ignore the version string and assume backward compatibility.
	 */

	if (strncmp(dblock.dbuf.magic, TMAGIC, TMAGLEN) == 0) {
		is_ustar = 1;
		if (strncmp(dblock.dbuf.version, TVERSION, TVERSLEN) != 0) {
			if (!did_warn) {
				fprintf(stderr, gettxt("tar:44", "Warning: ustar not version '%s'\n"),
					TVERSION);
				did_warn++;
			}
		}
	}

	if (is_ustar) {
		longname = make_long_name();
#ifdef DEBUG
		if (longname == (char *) 0)
			printf("is ustar, could not create name\n");
		else
			printf("is ustar, created name '%s'\n", longname);
#endif
		if (longname == (char *) 0) {
			passtape();
			continue;
		}
	} else {
		longname = dblock.dbuf.name;
#ifdef DEBUG
		printf("is NOT ustar, name is '%s'\n", longname);
#endif
	}

	if (*argv == 0)
		goto gotit;

	for (cp = argv; *cp; cp++)
		if (prefix(*cp, dblock.dbuf.name))
			goto gotit;
	passtape();
	continue;

gotit:

	if (checkw('x', longname) == 0) {
		if (is_ustar && (    (dblock.dbuf.typeflag == LNKTYPE)
				  || (dblock.dbuf.typeflag == SYMTYPE)
				  || (dblock.dbuf.typeflag == CHRTYPE)
				  || (dblock.dbuf.typeflag == BLKTYPE)
				  || (dblock.dbuf.typeflag == DIRTYPE)
				  || (dblock.dbuf.typeflag == FIFOTYPE) )) {
			continue;
		} else {
			passtape();
			continue;
		}
	}

	/*
	 * Pick up the owner and group of the file.
	 * If a POSIX tape look up by names on tape if possible.
	 */
	
	if (is_ustar) {
		owner = getuid_name(dblock.dbuf.uname, stbuf.st_uid);
		group = getgid_name(dblock.dbuf.gname, stbuf.st_gid);
#ifdef DEBUG
		printf("found owner '%s' uid %d\n", dblock.dbuf.uname, owner);
		printf("found group '%s' gid %d\n", dblock.dbuf.gname, group);
#endif
	} else {
		owner = stbuf.st_uid;
		group = stbuf.st_gid;
	}

	/*
	 * If this is a record type for a directory we will create the
	 * direcory.  If not, we check to see if a directory record from
	 * a BSD tar tape.  In that case also create directory.
	 */

	np = longname;
	while (*np)
		np++;
	if (is_ustar && ((dblock.dbuf.typeflag == DIRTYPE) ||
			 (dblock.dbuf.typeflag == CONTTYPE)  ) ) {
#ifdef DEBUG
		if (dblock.dbuf.typeflag == DIRTYPE)
			printf("found record DIRTYPE, mode is 0%o\n", stbuf.st_mode);
		else
			printf("found record CONTTYPE, mode is 0%o\n", stbuf.st_mode);
#endif
		if (np[-1] != '/') {	/* add final '/' if it is missing */
			*np = '/';
			np++;
			*np = '\0';
		}
		checkdir(longname, owner, group, stbuf.st_mode);
		continue;
	} else {
		/* Ending slash indicates a directory. */
		/* Probably a BSD tape.                */
		if (np[-1] == '/') {
#ifdef DEBUG
			printf("Was not DIRTYPE, but name ended in / so created dir\n");
#endif
			checkdir(longname, owner, group, stbuf.st_mode);
			continue;
		}
	}

	/* This insures that the path up to a file exists. */

	checkdir(longname, owner, group, 0755);

	/*
	 * Process symbolic links.
	 */

	if (dblock.dbuf.typeflag == SYMTYPE)  {	/* symlink */
#ifdef DEBUG
		printf("found record SYMTYPE\n");
#endif
		/*
		 * only unlink non directories or empty
		 * directories
		 */
		if (rmdir(longname) < 0) {
			if (errno == ENOTDIR)
				unlink(longname);
		}
		if (symlink(dblock.dbuf.linkname, longname)<0) {
			fprintf(stderr, gettxt("tar:57", "tar: %s: symbolic link failed: "),
			    longname);
			perror("");
			continue;
		}
		if (vflag)
			fprintf(stderr, gettxt("tar:69", "x %s symbolic link to %s\n"),
			    longname, dblock.dbuf.linkname);
#ifdef notdef
		/* ignore alien orders */
		chown(longname, stbuf.st_uid, stbuf.st_gid);
		if (mflag == 0)
			setimes(longname, stbuf.st_mtime);
		if (pflag)
			chmod(longname, stbuf.st_mode & 07777);
#endif
		continue;
	}

	/*
	 * Process links.
	 */

	if (dblock.dbuf.typeflag == LNKTYPE) {
#ifdef DEBUG
		printf("found record LNKTYPE\n");
#endif
		unlink(longname);
		if (link(dblock.dbuf.linkname, longname) < 0) {
			fprintf(stderr, gettxt("tar:29", "%s: cannot link\n"), longname);
			continue;
		}
		if (vflag)
			fprintf(stderr, gettxt("tar:30", "%s linked to %s\n"), longname, dblock.dbuf.linkname);
		continue;
	}

	/*
	 * Process regular files.
	 */

	if ( (dblock.dbuf.typeflag == AREGTYPE) ||
	     (dblock.dbuf.typeflag == REGTYPE)) {

#ifdef DEBUG
		if (dblock.dbuf.typeflag == REGTYPE)
			printf("found record REGTYPE\n");
		else
			printf("found record AREGTYPE\n");
#endif
		if ((ofile = creat(longname, stbuf.st_mode & 07777)) < 0) {
			fprintf(stderr, gettxt("tar:31", "tar: %s - cannot create\n"), longname);
			passtape();
			continue;
		}

		if (!oflag)
			chown(longname, owner, group);

		blocks = ((bytes = stbuf.st_size) + TBLOCK-1)/TBLOCK;
		if (vflag)
       		fprintf(stderr, gettxt("tar:32", "x %s, %ld bytes, %ld tape blocks\n"), longname, bytes, blocks);
		for (; blocks > 0; ) {
			register int nread;
			char *bufp;
			register int nwant;

			nwant = NBLOCK * TBLOCK;
			if (nwant > blocks * TBLOCK)
				nwant = blocks * TBLOCK;
			nread = readtbuf(&bufp, nwant);
			if (write(ofile, bufp, (int)min(nread, bytes)) < 0) {
				fprintf(stderr, gettxt("tar:33", "tar: %s: HELP - extract write error\n"), longname);
				done(2);
			}
			bytes -= nread;
			blocks -= (((nread-1)/TBLOCK)+1);
		}
		close(ofile);
		if (mflag == 0) {
			time_t timep[2];

			timep[0] = time(NULL);
			timep[1] = stbuf.st_mtime;
			utime(longname, timep);
		}
		continue;
	}

	/*
	 * Process special files.
	 */
	
	if (is_ustar && ((dblock.dbuf.typeflag == BLKTYPE) ||
	                 (dblock.dbuf.typeflag == CHRTYPE) ||
	                 (dblock.dbuf.typeflag == FIFOTYPE)   )) {
#ifdef DEBUG
		printf("found record BLKTYPE or CHRTYPE or FIFOTYPE\n");
#endif
		mode = stbuf.st_mode;
		if (dblock.dbuf.typeflag == FIFOTYPE) {
			mode |= S_IFIFO;
			if (mknod(longname, mode, 0) == 0) {
			    if (vflag)
				fprintf(stderr, gettxt("tar:68", "x %s fifo\n"), longname);
			} else {
			    fprintf(stderr, gettxt("tar:53", "tar: %s: cannot create fifo\n"), longname);
			}
			if (!oflag)
				chown(longname, owner, group);
			if (mflag == 0) {
				time_t timep[2];

				timep[0] = time(NULL);
				timep[1] = stbuf.st_mtime;
				utime(longname, timep);
			}
			continue;
		}
		if (dblock.dbuf.typeflag == BLKTYPE)
			mode |= S_IFBLK;
		else
			mode |= S_IFCHR;
		sscanf(dblock.dbuf.devmajor, "%o", &m_major);
		sscanf(dblock.dbuf.devminor, "%o", &m_minor);
		m_dev = makedev(m_major, m_minor);
		if (mknod(longname, mode, m_dev) == 0) {
		    if (vflag) {
		    	if (dblock.dbuf.typeflag == BLKTYPE)
			    fprintf(stderr, gettxt("tar:70", "x %s, block special file\n"),
				longname);
		        else
			    fprintf(stderr, gettxt("tar:71", "x %s, character special file\n"),
				longname);
		    }
		} else {
		    if (dblock.dbuf.typeflag == BLKTYPE)
			fprintf(stderr, gettxt("tar:66", "tar: x %s: cannot create block special file\n"),
			    longname);
		    else
			fprintf(stderr, gettxt("tar:65", "tar: x %s: cannot create  character special file\n"),
			    longname);
		}
		if (!oflag)
			chown(longname, owner, group);
		if (mflag == 0) {
			time_t timep[2];

			timep[0] = time(NULL);
			timep[1] = stbuf.st_mtime;
			utime(longname, timep);
		}
		continue;
	}

	/* Don't know what type of record this is! */

	fprintf(stderr, gettxt("tar:59", "tar: %s: unknown what type of file this is\n"), longname);
	passtape();
    }


    if (my_uid == 0)
    	(void) umask(my_umask);
}

dotable()
{
	char buf[PREFIXLEN+1];		/* assumes PREFIXLEN > NAMSIZ */

	for (;;) {
		getdir();
		if (endtape())
			break;
		if (vflag)
			longt(&stbuf);
		memset(buf, '\0', sizeof(buf));
		if ('\0' != dblock.dbuf.prefix[0]) {
			strncpy(buf, dblock.dbuf.prefix, PREFIXLEN);
			printf("%s/", buf);
		}
		strncpy(buf, dblock.dbuf.name, NAMSIZ);
		printf("%s", buf);
		if (dblock.dbuf.typeflag == LNKTYPE) {
			memset(buf, '\0', sizeof(buf));
			strncpy(buf, dblock.dbuf.linkname, NAMSIZ);
			printf(gettxt("tar:34", " linked to %s"), buf);
		}

		printf("\n");
		passtape();
	}
}

putempty()
{
	char buf[TBLOCK];
	char *cp;

	for (cp = buf; cp < &buf[TBLOCK]; )
		*cp++ = '\0';
	writetape(buf);
}

longt(st)
register struct stat *st;
{
	register char *cp;
	char *ctime();

	pmode(st);
	printf("%6u%6u", st->st_uid, st->st_gid);
	printf("%9lu", st->st_size);
	cp = ctime(&st->st_mtime);
	printf(" %-12.12s %-4.4s ", cp+4, cp+20);
}

#define	SUID	04000
#define	SGID	02000
#define	ROWN	0400
#define	WOWN	0200
#define	XOWN	0100
#define	RGRP	040
#define	WGRP	020
#define	XGRP	010
#define	ROTH	04
#define	WOTH	02
#define	XOTH	01
#define	STXT	01000
int	m1[] = { 1, ROWN, 'r', '-' };
int	m2[] = { 1, WOWN, 'w', '-' };
int	m3[] = { 2, SUID, 's', XOWN, 'x', '-' };
int	m4[] = { 1, RGRP, 'r', '-' };
int	m5[] = { 1, WGRP, 'w', '-' };
int	m6[] = { 2, SGID, 's', XGRP, 'x', '-' };
int	m7[] = { 1, ROTH, 'r', '-' };
int	m8[] = { 1, WOTH, 'w', '-' };
int	m9[] = { 2, STXT, 't', XOTH, 'x', '-' };

int	*m[] = { m1, m2, m3, m4, m5, m6, m7, m8, m9};

pmode(st)
register struct stat *st;
{
	register int **mp;

	for (mp = &m[0]; mp < &m[9];)
		select(*mp++, st);
}

select(pairp, st)
int *pairp;
struct stat *st;
{
	register int n, *ap;

	ap = pairp;
	n = *ap++;
	while (--n>=0 && (st->st_mode&*ap++)==0)
		ap++;
	printf("%c", *ap);
}

checkdir(name, owner, group, mode)
	register char *name;
	uid_t owner;
	gid_t group;
	int   mode;
{
	register char *cp=name;
	int i, pid;

#ifdef DEBUG
	printf("checkdir('%s', o %d, g %d, m 0%o\n)", name, owner, group, mode);
#endif

	if (my_uid != 0) {
		mode |= 0700;
#ifdef DEBUG
		printf("checkdir: my_uid not 0, so set mode to 0%o\n", mode);
#endif
	}
	for (cp++; *cp; cp++) {	/* start at *name++ */
		if (*cp == '/') {
			*cp = '\0';
			if (access(name, 01) < 0) {
#ifdef DEBUG
				printf("checkdir: making dir '%s', mode = 0%o\n",
				   name,  mode & 07777);
#endif
				if (mkdir(name, mode & 07777) != 0) {
#ifdef DEBUG
					perror("mkdir");
#endif
					fprintf(stderr, gettxt("tar:54", "tar: %s: cannot create\n"), name);
				}
				if (!oflag)
					chown(name, owner, group);
			}
			*cp = '/';
		}
	}
}

void
onintr()
{
	(void) signal(SIGINT, SIG_IGN);
	term++;
}

void
onquit()
{
	(void) signal(SIGQUIT, SIG_IGN);
	term++;
}

void
onhup()
{
	(void) signal(SIGHUP, SIG_IGN);
	term++;
}

tomodes(sp)
register struct stat *sp;
{
	register char *cp;

#ifdef NOTDEF		/* now done in putfile */
	for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++)
		*cp = '\0';
#endif
	(void) sprintf(dblock.dbuf.mode, "%06o ", sp->st_mode & 07777);
	(void) sprintf(dblock.dbuf.uid, "%06o ", sp->st_uid);
	(void) sprintf(dblock.dbuf.gid, "%06o ", sp->st_gid);
	(void) sprintf(dblock.dbuf.size, "%011lo ", sp->st_size);
	(void) sprintf(dblock.dbuf.mtime, "%011lo ", sp->st_mtime);
}

checksum()
{
	register i;
	register char *cp;

	for (cp = dblock.dbuf.chksum; cp < &dblock.dbuf.chksum[sizeof(dblock.dbuf.chksum)]; cp++)
		*cp = ' ';
	i = 0;
	for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++)
		i += *cp;
	return(i);
}

checkw(c, name)
char *name;
{
	if (wflag) {
		printf("%c ", c);
		if (vflag)
			longt(&stbuf);
		printf(gettxt("colon:3", "%s: "), name);
		if (response() == 'y'){
			return(1);
		}
		return(0);
	}
	return(1);
}

response()
{
	int c;

	c = getchar();
	if (c != '\n')
		while (getchar() != '\n');
	else c = 'n';
	return(c);
}

checkupdate(arg)
char	*arg;
{
	char name[100];
	long	mtime;
	daddr_t seekp;
	daddr_t	lookup();

	rewind(tfile);
	for (;;) {
		if ((seekp = lookup(arg)) < 0)
			return(1);
		fseek(tfile, seekp, 0);
		fscanf(tfile, "%s %lo", name, &mtime);
		if (stbuf.st_mtime > mtime)
			return(1);
		else
			return(0);
	}
}

done(n)
{
	unlink(tname);
	exit(n);
}

prefix(s1, s2)
register char *s1, *s2;
{
	while (*s1)
		if (*s1++ != *s2++)
			return(0);
	if (*s2)
		return(*s2 == '/');
	return(1);
}

#define	N	200
int	njab;
daddr_t
lookup(s)
char *s;
{
	register i;
	daddr_t a;

	for(i=0; s[i]; i++)
		if(s[i] == ' ')
			break;
	a = bsrch(s, i, low, high);
	return(a);
}

daddr_t
bsrch(s, n, l, h)
daddr_t l, h;
char *s;
{
	register i, j;
	char b[N];
	daddr_t offset, offset1;

	njab = 0;

loop:
	if(l >= h)
		return((daddr_t)-1);
	offset = l + (h-l)/2 - N/2;
	if(offset < l)
		offset = l;
	fseek(tfile, offset, 0);
	fread(b, 1, N, tfile);
	njab++;
	for(i=0; i<N; i++) {
		if(b[i] == '\n')
			break;
		offset++;
	}
	if(offset >= h)
		return((daddr_t)-1);
	offset1 = offset;
	j = i;
	for(i++; i<N; i++) {
		offset1++;
		if(b[i] == '\n')
			break;
	}
	i = cmp(b+j, s, n);
	if(i < 0) {
		h = offset;
		goto loop;
	}
	if(i > 0) {
		l = offset1;
		goto loop;
	}
	return(offset);
}

cmp(b, s, n)
char *b, *s;
{
	register i;

	if(b[0] != '\n')
		exit(2);
	for(i=0; i<n; i++) {
		if(b[i+1] > s[i])
			return(-1);
		if(b[i+1] < s[i])
			return(1);
	}
	return(b[i+1] == ' '? 0 : -1);
}

readtape(buffer)
	char *buffer;
{
	char *bufp;

	if (! got_buf)
		getbuf();

	(void) readtbuf(&bufp, TBLOCK);
	memcpy(buffer, bufp, TBLOCK);
	return(TBLOCK);
}

readtbuf(bufpp, size)
	char **bufpp;
	int size;
{
	int i, j;

	if (! got_buf)
		getbuf();

	if (recno >= nblock || first == 0) {
		if (first == 0 && nblock == 0)
			j = NBLOCK;
		else
			j = nblock;
		/*
		 * Expected read is (TBLOCK*j) bytes.
		 * Byte count must be even multiple of TBLOCK (512).
		 * Returns:
		 */
re_read:

		i = bread(mt, tbuf, TBLOCK*j);	/* do the read */

		if (i  == -1) {
			if ((errno == ENOSPC) || (errno == ENXIO)) {
				newreel(INPUT);
				first = 0;
				goto re_read;
			} else {
				/* a "real" error, give up */
				fprintf(stderr, gettxt("tar:37", "Tar: tape read error, %s\n"), sys_errlist[errno]);
				if (chrflg)
					fprintf(stderr, gettxt("tar:38", "Tar: warning, blocksize not specified, defaulted to %d\n"), nblock);
				done(3);
			}
		} else if (i == 0) {		/* no data */
			newreel(INPUT);
			first = 0;
			goto re_read;
		} else if (i < TBLOCK*j) {	/* less data than requested */
			first = 0;
			/*
			 * By setting this flag, we will re-evaluate
			 * the block size in code below.
			 */
		} else if (i > TBLOCK*j) {	/* ERROR - can't happen */
			fprintf(stderr, gettxt("tar:37", "Tar: tape read error, %s\n"), sys_errlist[errno]);
			if (chrflg)
				fprintf(stderr, gettxt("tar:38", "Tar: warning, blocksize not specified, defaulted to %d\n"), nblock);
			done(3);
		}				/* must have been desired read size */
		if (first == 0) {
			if ((i % TBLOCK) != 0) {
				fprintf(stderr, gettxt("tar:39", "Tar: tape blocksize error\n"));
				done(3);
			}
			i /= TBLOCK;
			if (rflag && i != 1) {
				fprintf(stderr, gettxt("tar:40", "Tar: Cannot update blocked tapes (yet)\n"));
				done(4);
			}
			if (i != nblock && i != 1) {
				fprintf(stderr, gettxt("tar:41", "Tar: blocksize = %d\n"), i);
			}
			nblock = i;
		}
		recno = 0;
	}
	first = 1;

	if (size > ((nblock-recno)*TBLOCK))
		size = (nblock - recno)*TBLOCK;
	*bufpp = (char *) &tbuf[recno];
	recno += (size / TBLOCK);
	return(size);
}

/*
 * bread - fill the buffer
 *
 * If the "-B" option was not used this translates to a standard
 * read() system call.
 *
 * If the "-B" was used it allows the buffer to be filled with
 * multiple reads.  Intent is to support transferrs over a network
 * via a pipe.
 */

bread(fd, buf, size)
        int fd;
        char *buf;
        int size;
{
        int count;
        static int lastread = 0;

        if (!Bflag)
                return (read(fd, buf, size));

        for (count = 0; count < size; count += lastread) {
                lastread = read(fd, buf, size - count);
                if (lastread <= 0) {
                        if (count > 0)
                                return (count);
                        return (lastread);
                }
                buf += lastread;
        }
        return (count);
}


writebuf(buffer, n)
	char *buffer;
	int n;
{
	int i;
	int num_blks;

	if (! got_buf)
		getbuf();
	first = 1;
	if (nblock == 0)
		nblock = 1;
re_write:
	if (recno >= nblock) {
		i = write(mt, tbuf, TBLOCK*nblock);
		if (i < 0) {				/* write error */
			if ((errno == ENOSPC) || (errno == ENXIO)) {
				newreel(OUTPUT);
				goto re_write;
			} else {
				fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
				done(2);
			}
		} else if (i == 0) {			/* no data */
			newreel(OUTPUT);
			goto re_write;
		} else if (i < TBLOCK*nblock) {		/* insufficient transfer */
			if ((i % TBLOCK) != 0) {
				if (mt_backup() == 0)
					goto re_write;
				fprintf(stderr, gettxt("tar:72", "Tar: tape write error -- blocksize\n"));
				done(2);
			}
			num_blks = i / TBLOCK;		/* how much did we just write */
			recno -= num_blks;		/* adjust blocks */
			smemcpy(tbuf, tbuf[num_blks], i); /* shift data */
			newreel(OUTPUT);
			goto re_write;
		} else if (i > TBLOCK*nblock) {		/* ERROR - can't happen */
			fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
			done(2);
		}
			/* else it is a complete write */
		recno = 0;
	}

	/* 
	 * Special case: We have an empty tape buffer, and the
	 * users data size is >= the tape block size:  Avoid
	 * the copy and dma direct to tape.  BIG WIN.  Add the
	 * residual to the tape buffer.
	 */

	while (recno == 0 && n >= nblock) {
		i = write(mt, buffer, TBLOCK*nblock);
		if (i < 0) {				/* write error */
			if ((errno == ENOSPC) || (errno == ENXIO)) {
				newreel(OUTPUT);
				goto re_write;
			} else {
				fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
				done(2);
			}
		} else if (i == 0) {			/* no data */
			newreel(OUTPUT);
			goto re_write;
		} else if (i < TBLOCK*nblock) {		/* insufficient transfer */
			if ((i % TBLOCK) != 0) {
				if (mt_backup() == 0)
					goto re_write;
				fprintf(stderr, gettxt("tar:72", "Tar: tape write error -- blocksize\n"));
				done(2);
			}
			num_blks = i / TBLOCK;		/* how much did we just write */
			n -= num_blks;			/* adjust data size;
			buffer += (num_blks * TBLOCK);	/* shift pointer to data start */
			newreel(OUTPUT);
			continue;
		} else if (i > TBLOCK*nblock) {		/* ERROR - can't happen */
			fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
			done(2);
		}
		/* else it is a complete write */
		n -= nblock;				/* adjust data size */
		buffer += (nblock*TBLOCK);		/* shift pointer to data start */
	}

	while (n-- > 0) {
		copy(&tbuf[recno++], buffer);
		buffer += TBLOCK;
		if (recno >= nblock) {
			i = write(mt, tbuf, TBLOCK*nblock);
			if (i < 0) {				/* write error */
				if ((errno == ENOSPC) || (errno == ENXIO)) {
					newreel(OUTPUT);
					goto re_write;
				} else {
					fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
					done(2);
				}
			} else if (i == 0) {			/* no data */
				newreel(OUTPUT);
				goto re_write;
			} else if (i < TBLOCK*nblock) {		/* insufficient transfer */
				if ((i % TBLOCK) != 0) {
					if (mt_backup() == 0)
						goto re_write;
					fprintf(stderr, gettxt("tar:72", "Tar: tape write error -- blocksize\n"));
					done(2);
				}
				num_blks = i / TBLOCK;		/* how much did we just write */
				recno -= num_blks;		/* adjust blocks */
				smemcpy(tbuf, tbuf[num_blks], i); /* shift data */
				newreel(OUTPUT);
				goto re_write;
			} else if (i > TBLOCK*nblock) {		/* ERROR - can't happen */
				fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
				done(2);
			}
			/* else it is a complete write */
			recno = 0;
		}
	}

	return(nblock - recno);
}

/*
 * This will grab space for read/write buffer.  To insure alligned
 * buffer valloc is used.  This insures we can do I/O to raw devices.
 * At some time might want to do fstatfs and find out how what
 * device block size is.  This would change size valloc'ed.  See the
 * Dynix 3.0 code.
 */

getbuf()
{
	struct statfs fsb;
	struct stat sb;

	if ( ((nblock == 0) || (nblock == 1)) && (bflag == 0)) {
	    if (fstat(mt, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) == S_IFCHR) {
		    nblock = NBLOCK;
		} else {
		    if (fstatfs(mt, &fsb, sizeof(struct statfs), 0) == 0) {
			nblock = fsb.f_bsize / TBLOCK;
			if (nblock == 0)
				nblock = NBLOCK;
		    }
		}
	    }
	}


	tbuf = (union hblock *)valloc((unsigned)nblock*TBLOCK);
	if (tbuf == NULL) {
	    fprintf(stderr, gettxt("tar:60", "tar: block size %d too big, can't get memory\n"),
		nblock);
	    done(1);
	}
	got_buf = 1;
}

backtape()
{
	lseek(mt, (long) -TBLOCK, 1);
	if (recno >= nblock) {
		recno = nblock - 1;
		if (read(mt, tbuf, TBLOCK*nblock) < 0) {
			fprintf(stderr, gettxt("tar:43", "Tar: tape read error after seek, %s\n"), sys_errlist[errno]);
			done(4);
		}
		lseek(mt, (long) -TBLOCK, 1);
	}
}

/*
 * Back up a tape.  This is used when a write returns a value which is not
 * a multiple of the block size (512 bytes).  We back up over the record
 * and request a new tape.  Not all tape drives will support backing up,
 * but the ones that don't (such as cartridge) won't write odd number of bytes.
 */

mt_backup()
{
	struct mtop mt_com;

	mt_com.mt_op = MTBSR;
	mt_com.mt_count = 1;

	if (ioctl(mt, MTIOCTOP, &mt_com) < 0)	/* backspace */
		return(1);
	
	mt_com.mt_op = MTWEOF;
	mt_com.mt_count = 2;

	if (ioctl(mt, MTIOCTOP, &mt_com) < 0)	/* write eof */
		return(1);

	newreel(OUTPUT);	/* request a new tape */

	return(0);		/* show success */
}

flushtape()
{
	int i;
	int num_blks;

re_flush:
	i = write(mt, tbuf, TBLOCK*nblock);	/* now write it out */

	if (i < 0) {				/* write error */
		if ((errno == ENOSPC) || (errno == ENXIO)) {
			newreel(OUTPUT);
			goto re_flush;
		} else {
			fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
			done(2);
		}
	} else if (i == 0) {			/* no data */
		newreel(OUTPUT);
		goto re_flush;
	} else if (i < TBLOCK*nblock) {		/* insufficient transfer */
		if ((i % TBLOCK) != 0) {
			if (mt_backup() == 0)
				goto re_flush;
			fprintf(stderr, gettxt("tar:72", "Tar: tape write error -- blocksize\n"));
			done(2);
		}
		num_blks = i / TBLOCK;		/* how much did we just write */
		recno -= num_blks;		/* adjust blocks */
		smemcpy(tbuf, tbuf[num_blks], i); /* shift data */
		newreel(OUTPUT);
		goto re_flush;
	} else if (i > TBLOCK*nblock) {		/* ERROR - can't happen */
		fprintf(stderr, gettxt("tar:42", "Tar: tape write error\n"));
		done(2);
	}
		/* else it is a complete write */
		/* that's it -- just return */
}

/* Routine to allow tape change -- multi-volume support. */

newreel(io)
	int io;
{
	static char tapename[BUFSIZ];	/* hold copy of tape name */
	static char newbuf[BUFSIZ];	/* enter new tape name */
	static FILE *Rtty;
	char *p;

	close(mt);

	/* If I/O was from stdin/stdout we don't support multi-volme */

	if (strcmp(usefile, "-") == 0) {
		if (io == INPUT)
			done(3);
		else
			done(2);
	}

re_reel:
	newbuf[0] = '\0';

	if (io == INPUT)
		fprintf(stderr, gettxt("tar:73", "\007\nEnd of media on input.\n"));
	else
		fprintf(stderr, gettxt("tar:74", "\007\nEnd of media on output.\n"));
	if (name_flag)
		fprintf(stderr, gettxt("tar:75", "Enter return only to re-use '%s'\n"), name_ptr);
	fprintf(stderr, gettxt("tar:76", "Enter new device name, or 'q' to quit: "));
	fflush(stderr);


	/* open terminal for I/O first time we prompt for new tape name */

	if (Rtty == NULL) {
		if ((Rtty = fopen(ttyname, "r")) == NULL) {
			fprintf(stderr, gettxt("tar:77", "Cannot open '%s' for reading\n"), ttyname);
			if (io == INPUT)
				done(3);
			else
				done(2);
		}
	}

	fgets(newbuf, sizeof(newbuf), Rtty);		/* request new tape name */
	p = newbuf;					/* and trim trailing newline */
	while (*p && *p != '\n' && p < &newbuf[BUFSIZ])
		p++;
	if (*p == '\n')
		*p = '\0';

	if ((newbuf[0] == 'q' || newbuf[0] == 'Q') && newbuf[1] == '\0') {
			if (io == INPUT)
				done(3);
			else
				done(2);
	}

	if (newbuf[0] == '\0' || newbuf[0] == '\r') {
		if (name_flag)				/* use previous name */
			strcpy(tapename, name_ptr);
		else
			goto re_reel;
	} else
		strcpy(tapename, newbuf);		/* use new name */

	if ((mt = open(tapename, O_RDWR | O_NDELAY)) == -1) {
		fprintf(stderr, gettxt("tar:78", "Cannot open '%s', %s\n"),
			tapename, sys_errlist[errno]);
		name_flag = 0;
		goto re_reel;
	}

	name_ptr = tapename;
	name_flag = 1;
}

/*
 * Replaced by memcopy.  Large speed improvement.
 *
 * copy(to, from)
 * register char *to, *from;
 * {
 * 	register i;
 * 
 * 	i = TBLOCK;
 * 	do {
 * 		*to++ = *from++;
 * 	} while (--i);
 * }
 * 
 */

/*
 * The following routines are used to support the hashed user/group names.
 */


/*
 * getname_uid - Get a user name given user id.
 *
 */

char *
getname_uid(uid)
uid_t uid;
{
	register int i, s, h;
	struct passwd *pw;

	/*
	 * Typical case is that we are called with the same uid as in
	 * our last call so we keep possibility of a quick return of
	 * the most recent user.
	 */

	if (   (uid == lastu)
	    && (u_vals[lastu_index] == uid)
	    && (u_names[lastu_index][0] != '\0') )
		return(&u_names[lastu_index][0]);

	/* Well we didn't have it.  So see if it is in the hash table. */

	h = uid % MAX_UID;

	for (i = 0, s = h; i < BUCKET_LEN; i++, s++) {
		if (s >= MAX_UID)
			s = 0;
		if ( (u_vals[s] == uid) && (u_names[s][0] != '\0') )
			return(&u_names[s][0]);
		if (u_names[s][0] == '\0')
			break;
	}

	/*
	 * Not in the table at all.  So we will have to look it up
	 * and insert it.  If index i from the for loop is less than
	 * the number of alternate slots we can use we want to insert
	 * at that index (the next free one).  If all were filled then
	 * we overwrite the slot found by direct hash.
	 */
	
	if ((pw = getpwuid(uid)) == (struct passwd *) NULL)
		return("");

	if (i == BUCKET_LEN)
		s = h;

	SCPYN(u_names[s], pw->pw_name);
	u_vals[s] = uid;
	lastu_index = s;
	lastu = uid;

	return(&u_names[s][0]);
}

/*
 * getname_gid - Get a group name given group id.
 *
 */

char *
getname_gid(gid)
gid_t gid;
{
	register int i, s, h;
	struct group *gr;

	/*
	 * Typical case is that we are called with the same gid as in
	 * our last call so we keep possibility of a quick return of
	 * the most recent group.
	 * A gid may not have an entry, this too is hashed.
	 */

	if ( (gid == lastg) && (g_vals[lastg_index] == gid)  )
		return(&g_names[lastg_index][0]);

	/* Well we didn't have it.  So see if it is in the hash table. */

	h = gid % MAX_GID;

	for (i = 0, s = h; i < BUCKET_LEN; i++, s++) {
		if (s >= MAX_GID)
			s = 0;
		if ( g_vals[s] == 0)
			break;
		if ( g_vals[s] == gid )
			return(&g_names[s][0]);
	}

	/*
	 * Not in the table at all.  So we will have to look it up
	 * and insert it.  If index i from the for loop is less than
	 * the number of alternate slots we can use we want to insert
	 * at that index (the next free one).  If all were filled then
	 * we overwrite the slot found by direct hash.
	 */

	if ((gr = getgrgid(gid)) == (struct group *) NULL)
		g_names[s][0] = '\0';
	else {
		SCPYN(g_names[s], gr->gr_name);
	}

	if (i == BUCKET_LEN)
		s = h;

	g_vals[s] = gid;
	lastg_index = s;
	lastg = gid;

	return(&g_names[s][0]);
}

/*
 * putname
 *
 * Put the given name in dbuf, breaking into name/prefix if necessary.
 * Return 0 for success, 1 if the name is too long.
 */

putname(s)
	char *s;
{
	char *p, *q;
	char buf[PREFIXLEN + 1 + NAMSIZ + 1];	/*  prefix/name\0	*/
	int n, i;

	n = strlen(s);

	/*
	 * The typical case is that name fits in allocated buffer and we
	 * just place it in buffer.
	 */

	if (n <= NAMSIZ) {
		strncpy(dblock.dbuf.name, s, NAMSIZ);
		return(0);
	}

	/*
	 * We are going to make a local copy, make sure it will fit.
	 * Even if it does the break might not come at the correct
	 * spot to allow placing in the buffer.
	 */

	if (n > sizeof(buf))
		return(1);

	strncpy(buf, s, NAMSIZ + PREFIXLEN + 1);

	/*
	 * Start "p" at the end of the string and work back.  Keep "q"
	 * jumpping back to the beginning of paths until a name no
	 * longer fits in the buffer allocated for the "suffix".
	 * Let i count the number of characters.
	 */

	p = &buf[n-1];		/* last char in name */
	q = 0;
	i = 0;

	while ((p >= &buf[0]) && (i <= NAMSIZ)) {
		if (*p == '/')
			q = p;
		i++;
		p--;
	}

	if (NULL == q && i > NAMSIZ) {
		/* no slashes, filename too long	*/
		return 1;
	}
	
	p = q;
	q++;
	if (q != & buf[n]) {
		strncpy(dblock.dbuf.name, q, NAMSIZ);
	} else {
		/* we have "155-chars/"	*/
		return 1;
	}

	*p = '\0';

	n = strlen(buf);

	if (n == 0) {
		/* we had "/100-chars"	*/
		return(1);
	}
	
	if (n > PREFIXLEN)
		return(1);
	
	strncpy(dblock.dbuf.prefix, buf, PREFIXLEN);
	return(0);
}

/*
 * make_long_name
 *
 * Create a possibly long name from the information in "prefix" and "name".
 */

char *
make_long_name()
{
	static char buf[NAMSIZ + PREFIXLEN + 8];

	memset(buf, '\0', NAMSIZ + PREFIXLEN + 4);
	if (dblock.dbuf.prefix[0] != '\0') {
		strncpy(buf, dblock.dbuf.prefix, PREFIXLEN);
		strcat(buf, "/");
		strncat(buf, dblock.dbuf.name, NAMSIZ);
		return(buf);
	} else if (dblock.dbuf.name[0] != '\0') {
		strncat(buf, dblock.dbuf.name, NAMSIZ);
		return(buf);
	} else
		return((char *) 0);
}

/*
 * getgid_name(name, alt_gid)
 *
 * Given a name return the group id for it.  If none can be found
 * then use the alternate given as "alt_gid".
 */

gid_t
getgid_name(name, alt_gid)
	char *name;
	gid_t alt_gid;
{
	static char last_group[NMAX + 1];
	static gid_t last_gid;
	static int first_time = 0;
	struct group *gr;

	if (!first_time) {
		if (strncmp(name, last_group, NMAX) == 0)
			return(last_gid);
	}

	gr = getgrnam(name);
	if (gr == (struct group *) NULL)
		return(alt_gid); 
	
	strncpy(last_group, name, NMAX);
	last_gid = gr->gr_gid;
	first_time = 1;
	return(last_gid);
}



/*
 * getuid_name(name, alt_uid)
 *
 * Given a name return the user id for it.  If none can be found
 * then use the alternate given as "alt_uid".
 */

gid_t
getuid_name(name, alt_uid)
	char *name;
	uid_t alt_uid;
{
	static char last_user[NMAX + 1];
	static gid_t last_uid;
	static int first_time = 0;
	struct passwd *pw;

	if (!first_time) {
		if (strncmp(name, last_user, NMAX) == 0)
			return(last_uid);
	}

	pw = getpwnam(name);
	if (pw == (struct passwd *) NULL)
		return(alt_uid); 
	
	strncpy(last_user, name, NMAX);
	last_uid = pw->pw_uid;
	first_time = 1;
	return(last_uid);
}
