[comp.sources.unix] v12i069: Public-domain TAR, Part02/03

rsalz@uunet.UU.NET (Rich Salz) (11/30/87)

Submitted-by: John Gilmore <hoptoad!gnu@UUNET.UU.NET>
Posting-number: Volume 12, Issue 69
Archive-name: pdtar/part02


: To unbundle, sh this file
echo tar.c
cat >tar.c <<'@@@ Fin de tar.c'
/*
 * A public domain tar(1) program.
 * 
 * Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
 *
 * @(#)tar.c 1.34 11/6/87 Public Domain - gnu
 */

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

extern char 	*malloc();
extern char 	*getenv();
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	diff_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 || f_diff) goto dupflags;
		create_archive();
	} else if (f_extract) {
		if (f_list || f_diff) goto dupflags;
		extr_init();
		read_and(extract_archive);
	} else if (f_list) {
		if (f_diff) goto dupflags;
		read_and(list_archive);
	} else if (f_diff) {
		diff_init();
		read_and(diff_archive);
	} else {
dupflags:
		fprintf (stderr,
"tar: you must specify exactly one of the c, t, x, or d options\n");
		describe();
		exit(EX_ARGSBAD);
	}
	exit(0);
	/* NOTREACHED */
}


/*
 * 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 = getenv("TAPE");	/* From environment, or */
	if (ar_file == 0)
		ar_file = DEF_AR_FILE;	/* From Makefile */

	/* Parse options */
	while ((c = getoldopt(argc, argv, "b:BcdDf:hiklmopRstT: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_diff++;		/* Find difference tape/disk */
			break;

		case 'D':
			f_dironly++;		/* Dump dir, not contents */
			break;

		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 */
#ifdef NO_OPEN3
			fprintf(stderr,
				"tar: can't do -k option on this system\n");
			exit(EX_ARGSBAD);
#else
			f_keep++;
#endif
			break;

		case 'l':
			f_local_filesys++;
			break;

		case 'm':
			f_modified++;
			break;

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

		case 'p':
			f_use_protection++;
			break;

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

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

		case 't':
			f_list++;
			f_verbose++;		/* "t" output == "cv" or "xv" */
			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;
}


/* 
 * Print as much help as the user's gonna get.
 *
 * We have to sprinkle in the KLUDGE lines because too many compilers
 * cannot handle character strings longer than about 512 bytes.  Yuk!
 * In particular, MSDOS MSC 4.0 (and 5.0) and PDP-11 V7 Unix have this
 * problem.
 */
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	find differences between archive and file system\n\
-D	don't dump the contents of directories, just the directory\n\
", stderr); /* KLUDGE */ fputs("\
-f F	read/write archive from file or device F (or hostname:/ForD)\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\
-l	stay in the local file system (like dump(8)) when creating an archive\n\
", stderr); /* KLUDGE */ fputs("\
-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\
-R	dump record number within archive with each message\n\
-s	list of names to extract is sorted to match the archive\n\
-t	list a table of contents of an archive\n\
", stderr); /* KLUDGE */ fputs("\
-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;
	}
	for (;;) {
		p = fgets(buffer, NAMSIZ+1 /*nl*/, namef);
		if (p == NULL) return p;	/* End of file */
		q = p+strlen(p)-1;		/* Find the newline */
		if (q <= p) continue;		/* Ignore empty lines */
		*q-- = '\0';			/* Zap the newline */
		while (q > p && *q == '/')  *q-- = '\0'; /* Zap trailing /s */
		return p;
	}
	/* NOTREACHED */
}


