[comp.sources.atari.st] v02i047: gnutar -- GNU's version of UNIX "tar" part01/08

koreth%panarthea.ebay@sun.com (Steven Grimm) (07/20/89)

Submitted-by: 7103_300@uwovax.uwo.ca (Eric R. Smith)
Posting-number: Volume 2, Issue 47
Archive-name: gnutar/part01

   Here is GNU Tar 1.04 ported to TOS. It has all of the
features that are applicable on a single tasking machine;
it can read, write, and create files that are completely
compatible with the Unix tar utility (even the timestamps
are the same).  This version is more recent than the one
distributed with the GCC; in particular, a minor bug in
the interpretation of the TZ environment variable is fixed,
and the "stat" function returns more sensible read/write/execute
flags (and can now be used on "." and "..").
   For those not familiar with "tar", it is
an archive utility, widely used on Unix systems. It can
be used to store whole filesystems. Unlike zoo and arc,
it does not do compression; under Unix the "compress"
utility is often applied to tar files, and the same could
easily be done on the ST. GNU tar has a number of extra
features, including the ability to create multi-volume
tar files. The files readme.st and tar.tex in the ARC file
contain more information.
   In accordance with the GNU license, the complete source code
(in C) is included, as well as the executable file gnutar.ttp.
[The binary will appear in comp.binaries.atari.st. -sg]
gnutar was compiled with the GCC v1.34, with the new improved
library (in fact, a newer more improved version :-).
--
Eric R. Smith                      email:
Dept. of Mathematics               7103_300@uwovax.uwo.ca
University of Western Ontario      7103_300@uwovax.bitnet
London, Ont. Canada N6A 5B7    (a shared mailbox: put my name on
ph: (519) 661-3638              the Subj: line, please!)

#!/bin/sh
# shar:	Shell Archiver  (v1.22)
#
# This is part 1 of a multipart archive                                    
# do not concatenate these parts, unpack them in order with /bin/sh        
#
#	Run the following text with /bin/sh to create:
#	  BUFFER.C
#	  COPYING
#	  CREATE.C
#	  DIFFARCH.C
#	  EXTRACT.C
#	  GETDATE.Y
#	  GETOLDOP.C
#	  LIST.C
#	  MAKEFILE
#	  MSD_DIR.C
#	  MSD_DIR.H
#	  NAMES.C
#	  OPEN3.H
#	  PORT.C
#	  PORT.H
#	  README
#	  README.ST
#	  RMT.H
#	  RTAPE_LI.C
#	  RTAPE_SE.C
#	  TAR.C
#	  TAR.H
#	  TAR.TEX
#	  UPDATE.C
#	  VERSION.C
#	  WILDMAT.C
#
if test -r s2_seq_.tmp
then echo "Must unpack archives in sequence!"
     next=`cat s2_seq_.tmp`; echo "Please unpack part $next next"
     exit 1; fi
