Ultrix-3.1/src/cmd/tar/readtape.c

Compare this file to the similar file:
Show the results in this format:


/**********************************************************************
 *   Copyright (c) Digital Equipment Corporation 1984, 1985, 1986.    *
 *   All Rights Reserved. 					      *
 *   Reference "/usr/src/COPYRIGHT" for applicable restrictions.      *
 **********************************************************************/

#ifndef lint
static	char	*sccsid = "@(#)readtape.c	3.1	(ULTRIX)	9/5/87";
#endif	lint

/**/
/*
 *
 *	File name:
 *
 *		readtape.c
 *
 *	Source file description:
 *
 *		Mainly functions concerned with reading
 *		from the input archive by the "tar" logic.
 *
 *	Functions:
 *
 *	Usage:	n/a
 *
 *	Compile:
 *
 *	Modification history:
 *	~~~~~~~~~~~~~~~~~~~~
 *
 *	revision			comments
 *	--------	-----------------------------------------------
 *	I		19-Dec-85 rjg/
 *			Create orginal version.
 *	
 */
#include "tar.h"

/*.sbttl backtape() */

/* Function:
 *
 *	backtape
 *
 * Function Description:
 *
 *	Backspace tape (if possible). Part of the "update" function.
 *
 * Arguments:
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
backtape()
{
/*------*\
  Locals
\*------*/

static	int	mtdev = 1;
static	struct	mtop	lmtop = {MTBSR, 1};

/*------*\
   Code
\*------*/

if (mtdev == 1)
	mtdev = ioctl(mt, MTIOCGET, &mtsts);

if (mtdev == 0) {
	if (ioctl(mt, MTIOCTOP, &lmtop) < 0) {
		fprintf(stderr, "%s: Archive backspace error\n", progname);
		done(FAIL);
	}
} else

	lseek(mt, (SIZE_L) -TBLOCK * nblock, 1);

recno--;

}/*E backtape() */

/*.sbttl bread() */

/* Function:
 *
 *	bread
 *
 * Function Description:
 *
 *	Buffer / block read
 *
 * Arguments:
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
bread(fd, buf, size)
	FILE_D	fd;
	STRING_POINTER	buf;
	SIZE_I	size;
{
/*------*\
  Locals
\*------*/

	SIZE_I	bytes;
	SIZE_I	count;
	SIZE_I	lastread = 0;

/*------*\
   Code
\*------*/

NEWVOL:
/**/
if (!Bflag) {
	bytes = read(fd, buf, size);

	if ((bytes <= 0) && (errno == ENOSPC)) {
		if ((ioctl(mt, MTIOCGET, &mtsts) < 0) ||
			size_of_media[CARCH])
			return(bytes);
		else {
			if (!(mtsts.mt_softstat & MT_EOT))
				return(bytes);
			else {
				if (blocks >= 0)
					/* blocks < 0 implies that a
					 * file ended on an exact EOT
					 * boundry and is not continued
					 * to the next archive.
					 */
					FILE_CONTINUES++;
				EOTFLAG++;
				MULTI++;

				/* Go announce end of media & get user
				 * to mount the next tape archive.
				 */
				getdir();

				/* Now go read the requested amount of
				 * data from the newly mounted tape.
				 */
				goto NEWVOL;
			}
		}
	}
	else
		return(bytes);
}

for (count = 0; count < size; count += lastread) {
	if (lastread < 0) {
		if (count > 0)
			return (count);

		return (lastread);
	}

	lastread = read(fd, buf, size - count);

	/* Zero byte read indicates EOF.
	 * Return that or the count of last good read.
	 * ie. If we never read anything, count will be 0.
	 */
	if (!lastread) 
		return(count);

	buf += lastread;

}/*E for count = 0 ..*/

return (count);

}/*E bread() */

/*.sbttl bsrch() */

/* Function:
 *
 *	bsrch
 *
 * Function Description:
 *
 *	Part of the "update" archive function.
 *
 * Arguments:
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
daddr_t bsrch(s, n, l, h)
	daddr_t	l, h;
	STRING_POINTER	s;
{
/*------*\
  Locals
\*------*/

	char	b[N];
	int	i, j;
	daddr_t	m, m1;

/*------*\
   Code
\*------*/

njab = 0;

loop:
/*--:
 */
if (l >= h)
	return (-1L);

m = l + (h-l)/2 - N/2;
if (m < l)
	m = l;

fseek(tfile, m, 0);
fread(b, 1, N, tfile);
njab++;

for(i=0; i<N; i++) {
	if (b[i] == '\n')
		break;
	m++;
}
if (m >= h)
	return (-1L);
m1 = m;
j = i;

for(i++; i<N; i++) {
	m1++;
	if (b[i] == '\n')
		break;
}
i = cmp(b+j, s, n);
if (i < 0) {
	h = m;
	goto loop;
}
if (i > 0) {
	l = m1;
	goto loop;
}
return (m);

}/*E bsrch() */

/*.sbttl checkdir() */

/* Function:
 *
 *	checkdir
 *
 * Function Description:
 *
 *	Checks for existance of a directory on disk. If it is
 *	not there, the function trys to create it.
 *
 * Arguments:
 *
 *	char	*name	Pointer to directory name character string
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *
 */
checkdir(name)
	STRING_POINTER	name;
{
/*------*\
  Locals
\*------*/

	STRING_POINTER	cp;
	int	i;

/*------*\
   Code
\*------*/

/*
 * Quick check for existance of directory.
 */
if (!(cp = rindex(name, '/')))
	return (0);

*cp = '\0';
if (access(name, 0) >= 0) {
	*cp = '/';
	return (cp[1] == '\0');
}

*cp = '/';
/*
 * No luck, try to make all directories in path.
 */
for (cp = name; *cp; cp++) {
	if (*cp != '/')
		continue;

	*cp = '\0';
	if (access(name, 0) < 0) {
#ifndef U11
		if (mkdir(name, 0777) < 0) {
			perror(name);
			*cp = '/';
			return (FAIL);
		}
#endif
#ifdef U11
		if (!fork()) {
			execl("/bin/mkdir","mkdir",name,0);
			fprintf(stderr,"%s: Failed to execute /bin/mkdir  for:  %s\n", progname, name);
			perror(name);
			done(FAIL);
		}
/*
 * Check returned status. If status came back (byte of zero = normal)
 * then the high byte is the exit status. Mkdir returns a high byte
 * of zero if ok, and non-zero otherwise. If zero returns, we notify the
 * user dir made ok (if vflag) otherwise mkdir would have displayed an
 * error.
 *
 */
		while(wait(&i) > 0)
			if ((i & 0377) == 0)
				if ((i & ~0377) != 0) {
					fprintf(stderr,"%s: Error making directory:  %s\n", progname, name);
					perror(name);
					done(FAIL);
				}
				
#endif
	if (VFLAG)
		fprintf(stderr,"x %s/ (directory created)\n", name);
	else
		if (vflag)
			fprintf(stderr,"x %s/\n", name);

	if (pflag)
		chown(name, stbuf.st_uid, stbuf.st_gid);

	if (pflag && cp[1] == '\0')
		chmod(name, stbuf.st_mode & 07777);

	}/*E if (access(name, 0) < 0) */

	*cp = '/';

}/*E for (cp = name; *cp; cp++) */

return (cp[-1]=='/');

}/*E checkdir() */