/*
 * 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[0].length = strlen(p);
			if (namebuf[0].length >= sizeof namebuf[0].name) {
				fprintf(stderr, "Argument name too long: %s\n",
					p);
				namebuf[0].length = (sizeof namebuf[0].name) - 1;
			}
			strncpy(namebuf[0].name, p, namebuf[0].length);
			namebuf[0].name[ namebuf[0].length ] = 0;
			namebuf[0].next = (struct name *)NULL;
			namebuf[0].found = 0;
			namelist = namebuf;
			namelast = namelist;
		}
		return;
	}

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

/*
 * 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));
	if (!p) {
		fprintf(stderr,"tar: cannot allocate mem for namelist entry\n");
		exit(EX_SYSTEM);
	}
	p->next = (struct name *)NULL;
	p->length = i;
	strncpy(p->name, name, i);
	p->name[i] = '\0';	/* Null term */
	p->found = 0;
	p->regexp = 0;		/* Assume not a regular expression */
	p->firstch = 1;		/* Assume first char is literal */
	if (index(name, '*') || index(name, '[') || index(name, '?')) {
		p->regexp = 1;	/* No, it's a regexp */
		if (name[0] == '*' || name[0] == '[' || name[0] == '?')
			p->firstch = 0;		/* Not even 1st char literal */
	}

	if (namelast) namelast->next = p;
	namelast = p;
	if (!namelist) namelist = p;
}


/*
 * Match a name from an archive, p, with a name from the namelist.
 */
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 first chars don't match, quick skip */
		if (nlp->firstch && nlp->name[0] != p[0])
			continue;

		/* Regular expressions */
		if (nlp->regexp) {
			if (wildmat(p, nlp->name)) {
				nlp->found = 1;	/* Remember it matched */
				return 1;	/* We got a match */
			}
			continue;
		}

		/* Plain Old Strings */
		if (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
	}
	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);
	}
}
@@@ Fin de tar.c
echo create.c
cat >create.c <<'@@@ Fin de create.c'
/*
 * Create a tar archive.
 *
 * Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu.
 *
 * @(#)create.c 1.36 11/6/87 Public Domain - gnu
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

#ifndef V7
#include <fcntl.h>
#endif

#ifndef	MSDOS
#include <pwd.h>
#include <grp.h>
#endif

#ifdef BSD42
#include <sys/dir.h>
#else
#ifdef MSDOS
#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
#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

/*
 * Most people don't have a #define for this.
 */
#ifndef	O_BINARY
#define	O_BINARY	0
#endif

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

extern union record *head;		/* Points to current tape header */
extern struct stat hstat;		/* Stat struct corresponding */
extern int head_standard;		/* Tape header is in ANSI format */

/*
 * 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 void	bzero();
extern void	bcopy();
extern int	errno;

extern void print_header();

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

static nolinks;			/* Gets set if we run out of RAM */

void
create_archive()
{
	register char	*p;

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

	while (p = name_next()) {
		dump_file(p, -1);
	}

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

/*
 * Dump a single file.  If it's a directory, recurse.
 * Result is 1 for success, 0 for failure.
 * Sets global "hstat" to stat() output for this file.
 */
void
dump_file(p, curdev)
	char	*p;			/* File name to dump */
	int	curdev;			/* Device our parent dir was on */
{
	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, &hstat): lstat(p, &hstat))
	{
badperror:
		perror(p);
badfile:
		errors++;
		return;
	}

	/*
	 * See if we are crossing from one file system to another,
	 * and avoid doing so if the user only wants to dump one file system.
	 */
	if (f_local_filesys && curdev >= 0 && curdev != hstat.st_dev) {
		annorec(stderr, tar);
		fprintf(stderr,
			"%s: is on a different filesystem; not dumped\n",
			p);
		return;
	}

	/*
	 * Check for 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 (hstat.st_nlink > 1) switch (hstat.st_mode & S_IFMT) {
		register struct link	*lp;

	case S_IFREG:			/* Regular file */
#ifdef S_IFCTG
	case S_IFCTG:			/* Contigous file */
#endif
#ifdef S_IFCHR
	case S_IFCHR:			/* Character special file */
#endif

#ifdef S_IFBLK
	case S_IFBLK:			/* Block     special file */
#endif

#ifdef S_IFIFO
	case S_IFIFO:			/* Fifo      special file */
