[comp.sys.amiga] PD tar, part 1

dpz@klinzhai.RUTGERS.EDU (David P. Zimmerman) (01/16/87)

Hello all,

	Mike Meyer mentioned a PD tar; well, I found one!  Have fun
porting it.  This is shar #1 of 2, run it through your favorite Bourne
shell or Amiga shar program.

						dpz

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	buffer.c
#	create.c
#	extract.c
#	getoldopt.c
#	list.c
#	names.c
#	port.c
#	port.h
#	tar.c
#	tar.h
# This archive created: Thu Jan 15 21:02:13 1987
cat << \SHAR_EOF > buffer.c
/*
 * Buffer management for public domain tar.
 *
 * Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985.
 *
 * @(#) buffer.c 1.14 10/28/86 Public Domain - gnu
 */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>		/* For non-Berkeley systems */
#include <sys/file.h>
#include <signal.h>

#include "tar.h"
#include "port.h"

#define	STDIN	0		/* Standard input  file descriptor */
#define	STDOUT	1		/* Standard output file descriptor */

#define	PREAD	0		/* Read  file descriptor from pipe() */
#define	PWRITE	1		/* Write file descriptor from pipe() */

extern char	*valloc();

/*
 * V7 doesn't have a #define for this.
 */
#ifndef O_RDONLY
#define	O_RDONLY	0
#endif

#define	MAGIC_STAT	105	/* Magic status returned by child, if
				   it can't exec compress.  We hope compress
				   never returns this status! */
/*
 * The record pointed to by save_rec should not be overlaid
 * when reading in a new tape block.  Copy it to record_save_area first, and
 * change the pointer in *save_rec to point to record_save_area.
 * Saved_recno records the record number at the time of the save.
 * This is used by annofile() to print the record number of a file's
 * header record.
 */
static union record **save_rec;
static union record record_save_area;
static int	    saved_recno;

/*
 * PID of child compress program, if f_compress.
 */
static int	compress_pid;

/*
 * Record number of the start of this block of records
 */
static int	baserec;

/*
 * Error recovery stuff
 */
static int	r_error_count;


/*
 * Return the location of the next available input or output record.
 */
union record *
findrec()
{
	if (ar_record == ar_last) {
		flush_archive();
		if (ar_record == ar_last)
			return (union record *)NULL;	/* EOF */
	}
	return ar_record;
}


/*
 * Indicate that we have used all records up thru the argument.
 * (should the arg have an off-by-1? XXX FIXME)
 */
void
userec(rec)
	union record *rec;
{
	while(rec >= ar_record)
		ar_record++;
	/*
	 * Do NOT flush the archive here.  If we do, the same
	 * argument to userec() could mean the next record (if the
	 * input block is exactly one record long), which is not what
	 * is intended.
	 */
	if (ar_record > ar_last)
		abort();
}


/*
 * Return a pointer to the end of the current records buffer.
 * All the space between findrec() and endofrecs() is available
 * for filling with data, or taking data from.
 */
union record *
endofrecs()
{
	return ar_last;
}


/*
 * Open an archive file.  The argument specifies whether we are
 * reading or writing.
 */
open_archive(read)
	int read;
{

	if (ar_file[0] == '-' && ar_file[1] == '\0') {
		if (read)	archive = STDIN;
		else		archive = STDOUT;
	} else if (read) {
		archive = open(ar_file, O_RDONLY);
	} else {
		archive = creat(ar_file, 0666);
	}

	if (archive < 0) {
		perror(ar_file);
		exit(EX_BADARCH);
	}

	/*NOSTRICT*/
	ar_block = (union record *) valloc((unsigned)blocksize);
	if (!ar_block) {
		fprintf(stderr,
		"tar: could not allocate memory for blocking factor %d\n",
			blocking);
		exit(EX_ARGSBAD);
	}

	ar_record = ar_block;
	ar_last   = ar_block + blocking;

	/*
	 * Handle compressed archives.
	 *
	 * FIXME, currently supported for reading only.
	 * FIXME, writing involves forking again for a small process
	 * that will reblock the output of compress to the user's specs.
	 */
	if (f_compress) {
		int pipes[2];
		int err;

		if (!read) {
			fprintf(stderr,
				"tar: cannot write compressed archives yet.\n");
			exit(EX_ARGSBAD);
		}

		/* Create a pipe to get compress's output to us */
		err = pipe(pipes);
		if (err < 0) {
			perror ("tar: cannot create pipe to compress");
			exit(EX_SYSTEM);
		}
		
		/* Fork compress process */
		compress_pid = fork();
		if (compress_pid < 0) {
			perror("tar: cannot fork compress");
			exit(EX_SYSTEM);
		}

		/*
		 * Child process.
		 * 
 		 * Move input to stdin, write side of pipe to stdout,
		 * then exec compress.
		 */
		if (compress_pid == 0) {
			(void) close (pipes[PREAD]);	/* We won't use it */
			if (archive != STDIN) {
				(void) close(STDIN);
				err = dup(archive);
				if (err != 0) {
					perror(
					 "tar: cannot dup input to stdin");
					exit(EX_SYSTEM);
				}
				(void) close(archive);
			}
			if (pipes[PWRITE] != STDOUT) {
				(void) close (STDOUT);
				err = dup (pipes[PWRITE]);
				if (err != STDOUT) {
					perror(
					  "tar: cannot dup pipe output");
					exit(MAGIC_STAT);
				}
				(void) close (pipes[PWRITE]);
			}
			execlp("compress", "compress", "-d", (char *)0);
			perror("tar: cannot exec compress");
			exit(MAGIC_STAT);
		}

		/*
		 * Parent process.  Clean up.
		 * FIXME, note that this may leave standard input closed,
		 * if the compressed archive was on standard input.
		 */
		(void) close (archive);		/* Close compressed archive */
		(void) close (pipes[PWRITE]);	/* Close write side of pipe */
		archive = pipes[PREAD];		/* Read side is our archive */

#ifdef BSD42
		f_reblock++;		/* Pipe will give random # of bytes */
#endif BSD42
	}

	ar_reading = read;
	if (read) {
		ar_last = ar_block;		/* Set up for 1st block = # 0 */
		flush_archive();
	}
}


/*
 * Remember a union record * as pointing to something that we
 * need to keep when reading onward in the file.  Only one such
 * thing can be remembered at once, and it only works when reading
 * an archive.
 */
saverec(pointer)
	union record **pointer;
{

	save_rec = pointer;
	saved_recno = baserec + ar_record - ar_block;
}

/*
 * Perform a write to flush the buffer.
 */
fl_write()
{
	int err;

	err = write(archive, ar_block->charptr, blocksize);
	if (err == blocksize) return;
	/* FIXME, multi volume support on write goes here */
	if (err < 0)
		perror(ar_file);
	else
		fprintf(stderr, "tar: %s: write failed, short %d bytes\n",
			ar_file, blocksize - err);
	exit(EX_BADARCH);
}


/*
 * Handle read errors on the archive.
 *
 * If the read should be retried, readerror() returns to the caller.
 */
void
readerror()
{
#	define	READ_ERROR_MAX	10

	read_error_flag++;		/* Tell callers */

	annorec(stderr, tar);
	fprintf(stderr, "Read error on ");
	perror(ar_file);

	if (baserec == 0) {
		/* First block of tape.  Probably stupidity error */
		exit(EX_BADARCH);
	}	

	/*
	 * Read error in mid archive.  We retry up to READ_ERROR_MAX times
	 * and then give up on reading the archive.  We set read_error_flag
	 * for our callers, so they can cope if they want.
	 */
	if (r_error_count++ > READ_ERROR_MAX) {
		annorec(stderr, tar);
		fprintf(stderr, "Too many errors, quitting.\n");
		exit(EX_BADARCH);
	}
	return;
}


/*
 * Perform a read to flush the buffer.
 */
fl_read()
{
	int err;		/* Result from system call */
	int left;		/* Bytes left */
	char *more;		/* Pointer to next byte to read */

	/*
	 * Clear the count of errors.  This only applies to a single
	 * call to fl_read.  We leave read_error_flag alone; it is
	 * only turned off by higher level software.
	 */
	r_error_count = 0;	/* Clear error count */

	/*
	 * If we are about to wipe out a record that
	 * somebody needs to keep, copy it out to a holding
	 * area and adjust somebody's pointer to it.
	 */
	if (save_rec &&
	    *save_rec >= ar_record &&
	    *save_rec < ar_last) {
		record_save_area = **save_rec;
		*save_rec = &record_save_area;
	}
error_loop:
	err = read(archive, ar_block->charptr, blocksize);
	if (err == blocksize) return;
	if (err < 0) {
		readerror();
		goto error_loop;	/* Try again */
	}

	more = ar_block->charptr + err;
	left = blocksize - err;

again:
	if (0 == (((unsigned)left) % RECORDSIZE)) {
		/* FIXME, for size=0, multi vol support */
		/* On the first block, warn about the problem */
		if (!f_reblock && baserec == 0 && f_verbose) {
			annorec(stderr, tar);
			fprintf(stderr, "Blocksize = %d records\n",
				err / RECORDSIZE);
		}
		ar_last = ar_block + ((unsigned)(blocksize - left))/RECORDSIZE;
		return;
	}
	if (f_reblock) {
		/*
		 * User warned us about this.  Fix up.
		 */
		if (left > 0) {
error_loop_2:
			err = read(archive, more, left);
			if (err < 0) {
				readerror();
				goto error_loop_2;	/* Try again */
			}
			if (err == 0) {
				annorec(stderr, tar);
				fprintf(stderr,
		"%s: eof not on block boundary, strange...\n",
					ar_file);
				exit(EX_BADARCH);
			}
			left -= err;
			more += err;
			goto again;
		}
	} else {
		annorec(stderr, tar);
		fprintf(stderr, "%s: read %d bytes, strange...\n",
			ar_file, err);
		exit(EX_BADARCH);
	}
}