/*.sbttl CHECKF() */

#ifdef TAR40

/* Function:
 *
 *	CHECKF
 *
 * Function Description:
 *
 *   Routine to determine whether the specified file should
 *   be skipped or not. Implements the F, FF and FFF modifiers.
 *
 *   The set of files skipped depends upon the value of howmuch.
 *
 *   When howmuch > 0 the set of skip files consists of:
 *
 *		SCCS directories
 *		core files
 *		errs files
 *
 *    Howmuch > 1 increases the skip set to include:
 *
 *		a.out
 *		*.o
 *
 *    Howmuch > 2 causes executable files to be skipped
 *    as well. (A file is determined to be executable by
 *    looking at its "magic numbers")
 *
 *
 * Arguments:
 *
 *	char	*longname	Pointer to name string of file to
 *				to be checked
 *	int	mode		File mode bits of the file
 *	int	howmuch		Skip set modifier
 *
 * Return values:
 *
 *	1	The file is to be skipped
 *	0	The file is not to be skipped
 *
 * Side Effects:
 *
 *	The routine assumes that the file is in the current directory.
 *	The routine is not fully implemented as described above. See
 *	the #ifdef below.. 
 *	
 */
CHECKF(longname, mode, howmuch)
	STRING_POINTER	longname;
	int	mode;
	int	howmuch;
{
/*------*\
  Locals
\*------*/

	STRING_POINTER	shortname;

/*------*\
   Code
\*------*/

/* Strip off any directory and get the base filename.
 */ 
if ((shortname = rindex(longname, '/')))
	shortname++;
else
	shortname = longname;

/* Basic skip set ?
 */
if (howmuch > 0) {
	/*
	 * Skip SCCS directories on input
	 */
	if ((mode & S_IFMT) == S_IFDIR)
		return (strcmp(shortname, "SCCS") != 0);

	/*
	 * Skip SCCS directory files on extraction
	 * Check SCCS as a toplevel directory and
	 * as a subdirectory.
	 */
#ifdef TAR40
	if (STRFIND(longname,"SCCS/") == longname || STRFIND(longname,"/SCCS/"))
#else
	if (strfind(longname,"SCCS/") == longname || strfind(longname,"/SCCS/"))
#endif
		return (0);
	/*
	 * Skip core and errs files. 
	 */
	if (strcmp(shortname,"core")==0 || strcmp(shortname,"errs")==0)
		return (0);

}/*E if howmuch > 0 */

/* First level additions ?
 */
if (howmuch > 1) {
	int l;

	l = strlen(shortname);	/* get string len */

	/* Skip .o files
	 */
	if (shortname[--l] == 'o' && shortname[--l] == '.')
		return (0);

	/* Skip a.out
	 */
	if (strcmp(shortname, "a.out") == 0)
		return (0);

}/*E if howmuch > 1 */

#ifdef notdefFFF
/*
 * This routine works for -c and -r options, but is
 * not sufficent for -x option.  Since it cannot be implemented
 * fully, it is not implemented at all at this time.
 */
/*
 * Second level additions ?
 */
if (howmuch > 2) {
	/*
	 * Open the file to examine the magic numbers.
	 * If the file cannot be opened, then assume
	 * that it is not to be skipped and that another
	 * routine will perform the error handling for it.
	 * Otherwise, read from the file and check the
	 * magic numbers, indicate skipping if they are good.
	 */
	int	ifile, in;
	struct exec	hdr;

	ifile = open(shortname,O_RDONLY,0);

	if (ifile >= 0)	{
		in = read(ifile, &hdr, sizeof (struct exec));
		close(ifile);
		if (in > 0 && ! N_BADMAG(hdr))	/* executable: skip */
			return (0);
	}
}/* end if howmuch > 2 */
#endif notdefFFF

return (1);	/* Default action is not to skip */

}/*E CHECKF() */
#endif
/*.sbttl CHECKSUM() */

#ifdef TAR40

/* Function:
 *
 *	CHECKSUM
 *
 * Function Description:
 *
 *	Perform first-level CHECKSUM for file header block.
 *
 * Arguments:
 *
 *	none
 *
 * Return values:
 *
 *	The CHECKSUM value is returned.
 *
 * Side Effects:
 *
 *	
 */
CHECKSUM()
{
/*------*\
  Locals
\*------*/

	STRING_POINTER	cp;
	int	i = 0;

/*------*\
   Code
\*------*/

for (cp = dblock.dbuf.chksum; cp < &dblock.dbuf.chksum[sizeof(dblock.dbuf.chksum)]; cp++)
	*cp = ' ';

for (cp = dblock.dummy; cp < &dblock.dummy[TBLOCK]; cp++)
	i += *cp;

return (i);

}/*E CHECKSUM() */
#endif
/*.sbttl checkw() */

/* Function:
 *
 *	checkw
 *
 * Function Description:
 *
 *	Checks to see if the user wants this file or not if
 *	the wait flag is set. Outputs the yes/no and name of
 *	the file, gets a response and returns.
 *
 * Arguments:
 *
 *	char	*name	Pointer to the current file name string
 *
 * Return values:
 *
 *	Non-zero	User wants this file. Default case if the
 *			wait flag is not set.
 *	zero		User does not want this file. Set only if
 *			the wait function was requested in the
 *			command line, and user types a no to our
 *			query.
 *
 * Side Effects:
 *
 *	
 */
checkw(c, name)
	char *name;
{
/*------*\
   Code
\*------*/

if (!wflag)
	return (1);

fprintf(stderr,"%c ", c);

if (vflag)
	longt(&stbuf);

fprintf(stderr,"%s: ", name);
	return (response() == 'y');

}/*E checkw() */

/*.sbttl cmp() */

/* Function:
 *
 *	cmp
 *
 * Function Description:
 *
 *	Part of the "update" archive function.
 *
 * Arguments:
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
cmp(b, s, n)
	STRING_POINTER	b;
	STRING_POINTER	s;
{
/*------*\
  Locals
\*------*/

	INDEX	i;

/*------*\
   Code
\*------*/

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);

}/*E cmp() */

/*.sbttl dotable()  Top level logic for "t" function */