#endif

		/* First quick and dirty.  Hashing, etc later FIXME */
		for (lp = linklist; lp; lp = lp->next) {
			if (lp->ino == hstat.st_ino &&
			    lp->dev == hstat.st_dev) {
				/* We found a link. */
				hstat.st_size = 0;
				header = start_header(p, &hstat);
				if (header == NULL) goto badfile;
				strcpy(header->header.linkname,
					lp->name);
				header->header.linkflag = LF_LINK;
				finish_header(header);
		/* FIXME: Maybe remove from list after all links found? */
				return;		/* We dumped it */
			}
		}

		/* Not found.  Add it to the list of possible links. */
		lp = (struct link *) malloc( (unsigned)
			(strlen(p) + sizeof(struct link) - NAMSIZ));
		if (!lp) {
			if (!nolinks) {
				fprintf(stderr,
	"tar: no memory for links, they will be dumped as separate files\n");
				nolinks++;
			}
		}
		lp->ino = hstat.st_ino;
		lp->dev = hstat.st_dev;
		strcpy(lp->name, p);
		lp->next = linklist;
		linklist = lp;
	}

	/*
	 * This is not a link to a previously dumped file, so dump it.
	 */
	switch (hstat.st_mode & S_IFMT) {

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

		sizeleft = hstat.st_size;
		/* Don't bother opening empty, world readable files. */
		if (sizeleft > 0 || 0444 != (0444 & hstat.st_mode)) {
			f = open(p, O_RDONLY|O_BINARY);
			if (f < 0) goto badperror;
		} else {
			f = -1;
		}
		header = start_header(p, &hstat);
		if (header == NULL) goto badfile;
#ifdef S_IFCTG
		/* Mark contiguous files, if we support them */
		if (f_standard && (hstat.st_mode & S_IFMT) == S_IFCTG) {
			header->header.linkflag = LF_CONTIG;
		}
#endif
		finish_header(header);
		while (sizeleft > 0) {
			start = findrec();
			bufsize = endofrecs()->charptr - start->charptr;
			if (sizeleft < bufsize) {
				/* Last read -- zero out area beyond */
				bufsize = (int)sizeleft;
				count = bufsize % RECORDSIZE;
				if (count) 
					bzero(start->charptr + sizeleft,
						RECORDSIZE - count);
			}
			count = read(f, start->charptr, bufsize);
			if (count < 0) {
				annorec(stderr, tar);
				fprintf(stderr,
				  "read error at byte %ld, reading %d bytes, in file ",
					hstat.st_size - sizeleft,
					bufsize);
				perror(p);	/* FIXME */
				goto padit;
			}
			sizeleft -= count;
			/* This is nonportable (the type of userec's arg). */
			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);

		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;

		hstat.st_size = 0;		/* Force 0 size on symlink */
		header = start_header(p, &hstat);
		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 */
	}
		break;
#endif

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

		/* 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 */
		namebuf[len] = '\0';		/* Make sure null-terminated */

		/*
		 * Output directory header record with permissions
		 * FIXME, do this AFTER files, to avoid R/O dir problems?
		 * If old archive format, don't write record at all.
		 */
		if (!f_oldarch) {
			hstat.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, &hstat);
			 * but since they'd interpret LF_DIR records as
			 * regular files, we'd better put the / on the name.
			 */
			header = start_header(namebuf, &hstat);
			if (header == NULL)
				goto badfile;	/* eg name too long */
			if (f_standard) {
				header->header.linkflag = LF_DIR;
			}
			finish_header(header);	/* Done with directory header */
		}

		/* 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 */
		if (f_dironly)
			break;		/* Unless the user says no */
		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, our_device);
		}

		closedir(dirp);
	}
		break;

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

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

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

	easy:
		if (!f_standard) goto unknown;

		hstat.st_size = 0;		/* Force 0 size */
		header = start_header(p, &hstat);
		if (header == NULL) goto badfile;	/* eg name too long */

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

		finish_header(header);
		break;

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


/*
 * 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 */

	/*
	 * Check the file name and put it in the record.
	 */
	while ('/' == *name) {
		static int warned_once = 0;

		name++;				/* Force relative path */
		if (!warned_once++) {
			annorec(stderr, tar);
			fprintf(stderr,
	"Removing leading / from absolute path names in the archive.\n");
		}
	}
	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.
 * We also print the file name and/or full info if verbose is on.
 */
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);

	if (f_verbose) {
		/* These globals are parameters to print_header, sigh */
		head = header;
		/* hstat is already set up */
		head_standard = f_standard;
		print_header(stderr);
	}

	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' + (char)(value & 7); /* one octal digit */
		value >>= 3;
	} while (digs > 0 && value != 0);

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

}