echo "x - extracting BUFFER.C (Text)"
sed 's/^X//' << 'SHAR_EOF' > BUFFER.C &&
X/*
X
X	Copyright (C) 1988 Free Software Foundation
X
XGNU tar is distributed in the hope that it will be useful, but WITHOUT ANY
XWARRANTY.  No author or distributor accepts responsibility to anyone
Xfor the consequences of using it or for whether it serves any
Xparticular purpose or works at all, unless he says so in writing.
XRefer to the GNU tar General Public License for full details.
X
XEveryone is granted permission to copy, modify and redistribute GNU tar,
Xbut only under the conditions described in the GNU tar General Public
XLicense.  A copy of this license is supposed to have been given to you
Xalong with GNU tar so you can know your rights and responsibilities.  It
Xshould be in a file named COPYING.  Among other things, the copyright
Xnotice and this notice must be preserved on all copies.
X
XIn other words, go ahead and share GNU tar, but don't try to stop
Xanyone else from sharing it farther.  Help stamp out software hoarding!
X*/
X
X/*
X * Buffer management for tar.
X *
X * Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985.
X *
X * @(#) buffer.c 1.28 11/6/87 - gnu
X */
X
X#include <stdio.h>
X#include <errno.h>
X#ifdef atarist
X#include <types.h>
X#include <stat.h>
X#include <string.h>
X#else
X#include <sys/types.h>		/* For non-Berkeley systems */
X#include <sys/stat.h>
X#endif
X
X#include <signal.h>
X
X#ifndef MSDOS
X#include <sys/ioctl.h>
X#ifndef USG
X#include <sys/mtio.h>
X#endif
X#endif
X
X#ifdef	MSDOS
X# include <fcntl.h>
X# include <process.h>
X#else
X# ifdef XENIX
X#  include <sys/inode.h>
X# endif
X# include <sys/file.h>
X#endif
X
X#include "tar.h"
X#include "port.h"
X#include "rmt.h"
X
XFILE *msg_file;		/* Either stdout or stderr:  The thing we write messages
X			   (standard msgs, not errors) to.  Stdout unless we're
X			   writing a pipe, in which case stderr */
X
X#define	STDIN	0		/* Standard input  file descriptor */
X#define	STDOUT	1		/* Standard output file descriptor */
X
X#define	PREAD	0		/* Read  file descriptor from pipe() */
X#define	PWRITE	1		/* Write file descriptor from pipe() */
X
X#ifdef __GNU__
Xextern void	*malloc();
Xextern void	*valloc();
X#else
Xextern char	*malloc();
Xextern char	*valloc();
X#endif
X
Xextern char	*index(), *strcat();
Xextern char	*strcpy();
X
X/*
X * V7 doesn't have a #define for this.
X */
X#ifndef O_RDONLY
X#define	O_RDONLY	0
X#endif
X#ifndef O_RDWR
X#define O_RDWR		2
X#endif
X#ifndef O_CREAT
X#define O_CREAT		0
X#endif
X#ifndef O_BINARY
X#define O_BINARY	0
X#endif
X
X#define	MAGIC_STAT	105	/* Magic status returned by child, if
X				   it can't exec.  We hope compress/sh
X				   never return this status! */
X
Xvoid writeerror();
Xvoid readerror();
Xextern void finish_header();
X
X/*
X * The record pointed to by save_rec should not be overlaid
X * when reading in a new tape block.  Copy it to record_save_area first, and
X * change the pointer in *save_rec to point to record_save_area.
X * Saved_recno records the record number at the time of the save.
X * This is used by annofile() to print the record number of a file's
X * header record.
X */
Xstatic union record **save_rec;
X union record record_save_area;
Xstatic long	    saved_recno;
X
X/*
X * PID of child program, if f_compress or remote archive access.
X */
Xstatic int	childpid = 0;
X
X/*
X * Record number of the start of this block of records
X */
Xlong	baserec;
X
X/*
X * Error recovery stuff
X */
Xstatic int	r_error_count;
X
X/*
X * Have we hit EOF yet?
X */
Xstatic int	eof;
X
X/* JF we're reading, but we just read the last record and its time to update */
Xextern time_to_start_writing;
Xint file_to_switch_to= -1;	/* If remote update, close archive, and use
X				   this descriptor to write to */
X
Xstatic int volno = 1;		/* JF which volume of a multi-volume tape
X				   we're on */
X
Xchar *save_name = 0;		/* Name of the file we are currently writing */
Xlong save_totsize;		/* total size of file we are writing.  Only
X				   valid if save_name is non_zero */
Xlong save_sizeleft;		/* Where we are in the file we are writing.
X				   Only valid if save_name is non-zero */
X
Xint write_archive_to_stdout;
X
X/* Used by fl_read and fl_write to store the real info about saved names */
Xstatic char real_s_name[NAMSIZ];
Xstatic long real_s_totsize;
Xstatic long real_s_sizeleft;
X
X/*
X * Return the location of the next available input or output record.
X * Return NULL for EOF.  Once we have returned NULL, we just keep returning
X * it, to avoid accidentally going on to the next file on the "tape".
X */
Xunion record *
Xfindrec()
X{
X	if (ar_record == ar_last) {
X		if (eof)
X			return (union record *)NULL;	/* EOF */
X		flush_archive();
X		if (ar_record == ar_last) {
X			eof++;
X			return (union record *)NULL;	/* EOF */
X		}
X	}
X	return ar_record;
X}
X
X
X/*
X * Indicate that we have used all records up thru the argument.
X * (should the arg have an off-by-1? XXX FIXME)
X */
Xvoid
Xuserec(rec)
X	union record *rec;
X{
X	while(rec >= ar_record)
X		ar_record++;
X	/*
X	 * Do NOT flush the archive here.  If we do, the same
X	 * argument to userec() could mean the next record (if the
X	 * input block is exactly one record long), which is not what
X	 * is intended.
X	 */
X	if (ar_record > ar_last)
X		abort();
X}
X
X
X/*
X * Return a pointer to the end of the current records buffer.
X * All the space between findrec() and endofrecs() is available
X * for filling with data, or taking data from.
X */
Xunion record *
Xendofrecs()
X{
X	return ar_last;
X}
X
X
X/*
X * Duplicate a file descriptor into a certain slot.
X * Equivalent to BSD "dup2" with error reporting.
X */
Xvoid
Xdupto(from, to, msg)
X	int from, to;
X	char *msg;
X{
X	int err;
X
X	if (from != to) {
X		(void) close(to);
X		err = dup(from);
X		if (err != to) {
X			msg_perror("cannot dup %s",msg);
X			exit(EX_SYSTEM);
X		}
X		(void) close(from);
X	}
X}
X
X#ifdef MSDOS
Xvoid
Xchild_open()
X{
X#ifdef atarist
X	fprintf(stderr,"GEMDOS %s can't use compressed or remote archives\n",tar);
X#else
X	fprintf(stderr,"MSDOS %s can't use compressed or remote archives\n",tar);
X#endif
X	exit(EX_ARGSBAD);
X}
X#else
Xvoid
Xchild_open()
X{
X	int pipe[2];
X	int err = 0;
X
X	int kidpipe[2];
X	int kidchildpid;
X
X	void ck_pipe();
X	void ck_close();
X
X#define READ	0
X#define WRITE	1
X
X	ck_pipe(pipe);
X
X	childpid=fork();
X	if(childpid<0) {
X		msg_perror("cannot fork");
X		exit(EX_SYSTEM);
X	}
X	if(childpid>0) {
X		/* We're the parent.  Clean up and be happy */
X		/* This, at least, is easy */
X
X		if(ar_reading) {
X			f_reblock++;
X			archive=pipe[READ];
X			ck_close(pipe[WRITE]);
X		} else {
X			archive = pipe[WRITE];
X			ck_close(pipe[READ]);
X		}
X		return;
X	}
X
X	/* We're the kid */
X	if(ar_reading)
X		dupto(pipe[WRITE],STDOUT,"(child) pipe to stdout");
X	else
X		dupto(pipe[READ],STDIN,"(child) pipe to stdin");
X	/* ck_close(pipe[READ]);
X	ck_close(pipe[WRITE]);*/
X
X	/* We need a child tar only if
X	   1: we're reading/writing stdin/out (to force reblocking)
X	   2: the file is to be accessed by rmt (compress doesn't know how
X	   3: the file is not a plain file */
X#ifdef NO_REMOTE
X	if(!(ar_file[0]=='-' && ar_file[1]=='\0') && isfile(ar_file))
X#else
X	if(!(ar_file[0]=='-' && ar_file[1]=='\0') && !_remdev(ar_file) && isfile(ar_file))
X#endif
X	{
X		/* We don't need a child tar.  Open the archive */
X		if(ar_reading) {
X			archive=open(ar_file, O_RDONLY|O_BINARY, 0666);
X			if(archive<0) {
X				msg_perror("can't open archive %s",ar_file);
X				exit(EX_BADARCH);
X			}
X			dupto(archive,STDIN,"archive to stdin");
X			/* close(archive); */
X		} else {
X			archive=creat(ar_file,0666);
X			if(archive<0) {
X				msg_perror("can't open archive %s",ar_file);
X				exit(EX_BADARCH);
X			}
X			dupto(archive,STDOUT,"archive to stdout");
X			/* close(archive); */
X		}
X	} else {
X		/* We need a child tar */
X		ck_pipe(kidpipe);
X
X		kidchildpid=fork();
X		if(kidchildpid<0) {
X			msg_perror("child can't fork");
X			exit(EX_SYSTEM);
X		}
X
X		if(kidchildpid>0) {
X			/* About to exec compress:  set up the files */
X			if(ar_reading) {
X				dupto(kidpipe[READ],STDIN,"((child)) pipe to stdin");
X				ck_close(kidpipe[WRITE]);
X				/* dup2(pipe[WRITE],STDOUT); */
X			} else {
X				/* dup2(pipe[READ],STDIN); */
X				dupto(kidpipe[WRITE],STDOUT,"((child)) pipe to stdout");
X				ck_close(kidpipe[READ]);
X			}
X			/* ck_close(pipe[READ]); */
X			/* ck_close(pipe[WRITE]); */
X			/* ck_close(kidpipe[READ]);
X			ck_close(kidpipe[WRITE]); */
X		} else {
X		/* Grandchild.  Do the right thing, namely sit here and
X		   read/write the archive, and feed stuff back to compress */
X			tar="tar (child)";
X			if(ar_reading) {
X				dupto(kidpipe[WRITE],STDOUT,"[child] pipe to stdout");
X				ck_close(kidpipe[READ]);
X			} else {
X				dupto(kidpipe[READ],STDIN,"[child] pipe to stdin");
X				ck_close(kidpipe[WRITE]);
X			}
X
X			if (ar_file[0] == '-' && ar_file[1] == '\0') {
X				if (ar_reading)
X					archive = STDIN;
X				else
X					archive = STDOUT;
X			} else /* This can't happen if (ar_reading==2)
X				archive = rmtopen(ar_file, O_RDWR|O_CREAT|O_BINARY, 0666);
X			else */if(ar_reading)
X				archive = rmtopen(ar_file, O_RDONLY|O_BINARY, 0666);
X			else
X				archive = rmtcreat(ar_file, 0666);
X
X			if (archive < 0) {
X				msg_perror("can't open archive %s",ar_file);
X				exit(EX_BADARCH);
X			}
X
X			if(ar_reading) {
X				for(;;) {
X					char *ptr;
X					int max,count;
X		
X					r_error_count = 0;
X				error_loop:
X					err=rmtread(archive, ar_block->charptr,(int)(blocksize));
X					if(err<0) {
X						readerror();
X						goto error_loop;
X					}
X					if(err==0)
X						break;
X					ptr = ar_block->charptr;
X					max = err;
X					while(max) {
X						count = (max<RECORDSIZE) ? max : RECORDSIZE;
X						err=write(STDOUT,ptr,count);
X						if(err!=count) {
X							if(err<0) {
X								msg_perror("can't write to compress");
X								exit(EX_SYSTEM);
X							} else
X								msg("write to compress short %d bytes",count-err);
X							count = (err<0) ? 0 : err;
X						}
X						ptr+=count;
X						max-=count;
X					}
X				}
X			} else {
X				for(;;) {
X					int n;
X					char *ptr;
X		
X					n=blocksize;
X					ptr = ar_block->charptr;
X					while(n) {
X						err=read(STDIN,ptr,(n<RECORDSIZE) ? n : RECORDSIZE);
X						if(err<=0)
X							break;
X						n-=err;
X						ptr+=err;
X					}
X						/* EOF */
X					if(err==0) {
X						blocksize-=n;
X						err=rmtwrite(archive,ar_block->charptr,blocksize);
X						if(err!=(blocksize))
X							writeerror(err);
X						blocksize+=n;
X						break;
X					}
X					if(n) {
X						msg_perror("can't read from compress");
X						exit(EX_SYSTEM);
X					}
X					err=rmtwrite(archive, ar_block->charptr, (int)blocksize);
X					if(err!=blocksize)
X						writeerror(err);
X				}
X			}
X		
X			/* close_archive(); */
X			exit(0);
X		}
X	}
X		/* So we should exec compress (-d) */
X	if(ar_reading)
X		execlp("compress", "compress", "-d", (char *)0);
X	else
X		execlp("compress", "compress", (char *)0);
X	msg_perror("can't exec compress");
X	_exit(EX_SYSTEM);
X}
X
X
X/* return non-zero if p is the name of a directory */
Xisfile(p)
Xchar *p;
X{
X	struct stat stbuf;
X
X	if(stat(p,&stbuf)<0)
X		return 1;
X	if((stbuf.st_mode&S_IFMT)==S_IFREG)
X		return 1;
X	return 0;
X}
X
X#endif
X
X#ifdef DONTDEF
X/*
X * Fork a child to deal with remote files or compression.
X * If rem_host is zero, we are called only for compression.
X */
Xvoid
Xchild_open(rem_host, rem_file)
X	char *rem_host, *rem_file;
X{
X
X#ifdef	MSDOS
X	fprintf(stderr,
X#ifdef atarist
X	  "MSDOS %s cannot deal with compressed or remote archives\n", tar);
X#else
X	  "MSDOS %s cannot deal with compressed or remote archives\n", tar);
X#endif
X	exit(EX_ARGSBAD);
X#else
X
X	int pipes[2];
X	int err;
X	struct stat arstat;
X	char cmdbuf[1000];		/* For big file and host names */
X
X	int other_pipes[2];	/* JF for remote update and Multivol */
X
X	/* Create a pipe to talk to the child over */
X	err = pipe(pipes);
X	if (err < 0) {
X		msg_perror ("cannot create pipe to child");
X		exit(EX_SYSTEM);
X	}
X
X	if(cmd_mode==CMD_CAT || cmd_mode==CMD_UPDATE || cmd_mode==CMD_APPEND) {
X		err=pipe(other_pipes);
X		if(err<0) {
X			msg_perror("cannot create a pipe");
X			exit(EX_SYSTEM);
X		}
X	}
X
X	/* Fork child process */
X	childpid = fork();
X	if (childpid < 0) {
X		msg_perror("cannot fork");
X		exit(EX_SYSTEM);
X	}
X
X	/*
X	 * Parent process.  Clean up.
X	 *
X	 * We always close the archive file (stdin, stdout, or opened file)
X	 * since the child will end up reading or writing that for us.
X	 * Note that this may leave standard input closed.
X	 * We close the child's end of the pipe since they will handle
X	 * that too; and we set <archive> to the other end of the pipe.
X	 *
X	 * If reading, we set f_reblock since reading pipes or network
X	 * sockets produces odd length data.
X	 */
X	if (childpid > 0) {
X		if(f_multivol || cmd_mode==CMD_CAT || cmd_mode==CMD_UPDATE || cmd_mode==CMD_APPEND) {
X			(void)close(other_pipes[PREAD]);
X			file_to_switch_to=other_pipes[PWRITE];
X		}
X		(void) close (archive);
X		if (ar_reading) {
X			(void) close (pipes[PWRITE]);
X			archive = pipes[PREAD];
X			f_reblock++;
X		} else {
X			(void) close (pipes[PREAD]);
X			archive = pipes[PWRITE];
X		}
X		return;
X	}
X
X	/*
X	 * Child process.
X	 */
X
X	/* We can't do any of these to a compressed file, so it'd better
X	   be remote */
X	if(f_multivol || cmd_mode==CMD_UPDATE || cmd_mode==CMD_APPEND || cmd_mode==CMD_CAT) {
X		char mode;
X
X		(void) close(pipes[PREAD]);
X		dupto(pipes[PWRITE],STDOUT,"(child) to stdout");
X		(void) close(other_pipes[PWRITE]);
X		dupto(other_pipes[PREAD],STDIN,"(child) to stdin");
X		if(f_multivol)  {
X			if(cmd_mode==CMD_CREATE)
X				mode='c';
X			else if(cmd_mode==CMD_DIFF || cmd_mode==CMD_LIST || cmd_mode==CMD_EXTRACT)
X				mode='r';
X			else mode='c';
X		} else
X			mode='c';
X
X		(void) sprintf(cmdbuf,"rsh '%s' tar -S%c -b %d -f '%s'",rem_host,mode,blocking,rem_file);
X
X		execlp("sh","sh","-c",cmdbuf,(char *)0);
X		msg_perror("cannot exec sh");
X		exit(MAGIC_STAT);
X	}
X
X	if (ar_reading) {
X		/*
X		 * Reading from the child...
X		 *
X		 * Close the read-side of the pipe, which our parent will use.
X		 * Move the write-side of pipe to stdout,
X		 * If local, move archive input to child's stdin,
X		 * then run the child.
X		 */
X		(void) close (pipes[PREAD]);
X		dupto(pipes[PWRITE], STDOUT, "to stdout");
X		if (rem_host) {
X			(void) close (STDIN);	/* rsh abuses stdin */
X			if (STDIN != open("/dev/null"))
X				msg_perror("Can't open /dev/null");
X			(void) sprintf(cmdbuf,
X				"rsh '%s' dd '<%s' bs=%db",
X				rem_host, rem_file, blocking);
X			if (f_compress)
X				strcat(cmdbuf, "| compress -d");
X#ifdef DEBUG
X			fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
X#endif
X			execlp("sh", "sh", "-c", cmdbuf, (char *)0);
X			msg_perror("cannot exec sh");
X		} else {
X			/*
X			 * If we are reading a disk file, compress is OK;
X			 * otherwise, we have to reblock the input in case it's
X			 * coming from a tape drive.  This is an optimization.
X			 */
X			dupto(archive, STDIN, "to stdin");
X			err = fstat(STDIN, &arstat);
X			if (err != 0) {
X				msg_perror("can't fstat archive");
X				exit(EX_SYSTEM);
X			}
X			if ((arstat.st_mode & S_IFMT) == S_IFREG) {
X				execlp("compress", "compress", "-d", (char *)0);
X				msg_perror("cannot exec compress");
X			} else {
X				/* Non-regular file needs dd before compress */
X				(void) sprintf(cmdbuf,
X					"dd bs=%db | compress -d",
X					blocking);
X#ifdef DEBUG
X				fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
X#endif
X				execlp("sh", "sh", "-c", cmdbuf, (char *)0);
X				msg_perror("cannot exec sh");
X			}
X		}
X		exit(MAGIC_STAT);
X	} else {
X		/*
X		 * Writing archive to the child.
X		 * It would like to run either:
X		 *	compress
X		 *	compress |            dd obs=20b
X		 *		   rsh 'host' dd obs=20b '>foo'
X		 * or	compress | rsh 'host' dd obs=20b '>foo'
X		 *
X		 * We need the dd to reblock the output to the
X		 * user's specs, if writing to a device or over
X		 * the net.  However, it produces a stupid
X		 * message about how many blocks it processed.
X		 * Because the shell on the remote end could be just
X		 * about any shell, we can't depend on it to do
X		 * redirect stderr properly for us -- the csh
X		 * doesn't use the same syntax as the Bourne shell.
X		 * On the other hand, if we just ignore stderr on
X		 * this end, we won't see errors from rsh, or from
X		 * the inability of "dd" to write its output file.
X		 * The combination of the local sh, the rsh, the
X		 * remote csh, and maybe a remote sh conspires to mess
X		 * up any possible quoting method, so grumble! we
X		 * punt and just accept the fucking "xxx blocks"
X		 * messages.  The real fix would be a "dd" that
X		 * would shut up.
X		 *
X		 * Close the write-side of the pipe, which our parent will use.
X		 * Move the read-side of the pipe to stdin,
X		 * If local, move archive output to the child's stdout.
X		 * then run the child.
X		 */
X		(void) close (pipes[PWRITE]);
X		dupto(pipes[PREAD], STDIN, "to stdin");
X		if (!rem_host)
X			dupto(archive, STDOUT, "to stdout");
X
X		cmdbuf[0] = '\0';
X		if (f_compress) {
X			if (!rem_host) {
X				err = fstat(STDOUT, &arstat);
X				if (err != 0) {
X					msg_perror("can't fstat archive");
X					exit(EX_SYSTEM);
X				}
X				if ((arstat.st_mode & S_IFMT) == S_IFREG) {
X					execlp("compress", "compress", (char *)0);
X					msg_perror("cannot exec compress");
X					_exit(EX_SYSTEM);
X				}
X			}
X			strcat(cmdbuf, "compress | ");
X		}
X		if (rem_host) {
X			(void) sprintf(cmdbuf+strlen(cmdbuf),
X			  "rsh '%s' dd obs=%db '>%s'",
X				 rem_host, blocking, rem_file);
X		} else {
X			(void) sprintf(cmdbuf+strlen(cmdbuf),
X				"dd obs=%db",
X				blocking);
X		}
X#ifdef DEBUG
X		fprintf(stderr, "Exec-ing: %s\n", cmdbuf);
X#endif
X		execlp("sh", "sh", "-c", cmdbuf, (char *)0);
X		msg_perror("cannot exec sh");
X		exit(MAGIC_STAT);
X	}
X#endif	/* MSDOS */
X}
X
X#endif
X
X/*
X * Open an archive file.  The argument specifies whether we are
X * reading or writing.
X */
X/* JF if the arg is 2, open for reading and writing. */
Xopen_archive(reading)
X	int reading;
X{
X	msg_file = stdout;
X
X/* #ifndef MSDOS */
X#ifdef DonTDeF
X	/* JF Not doing this on MSdos systems looks like a good idea.
X	   Does this stuff about 'slash' mean I can't simply say
X	   remotesys:iggy to store it in 'iggy' on remotesys?  Do I
X	   have to say remotesys:./iggy in order for it to work?
X	   Who knows??  */
X	colon = index(ar_file, ':');
X	if (colon) {
X		slash = index(ar_file, '/');
X		if (slash && slash > colon) {
X			/*
X			 * Remote file specified.  Parse out separately,
X			 * and don't try to open it on the local system.
X			 */
X			rem_file = colon + 1;
X			rem_host = ar_file;
X			*colon = '\0';
X			goto gotit;
X		}
X	}
X#endif
X
X	if (blocksize == 0) {
X		msg("invalid value for blocksize");
X		exit(EX_ARGSBAD);
X	}
X
X	/*NOSTRICT*/
X	if(f_multivol) {
X		ar_block = (union record *) valloc((unsigned)(blocksize+(2*RECORDSIZE)));
X		if(ar_block)
X			ar_block += 2;
X	} else
X		ar_block = (union record *) valloc((unsigned)blocksize);
X	if (!ar_block) {
X		msg("could not allocate memory for blocking factor %d",
X			blocking);
X		exit(EX_ARGSBAD);
X	}
X
X	ar_record = ar_block;
X	ar_last   = ar_block + blocking;
X	ar_reading = reading;
X
X	if (f_compress) {
X		if(reading==2 || f_verify) {
X			msg("cannot update or verify compressed archives");
X			exit(EX_ARGSBAD);
X		}
X		child_open();
X		if(!reading && ar_file[0]=='-' && ar_file[1]=='\0')
X			msg_file = stderr;
X		/* child_open(rem_host, rem_file); */
X	} else if (ar_file[0] == '-' && ar_file[1] == '\0') {
X		f_reblock++;	/* Could be a pipe, be safe */
X		if(f_verify) {
X			msg("can't verify stdin/stdout archive");
X			exit(EX_ARGSBAD);
X		}
X		if(reading==2) {
X			archive=STDIN;
X			msg_file=stderr;
X			write_archive_to_stdout++;
X		} else if (reading)
X			archive = STDIN;
X		else {
X			archive = STDOUT;
X			msg_file = stderr;
X		}
X	} else if (reading==2 || f_verify) {
X		archive = rmtopen(ar_file, O_RDWR|O_CREAT|O_BINARY, 0666);
X	} else if(reading) {
X		archive = rmtopen(ar_file, O_RDONLY|O_BINARY, 0666);
X	} else {
X		archive = rmtcreat(ar_file, 0666);
X	}
X
X	if (archive < 0) {
X		msg_perror("can't open %s",ar_file);
X		exit(EX_BADARCH);
X	}
X#ifdef	MSDOS
X	setmode(archive, O_BINARY);
X#endif
X
X	if (reading) {
X		ar_last = ar_block;		/* Set up for 1st block = # 0 */
X		(void) findrec();		/* Read it in, check for EOF */
X
X		if(f_volhdr) {
X			union record *head;
X			char *ptr;
X
X			if(f_multivol) {
X				ptr=malloc(strlen(f_volhdr)+20);
X				sprintf(ptr,"%s Volume %d",f_volhdr,1);
X			} else
X				ptr=f_volhdr;
X			head=findrec();
X			if(!head)
X				exit(EX_BADARCH);
X			if(strcmp(ptr,head->header.name)) {
X				msg("Volume mismatch!  %s!=%s\n",ptr,head->header.name);
X				exit(EX_BADARCH);
X			}
X			if(ptr!=f_volhdr)
X				free(ptr);
X		}
X	} else if(f_volhdr) {
X		bzero((void *)ar_block,RECORDSIZE);
X		if(f_multivol)
X			sprintf(ar_block->header.name,"%s Volume 1",f_volhdr);
X		else
X			strcpy(ar_block->header.name,f_volhdr);
X		ar_block->header.linkflag = LF_VOLHDR;
X		finish_header(ar_block);
X		/* ar_record++; */
X	}
X}
X
X
X/*
X * Remember a union record * as pointing to something that we
X * need to keep when reading onward in the file.  Only one such
X * thing can be remembered at once, and it only works when reading
X * an archive.
X *
X * We calculate "offset" then add it because some compilers end up
X * adding (baserec+ar_record), doing a 9-bit shift of baserec, then
X * subtracting ar_block from that, shifting it back, losing the top 9 bits.
X */
Xsaverec(pointer)
X	union record **pointer;
X{
X	long offset;
X
X	save_rec = pointer;
X	offset = ar_record - ar_block;
X	saved_recno = baserec + offset;
X}
X
X/*
X * Perform a write to flush the buffer.
X */
X
X/*send_buffer_to_file();
X  if(new_volume) {
X  	deal_with_new_volume_stuff();
X	send_buffer_to_file();
X  }
X */
X
Xfl_write()
X{
X	int err;
X	int copy_back;
X	extern int errno;
X#ifdef TEST
X	static long test_written = 0;
X#endif
X
X#ifdef TEST
X	if(test_written>=30720) {
X		errno = ENOSPC;
X		err = 0;
X	} else
X#endif
X	err = rmtwrite(archive, ar_block->charptr,(int) blocksize);
X	if(err!=blocksize && !f_multivol)
X		writeerror(err);
X
X#ifdef TEST
X	if(err>0)
X		test_written+=err;
X#endif
X	if (err == blocksize) {
X		if(f_multivol) {
X			if(!save_name) {
X				real_s_name[0]='\0';
X				real_s_totsize=0;
X				real_s_sizeleft = 0;
X				return;
X			}
X#ifdef MSDOS
X			if(save_name[1]==':')
X				save_name+=2;
X#endif
X			while(*save_name=='/')
X				save_name++;
X
X			strcpy(real_s_name,save_name);
X			real_s_totsize = save_totsize;
X			real_s_sizeleft = save_sizeleft;
X		}
X		return;
X	}
X
X#ifndef atarist /* ENOSPC isn't available under GEMDOS; punt */
X	/* We're multivol  Panic if we didn't get the right kind of response */
X	if(err>0 || (errno!=ENOSPC && errno!=EIO))
X		writeerror(err);
X#endif
X
X	if(new_volume(0)<0)
X		return;
X#ifdef TEST
X	test_written=0;
X#endif
X	if(f_volhdr && real_s_name[0]) {
X		copy_back=2;
X		ar_block-=2;
X	} else if(f_volhdr || real_s_name[0]) {
X		copy_back = 1;
X		ar_block--;
X	} else
X		copy_back = 0;
X	if(f_volhdr) {
X		bzero((void *)ar_block,RECORDSIZE);
X		sprintf(ar_block->header.name,"%s Volume %d",f_volhdr,volno);
X		ar_block->header.linkflag = LF_VOLHDR;
X		finish_header(ar_block);
X	}
X	if(real_s_name[0]) {
X		extern void to_oct();
X		int tmp;
X
X		if(f_volhdr)
X			ar_block++;
X		bzero((void *)ar_block,RECORDSIZE);
X		strcpy(ar_block->header.name,real_s_name);
X		ar_block->header.linkflag = LF_MULTIVOL;
X		to_oct((long)real_s_sizeleft,1+12,
X		       ar_block->header.size);
X		to_oct((long)real_s_totsize-real_s_sizeleft,
X		       1+12,ar_block->header.offset);
X		tmp=f_verbose;
X		f_verbose=0;
X		finish_header(ar_block);
X		f_verbose=tmp;
X		if(f_volhdr)
X			ar_block--;
X	}
X
X	err = rmtwrite(archive, ar_block->charptr,(int) blocksize);
X	if(err!=blocksize)
X		writeerror(err);
X
X#ifdef TEST
X	test_written = blocksize;
X#endif
X	if(copy_back) {
X		ar_block+=copy_back;
X		bcopy((void *)(ar_block+blocking-copy_back),
X		      (void *)ar_record,
X		      copy_back*RECORDSIZE);
X		ar_record+=copy_back;
X
X		if(real_s_sizeleft>=copy_back*RECORDSIZE)
X			real_s_sizeleft-=copy_back*RECORDSIZE;
X		else if((real_s_sizeleft+RECORDSIZE-1)/RECORDSIZE<=copy_back)
X			real_s_name[0] = '\0';
X		else {
X#ifdef MSDOS
X			if(save_name[1]==':')
X				save_name+=2;
X#endif
X			while(*save_name=='/')
X				save_name++;
X
X			strcpy(real_s_name,save_name);
X			real_s_sizeleft = save_sizeleft;
X			real_s_totsize=save_totsize;
X		}
X		copy_back = 0;
X	}
X}
X
X/* Handle write errors on the archive.  Write errors are always fatal */
X/* Hitting the end of a volume does not cause a write error unless the write
X*  was the first block of the volume */
X
Xvoid
Xwriteerror(err)
Xint err;
X{
X	if (err < 0) {
X		msg_perror("can't write to %s",ar_file);
X		exit(EX_BADARCH);
X	} else {
X		msg("write to %s short %d bytes",ar_file,blocksize-err);
X		exit(EX_BADARCH);
X	}
X}
X
X/*
X * Handle read errors on the archive.
X *
X * If the read should be retried, readerror() returns to the caller.
X */
Xvoid
Xreaderror()
X{
X#	define	READ_ERROR_MAX	10
X
X	read_error_flag++;		/* Tell callers */
X
X	msg_perror("read error on %s",ar_file);
X
X	if (baserec == 0) {
X		/* First block of tape.  Probably stupidity error */
X		exit(EX_BADARCH);
X	}
X
X	/*
X	 * Read error in mid archive.  We retry up to READ_ERROR_MAX times
X	 * and then give up on reading the archive.  We set read_error_flag
X	 * for our callers, so they can cope if they want.
X	 */
X	if (r_error_count++ > READ_ERROR_MAX) {
X		msg("Too many errors, quitting.");
X		exit(EX_BADARCH);
X	}
X	return;
X}
X
X
X/*
X * Perform a read to flush the buffer.
X */
Xfl_read()
X{
X	int err;		/* Result from system call */
X	int left;		/* Bytes left */
X	char *more;		/* Pointer to next byte to read */
X
X	/*
X	 * Clear the count of errors.  This only applies to a single
X	 * call to fl_read.  We leave read_error_flag alone; it is
X	 * only turned off by higher level software.
X	 */
X	r_error_count = 0;	/* Clear error count */
X
X	/*
X	 * If we are about to wipe out a record that
X	 * somebody needs to keep, copy it out to a holding
X	 * area and adjust somebody's pointer to it.
X	 */
X	if (save_rec &&
X	    *save_rec >= ar_record &&
X	    *save_rec < ar_last) {
X		record_save_area = **save_rec;
X		*save_rec = &record_save_area;
X	}
X	if(write_archive_to_stdout && baserec!=0) {
X		err=rmtwrite(1, ar_block->charptr, blocksize);
X		if(err!=blocksize)
X			writeerror(err);
X	}
X	if(f_multivol) {
X		if(save_name) {
X			if(save_name!=real_s_name) {
X#ifdef MSDOS
X				if(save_name[1]==':')
X					save_name+=2;
X#endif
X				while(*save_name=='/')
X					save_name++;
X
X				strcpy(real_s_name,save_name);
X				save_name=real_s_name;
X			}
X			real_s_totsize = save_totsize;
X			real_s_sizeleft = save_sizeleft;
X				
X		} else {
X			real_s_name[0]='\0';
X			real_s_totsize=0;
X			real_s_sizeleft = 0;
X		}
X	}
X
Xerror_loop:
X	err = rmtread(archive, ar_block->charptr, (int)blocksize);
X	if (err == blocksize)
X		return;
X
X	if (err < 0) {
X		readerror();
X		goto error_loop;	/* Try again */
X	}
X
X	if(err == 0 && f_multivol) {
X		union record *head;
X
X	try_volume:
X		if(new_volume((cmd_mode==CMD_APPEND || cmd_mode==CMD_CAT || cmd_mode==CMD_UPDATE) ? 2 : 1)<0)
X			return;
X	vol_error:
X		err = rmtread(archive, ar_block->charptr,(int) blocksize);
X		if(err < 0) {
X			readerror();
X			goto vol_error;
X		}
X		if(err!=blocksize)
X			goto short_read;
X
X		head=ar_block;
X
X		if(head->header.linkflag==LF_VOLHDR) {
X			if(f_volhdr) {
X				char *ptr;
X
X				ptr=(char *)malloc(strlen(f_volhdr)+20);
X				sprintf(ptr,"%s Volume %d",f_volhdr,volno);
X 				if(strcmp(ptr,head->header.name)) {
X					msg("Volume mismatch! %s!=%s\n",ptr,head->header.name);
X					--volno;
X					free(ptr);
X					goto try_volume;
X				}
X				free(ptr);
X			}
X			if(f_verbose)
X				fprintf(msg_file,"Reading %s\n",head->header.name);
X			head++;
X		} else if(f_volhdr) {
X			msg("Warning:  No volume header!");
X		}
X
X		if(real_s_name[0]) {
X			long from_oct();
X
X			if(head->header.linkflag!=LF_MULTIVOL || strcmp(head->header.name,real_s_name)) {
X				msg("%s is not continued on this volume!",real_s_name);
X				--volno;
X				goto try_volume;
X			}
X			if(real_s_totsize!=from_oct(1+12,head->header.size)+from_oct(1+12,head->header.offset)) {
X				msg("%s is the wrong size (%ld!=%ld+%ld)",
X				       head->header.name,save_totsize,
X				       from_oct(1+12,head->header.size),
X				       from_oct(1+12,head->header.offset));
X				--volno;
X				goto try_volume;
X			}
X			if(real_s_totsize-real_s_sizeleft!=from_oct(1+12,head->header.offset)) {
X				msg("This volume is out of sequence");
X				--volno;
X				goto try_volume;
X			}
X			head++;
X		}
X		ar_record=head;
X		return;
X	}
X
X short_read:
X	more = ar_block->charptr + err;
X	left = blocksize - err;
X
Xagain:
X	if (0 == (((unsigned)left) % RECORDSIZE)) {
X		/* FIXME, for size=0, multi vol support */
X		/* On the first block, warn about the problem */
X		if (!f_reblock && baserec == 0 && f_verbose && err > 0) {
X			msg("Blocksize = %d record%s",
X				err / RECORDSIZE, (err > RECORDSIZE)? "s": "");
X		}
X		ar_last = ar_block + ((unsigned)(blocksize - left))/RECORDSIZE;
X		return;
X	}
X	if (f_reblock) {
X		/*
X		 * User warned us about this.  Fix up.
X		 */
X		if (left > 0) {
Xerror2loop:
X			err = rmtread(archive, more, (int)left);
X			if (err < 0) {
X				readerror();
X				goto error2loop;	/* Try again */
X			}
X			if (err == 0) {
X				msg("archive %s EOF not no block boundry",ar_file);
X				exit(EX_BADARCH);
X			}
X			left -= err;
X			more += err;
X			goto again;
X		}
X	} else {
X		msg("only read %d byes from archive %s",err,ar_file);
X		exit(EX_BADARCH);
X	}
X}
X
X
X/*
X * Flush the current buffer to/from the archive.
X */
Xflush_archive()
X{
X	baserec += ar_last - ar_block;	/* Keep track of block #s */
X	ar_record = ar_block;		/* Restore pointer to start */
X	ar_last = ar_block + blocking;	/* Restore pointer to end */
X
X	if (ar_reading) {
X		if(time_to_start_writing) {
X			time_to_start_writing=0;
X			ar_reading=0;
X
X			if(file_to_switch_to>=0) {
X				rmtclose(archive);
X				archive=file_to_switch_to;
X			} else
X	 			(void)backspace_output();
X			fl_write();
X		} else
X			fl_read();
X	} else {
X		fl_write();
X	}
X}
X
X/* Backspace the archive descriptor by one blocks worth.
X   If its a tape, MTIOCTOP will work.  If its something else,
X   we try to seek on it.  If we can't seek, we lose! */
Xbackspace_output()
X{
X	long cur;
X	/* int er; */
X	extern char *output_start;
X	extern int errno;
X
X#ifdef MTIOCTOP
X	struct mtop t;
X
X	t.mt_op = MTBSR;
X	t.mt_count = 1;
X	if((rmtioctl(archive,MTIOCTOP,&t))>=0)
X		return 1;
X	if(errno==EIO && (rmtioctl(archive,MTIOCTOP,&t))>=0)
X		return 1;
X#endif
X
X		cur=rmtlseek(archive,0L,1);
X	cur-=blocksize;
X	/* Seek back to the beginning of this block and
X	   start writing there. */
X
X	if(rmtlseek(archive,cur,0)!=cur) {
X		/* Lseek failed.  Try a different method */
X		msg("Couldn't backspace archive file.  It may be unreadable without -i.");
X		/* Replace the first part of the block with nulls */
X		if(ar_block->charptr!=output_start)
X			bzero(ar_block->charptr,output_start-ar_block->charptr);
X		return 2;
X	}
X	return 3;
X}
X
X
X/*
X * Close the archive file.
X */
Xclose_archive()
X{
X	int child;
X	int status;
X
X	if (time_to_start_writing || !ar_reading)
X		flush_archive();
X	if(cmd_mode==CMD_DELETE) {
X		long pos;
X
X		pos = rmtlseek(archive,0L,1);
X#ifndef MSDOS
X		/* FIXME does ftruncate really take an INT?! */
X		(void) ftruncate(archive,(int)pos);
X#else
X		(void)rmtwrite(archive,"",0);
X#endif
X	}
X	if(f_verify)
X		verify_volume();
X	(void) rmtclose(archive);
X
X#ifndef	MSDOS
X	if (childpid) {
X		/*
X		 * Loop waiting for the right child to die, or for
X		 * no more kids.
X		 */
X		while (((child = wait(&status)) != childpid) && child != -1)
X			;
X
X		if (child != -1) {
X			switch (TERM_SIGNAL(status)) {
X			case 0:
X				/* Child voluntarily terminated  -- but why? */
X				if (TERM_VALUE(status) == MAGIC_STAT) {
X					exit(EX_SYSTEM);/* Child had trouble */
X				}
X				if (TERM_VALUE(status) == (SIGPIPE + 128)) {
X					/*
X					 * /bin/sh returns this if its child
X					 * dies with SIGPIPE.  'Sok.
X					 */
X					break;
X				} else if (TERM_VALUE(status))
X					msg("child returned status %d",
X						TERM_VALUE(status));
X			case SIGPIPE:
X				break;		/* This is OK. */
X
X			default:
X				msg("child died with signal %d%s",
X				 TERM_SIGNAL(status),
X				 TERM_COREDUMP(status)? " (core dumped)": "");
X			}
X		}
X	}
X#endif	/* MSDOS */
X}
X
X
X#ifdef DONTDEF
X/*
X * Message management.
X *
X * anno writes a message prefix on stream (eg stdout, stderr).
X *
X * The specified prefix is normally output followed by a colon and a space.
X * However, if other command line options are set, more output can come
X * out, such as the record # within the archive.
X *
X * If the specified prefix is NULL, no output is produced unless the
X * command line option(s) are set.
X *
X * If the third argument is 1, the "saved" record # is used; if 0, the
X * "current" record # is used.
X */
Xvoid
Xanno(stream, prefix, savedp)
X	FILE	*stream;
X	char	*prefix;
X	int	savedp;
X{
X#	define	MAXANNO	50
X	char	buffer[MAXANNO];	/* Holds annorecment */
X#	define	ANNOWIDTH 13
X	int	space;
X	long	offset;
X	int	save_e;
X
X	save_e=errno;
X	/* Make sure previous output gets out in sequence */
X	if (stream == stderr)
X		fflush(stdout);
X	if (f_sayblock) {
X		if (prefix) {
X			fputs(prefix, stream);
X			putc(' ', stream);
X		}
X		offset = ar_record - ar_block;
X		(void) sprintf(buffer, "rec %d: ",
X			savedp?	saved_recno:
X				baserec + offset);
X		fputs(buffer, stream);
X		space = ANNOWIDTH - strlen(buffer);
X		if (space > 0) {
X			fprintf(stream, "%*s", space, "");
X		}
X	} else if (prefix) {
X		fputs(prefix, stream);
X		fputs(": ", stream);
X	}
X	errno=save_e;
X}
X#endif
X
X/* We've hit the end of the old volume.  Close it and open the next one */
X/* Values for type:  0: writing  1: reading  2: updating */
Xnew_volume(type)
Xint	type;
X{
X	/* int	c; */
X	char	inbuf[80];
X	char	*p;
X	extern int now_verifying;
X	char *getenv();
X
X	if(now_verifying)
X		return -1;
X	if(f_verify)
X		verify_volume();
X	if(rmtclose(archive)<0) {
X		msg_perror("can't close %s",ar_file);
X		exit(EX_BADARCH);
X	}
X	volno++;
X	for(;;) {
X		fprintf(msg_file,"Prepare volume #%d and hit return: ",volno);
X		if(fgets(inbuf,80,stdin)==0 || inbuf[0]=='\n')
X			break;
X		switch(inbuf[0]) {
X		case '?':
X			fprintf(msg_file,"\
X n [name]   Give a new filename for the next (and subsequent) volume(s)\n\
X q          Abort tar\n\
X !          Spawn a subshell\n\
X ?          Print this list\n");
X			break;
X
X		case 'q':	/* Quit */
X			fprintf(msg_file,"No new volume; exiting.\n");
X			if(cmd_mode!=CMD_EXTRACT && cmd_mode!=CMD_LIST && cmd_mode!=CMD_DIFF)
X				msg("Warning:  Archive is INCOMPLETE!");
X			exit(EX_BADARCH);
X
X		case 'n':	/* Get new file name */
X		{
X			char *q,*r;
X			static char *old_name;
X
X			for(q= &inbuf[1];*q==' ' || *q=='\t';q++)
X				;
X			for(r=q;*r;r++)
X				if(*r=='\n')
X					*r='\0';
X			if(old_name)
X				free(old_name);
X			old_name=p=(char *)malloc((unsigned)(strlen(q)+2));
X			if(p==0) {
X				msg("Can't allocate memory for name");
X				exit(EX_SYSTEM);
X			}
X			(void) strcpy(p,q);
X			ar_file=p;
X			break;
X		}
X
X		case '!':
X#ifdef MSDOS
X# ifdef atarist
X			spawnl(P_WAIT,getenv("SHELL"),NULL);
X# else
X			spawnl(P_WAIT,getenv("COMSPEC"),"-",0);
X# endif
X#else
X				/* JF this needs work! */
X			switch(fork()) {
X			case -1:
X				msg_perror("can't fork!");
X				break;
X			case 0:
X				p=getenv("SHELL");
X				if(p==0) p="/bin/sh";
X				execlp(p,"-sh","-i",0);
X				msg_perror("can't exec a shell %s",p);
X				_exit(55);
X			default:
X				wait(0);
X				break;
X			}
X#endif
X			break;
X		}
X	}
X
X	if(type==2 || f_verify)
X		archive=rmtopen(ar_file,O_RDWR|O_CREAT,0666);
X	else if(type==1)
X		archive=rmtopen(ar_file,O_RDONLY,0666);
X	else if(type==0)
X		archive=rmtcreat(ar_file,0666);
X	else
X		archive= -1;
X
X	if(archive<0) {
X		msg_perror("can't open %s",ar_file);
X		exit(EX_BADARCH);
X	}
X#ifdef MSDOS
X	setmode(archive,O_BINARY);
X#endif
X	return 0;
X}
X
X/* this is a useless function that takes a buffer returned by wantbytes
X   and does nothing with it.  If the function called by wantbytes returns
X   an error indicator (non-zero), this function is called for the rest of
X   the file.
X */
Xint
Xno_op(size,data)
Xint size;
Xchar *data;
X{
X	return 0;
X}
X
X/* Some other routine wants SIZE bytes in the archive.  For each chunk of
X   the archive, call FUNC with the size of the chunk, and the address of
X   the chunk it can work with.
X */
Xint
Xwantbytes(size,func)
Xlong size;
Xint (*func)();
X{
X	char *data;
X	long	data_size;
X
X	while(size) {
X		data = findrec()->charptr;
X		if (data == NULL) {	/* Check it... */
X			msg("Unexpected EOF on archive file");
X			return -1;
X		}
X		data_size = endofrecs()->charptr - data;
X		if(data_size>size)
X			data_size=size;
X		if((*func)(data_size,data))
X			func=no_op;
X		userec((union record *)(data + data_size - 1));
X		size-=data_size;
X	}
X	return 0;
X}
SHAR_EOF
chmod 0644 BUFFER.C || echo "restore of BUFFER.C fails"
echo "x - extracting COPYING (Text)"
sed 's/^X//' << 'SHAR_EOF' > COPYING &&
X
X		    GNU TAR GENERAL PUBLIC LICENSE
X		    (Clarified 11 Feb 1988)
X
X Copyright (C) 1988 Richard M. Stallman
X Everyone is permitted to copy and distribute verbatim copies
X of this license, but changing it is not allowed.  You can also
X use this wording to make the terms for other programs.
X
X  The license agreements of most software companies keep you at the
Xmercy of those companies.  By contrast, our general public license is
Xintended to give everyone the right to share GNU tar.  To make sure that
Xyou get the rights we want you to have, we need to make restrictions
Xthat forbid anyone to deny you these rights or to ask you to surrender
Xthe rights.  Hence this license agreement.
X
X  Specifically, we want to make sure that you have the right to give
Xaway copies of GNU tar, that you receive source code or else can get it
Xif you want it, that you can change GNU tar or use pieces of it in new
Xfree programs, and that you know you can do these things.
X
X  To make sure that everyone has such rights, we have to forbid you to
Xdeprive anyone else of these rights.  For example, if you distribute
Xcopies of GNU tar, you must give the recipients all the rights that you
Xhave.  You must make sure that they, too, receive or can get the
Xsource code.  And you must tell them their rights.
X
X  Also, for our own protection, we must make certain that everyone
Xfinds out that there is no warranty for GNU tar.  If GNU tar is modified by
Xsomeone else and passed on, we want its recipients to know that what
Xthey have is not what we distributed, so that any problems introduced
Xby others will not reflect on our reputation.
X
X  Therefore we (Richard Stallman and the Free Software Foundation,
XInc.) make the following terms which say what you must do to be
Xallowed to distribute or change GNU tar.
X
X
X			COPYING POLICIES
X
X  1. You may copy and distribute verbatim copies of GNU tar source code as
Xyou receive it, in any medium, provided that you conspicuously and
Xappropriately publish on each copy a valid copyright notice "Copyright
X(C) 1988 Free Software Foundation, Inc." (or with whatever year is
Xappropriate); keep intact the notices on all files that refer to this
XLicense Agreement and to the absence of any warranty; and give any
Xother recipients of the GNU tar program a copy of this License
XAgreement along with the program.  You may charge a distribution
Xfee for the physical act of transferring a copy.
X
X  2. You may modify your copy or copies of GNU tar or any portion of it,
Xand copy and distribute such modifications under the terms of
XParagraph 1 above, provided that you also do the following:
X
X    a) cause the modified files to carry prominent notices stating
X    that you changed the files and the date of any change; and
X
X    b) cause the whole of any work that you distribute or publish,
X    that in whole or in part contains or is a derivative of GNU tar or
X    any part thereof, to be licensed at no charge to all third
X    parties on terms identical to those contained in this License
X    Agreement (except that you may choose to grant more extensive
X    warranty protection to third parties, at your option).
X
X    c) You may charge a distribution fee for the physical act of
X    transferring a copy, and you may at your option offer warranty
X    protection in exchange for a fee.
X
XMere aggregation of another unrelated program with this program (or its
Xderivative) on a volume of a storage or distribution medium does not bring
Xthe other program under the scope of these terms.
X
X  3. You may copy and distribute GNU CC (or a portion or derivative of it,
Xunder Paragraph 2) in object code or executable form under the terms of
XParagraphs 1 and 2 above provided that you also do one of the following:
X
X    a) accompany it with the complete corresponding machine-readable
X    source code, which must be distributed under the terms of
X    Paragraphs 1 and 2 above; or,
X
X    b) accompany it with a written offer, valid for at least three
X    years, to give any third party free (except for a nominal
X    shipping charge) a complete machine-readable copy of the
X    corresponding source code, to be distributed under the terms of
X    Paragraphs 1 and 2 above; or,
X
X    c) accompany it with the information you received as to where the
X    corresponding source code may be obtained.  (This alternative is
X    allowed only for noncommercial distribution and only if you
X    received the program in object code or executable form alone.)
X
XFor an executable file, complete source code means all the source code for
Xall modules it contains; but, as a special exception, it need not include
Xsource code for modules which are standard libraries that accompany the
Xoperating system on which the executable file runs.
X
X  4. You may not copy, sublicense, distribute or transfer GNU tar
Xexcept as expressly provided under this License Agreement.  Any attempt
Xotherwise to copy, sublicense, distribute or transfer GNU tar is void and
Xyour rights to use the program under this License agreement shall be
Xautomatically terminated.  However, parties who have received computer
Xsoftware programs from you with this License Agreement will not have
Xtheir licenses terminated so long as such parties remain in full compliance.
X
X  5. If you wish to incorporate parts of GNU tar into other free
Xprograms whose distribution conditions are different, write to the Free
XSoftware Foundation at 675 Mass Ave, Cambridge, MA 02139.  We have not yet
Xworked out a simple rule that can be stated here, but we will often permit
Xthis.  We will be guided by the two goals of preserving the free status of
Xall derivatives of our free software and of promoting the sharing and reuse
Xof software.
X
X
XYour comments and suggestions about our licensing policies and our
Xsoftware are welcome!  Please contact the Free Software Foundation, Inc.,
X675 Mass Ave, Cambridge, MA 02139, or call (617) 876-3296.
X
X		       NO WARRANTY
X
X  BECAUSE GNU tar IS LICENSED FREE OF CHARGE, WE PROVIDE ABSOLUTELY NO
XWARRANTY, TO THE EXTENT PERMITTED BY APPLICABLE STATE LAW.  EXCEPT
XWHEN OTHERWISE STATED IN WRITING, FREE SOFTWARE FOUNDATION, INC,
XRICHARD M. STALLMAN AND/OR OTHER PARTIES PROVIDE GNU tar "AS IS" WITHOUT
XWARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
XLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
XA PARTICULAR PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND
XPERFORMANCE OF GNU tar IS WITH YOU.  SHOULD GNU tar PROVE DEFECTIVE, YOU
XASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
X
X IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL RICHARD M.
SHAR_EOF
echo "End of part 1"
echo "File COPYING is continued in part 2"
echo "2" > s2_seq_.tmp
exit 0