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