/*
 * Write the EOT record(s).
 * We actually zero at least one record, through the end of the block.
 * Old tar writes garbage after two zeroed records -- and PDtar used to.
 */
write_eot()
{
	union record *p;
	int bufsize;

	p = findrec();
	bufsize = endofrecs()->charptr - p->charptr;
	bzero(p->charptr, bufsize);
	userec(p);
}
@@@ Fin de create.c
echo extract.c
cat >extract.c <<'@@@ Fin de extract.c'
/*
 * Extract files from a tar archive.
 *
 * Written 19 Nov 1985 by John Gilmore, ihnp4!hoptoad!gnu.
 *
 * @(#) extract.c 1.32 87/11/11 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

#ifdef	MSDOS
#include <fcntl.h>
#endif	/* MSDOS */

/*
 * Some people don't have a #define for these.
 */
#ifndef	O_BINARY
#define	O_BINARY	0
#endif
#ifndef O_NDELAY
#define	O_NDELAY	0
#endif

#ifdef NO_OPEN3
/* We need the #define's even though we don't use them. */
#include "open3.h"
#endif

#ifdef EMUL_OPEN3
/* Simulated 3-argument open for systems that don't have it */
#include "open3.h"
#endif

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

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

extern union record *head;		/* Points to current tape header */
extern struct stat hstat;		/* Stat struct corresponding */
extern int head_standard;		/* Tape header is in ANSI format */

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

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

static time_t now = 0;			/* Current time */
static we_are_root = 0;			/* True if our effective uid == 0 */
static int notumask = ~0;		/* Masks out bits user doesn't want */

/*
 * Set up to extract files.
 */
