#! /bin/sh echo 'README': sed 's/^X//' >'README' <<'!' XTarx is a newer, cleaner, more versatile replacement for my old targ/tarl Xprograms for salvaging information from damaged tar tapes. It does a Xbetter job on various details and has been extensively tidied up. X X Henry Spencer at U of Toronto Zoology X henry@zoo.toronto.edu utzoo!henry ! echo 'Makefile': sed 's/^X//' >'Makefile' <<'!' XCFLAGS = -O XLDFLAGS = X Xtarx: tarx.o X $(CC) $(LDFLAGS) tarx.o -o $@ ! echo 'tarx.1': sed 's/^X//' >'tarx.1' <<'!' X.TH TARX 1 "26 July 1990" X.BY Zoology X.SH NAME Xtarx \- recover files from damaged tar-format archives X.SH SYNOPSIS X\fBtarx\fR X[ X.B \-t X] X[ X.B \-b Xblockfactor X] X[ X.B \-e Xerrlimit X] [ X.B \-E Xeoflimit X] Xname ... X.SH DESCRIPTION X\fITarx\fR is used to list and recover files from a Xdamaged \fItar\fR(1) archive. XIt uses a simplistic pattern-matching Xapproach to identify \fItar\fR header blocks. XIt will cheerfully persist despite all sorts of bad things about the archive X(such as wrong checksums, read errors, and scraped-off magnetic surface...), Xup to a maximum of \fIerrlimit\fR (default 3) hard errors in a row Xor \fIeoflimit\fR (default 2) EOFs in a row. XSuch events are reported but don't terminate operations. XThe \fItar\fR archive is read from standard input. X.PP XWith the X.B \-t Xoption, X\fItarx\fR lists the file names it sees in the archive. X.PP XWithout the X.B \-t Xoption, X\fItarx\fR takes file or directory \fIname\fRs as arguments Xand attempts to extract them from the archive. X(If no \fIname\fRs are given, \fItarx\fR extracts everything it can find.) X\fITarx\fR is not willing to create directories, however, Xso these must be made manually beforehand if they do not already exist. XFiles are owned by the user, and have his default permissions. X.PP X.I Tarx Xallocates a read buffer of \fIblockfactor\fR*512 bytes, Xwhere the default \fIblockfactor\fR is 400 (to allow for the very large Xblocks sometimes found on cartridge tapes), Xand attempts a full-sized read each time. XIt copes properly with getting less than it expected, although it does Xinsist that the result be a multiple of 512 bytes. XWhen dealing with cranky tape drives or other special situations, Xor severely short of memory, or working on a 16-bit machine, Xit may be useful to specify a smaller \fIblockfactor\fR. X(Notably, on 16-bit machines 126 is often the maximum that will work.) XSpecifying a larger \fIblockfactor\fR may be necessary if tape blocks Xare truly enormous. X.SH EXAMPLE X``tarx \-t filelist'' lists all files on the tape Xmounted on /dev/rmt0 and places the results in ``filelist''. X.PP X``tarx joe/precious 'tarx.c' <<'!' X/* X * tarx - manipulate damaged tar tapes heuristically X */ X X#include X#include X X#define NAMSIZ 100 /* why isn't there a tar.h??? */ X#define FLAG (NAMSIZ+8+8+8+12+12+8) /* offset of is-a-link flag */ Xstruct matches { X int offset; X char value; X} matches[] = { /* pattern-match table for header blocks */ X NAMSIZ+6, ' ', X NAMSIZ+7, '\0', X NAMSIZ+8+6, ' ', X NAMSIZ+8+7, '\0', X NAMSIZ+16+6, ' ', X NAMSIZ+16+7, '\0', X NAMSIZ+24+11, ' ', X NAMSIZ+36+11, ' ', X NAMSIZ+48+6, '\0', X 0, 0, X}; X X#ifndef MAXBLOCK X#define MAXBLOCK 400 /* SGI makes blocky tapes */ X#endif Xint maxblock = MAXBLOCK; X#define BLOCK 512 X Xchar *buf; /* -> malloced buffer */ Xint nleft = 0; /* number of blocks left in buffer */ Xint whichnow; /* index in buffer of current block */ X Xint nbad = 0; /* number of consecutive bad reads */ Xint badlimit = 3; /* limit on consecutive bads */ Xint neof = 0; /* number of consecutive EOF marks */ Xint eoflimit = 2; /* limit on consecutive eofs */ X Xint opened = 0; /* are we writing a file? */ Xint f; /* the file descriptor, if any */ Xlong fsize; /* number of bytes not yet written to file */ X Xchar op = 'x'; /* what operation is being done? */ X X#define STREQ(a, b) (*(a) == *(b) && strcmp((a), (b)) == 0) X X#ifndef lint Xstatic char RCSid[] = "$Header$"; X#endif X Xint debug = 0; Xchar *progname; X Xextern void exit(); X#ifdef UTZOOERR Xextern char *mkprogname(); X#else X#define mkprogname(a) (a) X#endif X X/* X - main - parse arguments and handle options X */ Xmain(argc, argv) Xint argc; Xchar *argv[]; X{ X int c; X int errflg = 0; X register char *block; X extern char *readblock(); X extern char *malloc(); X extern int optind; X extern char *optarg; X X progname = mkprogname(argv[0]); X X while ((c = getopt(argc, argv, "tb:e:E:d")) != EOF) X switch (c) { X case 't': /* just list files */ X op = 't'; X break; X case 'b': /* set blocking factor */ X maxblock = atoi(optarg); X break; X case 'e': /* set error limit */ X badlimit = atoi(optarg); X break; X case 'E': /* set eof limit */ X eoflimit = atoi(optarg); X break; X case 'd': /* Debugging. */ X debug++; X break; X case '?': X default: X errflg++; X break; X } X if (errflg) { X fprintf(stderr, "usage: %s ", progname); X fprintf(stderr, "[-t] [-b blockf] [-e errs] [-E eofs] [name] ...\n"); X exit(2); X } X X buf = malloc(maxblock*BLOCK); X if (buf == NULL) { X fprintf(stderr, "%s: cannot allocate buffer of %d blocks\n", X progname, maxblock); X exit(1); X } X X for(;;) { X block = readblock(0); X X if (block != NULL) X doblock(block, argc - optind, &argv[optind]); X } X /* NOTREACHED */ X} X X/* X - readblock - read in a block and deal with error/eof X */ Xchar * Xreadblock(desc) Xint desc; X{ X register int count; X extern int errno; X extern int sys_nerr; X extern char *sys_errlist[]; X X if (nleft > 0) { X whichnow++; X nleft--; X return(buf+whichnow*BLOCK); X } X X count = read(desc, buf, maxblock*BLOCK); X if (count != 0 && neof > 0) X printf("---! %d EOF(s)\n", neof); /* delayed EOF count */ X if (count <= 0 || count%BLOCK != 0) { X if (count == 0) X neof++; X else if (count > 0) { X printf("---! bad block size (%d) - treated as bad\n", count); X nbad++; X } else { X if (errno >= 0 && errno < sys_nerr) X printf("---! error (%s)\n", sys_errlist[errno]); X else X printf("---! error %d\n", errno); X nbad++; X } X if (nbad >= badlimit) X exit(1); X if (neof >= eoflimit) X exit(0); X return(NULL); X } X X /* successful read */ X nbad = 0; X neof = 0; X whichnow = 0; X nleft = count/BLOCK - 1; /* -1 for one we're about to return */ X return(buf); X} X X/* X - doblock - process a block X */ Xdoblock(block, argc, argv) Xchar *block; Xint argc; Xchar **argv; X{ X register int count; X register int tar = istar(block); X register int ret; X X if (!tar && !opened) X return; X X if (!tar && opened) { X count = (fsize > BLOCK) ? BLOCK : (int)fsize; X ret = write(f, block, count); X if (ret < 0) X printf("---! write error in file!\n"); X fsize -= count; X if (fsize <= 0) { X opened = 0; X close(f); X printf("--- done\n"); X } X return; X } X /* it's a tar header block */ X X if (op == 't') { X printf("%s\n", block); X return; X } X /* op == 'x' */ X X if (opened) { X printf("---! premature end\n"); X close(f); X opened = 0; X } X X if (!match(block, argc, argv)) X return; /* this file is not of interest */ X X switch (block[FLAG]) { X case '0': X case '\0': X f = creat(block, 0666); X if (f < 0) X printf("---! unable to create `%s'\n", block); X else { X opened = 1; X ret = sscanf(block+NAMSIZ+24, "%lo", &fsize); X if (ret != 1) { X printf("---! can't read size of `%s'", block); X close(f); X opened = 0; X } else { X printf("--- reading %s %ld\n", block, fsize); X if (fsize <= 0) { X close(f); X opened = 0; X printf("--- done\n"); X } X } X } X break; X case '1': X f = link(block+FLAG+1, block); X if (f < 0) X printf("---! unable to link %s to %s\n", block+FLAG+1, block); X else X printf("--- link %s to %s\n", block+FLAG+1, block); X break; X case '2': X f = symlink(block+FLAG+1, block); X if (f < 0) X printf("---! unable to symlink %s to %s\n", block+FLAG+1, block); X else X printf("--- symlink %s to %s\n", block+FLAG+1, block); X break; X default: X printf("---! unknown flag value %c\n", block[FLAG]); X break; X } X} X X/* X - match - does this string match one of the arguments? X */ Xint Xmatch(s, argc, argv) Xchar *s; Xint argc; Xchar **argv; X{ X register int i; X register int len; X X if (argc == 0) X return(1); X X for (i = 0; i < argc; i++) { X len = strlen(argv[i]); X if (strncmp(s, argv[i], len) == 0 && X (s[len] == '/' || s[len] == '\0')) X return(1); X } X return(0); X} X X/* X - istar - is this plausibly a tar header block? X */ Xint Xistar(block) Xregister char *block; X{ X register int loop; X X for (loop = 0; matches[loop].offset != 0; loop++) X if (block[matches[loop].offset] != matches[loop].value) X return(0); X return(1); X} ! echo done exit 0 # Just in case...