/* Function:
 *
 *	dotable
 *
 * Function Description:
 *
 *	Top level logic to do the Table of Contents function
 *	for an input archive.
 *
 * Arguments:
 *
 *	none
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
dotable()
{

for (;;) {
	extracted_size = 0L;
TNEXTA:
/**/
	getdir();

	if (endtape()) {
		if (FILE_CONTINUES)
			goto TNEXTA;

		if (VFLAG)
			printf("\n%s: %ld Blocks used on %s for archive  %d\n\n",
				progname, blocks_used, usefile, CARCH);
		break;
	}
	dostats();
	
	/* Check for F[FF] operation, skipping SCCS stuff, etc..
 	 */
#ifdef TAR40
	if (Fflag && !CHECKF(dblock.dbuf.name, stbuf.st_mode, Fflag)) {
#else
	if (Fflag && !checkf(dblock.dbuf.name, stbuf.st_mode, Fflag)) {

#endif
		passtape();
		continue;
	}
	
	passtape();

}/*E for ;; */

}/*E dotable() */

dostats()
{
	/*
 	 * Save last file name  &  mod time for possible
 	 * archive switch compare.
 	 */

	strcpy(file_name,dblock.dbuf.name);
	modify_time = stbuf.st_mtime;

	if (dblock.dbuf.typeflag == DIRTYPE) {
		if (vflag)
			longt(&stbuf);

		printf("%s", dblock.dbuf.name);
	}
	else {
		if (vflag)
			longt(&stbuf);

		printf("%s", dblock.dbuf.name);

		if (!vflag && OARCH)
			printf("  <- continued from archive  %d,  Current archive is  %d, Original archive is  %d",
				(CARCH-1), CARCH, OARCH);
	}
	if (dblock.dbuf.typeflag == LNKTYPE)
		printf(" linked to %s", dblock.dbuf.linkname);

	if (dblock.dbuf.typeflag == SYMTYPE)
		printf(" symbolic link to %s", dblock.dbuf.linkname);

	if (VFLAG && dblock.dbuf.typeflag == CHRTYPE)
		printf(" (character special)");

	if (VFLAG && dblock.dbuf.typeflag == BLKTYPE)
		printf(" (block special)");
	
	printf("\n");

}/*E dostats() */

/*.sbttl doxtract() */

/* Function:
 *
 *	doxtract
 *
 * Function Description:
 *
 *	eXtracts (reads) files from an input archive and places
 *	them into disk files.
 *
 * Arguments:
 *
 *	char	*argv	Pointer to list of input file names
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
doxtract(argv)
	char *argv[];
{
/*------*\
  Locals
\*------*/

	char	**cp;
	char	buf[TBLOCK];
	FILE_D	ofile;

/*------*\
   Code
\*------*/