/*
 * Flush the current buffer to/from the archive.
 */
flush_archive()
{
	baserec += ar_last - ar_block;/* Keep track of block #s */
	ar_record = ar_block;		/* Restore pointer to start */
	ar_last = ar_block + blocking;	/* Restore pointer to end */

	if (!ar_reading) 
		fl_write();
	else
		fl_read();
}

/*
 * Close the archive file.
 */
close_archive()
{
	int child;
	int status;

	if (!ar_reading) flush_archive();
	(void) close(archive);

	if (f_compress) {
		/*
		 * Loop waiting for the right child to die, or for
		 * no more kids.
		 */
		while (((child = wait(&status)) != compress_pid) && child != -1)
			;

		if (child != -1) {
			switch (TERM_SIGNAL(status)) {
			case 0:		/* Terminated by itself */
				if (TERM_VALUE(status) == MAGIC_STAT) {
					exit(EX_SYSTEM);/* Child had trouble */
				}
				if (TERM_VALUE(status))
					fprintf(stderr,
				  "tar: compress child returned status %d\n",
						TERM_VALUE(status));
			case SIGPIPE:
				break;		/* This is OK. */

			default:
				fprintf(stderr,
				 "tar: compress child died with signal %d%s\n",
				 TERM_SIGNAL(status),
				 TERM_COREDUMP(status)? " (core dumped)": "");
			}
		}
	}
}


/*
 * Message management.
 *
 * anno writes a message prefix on stream (eg stdout, stderr).
 *
 * The specified prefix is normally output followed by a colon and a space.
 * However, if other command line options are set, more output can come
 * out, such as the record # within the archive.
 *
 * If the specified prefix is NULL, no output is produced unless the
 * command line option(s) are set.
 *
 * If the third argument is 1, the "saved" record # is used; if 0, the
 * "current" record # is used.
 */
void
anno(stream, prefix, savedp)
	FILE	*stream;
	char	*prefix;
	int	savedp;
{
#	define	MAXANNO	50
	char	buffer[MAXANNO];	/* Holds annorecment */
#	define	ANNOWIDTH 13
	int	space;

	if (f_sayblock) {
		if (prefix) {
			fputs(prefix, stream);
			putc(' ', stream);
		}
		sprintf(buffer, "rec %d: ",
			savedp?	saved_recno:
				baserec + ar_record - ar_block);
		fputs(buffer, stream);
		space = ANNOWIDTH - strlen(buffer);
		if (space > 0) {
			fprintf(stream, "%*s", space, "");
		}
	} else if (prefix) {
		fputs(prefix, stream);
		fputs(": ", stream);
	}
}
SHAR_EOF
cat << \SHAR_EOF > create.c
/*
 * Create a tar archive.
 *
 * Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu.
 *
 * @(#)create.c 1.19 9/9/86 Public Domain - gnu
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>

#ifdef BSD42
#include <sys/dir.h>
#else
/*
 * FIXME: On other systems there is no standard place for the header file
 * for the portable directory access routines.  Change the #include line
 * below to bring it in from wherever it is.
 */
#include "ndir.h"
#endif

#ifdef USG
#include <sys/sysmacros.h>	/* major() and minor() defined here */
#endif

/*
 * V7 doesn't have a #define for this.
 */
#ifndef O_RDONLY
#define	O_RDONLY	0
#endif

#include "tar.h"

/*
 * If there are no symbolic links, there is no lstat().  Use stat().
 */
#ifndef S_IFLNK
#define lstat stat
#endif

extern char	*malloc();
extern char	*strcpy();
extern char	*strncpy();
extern int	errno;

union record *start_header();
void finish_header();
void finduname();
void findgname();
char *name_next();
void to_oct();


void
create_archive()
{
	register char	*p;

	open_archive(0);		/* Open for writing */

	while (p = name_next()) {
		dump_file(p);
	}

	write_eot();
	close_archive();
	name_close();
}		

/*
 * Dump a single file.  If it's a directory, recurse.
 * Result is 1 for success, 0 for failure.
 */
