dpz@klinzhai.RUTGERS.EDU (David P. Zimmerman) (01/16/87)
Hello all,
Mike Meyer mentioned a PD tar; well, I found one! Have fun
porting it. This is shar #1 of 2, run it through your favorite Bourne
shell or Amiga shar program.
dpz
# This is a shell archive.
# Remove everything above and including the cut line.
# Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar: Shell Archiver
# Run the following text with /bin/sh to create:
# buffer.c
# create.c
# extract.c
# getoldopt.c
# list.c
# names.c
# port.c
# port.h
# tar.c
# tar.h
# This archive created: Thu Jan 15 21:02:13 1987
cat << \SHAR_EOF > buffer.c
/*
* Buffer management for public domain tar.
*
* Written by John Gilmore, ihnp4!hoptoad!gnu, on 25 August 1985.
*
* @(#) buffer.c 1.14 10/28/86 Public Domain - gnu
*/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h> /* For non-Berkeley systems */
#include <sys/file.h>
#include <signal.h>
#include "tar.h"
#include "port.h"
#define STDIN 0 /* Standard input file descriptor */
#define STDOUT 1 /* Standard output file descriptor */
#define PREAD 0 /* Read file descriptor from pipe() */
#define PWRITE 1 /* Write file descriptor from pipe() */
extern char *valloc();
/*
* V7 doesn't have a #define for this.
*/
#ifndef O_RDONLY
#define O_RDONLY 0
#endif
#define MAGIC_STAT 105 /* Magic status returned by child, if
it can't exec compress. We hope compress
never returns this status! */
/*
* The record pointed to by save_rec should not be overlaid
* when reading in a new tape block. Copy it to record_save_area first, and
* change the pointer in *save_rec to point to record_save_area.
* Saved_recno records the record number at the time of the save.
* This is used by annofile() to print the record number of a file's
* header record.
*/
static union record **save_rec;
static union record record_save_area;
static int saved_recno;
/*
* PID of child compress program, if f_compress.
*/
static int compress_pid;
/*
* Record number of the start of this block of records
*/
static int baserec;
/*
* Error recovery stuff
*/
static int r_error_count;
/*
* Return the location of the next available input or output record.
*/
union record *
findrec()
{
if (ar_record == ar_last) {
flush_archive();
if (ar_record == ar_last)
return (union record *)NULL; /* EOF */
}
return ar_record;
}
/*
* Indicate that we have used all records up thru the argument.
* (should the arg have an off-by-1? XXX FIXME)
*/
void
userec(rec)
union record *rec;
{
while(rec >= ar_record)
ar_record++;
/*
* Do NOT flush the archive here. If we do, the same
* argument to userec() could mean the next record (if the
* input block is exactly one record long), which is not what
* is intended.
*/
if (ar_record > ar_last)
abort();
}
/*
* Return a pointer to the end of the current records buffer.
* All the space between findrec() and endofrecs() is available
* for filling with data, or taking data from.
*/
union record *
endofrecs()
{
return ar_last;
}
/*
* Open an archive file. The argument specifies whether we are
* reading or writing.
*/
open_archive(read)
int read;
{
if (ar_file[0] == '-' && ar_file[1] == '\0') {
if (read) archive = STDIN;
else archive = STDOUT;
} else if (read) {
archive = open(ar_file, O_RDONLY);
} else {
archive = creat(ar_file, 0666);
}
if (archive < 0) {
perror(ar_file);
exit(EX_BADARCH);
}
/*NOSTRICT*/
ar_block = (union record *) valloc((unsigned)blocksize);
if (!ar_block) {
fprintf(stderr,
"tar: could not allocate memory for blocking factor %d\n",
blocking);
exit(EX_ARGSBAD);
}
ar_record = ar_block;
ar_last = ar_block + blocking;
/*
* Handle compressed archives.
*
* FIXME, currently supported for reading only.
* FIXME, writing involves forking again for a small process
* that will reblock the output of compress to the user's specs.
*/
if (f_compress) {
int pipes[2];
int err;
if (!read) {
fprintf(stderr,
"tar: cannot write compressed archives yet.\n");
exit(EX_ARGSBAD);
}
/* Create a pipe to get compress's output to us */
err = pipe(pipes);
if (err < 0) {
perror ("tar: cannot create pipe to compress");
exit(EX_SYSTEM);
}
/* Fork compress process */
compress_pid = fork();
if (compress_pid < 0) {
perror("tar: cannot fork compress");
exit(EX_SYSTEM);
}
/*
* Child process.
*
* Move input to stdin, write side of pipe to stdout,
* then exec compress.
*/
if (compress_pid == 0) {
(void) close (pipes[PREAD]); /* We won't use it */
if (archive != STDIN) {
(void) close(STDIN);
err = dup(archive);
if (err != 0) {
perror(
"tar: cannot dup input to stdin");
exit(EX_SYSTEM);
}
(void) close(archive);
}
if (pipes[PWRITE] != STDOUT) {
(void) close (STDOUT);
err = dup (pipes[PWRITE]);
if (err != STDOUT) {
perror(
"tar: cannot dup pipe output");
exit(MAGIC_STAT);
}
(void) close (pipes[PWRITE]);
}
execlp("compress", "compress", "-d", (char *)0);
perror("tar: cannot exec compress");
exit(MAGIC_STAT);
}
/*
* Parent process. Clean up.
* FIXME, note that this may leave standard input closed,
* if the compressed archive was on standard input.
*/
(void) close (archive); /* Close compressed archive */
(void) close (pipes[PWRITE]); /* Close write side of pipe */
archive = pipes[PREAD]; /* Read side is our archive */
#ifdef BSD42
f_reblock++; /* Pipe will give random # of bytes */
#endif BSD42
}
ar_reading = read;
if (read) {
ar_last = ar_block; /* Set up for 1st block = # 0 */
flush_archive();
}
}
/*
* Remember a union record * as pointing to something that we
* need to keep when reading onward in the file. Only one such
* thing can be remembered at once, and it only works when reading
* an archive.
*/
saverec(pointer)
union record **pointer;
{
save_rec = pointer;
saved_recno = baserec + ar_record - ar_block;
}
/*
* Perform a write to flush the buffer.
*/
fl_write()
{
int err;
err = write(archive, ar_block->charptr, blocksize);
if (err == blocksize) return;
/* FIXME, multi volume support on write goes here */
if (err < 0)
perror(ar_file);
else
fprintf(stderr, "tar: %s: write failed, short %d bytes\n",
ar_file, blocksize - err);
exit(EX_BADARCH);
}
/*
* Handle read errors on the archive.
*
* If the read should be retried, readerror() returns to the caller.
*/
void
readerror()
{
# define READ_ERROR_MAX 10
read_error_flag++; /* Tell callers */
annorec(stderr, tar);
fprintf(stderr, "Read error on ");
perror(ar_file);
if (baserec == 0) {
/* First block of tape. Probably stupidity error */
exit(EX_BADARCH);
}
/*
* Read error in mid archive. We retry up to READ_ERROR_MAX times
* and then give up on reading the archive. We set read_error_flag
* for our callers, so they can cope if they want.
*/
if (r_error_count++ > READ_ERROR_MAX) {
annorec(stderr, tar);
fprintf(stderr, "Too many errors, quitting.\n");
exit(EX_BADARCH);
}
return;
}
/*
* Perform a read to flush the buffer.
*/
fl_read()
{
int err; /* Result from system call */
int left; /* Bytes left */
char *more; /* Pointer to next byte to read */
/*
* Clear the count of errors. This only applies to a single
* call to fl_read. We leave read_error_flag alone; it is
* only turned off by higher level software.
*/
r_error_count = 0; /* Clear error count */
/*
* If we are about to wipe out a record that
* somebody needs to keep, copy it out to a holding
* area and adjust somebody's pointer to it.
*/
if (save_rec &&
*save_rec >= ar_record &&
*save_rec < ar_last) {
record_save_area = **save_rec;
*save_rec = &record_save_area;
}
error_loop:
err = read(archive, ar_block->charptr, blocksize);
if (err == blocksize) return;
if (err < 0) {
readerror();
goto error_loop; /* Try again */
}
more = ar_block->charptr + err;
left = blocksize - err;
again:
if (0 == (((unsigned)left) % RECORDSIZE)) {
/* FIXME, for size=0, multi vol support */
/* On the first block, warn about the problem */
if (!f_reblock && baserec == 0 && f_verbose) {
annorec(stderr, tar);
fprintf(stderr, "Blocksize = %d records\n",
err / RECORDSIZE);
}
ar_last = ar_block + ((unsigned)(blocksize - left))/RECORDSIZE;
return;
}
if (f_reblock) {
/*
* User warned us about this. Fix up.
*/
if (left > 0) {
error_loop_2:
err = read(archive, more, left);
if (err < 0) {
readerror();
goto error_loop_2; /* Try again */
}
if (err == 0) {
annorec(stderr, tar);
fprintf(stderr,
"%s: eof not on block boundary, strange...\n",
ar_file);
exit(EX_BADARCH);
}
left -= err;
more += err;
goto again;
}
} else {
annorec(stderr, tar);
fprintf(stderr, "%s: read %d bytes, strange...\n",
ar_file, err);
exit(EX_BADARCH);
}
}
/*
* Flush the current buffer to/from the archive.
*/
flush_archive()
{
baserec += ar_last - ar_block;/* Keep track of block #s */
ar_record = ar_block; /* Restore pointer to start */
ar_last = ar_block + blocking; /* Restore pointer to end */
if (!ar_reading)
fl_write();
else
fl_read();
}
/*
* Close the archive file.
*/
close_archive()
{
int child;
int status;
if (!ar_reading) flush_archive();
(void) close(archive);
if (f_compress) {
/*
* Loop waiting for the right child to die, or for
* no more kids.
*/
while (((child = wait(&status)) != compress_pid) && child != -1)
;
if (child != -1) {
switch (TERM_SIGNAL(status)) {
case 0: /* Terminated by itself */
if (TERM_VALUE(status) == MAGIC_STAT) {
exit(EX_SYSTEM);/* Child had trouble */
}
if (TERM_VALUE(status))
fprintf(stderr,
"tar: compress child returned status %d\n",
TERM_VALUE(status));
case SIGPIPE:
break; /* This is OK. */
default:
fprintf(stderr,
"tar: compress child died with signal %d%s\n",
TERM_SIGNAL(status),
TERM_COREDUMP(status)? " (core dumped)": "");
}
}
}
}
/*
* Message management.
*
* anno writes a message prefix on stream (eg stdout, stderr).
*
* The specified prefix is normally output followed by a colon and a space.
* However, if other command line options are set, more output can come
* out, such as the record # within the archive.
*
* If the specified prefix is NULL, no output is produced unless the
* command line option(s) are set.
*
* If the third argument is 1, the "saved" record # is used; if 0, the
* "current" record # is used.
*/
void
anno(stream, prefix, savedp)
FILE *stream;
char *prefix;
int savedp;
{
# define MAXANNO 50
char buffer[MAXANNO]; /* Holds annorecment */
# define ANNOWIDTH 13
int space;
if (f_sayblock) {
if (prefix) {
fputs(prefix, stream);
putc(' ', stream);
}
sprintf(buffer, "rec %d: ",
savedp? saved_recno:
baserec + ar_record - ar_block);
fputs(buffer, stream);
space = ANNOWIDTH - strlen(buffer);
if (space > 0) {
fprintf(stream, "%*s", space, "");
}
} else if (prefix) {
fputs(prefix, stream);
fputs(": ", stream);
}
}
SHAR_EOF
cat << \SHAR_EOF > create.c
/*
* Create a tar archive.
*
* Written 25 Aug 1985 by John Gilmore, ihnp4!hoptoad!gnu.
*
* @(#)create.c 1.19 9/9/86 Public Domain - gnu
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#ifdef BSD42
#include <sys/dir.h>
#else
/*
* FIXME: On other systems there is no standard place for the header file
* for the portable directory access routines. Change the #include line
* below to bring it in from wherever it is.
*/
#include "ndir.h"
#endif
#ifdef USG
#include <sys/sysmacros.h> /* major() and minor() defined here */
#endif
/*
* V7 doesn't have a #define for this.
*/
#ifndef O_RDONLY
#define O_RDONLY 0
#endif
#include "tar.h"
/*
* If there are no symbolic links, there is no lstat(). Use stat().
*/
#ifndef S_IFLNK
#define lstat stat
#endif
extern char *malloc();
extern char *strcpy();
extern char *strncpy();
extern int errno;
union record *start_header();
void finish_header();
void finduname();
void findgname();
char *name_next();
void to_oct();
void
create_archive()
{
register char *p;
open_archive(0); /* Open for writing */
while (p = name_next()) {
dump_file(p);
}
write_eot();
close_archive();
name_close();
}
/*
* Dump a single file. If it's a directory, recurse.
* Result is 1 for success, 0 for failure.
*/
int
dump_file(p)
char *p; /* File name to dump */
{
struct stat statbuf[1];
union record *header;
char type;
/*
* Use stat if following (rather than dumping) 4.2BSD's
* symbolic links. Otherwise, use lstat (which, on non-4.2
* systems, is #define'd to stat anyway.
*/
if (0 != f_follow_links? stat(p, statbuf): lstat(p, statbuf))
{
badperror:
perror(p);
badfile:
errors++;
return 0;
}
switch (statbuf->st_mode & S_IFMT) {
case S_IFREG: /* Regular file */
{
int f; /* File descriptor */
int bufsize, count;
register long sizeleft;
register union record *start;
/*
* Handle a regular file with multiple links.
*
* We maintain a list of all such files that we've written so
* far. Any time we see another, we check the list and
* avoid dumping the data again if we've done it once already.
*/
if (statbuf->st_nlink > 1) {
register struct link *lp;
/* First quick and dirty. Hashing, etc later FIXME */
for (lp = linklist; lp; lp = lp->next) {
if (lp->ino == statbuf->st_ino &&
lp->dev == statbuf->st_dev) {
/* We found a link. */
statbuf->st_size = 0;
header = start_header(p, statbuf);
if (header == NULL) goto badfile;
strcpy(header->header.linkname,
lp->name);
header->header.linkflag = LF_LINK;
finish_header(header);
if (f_verbose)
annorec(stdout, (char *)NULL);
printf("%s link to %s\n",
p, lp->name);
/* Maybe remove from list after all links found? */
/* If so, have to compare names in case he dumps twice. */
/* Later: I don't understand the above. If she
* dumps the file twice, it would be BAD to dump
* it the second time as a link... gnu 25Jul86
*/
/* FIXME */
goto donefile;
}
}
/* Not found. Add it to the list. */
lp = (struct link *) malloc( (unsigned)
(strlen(p) + sizeof(struct link) - NAMSIZ));
lp->ino = statbuf->st_ino;
lp->dev = statbuf->st_dev;
strcpy(lp->name, p);
lp->next = linklist;
linklist = lp;
}
sizeleft = statbuf->st_size;
/* Don't bother opening empty, world readable files. */
if (sizeleft > 0 || 0444 != (0444 & statbuf->st_mode)) {
f = open(p, O_RDONLY);
if (f < 0) goto badperror;
} else {
f = -1;
}
header = start_header(p, statbuf);
if (header == NULL) goto badfile;
finish_header(header);
while (sizeleft > 0) {
start = findrec();
bufsize = endofrecs()->charptr - start->charptr;
if (sizeleft < bufsize)
bufsize = sizeleft;
count = read(f, start->charptr, bufsize);
if (count < 0) {
annorec(stderr, tar);
fprintf(stderr,
"read error at byte %ld, reading %d bytes, in file ",
statbuf->st_size - sizeleft,
bufsize);
perror(p); /* FIXME */
goto padit;
}
sizeleft -= count;
userec(start+(count-1)/RECORDSIZE);
if (count == bufsize) continue;
annorec(stderr, tar);
fprintf(stderr,
"%s: file shrunk by %d bytes, padding with zeros.\n",
p, sizeleft);
goto padit; /* Short read */
}
if (f >= 0)
(void)close(f);
/* Clear last block garbage to zeros, FIXME */
if (f_verbose) {
annorec(stdout, (char *)NULL);
printf("%s\n", p);
}
donefile:
break;
/*
* File shrunk or gave error, pad out tape to match
* the size we specified in the header.
*/
padit:
abort();
}
#ifdef S_IFLNK
case S_IFLNK: /* Symbolic link */
{
int size;
statbuf->st_size = 0; /* Force 0 size on symlink */
header = start_header(p, statbuf);
if (header == NULL) goto badfile;
size = readlink(p, header->header.linkname, NAMSIZ);
if (size < 0) goto badperror;
if (size == NAMSIZ) {
annorec(stderr, tar);
fprintf(stderr,
"%s: symbolic link too long\n", p);
break;
}
header->header.linkname[size] = '\0';
header->header.linkflag = LF_SYMLINK;
finish_header(header); /* Nothing more to do to it */
if (f_verbose) {
annorec(stdout, (char *)NULL);
printf("%s\n", p);
}
}
break;
#endif
case S_IFDIR: /* Directory */
{
register DIR *dirp;
register struct direct *d;
char namebuf[NAMSIZ+2];
register int len;
/* Build new prototype name */
strncpy(namebuf, p, sizeof (namebuf));
len = strlen(namebuf);
while (len >= 1 && '/' == namebuf[len-1])
len--; /* Delete trailing slashes */
namebuf[len++] = '/'; /* Now add exactly one back */
/*
* Output directory header record with permissions
* FIXME, do this AFTER files, to avoid R/O dir problems?
* If Unix Std format, don't put / on end of dir name
* If old archive format, don't write record at all.
*/
if (!f_oldarch) {
statbuf->st_size = 0; /* Force 0 size on dir */
/*
* If people could really read standard archives,
* this should be: (FIXME)
header = start_header(f_standard? p: namebuf, statbuf);
* but since they'd interpret LF_DIR records as
* regular files, we'd better put the / on the name.
*/
header = start_header(namebuf, statbuf);
if (header == NULL)
goto badfile; /* eg name too long */
if (f_standard) {
header->header.linkflag = LF_DIR;
}
finish_header(header); /* Done with directory header */
}
if (f_verbose) {
annorec(stdout, (char *)NULL);
printf("%s\n", p);
}
/* Hack to remove "./" from the front of all the file names */
if (len == 2 && namebuf[0] == '.') {
len = 0;
}
/* Now output all the files in the directory */
errno = 0;
dirp = opendir(p);
if (!dirp) {
if (errno) {
perror (p);
} else {
annorec(stderr, tar);
fprintf(stderr, "%s: error opening directory",
p);
}
break;
}
/* Should speed this up by cd-ing into the dir, FIXME */
while (NULL != (d=readdir(dirp))) {
/* Skip . and .. */
if (d->d_name[0] == '.') {
if (d->d_name[1] == '\0') continue;
if (d->d_name[1] == '.') {
if (d->d_name[2] == '\0') continue;
}
}
if (d->d_namlen + len >= NAMSIZ) {
annorec(stderr, tar);
fprintf(stderr, "%s%s: name too long\n",
namebuf, d->d_name);
continue;
}
strcpy(namebuf+len, d->d_name);
dump_file(namebuf);
}
closedir(dirp);
}
break;
case S_IFCHR: /* Character special file */
type = LF_CHR;
goto easy;
case S_IFBLK: /* Block special file */
type = LF_BLK;
goto easy;
#ifdef S_IFIFO
case S_IFIFO: /* Fifo special file */
type = LF_FIFO;
#endif S_IFIFO
easy:
if (!f_standard) goto unknown;
statbuf->st_size = 0; /* Force 0 size */
header = start_header(p, statbuf);
if (header == NULL) goto badfile; /* eg name too long */
header->header.linkflag = type;
if (type != LF_FIFO) {
to_oct((long) major(statbuf->st_rdev), 8,
header->header.devmajor);
to_oct((long) minor(statbuf->st_rdev), 8,
header->header.devminor);
}
finish_header(header);
if (f_verbose) {
annorec(stdout, (char *)NULL);
printf("%s\n", p);
}
break;
default:
unknown:
annorec(stderr, tar);
fprintf(stderr,
"%s: Unknown file type; file ignored.\n", p);
break;
}
return 1; /* Success */
}
/*
* Make a header block for the file name whose stat info is st .
* Return header pointer for success, NULL if the name is too long.
*/
union record *
start_header(name, st)
char *name;
register struct stat *st;
{
register union record *header;
header = (union record *) findrec();
bzero(header->charptr, sizeof(*header)); /* XXX speed up */
strcpy(header->header.name, name);
if (header->header.name[NAMSIZ-1]) {
annorec(stderr, tar);
fprintf(stderr, "%s: name too long\n", name);
return NULL;
}
to_oct((long) (st->st_mode & ~S_IFMT),
8, header->header.mode);
to_oct((long) st->st_uid, 8, header->header.uid);
to_oct((long) st->st_gid, 8, header->header.gid);
to_oct((long) st->st_size, 1+12, header->header.size);
to_oct((long) st->st_mtime, 1+12, header->header.mtime);
/* header->header.linkflag is left as null */
#ifndef NONAMES
/* Fill in new Unix Standard fields if desired. */
if (f_standard) {
header->header.linkflag = LF_NORMAL; /* New default */
strcpy(header->header.magic, TMAGIC); /* Mark as Unix Std */
finduname(header->header.uname, st->st_uid);
findgname(header->header.gname, st->st_gid);
}
#endif
return header;
}
/*
* Finish off a filled-in header block and write it out.
*/
void
finish_header(header)
register union record *header;
{
register int i, sum;
register char *p;
bcopy(CHKBLANKS, header->header.chksum, sizeof(header->header.chksum));
sum = 0;
p = header->charptr;
for (i = sizeof(*header); --i >= 0; ) {
/*
* We can't use unsigned char here because of old compilers,
* e.g. V7.
*/
sum += 0xFF & *p++;
}
/*
* Fill in the checksum field. It's formatted differently
* from the other fields: it has [6] digits, a null, then a
* space -- rather than digits, a space, then a null.
* We use to_oct then write the null in over to_oct's space.
* The final space is already there, from checksumming, and
* to_oct doesn't modify it.
*
* This is a fast way to do:
* (void) sprintf(header->header.chksum, "%6o", sum);
*/
to_oct((long) sum, 8, header->header.chksum);
header->header.chksum[6] = '\0'; /* Zap the space */
userec(header);
return;
}
/*
* Quick and dirty octal conversion.
* Converts long "value" into a "digs"-digit field at "where",
* including a trailing space and room for a null. "digs"==3 means
* 1 digit, a space, and room for a null.
*
* We assume the trailing null is already there and don't fill it in.
* This fact is used by start_header and finish_header, so don't change it!
*
* This should be equivalent to:
* (void) sprintf(where, "%*lo ", digs-2, value);
* except that sprintf fills in the trailing null and we don't.
*/
void
to_oct(value, digs, where)
register long value;
register int digs;
register char *where;
{
--digs; /* Trailing null slot is left alone */
where[--digs] = ' '; /* Put in the space, though */
/* Produce the digits -- at least one */
do {
where[--digs] = '0' + (value & 7); /* one octal digit */
value >>= 3;
} while (digs > 0 && value != 0);
/* Leading spaces, if necessary */
while (digs > 0)
where[--digs] = ' ';
}
/*
* Write the EOT block(s).
*/
write_eot()
{
union record *p;
p = findrec();
bzero(p->charptr, RECORDSIZE);
userec(p);
/* FIXME, only one EOT block should be needed. */
p = findrec();
bzero(p->charptr, RECORDSIZE);
userec(p);
}
SHAR_EOF
cat << \SHAR_EOF > extract.c
/*
* Extract files from a tar archive.
*
* Written 19 Nov 1985 by John Gilmore, ihnp4!hoptoad!gnu.
*
* @(#) extract.c 1.17 86/10/29 Public Domain - gnu
*/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef BSD42
#include <sys/file.h>
#endif
#ifdef USG
#include <fcntl.h>
#endif
extern int errno; /* From libc.a */
extern char *index(); /* From libc.a or port.c */
#include "tar.h"
extern union record *head; /* Points to current tape header */
extern struct stat hstat[1]; /* Stat struct corresponding */
extern void print_header();
extern void skip_file();
extern void pr_mkdir();
int make_dirs(); /* Makes required directories */
time_t now = 0; /* Current time */
/*
* Extract a file from the archive.
*/
void
extract_archive()
{
register char *data;
int fd, check, namelen, written;
long size;
time_t acc_upd_times[2];
int standard; /* Is header standard? */
saverec(&head); /* Make sure it sticks around */
userec(head); /* And go past it in the archive */
decode_header(head, hstat, &standard, 1); /* Snarf fields */
/* Print the record from 'head' and 'hstat' */
if (f_verbose)
print_header();
switch (head->header.linkflag) {
default:
annofile(stderr, tar);
fprintf(stderr, "Unknown file type %d for %s\n",
head->header.linkflag, head->header.name);
/* FALL THRU */
case LF_OLDNORMAL:
case LF_NORMAL:
/*
* Appears to be a file.
* See if it's really a directory.
*/
namelen = strlen(head->header.name)-1;
if (head->header.name[namelen] == '/')
goto really_dir;
/* FIXME, deal with protection issues */
/* FIXME, f_keep doesn't work on V7, st_mode loses too */
again_file:
fd = open(head->header.name,
f_keep?
O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_EXCL:
O_NDELAY|O_WRONLY|O_APPEND|O_CREAT|O_TRUNC,
hstat->st_mode);
if (fd < 0) {
if (make_dirs(head->header.name))
goto again_file;
annofile(stderr, tar);
fprintf(stderr, "Could not make file ");
perror(head->header.name);
skip_file((long)hstat->st_size);
goto quit;
}
for (size = hstat->st_size;
size > 0;
size -= written) {
/*
* Locate data, determine max length
* writeable, write it, record that
* we have used the data, then check
* if the write worked.
*/
data = findrec()->charptr;
written = endofrecs()->charptr - data;
if (written > size) written = size;
errno = 0;
check = write (fd, data, written);
/*
* The following is in violation of strict
* typing, since the arg to userec
* should be a struct rec *. FIXME.
*/
userec(data + written - 1);
if (check == written) continue;
/*
* Error in writing to file.
* Print it, skip to next file in archive.
*/
annofile(stderr, tar);
fprintf(stderr,
"Tried to write %d bytes to file, could only write %d:\n",
written, check);
perror(head->header.name);
(void) close(fd);
skip_file((long)(size - written));
goto quit;
}
check = close(fd);
if (check < 0) {
annofile(stderr, tar);
fprintf(stderr, "Error while closing ");
perror(head->header.name);
}
/* FIXME, deal with uid/gid/mtimes/suid */
/*
* Set the modified time of the file.
*
* Note that we set the accessed time to "now", which
* is really "the time we started extracting files".
*/
if (!f_modified) {
if (!now)
now = time((time_t *)0); /* Just do it once */
acc_upd_times[0] = now; /* Accessed now */
acc_upd_times[1] = hstat->st_mtime; /* Mod'd */
if (utime(head->header.name, acc_upd_times) < 0) {
annofile(stderr, tar);
perror(head->header.name);
}
}
/*
* If '-p' is not set, OR if the file has pretty normal
* mode bits, we can skip the chmod and save a sys call.
* This works because we did umask(0) if -p is set, so
* the open() that created the file will have set the modes
* properly.
* FIXME: I don't know what open() does w/UID/GID/SVTX bits.
* However, if we've done a chown(), they got reset.
*/
if (f_use_protection
&& (hstat->st_mode & (S_ISUID|S_ISGID|S_ISVTX))) {
if (chmod(head->header.name, (int)hstat->st_mode) < 0) {
annofile(stderr, tar);
perror(head->header.name);
}
}
quit:
break;
case LF_LINK:
again_link:
check = link (head->header.linkname,
head->header.name);
/* FIXME, don't worry uid, gid, etc... */
if (check == 0)
break;
if (make_dirs(head->header.linkname))
goto again_link;
annofile(stderr, tar);
fprintf(stderr, "Could not link %s to ",
head->header.name);
perror(head->header.linkname);
break;
#ifdef S_IFLNK
case LF_SYMLINK:
again_symlink:
check = symlink(head->header.linkname,
head->header.name);
/* FIXME, don't worry uid, gid, etc... */
if (check == 0)
break;
if (make_dirs(head->header.linkname))
goto again_symlink;
annofile(stderr, tar);
fprintf(stderr, "Could not create symlink ");
perror(head->header.linkname);
break;
#endif
case LF_CHR:
hstat->st_mode |= S_IFCHR;
goto make_node;
case LF_BLK:
hstat->st_mode |= S_IFBLK;
make_node:
check = mknod(head->header.name, (int) hstat->st_mode,
(int) hstat->st_dev);
if (check != 0) {
if (make_dirs(head->header.name))
goto make_node;
annofile(stderr, tar);
fprintf(stderr, "Could not make special file ");
perror(head->header.name);
break;
};
break;
case LF_DIR:
/* Check for trailing / */
namelen = strlen(head->header.name)-1;
really_dir:
while (namelen && head->header.name[namelen] == '/')
head->header.name[namelen--] = '\0'; /* Zap / */
/* FIXME, deal with umask */
again_dir:
check = mkdir(head->header.name, (int)hstat->st_mode);
if (check != 0) {
if (make_dirs(head->header.name))
goto again_dir;
annofile(stderr, tar);
fprintf(stderr, "Could not make directory ");
perror(head->header.name);
break;
}
/* FIXME, deal with uid/gid */
/* FIXME, Remember timestamps for after files created? */
break;
case LF_FIFO:
abort(); /* FIXME */
break;
}
/* We don't need to save it any longer. */
saverec((union record **) 0); /* Unsave it */
}
/*
* After a file/link/symlink/dir creation has failed, see if
* it's because some required directory was not present, and if
* so, create all required dirs.
*/
int
make_dirs(pathname)
char *pathname;
{
char *p; /* Points into path */
int madeone = 0; /* Did we do anything yet? */
int save_errno = errno; /* Remember caller's errno */
int check;
if (errno != ENOENT)
return 0; /* Not our problem */
for (p = index(pathname, '/'); p != NULL; p = index(p+1, '/')) {
/* Avoid mkdir of empty string, if leading or double '/' */
if (p == pathname || p[-1] == '/')
continue;
/* Avoid mkdir where last part of path is '.' */
if (p[-1] == '.' && (p == pathname+1 || p[-2] == '/'))
continue;
*p = 0; /* Truncate the path there */
check = mkdir (pathname, 0777); /* Try to create it as a dir */
*p = '/';
if (check == 0) {
/* FIXME chown, chgrp it same as file being created */
/* FIXME, show mode as modified by current umask */
pr_mkdir(pathname, p-pathname, 0777);
madeone++; /* Remember if we made one */
continue;
}
if (errno == EEXIST) /* Directory already exists */
continue;
/*
* Some other error in the mkdir. We return to the caller.
*/
break;
}
errno = save_errno; /* Restore caller's errno */
return madeone; /* Tell them to retry if we made one */
}
SHAR_EOF
cat << \SHAR_EOF > getoldopt.c
/*
* Plug-compatible replacement for getopt() for parsing tar-like
* arguments. If the first argument begins with "-", it uses getopt;
* otherwise, it uses the old rules used by tar, dump, and ps.
*
* Written 25 August 1985 by John Gilmore (ihnp4!hoptoad!gnu) and placed
* in the Pubic Domain for your edification and enjoyment.
*
* @(#)getoldopt.c 1.4 2/4/86 Public Domain - gnu
*/
#include <stdio.h>
int
getoldopt(argc, argv, optstring)
int argc;
char **argv;
char *optstring;
{
extern char *optarg; /* Points to next arg */
extern int optind; /* Global argv index */
static char *key; /* Points to next keyletter */
static char use_getopt; /* !=0 if argv[1][0] was '-' */
extern char *index();
char c;
char *place;
optarg = NULL;
if (key == NULL) { /* First time */
if (argc < 2) return EOF;
key = argv[1];
if (*key == '-')
use_getopt++;
else
optind = 2;
}
if (use_getopt)
return getopt(argc, argv, optstring);
c = *key++;
if (c == '\0') {
key--;
return EOF;
}
place = index(optstring, c);
if (place == NULL || c == ':') {
fprintf(stderr, "%s: unknown option %c\n", argv[0], c);
return('?');
}
place++;
if (*place == ':') {
if (optind < argc) {
optarg = argv[optind];
optind++;
} else {
fprintf(stderr, "%s: %c argument missing\n",
argv[0], c);
return('?');
}
}
return(c);
}
SHAR_EOF
cat << \SHAR_EOF > list.c
/*
* List a tar archive.
*
* Also includes support routines for reading a tar archive.
*
* Pubic Domain version written 26 Aug 1985 by John Gilmore (ihnp4!hoptoad!gnu).
*
* @(#)list.c 1.18 9/23/86 Public Domain - gnu
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
char *ctime(); /* From libc.a */
#define isodigit(c) ( ((c) >= '0') && ((c) <= '7') )
#include "tar.h"
long from_oct(); /* Decode octal number */
void demode(); /* Print file mode */
union record *head; /* Points to current archive header */
struct stat hstat[1]; /* Stat struct corresponding */
void print_header();
void skip_file();
/*
* Main loop for reading an archive.
*/
void
read_and(do_something)
void (*do_something)();
{
int status = 1;
int prev_status;
name_gather(); /* Gather all the names */
open_archive(1); /* Open for reading */
for(;;) {
prev_status = status;
status = read_header();
switch (status) {
case 1: /* Valid header */
/* We should decode next field (mode) first... */
/* Ensure incoming names are null terminated. */
head->header.name[NAMSIZ-1] = '\0';
if (!name_match(head->header.name)) {
/* Skip past it in the archive */
userec(head);
/* Skip to the next header on the archive */
skip_file((long)hstat->st_size);
continue;
}
(*do_something)();
continue;
/*
* If the previous header was good, tell them
* that we are skipping bad ones.
*/
case 0: /* Invalid header */
case0:
userec(head);
if (prev_status == 1) {
annorec(stderr, tar);
fprintf(stderr,
"Skipping to next file header...\n");
}
continue;
case 2: /* Block of zeroes */
if (f_ignorez)
goto case0; /* Just skip if asked */
/* FALL THRU */
case EOF: /* End of archive */
break;
}
break;
};
close_archive();
names_notfound(); /* Print names not found */
}
/*
* Print a header record, based on tar options.
*/
void
list_archive()
{
/* Save the record */
saverec(&head);
/* Print the header record */
print_header();
/* Skip past it in the archive */
saverec((union record **) 0); /* Unsave it */
userec(head);
/* Skip to the next header on the archive */
skip_file((long)hstat->st_size);
}
/*
* Read a record that's supposed to be a header record.
* Return its address in "head", and if it is good, the file's
* size in hstat->st_size.
*
* Return 1 for success, 0 if the checksum is bad, EOF on eof,
* 2 for a block full of zeros (EOF marker).
*
* You must always userec(head) to skip past the header which this
* routine reads.
*/
int
read_header()
{
register int i;
register long sum, recsum;
register char *p;
register union record *header;
header = findrec();
head = header; /* This is our current header */
if (NULL == header) return EOF;
recsum = from_oct(8, header->header.chksum);
sum = 0;
p = header->charptr;
for (i = sizeof(*header); --i >= 0;) {
/*
* We can't use unsigned char here because of old compilers,
* e.g. V7.
*/
sum += 0xFF & *p++;
}
/* Adjust checksum to count the "chksum" field as blanks. */
for (i = sizeof(header->header.chksum); --i >= 0;)
sum -= 0xFF & header->header.chksum[i];
sum += ' '* sizeof header->header.chksum;
if (sum == recsum) {
/*
* Good record. Decode file size and return.
*/
if (header->header.linkflag == LF_LINK)
hstat->st_size = 0; /* Links 0 size on tape */
else
hstat->st_size = from_oct(1+12, header->header.size);
return 1;
}
if (sum == 8*' ') {
/*
* This is a zeroed block...whole block is 0's except
* for the 8 blanks we faked for the checksum field.
*/
return 2;
}
return 0;
}
/*
* Decode things from a file header record into a "struct stat".
* Also set "*stdp" to !=0 or ==0 depending whether header record is "Unix
* Standard" tar format or regular old tar format.
*
* read_header() has already decoded the checksum and length, so we don't.
*
* If wantug != 0, we want the uid/group info decoded from Unix Standard
* tapes (for extraction). If == 0, we are just printing anyway, so save time.
*/
decode_header(header, st, stdp, wantug)
register union record *header;
register struct stat *st;
int *stdp;
int wantug;
{
st->st_mode = from_oct(8, header->header.mode);
st->st_mtime = from_oct(1+12, header->header.mtime);
if (0==strcmp(header->header.magic, TMAGIC)) {
/* Unix Standard tar archive */
*stdp = 1;
if (wantug) {
st->st_uid = finduid(header->header.uname);
st->st_gid = findgid(header->header.gname);
}
switch (header->header.linkflag)
case LF_BLK: case LF_CHR:
st->st_dev = makedev(from_oct(8, header->header.devmajor),
from_oct(8, header->header.devminor));
} else {
/* Old fashioned tar archive */
*stdp = 0;
st->st_uid = from_oct(8, header->header.uid);
st->st_gid = from_oct(8, header->header.gid);
st->st_dev = 0;
}
}
/*
* Quick and dirty octal conversion.
*
* Result is -1 if the field is invalid (all blank, or nonoctal).
*/
long
from_oct(digs, where)
register int digs;
register char *where;
{
register long value;
while (isspace(*where)) { /* Skip spaces */
where++;
if (--digs <= 0)
return -1; /* All blank field */
}
value = 0;
while (digs > 0 && isodigit(*where)) { /* Scan til nonoctal */
value = (value << 3) | (*where++ - '0');
--digs;
}
if (digs > 0 && *where && !isspace(*where))
return -1; /* Ended on non-space/nul */
return value;
}
/*
* Actually print it.
*/
#define UGSWIDTH 11 /* min width of User, group, size */
#define DATEWIDTH 19 /* Last mod date */
static int ugswidth = UGSWIDTH; /* Max width encountered so far */
void
print_header()
{
char modes[11];
char *timestamp;
char uform[11], gform[11]; /* These hold formatted ints */
char *user, *group;
char size[24]; /* Holds a formatted long or maj, min */
long longie; /* To make ctime() call portable */
int pad;
int header_std; /* Is header standard or not? */
annofile(stdout, (char *)NULL);
if (f_verbose) {
decode_header(head, hstat, &header_std, 0);
/* File type and modes */
modes[0] = '?';
switch (head->header.linkflag) {
case LF_NORMAL:
case LF_OLDNORMAL:
case LF_LINK:
modes[0] = '-';
if ('/' == head->header.name[strlen(head->header.name)-1])
modes[0] = 'd';
break;
case LF_DIR: modes[0] = 'd'; break;
case LF_SYMLINK:modes[0] = 'l'; break;
case LF_BLK: modes[0] = 'b'; break;
case LF_CHR: modes[0] = 'c'; break;
case LF_FIFO: modes[0] = 'f'; break;
case LF_CONTIG: modes[0] = '='; break;
}
demode((unsigned)hstat->st_mode, modes+1);
/* Timestamp */
longie = hstat->st_mtime;
timestamp = ctime(&longie);
timestamp[16] = '\0';
timestamp[24] = '\0';
/* User and group names */
if (*head->header.uname && header_std) {
user = head->header.uname;
} else {
user = uform;
(void)sprintf(uform, "%d", (int)hstat->st_uid);
}
if (*head->header.gname && header_std) {
group = head->header.gname;
} else {
group = gform;
(void)sprintf(gform, "%d", (int)hstat->st_gid);
}
/* Format the file size or major/minor device numbers */
switch (head->header.linkflag) {
case LF_CHR:
case LF_BLK:
(void)sprintf(size, "%d, %d",
major(hstat->st_dev),
minor(hstat->st_dev));
break;
default:
(void)sprintf(size, "%ld", (long)hstat->st_size);
}
/* Figure out padding and print the whole line. */
pad = strlen(user) + strlen(group) + strlen(size) + 1;
if (pad > ugswidth) ugswidth = pad;
printf("%s %s/%s %*s%s %s %s %.*s",
modes,
user,
group,
ugswidth - pad,
"",
size,
timestamp+4, timestamp+20,
sizeof(head->header.name),
head->header.name);
} else {
printf("%s", head->header.name);
}
if (f_verbose) switch (head->header.linkflag) {
case LF_SYMLINK:
printf(" -> %s\n", head->header.linkname);
break;
case LF_LINK:
printf(" link to %s\n", head->header.linkname);
break;
default:
printf(" unknown file type '%c'\n", head->header.linkflag);
break;
case LF_OLDNORMAL:
case LF_NORMAL:
case LF_CHR:
case LF_BLK:
case LF_DIR:
case LF_FIFO:
case LF_CONTIG:
putc('\n', stdout);
break;
} else {
putc('\n', stdout);
}
/* FIXME: we don't print major/minor device numbers */
}
/*
* Print a similar line when we make a directory automatically.
*/
void
pr_mkdir(pathname, length, mode)
char *pathname;
int length;
int mode;
{
char modes[11];
if (f_verbose) {
/* File type and modes */
modes[0] = 'd';
demode((unsigned)mode, modes+1);
annofile(stdout, (char *)NULL);
printf("%s %*s %.*s\n",
modes,
ugswidth+DATEWIDTH,
"Creating directory:",
length,
pathname);
}
}
/*
* Skip over <size> bytes of data in records in the archive.
*/
void
skip_file(size)
register long size;
{
union record *x;
while (size > 0) {
x = findrec();
if (x == NULL) { /* Check it... */
annorec(stderr, tar);
fprintf(stderr, "Unexpected EOF on archive file\n");
exit(EX_BADARCH);
}
userec(x);
size -= RECORDSIZE;
}
}
/*
* Decode the mode string from a stat entry into a 9-char string and a null.
*/
void
demode(mode, string)
register unsigned mode;
register char *string;
{
register unsigned mask;
register char *rwx = "rwxrwxrwx";
for (mask = 0400; mask != 0; mask >>= 1) {
if (mode & mask)
*string++ = *rwx++;
else {
*string++ = '-';
rwx++;
}
}
if (mode & S_ISUID)
if (string[-7] == 'x')
string[-7] = 's';
else
string[-7] = 'S';
if (mode & S_ISGID)
if (string[-4] == 'x')
string[-4] = 's';
else
string[-4] = 'S';
if (mode & S_ISVTX)
if (string[-1] == 'x')
string[-1] = 't';
else
string[-1] = 'T';
*string = '\0';
}
SHAR_EOF
cat << \SHAR_EOF > names.c
/*
* Look up user and/or group names.
*
* This file should be modified for non-unix systems to do something
* reasonable.
*
* @(#)names.c 1.1 9/9/86 Public Domain - gnu
*/
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include "tar.h"
static int saveuid = -993;
static char saveuname[TUNMLEN];
static int my_uid = -993;
static int savegid = -993;
static char savegname[TGNMLEN];
static int my_gid = -993;
#define myuid ( my_uid < 0? my_uid = getuid(): my_uid )
#define mygid ( my_gid < 0? my_gid = getgid(): my_gid )
#ifndef NONAMES
/*
* Look up a user or group name from a uid/gid, maintaining a cache.
* FIXME, for now it's a one-entry cache.
* FIXME2, the "-993" is to reduce the chance of a hit on the first lookup.
*
* This is ifdef'd because on Suns, it drags in about 38K of "yellow
* pages" code, roughly doubling the program size. Thanks guys.
*/
void
finduname(uname, uid)
char uname[TUNMLEN];
int uid;
{
struct passwd *pw;
extern struct passwd *getpwuid ();
if (uid != saveuid) {
saveuid = uid;
saveuname[0] = '\0';
pw = getpwuid(uid);
if (pw)
strncpy(saveuname, pw->pw_name, TUNMLEN);
}
strncpy(uname, saveuname, TUNMLEN);
}
int
finduid(uname)
char uname[TUNMLEN];
{
struct passwd *pw;
extern struct passwd *getpwnam();
if (uname[0] != saveuname[0] /* Quick test w/o proc call */
|| 0!=strncmp(uname, saveuname, TUNMLEN)) {
strncpy(saveuname, uname, TUNMLEN);
pw = getpwnam(uname);
if (pw) {
saveuid = pw->pw_uid;
} else {
saveuid = myuid;
}
}
return saveuid;
}
void
findgname(gname, gid)
char gname[TGNMLEN];
int gid;
{
struct group *gr;
extern struct group *getgrgid ();
if (gid != savegid) {
savegid = gid;
savegname[0] = '\0';
(void)setgrent();
gr = getgrgid(gid);
if (gr)
strncpy(savegname, gr->gr_name, TGNMLEN);
}
(void) strncpy(gname, savegname, TGNMLEN);
}
int
findgid(gname)
char gname[TUNMLEN];
{
struct group *gr;
extern struct group *getgrnam();
if (gname[0] != savegname[0] /* Quick test w/o proc call */
|| 0!=strncmp(gname, savegname, TUNMLEN)) {
strncpy(savegname, gname, TUNMLEN);
gr = getgrnam(gname);
if (gr) {
savegid = gr->gr_gid;
} else {
savegid = mygid;
}
}
return savegid;
}
#endif
SHAR_EOF
cat << \SHAR_EOF > port.c
/*
* @(#)port.c 1.6 86/08/11 Public Domain, by John Gilmore, 1986
*
* These are routines not available in all environments.
*
* I know this introduces an extra level of subroutine calls and is
* slightly slower. Frankly, my dear, I don't give a damn. Let the
* Missed-Em Vee losers suffer a little. This software is proud to
* have been written on a BSD system.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#include "port.h"
#ifndef BSD42
/*
* lstat() is a stat() which does not follow symbolic links.
* If there are no symbolic links, just use stat().
*/
int
lstat (path, buf)
char *path;
struct stat *buf;
{
extern int stat ();
return (stat (path, buf));
}
/*
* valloc() does a malloc() on a page boundary. On some systems,
* this can make large block I/O more efficient.
*/
char *
valloc (size)
unsigned size;
{
extern char *malloc ();
return (malloc (size));
}
/*
** NMKDIR.C
**
** Written by Robert Rother, Mariah Corporation, August 1985.
**
** I wrote this out of shear disgust with myself because I couldn't
** figure out how to do this in /bin/sh.
**
** If you want it, it's yours. All I ask in return is that if you
** figure out how to do this in a Bourne Shell script you send me
** a copy.
** sdcsvax!rmr or rmr@uscd
*
* Severely hacked over by John Gilmore to make a 4.2BSD compatible
* subroutine. 11Mar86; hoptoad!gnu
*/
/*
* Make a directory. Compatible with the mkdir() system call on 4.2BSD.
*/
int
mkdir(dpath, dmode)
char *dpath;
int dmode;
{
int cpid, status;
extern int errno;
switch (cpid = fork()) {
case -1: /* Error in fork() */
return(-1); /* Errno is set already */
case 0: /* Child process */
/*
* Cheap hack to set mode of new directory. Since this
* child process is going away anyway, we zap its umask.
* FIXME, this won't suffice to set SUID, SGID, etc. on this
* directory. Does anybody care?
*/
status = umask(0); /* Get current umask */
status = umask(status | (0777 & ~dmode)); /* Set for mkdir */
execl("/bin/mkdir", "mkdir", dpath, (char *)0);
_exit(-1); /* Can't exec /bin/mkdir */
default: /* Parent process */
while (cpid != wait(&status)) ; /* Wait for kid to finish */
}
if (TERM_SIGNAL(status) != 0 || TERM_VALUE(status) != 0) {
errno = EIO; /* We don't know why, but */
return -1; /* /bin/mkdir failed */
}
return 0;
}
#endif
#ifdef USG
/*
* Translate V7 style into Sys V style.
*/
#include <string.h>
#include <memory.h>
char *
index (s, c)
char *s;
int c;
{
return (strchr (s, c));
}
char *
bcopy (s1, s2, n)
char *s1, *s2;
int n;
{
(void) memcpy (s2, s1, n);
return (s1);
}
void
bzero (s1, n)
char *s1;
int n;
{
(void) memset(s1, 0, n);
}
#endif
SHAR_EOF
cat << \SHAR_EOF > port.h
/*
* Portability declarations for public domain tar.
*
* @(#)port.h 1.1 86/03/11 Public Domain by John Gilmore, 1986
*/
/*
* Everybody does wait() differently. There seem to be no definitions
* for this in V7 (e.g. you are supposed to shift and mask things out
* using constant shifts and masks.) So fuck 'em all -- my own non
* standard but portable macros. Don't change to a "union wait"
* based approach -- the ordering of the elements of the struct
* depends on the byte-sex of the machine. Foo!
*/
#define TERM_SIGNAL(status) ((status) & 0x7F)
#define TERM_COREDUMP(status) (((status) & 0x80) != 0)
#define TERM_VALUE(status) ((status) >> 8)
SHAR_EOF
cat << \SHAR_EOF > tar.c
/*
* A public domain tar(1) program.
*
* Written by John Gilmore, ihnp4!hoptoad!gnu, starting 25 Aug 85.
*
* @(#)tar.c 1.21 10/29/86 Public Domain - gnu
*/
#include <stdio.h>
#include <sys/types.h> /* Needed for typedefs in tar.h */
extern char *malloc();
extern char *strncpy();
extern char *optarg; /* Pointer to argument */
extern int optind; /* Global argv index from getopt */
/*
* The following causes "tar.h" to produce definitions of all the
* global variables, rather than just "extern" declarations of them.
*/
#define TAR_EXTERN /**/
#include "tar.h"
/*
* We should use a conversion routine that does reasonable error
* checking -- atoi doesn't. For now, punt. FIXME.
*/
#define intconv atoi
extern int getoldopt();
extern void read_and();
extern void list_archive();
extern void extract_archive();
extern void create_archive();
static FILE *namef; /* File to read names from */
static char **n_argv; /* Argv used by name routines */
static int n_argc; /* Argc used by name routines */
/* They also use "optind" from getopt(). */
void describe();
/*
* Main routine for tar.
*/
main(argc, argv)
int argc;
char **argv;
{
/* Uncomment this message in particularly buggy versions...
fprintf(stderr,
"tar: You are running an experimental PD tar, maybe use /bin/tar.\n");
*/
tar = "tar"; /* Set program name */
options(argc, argv);
name_init(argc, argv);
if (f_create) {
if (f_extract || f_list) goto dupflags;
create_archive();
} else if (f_extract) {
if (f_list) goto dupflags;
read_and(extract_archive);
} else if (f_list) {
read_and(list_archive);
} else {
dupflags:
fprintf (stderr,
"tar: you must specify exactly one of the c, t, or x options\n");
describe();
exit(EX_ARGSBAD);
}
exit(0);
}
/*
* Parse the options for tar.
*/
int
options(argc, argv)
int argc;
char **argv;
{
register int c; /* Option letter */
/* Set default option values */
blocking = DEFBLOCKING; /* From Makefile */
ar_file = DEF_AR_FILE; /* From Makefile */
/* Parse options */
while ((c = getoldopt(argc, argv, "b:BcdDf:hikmopstT:vxzZ")
) != EOF) {
switch (c) {
case 'b':
blocking = intconv(optarg);
break;
case 'B':
f_reblock++; /* For reading 4.2BSD pipes */
break;
case 'c':
f_create++;
break;
case 'd':
f_debug++; /* Debugging code */
break; /* Yes, even with dbx */
case 'D':
f_sayblock++; /* Print block #s for debug */
break; /* of bad tar archives */
case 'f':
ar_file = optarg;
break;
case 'h':
f_follow_links++; /* follow symbolic links */
break;
case 'i':
f_ignorez++; /* Ignore zero records (eofs) */
/*
* This can't be the default, because Unix tar
* writes two records of zeros, then pads out the
* block with garbage.
*/
break;
case 'k': /* Don't overwrite files */
f_keep++;
break;
case 'm':
f_modified++;
break;
case 'o': /* Generate old archive */
f_oldarch++;
break;
case 'p':
f_use_protection++;
(void)umask(0); /* Turn off kernel "help" */
break;
case 's':
f_sorted_names++; /* Names to extr are sorted */
break;
case 't':
f_list++;
break;
case 'T':
name_file = optarg;
f_namefile++;
break;
case 'v':
f_verbose++;
break;
case 'x':
f_extract++;
break;
case 'z': /* Easy to type */
case 'Z': /* Like the filename extension .Z */
f_compress++;
break;
case '?':
describe();
exit(EX_ARGSBAD);
}
}
blocksize = blocking * RECORDSIZE;
}
/* FIXME, describe tar options here */
void
describe()
{
fputs("tar: valid options:\n\
-b N blocking factor N (block size = Nx512 bytes)\n\
-B reblock as we read (for reading 4.2BSD pipes)\n\
-c create an archive\n\
-D dump record number within archive with each message\n\
-f F read/write archive from file or device F\n\
-h don't dump symbolic links; dump the files they point to\n\
-i ignore blocks of zeros in the archive, which normally mean EOF\n\
-k keep existing files, don't overwrite them from the archive\n\
-m don't extract file modified time\n\
-o write an old V7 format archive, rather than ANSI [draft 6] format\n\
-p do extract all protection information\n\
-s list of names to extract is sorted to match the archive\n\
-t list a table of contents of an archive\n\
-T F get names to extract or create from file F\n\
-v verbosely list what files we process\n\
-x extract files from an archive\n\
-z or Z run the archive through compress(1)\n\
", stderr);
}
/*
* Set up to gather file names for tar.
*
* They can either come from stdin or from argv.
*/
name_init(argc, argv)
int argc;
char **argv;
{
if (f_namefile) {
if (optind < argc) {
fprintf(stderr, "tar: too many args with -T option\n");
exit(EX_ARGSBAD);
}
if (!strcmp(name_file, "-")) {
namef = stdin;
} else {
namef = fopen(name_file, "r");
if (namef == NULL) {
fprintf(stderr, "tar: ");
perror(name_file);
exit(EX_BADFILE);
}
}
} else {
/* Get file names from argv, after options. */
n_argc = argc;
n_argv = argv;
}
}
/*
* Get the next name from argv or the name file.
*
* Result is in static storage and can't be relied upon across two calls.
*/
char *
name_next()
{
static char buffer[NAMSIZ+2]; /* Holding pattern */
register char *p;
register char *q;
if (namef == NULL) {
/* Names come from argv, after options */
if (optind < n_argc)
return n_argv[optind++];
return (char *)NULL;
}
p = fgets(buffer, NAMSIZ+1 /*nl*/, namef);
if (p == NULL) return p; /* End of file */
q = p+strlen(p)-1; /* Find the newline */
*q-- = '\0'; /* Zap the newline */
while (*q == '/') *q-- = '\0'; /* Zap trailing slashes too */
return p;
}
/*
* Close the name file, if any.
*/
name_close()
{
if (namef != NULL && namef != stdin) fclose(namef);
}
/*
* Gather names in a list for scanning.
* Could hash them later if we really care.
*
* If the names are already sorted to match the archive, we just
* read them one by one. name_gather reads the first one, and it
* is called by name_match as appropriate to read the next ones.
* At EOF, the last name read is just left in the buffer.
* This option lets users of small machines extract an arbitrary
* number of files by doing "tar t" and editing down the list of files.
*/
name_gather()
{
register char *p;
static struct name namebuf[1]; /* One-name buffer */
if (f_sorted_names) {
p = name_next();
if (p) {
namebuf->length = strlen(p);
if (namebuf->length >= sizeof namebuf->name) {
fprintf(stderr, "Argument name too long: %s\n",
p);
namebuf->length = (sizeof namebuf->name) - 1;
}
strncpy(namebuf->name, p, namebuf->length);
namebuf->next = (struct name *)NULL;
namebuf->found = 0;
namelist = namebuf;
namelast = namelist;
}
return;
}
/* Non sorted names -- read them all in */
while (NULL != (p = name_next())) {
addname(p);
}
}
/*
* A name from the namelist has been found.
* If it's just a list,
/*
* Add a name to the namelist.
*/
addname(name)
char *name; /* pointer to name */
{
register int i; /* Length of string */
register struct name *p; /* Current struct pointer */
i = strlen(name);
/*NOSTRICT*/
p = (struct name *)
malloc((unsigned)(i + sizeof(struct name) - NAMSIZ));
p->next = (struct name *)NULL;
p->length = i;
p->found = 0;
strncpy(p->name, name, i);
p->name[i] = '\0'; /* Null term */
if (namelast) namelast->next = p;
namelast = p;
if (!namelist) namelist = p;
}
/*
* Match a name from an archive, p, with a name from the namelist.
*
* FIXME: Allow regular expressions in the name list.
*/
name_match(p)
register char *p;
{
register struct name *nlp;
register int len;
again:
if (0 == (nlp = namelist)) /* Empty namelist is easy */
return 1;
len = strlen(p);
for (; nlp != 0; nlp = nlp->next) {
if ( nlp->name[0] == p[0] /* First chars match */
&& nlp->length <= len /* Archive len >= specified */
&& (p[nlp->length] == '\0' || p[nlp->length] == '/')
/* Full match on file/dirname */
&& strncmp(p, nlp->name, nlp->length) == 0) /* Name compare */
{
nlp->found = 1; /* Remember it matched */
return 1; /* We got a match */
}
}
/*
* Filename from archive not found in namelist.
* If we have the whole namelist here, just return 0.
* Otherwise, read the next name in and compare it.
* If this was the last name, namelist->found will remain on.
* If not, we loop to compare the newly read name.
*/
if (f_sorted_names && namelist->found) {
name_gather(); /* Read one more */
if (!namelist->found) goto again;
}
return 0;
}
/*
* Print the names of things in the namelist that were not matched.
*/
names_notfound()
{
register struct name *nlp;
register char *p;
for (nlp = namelist; nlp != 0; nlp = nlp->next) {
if (!nlp->found) {
fprintf(stderr, "tar: %s not found in archive\n",
nlp->name);
}
/*
* We could free() the list, but the process is about
* to die anyway, so save some CPU time. Amigas and
* other similarly broken software will need to waste
* the time, though.
*/
#ifndef unix
if (!f_sorted_names)
free(nlp);
#endif unix
}
namelist = (struct name *)NULL;
namelast = (struct name *)NULL;
if (f_sorted_names) {
while (0 != (p = name_next()))
fprintf(stderr, "tar: %s not found in archive\n", p);
}
}
SHAR_EOF
cat << \SHAR_EOF > tar.h
/*
* Header file for public domain tar (tape archive) program.
*
* @(#)tar.h 1.20 86/10/29 Public Domain.
*
* Created 25 August 1985 by John Gilmore, ihnp4!hoptoad!gnu.
*/
/*
* Kludge for handling systems that can't cope with multiple
* external definitions of a variable. In ONE routine (tar.c),
* we #define TAR_EXTERN to null; here, we set it to "extern" if
* it is not already set.
*/
#ifndef TAR_EXTERN
#define TAR_EXTERN extern
#endif
/*
* Header block on tape.
*
* I'm going to use traditional DP naming conventions here.
* A "block" is a big chunk of stuff that we do I/O on.
* A "record" is a piece of info that we care about.
* Typically many "record"s fit into a "block".
*/
#define RECORDSIZE 512
#define NAMSIZ 100
#define TUNMLEN 32
#define TGNMLEN 32
union record {
char charptr[RECORDSIZE];
struct header {
char name[NAMSIZ];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char linkflag;
char linkname[NAMSIZ];
char magic[8];
char uname[TUNMLEN];
char gname[TGNMLEN];
char devmajor[8];
char devminor[8];
} header;
};
/* The checksum field is filled with this while the checksum is computed. */
#define CHKBLANKS " " /* 8 blanks, no null */
/* The magic field is filled with this if uname and gname are valid. */
#define TMAGIC "ustar " /* 7 chars and a null */
/* The linkflag defines the type of file */
#define LF_OLDNORMAL '\0' /* Normal disk file, Unix compat */
#define LF_NORMAL '0' /* Normal disk file */
#define LF_LINK '1' /* Link to previously dumped file */
#define LF_SYMLINK '2' /* Symbolic link */
#define LF_CHR '3' /* Character special file */
#define LF_BLK '4' /* Block special file */
#define LF_DIR '5' /* Directory */
#define LF_FIFO '6' /* FIFO special file */
#define LF_CONTIG '7' /* Contiguous file */
/* Further link types may be defined later. */
/*
* Exit codes from the "tar" program
*/
#define EX_SUCCESS 0 /* success! */
#define EX_ARGSBAD 1 /* invalid args */
#define EX_BADFILE 2 /* invalid filename */
#define EX_BADARCH 3 /* bad archive */
#define EX_SYSTEM 4 /* system gave unexpected error */
/*
* Global variables
*/
TAR_EXTERN union record *ar_block; /* Start of block of archive */
TAR_EXTERN union record *ar_record; /* Current record of archive */
TAR_EXTERN union record *ar_last; /* Last+1 record of archive block */
TAR_EXTERN char ar_reading; /* 0 writing, !0 reading archive */
TAR_EXTERN int blocking; /* Size of each block, in records */
TAR_EXTERN int blocksize; /* Size of each block, in bytes */
TAR_EXTERN char *ar_file; /* File containing archive */
TAR_EXTERN char *name_file; /* File containing names to work on */
TAR_EXTERN char *tar; /* Name of this program */
/*
* Flags from the command line
*/
TAR_EXTERN char f_reblock; /* -B */
TAR_EXTERN char f_create; /* -c */
TAR_EXTERN char f_debug; /* -d */
TAR_EXTERN char f_sayblock; /* -D */
TAR_EXTERN char f_follow_links; /* -h */
TAR_EXTERN char f_ignorez; /* -i */
TAR_EXTERN char f_keep; /* -k */
TAR_EXTERN char f_modified; /* -m */
TAR_EXTERN char f_oldarch; /* -o */
TAR_EXTERN char f_use_protection; /* -p */
TAR_EXTERN char f_sorted_names; /* -s */
TAR_EXTERN char f_list; /* -t */
TAR_EXTERN char f_namefile; /* -T */
TAR_EXTERN char f_verbose; /* -v */
TAR_EXTERN char f_extract; /* -x */
TAR_EXTERN char f_compress; /* -z */
/*
* We now default to Unix Standard format rather than 4.2BSD tar format.
* The code can actually produce all three:
* f_standard ANSI standard
* f_oldarch V7
* neither 4.2BSD
* but we don't bother, since 4.2BSD can read ANSI standard format anyway.
* The only advantage to the "neither" option is that we can cmp(1) our
* output to the output of 4.2BSD tar, for debugging.
*/
#define f_standard (!f_oldarch)
/*
* Structure for keeping track of filenames and lists thereof.
*/
struct name {
struct name *next;
short length;
char found;
char name[NAMSIZ+1];
};
TAR_EXTERN struct name *namelist; /* Points to first name in list */
TAR_EXTERN struct name *namelast; /* Points to last name in list */
TAR_EXTERN int archive; /* File descriptor for archive file */
TAR_EXTERN int errors; /* # of files in error */
/*
*
* Due to the next struct declaration, each routine that includes
* "tar.h" must also include <sys/types.h>. I tried to make it automatic,
* but System V has no defines in <sys/types.h>, so there is no way of
* knowing when it has been included. In addition, it cannot be included
* twice, but must be included exactly once. Argghh!
*
* Thanks, typedef. Thanks, USG.
*/
struct link {
struct link *next;
dev_t dev;
ino_t ino;
short linkcount;
char name[NAMSIZ+1];
};
TAR_EXTERN struct link *linklist; /* Points to first link in list */
/*
* Error recovery stuff
*/
TAR_EXTERN char read_error_flag;
/*
* Declarations of functions available to the world.
*/
union record *findrec();
void userec();
union record *endofrecs();
void anno();
#define annorec(stream, msg) anno(stream, msg, 0) /* Cur rec */
#define annofile(stream, msg) anno(stream, msg, 1) /* Saved rec */
SHAR_EOF
# End of shell archive
exit 0
--
David P. Zimmerman "When I'm having fun, the world doesn't exist."
Arpa: dpz@rutgers.rutgers.edu
Uucp: ...{harvard | seismo | pyramid}!rutgers!dpz