extr_init()
{
	int ourmask;

	now = time((time_t *)0);
	if (geteuid() == 0)
		we_are_root = 1;

	/*
	 * We need to know our umask.  But if f_use_protection is set,
	 * leave our kernel umask at 0, and our "notumask" at ~0.
	 */
	ourmask = umask(0);		/* Read it */
	if (!f_use_protection) {
		(void) umask (ourmask);	/* Set it back how it was */
		notumask = ~ourmask;	/* Make umask override permissions */
	}
}


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

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

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

	/*
	 * Check for fully specified pathnames and other atrocities.
	 *
	 * Note, we can't just make a pointer to the new file name,
	 * since saverec() might move the header and adjust "head".
	 * We have to start from "head" every time we want to touch
	 * the header record.
	 */
	skipcrud = 0;
	while ('/' == head->header.name[skipcrud]) {
		static int warned_once = 0;

		skipcrud++;	/* Force relative path */
		if (!warned_once++) {
			annorec(stderr, tar);
			fprintf(stderr,
	"Removing leading / from absolute path names in the archive.\n");
		}
	}

	switch (head->header.linkflag) {

	default:
		annofile(stderr, tar);
		fprintf(stderr,
		   "Unknown file type '%c' for %s, extracted as normal file\n",
			head->header.linkflag, skipcrud+head->header.name);
		/* FALL THRU */

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

		/* FIXME, deal with protection issues */
	again_file:
		openflag = f_keep?
			O_BINARY|O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_EXCL:
			O_BINARY|O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_TRUNC;
#ifdef O_CTG
		/*
		 * Contiguous files (on the Masscomp) have to specify
		 * the size in the open call that creates them.
		 */
		if (head->header.lnkflag == LF_CONTIG)
			fd = open(skipcrud+head->header.name, openflag | O_CTG,
				hstat.st_mode, hstat.st_size);
		else
#endif
		{
#ifdef NO_OPEN3
			/*
			 * On raw V7 we won't let them specify -k (f_keep), but
			 * we just bull ahead and create the files.
			 */
			fd = creat(skipcrud+head->header.name, 
				hstat.st_mode);
#else
			/*
			 * With 3-arg open(), we can do this up right.
			 */
			fd = open(skipcrud+head->header.name, openflag,
				hstat.st_mode);
#endif
		}

		if (fd < 0) {
			if (make_dirs(skipcrud+head->header.name))
				goto again_file;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make file ");
			perror(skipcrud+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;
			if (data == NULL) {	/* Check it... */
				annorec(stderr, tar);
				fprintf(stderr, "Unexpected EOF on archive file\n");
				break;
			}
			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(skipcrud+head->header.name);
			skip_file((long)(size - written));
			break;	/* Still do the close, mod time, chmod, etc */
		}

		check = close(fd);
		if (check < 0) {
			annofile(stderr, tar);
			fprintf(stderr, "Error while closing ");
			perror(skipcrud+head->header.name);
		}
		
	set_filestat:
		/*
		 * 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) {
			acc_upd_times[0] = now;	         /* Accessed now */
			acc_upd_times[1] = hstat.st_mtime; /* Mod'd */
			if (utime(skipcrud+head->header.name,
			    acc_upd_times) < 0) {
				annofile(stderr, tar);
				perror(skipcrud+head->header.name);
			}
		}

		/*
		 * If we are root, set the owner and group of the extracted
		 * file.  This does what is wanted both on real Unix and on
		 * System V.  If we are running as a user, we extract as that
		 * user; if running as root, we extract as the original owner.
		 */
		if (we_are_root) {
			if (chown(skipcrud+head->header.name, hstat.st_uid,
				  hstat.st_gid) < 0) {
				annofile(stderr, tar);
				perror(skipcrud+head->header.name);
			}
		}

		/*
		 * If '-k' is not set, open() or creat() could have saved
		 * the permission bits from a previously created file,
		 * ignoring the ones we specified.
		 * Even if -k is set, if the file has abnormal
		 * mode bits, we must chmod since writing or chown() has
		 * probably reset them.
		 *
		 * If -k is set, we know *we* created this file, so the mode
		 * bits were set by our open().   If the file is "normal", we
		 * skip the chmod.  This works because we did umask(0) if -p
		 * is set, so umask will have left the specified mode alone.
		 */
		if ((!f_keep)
		    || (hstat.st_mode & (S_ISUID|S_ISGID|S_ISVTX))) {
			if (chmod(skipcrud+head->header.name,
				  notumask & (int)hstat.st_mode) < 0) {
				annofile(stderr, tar);
				perror(skipcrud+head->header.name);
			}
		}

	quit:
		break;

	case LF_LINK:
	again_link:
		check = link (head->header.linkname,
			      skipcrud+head->header.name);
		if (check == 0)
			break;
		if (make_dirs(skipcrud+head->header.name))
			goto again_link;
		annofile(stderr, tar);
		fprintf(stderr, "Could not link %s to ",
			skipcrud+head->header.name);
		perror(head->header.linkname);
		break;

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

#ifdef S_IFCHR
	case LF_CHR:
		hstat.st_mode |= S_IFCHR;
		goto make_node;
#endif

#ifdef S_IFBLK
	case LF_BLK:
		hstat.st_mode |= S_IFBLK;
		goto make_node;
#endif

#ifdef S_IFIFO
	/* If local system doesn't support FIFOs, use default case */
	case LF_FIFO:
		hstat.st_mode |= S_IFIFO;
		hstat.st_rdev = 0;		/* FIXME, do we need this? */
		goto make_node;
#endif

	make_node:
		check = mknod(skipcrud+head->header.name,
			      (int) hstat.st_mode, (int) hstat.st_rdev);
		if (check != 0) {
			if (make_dirs(skipcrud+head->header.name))
				goto make_node;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make ");
			perror(skipcrud+head->header.name);
			break;
		};
		goto set_filestat;

	case LF_DIR:
		namelen = strlen(skipcrud+head->header.name)-1;
	really_dir:
		/* Check for trailing /, and zap as many as we find. */
		while (namelen && head->header.name[skipcrud+namelen] == '/')
			head->header.name[skipcrud+namelen--] = '\0';
		
	again_dir:
		check = mkdir(skipcrud+head->header.name,
			      0300 | (int)hstat.st_mode);
		if (check != 0) {
			if (make_dirs(skipcrud+head->header.name))
				goto again_dir;
			/* If we're trying to create '.', let it be. */
			if (head->header.name[skipcrud+namelen] == '.' && 
			    (namelen==0 ||
			     head->header.name[skipcrud+namelen-1]=='/'))
				goto check_perms;
			annofile(stderr, tar);
			fprintf(stderr, "Could not make directory ");
			perror(skipcrud+head->header.name);
			break;
		}
		
	check_perms:
		if (0300 != (0300 & (int) hstat.st_mode)) {
			hstat.st_mode |= 0300;
			annofile(stderr, tar);
			fprintf(stderr,
			  "Added write & execute permission to directory %s\n",
			  skipcrud+head->header.name);
		}

		goto set_filestat;
		/* FIXME, Remember timestamps for after files created? */
		/* FIXME, change mode after files created (if was R/O dir) */

	}

	/* 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 */
		if (check == 0) {
			/* Fix ownership */
			if (we_are_root) {
				if (chown(pathname, hstat.st_uid,
					  hstat.st_gid) < 0) {
					annofile(stderr, tar);
					perror(pathname);
				}
			}
			pr_mkdir(pathname, p-pathname, notumask&0777, stdout);
			madeone++;		/* Remember if we made one */
			*p = '/';
			continue;
		}
		*p = '/';
		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 */
}
@@@ Fin de extract.c
echo buffer.c
cat >buffer.c <<'@@@ Fin de buffer.c'
/*
 * Buffer management for public domain tar.
 *
 * Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985.
 *
 * @(#) buffer.c 1.28 11/6/87 Public Domain - gnu
 */

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