int
dump_file(p)
	char	*p;			/* File name to dump */
{
	struct stat	statbuf[1];
	union record	*header;
	char type;

	/*
	 * Use stat if following (rather than dumping) 4.2BSD's
	 * symbolic links.  Otherwise, use lstat (which, on non-4.2
	 * systems, is #define'd to stat anyway.
	 */
	if (0 != f_follow_links? stat(p, statbuf): lstat(p, statbuf))
	{
badperror:
		perror(p);
badfile:
		errors++;
		return 0;
	}

	switch (statbuf->st_mode & S_IFMT) {

	case S_IFREG:			/* Regular file */
	{
		int	f;		/* File descriptor */
		int	bufsize, count;
		register long	sizeleft;
		register union record 	*start;

		/*
		 * Handle a regular file with multiple links.
		 *
		 * We maintain a list of all such files that we've written so
		 * far.  Any time we see another, we check the list and
		 * avoid dumping the data again if we've done it once already.
		 */
		if (statbuf->st_nlink > 1) {
			register struct link	*lp;

			/* First quick and dirty.  Hashing, etc later FIXME */
			for (lp = linklist; lp; lp = lp->next) {
				if (lp->ino == statbuf->st_ino &&
				    lp->dev == statbuf->st_dev) {
					/* We found a link. */
					statbuf->st_size = 0;
					header = start_header(p, statbuf);
					if (header == NULL) goto badfile;
					strcpy(header->header.linkname,
						lp->name);
					header->header.linkflag = LF_LINK;
					finish_header(header);
					if (f_verbose)
						annorec(stdout, (char *)NULL);
						printf("%s link to %s\n",
							p, lp->name);
			/* Maybe remove from list after all links found? */
		/* If so, have to compare names in case he dumps twice. */
			/* Later: I don't understand the above.  If she
			 * dumps the file twice, it would be BAD to dump
			 * it the second time as a link...  gnu 25Jul86
			 */
					/* FIXME */
					goto donefile;
				}
			}

			/* Not found.  Add it to the list. */
			lp = (struct link *) malloc( (unsigned)
				(strlen(p) + sizeof(struct link) - NAMSIZ));
			lp->ino = statbuf->st_ino;
			lp->dev = statbuf->st_dev;
			strcpy(lp->name, p);
			lp->next = linklist;
			linklist = lp;
		}

		sizeleft = statbuf->st_size;
		/* Don't bother opening empty, world readable files. */
		if (sizeleft > 0 || 0444 != (0444 & statbuf->st_mode)) {
			f = open(p, O_RDONLY);
			if (f < 0) goto badperror;
		} else {
			f = -1;
		}
		header = start_header(p, statbuf);
		if (header == NULL) goto badfile;
		finish_header(header);
		while (sizeleft > 0) {
			start = findrec();
			bufsize = endofrecs()->charptr - start->charptr;
			if (sizeleft < bufsize)
				bufsize = sizeleft;
			count = read(f, start->charptr, bufsize);
			if (count < 0) {
				annorec(stderr, tar);
				fprintf(stderr,
				  "read error at byte %ld, reading %d bytes, in file ",
					statbuf->st_size - sizeleft,
					bufsize);
				perror(p);	/* FIXME */
				goto padit;
			}
			sizeleft -= count;
			userec(start+(count-1)/RECORDSIZE);
			if (count == bufsize) continue;
			annorec(stderr, tar);
			fprintf(stderr,
	"%s: file shrunk by %d bytes, padding with zeros.\n",
				p, sizeleft);
			goto padit;		/* Short read */
		}
		if (f >= 0)
			(void)close(f);

		/* Clear last block garbage to zeros, FIXME */

		if (f_verbose) {
			annorec(stdout, (char *)NULL);
			printf("%s\n", p);
		}
	donefile:
		break;

		/*
		 * File shrunk or gave error, pad out tape to match
		 * the size we specified in the header.
		 */
	padit:
		abort();
	}

#ifdef S_IFLNK
	case S_IFLNK:			/* Symbolic link */
	{
		int size;

		statbuf->st_size = 0;		/* Force 0 size on symlink */
		header = start_header(p, statbuf);
		if (header == NULL) goto badfile;
		size = readlink(p, header->header.linkname, NAMSIZ);
		if (size < 0) goto badperror;
		if (size == NAMSIZ) {
			annorec(stderr, tar);
			fprintf(stderr,
				"%s: symbolic link too long\n", p);
			break;
		}
		header->header.linkname[size] = '\0';
		header->header.linkflag = LF_SYMLINK;
		finish_header(header);		/* Nothing more to do to it */
		if (f_verbose) {
			annorec(stdout, (char *)NULL);
			printf("%s\n", p);
		}
	}
		break;
#endif

	case S_IFDIR:			/* Directory */
	{
		register DIR *dirp;
		register struct direct *d;
		char namebuf[NAMSIZ+2];
		register int len;

		/* Build new prototype name */
		strncpy(namebuf, p, sizeof (namebuf));
		len = strlen(namebuf);
		while (len >= 1 && '/' == namebuf[len-1]) 
			len--;			/* Delete trailing slashes */
		namebuf[len++] = '/';		/* Now add exactly one back */

		/*
		 * Output directory header record with permissions
		 * FIXME, do this AFTER files, to avoid R/O dir problems?
		 * If Unix Std format, don't put / on end of dir name
		 * If old archive format, don't write record at all.
		 */
		if (!f_oldarch) {
			statbuf->st_size = 0;	/* Force 0 size on dir */
			/*
			 * If people could really read standard archives,
			 * this should be:		(FIXME)
			header = start_header(f_standard? p: namebuf, statbuf);
			 * but since they'd interpret LF_DIR records as
			 * regular files, we'd better put the / on the name.
			 */
			header = start_header(namebuf, statbuf);
			if (header == NULL)
				goto badfile;	/* eg name too long */
			if (f_standard) {
				header->header.linkflag = LF_DIR;
			}
			finish_header(header);	/* Done with directory header */
		}
		if (f_verbose) {
			annorec(stdout, (char *)NULL);
			printf("%s\n", p);
		}

		/* Hack to remove "./" from the front of all the file names */
		if (len == 2 && namebuf[0] == '.') {
			len = 0;
		}

		/* Now output all the files in the directory */
		errno = 0;
		dirp = opendir(p);
		if (!dirp) {
			if (errno) {
				perror (p);
			} else {
				annorec(stderr, tar);
				fprintf(stderr, "%s: error opening directory",
					p);
			}
			break;
		}
		
		/* Should speed this up by cd-ing into the dir, FIXME */
		while (NULL != (d=readdir(dirp))) {
			/* Skip . and .. */
			if (d->d_name[0] == '.') {
				if (d->d_name[1] == '\0') continue;
				if (d->d_name[1] == '.') {
					if (d->d_name[2] == '\0') continue;
				}
			}
			if (d->d_namlen + len >= NAMSIZ) {
				annorec(stderr, tar);
				fprintf(stderr, "%s%s: name too long\n", 
					namebuf, d->d_name);
				continue;
			}
			strcpy(namebuf+len, d->d_name);
			dump_file(namebuf);
		}

		closedir(dirp);
	}
		break;

	case S_IFCHR:			/* Character special file */
		type = LF_CHR;
		goto easy;

	case S_IFBLK:			/* Block     special file */
		type = LF_BLK;
		goto easy;

#ifdef S_IFIFO
	case S_IFIFO:			/* Fifo      special file */
		type = LF_FIFO;
#endif S_IFIFO

	easy:
		if (!f_standard) goto unknown;

		statbuf->st_size = 0;		/* Force 0 size */
		header = start_header(p, statbuf);
		if (header == NULL) goto badfile;	/* eg name too long */

		header->header.linkflag = type;
		if (type != LF_FIFO) {
			to_oct((long) major(statbuf->st_rdev), 8,
				header->header.devmajor);
			to_oct((long) minor(statbuf->st_rdev), 8,
				header->header.devminor);
		}

		finish_header(header);
		if (f_verbose) {
			annorec(stdout, (char *)NULL);
			printf("%s\n", p);
		}
		break;

	default:
	unknown:
		annorec(stderr, tar);
		fprintf(stderr,
			"%s: Unknown file type; file ignored.\n", p);
		break;
	}

	return 1;			/* Success */
}


/*
 * Make a header block for the file  name  whose stat info is  st .
 * Return header pointer for success, NULL if the name is too long.
 */
union record *
start_header(name, st)
	char	*name;
	register struct stat *st;
{
	register union record *header;

	header = (union record *) findrec();
	bzero(header->charptr, sizeof(*header)); /* XXX speed up */
	strcpy(header->header.name, name);
	if (header->header.name[NAMSIZ-1]) {
		annorec(stderr, tar);
		fprintf(stderr, "%s: name too long\n", name);
		return NULL;
	}
	to_oct((long) (st->st_mode & ~S_IFMT),
					8,  header->header.mode);
	to_oct((long) st->st_uid,	8,  header->header.uid);
	to_oct((long) st->st_gid,	8,  header->header.gid);
	to_oct((long) st->st_size,	1+12, header->header.size);
	to_oct((long) st->st_mtime,	1+12, header->header.mtime);
	/* header->header.linkflag is left as null */

#ifndef NONAMES
	/* Fill in new Unix Standard fields if desired. */
	if (f_standard) {
		header->header.linkflag = LF_NORMAL;	/* New default */
		strcpy(header->header.magic, TMAGIC);	/* Mark as Unix Std */
		finduname(header->header.uname, st->st_uid);
		findgname(header->header.gname, st->st_gid);
	}
#endif
	return header;
}

/* 
 * Finish off a filled-in header block and write it out.
 */
void
finish_header(header)
	register union record *header;
{
	register int	i, sum;
	register char	*p;

	bcopy(CHKBLANKS, header->header.chksum, sizeof(header->header.chksum));

	sum = 0;
	p = header->charptr;
	for (i = sizeof(*header); --i >= 0; ) {
		/*
		 * We can't use unsigned char here because of old compilers,
		 * e.g. V7.
		 */
		sum += 0xFF & *p++;
	}

	/*
	 * Fill in the checksum field.  It's formatted differently
	 * from the other fields:  it has [6] digits, a null, then a
	 * space -- rather than digits, a space, then a null.
	 * We use to_oct then write the null in over to_oct's space.
	 * The final space is already there, from checksumming, and
	 * to_oct doesn't modify it.
	 *
	 * This is a fast way to do:
	 * (void) sprintf(header->header.chksum, "%6o", sum);
	 */
	to_oct((long) sum,	8,  header->header.chksum);
	header->header.chksum[6] = '\0';	/* Zap the space */

	userec(header);
	return;
}


/*
 * Quick and dirty octal conversion.
 * Converts long "value" into a "digs"-digit field at "where",
 * including a trailing space and room for a null.  "digs"==3 means
 * 1 digit, a space, and room for a null.
 *
 * We assume the trailing null is already there and don't fill it in.
 * This fact is used by start_header and finish_header, so don't change it!
 *
 * This should be equivalent to:
 *	(void) sprintf(where, "%*lo ", digs-2, value);
 * except that sprintf fills in the trailing null and we don't.
 */
void
to_oct(value, digs, where)
	register long	value;
	register int	digs;
	register char	*where;
{
	
	--digs;				/* Trailing null slot is left alone */
	where[--digs] = ' ';		/* Put in the space, though */

	/* Produce the digits -- at least one */
	do {
		where[--digs] = '0' + (value & 7);	/* one octal digit */
		value >>= 3;
	} while (digs > 0 && value != 0);

	/* Leading spaces, if necessary */
	while (digs > 0)
		where[--digs] = ' ';

}


/*
 * Write the EOT block(s).
 */
write_eot()
{
	union record *p;

	p = findrec();
	bzero(p->charptr, RECORDSIZE);
	userec(p);
	/* FIXME, only one EOT block should be needed. */
	p = findrec();
	bzero(p->charptr, RECORDSIZE);
	userec(p);
}
SHAR_EOF
cat << \SHAR_EOF > extract.c
/*
 * Extract files from a tar archive.
 *
 * Written 19 Nov 1985 by John Gilmore, ihnp4!hoptoad!gnu.
 *
 * @(#) extract.c 1.17 86/10/29 Public Domain - gnu
 */

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifdef BSD42
#include <sys/file.h>
#endif

#ifdef USG
#include <fcntl.h>
#endif

extern int errno;			/* From libc.a */
extern char *index();			/* From libc.a or port.c */

#include "tar.h"

extern union record *head;		/* Points to current tape header */
extern struct stat hstat[1];		/* Stat struct corresponding */

extern void print_header();
extern void skip_file();
extern void pr_mkdir();

