diff options
Diffstat (limited to 'unix/boot/rtar/rtar.c')
-rw-r--r-- | unix/boot/rtar/rtar.c | 863 |
1 files changed, 863 insertions, 0 deletions
diff --git a/unix/boot/rtar/rtar.c b/unix/boot/rtar/rtar.c new file mode 100644 index 00000000..6ef2e37e --- /dev/null +++ b/unix/boot/rtar/rtar.c @@ -0,0 +1,863 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> + +#define NOKNET +#define import_spp +#define import_knames +#include <iraf.h> + +#include "../bootProto.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 + * m do not restore file modify times + * 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 + * u do not attempt to restore user id + * 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 MAXLINELEN 256 +#define SZ_TAPEBUFFER (TBLOCK * NBLOCK) +#define EOS '\0' +#define ERR (-1) +#define OK 0 +#define RWXR_XR_X 0755 +#define SZ_PADBUF 8196 +#define ctrlcode(c) ((c) >= '\007' && (c) <= '\017') + +#define LF_LINK 1 +#define LF_SYMLINK 2 +#define LF_DIR 5 + +/* 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]; +}; + + +static int advance; /* Advance to named file */ +static int stripblanks; /* strip blank padding at end of file */ +static int debug; /* Print debugging messages */ +static int binaryout; /* make only binary byte stream files */ +static int omitbinary; /* omit binary files (do not write) */ +static int extract; /* Extract files from the tape */ +static int replace; /* Replace existing files */ +static int exclude; /* Excluded named files */ +static int printfnames; /* Print file names */ +static int verbose; /* Print everything */ +static int links; /* Defeat copy to resolve link */ +static int setmtime; /* Restore file modify times */ +static int rsetuid; /* Restore file user id */ + +static char *pathprefix = NULL; +static int len_pathprefix = 0; +static struct fheader *curfil; +static int eof; +static int nerrs; +static char *first_file; +static char tapeblock[SZ_TAPEBUFFER]; +static char *nextblock; +static int nblocks; + +extern int ZZSTRT (void); +extern int ZZSTOP (void); + +extern int tape_open (char *fname, int mode); +extern int tape_close (int fd); +extern int tape_read (int fd, char *buf, int nbytes); + +static int matchfile (char *fname, register char **files); +static int getheader (int in, register struct fheader *fh); +static int cchksum (register char *p, register int nbyte); +static void printheader (FILE *out, register struct fheader *fh, int verbose); +static int filetype (int in, struct fheader *fh); +static int newfile (char *fname, int mode, int uid, int gid, int type); +static int checkdir (register char *path, int mode, int uid, int gid); +static void copyfile (int in, int out, struct fheader *fh, int ftype); +static void strip_blanks (int in, int out, long nbytes); +static void skipfile (int in, struct fheader *fh); +static char *getblock (int in); + + + + +char *getblock(); + + +/* MAIN -- "rtar [xtvlef] [names]". The default operation is to extract all + * files from the tar format standard input in quiet mode. + */ +int main (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; + setmtime = 1; + rsetuid = 1; + 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': + printfnames++; + verbose++; + break; + case 'l': + links++; + break; + case 'm': + setmtime = 0; + break; + case 'u': + rsetuid = 0; + 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 == LF_SYMLINK || fh.linkflag == LF_LINK) { + /* 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 (fh.linkflag == LF_SYMLINK) { + if (replace) + os_delete (fh.name); + if (symlink (fh.linkname, fh.name) != 0) { + fprintf (stderr, + "Cannot make symbolic link %s -> %s\n", + fh.name, fh.linkname); + } + } else if (fh.linkflag == LF_LINK && !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); + if (rsetuid) + os_setowner (fh.name, fh.uid, fh.gid); + if (setmtime) + 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); + if (rsetuid) + os_setowner (fh.name, fh.uid, fh.gid); + if (setmtime) + 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); + + return (0); +} + + +/* 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. + */ +static int +matchfile ( + 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. + */ +static int +getheader ( + 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. + */ +static int +cchksum ( + 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 . + */ +static void +printheader ( + 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 %8ld %-12.12s %-4.4s %s", + fh->linkflag, + fh->uid, + fh->gid, + fh->size, + tp + 4, tp + 20, + fh->name); + + if (fh->linkflag && *fh->linkname) + 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. + */ +static int +filetype ( + int in, /* input file */ + struct fheader *fh /* decoded file header */ +) +{ + register char *cp; + register int n, ch; + int newline_seen, nchars; + + /* 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 = nchars = (fh->size < TBLOCK) ? fh->size : TBLOCK; + for (newline_seen=0; --n >= 0; ) { + ch = *cp++; + if (ch == '\n') + newline_seen++; + else if (!isprint(ch) && !isspace(ch) && !ctrlcode(ch)) + break; + } + + if (n >= 0 || (nchars > MAXLINELEN && !newline_seen)) + return (BINARY_FILE); + else + return (TEXT_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). + */ +static int +newfile ( + char *fname, /* pathname of file */ + int mode, /* file mode bits */ + int uid, int 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. + */ +static int +checkdir ( + register char *path, + int mode, + int uid, int 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. + */ +static void +copyfile ( + 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. + */ +static void +strip_blanks (int in, int 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. + */ +static void +skipfile ( + 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. + */ +static char * +getblock (int in) +{ + 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; + } + } +} |