for (;;) {
	extracted_size = 0L;
	getdir();
	if (endtape()) {

		if (VFLAG)
			fprintf(stderr,"\n%s: %ld Blocks used on %s for archive  %d\n\n",
				progname, blocks_used, usefile, CARCH);
			break;
	}
NEW_FILE:
/**/
	/* Save last file name  &  modify time for possbile
	 * archive switch ck.
	 */
	strcpy(file_name,dblock.dbuf.name);
	modify_time = stbuf.st_mtime;
	
	if (!*argv)
		goto gotit;

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

	passtape();
	continue;

gotit:
/*---:
 */
	/* (w) Pass user conf.? No: skip file.
	 */
	if (wflag) {
		if (!checkw('x', dblock.dbuf.name)) {
			passtape();
			continue;
		}
	}

	/* (F[FF]) Fast? Check to see if the file is in the skip set.
	 */
	if (Fflag) {
#ifdef TAR40
		if (!CHECKF(dblock.dbuf.name, stbuf.st_mode, Fflag)) {
#else
		if (!checkf(dblock.dbuf.name, stbuf.st_mode, Fflag)) {

#endif
			passtape();
			continue;
		}
	}
	if (checkdir(dblock.dbuf.name))
		continue;

	/* See if this is a symbolic link.
	 */
	if (dblock.dbuf.typeflag == SYMTYPE) {
		/*
		 * Fix for IPR-00014. Prevent tar from
		 * unlinking non-empty directories.
		 *
		 * Only unlink non-directories or
		 * empty directories.
		 */
#ifndef U11
		if (rmdir(dblock.dbuf.name) < 0) {
			if (errno == ENOTDIR)
				unlink(dblock.dbuf.name);
			else
				if (errno == ENOTEMPTY)
					perror(progname);
		}

		if (symlink(dblock.dbuf.linkname, dblock.dbuf.name)<0) {
			fprintf(stderr, "%s: Symbolic link failed:  %s\n",
				progname, dblock.dbuf.name);

			perror(dblock.dbuf.name);
			continue;
		}
#else
		if (stat(dblock.dbuf.name, &stbuf) != ENOENT) {
			if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
				if (rmdir(dblock.dbuf.name) < 0) {
					fprintf(stderr,"%s: Can't remove directory symbolically linked to by  %s\n",
					progname,dblock.dbuf.name);

					perror(dblock.dbuf.name);
				}
			}
		}
		unlink(dblock.dbuf.name);

#ifndef PRO
		if (symlink(dblock.dbuf.linkname, dblock.dbuf.name) < 0) {
#else
		if (link(dblock.dbuf.linkname, dblock.dbuf.name) < 0) {
#endif
			fprintf(stderr, "%s: Can't link:  %s\n", progname, dblock.dbuf.name);
			perror(dblock.dbuf.name);
			continue;
		}
#endif
		if (vflag)
			fprintf(stderr,"x %s symbolic link to %s\n", dblock.dbuf.name, dblock.dbuf.linkname);

		if (pflag)
			chown(dblock.dbuf.name, stbuf.st_uid, stbuf.st_gid);

		continue;
	}/*E if (dblock.dbuf.typeflag == SYMTYPE) */

	/* See if this is a hard link.
	 */
	if (dblock.dbuf.typeflag == LNKTYPE) {

		/* Fix for IPR-00014. Prevent tar from
		 * unlinking non-empty directories.
		 *
		 * Only unlink non-directories, or
		 * empty directories.
		 */
#ifndef U11
		if (rmdir(dblock.dbuf.name) < 0) {

			if (errno == ENOTDIR)
				unlink(dblock.dbuf.name);
			else
				if (errno == ENOTEMPTY)
					perror(progname);
		}
#else
		if (stat(dblock.dbuf.name, &stbuf) != ENOENT) {
			if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
				if (rmdir(dblock.dbuf.name) < 0) {
					fprintf(stderr,"%s: Can't remove directory linked to by  %s\n",progname,dblock.dbuf.name);
					perror(dblock.dbuf.name);
				}
			}
			else 
				unlink(dblock.dbuf.name);
		}
#endif
		if (link(dblock.dbuf.linkname, dblock.dbuf.name) < 0) {
			fprintf(stderr, "%s: Can't link:  %s\n", progname, dblock.dbuf.name);
			perror(dblock.dbuf.name);
			continue;
		}
		if (vflag)
			fprintf(stderr,"%s linked to %s\n", dblock.dbuf.name, dblock.dbuf.linkname);
		if (pflag)
			chown(dblock.dbuf.linkname, stbuf.st_uid, stbuf.st_gid);


		continue;

	}/*E if dblock.dbuf.typeflag == LNKTYPE */

	/*
	 * Are we extracting a special file ?
	 */
	if (((stbuf.st_mode & S_IFMT) == S_IFCHR) ||
	      ((stbuf.st_mode & S_IFMT) == S_IFBLK)) {	

		if ((ofile = mknod(dblock.dbuf.name,
		     stbuf.st_mode,stbuf.st_rdev)) < 0 ) {

			fprintf(stderr,"%s: Can't create special file:  %s\n", progname,dblock.dbuf.name);
			perror(dblock.dbuf.name);
			passtape();
			continue;
		}
		if (vflag)
			fprintf(stderr,"x %s, (special file)\n", dblock.dbuf.name);
		continue;	

	}/*E if st->st_mode etc..*/

	/*
	 * Assume a regular type of file.
	 */
	if ((ofile = creat(dblock.dbuf.name,stbuf.st_mode&0xfff)) < 0) {
		fprintf(stderr, "%s: Can't create:  %s\n", progname, dblock.dbuf.name);
		perror(dblock.dbuf.name);
		passtape();
		continue;
	}

NEXT_CHUNK:
/**/
	bytes = stbuf.st_size;
	blocks = (bytes + (SIZE_L)TBLOCK - 1L) / (SIZE_L)TBLOCK;

	if (vflag) {
		if (OARCH  && (original_size > chctrs_in_this_chunk)) 
#ifndef U11
			fprintf(stderr,"x %s, %ld bytes of %ld, %d blocks\n",
				dblock.dbuf.name, chctrs_in_this_chunk,
				original_size, blocks);
		else
			fprintf(stderr,"x %s, %ld bytes, %d blocks\n",
				dblock.dbuf.name, bytes, blocks);

#else
			fprintf(stderr,"x %s, %ld bytes of %ld, %d tape blocks\n",
				dblock.dbuf.name, chctrs_in_this_chunk,
				original_size, blocks);
		else
			fprintf(stderr,"x %s, %ld bytes, %d tape blocks\n",
				dblock.dbuf.name, bytes, blocks);
#endif
	}

	for (; blocks-- > 0; bytes -= (SIZE_L)TBLOCK) {
		readtape(buf);

		if (bytes > (SIZE_L)TBLOCK) {
			if (write(ofile, buf, TBLOCK) < 0) {
				fprintf(stderr, "%s: Write error on extract:  %s\n",
					progname, dblock.dbuf.name);

				perror(dblock.dbuf.name);
				done(FAIL);
			}
			continue;
		}
		if (write(ofile, buf, (int) bytes) < 0) {
			fprintf(stderr,"%s: Write error on extract:  %s\n",
				progname, dblock.dbuf.name);

			perror(dblock.dbuf.name);
			done(FAIL);
		}
	}/*E for ; blocks-- > 0 ..*/
	
	/* Have we extracted all of the file ?
	 * Non UMA files can't be continued, by definition.
	 */
	if ((hdrtype != UMA) || (extracted_size == original_size)) {
		close(ofile);
	}
	else {
		/* Come here when we need to process another non-tape
		 * archive to get more of the file. For tapes, the
		 * archive switching is transparant to this logic.
		 * It is delt with by the readtape(), bread(), and
		 * getdir() routines.
		 */
		char last_file[NAMSIZ+1];

		strcpy(last_file,dblock.dbuf.name);
		modify_time = stbuf.st_mtime;
		getdir();
		if (endtape()) {

			/* If this is the last archive, we will never 
			 * see the rest of the file. Give up.
			 */
			if (!FILE_CONTINUES) {
				fprintf(stderr,"\n%s: File  %s  not correctly extracted \n\n",
				  progname, last_file);

				close(ofile);

				if (VFLAG)
					fprintf(stderr,"\n%s: %ld Blocks used on %s for archive  %d\n\n",
						progname, blocks_used,
						usefile, CARCH);
					done(FAIL);
			}
			else {
				/*
		 	 	* Else, next archive should be inserted.
		 	 	*/
				getdir();
				goto NEXT_CHUNK;
			}
		}
		else {
			fprintf(stderr,"\n%s: File  %s  not correctly extracted \n\n",
			  progname, last_file);

			close(ofile);
			goto NEW_FILE;
		}
	}
	if (pflag)
		chown(dblock.dbuf.name, stbuf.st_uid, stbuf.st_gid);

#ifndef U11 
	if (!mflag) {
		struct timeval tv[2];

		tv[0].tv_sec = time(0);
		tv[0].tv_usec = 0;
		tv[1].tv_sec = stbuf.st_mtime;
		tv[1].tv_usec = 0;
		utimes(dblock.dbuf.name, tv);
	}
#endif

#ifdef U11
	if (!mflag) {
		time_t timep[2];

		timep[0] = time(NULL);
		timep[1] = stbuf.st_mtime;
		utime(dblock.dbuf.name, timep);
	}
#endif

	if (pflag)
		chmod(dblock.dbuf.name, stbuf.st_mode & 07777);

}/*E for ;; */

}/*E doxtract()*/

/*.sbttl endtape()  Determine if end of archive reached */

/* Function:
 *
 *	endtape
 *
 * Function Description:
 *
 *	Decide if the end of archive has been reached.
 *
 * Arguments:
 *
 *	None explictly stated. However, the routine expects that
 *	"dblock" has been filled with the contents of a file
 *	header block from the input archive.
 *
 * Return values:
 *
 *	The content of  dblock.dbuf.name  is returned.
 *	If zero, tar has seen its' end of archive indicator.
 *	Else, there is yet more data on this archive.
 *
 * Side Effects:
 *
 *	none
 */