int make_dirs();			/* Makes required directories */

time_t now = 0;				/* Current time */

/*
 * Extract a file from the archive.
 */
void
extract_archive()
{
	register char *data;
	int fd, check, namelen, written;
	long size;
	time_t acc_upd_times[2];
	int	standard;		/* Is header standard? */

	saverec(&head);			/* Make sure it sticks around */
	userec(head);			/* And go past it in the archive */
	decode_header(head, hstat, &standard, 1);	/* Snarf fields */

	/* Print the record from 'head' and 'hstat' */
	if (f_verbose)
		print_header();

	switch (head->header.linkflag) {

	default:
		annofile(stderr, tar);
		fprintf(stderr, "Unknown file type %d for %s\n",
			head->header.linkflag, head->header.name);
		/* FALL THRU */

	case LF_OLDNORMAL:
	case LF_NORMAL:
		/*
		 * Appears to be a file.
		 * See if it's really a directory.
		 */
		namelen = strlen(head->header.name)-1;
		if (head->header.name[namelen] == '/')
			goto really_dir;

		/* FIXME, deal with protection issues */
		/* FIXME, f_keep doesn't work on V7, st_mode loses too */
	again_file:
		fd = open(head->header.name,
			f_keep?
			O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_EXCL:
			O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
			hstat->st_mode);
		if (fd < 0) {
			if (make_dirs(head->header.name))
				goto again_file;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make file ");
			perror(head->header.name);
			skip_file((long)hstat->st_size);
			goto quit;
		}

		for (size = hstat->st_size;
		     size > 0;
		     size -= written) {
			/*
			 * Locate data, determine max length
			 * writeable, write it, record that
			 * we have used the data, then check
			 * if the write worked.
			 */
			data = findrec()->charptr;
			written = endofrecs()->charptr - data;
			if (written > size) written = size;
			errno = 0;
			check = write (fd, data, written);
			/*
			 * The following is in violation of strict
			 * typing, since the arg to userec
			 * should be a struct rec *.  FIXME.
			 */
			userec(data + written - 1);
			if (check == written) continue;
			/*
			 * Error in writing to file.
			 * Print it, skip to next file in archive.
			 */
			annofile(stderr, tar);
			fprintf(stderr,
	"Tried to write %d bytes to file, could only write %d:\n",
				written, check);
			perror(head->header.name);
			(void) close(fd);
			skip_file((long)(size - written));
			goto quit;
		}

		check = close(fd);
		if (check < 0) {
			annofile(stderr, tar);
			fprintf(stderr, "Error while closing ");
			perror(head->header.name);
		}
		
		/* FIXME, deal with uid/gid/mtimes/suid */

		/*
		 * Set the modified time of the file.
		 * 
		 * Note that we set the accessed time to "now", which
		 * is really "the time we started extracting files".
		 */
		if (!f_modified) {
			if (!now)
				now = time((time_t *)0); /* Just do it once */
			acc_upd_times[0] = now;	         /* Accessed now */
			acc_upd_times[1] = hstat->st_mtime; /* Mod'd */
			if (utime(head->header.name, acc_upd_times) < 0) {
				annofile(stderr, tar);
				perror(head->header.name);
			}
		}

		/*
		 * If '-p' is not set, OR if the file has pretty normal
		 * mode bits, we can skip the chmod and save a sys call.
		 * This works because we did umask(0) if -p is set, so
		 * the open() that created the file will have set the modes
		 * properly.  
		 * FIXME: I don't know what open() does w/UID/GID/SVTX bits.
		 * However, if we've done a chown(), they got reset.
		 */
		if (f_use_protection
		    && (hstat->st_mode & (S_ISUID|S_ISGID|S_ISVTX))) {
			if (chmod(head->header.name, (int)hstat->st_mode) < 0) {
				annofile(stderr, tar);
				perror(head->header.name);
			}
		}

	quit:
		break;

	case LF_LINK:
	again_link:
		check = link (head->header.linkname,
			      head->header.name);
		/* FIXME, don't worry uid, gid, etc... */
		if (check == 0)
			break;
		if (make_dirs(head->header.linkname))
			goto again_link;
		annofile(stderr, tar);
		fprintf(stderr, "Could not link %s to ",
			head->header.name);
		perror(head->header.linkname);
		break;

#ifdef S_IFLNK
	case LF_SYMLINK:
	again_symlink:
		check = symlink(head->header.linkname,
			        head->header.name);
		/* FIXME, don't worry uid, gid, etc... */
		if (check == 0)
			break;
		if (make_dirs(head->header.linkname))
			goto again_symlink;
		annofile(stderr, tar);
		fprintf(stderr, "Could not create symlink ");
		perror(head->header.linkname);
		break;
#endif

	case LF_CHR:
		hstat->st_mode |= S_IFCHR;
		goto make_node;

	case LF_BLK:
		hstat->st_mode |= S_IFBLK;
	make_node:
		check = mknod(head->header.name, (int) hstat->st_mode,
			(int) hstat->st_dev);
		if (check != 0) {
			if (make_dirs(head->header.name))
				goto make_node;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make special file ");
			perror(head->header.name);
			break;
		};
		break;

	case LF_DIR:
		/* Check for trailing / */
		namelen = strlen(head->header.name)-1;
	really_dir:
		while (namelen && head->header.name[namelen] == '/')
			head->header.name[namelen--] = '\0';	/* Zap / */
		
		/* FIXME, deal with umask */
	again_dir:
		check = mkdir(head->header.name, (int)hstat->st_mode);
		if (check != 0) {
			if (make_dirs(head->header.name))
				goto again_dir;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make directory ");
			perror(head->header.name);
			break;
		}
		
		/* FIXME, deal with uid/gid */
		/* FIXME, Remember timestamps for after files created? */
		break;

	case LF_FIFO:
		abort();	/* FIXME */
		break;

	}

	/* We don't need to save it any longer. */
	saverec((union record **) 0);	/* Unsave it */
}

/*
 * After a file/link/symlink/dir creation has failed, see if
 * it's because some required directory was not present, and if
 * so, create all required dirs.
 */
int
make_dirs(pathname)
	char *pathname;
{
	char *p;			/* Points into path */
	int madeone = 0;		/* Did we do anything yet? */
	int save_errno = errno;		/* Remember caller's errno */
	int check;

	if (errno != ENOENT)
		return 0;		/* Not our problem */

	for (p = index(pathname, '/'); p != NULL; p = index(p+1, '/')) {
		/* Avoid mkdir of empty string, if leading or double '/' */
		if (p == pathname || p[-1] == '/')
			continue;
		/* Avoid mkdir where last part of path is '.' */
		if (p[-1] == '.' && (p == pathname+1 || p[-2] == '/'))
			continue;
		*p = 0;				/* Truncate the path there */
		check = mkdir (pathname, 0777);	/* Try to create it as a dir */
		*p = '/';
		if (check == 0) {
			/* FIXME chown, chgrp it same as file being created */
			/* FIXME, show mode as modified by current umask */
			pr_mkdir(pathname, p-pathname, 0777);
			madeone++;		/* Remember if we made one */
			continue;
		}
		if (errno == EEXIST)		/* Directory already exists */
			continue;
		/*
		 * Some other error in the mkdir.  We return to the caller.
		 */
		break;
	}

	errno = save_errno;		/* Restore caller's errno */
	return madeone;			/* Tell them to retry if we made one */
}
SHAR_EOF
cat << \SHAR_EOF > getoldopt.c
/*
 * Plug-compatible replacement for getopt() for parsing tar-like
 * arguments.  If the first argument begins with "-", it uses getopt;
 * otherwise, it uses the old rules used by tar, dump, and ps.
 *
 * Written 25 August 1985 by John Gilmore (ihnp4!hoptoad!gnu) and placed
 * in the Pubic Domain for your edification and enjoyment.
 *
 * @(#)getoldopt.c 1.4 2/4/86 Public Domain - gnu
 */

#include <stdio.h>


int
getoldopt(argc, argv, optstring)
	int	argc;
	char	**argv;
	char	*optstring;
{
	extern char	*optarg;	/* Points to next arg */
	extern int	optind;		/* Global argv index */
	static char	*key;		/* Points to next keyletter */
	static char	use_getopt;	/* !=0 if argv[1][0] was '-' */
	extern char	*index();
	char		c;
	char		*place;

	optarg = NULL;
	
	if (key == NULL) {		/* First time */
		if (argc < 2) return EOF;
		key = argv[1];
		if (*key == '-')
			use_getopt++;
		else
			optind = 2;
	}

	if (use_getopt)
		return getopt(argc, argv, optstring);

	c = *key++;
	if (c == '\0') {
		key--;
		return EOF;
	}
	place = index(optstring, c);

	if (place == NULL || c == ':') {
		fprintf(stderr, "%s: unknown option %c\n", argv[0], c);
		return('?');
	}

	place++;
	if (*place == ':') {
		if (optind < argc) {
			optarg = argv[optind];
			optind++;
		} else {
			fprintf(stderr, "%s: %c argument missing\n",
				argv[0], c);
			return('?');
		}
	}

	return(c);
}
SHAR_EOF
cat << \SHAR_EOF > list.c
/*
 * List a tar archive.
 *
 * Also includes support routines for reading a tar archive.
 *
 * Pubic Domain version written 26 Aug 1985 by John Gilmore (ihnp4!hoptoad!gnu).
 *
 * @(#)list.c 1.18 9/23/86 Public Domain - gnu
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>

char *ctime();				/* From libc.a */

