home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
kermit.columbia.edu
/
kermit.columbia.edu.tar
/
kermit.columbia.edu
/
archives
/
tapeutils.zip
/
tuvrta.c
< prev
next >
Wrap
C/C++ Source or Header
|
1988-08-16
|
20KB
|
775 lines
#include <stdio.h>
#include <ctype.h>
#define NOKNET
#define import_spp
#define import_knames
#include <iraf.h>
/*
* RTAR -- Read a UNIX tar format tape containing files with legal IRAF
* virtual filenames. Map tape filenames to host system filenames using
* IRAF filename mapping if the tape does not contain legal host system
* filenames.
*
* Switches:
* a advance to first file in filelist before doing
* anything. useful for restarting an aborted
* operation. first file is not otherwise used.
* b generate only C style binary byte stream output
* files (default is to write a text file when
* the input stream is text).
* d print debug messages
* e exclude, rather than include, listed files
* f read from named file rather than stdin
* l do not try to resolve links by a file copy
* n do not strip tailing blank lines from text files
* o omit binary files (e.g. when foreign host has
* incompatible binary file format)
* p omit the given pathname prefix when creating files
* r replace existing file at extraction
* t print name of each file matched
* v verbose; print full description of each file
* x extract files (extract everything if no files
* listed or if -e is set)
*
* Switches must be given in a group, in any order, e.g.:
*
* rtar -xetvf tarfile sys/osb sys/os lib/config.h$
*
* would extract all files from tarfile with names not beginning with sys/os
* or sys/osb or with names not equal to lib/config.h, printing a verbose
* description of each file extracted. If an exclude filename does not end
* with a $ all files with the given string as a prefix are excluded.
*/
#define TBLOCK 512
#define NBLOCK 20
#define NAMSIZ 100
#define MAXERR 20
#define MAXTRYS 100
#define SZ_TAPEBUFFER (TBLOCK * NBLOCK)
#define EOS '\0'
#define ERR (-1)
#define OK 0
#define RWXR_XR_X 0755
#define SZ_PADBUF 8196
/* File header structure. One of these precedes each file on the tape.
* Each file occupies an integral number of TBLOCK size logical blocks
* on the tape. The number of logical blocks per physical block is variable,
* with at most NBLOCK logical blocks per physical tape block. Two zero
* blocks mark the end of the tar file.
*/
union hblock {
char dummy[TBLOCK];
struct header {
char name[NAMSIZ]; /* NULL delimited */
char mode[8]; /* octal, ascii */
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char linkflag;
char linkname[NAMSIZ];
} dbuf;
};
/* Decoded file header.
*/
struct fheader {
char name[NAMSIZ];
int mode;
int uid;
int gid;
int isdir;
long size;
long mtime;
long chksum;
int linkflag;
char linkname[NAMSIZ];
};
int advance; /* Advance to named file */
int stripblanks; /* strip blank padding at end of file */
int debug; /* Print debugging messages */
int binaryout; /* make only binary byte stream files */
int omitbinary; /* omit binary files (do not write) */
int extract; /* Extract files from the tape */
int replace; /* Replace existing files */
int exclude; /* Excluded named files */
int printfnames; /* Print file names */
int verbose; /* Print everything */
int links; /* Defeat copy to resolve link */
char *pathprefix = NULL;
int len_pathprefix = 0;
struct fheader *curfil;
int eof;
int nerrs;
char *first_file;
char tapeblock[SZ_TAPEBUFFER];
char *nextblock;
int nblocks;
char *getblock();
/* MAIN -- "rtar [xtvlef] [names]". The default operation is to extract all
* files from the tar format standard input in quiet mode.
*/
main (argc, argv)
int argc;
char *argv[];
{
struct fheader fh;
char **argp;
char *ip;
int in = 0, out;
int ftype;
int ch;
ZZSTRT(); /* initialize the IRAF kernel */
advance = 0;
debug = 0;
binaryout = 0;
omitbinary = 0;
extract = 0;
replace = 0;
exclude = 0;
printfnames = 0;
verbose = 0;
links = 0;
stripblanks = 1; /* strip blanks at end of file by default */
/* Get parameters. Argp is left pointing at the list of files to be
* extracted (default all if no files named).
*/
argp = &argv[1];
if (argc <= 1)
extract++;
else {
while (*argp && **argp == '-') {
ip = *argp++ + 1;
while ((ch = *ip++) != EOS) {
switch (ch) {
case 'a':
advance++;
break;
case 'n':
stripblanks = 0;
break;
case 'x':
extract++;
break;
case 'b':
binaryout++;
break;
case 'd':
debug++;
break;
case 'e':
exclude++;
break;
case 'r':
replace++;
break;
case 't':
printfnames++;
break;
case 'v':
verbose++;
break;
case 'l':
links++;
break;
case 'o':
omitbinary++;
break;
case 'p':
if (*argp != NULL) {
pathprefix = *argp++;
len_pathprefix = strlen (pathprefix);
}
break;
case 'f':
if (*argp == NULL) {
fprintf (stderr, "missing filename argument\n");
exit (OSOK+1);
}
in = tape_open (*argp, 0);
if (in == ERR) {
fprintf (stderr, "cannot open `%s'\n", *argp);
ZZSTOP();
exit (OSOK+1);
}
argp++;
break;
default:
fprintf (stderr, "Warning: unknown switch `%c'\n", ch);
fflush (stderr);
break;
}
}
}
}
/* If advancing to a file get the name of the file. This file name
* occurs at the beginning of the file list but is not part of the list.
* Only full filenames are permitted here.
*/
if (advance)
first_file = *argp++;
/* Step along through the tar format file. Read file header and if
* file is in list and extraction is enabled, extract file.
*/
while (getheader (in, &fh) != EOF) {
curfil = &fh;
if (advance)
if (strcmp (fh.name, first_file) == 0) {
if (debug)
fprintf (stderr, "match\n");
advance = 0;
} else {
if (debug)
printheader (stderr, &fh, verbose);
skipfile (in, &fh);
continue;
}
if (matchfile (fh.name, argp) == exclude) {
if (debug)
fprintf (stderr, "skip file `%s'\n", fh.name);
skipfile (in, &fh);
continue;
}
if (printfnames) {
printheader (stdout, &fh, verbose);
fflush (stdout);
}
if (fh.linkflag) {
/* No file follows header if file is a link. Try to resolve
* the link by copying the original file, assuming it has been
* read from the tape.
*/
if (extract) {
if (!links) {
if (replace)
os_delete (fh.name);
if (os_fcopy (fh.linkname, fh.name) == ERR) {
fprintf (stderr, "Copy `%s' to `%s' fails\n",
fh.linkname, fh.name);
} else {
os_setfmode (fh.name, fh.mode);
os_setowner (fh.name, fh.uid, fh.gid);
os_setmtime (fh.name, fh.mtime);
}
} else {
fprintf (stderr,
"Warning: cannot make link `%s' to `%s'\n",
fh.name, fh.linkname);
}
}
continue;
}
if (extract) {
ftype = filetype (in, &fh);
if (fh.size > 0 && ftype == BINARY_FILE && omitbinary) {
if (printfnames)
fprintf (stderr, "omit binary file `%s'\n", fh.name);
skipfile (in, &fh);
continue;
}
out = newfile (fh.name, fh.mode, fh.uid, fh.gid, ftype);
if (out == ERR) {
fprintf (stderr, "cannot create file `%s'\n", fh.name);
skipfile (in, &fh);
continue;
}
if (!fh.isdir) {
copyfile (in, out, &fh, ftype);
os_close (out);
}
os_setfmode (fh.name, fh.mode);
os_setowner (fh.name, fh.uid, fh.gid);
os_setmtime (fh.name, fh.mtime);
} else
skipfile (in, &fh);
}
/* End of TAR file normally occurs when a zero tape block is read;
* this is not the same as the physical end of file, leading to
* problems when reading from sequential devices (e.g. pipes and
* magtape). Advance to the physical end of file before exiting.
*/
if (!eof)
while (tape_read (in, tapeblock, SZ_TAPEBUFFER) > 0)
;
if (in)
tape_close (in);
ZZSTOP();
exit (OSOK);
}
/* MATCHFILE -- Search the filelist for the named file. If the file list
* is empty anything is a match. If the list element ends with a $ an
* exact match is required (excluding the $), otherwise we have a match if
* the list element is a prefix of the filename.
*/
matchfile (fname, files)
char *fname; /* filename to be compared to list */
register char **files; /* pointer to array of fname pointers */
{
register char *fn, *ln;
register int firstchar;
if (*files == NULL)
return (1);
firstchar = *fname;
do {
if (**files++ == firstchar) {
for (fn=fname, ln = *(files-1); *ln && *ln == *fn++; )
ln++;
if (*ln == EOS)
return (1);
else if (*ln == '$' && *(fn-1) == EOS)
return (1);
}
} while (*files);
return (0);
}
/* GETHEADER -- Read the next file block and attempt to interpret it as a
* file header. A checksum error on the file header is fatal and usually
* indicates that the tape is not positioned to the beginning of a file.
* If we have a legal header, decode the character valued fields into binary.
*/
getheader (in, fh)
int in; /* input file */
register struct fheader *fh; /* decoded file header (output) */
{
register char *ip, *op;
register int n;
union hblock *hb;
int tape_checksum, ntrys;
for (ntrys=0; ; ntrys++) {
if ((hb = (union hblock *)getblock (in)) == NULL)
return (EOF);
/* Decode the checksum value saved in the file header and then
* overwrite the field with blanks, as the field was blank when
* the checksum was originally computed. Compute the actual
* checksum as the sum of all bytes in the header block. If the
* sum is zero this indicates the end of the tar file, otherwise
* the checksums must match.
*/
if (*hb->dbuf.chksum == '\0' && cchksum ((char *)hb, TBLOCK) == 0)
return (EOF);
else
sscanf (hb->dbuf.chksum, "%o", &tape_checksum);
for (ip=hb->dbuf.chksum, n=8; --n >= 0; )
*ip++ = ' ';
if (cchksum ((char *)hb, TBLOCK) != tape_checksum) {
/* If a checksum error occurs try to advance to the next
* header block.
*/
if (ntrys == 0) {
fprintf (stderr,
"rtar: file header checksum error %o != %o\n",
cchksum ((char *)hb, TBLOCK), tape_checksum);
} else if (ntrys >= MAXTRYS) {
fprintf (stderr, "cannot recover from checksum error\n");
exit (OSOK+1);
}
} else
break;
}
if (ntrys > 1)
fprintf (stderr, "found next file following checksum error\n");
/* Decode the ascii header fields into the output file header
* structure.
*/
for (ip=hb->dbuf.name, op=fh->name; (*op++ = *ip++); )
;
fh->isdir = (*(op-2) == '/');
sscanf (hb->dbuf.mode, "%o", &fh->mode);
sscanf (hb->dbuf.uid, "%o", &fh->uid);
sscanf (hb->dbuf.gid, "%o", &fh->gid);
sscanf (hb->dbuf.size, "%lo", &fh->size);
sscanf (hb->dbuf.mtime, "%lo", &fh->mtime);
n = hb->dbuf.linkflag;
if (n >= '0' && n <= '9')
fh->linkflag = n - '0';
else
fh->linkflag = 0;
if (fh->linkflag)
strcpy (fh->linkname, hb->dbuf.linkname);
return (TBLOCK);
}
/* CCHKSUM -- Compute the checksum of a byte array.
*/
cchksum (p, nbytes)
register char *p;
register int nbytes;
{
register int sum;
for (sum=0; --nbytes >= 0; )
sum += *p++;
return (sum);
}
struct _modebits {
int code;
char ch;
} modebits[] = {
040000, 'd',
0400, 'r',
0200, 'w',
0100, 'x',
040, 'r',
020, 'w',
010, 'x',
04, 'r',
02, 'w',
01, 'x',
0, 0
};
/* PRINTHEADER -- Print the file header in either short or long (verbose)
* format, e.g.:
* drwxr-xr-x 9 tody 1024 Nov 3 17:53 .
*/
printheader (out, fh, verbose)
FILE *out; /* output file */
register struct fheader *fh; /* file header struct */
int verbose; /* long format output */
{
register struct _modebits *mp;
char *tp, *ctime();
if (!verbose) {
fprintf (out, "%s\n", fh->name);
return;
}
for (mp=modebits; mp->code; mp++)
fprintf (out, "%c", mp->code & fh->mode ? mp->ch : '-');
tp = ctime (&fh->mtime);
fprintf (out, "%3d %4d %2d %8d %-12.12s %-4.4s %s",
fh->linkflag,
fh->uid,
fh->gid,
fh->size,
tp + 4, tp + 20,
fh->name);
if (fh->linkflag)
fprintf (out, " -> %s\n", fh->linkname);
else
fprintf (out, "\n");
}
/* FILETYPE -- Determine the file type (text, binary, or directory) of the
* next file on the input stream. Directory files are easy; the tar format
* identifies directories unambiguously. Discriminating between text and
* binary files is not possible in general because UNIX does not make such
* a distinction, but in practice we can apply a heuristic which will work
* in nearly all cases. This can be overriden, producing only binary byte
* stream files as output, by a command line switch.
*/
filetype (in, fh)
int in; /* input file */
struct fheader *fh; /* decoded file header */
{
register char *cp;
register int n;
/* Easy cases first.
*/
if (fh->isdir)
return (DIRECTORY_FILE);
else if (fh->size == 0 || binaryout)
return (BINARY_FILE);
/* Get a pointer to the first block of the input file and set the
* input pointers back so that the block is returned by the next
* call to getblock.
*/
if ((cp = getblock (in)) == NULL)
return (BINARY_FILE);
nextblock -= TBLOCK;
nblocks++;
/* Examine the data to see if it is text. The simple heuristic
* used requires that all characters be either printable ascii
* or common control codes.
*/
n = (fh->size < TBLOCK) ? fh->size : TBLOCK;
while (--n >= 0 && (isprint(*cp) || isspace(*cp)))
cp++;
return (n < 0 ? TEXT_FILE : BINARY_FILE);
}
/* NEWFILE -- Try to open a new file for writing, creating the new file
* with the mode bits given. Create all directories leading to the file if
* necessary (and possible).
*/
newfile (fname, mode, uid, gid, type)
char *fname; /* pathname of file */
int mode; /* file mode bits */
int uid, gid; /* file owner, group codes */
int type; /* text, binary, directory */
{
int fd;
char *cp;
char *rindex();
if (len_pathprefix && strncmp(fname,pathprefix,len_pathprefix) == 0)
fname += len_pathprefix;
if (debug)
fprintf (stderr, "newfile `%s':\n", fname);
if (checkdir (fname, mode, uid, gid) == ERR)
return (ERR);
if (type == DIRECTORY_FILE) {
cp = rindex (fname, '/');
if (cp && *(cp+1) == EOS)
*cp = EOS;
fd = os_createdir (fname, mode);
/* Ignore any error creating directory, as this may just mean
* that the directory already exists. If the directory does
* not exist and cannot be created, there will be plenty of
* other errors when we try to write files into it.
*/
fd = OK;
} else {
if (replace)
os_delete (fname);
fd = os_createfile (fname, mode, type);
}
return (fd);
}
/* CHECKDIR -- Verify that all the directories in the pathname of a file
* exist. If they do not exist, try to create them.
*/
checkdir (path, mode, uid, gid)
register char *path;
int mode;
int uid, gid;
{
register char *cp;
char *rindex();
/* Quick check to see if the directory exists.
*/
if ((cp = rindex (path, '/')) == NULL)
return (OK);
*cp = EOS;
if (os_access (path, 0, DIRECTORY_FILE) == YES) {
*cp = '/';
return (OK);
}
*cp = '/';
/* The directory cannot be accessed. Try to make all directories
* in the pathname. If the file is itself a directory leave its
* creation until later.
*/
for (cp=path; *cp; cp++) {
if (*cp != '/')
continue;
if (*(cp+1) == EOS)
return (OK);
*cp = EOS;
if (os_access (path, 0, DIRECTORY_FILE) == NO) {
if (os_createdir (path, RWXR_XR_X) == ERR) {
fprintf (stderr, "cannot create directory `%s'\n", path);
*cp = '/';
return (ERR);
} else
os_setowner (path, uid, gid);
}
*cp = '/';
}
return (OK);
}
/* COPYFILE -- Copy bytes from the input (tar) file to the output file.
* Each file consists of a integral number of TBLOCK size blocks on the
* input file.
*/
copyfile (in, out, fh, ftype)
int in; /* input file */
int out; /* output file */
struct fheader *fh; /* file header structure */
int ftype; /* text or binary file */
{
long nbytes = fh->size;
int nblocks = 0, maxpad;
char *bp;
/* Link files are zero length on the tape. */
if (fh->linkflag)
return;
if (ftype == BINARY_FILE || !stripblanks)
maxpad = 0;
else
maxpad = SZ_PADBUF;
/* Copy all but the last MAXPAD characters if the file is a text file
* and stripping is enabled.
*/
while (nbytes > maxpad && (bp = getblock (in)) != NULL)
if (os_write (out, bp, nbytes<TBLOCK ? (int)nbytes:TBLOCK) == ERR) {
fprintf (stderr, "Warning: file write error on `%s'\n",
curfil->name);
if (nerrs++ > MAXERR) {
fprintf (stderr, "Too many errors\n");
exit (OSOK+1);
}
} else {
nbytes -= TBLOCK;
nblocks++;
}
/* Strip whitespace at end of file added by WTAR when the archive was
* created.
*/
if (nbytes > 0)
strip_blanks (in, out, nbytes);
if (debug)
fprintf (stderr, "%d blocks written\n", nblocks);
}
/* STRIP_BLANKS -- Read the remaining file data into the pad buffer.
* Write out the remaining data, minus any extra blanks or empty blank lines
* at the end of the file. Some versions of WTAR (e.g., VMS) do not know
* the actual size of a text file and have to pad with blanks at the end to
* make the file the size noted in the file header.
*/
strip_blanks (in, out, nbytes)
int in, out;
long nbytes;
{
register char *ip, *op;
char padbuf[SZ_PADBUF+10];
char *lastnl;
int n;
/* Fill buffer.
*/
op = padbuf;
while (nbytes > 0 && (ip = getblock (in)) != NULL) {
n = nbytes < TBLOCK ? (int)nbytes : TBLOCK;
os_amovb (ip, op, n + sizeof(XCHAR)-1);
nbytes -= n;
op += n;
}
/* Backspace from the end of the buffer until the last nonblank line
* is found.
*/
lastnl = op - 1;
for (ip=lastnl; ip > padbuf; --ip)
if (*ip == '\n')
lastnl = ip;
else if (*ip != ' ')
break;
/* Write out everything up to and including the newline at the end of
* the last line containing anything but blanks.
*/
os_write (out, padbuf, lastnl - padbuf + 1);
}
/* SKIPFILE -- Skip the indicated number of bytes on the input (tar) file.
*/
skipfile (in, fh)
int in; /* input file */
struct fheader *fh; /* file header */
{
register long nbytes = fh->size;
/* Link files are zero length on the tape. */
if (fh->linkflag)
return;
while (nbytes > 0 && getblock (in) != NULL)
nbytes -= TBLOCK;
}
/* GETBLOCK -- Return a pointer to the next file block of size TBLOCK bytes
* in the input file.
*/
char *
getblock (in)
int in; /* input file */
{
char *bp;
int nbytes;
for (;;) {
if (eof)
return (NULL);
else if (--nblocks >= 0) {
bp = nextblock;
nextblock += TBLOCK;
return (bp);
}
if ((nbytes = tape_read (in, tapeblock, SZ_TAPEBUFFER)) < TBLOCK)
eof++;
else {
nblocks = (nbytes + TBLOCK-1) / TBLOCK;
nextblock = tapeblock;
}
}
}