endtape()
{

EODFLAG = FILE_CONTINUES = 0;

/* If this is an empty block, read one more block so we don't break
 * our pipe by closing down before reading ALL the blocks ! 
 * From a tape, it doesn't matter, but thru a pipe...
 */ 

if (pipein) {
	if (dblock.dbuf.name[0] == '\0') {
		sleep(2);
		readtape((char *)&dblock);
		return(TRUE);
	}
}

/* For multi_archive operations, normally read thru the 2 zero-filled
 * blocks and return status from next one. This block will tell
 * us if the file is continued to another archive - but only if
 * the last file on the archive was written by this version of tar.
 * For appends and updates, we need to NOT read thru the zero-blocks
 * in order to maintain correct physical postion.
 */

if (OARCH && rflag && !dblock.dbuf.name[0] && size_of_media[CARCH]) {
	/*
	 * The current file either is the last one on the media
	 * or the first and is continued from a previous archive.
	 */
	if (blocks_used == (size_of_media[CARCH] - 2L)) {

		/* The file must be the last one and is continued
		 * to next archive.
		 */
		blocks_used += 2L;
		EODFLAG = FILE_CONTINUES = TRUE;
		return(TRUE);
	}
	else {
		/* There is more room on the archive.
		 */
		blocks_used -= 1;
		return(TRUE);
	}
}

/* Deal with an append to an archive not beginning or ending
 * with a continued file.
 */
if (!dblock.dbuf.name[0] && rflag) {
	blocks_used--;
	return(TRUE);
}

/* Now, if NOT dealing with an update or rappend  AND
 * the last file was an Ultrix Multi Archive format.
 */
if ((dblock.dbuf.name[0] == '\0') && (hdrtype == UMA)) {

	EODFLAG = TRUE;
	readtape((char *)&dblock);
	readtape((char *)&dblock);

	if (dblock.dbuf.name[0]) {

		FILE_CONTINUES++;

		strcpy(file_name,dblock.dbuf.name);
		dblock.dbuf.name[0] = '\0';
	}

	return (dblock.dbuf.name[0]=='\0');
}

/* Adjust the block count for archives not ending with a file
 * written in "UMA" format.
 */

if (dblock.dbuf.name[0] == '\0') {
	if (!rflag)
		blocks_used++;
	else
		blocks_used--;
}
return (dblock.dbuf.name[0]=='\0');

}/*E endtape() */

/*.sbttl getdir() */

/* Function:
 *
 *	getdir
 *
 * Function Description:
 *
 *	Reads a file header block from the input archive.
 *	Extract desired information from it and place in
 *	appropriate variables.
 *
 *	This routine also determines the "type" of tar header.
 *	and sets control variable "hdrtype"; 
 *
 * Arguments:
 *
 *	none
 *
 * Return values:
 *
 *	none
 *
 * Side Effects:
 *
 *	Routine may exit to the system if a checksum error
 *	is detected and the -i switch was not specified.
 */