#define	isodigit(c)	( ((c) >= '0') && ((c) <= '7') )

#include "tar.h"

long from_oct();			/* Decode octal number */
void demode();				/* Print file mode */

union record *head;			/* Points to current archive header */
struct stat hstat[1];			/* Stat struct corresponding */

void print_header();
void skip_file();


/*
 * Main loop for reading an archive.
 */
void
read_and(do_something)
	void (*do_something)();
{
	int status = 1;
	int prev_status;

	name_gather();			/* Gather all the names */
	open_archive(1);		/* Open for reading */

	for(;;) {
		prev_status = status;
		status = read_header();
		switch (status) {

		case 1:			/* Valid header */
			/* We should decode next field (mode) first... */
			/* Ensure incoming names are null terminated. */
			head->header.name[NAMSIZ-1] = '\0';
			
			if (!name_match(head->header.name)) {
				/* Skip past it in the archive */
				userec(head);
				/* Skip to the next header on the archive */
				skip_file((long)hstat->st_size);
				continue;
			}

			(*do_something)();
			continue;

			/*
			 * If the previous header was good, tell them
			 * that we are skipping bad ones.
			 */
		case 0:			/* Invalid header */
		case0:
			userec(head);
			if (prev_status == 1) {
				annorec(stderr, tar);
				fprintf(stderr,
					"Skipping to next file header...\n");
			}
			continue;

		case 2:			/* Block of zeroes */
			if (f_ignorez)	
				goto case0;	/* Just skip if asked */
			/* FALL THRU */
		case EOF:		/* End of archive */
			break;
		}
		break;
	};

	close_archive();
	names_notfound();		/* Print names not found */
}		


/*
 * Print a header record, based on tar options.
 */
void
list_archive()
{

	/* Save the record */
	saverec(&head);

	/* Print the header record */
	print_header();

	/* Skip past it in the archive */
	saverec((union record **) 0);	/* Unsave it */
	userec(head);

	/* Skip to the next header on the archive */
	skip_file((long)hstat->st_size);
}


/*
 * Read a record that's supposed to be a header record.
 * Return its address in "head", and if it is good, the file's
 * size in hstat->st_size.
 *
 * Return 1 for success, 0 if the checksum is bad, EOF on eof,
 * 2 for a block full of zeros (EOF marker).
 *
 * You must always userec(head) to skip past the header which this
 * routine reads.
 */
int
read_header()
{
	register int	i;
	register long	sum, recsum;
	register char	*p;
	register union record *header;

	header = findrec();
	head = header;		/* This is our current header */
	if (NULL == header) return EOF;

	recsum = from_oct(8,  header->header.chksum);

	sum = 0;
	p = header->charptr;
	for (i = sizeof(*header); --i >= 0;) {
		/*
		 * We can't use unsigned char here because of old compilers,
		 * e.g. V7.
		 */
		sum += 0xFF & *p++;
	}

	/* Adjust checksum to count the "chksum" field as blanks. */
	for (i = sizeof(header->header.chksum); --i >= 0;)
		sum -= 0xFF & header->header.chksum[i];
	sum += ' '* sizeof header->header.chksum;	

	if (sum == recsum) {
		/*
		 * Good record.  Decode file size and return.
		 */
		if (header->header.linkflag == LF_LINK)
			hstat->st_size = 0;	/* Links 0 size on tape */
		else
			hstat->st_size = from_oct(1+12, header->header.size);
		return 1;
	}

	if (sum == 8*' ') {
		/*
		 * This is a zeroed block...whole block is 0's except
		 * for the 8 blanks we faked for the checksum field.
		 */
		return 2;
	}

	return 0;
}


/* 
 * Decode things from a file header record into a "struct stat".
 * Also set "*stdp" to !=0 or ==0 depending whether header record is "Unix
 * Standard" tar format or regular old tar format.
 *
 * read_header() has already decoded the checksum and length, so we don't.
 *
 * If wantug != 0, we want the uid/group info decoded from Unix Standard
 * tapes (for extraction).  If == 0, we are just printing anyway, so save time.
 */
decode_header(header, st, stdp, wantug)
	register union record	*header;
	register struct stat	*st;
	int	*stdp;
	int	wantug;
{

	st->st_mode = from_oct(8,  header->header.mode);
	st->st_mtime = from_oct(1+12, header->header.mtime);
	
	if (0==strcmp(header->header.magic, TMAGIC)) {
		/* Unix Standard tar archive */
		*stdp = 1;
		if (wantug) {
			st->st_uid = finduid(header->header.uname);
			st->st_gid = findgid(header->header.gname);
		}
		switch  (header->header.linkflag) 
		case LF_BLK: case LF_CHR:
		    st->st_dev = makedev(from_oct(8, header->header.devmajor),
			 		 from_oct(8, header->header.devminor));
	} else {
		/* Old fashioned tar archive */
		*stdp = 0;
		st->st_uid = from_oct(8,  header->header.uid);
		st->st_gid = from_oct(8,  header->header.gid);
		st->st_dev = 0;
	}
}


/*
 * Quick and dirty octal conversion.
 *
 * Result is -1 if the field is invalid (all blank, or nonoctal).
 */
long
from_oct(digs, where)
	register int	digs;
	register char	*where;
{
	register long	value;

	while (isspace(*where)) {		/* Skip spaces */
		where++;
		if (--digs <= 0)
			return -1;		/* All blank field */
	}
	value = 0;
	while (digs > 0 && isodigit(*where)) {	/* Scan til nonoctal */
		value = (value << 3) | (*where++ - '0');
		--digs;
	}

	if (digs > 0 && *where && !isspace(*where))
		return -1;			/* Ended on non-space/nul */

	return value;
}


/*
 * Actually print it.
 */
#define	UGSWIDTH	11		/* min width of User, group, size */
#define	DATEWIDTH	19		/* Last mod date */
static int	ugswidth = UGSWIDTH;	/* Max width encountered so far */

void
print_header()
{
	char modes[11];
	char *timestamp;
	char uform[11], gform[11];	/* These hold formatted ints */
	char *user, *group;
	char size[24];		/* Holds a formatted long or maj, min */
	long longie;		/* To make ctime() call portable */
	int	pad;
	int	header_std;	/* Is header standard or not? */

	annofile(stdout, (char *)NULL);

	if (f_verbose) {
		decode_header(head, hstat, &header_std, 0);

		/* File type and modes */
		modes[0] = '?';
		switch (head->header.linkflag) {
		case LF_NORMAL:
		case LF_OLDNORMAL:
		case LF_LINK:
				modes[0] = '-'; 
				if ('/' == head->header.name[strlen(head->header.name)-1])
					modes[0] = 'd';
				break;
		case LF_DIR:	modes[0] = 'd'; break;
		case LF_SYMLINK:modes[0] = 'l'; break;
		case LF_BLK:	modes[0] = 'b'; break;
		case LF_CHR:	modes[0] = 'c'; break;
		case LF_FIFO:	modes[0] = 'f'; break;
		case LF_CONTIG:	modes[0] = '='; break;
		}

		demode((unsigned)hstat->st_mode, modes+1);

		/* Timestamp */
		longie = hstat->st_mtime;
		timestamp = ctime(&longie);
		timestamp[16] = '\0';
		timestamp[24] = '\0';

		/* User and group names */
		if (*head->header.uname && header_std) {
			user  = head->header.uname;
		} else {
			user = uform;
			(void)sprintf(uform, "%d", (int)hstat->st_uid);
		}
		if (*head->header.gname && header_std) {
			group = head->header.gname;
		} else {
			group = gform;
			(void)sprintf(gform, "%d", (int)hstat->st_gid);
		}

		/* Format the file size or major/minor device numbers */
		switch (head->header.linkflag) {
		case LF_CHR:
		case LF_BLK:
			(void)sprintf(size, "%d, %d",
					major(hstat->st_dev),
					minor(hstat->st_dev));
			break;

		default:
			(void)sprintf(size, "%ld", (long)hstat->st_size);
		}

		/* Figure out padding and print the whole line. */
		pad = strlen(user) + strlen(group) + strlen(size) + 1;
		if (pad > ugswidth) ugswidth = pad;

		printf("%s %s/%s %*s%s %s %s %.*s",
			modes,
			user,
			group,
			ugswidth - pad,
			"",
			size,
			timestamp+4, timestamp+20,
			sizeof(head->header.name),
			head->header.name);
	} else {
		printf("%s", head->header.name);
	}

	if (f_verbose) switch (head->header.linkflag) {
	case LF_SYMLINK:
		printf(" -> %s\n", head->header.linkname);
		break;

	case LF_LINK:
		printf(" link to %s\n", head->header.linkname);
		break;

	default:
		printf(" unknown file type '%c'\n", head->header.linkflag);
		break;

	case LF_OLDNORMAL:
	case LF_NORMAL:
	case LF_CHR:
	case LF_BLK:
	case LF_DIR:
	case LF_FIFO:
	case LF_CONTIG:
		putc('\n', stdout);
		break;
	} else {
		putc('\n', stdout);
	}

	/* FIXME: we don't print major/minor device numbers */
}