#ifdef	MSDOS
# include <fcntl.h>
#else
# ifdef XENIX
#  include <sys/inode.h>
# endif
# include <sys/file.h>
#endif

#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();
extern char	*index(), *strcat();

/*
 * 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.  We hope compress/sh
				   never return 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 long	    saved_recno;

/*
 * PID of child program, if f_compress or remote archive access.
 */
static int	childpid = 0;

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

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

/*
 * Have we hit EOF yet?
 */
static int	eof;


/*
 * Return the location of the next available input or output record.
 * Return NULL for EOF.  Once we have returned NULL, we just keep returning
 * it, to avoid accidentally going on to the next file on the "tape".
 */
union record *
findrec()
{
	if (ar_record == ar_last) {
		if (eof)
			return (union record *)NULL;	/* EOF */
		flush_archive();
		if (ar_record == ar_last) {
			eof++;
			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;
}


/* 
 * Duplicate a file descriptor into a certain slot.
 * Equivalent to BSD "dup2" with error reporting.
 */
void
dupto(from, to, msg)
	int from, to;
	char *msg;
{
	int err;

	if (from != to) {
		(void) close(to);
		err = dup(from);
		if (err != to) {
			fprintf(stderr, "tar: cannot dup ");
			perror(msg);
			exit(EX_SYSTEM);
		}
		(void) close(from);
	}
}


/*
 * Fork a child to deal with remote files or compression.
 * If rem_host is zero, we are called only for compression.
 */
void
child_open(rem_host, rem_file)
	char *rem_host, *rem_file;
{

#ifdef	MSDOS
	fprintf(stderr,
	  "MSDOS %s cannot deal with compressed or remote archives\n", tar);
	exit(EX_ARGSBAD);
#else
	
	int pipes[2];
	int err;
	struct stat arstat;
	char cmdbuf[1000];		/* For big file and host names */

	/* Create a pipe to talk to the child over */
	err = pipe(pipes);
	if (err < 0) {
		perror ("tar: cannot create pipe to child");
		exit(EX_SYSTEM);
	}
	
	/* Fork child process */
	childpid = fork();
	if (childpid < 0) {
		perror("tar: cannot fork");
		exit(EX_SYSTEM);
	}

	/*
	 * Parent process.  Clean up.
	 *
	 * We always close the archive file (stdin, stdout, or opened file)
	 * since the child will end up reading or writing that for us.
	 * Note that this may leave standard input closed.
	 * We close the child's end of the pipe since they will handle
	 * that too; and we set <archive> to the other end of the pipe.
	 *
	 * If reading, we set f_reblock since reading pipes or network
	 * sockets produces odd length data.
	 */
	if (childpid > 0) {
		(void) close (archive);	
		if (ar_reading) {
			(void) close (pipes[PWRITE]);
			archive = pipes[PREAD];
			f_reblock++;
		} else {
			(void) close (pipes[PREAD]);
			archive = pipes[PWRITE];
		}
		return;
	}

	/*
	 * Child process.
	 */
	if (ar_reading) {
		/*
		 * Reading from the child...
		 *
		 * Close the read-side of the pipe, which our parent will use.
		 * Move the write-side of pipe to stdout,
		 * If local, move archive input to child's stdin,
		 * then run the child.
		 */
		(void) close (pipes[PREAD]);
		dupto(pipes[PWRITE], STDOUT, "to stdout");
		if (rem_host) {
			(void) close (STDIN);	/* rsh abuses stdin */
			if (STDIN != open("/dev/null"))
				perror("Can't open /dev/null");
			sprintf(cmdbuf,
				"rsh '%s' dd '<%s' bs=%db",
				rem_host, rem_file, blocking);
			if (f_compress)
				strcat(cmdbuf, "| compress -d");
#ifdef DEBUG
			fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
#endif
			execlp("sh", "sh", "-c", cmdbuf, (char *)0);
			perror("tar: cannot exec sh");
		} else {
			/*
			 * If we are reading a disk file, compress is OK;
			 * otherwise, we have to reblock the input in case it's
			 * coming from a tape drive.  This is an optimization.
			 */
			dupto(archive, STDIN, "to stdin");
			err = fstat(STDIN, &arstat);
			if (err != 0) {
				perror("tar: can't fstat archive");
				exit(EX_SYSTEM);
			}
			if ((arstat.st_mode & S_IFMT) == S_IFREG) {
				execlp("compress", "compress", "-d", (char *)0);
				perror("tar: cannot exec compress");
			} else {
				/* Non-regular file needs dd before compress */
				sprintf(cmdbuf,
					"dd bs=%db | compress -d",
					blocking);
#ifdef DEBUG
				fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
#endif
				execlp("sh", "sh", "-c", cmdbuf, (char *)0);
				perror("tar: cannot exec sh");
			}
		}
		exit(MAGIC_STAT);
	} else {
		/*
		 * Writing archive to the child.
		 * It would like to run either:
		 *	compress
		 *	compress |            dd obs=20b
		 *		   rsh 'host' dd obs=20b '>foo'
		 * or	compress | rsh 'host' dd obs=20b '>foo'
		 *
		 * We need the dd to reblock the output to the
		 * user's specs, if writing to a device or over
		 * the net.  However, it produces a stupid
		 * message about how many blocks it processed.
		 * Because the shell on the remote end could be just
		 * about any shell, we can't depend on it to do
		 * redirect stderr properly for us -- the csh
		 * doesn't use the same syntax as the Bourne shell.
		 * On the other hand, if we just ignore stderr on
		 * this end, we won't see errors from rsh, or from
		 * the inability of "dd" to write its output file.
		 * The combination of the local sh, the rsh, the
		 * remote csh, and maybe a remote sh conspires to mess
		 * up any possible quoting method, so grumble! we
		 * punt and just accept the fucking "xxx blocks"
		 * messages.  The real fix would be a "dd" that
		 * would shut up.
		 * 
		 * Close the write-side of the pipe, which our parent will use.
		 * Move the read-side of the pipe to stdin,
		 * If local, move archive output to the child's stdout.
		 * then run the child.
		 */
		(void) close (pipes[PWRITE]);
		dupto(pipes[PREAD], STDIN, "to stdin");
		if (!rem_host)
			dupto(archive, STDOUT, "to stdout");

		cmdbuf[0] = '\0';
		if (f_compress) {
			if (!rem_host) {
				err = fstat(STDOUT, &arstat);
				if (err != 0) {
					perror("tar: can't fstat archive");
					exit(EX_SYSTEM);
				}
				if ((arstat.st_mode & S_IFMT) == S_IFREG) {
					execlp("compress", "compress", (char *)0);
					perror("tar: cannot exec compress");
				}
			}
			strcat(cmdbuf, "compress | ");
		}
		if (rem_host) {
			sprintf(cmdbuf+strlen(cmdbuf),
			  "rsh '%s' dd obs=%db '>%s'",
				 rem_host, blocking, rem_file);
		} else {
			sprintf(cmdbuf+strlen(cmdbuf),
				"dd obs=%db",
				blocking);
		}
#ifdef DEBUG
		fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
#endif
		execlp("sh", "sh", "-c", cmdbuf, (char *)0);
		perror("tar: cannot exec sh");
		exit(MAGIC_STAT);
	}
#endif	/* MSDOS */
}


/*
 * Open an archive file.  The argument specifies whether we are
 * reading or writing.
 */
open_archive(read)
	int read;
{
	char *colon, *slash;
	char *rem_host = 0, *rem_file;

	colon = index(ar_file, ':');
	if (colon) {
		slash = index(ar_file, '/');
		if (slash && slash > colon) {
			/*
			 * Remote file specified.  Parse out separately,
			 * and don't try to open it on the local system.
			 */
			rem_file = colon + 1;
			rem_host = ar_file;
			*colon = '\0';
			goto gotit;
		}
	}

	if (ar_file[0] == '-' && ar_file[1] == '\0') {
		f_reblock++;	/* Could be a pipe, be safe */
		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);
	}

#ifdef	MSDOS
	setmode(archive, O_BINARY);
#endif

gotit:
	if (blocksize == 0) {
		fprintf(stderr, "tar: invalid value for blocksize\n");
		exit(EX_ARGSBAD);
	}

	/*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;
	ar_reading = read;

	if (f_compress || rem_host) 
		child_open(rem_host, rem_file);

	if (read) {
		ar_last = ar_block;		/* Set up for 1st block = # 0 */
		(void) findrec();		/* Read it in, check for EOF */
	}
}