getdir()
{
/*------*\
  Locals
\*------*/

	FLAG	retrying = 0;
	int	i;
	int	majordev, minordev;
	int	openm;
	struct	passwd	*pwp;
	FLAG	reload;
	struct	stat	*sp;
	SIZE_I	tcarch;
	SIZE_I  toarch;


/*------*\
   Code
\*------*/

sp = &stbuf;
	
/*
 * Determine how much space is left on
 * this archive and assume one block as a
 * minimum for any output.
 */
if(size_of_media[0]){
	SIZE_L available_blocks;

	available_blocks = size_of_media[CARCH] - (nblock + blocks_used +3L);
	if(available_blocks < 0L && !FILE_CONTINUES)
		goto EOTD;
}

/* If the condition  EOTFLAG && !VOLCHK  is true,
 * bread() has detected a real tape EOT marker & is calling us
 * to announce the end of media & to request the user to mount
 * the next archive.
 */
if ((EOTFLAG && !VOLCHK) || (FILE_CONTINUES && EODFLAG))
	goto EOTD;

/* The following test implies that  readtape()  has been told of a
 * real tape EOT by bread() & is calling us to verify that the correct
 * (next) archive was mounted.
 */
if (EOTFLAG && VOLCHK)
	goto CHKVOL;

top:
NEXTVOL:
/**/

readtape((char *)&dblock);

if (!dblock.dbuf.name[0]) {
	
	if (EODFLAG || EOTFLAG)
		goto CHKVOL;
	else
		return;
}

if (FILE_CONTINUES) {

EOTD:
/**/
	nextvol = CARCH+1;

	if (VFLAG) {
		if (MULTI)
			fprintf(stderr,"\n%s: End of archive media\n",progname);
		else
			fprintf(stderr,"\n");

		fprintf(stderr,"%s: %ld Blocks used on %s for archive  %d\n",
			progname, blocks_used, usefile, CARCH);
	}
	if (FILE_CONTINUES)
		fprintf(stderr,"\n%s: File  %s  is continued on archive  %d",
	 	progname, file_name, nextvol);

	FILE_CONTINUES = 0;

RELOAD:
/**/
	close(mt);
	blocks_used = 0L;
	recno = 0;
	first = 0;
	if (retrying)
		fprintf(stderr,"\n%s: Please load correct archive on  %s  & press RETURN ",
			progname, usefile);
	else
		fprintf(stderr,"\n\007%s: Please load archive  %d  on  %s  & press RETURN ",
			progname, nextvol, usefile);

	response();
	fprintf(stderr,"\n");

	if (tflag || xflag)
		openm = O_RDONLY;
	else
		openm = O_RDWR;
OPENT:
/**/
	if ((mt = open(usefile, openm)) < 0) {
		fprintf(stderr, "%s: Can't open:  %s\n",
			progname, usefile);

		perror(usefile);
		fprintf(stderr,"\n%s: Please press  RETURN  to retry ",progname);
		response();
		goto OPENT;
	}
	MULTI = 0;
	if (!EOTFLAG)
		goto NEXTVOL;
	else
		return; /*<- go back to bread() or readtape() */
}
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;
/*
 * If this is an older version of Ultrix tar archive, this
 * will extract (if any) the major/minor device number of
 * a "special" file.
 */
sscanf(dblock.dbuf.magic,"%o", &i);
sp->st_rdev = i;
sscanf(dblock.dbuf.size, "%lo", &sp->st_size);
sscanf(dblock.dbuf.mtime, "%lo", &sp->st_mtime);
sscanf(dblock.dbuf.chksum, "%o", &chksum);

/*
 * Determine  true  tar  header format type  and 
 * announce result if requested.
 */
OARCH = 0;
volann++;
/*
 * Default to Original Tar Archive
 */
hdrtype = OTA;

if (!dblock.dbuf.magic[0]) {
	if (VFLAG && !volann)
	    printf("%s: Original tar archive format \n\n", progname);
}
else {	/* Further determination of tar header type.
	 * There is something in the former  rdev[6] field.
	 */
	hdrtype = OUA;	/* Set type to older Ultrix format  -
			 * ie. dblock.dbuf.magic MAY contain
			 * major/minor device numbers.
			 */ 
	if (!strcmp(dblock.dbuf.magic,TMAGIC)) {

		hdrtype = UGS;	/* Set type to plain User Group
				 * standard format.
				 * Do final check of format.
				 */
		if (dblock.dbuf.carch[0]) {

			/* Is User Group standard, PLUS -> Ultrix
			 * multi-archive extensions.
			 */
			hdrtype = UMA;
			/*
			 * Extract mod time & archive #'s.
			 */
CHKVOL:
/**/
			sscanf(dblock.dbuf.mtime, "%lo", &sp->st_mtime);
			sscanf(dblock.dbuf.carch, "%d", &tcarch);
			sscanf(dblock.dbuf.oarch, "%d", &toarch);

			if (tcarch >= CARCH) {
				CARCH = tcarch;
				OARCH = toarch;
			}

			if (EODFLAG || VOLCHK) {
				reload = 0;


				if (CARCH != nextvol) {
					reload++;
					fprintf(stderr,"%s: Incorrect archive (%d) loaded on %s\n",
						progname,CARCH,usefile);

					fprintf(stderr,"%s: Archive  %d  expected\n",
						progname, nextvol);
				}

			    if (blocks >= 0) {
				/*
				 * If blocks is less than 0, an EOT
				 * and end of file occured at the
				 * same time. In this case, the file
				 * is not continued to the next archive
				 * and therefore the following checks
				 * do not apply. A new and different
				 * file will have been started.
				 */
				if (strcmp(file_name,dblock.dbuf.name)){
					if (!reload) {
						fprintf(stderr,"%s: Archive  %d  does not begin with correct file.\n",
							progname,CARCH);

					fprintf(stderr,"%s: Continued file name:  %s\n",
						progname, file_name);

					fprintf(stderr,"%s: File name on current archive  %s\n",
					     progname,dblock.dbuf.name);

					reload++;
					}
				}
				if (!reload && (sp->st_mtime != modify_time)) {

					fprintf(stderr,"\n%s: Continued file:  %s\n%s: modification time does not match previous archive.\n",
					   progname,file_name,progname);

					reload++;
				}
			    }/*E if blocks > 0 */

				if (reload) {
					retrying = reload;
					reload = VOLCHK = 0;
					goto RELOAD;
				}
			}
			sscanf(dblock.dbuf.size, "%lo", &sp->st_size);

			/* dblock.dbuf.size  contains the size of the
			 * file, or a file chunk.
			 * For "real" tapes, the portion of a file 
			 * written when we encounter an  EOT  has the
			 * true file size as it is not possible to
			 * know in advance when we'll run out of tape.
			 * For disks,  dblock.dbuf.size  contains the
			 * the amount of data we could put on the
			 * current archive. This would be either at the
			 * end of an archive, or at the beginning of
			 * a continuation archive. This is possible
			 * because we know the media size in advance
			 * and can calculate the amount of data we
			 * can place on it without running out of
			 * available space.
			 */

			chctrs_in_this_chunk = sp->st_size;

			/* The next test is seeking to determine if
			 * this is a "continuation" header. Either
			 * continued to next archive (disks only) or
			 * continued from a previous archive.
			 * Real tapes have only continued "from" headers
			 * and the  EOT  detection is used in lieu of
			 * a "continued to next archive" header.
			 */
			if (OARCH) {
				sscanf(dblock.dbuf.org_size, "%lo", &original_size);

				/* Don't add up the size when switching
				 * real tape archives. The initial
				 * archive contained the size info
				 * required to extract the file.
				 * For non-tapes, the various portion
				 * sizes of the file are contained in
				 * each header. For tapes, you cannot
				 * know when you are going to expire
				 * the media - ie. run out of tape.
				 */
				if (!EOTFLAG)
					extracted_size += sp->st_size;
			}
			else  {
				/* When processing a "real" tape, the
				 * header of a continued file on the
				 * ORIGINAL archive will contain the
				 * true size of the file. It is this
				 * amount in blocks that the logic
				 * will attempt to read.
				 */
				extracted_size = original_size = sp->st_size;
			}
			if (VOLCHK && EOTFLAG) {
				/*
				 * Return to  readtape()  indicating
				 * that the correct continuation
				 * archive was verified as having
				 * been mounted on the input device.
				 */
				return;
			}
			if (VFLAG && !volann) {
				printf("%s: Ultrix multi-archive tar format\n", progname);
				printf("%s: Current archive  %d\n", progname, CARCH);
				if (OARCH && (OARCH != CARCH))
					printf("%s: Original archive  %d\n\n", progname, OARCH);
				else
					printf("\n");
			}
		}/*T if (dblock.dbuf.carch[0]) */
		else {
			/* Archive is User Group Standard 
			 * format.
			 */
			if (VFLAG && !volann)
				printf("%s: User Group Standard tar archive format\n\n", progname);

		}/*F if dblock.dbuf.carch[0] */
	}/*T if (strcmp(dblock.dbuf.magic,TMAGIC)==0) */

	else {
		/* Archive is older Ultrix version ..
		 * ie. May have a device major/minor number
		 * the  dblock.dbuf.magic  (formally rdev[6]).
		 */
		if (VFLAG && !volann)
			printf("%s: Older Ultrix tar archive format\n\n", progname);

	}/*F if strcmp(dblock.dbuf.magic, TMAGIC)==0 */
}

/* If this file is in User Group format, with or without Ultrix
 * extensions, convert fields to the internal format used by the
 * original  tar  code for ease of implementation.
 */
if (hdrtype >= UGS) {

	/* Put possible major/minor device numbers where tar
	 * usually expects to find them.
	 */
	sscanf(dblock.dbuf.devmajor, "%o", &majordev);
	sscanf(dblock.dbuf.devminor, "%o", &minordev);
	sp->st_rdev = ((majordev << 8) | minordev);

	/* Scan /etc/passwd and /etc/group to get user id
	 * and group id numbers.
	 */
	if (!NFLAG) {
		if (pwp=getpwnam(dblock.dbuf.uname))
			sp->st_uid = pwp->pw_uid;

		if (gp=getgrnam(dblock.dbuf.gname))
			sp->st_gid = gp->gr_gid;
	}
	/*
	 * Take further special action regarding "file types"
	 * as defined by the User Group standard.
	 */
	switch(dblock.dbuf.typeflag) {

		/* A regular file ?
		 */
		case AREGTYPE:
		case REGTYPE:
			sp->st_mode |= S_IFREG;
			break;

		/* Symbolic link ?
		 *
		case SYMTYPE:
			sp->st_mode |= S_IFLNK;
			break;

		/* Character special ?
		 */
		case CHRTYPE:
			sp->st_mode |= S_IFCHR;
			break;

		/* Block special ?
		 */
		case BLKTYPE:	
			sp->st_mode |= S_IFBLK;		
			break;

		/* Directory ?
		 */
		case DIRTYPE:
			sp->st_mode |= S_IFDIR;
			break;

		/* FIFO special
		 *  and/or  Contiguous ?
		 *
 		 * (for want of anything better at the moment,
		 *  classify these as -regular- files.)
		 */
		case FIFOTYPE:
		case CONTTYPE:
		default:
			sp->st_mode |= S_IFREG;
			break;

	}/*E switch(typeflag) */

}/* if hdrtype >= UGS */


EODFLAG = 0;

#ifdef TAR40
if (chksum != (i = CHECKSUM())) {
#else
if (chksum != (i = checksum())) {
#endif

	fprintf(stderr, "\n\n\007%s: Directory checksum error, possible file name:\n", progname);

	for (i=0; i < NAMSIZ; i++) 
		fprintf(stderr,"%c",dblock.dbuf.name[i]);

	fprintf(stderr,"\n");

	if (iflag)
		goto top;

	done(FAIL);
}
if (tfile)
	fprintf(tfile, "%-s %-12.12s\n", dblock.dbuf.name, dblock.dbuf.mtime);

}/*E getdir() */

/*.sbttl longt() */

/* Function:
 *
 *	longt
 *
 * Function Description:
 *
 *	Prints out the long statistics about the current file.
 *
 * Arguments:
 *
 *	struct stat	*st	Pointer to filestat data
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
longt(st)
	struct stat	*st;
{
/*------*\
  Locals
\*------*/

	STRING_POINTER	cp;
	STRING_POINTER	ctime();
	SIZE_I	tblocks;
	int	majordev, minordev;

/*------*\
   Code
\*------*/

pmode(st);

if (((st->st_mode & S_IFMT) == S_IFCHR) ||
      ((st->st_mode & S_IFMT) == S_IFBLK)) {

	majordev = st->st_rdev >> 8;
	minordev = st->st_rdev & 0377;
	if (OFLAG && vflag) {
		if (hdrtype >= UGS) 
			printf(" %s/%s\t",dblock.dbuf.uname,dblock.dbuf.gname);
	}

	printf("%3d/%d    %3d,%3d ", st->st_uid, st->st_gid, majordev,minordev);
}
else {
	if (OFLAG && vflag) {
		if (hdrtype >= UGS) 
			printf(" %s/%s  ",dblock.dbuf.uname,dblock.dbuf.gname);
	}
	printf("%3d/%d ", st->st_uid, st->st_gid);

	if (OARCH && (original_size > chctrs_in_this_chunk)) {
		printf(" (%ld bytes of %ld)", chctrs_in_this_chunk, original_size);
	}
	else
		printf("%7D", st->st_size);

	if (!OARCH)
		tblocks = (st->st_size + (SIZE_L)(TBLOCK-1L)) / (SIZE_L)TBLOCK;
	else
		tblocks = (chctrs_in_this_chunk + (SIZE_L)(TBLOCK-1L)) / (SIZE_L)TBLOCK;

	if (VFLAG)
		printf("/%03d ", tblocks);
}
cp = ctime(&st->st_mtime);
printf(" %-12.12s %-4.4s ", cp+4, cp+20);

}/*E longt() */