/*
 * Print a similar line when we make a directory automatically.
 */
void
pr_mkdir(pathname, length, mode)
	char *pathname;
	int length;
	int mode;
{
	char modes[11];

	if (f_verbose) {
		/* File type and modes */
		modes[0] = 'd';
		demode((unsigned)mode, modes+1);

		annofile(stdout, (char *)NULL);
		printf("%s %*s %.*s\n",
			modes,
			ugswidth+DATEWIDTH,
			"Creating directory:",
			length,
			pathname);
	}
}


/*
 * Skip over <size> bytes of data in records in the archive.
 */
void
skip_file(size)
	register long size;
{
	union record *x;

	while (size > 0) {
		x = findrec();
		if (x == NULL) {	/* Check it... */
			annorec(stderr, tar);
			fprintf(stderr, "Unexpected EOF on archive file\n");
			exit(EX_BADARCH);
		}
		userec(x);
		size -= RECORDSIZE;
	}
}


/*
 * Decode the mode string from a stat entry into a 9-char string and a null.
 */
void
demode(mode, string)
	register unsigned mode;
	register char *string;
{
	register unsigned mask;
	register char *rwx = "rwxrwxrwx";

	for (mask = 0400; mask != 0; mask >>= 1) {
		if (mode & mask)
			*string++ = *rwx++;
		else {
			*string++ = '-';
			rwx++;
		}
	}

	if (mode & S_ISUID)
		if (string[-7] == 'x')
			string[-7] = 's';
		else
			string[-7] = 'S';
	if (mode & S_ISGID)
		if (string[-4] == 'x')
			string[-4] = 's';
		else
			string[-4] = 'S';
	if (mode & S_ISVTX)
		if (string[-1] == 'x')
			string[-1] = 't';
		else
			string[-1] = 'T';
	*string = '\0';
}
SHAR_EOF
cat << \SHAR_EOF > names.c
/*
 * Look up user and/or group names.
 *
 * This file should be modified for non-unix systems to do something
 * reasonable.
 *
 * @(#)names.c 1.1 9/9/86 Public Domain - gnu
 */ 
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include "tar.h"

static int	saveuid = -993;
static char	saveuname[TUNMLEN];
static int	my_uid = -993;

static int	savegid = -993;
static char	savegname[TGNMLEN];
static int	my_gid = -993;

#define myuid	( my_uid < 0? my_uid = getuid(): my_uid )
#define	mygid	( my_gid < 0? my_gid = getgid(): my_gid )


#ifndef NONAMES
/*
 * Look up a user or group name from a uid/gid, maintaining a cache.
 * FIXME, for now it's a one-entry cache.
 * FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
 *
 * This is ifdef'd because on Suns, it drags in about 38K of "yellow
 * pages" code, roughly doubling the program size.  Thanks guys.
 */
void
finduname(uname, uid)
	char	uname[TUNMLEN];
	int	uid;
{
	struct passwd	*pw;
	extern struct passwd *getpwuid ();

	if (uid != saveuid) {
		saveuid = uid;
		saveuname[0] = '\0';
		pw = getpwuid(uid); 
		if (pw) 
			strncpy(saveuname, pw->pw_name, TUNMLEN);
	}
	strncpy(uname, saveuname, TUNMLEN);
}

int
finduid(uname)
	char	uname[TUNMLEN];
{
	struct passwd	*pw;
	extern struct passwd *getpwnam();

	if (uname[0] != saveuname[0]	/* Quick test w/o proc call */
	    || 0!=strncmp(uname, saveuname, TUNMLEN)) {
		strncpy(saveuname, uname, TUNMLEN);
		pw = getpwnam(uname); 
		if (pw) {
			saveuid = pw->pw_uid;
		} else {
			saveuid = myuid;
		}
	}
	return saveuid;
}


void
findgname(gname, gid)
	char	gname[TGNMLEN];
	int	gid;
{
	struct group	*gr;
	extern struct group *getgrgid ();

	if (gid != savegid) {
		savegid = gid;
		savegname[0] = '\0';
		(void)setgrent();
		gr = getgrgid(gid); 
		if (gr) 
			strncpy(savegname, gr->gr_name, TGNMLEN);
	}
	(void) strncpy(gname, savegname, TGNMLEN);
}


int
findgid(gname)
	char	gname[TUNMLEN];
{
	struct group	*gr;
	extern struct group *getgrnam();

	if (gname[0] != savegname[0]	/* Quick test w/o proc call */
	    || 0!=strncmp(gname, savegname, TUNMLEN)) {
		strncpy(savegname, gname, TUNMLEN);
		gr = getgrnam(gname); 
		if (gr) {
			savegid = gr->gr_gid;
		} else {
			savegid = mygid;
		}
	}
	return savegid;
}
#endif
SHAR_EOF
cat << \SHAR_EOF > port.c
/*
 * @(#)port.c 1.6	86/08/11	Public Domain, by John Gilmore, 1986
 *
 * These are routines not available in all environments.
 *
 * I know this introduces an extra level of subroutine calls and is
 * slightly slower.  Frankly, my dear, I don't give a damn.  Let the
 * Missed-Em Vee losers suffer a little.  This software is proud to
 * have been written on a BSD system.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>

#include "port.h"

#ifndef BSD42
/*
 * lstat() is a stat() which does not follow symbolic links.
 * If there are no symbolic links, just use stat().
 */
int
lstat (path, buf)
	char *path;
	struct stat *buf;
{
	extern int stat ();
	return (stat (path, buf));
}

/*
 * valloc() does a malloc() on a page boundary.  On some systems,
 * this can make large block I/O more efficient.
 */
char *
valloc (size)
	unsigned size;
{
	extern char *malloc ();
	return (malloc (size));
}

/*
**				NMKDIR.C
**
** Written by Robert Rother, Mariah Corporation, August 1985. 
**
** I wrote this out of shear disgust with myself because I couldn't
** figure out how to do this in /bin/sh.
**
** If you want it, it's yours.  All I ask in return is that if you
** figure out how to do this in a Bourne Shell script you send me
** a copy.
**					sdcsvax!rmr or rmr@uscd
*
* Severely hacked over by John Gilmore to make a 4.2BSD compatible
* subroutine.	11Mar86; hoptoad!gnu
*/

/*
 * Make a directory.  Compatible with the mkdir() system call on 4.2BSD.
 */
int
mkdir(dpath, dmode)
	char *dpath;
	int dmode;
{
	int cpid, status;
	extern int errno;

	switch (cpid = fork()) {

	case -1:			/* Error in fork() */
		return(-1);		/* Errno is set already */

	case 0:				/* Child process */
		/*
		 * Cheap hack to set mode of new directory.  Since this
		 * child process is going away anyway, we zap its umask.
		 * FIXME, this won't suffice to set SUID, SGID, etc. on this
		 * directory.  Does anybody care?
		 */
		status = umask(0);	/* Get current umask */
		status = umask(status | (0777 & ~dmode)); /* Set for mkdir */
		execl("/bin/mkdir", "mkdir", dpath, (char *)0);
		_exit(-1);		/* Can't exec /bin/mkdir */
	
	default:			/* Parent process */
		while (cpid != wait(&status)) ;	/* Wait for kid to finish */
	}

	if (TERM_SIGNAL(status) != 0 || TERM_VALUE(status) != 0) {
		errno = EIO;		/* We don't know why, but */
		return -1;		/* /bin/mkdir failed */
	}

	return 0;
}
#endif

#ifdef USG
/*
 * Translate V7 style into Sys V style.
 */
#include <string.h>
#include <memory.h>

char *
index (s, c)
	char *s;
	int c;
{
	return (strchr (s, c));
}

char *
bcopy (s1, s2, n)
	char *s1, *s2;
	int n;
{
	(void) memcpy (s2, s1, n);
	return (s1);
}

void
bzero (s1, n)
	char *s1;
	int n;
{
	(void) memset(s1, 0, n);
}
#endif
SHAR_EOF
cat << \SHAR_EOF > port.h
/*
 * Portability declarations for public domain tar.
 *
 * @(#)port.h 1.1	86/03/11	Public Domain by John Gilmore, 1986
 */