/*
 * 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.
 *
 * We calculate "offset" then add it because some compilers end up
 * adding (baserec+ar_record), doing a 9-bit shift of baserec, then
 * subtracting ar_block from that, shifting it back, losing the top 9 bits.
 */
saverec(pointer)
	union record **pointer;
{
	long offset;

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

/*
 * 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 && err > 0) {
			annorec(stderr, tar);
			fprintf(stderr, "Blocksize = %d record%s\n",
				err / RECORDSIZE, (err > RECORDSIZE)? "s": "");
		}
		ar_last = ar_block + ((unsigned)(blocksize - left))/RECORDSIZE;
		return;
	}
	if (f_reblock) {
		/*
		 * User warned us about this.  Fix up.
		 */
		if (left > 0) {
error2loop:
			err = read(archive, more, left);
			if (err < 0) {
				readerror();
				goto error2loop;	/* 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);

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

		if (child != -1) {
			switch (TERM_SIGNAL(status)) {
			case 0:
				/* Child voluntarily terminated  -- but why? */
				if (TERM_VALUE(status) == MAGIC_STAT) {
					exit(EX_SYSTEM);/* Child had trouble */
				}
				if (TERM_VALUE(status) == (SIGPIPE + 128)) {
					/*
					 * /bin/sh returns this if its child
					 * dies with SIGPIPE.  'Sok.
					 */
					break;
				} else if (TERM_VALUE(status))
					fprintf(stderr,
				  "tar: child returned status %d\n",
						TERM_VALUE(status));
			case SIGPIPE:
				break;		/* This is OK. */

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


/*
 * 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;
	long	offset;

	/* Make sure previous output gets out in sequence */
	if (stream == stderr)
		fflush(stdout);
	if (f_sayblock) {
		if (prefix) {
			fputs(prefix, stream);
			putc(' ', stream);
		}
		offset = ar_record - ar_block;
		sprintf(buffer, "rec %d: ",
			savedp?	saved_recno:
				baserec + offset);
		fputs(buffer, stream);
		space = ANNOWIDTH - strlen(buffer);
		if (space > 0) {
			fprintf(stream, "%*s", space, "");
		}
	} else if (prefix) {
		fputs(prefix, stream);
		fputs(": ", stream);
	}
}
@@@ Fin de buffer.c
echo getoldopt.c
cat >getoldopt.c <<'@@@ Fin de 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);
}
@@@ Fin de getoldopt.c
exit 0