/*.sbttl lookup() */

/* Function:
 *
 *	lookup
 *
 * Function Description:
 *
 *	Used during an archive "update" function.
 *
 * Arguments:
 *
 *	char	*s
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
daddr_t lookup(s)
	STRING_POINTER	s;
{
/*------*\
  Locals
\*------*/

	daddr_t	a;
	INDEX	i;

/*------*\
   Code
\*------*/

for (i=0; s[i]; i++)
	if (s[i] == ' ')
		break;

a = bsrch(s, i, low, high);
	return (a);

}/*E lookup() */

/*.sbttl passtape() */

/* Function:
 *
 *	passtape
 *
 * Function Description:
 *
 *	Skips over input file content when desired.
 *
 * Arguments:
 *
 *	none
 *
 * Return values:
 *
 *	none
 *
 * Side Effects:
 *
 */
passtape()
{
/*------*\
  Locals
\*------*/

	SIZE_L	pblocks;
	char	buf[TBLOCK];

/*------*\
   Code
\*------*/

if (dblock.dbuf.typeflag) {
	if ((dblock.dbuf.typeflag != REGTYPE) &&
    	    (dblock.dbuf.typeflag != AREGTYPE))

		return;
}
pblocks = stbuf.st_size;
pblocks += (SIZE_L)TBLOCK-1L;
pblocks /= (SIZE_L)TBLOCK;

/* Number of blocks in a file better not ever exceed an "int"
 * size, or this will fail. 
 */
blocks = (SIZE_I)pblocks;

/* 		*-*  WARNING  *-*
 *
 * readtape() will make appropriate adjustments to the global "blocks"
 * variable if a real "tape" archive switch is performed because
 * of an encountered  EOT. This must be done in order to prevent
 * this logic from reading an incorrect number of blocks when
 * switching tape archives.
 */
while (blocks-- > 0)
	readtape(buf);

}/*E passtape() */

/*.sbttl pmode() */

/* Function:
 *
 *	pmode
 *
 * Function Description:
 *
 *	Prints out the Unix file protection modes in
 *	symbolic (rwx) format. Also outputs the special
 *	file indication - for directorys (d), character
 *	special (c), and block special (b). If none of these,
 *	first character on line is given as "-".
 *
 * Arguments:
 *
 *	struct stat	*st	Pointer to filestat buffer.
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */

static int	m1[]	= { 1, ROWN, 'r', '-' };
static int	m2[]	= { 1, WOWN, 'w', '-' };
static int	m3[]	= { 2, SUID, 's', XOWN, 'x', '-' };
static int	m4[]	= { 1, RGRP, 'r', '-' };
static int	m5[]	= { 1, WGRP, 'w', '-' };
static int	m6[]	= { 2, SGID, 's', XGRP, 'x', '-' };
static int	m7[]	= { 1, ROTH, 'r', '-' };
static int	m8[]	= { 1, WOTH, 'w', '-' };
static int	m9[]	= { 2, STXT, 't', XOTH, 'x', '-' };

/* Next variable must come after the above to supply addresses.
 */
static int	*m[]	= { m1, m2, m3, m4, m5, m6, m7, m8, m9};

pmode(st)
	struct stat *st;
{

	char *cp;
	register int **mp;

#ifndef U11
if (VFLAG) {
#endif
	switch(st->st_mode & S_IFMT) {

		case S_IFCHR:
			printf("c");
			break;

		case S_IFBLK:
			printf("b");
			break;

		case S_IFDIR:
			printf("d");
			break;

		default: {
			if ((cp = rindex(dblock.dbuf.name,'/'))==0)
				printf("-");
			else {
				cp++;
				if (!*cp)
					printf("d");
				else
					printf("-");
			}
		}
	}
#ifndef U11
}/*E if VFLAG */
#endif
	for (mp = &m[0]; mp < &m[9];)
		select(*mp++, st);

}/*E pmode() */

/*.sbttl prefix() */

/* Function:
 *
 *	prefix
 *
 * Function Description:
 *
 *	Part of the extract logic.
 *
 * Arguments:
 *
 *	char	*s1
 *	char	*s2
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
prefix(s1, s2)
	register char *s1, *s2;
{
/*------*\
   Code
\*------*/

while (*s1)
	if (*s1++ != *s2++)
		return (0);
if (*s2)
	return (*s2 == '/');

return (1);

}/*E prefix() */

/*.sbttl readtape()  Read data from input archive */

/* Function:
 *
 *	readtape
 *
 * Function Description:
 *
 *	Logic to read from an input archive.
 *
 * Arguments:
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
readtape(buffer)
	STRING_POINTER	buffer;
{
/*------*\
  Locals
\*------*/

	SIZE_I	i;
	STRING_POINTER	from, to;