/*
 * Everybody does wait() differently.  There seem to be no definitions
 * for this in V7 (e.g. you are supposed to shift and mask things out
 * using constant shifts and masks.)  So fuck 'em all -- my own non
 * standard but portable macros.  Don't change to a "union wait"
 * based approach -- the ordering of the elements of the struct 
 * depends on the byte-sex of the machine.  Foo!
 */
#define	TERM_SIGNAL(status)	((status) & 0x7F)
#define TERM_COREDUMP(status)	(((status) & 0x80) != 0)
#define TERM_VALUE(status)	((status) >> 8)


SHAR_EOF
cat << \SHAR_EOF > tar.c
/*
 * A public domain tar(1) program.
 * 
 * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
 *
 * @(#)tar.c 1.21 10/29/86 Public Domain - gnu
 */

#include <stdio.h>
#include <sys/types.h>		/* Needed for typedefs in tar.h */

extern char 	*malloc();
extern char	*strncpy();
extern char	*optarg;	/* Pointer to argument */
extern int	optind;		/* Global argv index from getopt */

/*
 * The following causes "tar.h" to produce definitions of all the
 * global variables, rather than just "extern" declarations of them.
 */
#define TAR_EXTERN /**/
#include "tar.h"

/*
 * We should use a conversion routine that does reasonable error
 * checking -- atoi doesn't.  For now, punt.  FIXME.
 */
#define intconv	atoi
extern int	getoldopt();
extern void	read_and();
extern void	list_archive();
extern void	extract_archive();
extern void	create_archive();

static FILE	*namef;		/* File to read names from */
static char	**n_argv;	/* Argv used by name routines */
static int	n_argc;	/* Argc used by name routines */
				/* They also use "optind" from getopt(). */

void	describe();


/*
 * Main routine for tar.
 */
main(argc, argv)
	int	argc;
	char	**argv;
{

	/* Uncomment this message in particularly buggy versions...
	fprintf(stderr,
	 "tar: You are running an experimental PD tar, maybe use /bin/tar.\n");
	 */

	tar = "tar";		/* Set program name */

	options(argc, argv);

	name_init(argc, argv);

	if (f_create) {
		if (f_extract || f_list) goto dupflags;
		create_archive();
	} else if (f_extract) {
		if (f_list) goto dupflags;
		read_and(extract_archive);
	} else if (f_list) {
		read_and(list_archive);
	} else {
dupflags:
		fprintf (stderr,
"tar: you must specify exactly one of the c, t, or x options\n");
		describe();
		exit(EX_ARGSBAD);
	}
	exit(0);
}


/*
 * Parse the options for tar.
 */
int
options(argc, argv)
	int	argc;
	char	**argv;
{
	register int	c;		/* Option letter */

	/* Set default option values */
	blocking = DEFBLOCKING;		/* From Makefile */
	ar_file = DEF_AR_FILE;		/* From Makefile */

	/* Parse options */
	while ((c = getoldopt(argc, argv, "b:BcdDf:hikmopstT:vxzZ")
		) != EOF) {
		switch (c) {

		case 'b':
			blocking = intconv(optarg);
			break;

		case 'B':
			f_reblock++;		/* For reading 4.2BSD pipes */
			break;

		case 'c':
			f_create++;
			break;

		case 'd':
			f_debug++;		/* Debugging code */
			break;			/* Yes, even with dbx */

		case 'D':
			f_sayblock++;		/* Print block #s for debug */
			break;			/* of bad tar archives */

		case 'f':
			ar_file = optarg;
			break;

		case 'h':
			f_follow_links++;	/* follow symbolic links */
			break;

		case 'i':
			f_ignorez++;		/* Ignore zero records (eofs) */
			/*
			 * This can't be the default, because Unix tar
			 * writes two records of zeros, then pads out the
			 * block with garbage.
			 */
			break;

		case 'k':			/* Don't overwrite files */
			f_keep++;
			break;

		case 'm':
			f_modified++;
			break;

		case 'o':			/* Generate old archive */
			f_oldarch++;
			break;

		case 'p':
			f_use_protection++;
			(void)umask(0);		/* Turn off kernel "help" */
			break;

		case 's':
			f_sorted_names++;	/* Names to extr are sorted */
			break;

		case 't':
			f_list++;
			break;

		case 'T':
			name_file = optarg;
			f_namefile++;
			break;

		case 'v':
			f_verbose++;
			break;

		case 'x':
			f_extract++;
			break;

		case 'z':		/* Easy to type */
		case 'Z':		/* Like the filename extension .Z */
			f_compress++;
			break;

		case '?':
			describe();
			exit(EX_ARGSBAD);

		}
	}

	blocksize = blocking * RECORDSIZE;
}


/* FIXME, describe tar options here */
void
describe()
{

	fputs("tar: valid options:\n\
-b N	blocking factor N (block size = Nx512 bytes)\n\
-B	reblock as we read (for reading 4.2BSD pipes)\n\
-c	create an archive\n\
-D	dump record number within archive with each message\n\
-f F	read/write archive from file or device F\n\
-h	don't dump symbolic links; dump the files they point to\n\
-i	ignore blocks of zeros in the archive, which normally mean EOF\n\
-k	keep existing files, don't overwrite them from the archive\n\
-m	don't extract file modified time\n\
-o	write an old V7 format archive, rather than ANSI [draft 6] format\n\
-p	do extract all protection information\n\
-s	list of names to extract is sorted to match the archive\n\
-t	list a table of contents of an archive\n\
-T F	get names to extract or create from file F\n\
-v	verbosely list what files we process\n\
-x	extract files from an archive\n\
-z or Z	run the archive through compress(1)\n\
", stderr);
}


/*
 * Set up to gather file names for tar.
 *
 * They can either come from stdin or from argv.
 */
name_init(argc, argv)
	int	argc;
	char	**argv;
{

	if (f_namefile) {
		if (optind < argc) {
			fprintf(stderr, "tar: too many args with -T option\n");
			exit(EX_ARGSBAD);
		}
		if (!strcmp(name_file, "-")) {
			namef = stdin;
		} else {
			namef = fopen(name_file, "r");
			if (namef == NULL) {
				fprintf(stderr, "tar: ");
				perror(name_file);
				exit(EX_BADFILE);
			}
		}
	} else {
		/* Get file names from argv, after options. */
		n_argc = argc;
		n_argv = argv;
	}
}

/*
 * Get the next name from argv or the name file.
 *
 * Result is in static storage and can't be relied upon across two calls.
 */
char *
name_next()
{
	static char	buffer[NAMSIZ+2];	/* Holding pattern */
	register char	*p;
	register char	*q;

	if (namef == NULL) {
		/* Names come from argv, after options */
		if (optind < n_argc)
			return n_argv[optind++];
		return (char *)NULL;
	}
	p = fgets(buffer, NAMSIZ+1 /*nl*/, namef);
	if (p == NULL) return p;	/* End of file */
	q = p+strlen(p)-1;		/* Find the newline */
	*q-- = '\0';			/* Zap the newline */
	while (*q == '/')  *q-- = '\0';	/* Zap trailing slashes too */
	return p;
}


/*
 * Close the name file, if any.
 */
name_close()
{

	if (namef != NULL && namef != stdin) fclose(namef);
}


/*
 * Gather names in a list for scanning.
 * Could hash them later if we really care.
 *
 * If the names are already sorted to match the archive, we just
 * read them one by one.  name_gather reads the first one, and it
 * is called by name_match as appropriate to read the next ones.
 * At EOF, the last name read is just left in the buffer.
 * This option lets users of small machines extract an arbitrary
 * number of files by doing "tar t" and editing down the list of files.
 */
name_gather()
{
	register char *p;
	static struct name namebuf[1];	/* One-name buffer */

	if (f_sorted_names) {
		p = name_next();
		if (p) {
			namebuf->length = strlen(p);
			if (namebuf->length >= sizeof namebuf->name) {
				fprintf(stderr, "Argument name too long: %s\n",
					p);
				namebuf->length = (sizeof namebuf->name) - 1;
			}
			strncpy(namebuf->name, p, namebuf->length);
			namebuf->next = (struct name *)NULL;
			namebuf->found = 0;
			namelist = namebuf;
			namelast = namelist;
		}
		return;
	}

	/* Non sorted names -- read them all in */
	while (NULL != (p = name_next())) {
		addname(p);
	}
}

/*
 * A name from the namelist has been found.
 * If it's just a list, 

/*
 * Add a name to the namelist.
 */
addname(name)
	char	*name;			/* pointer to name */
{
	register int	i;		/* Length of string */
	register struct name	*p;	/* Current struct pointer */

	i = strlen(name);
	/*NOSTRICT*/
	p = (struct name *)
		malloc((unsigned)(i + sizeof(struct name) - NAMSIZ));
	p->next = (struct name *)NULL;
	p->length = i;
	p->found = 0;
	strncpy(p->name, name, i);
	p->name[i] = '\0';	/* Null term */
	if (namelast) namelast->next = p;
	namelast = p;
	if (!namelist) namelist = p;
}


