aboutsummaryrefslogtreecommitdiff
path: root/unix/boot/rtar
diff options
context:
space:
mode:
authorJoe Hunkeler <jhunkeler@gmail.com>2015-08-11 16:51:37 -0400
committerJoe Hunkeler <jhunkeler@gmail.com>2015-08-11 16:51:37 -0400
commit40e5a5811c6ffce9b0974e93cdd927cbcf60c157 (patch)
tree4464880c571602d54f6ae114729bf62a89518057 /unix/boot/rtar
downloadiraf-osx-40e5a5811c6ffce9b0974e93cdd927cbcf60c157.tar.gz
Repatch (from linux) of OSX IRAF
Diffstat (limited to 'unix/boot/rtar')
-rw-r--r--unix/boot/rtar/README5
-rw-r--r--unix/boot/rtar/mkpkg.sh6
-rw-r--r--unix/boot/rtar/rtar.c863
-rw-r--r--unix/boot/rtar/rtar.hlp165
-rw-r--r--unix/boot/rtar/rtar.ms125
5 files changed, 1164 insertions, 0 deletions
diff --git a/unix/boot/rtar/README b/unix/boot/rtar/README
new file mode 100644
index 00000000..61e45d80
--- /dev/null
+++ b/unix/boot/rtar/README
@@ -0,0 +1,5 @@
+RTAR -- Read tar format file or tape. This is a portable, non-UNIX, non-
+ proprietary program for reading tar format files on a variety of
+ systems. The TAR format is an excellent choice for transporting
+ files between different machines because of its simplicity, efficiency,
+ and machine independence.
diff --git a/unix/boot/rtar/mkpkg.sh b/unix/boot/rtar/mkpkg.sh
new file mode 100644
index 00000000..ec801f5f
--- /dev/null
+++ b/unix/boot/rtar/mkpkg.sh
@@ -0,0 +1,6 @@
+# Bootstrap RTAR.
+
+$CC -c $HSI_CF rtar.c
+$CC $HSI_LF rtar.o $HSI_LIBS -o rtar.e
+mv rtar.e ../../hlib
+rm -f rtar.o
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;
+ }
+ }
+}
diff --git a/unix/boot/rtar/rtar.hlp b/unix/boot/rtar/rtar.hlp
new file mode 100644
index 00000000..843add6f
--- /dev/null
+++ b/unix/boot/rtar/rtar.hlp
@@ -0,0 +1,165 @@
+.help rtar Oct92 softools
+.IH
+NAME
+rtar -- read TAR format archive file
+.IH
+USAGE
+rtar [ flags ] [ archive ] [ after ] [ files ]
+.IH
+PARAMETERS
+.ls 4 -a
+Advance to the archive file named by the \fIafter\fR argument before
+performing the main operation. The extract or list operation will begin with
+the file \fIafter\fR and continue to the end of the archive.
+.le
+.ls 4 -b
+Output only binary byte stream files. By default, \fIrtar\fR outputs text
+files in the host system textfile format. The conversion from the byte stream
+\fItar\fR format to host textfile format may involve modification of the
+file, e.g., conversion from ASCII to EBCDIC. A binary extraction copies
+the file to disk without modification.
+.le
+.ls 4 -d
+Print detailed information about what \fIrtar\fR is doing.
+.le
+.ls 4 -e
+Extract the entire contents of the tape \fIexcluding\fR the files or directories
+listed in \fIfiles\fR.
+.le
+.ls 4 -f filename
+\fIRtar\fR uses the first filename argument as the host filename of the
+archive instead of reading from \fIstdin\fR. Magtape devices should be
+specified using the host device name, e.g., "/dev/nrmt8" or "MSA0".
+Since \fIrtar\fR is a host level program and does not read the IRAF tapecap
+file, IRAF device names such as "mta" cannot be used.
+.le
+.ls 4 -l
+Do not try to resolve file links by a disk to disk file copy. By default,
+if file A appears in the archive as a link to file B,
+\fIrtar\fR trys to resolve the link by performing a disk to disk copy of
+file B to A. This is valid providing file B was present in the archive and
+has already been extracted. If the \fBl\fR flag is present linked files
+will not be extracted.
+.le
+.ls 4 -m
+Do not restore the file modify time.
+.le
+.ls 4 -n
+Do not strip trailing blank lines from text files read from the tape.
+The default is to strip any blank lines at the ends of files.
+This is necessary when the file was written by \fIwtar\fR on a system
+like VMS, where the size of the file is not known before it has been
+read. The \fIwtar\fR utility must guess at the final size and pad the
+file at the end with spaces to ensure that the size of the file actually
+written agrees with the file header.
+.le
+.ls 4 -o
+Omit binary files when performing the extraction. A binary file is any
+file containing ASCII values other than 040 through 0176 (the printable
+ASCII characters), tab, or newline in the first 512 byte block of the file.
+.le
+.ls 4 -p pathprefix
+When creating directories and files from the pathnames recorded in the archive,
+omit the given path prefix if it matches the pathname given in the archive.
+This feature is used to relocate directories, or to read tar archives
+containing absolute pathnames. For example, given "-p /usr/", the archive
+pathname "/usr/me/file" would be written to the file "me/file".
+.le
+.ls 4 -r
+The extracted file replaces any existing file of the same name, i.e.,
+\fIrtar\fR performs a delete before creating the extracted file.
+.le
+.ls 4 -t
+The names of the specified files are listed each time they occur on
+the tape. If no \fIfiles\fR argument is given, all of the names on the tape
+are listed.
+.le
+.ls 4 -u
+Do not attempt to restore the owner and group identification of each file.
+.le
+.ls 4 -v
+Print more information about the tape entries than just their names.
+The verbose file list format gives the file permissions, the link flag
+(zero if there were no links to the file), the owner and group identification
+numbers of the file on the system that wrote the archive, the file size in
+bytes, the date of last modification of the file, and the file name.
+.le
+.ls 4 -x
+The named files are extracted from the tape. If the named file
+matches a directory whose contents had been written onto the tape, this
+directory is (recursively) extracted. The owner, modification time, and mode
+are restored (if possible). If no file argument is given, the entire content
+of the tape is extracted. Note that if multiple entries specifying the same
+file are on the tape, the last one overwrites all earlier.
+.le
+.IH
+DESCRIPTION
+\fIRtar\fR reads multiple files from a UNIX \fItar\fR format file,
+restoring the files to disk on the local host machine.
+Output filenames are mapped according to the IRAF filenaming conventions
+of the local host operating system.
+
+\fIRtar\fR's actions are controlled by the \fIflags\fR argument.
+\fIFlags\fR consists of a minus sign followed by a string of characters
+containing any combination of the function flags described below.
+Other arguments to \fIrtar\fR are the name of the archive file to be read,
+the name of the file on the archive at which reading is to begin,
+and the names of the files or directories to be read or to be excluded
+from the read. In all cases, appearance of a directory name refers to
+the files and (recursively) subdirectories of that directory.
+
+All \fIrtar\fR filename arguments are IRAF virtual filenames (or host
+filenames), except the prefix strings, which pertain to the tape format and
+hence are UNIX pathnames. Magtape devices must be specified using a host
+physical or logical device name (i.e., IRAF device names like "mta" will not
+work).
+
+If the input archive file is a tape the blocksize must be a multiple
+of 512 bytes, with a maximum blocksize of 10240 bytes. Each archived file
+occupies an integral number of 512 byte blocks in the archive (this is
+required by the \fItar\fR format).
+
+Filenames appearing in the file list are interpreted as prefix strings,
+i.e., a match occurs if the given string is a prefix of an actual filename
+in the archive. If the last character in the \fIfiles\fR filename is
+a \fB$\fR then an exact match is required (excluding the $ meta-character).
+.IH
+DIAGNOSTICS
+A file read error occurring while reading the archive file is fatal unless
+caught and corrected by the host system.
+File header checksum errors result in skipping of the archive file
+currently being read, with execution continuing with the next archive
+file if possible.
+File write errors on the output file are reported but do not cause
+termination of \fIrtar\fR. The output file being written will be corrupted.
+.ih
+EXAMPLES
+Since \fIrtar\fR is a bootstrap utility implemented as a foreign task in
+the CL, it may be called either from within the CL (as in the examples),
+or at the host system level. The command syntax is identical on both cases.
+
+1. List the contents of the disk archive file "foo.tar".
+
+ cl> rtar -tvf foo.tar
+
+2. Unpack the tape archive on unix device /dev/nrmt8 in the current
+directory.
+
+ cl> rtar -xf /dev/nrmt8
+
+3. Unpack the tape archive on the VMS device MSA0: in the current
+directory.
+
+ cl> rtar -xf msa0
+
+When working within the CL, commands such as \fIrewind\fR may be used
+with \fIrtar\fR, but switching between IRAF and host device names may be
+confusing.
+.IH
+BUGS
+The current limit on file name length is 100 characters (this restriction
+is imposed by the standard UNIX \fItar\fR format).
+File links are not recreated.
+.ih
+SEE ALSO
+wtar, rmbin
diff --git a/unix/boot/rtar/rtar.ms b/unix/boot/rtar/rtar.ms
new file mode 100644
index 00000000..43746400
--- /dev/null
+++ b/unix/boot/rtar/rtar.ms
@@ -0,0 +1,125 @@
+.TH RTAR 1 "14 November 1984"
+.SH NAME
+rtar \- read tape archive format file
+.SH SYNOPSIS
+.B rtar
+[ flags ] [ archive ] [ after ] [ files ]
+.SH DESCRIPTION
+.PP
+.I Rtar
+reads multiple files from a UNIX \fItar\fR format file, restoring the files
+to disk on the local host machine. Output filenames are mapped according to
+the IRAF filenaming conventions of the local host operating system.
+.IR Rtar 's
+actions are controlled by the
+.I flags
+argument.
+.I Flags
+consists of an \fB-\fR followed by
+a string of characters containing any combination of the function flags
+described below.
+Other arguments to
+.I rtar
+are the name of the archive file to be read,
+the name of the file on the archive at which reading is to begin,
+and the names of the files or directories to be read or to be excluded
+from the read.
+In all cases, appearance of a directory name refers to
+the files and (recursively) subdirectories of that directory.
+All
+.I rtar
+filename arguments are UNIX pathnames except
+.I archive,
+which is a host system filename.
+.PP
+The default action of \fIrtar\fR is to unpack all files from the \fItar\fR
+format standard input. The following flag characters may be used to further
+control the function of \fIrtar\fR:
+.TP 8
+.B x
+The named files are extracted from the tape. If the named file
+matches a directory whose contents had been written onto the tape, this
+directory is (recursively) extracted. The owner, modification time, and mode
+are restored (if possible). If no file argument is given, the entire content
+of the tape is extracted. Note that if multiple entries specifying the same
+file are on the tape, the last one overwrites all earlier.
+.TP 8
+.B r
+The extracted file replaces any existing file of the same name, i.e.,
+.I rtar
+performs a delete before creating the extracted file.
+.TP 8
+.B e
+Extract the entire contents of the tape \fIexcluding\fR the files or directories
+listed in \fIfiles\fR.
+.TP 8
+.B a
+Advance to the archive file named by the \fIafter\fR argument before
+performing the main operation. The extract or list operation will begin with
+the file \fIafter\fR and continue to the end of the archive.
+.TP 8
+.B t
+The names of the specified files are listed each time they occur on
+the tape. If no \fIfiles\fR argument is given, all of the names on the tape
+are listed.
+.TP 8
+.B v
+Print more information about the tape entries than just their names.
+The verbose file list format gives the file permissions, the link flag
+(zero if there were no links to the file), the owner and group identification
+numbers of the file on the system that wrote the archive, the file size in
+bytes, the date of last modification of the file, and the file name.
+.TP 8
+.B d
+Print detailed information about what \fIrtar\fR is doing.
+.TP 8
+.B f
+.I Rtar
+uses the first filename argument as the host filename of the archive
+instead of reading from \fIstdin\fR.
+.TP 8
+.B l
+Do not try to resolve file links by a disk to disk file copy. By default,
+if file A appears in the archive as a link to file B,
+\fIrtar\fR trys to resolve the link by performing a disk to disk copy of
+file B to A. This is valid providing file B was present in the archive and
+has already been extracted. If the \fBl\fR flag is present linked files
+will not be extracted.
+.TP 8
+.B o
+Omit binary files when performing the extraction. A binary file is any
+file containing ASCII values other than 040 through 0176 (the printable
+ASCII characters), tab, or newline in the first 512 byte block of the file.
+.TP 8
+.B b
+Output only binary byte stream files. By default, \fIrtar\fR outputs text
+files in the host system textfile format. The conversion from the byte stream
+\fItar\fR format to host textfile format may involve modification of the
+file, e.g., conversion from ASCII to EBCDIC. A binary extraction copies
+the file to disk without modification.
+.PP
+If the input archive file is a tape the blocksize must be a multiple
+of 512 bytes, with a maximum blocksize of 10240 bytes. Each archived file
+occupies an integral number of 512 byte blocks in the archive.
+.PP
+Filenames appearing in the file list are interpreted as prefix strings,
+i.e., a match occurs if the given string is a prefix of an actual filename
+in the archive. If the last character in the \fIfiles\fR filename is
+a \fB$\fR then an exact match is required (excluding the $ metacharacter).
+.SH DIAGNOSTICS
+.br
+A file read error occurring while reading the archive file is fatal unless
+caught and corrected by the host system.
+.br
+File header checksum errors result in skipping of the archive file
+currently being read, with execution continuing with the next archive
+file if possible.
+.br
+File write errors on the output file are reported but do not cause
+termination of \fIrtar\fR. The output file being written will be corrupted.
+.SH BUGS
+.br
+The current limit on file name length is 100 characters (this restriction
+is imposed by the standard UNIX \fItar\fR format).
+.br
+File links are not recreated.