/*------*\
   Code
\*------*/

if (recno >= nblock || first == 0) {

REREAD:
/**/
	if ((i = bread(mt, tbuf, TBLOCK*nblock)) < 0) {
		fprintf(stderr, "%s: Archive read error at block %ld\n",
			progname, blocks_used);
		perror(usefile);
		done(FAIL);
	}
	if (!first) {

		if ((i % TBLOCK) != 0) {
			fprintf(stderr, "%s: Archive blocksize error\n",
				progname);
			done(FAIL);
		}
		i /= TBLOCK;
		if (i != nblock) {
			fprintf(stderr,"%s: blocksize = %d\n", progname, i);
			nblock = i;
		}
	}/*E if !first */

	recno = 0;

	if (EOTFLAG) {
	/*
	 * bread()  has detected an  EOT  on a "real" tape.
	 * It has called  getdir()  to announce the end of media
	 * & has asked the user to mount the next archive. This
	 * has been done & bread() has returned to us some amount of
	 * data it has read from the new archive.
	 */
#ifdef PRO
		for (from = (char *)&tbuf[recno], to = (char *)&dblock, i=TBLOCK; i; i--)
			*to++ = *from++;
#else
		bcopy((char *)&tbuf[recno], (char *)&dblock, TBLOCK);
#endif
	    if (blocks >= 0) {
		/*
		 * If blocks is < 0 at this point, an EOT and the
		 * end of file have occured at the same time.
		 * The logic will actually be in getdir() when the
		 * EOT is detected. When that is the case, then a
		 * file is not actually split across a volume.
		 * A new and different file will be started on the
		 * next archive in the set.
		 *
		 * Account for the continuation header block.
		 */
		blocks_used++;
		recno++;

	    }/*E if blocks >= 0 */

		/* Go ask  getdir()  to verify that the correct
		 * continuation archive has been mounted by the user.
		 */
		VOLCHK++;

		getdir();

		if (!VOLCHK) 
			/* 
			 * getdir()  will negate the  VOLCHK  flag if
			 * it feels that the mounted archive was not
			 * the correct archive. We'll re-cycle thru
			 * the logic & try to goad the user into
			 * putting up the correct archive.
			 */
			goto REREAD;
		
		if (blocks >= 0) {

			/* Get the number of blocks in this continued
			 * chunk of a file.
			 */
			blocks =
			    (chctrs_in_this_chunk + (SIZE_L)TBLOCK - 1L) / (SIZE_L)TBLOCK;


			if (vflag && xflag) {

				if (OARCH && (original_size > chctrs_in_this_chunk)) 
#ifndef U11
					fprintf(stderr,"x %s, %ld bytes of %ld, %d blocks\n",
					  dblock.dbuf.name, chctrs_in_this_chunk,
					  original_size, blocks);
				else
					fprintf(stderr,"x %s, %ld bytes, %d blocks\n",
					  dblock.dbuf.name, bytes, blocks);
#else
					fprintf(stderr,"x %s, %ld bytes of %ld, %d tape blocks\n",
					  dblock.dbuf.name, chctrs_in_this_chunk,
					  original_size, blocks);
				else
					fprintf(stderr,"x %s, %ld bytes, %l tape blocks\n",
					  dblock.dbuf.name, bytes, blocks);
#endif
			}/*E if vflag && xflag */

			if (tflag)
				dostats();	

			/*
			 * Because of the loop counters used by
			 * passtape() &  doxtract(), one is needed
			 * to be subtracted from the count of blocks
			 * left to read. Their loops have already
			 * counted the block we are going to return
			 * to them from the new archive. If we don't
			 * dec the count by one, they'll end up reading
			 * a block too many.
			 */
		 	blocks--;

		}/*E if blocks >= 0 */
			
		EOTFLAG = VOLCHK = MULTI = FILE_CONTINUES = 0;

	}/*E if EOTFLAG */
}/*E if recno >= nblock ..*/

first = 1;

#ifdef PRO
for (from = (char *)&tbuf[recno++], to = buffer, i=TBLOCK; i; i--)
	*to++ = *from++;
#else
bcopy((char *)&tbuf[recno++],buffer,TBLOCK);
#endif

blocks_used++;
return (TBLOCK);

}/*E readtape() */

/*.sbttl select() */

/* Function:
 *
 *	select
 *
 * Function Description:
 *
 *	Part of the "table" function.
 *
 * Arguments:
 *
 *	int		*pairp	?
 *	struct stat	*st	Pointer to filestat structure
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
select(pairp, st)
	int	*pairp;
	struct stat	*st;
{
/*------*\
  Locals
\*------*/

	int	*ap;
	int	n;

/*------*\
   Code
\*------*/

ap = pairp;
n = *ap++;

while (--n >= 0 && (st->st_mode & *ap++) == 0)
	ap++;

printf("%c", *ap);

}/*E select() */

/*.sbttl STRFIND() */

#ifdef TAR40
/* Function:
 *
 *	STRFIND
 *
 * Function Description:
 *
 *	Searches for  substr  in text.
 *	
 * Arguments:
 *
 *	char	*text		Text to search
 *	char	*substr		String to locate
 *
 * Return values:
 *
 *	Pointer to  substr  starting position in text if found.
 *	NULL if not found.
 *
 * Side Effects:
 *
 *	
 */
STRING_POINTER	STRFIND(text,substr)
		STRING_POINTER text, substr;
{
/*------*\
  Locals
\*------*/

	COUNTER	i;	/* counter for possible fits */
	SIZE_I	substrlen;/* len of substr--to avoid recalculating */

/*------*\
   Code
\*------*/

substrlen = strlen(substr);

/* Loop through text until not found or match.
 */
for (i = strlen(text) - substrlen; i >= 0 &&
     strncmp(text, substr, substrlen); i--)

	text++;

/* Return NULL if not found, ptr else NULL.
 */ 
return ((i < 0 ? NULL : text));

}/*E STRFIND() */
#endif
/*.sbttl update()  Top level logic to UPDATE an archive */

/* Function:
 *
 *	update
 *
 * Function Description:
 *
 *
 * Arguments:
 *
 *
 *
 * Return values:
 *
 *
 * Side Effects:
 *
 *	
 */
update(arg,ftime)
	char	*arg;
	time_t	ftime;
{
/*------*\
  Locals
\*------*/

	daddr_t	lookup();
	long	mtime;
	char	name[MAXPATHLEN];
	daddr_t	seekp;

/*------*\
   Code
\*------*/

rewind(tfile);

for (;;) {
	if ((seekp = lookup(arg)) < 0)
		return (1);

	fseek(tfile, seekp, 0);
	fscanf(tfile, "%s %lo", name, &mtime);
	return (ftime > mtime);
}
}/*E update() */