/*
 * Match a name from an archive, p, with a name from the namelist.
 *
 * FIXME: Allow regular expressions in the name list.
 */
name_match(p)
	register char *p;
{
	register struct name	*nlp;
	register int		len;

again:
	if (0 == (nlp = namelist))	/* Empty namelist is easy */
		return 1;
	len = strlen(p);
	for (; nlp != 0; nlp = nlp->next) {
		if ( nlp->name[0] == p[0]	/* First chars match */
		 && nlp->length <= len		/* Archive len >= specified */
		 && (p[nlp->length] == '\0' || p[nlp->length] == '/')
						/* Full match on file/dirname */
		 && strncmp(p, nlp->name, nlp->length) == 0) /* Name compare */
		{
			nlp->found = 1;		/* Remember it matched */
			return 1;		/* We got a match */
		}
	}
	/*
	 * Filename from archive not found in namelist.
	 * If we have the whole namelist here, just return 0.
	 * Otherwise, read the next name in and compare it.
	 * If this was the last name, namelist->found will remain on.
	 * If not, we loop to compare the newly read name.
	 */
	if (f_sorted_names && namelist->found) {
		name_gather();		/* Read one more */
		if (!namelist->found) goto again;
	}
	return 0;
}


/*
 * Print the names of things in the namelist that were not matched.
 */
names_notfound()
{
	register struct name	*nlp;
	register char		*p;

	for (nlp = namelist; nlp != 0; nlp = nlp->next) {
		if (!nlp->found) {
			fprintf(stderr, "tar: %s not found in archive\n",
				nlp->name);
		}
		/*
		 * We could free() the list, but the process is about
		 * to die anyway, so save some CPU time.  Amigas and
		 * other similarly broken software will need to waste
		 * the time, though.
		 */
#ifndef unix
		if (!f_sorted_names)
			free(nlp);
#endif unix
	}
	namelist = (struct name *)NULL;
	namelast = (struct name *)NULL;

	if (f_sorted_names) {
		while (0 != (p = name_next()))
			fprintf(stderr, "tar: %s not found in archive\n", p);
	}
}
SHAR_EOF
cat << \SHAR_EOF > tar.h
/*
 * Header file for public domain tar (tape archive) program.
 *
 * @(#)tar.h 1.20 86/10/29	Public Domain.
 *
 * Created 25 August 1985 by John Gilmore, ihnp4!hoptoad!gnu.
 */

/*
 * Kludge for handling systems that can't cope with multiple
 * external definitions of a variable.  In ONE routine (tar.c),
 * we #define TAR_EXTERN to null; here, we set it to "extern" if
 * it is not already set.
 */
#ifndef TAR_EXTERN
#define TAR_EXTERN extern
#endif

/*
 * Header block on tape.
 *
 * I'm going to use traditional DP naming conventions here.
 * A "block" is a big chunk of stuff that we do I/O on.
 * A "record" is a piece of info that we care about.
 * Typically many "record"s fit into a "block".
 */
#define	RECORDSIZE	512
#define	NAMSIZ	100
#define	TUNMLEN	32
#define	TGNMLEN	32

union record {
	char		charptr[RECORDSIZE];
	struct header {
		char	name[NAMSIZ];
		char	mode[8];
		char	uid[8];
		char	gid[8];
		char	size[12];
		char	mtime[12];
		char	chksum[8];
		char	linkflag;
		char	linkname[NAMSIZ];
		char	magic[8];
		char	uname[TUNMLEN];
		char	gname[TGNMLEN];
		char	devmajor[8];
		char	devminor[8];
	} header;
};

/* The checksum field is filled with this while the checksum is computed. */
#define	CHKBLANKS	"        "	/* 8 blanks, no null */

/* The magic field is filled with this if uname and gname are valid. */
#define	TMAGIC		"ustar  "	/* 7 chars and a null */

/* The linkflag defines the type of file */
#define	LF_OLDNORMAL	'\0'		/* Normal disk file, Unix compat */
#define	LF_NORMAL	'0'		/* Normal disk file */
#define	LF_LINK		'1'		/* Link to previously dumped file */
#define	LF_SYMLINK	'2'		/* Symbolic link */
#define	LF_CHR		'3'		/* Character special file */
#define	LF_BLK		'4'		/* Block special file */
#define	LF_DIR		'5'		/* Directory */
#define	LF_FIFO		'6'		/* FIFO special file */
#define	LF_CONTIG	'7'		/* Contiguous file */
/* Further link types may be defined later. */

/*
 * Exit codes from the "tar" program
 */
#define	EX_SUCCESS	0		/* success! */
#define	EX_ARGSBAD	1		/* invalid args */
#define	EX_BADFILE	2		/* invalid filename */
#define	EX_BADARCH	3		/* bad archive */
#define	EX_SYSTEM	4		/* system gave unexpected error */


/*
 * Global variables
 */
TAR_EXTERN union record	*ar_block;	/* Start of block of archive */
TAR_EXTERN union record	*ar_record;	/* Current record of archive */
TAR_EXTERN union record	*ar_last;	/* Last+1 record of archive block */
TAR_EXTERN char		ar_reading;	/* 0 writing, !0 reading archive */
TAR_EXTERN int		blocking;	/* Size of each block, in records */
TAR_EXTERN int		blocksize;	/* Size of each block, in bytes */
TAR_EXTERN char		*ar_file;	/* File containing archive */
TAR_EXTERN char		*name_file;	/* File containing names to work on */
TAR_EXTERN char		*tar;		/* Name of this program */

/*
 * Flags from the command line
 */
TAR_EXTERN char	f_reblock;		/* -B */
TAR_EXTERN char	f_create;		/* -c */
TAR_EXTERN char	f_debug;		/* -d */
TAR_EXTERN char	f_sayblock;		/* -D */
TAR_EXTERN char	f_follow_links;		/* -h */
TAR_EXTERN char	f_ignorez;		/* -i */
TAR_EXTERN char	f_keep;			/* -k */
TAR_EXTERN char	f_modified;		/* -m */
TAR_EXTERN char	f_oldarch;		/* -o */
TAR_EXTERN char	f_use_protection;	/* -p */
TAR_EXTERN char	f_sorted_names;		/* -s */
TAR_EXTERN char	f_list;			/* -t */
TAR_EXTERN char	f_namefile;		/* -T */
TAR_EXTERN char	f_verbose;		/* -v */
TAR_EXTERN char	f_extract;		/* -x */
TAR_EXTERN char	f_compress;		/* -z */

/*
 * We now default to Unix Standard format rather than 4.2BSD tar format.
 * The code can actually produce all three:
 *	f_standard	ANSI standard
 *	f_oldarch	V7
 *	neither		4.2BSD
 * but we don't bother, since 4.2BSD can read ANSI standard format anyway.
 * The only advantage to the "neither" option is that we can cmp(1) our
 * output to the output of 4.2BSD tar, for debugging.
 */
#define		f_standard		(!f_oldarch)

/*
 * Structure for keeping track of filenames and lists thereof.
 */
struct name {
	struct name	*next;
	short		length;
	char		found;
	char		name[NAMSIZ+1];
};

TAR_EXTERN struct name	*namelist;	/* Points to first name in list */
TAR_EXTERN struct name	*namelast;	/* Points to last name in list */

TAR_EXTERN int		archive;	/* File descriptor for archive file */
TAR_EXTERN int		errors;		/* # of files in error */

/*
 *
 * Due to the next struct declaration, each routine that includes
 * "tar.h" must also include <sys/types.h>.  I tried to make it automatic,
 * but System V has no defines in <sys/types.h>, so there is no way of
 * knowing when it has been included.  In addition, it cannot be included
 * twice, but must be included exactly once.  Argghh!
 *
 * Thanks, typedef.  Thanks, USG.
 */
struct link {
	struct link	*next;
	dev_t		dev;
	ino_t		ino;
	short		linkcount;
	char		name[NAMSIZ+1];
};

TAR_EXTERN struct link	*linklist;	/* Points to first link in list */


/*
 * Error recovery stuff
 */
TAR_EXTERN char		read_error_flag;


/*
 * Declarations of functions available to the world.
 */
union record *findrec();
void userec();
union record *endofrecs();
void anno();
#define	 annorec(stream, msg)	anno(stream, msg, 0)	/* Cur rec */
#define	annofile(stream, msg)	anno(stream, msg, 1)	/* Saved rec */
SHAR_EOF
#	End of shell archive
exit 0
-- 
David P. Zimmerman	"When I'm having fun, the world doesn't exist."

Arpa: dpz@rutgers.rutgers.edu
Uucp: ...{harvard | seismo | pyramid}!rutgers!dpz