/*
 *  Program to read Tops-20 Dumper format tapes
 *
 *		     Jim Guyton,  Rand Corporation
 *		     Original	    10/20/82
 *		     jdg:   -n added 6/11/83
 *		     jdg:    can now extract 8-bit-byte files  2/9/86
 *
 * Lot of mods by Jay Lepreau, Univ of Utah, 1-2/87.
 * More mods by Ken Harrenstien, 1992-98.
 */
/* KLH To-do:
	Figure out some convention for file numbering like ss.nnn which
	also accomodates designating savesets by sequential numbering from
	start of dump (rather than using the saveset # in block header, which
	is always 0 most of the time)

	Perhaps add an interactive browse capability similar to KRSTOR.
	Perhaps use a semi-permanent directory generated by initial scan.
*/
#ifdef __STDC__		/* Some compilers can't even define this correctly! */
#  define READ20_STDC __STDC__
#else
#  define READ20_STDC 0
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <time.h>		/* For ctime */
#include <stdlib.h>		/* For malloc */
#include <string.h>		/* For strchr, strstr, memcpy, etc */
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#if READ20_STDC
# include <stdarg.h>		/* For punt() */
#endif

#include "dump.h"

#define LOGFILE "Logfile"		/* logfile should be changeable */

char *unixname();
long getfield();
time_t unixtime();
#if READ20_STDC
  void punt(int, char *, ...);
#else
  void punt();
#endif

int  fdTape;			/* File handle for Dumper-20 format tape */
int tbrecsiz;			/* Tape buffer record size */
int tblen;			/* Tape buffer size (max recsize) */
char *tbuffer;			/* Tape buffer location */

long nblktape = 0;		/* # blocks read on entire tape so far */
long nblkfile = 0;		/* # blocks read in current tape file */
long nblksset = 0;		/* # blocks read in current saveset */

char *prevblock = 0;
int firstblock = 0;		/* We are reading first block of a file */

FILE *fpFile;			/* Output file handle on extracts */
int debug = 0;
int default_bytesize = 36;	/* Default byte size for binary files */
int wordflg = 0;		/* Set 1 to force 36-bit size for all files */
int numflg = 0;			/* Non-zero if using numeric filenames */
int keepcr = 0;			/* Keep CR's in CRLF pairs in text files */
int dodir = 0;			/* directory listing */
int xflg = 0;			/* extract */
int verbose = 0;
int fdbflg = 0;		/* Show FDB contents for files listed/extracted*/
int genflg;		/* keep generation number */
int nselect;		/* number of files still to be selected by number */
int doallflag;		/* act on all files cause no args given */
int qicflg = 0;			/* Non-zero if hacking QIC tape */
long tlocseek = 0;		/* Hack: tapeloc to seek to (-s) */
int showtloc = 0;		/* Non-zero to show tapelocs on listing */

int number;			/* Current output file "number" */

#define TAPE "/dev/rmt8"	/* Default input tape */

enum { OC_ASCII, OC_8BIT, OC_WORD, OC_PAGE }
	outmode;	/* File output conversion mode */
int	outnfbpw;	/* # of file bytes per word (0 if bytesize 0) */
int	outnfw;		/* # of file words remaining (set from fdb) */
int	outnbpiw;	/* # output bytes per input 36-bit file word */
int	outnbleft;	/* # of output bytes left to go */

int  bytesize;		/* FDB: Number of bits/byte in current file */
long numbytes;		/* FDB: Number of bytes in current file */
int  pgcount;		/* FDB: Number of twenex pages in file */

long tapefmt;		/* DUMPER tape format from saveset header */
long tapeno, ssno;	/* Tape # and Saveset # from saveset header */
long pageno, filenum;

unsigned tprot;		/* Tops-20 protection */

time_t timep[2];
#define atime (timep[0])
#define mtime (timep[1])

int offline, archived, invisible;
int apgcount, tp1, tp2, ss1, ss2, tf1, tf2;

#define DUMPFNAMLEN (0200*5)		/* Max length of dumped filename */
char topsname[DUMPFNAMLEN];
char sunixname[300];

struct want {
     unsigned short ssnum;
     unsigned short fnum;
} want[10000];				/* limited by 20000 char arglist */

int cursswant;
int compwant();

#define CONTAINS_BOF 1		/* Block contains the beginning of a file */
#define CONTAINS_EOF 2		/* Block contains the end of a file */
#define NUL_AT_END 4		/* Put a null at the end */
#define STRING (CONTAINS_BOF|CONTAINS_EOF|NUL_AT_END) /* Make strings easy */

char **patterns = 0;	 /* Filename match patterns */
int numpats = 0;	 /* Number of patterns */
char *expression = 0;
char *re_comp_error;	 /* Error message from re_comp() */
extern char *re_comp();

void scan();

char helpstr[] = "\
Usage: read20 [switches] [patterns]\n\
 Switches must be separated.\n\
 Patterns are simple substrings of the filenames to select.\n\
    -f <file>	Specify tapefile. '-' uses stdin.  Default is /dev/rmt8\n\
    -x		Extract files\n\
    -t 		List contents (one of -t or -x must be given)\n\
    -tl		Show tape locations in listing\n\
    -S <n>	Only process saveset <n>\n\
    -e <regexp>	Only process filenames matching <regexp> (one -e only)\n\
    -F <n> ...	Only process files numbered <n> ...\n\
    -V		Show FDB info for files extracted or listed\n\
    -s <loc>	Start reading at this byte loc in tapefile (seeks)\n\
    -q		Say using QIC (1/4\") cartridge tape\n\
    -v		Verbose feedback\n\
    -g		Keep generation # in extracted filename\n\
    -n <n>	Use numeric filenames for extracts, starting with <n>\n\
    -W		Treat all files as 36-bit.  Otherwise, 7-bit files\n\
		  are treated as ascii, 8-bit as 8-bit binary, and\n\
		  all others as 36-bit (direct copy of tape data).\n\
    -T		Treat 0 or 36-bit files as 7-bit ascii\n\
    -B		Treat 0 or 36-bit files as 8-bit binary\n\
    -c		Keep CRs in CRLF pairs for ascii files\n\
    -d<n>	Debug level (>0,>5,>10,>99) (default 0)\n\
";

main(argc, argv)
int argc;
char *argv[];
{
	char *tape = TAPE;		/* Pathname for tape device/file */

	/* Do switch parsing */

	signal(SIGINT, exit);	/* Allow ^C to cause profiling to finish */

	while(argc>1 && argv[1][0] == '-'){
		switch(argv[1][1]){
		case 's':		/* Seek to byte (temporary hack) */
			if (argc <= 2)
				punt(0, "Need byteloc after -s\n%s", helpstr);
			tlocseek = atoi(argv[2]);
			argc--; argv++;
			break;
		case 'f':
			if (argc <= 2)
				punt(0, "Need filename after -f\n%s", helpstr);
			tape = argv[2];
			argc--; argv++;
			break;
		case 'W':	/* Force all files to 36-bit word bytes */
			wordflg = 1;
			break;
		case 'T':	/* Force 0/36-bit files to 7-bit ascii */
			default_bytesize = 7;
			break;
		case 'B':	      /* Force 0/36-bit files to 8-bit bin */
			default_bytesize = 8;
			break;
		case 't':	      /* directory listing */
			if (argv[1][2] == 'l')
				showtloc = 1;
			else
				dodir = 1;
			break;
		case 'x':	      /* extract */
			xflg = 1;
			break;
		case 'q':	      /* Using QIC (1/4") tape */
			qicflg = 1;
			break;
		case 'v':	      /* verbosity */
			verbose++;
			break;
		case 'V':	      /* Output FDB info */
			fdbflg++;
			break;
		case 'g':	      /* keep gen number */
			genflg++;
			break;
		case 'd':
			debug = atoi(&argv[1][2]);
			fprintf(stderr, "Debug value set to %d\n", debug);
			break;
		case 'n':		/* numeric output filenames */
			if (argc <= 2)
				punt(0, "Need number after -n\n%s", helpstr);
			number = atoi(argv[2]);		/* First file name */
			numflg = 1;
			argc--; argv++;
			break;
		case 'c':		/* keep CR`s in CR/LF pairs */
			keepcr++;
			break;
		case 'e':		/* regular expression */
			if (argc <= 2)
				punt(0, "Need expression after -e\n%s", helpstr);
			if (expression)
				punt(0, "Only one -e regexp allowed");
			expression = argv[2];
			if ((re_comp_error = re_comp(expression)) != 0)
				punt(0, "error in -e from re_comp: %s",
						re_comp_error);
			argc--; argv++;
			break;
		case 'S':		/* selected save set number */
			if (argc <= 2)
				punt(0, "Need save set number after -S\n%s", helpstr);
			cursswant = atoi(argv[2]);
			argc--; argv++;
			break;
		case 'F':		/* selected file numbers */
			if (argc <= 2)
				punt(0, "Need file number(s) after -F\n%s", helpstr);
			for (argc -= 2, argv += 2;
			  argc && isdigit(**argv);
			  argc--, argv++, nselect++) {
			      want[nselect].ssnum = cursswant;
			      want[nselect].fnum = atoi(*argv);
			}
			argc += 2; argv -= 2;
			break;
		default:
			punt(0, "unknown flag %s\n%s", argv[1], helpstr);
		}
		argc--;	 argv++;
	}

	if (!xflg && !dodir)
		punt(0, "Need either '-x' or '-t' option.\n%s", helpstr);

	if (argc > 1) {
		patterns = &argv[1];
		numpats = argc - 1;
	}
	doallflag = !(patterns || expression || nselect);
	if (nselect)
		qsort((char *)want, nselect, sizeof (struct want), compwant);

	if (!strcmp(tape, "-"))		/* stdin */
		fdTape = 0;
	else
		fdTape = open(tape, 0);		/* Open tape for read */
	if (fdTape == -1)
		punt(1, "Can't open 'tape' file %s", tape);
	if (tlocseek) {
		if (lseek(fdTape, tlocseek, 0) == -1)
			punt(1, "Can't seek to %ld on 'tape' file %s",
				tlocseek, tape);
	}

	tbrecsiz = TAPEBLK * 15;	/* Default - use max block factor */
	tblen = ((tbrecsiz+511)/512)*512;	/* Round up to modulo-512 */
	if (!(tbuffer = malloc(tblen)))
		punt(1, "Can't allocate %d bytes for tape buffer", tblen);

	scan();				/* Now scan tape */
}

void
scan()
{
	char	*tapeblock;
	int rc;
	int rtype;
	long tloc = 0;

	rc = 0;
	for ( ; ; )		/* Loop till end of tape */
	{
					 /*** Read a block ***/
		if (rc == 0) {
			rc = getrec(fdTape, tbuffer, (qicflg ? tblen : tbrecsiz));
			if (rc > 0)
				tloc += rc;

			if (qicflg && rc > 0) {
				if (rc == tblen) rc = tbrecsiz;
				else punt(1, "Ugh, QIC rec %d long, shd be %d",
						rc, tblen);
			}
			if (debug > 99)
				printf("rc=%d\n", rc);
			if ((rc % (518*5)) != 0) {
				if (rc != 0)
				punt(1, "Oops.	Read block len = %d", rc);
			}
			if (rc == 0) {
				if (verbose)
					printf("\nEnd of tape.\n");
				exit(0);	/* Normal exit */
			}
			tapeblock = tbuffer;
			rc = rc - 518*5;
		}
		else {
			tapeblock = tapeblock + 518*5;
			rc = rc - 518*5;
		}

		prevblock = 0;
					/*** Do something with it ***/
		switch(rtype = -getfield(tapeblock,
		    WdoffRectype, BtoffRectype, BtlenRectype))
		{
		  case RectypeData:		/* Data block */
			doDatablock(tapeblock);
			break;

		  case RectypeTphd:		/* Saveset header */
			nblksset = 0;		/* Start of saveset */
			doSaveset(tapeblock, 0);
			break;

		  case RectypeFlhd:		/* File header */
			doFileHeader(tapeblock);
			break;

		  case RectypeFltr:		/* File trailer */
			doFileTrailer(tapeblock);
			break;

		  case RectypeTptr:		/* Tape trailer */
			doTapeTrailer(tapeblock);
			break;

		  case RectypeUsr:		/* User directory info ? */
			if (verbose /* >= 3 */)
				fprintf(stderr, "Directory block skipped\n");
			break;

		  case RectypeCtph:		/* Continued saveset hdr */
			nblksset = 0;		/* Start of saveset */
			doSaveset(tapeblock, 1);
			break;

		  case RectypeFill:		/* Fill block */
			if (verbose /* >= 3 */)
				fprintf(stderr, "Fill block skipped\n");
			break;

		  case RectypeTonext:		/* FMTV6 - to next tape */
			doTonext(tapeblock);
			break;

		  default:
			fprintf(stderr, "Unknown block type 0x%x, tapeloc %ld.\n",
				rtype, tloc - rc);
			if (debug == 0)			/* Unless debugging, */
				punt(0, "Aborting.");	/* stop now */
			break;
		}
		/* Done with record, bump counts */
		nblktape++;
		nblkfile++;
		nblksset++;
	}
}

int   masks[32] =	/* bitmasks for different length fields */
{	       0x00000001, 0x00000003, 0x00000007,
   0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f,
   0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
   0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff,
   0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff,
   0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,
   0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff,
   0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff,
   0xffffffff
};

long
getfield(block, wordoff, bitoff, bitlen)
char *block;		/* Tape block */
int wordoff;		/* 36-bit word offset */
register int bitoff;		 /* Bit offset of field (from msb) */
register int bitlen;		 /* Bit length of field */
{
	register char *p;	/* Used to point into block */
	register long w32;	/* First 32 bits of the 36 bit word */
	int   w4;		/* Last 4 bits of the 36 bit word */
	long  w = 0;		/* the word to return */

				/* First, the "illegal" kludge */
	if (bitoff == 0 && bitlen == 36) {
		bitoff = 4;
		bitlen = 32;
	}
	if (bitlen > 32)
		punt(0, "Can't get that large a field = %d!", bitlen);

	/* A PDP-10 (or 20) 36-bit word is laid out with the first 32 bits
	   as the first 4 bytes and the last 4 bits are the low order 4 bits
	   of the 5th byte.   The high 4 bits of that byte should be zero */

	p = block + (5*wordoff);	/* Get ptr to word of interest */
	w32 = *p++ & 0377;			/* First byte */
	w32 = (w32 << 8) | (*p++ & 0377);	/* 2nd */
	w32 = (w32 << 8) | (*p++ & 0377);	/* 3rd */
	w32 = (w32 << 8) | (*p++ & 0377);	/* 4th */
	w4  = *p;				/* 5th */
	if (w4 > 017)
		punt(0, "Not a PDP-10 tape!  w4 = octal %o", w4);

	/* Get the field right justified in the word "w".
	   There are three cases that I have to handle:
	      [1] field is contained in w32
	      [2] field crosses w32 and w4
	      [3] field is contained in w4
	*/

	if (bitoff+bitlen <= 32)	/* [1] field is contained in w32 */
	{
		w = w32 >> (32 - (bitoff+bitlen));
	}
	else if (bitoff <= 32)		/* [2] field crosses boundary */
	{
		w =  (w32 << (bitoff+bitlen-32))
		   | (w4  >> (36 - (bitoff+bitlen)));
	}
	else				/* [3] field is contained in w4 */
	{
		w = w4 >> (36 - (bitoff+bitlen));
	}
	w = w & masks[bitlen-1];	  /* Trim to proper size */
	return(w);
}

/* FDB extraction stuff
**	by KLH, 1992.
*/

struct fdbent {
	char *fe_name;		/* Symbol name */
	int fe_typ;		/* Value type (FBT_xxx) */
	int fe_woff;		/* Word offset */
	int fe_boff;		/* Bit offset (per MACRO, from hi-order bit) */
	int fe_bits;		/* Bits ((0 || 36) == full word) */
	char *fe_show;		/* Text to use when listing info */

	long fe_lh;		/* Extracted LH value */
	long fe_rh;		/* Extracted RH value */
	long fe_val;		/* Extracted value (what fits in a long) */
	char *fe_str;		/* String value (if one) */
	time_t fe_tim;		/* TAD converted to Unix timeval */
};
char festr_fname[DUMPFNAMLEN+1]; /* To hold extracted filename string */
char *festr_attrs;		/* Pointer to first fn attribute if any */
char festr_aut[10*5+1];		/* To hold extracted author string */
char festr_lwr[10*5+1];		/* To hold extracted last-writer string */

enum fdbtyp {
	FBT_V8,		/* Octal value */
	FBT_V10,	/* Decimal value */
	FBT_H8,		/* Two octal halfwords */
	FBT_H10,	/* Two decimal halfwords */
	FBT_FLG,	/* Boolean flag */
	FBT_TAD,	/* T20 TAD value */
	FBT_TOD,	/* T20 TAD value, or interval in days */
	FBT_AUT,	/* Author string - at FDBend */
	FBT_LWR,	/* Last-Writer string - at FDBend+010 */
	FBT_ACT,	/* Account string - in filename */
	FBT_6BT		/* Possible SIXBIT value */
};

#define fedef(nam,typ,wo,bo,bs,tx) {nam,typ,wo,bo,bs,tx}

#define FBLN0 030	/* DUMPER only saves this many actual FDB words */

    /* NOTE!!!! FMTV6 DUMPER writes out 037 FDB words, so all the offsets
    ** need to be changed if reading a format 6 tape...
    */

#define FDBAUT_Woff (WdoffFDB+FBLN0)
#define FDBLWR_Woff (WdoffFDB+FBLN0+010)
#define FDBARCF (FBLN0+010+010)		/* Offset in "DumperFDB" of ARCF blk */
#define FDBARC_Woff (WdoffFDB+FDBARCF)

/* Contents of ARCF block as stored by DUMPER */
#define ARCF_AROFL 0	/* Useless flags to ARCF% */
#define ARCF_ARTP1 1	/* == .FBTP1 */
#define ARCF_ARSF1 2	/* == .FBSS1 */
#define ARCF_ARTP2 3	/* == .FBTP2 */
#define ARCF_ARSF2 4	/* == .FBSS2 */
#define ARCF_ARODT 5	/* == .FBTDT */
#define ARCF_ARPSZ 6	/* == AR%PSZ in .FBBBT */


struct fdbent fdbt[] = {
    fedef(".FBHDR",FBT_H8,  0, 0,36,"Header word"),
    fedef("FB%LEN",FBT_V8,  0,35, 7,"Length of FDB"),
    fedef(".FBCTL",FBT_H8,  1,35,36,"Status bits"),

    fedef("FB%TMP",FBT_FLG, 1, 0, 1,"File is temporary"),
    fedef("FB%PRM",FBT_FLG, 1, 1, 1,"File is permanent"),
    fedef("FB%NEX",FBT_FLG, 1, 2, 1,"File doesn't really exist"),
    fedef("FB%DEL",FBT_FLG, 1, 3, 1,"File is deleted"),
    fedef("FB%NXF",FBT_FLG, 1, 4, 1,"File is not yet closed"),
    fedef("FB%LNG",FBT_FLG, 1, 5, 1,"File is longer than 512 pages"),
    fedef("FB%SHT",FBT_FLG, 1, 6, 1,"DEC-reserved bit"),
    fedef("FB%DIR",FBT_FLG, 1, 7, 1,"File is a directory"),
    fedef("FB%NOD",FBT_FLG, 1, 8, 1,"File is not to be backed up"),
    fedef("FB%BAT",FBT_FLG, 1, 9, 1,"File may have bad pages"),
    fedef("FB%SDR",FBT_FLG, 1,10, 1,"Directory has subdirectories"),
    fedef("FB%ARC",FBT_FLG, 1,11, 1,"File has archive status"),
    fedef("FB%INV",FBT_FLG, 1,12, 1,"File is invisible"),
    fedef("FB%OFF",FBT_FLG, 1,13, 1,"File is offline"),
    fedef("FB%FCF",FBT_V8,  1,17, 4,"File class field"),
    fedef("FB%NDL",FBT_FLG, 1,18, 1,"File is not to be deleted"),
    fedef("FB%WNC",FBT_FLG, 1,19, 1,"File last write not closed"),
    fedef("FB%FOR",FBT_FLG, 1,20, 1,"File has FORTRAN-style LPT chars"),

    fedef(".FBEXL",FBT_V8,  02,35,36,"Link to FDB of next file type"),
    fedef(".FBADR",FBT_H8,  03,35,36,"Disk address of index block"),
    fedef(".FBPRT",FBT_H8,  04,35,36,"File access bits"),
    fedef(".FBCRE",FBT_TAD, 05,35,36,"Time of last write"),
    fedef(".FBAUT",FBT_AUT, 06,35,36,"Author of file"),
    fedef(".FBGEN",FBT_H8,  07,35,36,"Generation and directory numbers"),
    fedef("FB%GEN",FBT_V10, 07,17,18,"Generation number"),
    fedef("FB%DRN",FBT_V8,  07,35,18,"Internal directory number"),
    fedef(".FBACT",FBT_ACT,010,35,36,"Account designator"),
    fedef(".FBBYV",FBT_H8, 011,35,36,"File I/O information"),
    fedef("FB%RET",FBT_V10,011, 5, 6,"Retention count"),
    fedef("FB%BSZ",FBT_V10,011,11, 6,"File byte size"),
    fedef("FB%MOD",FBT_V10,011,17, 4,"Data mode written in"),
    fedef("FB%PGC",FBT_V10,011,35,18,"Page count of file"),
    fedef(".FBSIZ",FBT_V10,012,35,36,"Number of bytes in file"),
    fedef(".FBCRV",FBT_TAD,013,35,36,"Creation time of file"),
    fedef(".FBWRT",FBT_TAD,014,35,36,"Time of last user write"),
    fedef(".FBREF",FBT_TAD,015,35,36,"Time of last nonwrite access"),
    fedef(".FBCNT",FBT_H10,016,35,36,"Count of writes,,references"),
    fedef(".FBBK0",FBT_H8, 017,35,36,"Backup word 1"),
    fedef(".FBBK1",FBT_H8, 020,35,36,"Backup word 2"),
    fedef(".FBBK2",FBT_H8, 021,35,36,"Backup word 3"),
    fedef(".FBBBT",FBT_H8, 022,35,36,"Archive status bits"),
    fedef("AR%RAR",FBT_FLG,022, 1, 1,"User requested archival"),
    fedef("AR%RIV",FBT_FLG,022, 2, 1,"System requested migration"),
    fedef("AR%NDL",FBT_FLG,022, 3, 1,"Don't delete contents"),
    fedef("AR%NAR",FBT_FLG,022, 4, 1,"Resist migration"),
    fedef("AR%EXM",FBT_FLG,022, 5, 1,"File is exempt from migration"),
    fedef("AR%1ST",FBT_FLG,022, 6, 1,"First archive pass in progress"),
    fedef("AR%RFL",FBT_FLG,022, 7, 1,"Restoral failed"),
    fedef("AR%WRN",FBT_FLG,022,10, 1,"File off-line exp date approaching"),
    fedef("AR%RSN",FBT_V8, 022,17, 3,"Offline reason"),
    fedef("AR%PSZ",FBT_V10,022,35,18,"Page count when file went offline"),
    fedef(".FBNET",FBT_TOD,023,35,36,"On-line expiration date"),
    fedef(".FBUSW",FBT_H8, 024,35,36,"User-settable word"),
    fedef(".FBGNL",FBT_H8, 025,35,36,"Address of FDB of next generation"),
    fedef(".FBNAM",FBT_H8, 026,35,36,"Pointer to filename block"),
    fedef(".FBEXT",FBT_H8, 027,35,36,"Pointer to file type block"),
/* FDB words beyond this are not actually saved by DUMPER, which instead
** uses various hacks to store the information in the rest of the block.
*/
    fedef(".FBLWR",FBT_LWR,030,35,36,"Last writer to file"),

#if 0	/* Note .FBFET vanishes completely -- no equiv in DumperFDB. */
    fedef(".FBTDT",FBT_TAD,031,35,36,"Date archived"),
    fedef(".FBFET",FBT_TOD,032,35,36,"Offline expiration date"),
    fedef(".FBTP1",FBT_6BT,033,35,36,"Tape ID for run 1"),
    fedef(".FBSS1",FBT_H10,034,35,36,"Run 1 saveset number,,Tape file number"),
    fedef(".FBTP2",FBT_6BT,035,35,36,"Tape ID for run 2"),
    fedef(".FBSS2",FBT_H10,036,35,36,"Run 2 saveset number,,Tape file number"),
#endif

    fedef(".AROFL",FBT_H8, FDBARCF+ARCF_AROFL,35,36,"ARCF% flags"),
    fedef(".ARTP1",FBT_6BT,FDBARCF+ARCF_ARTP1,35,36,"Archive tape 1 ID"),
    fedef(".ARSF1",FBT_H10,FDBARCF+ARCF_ARSF1,35,36,"Archive tape 1 saveset,,fileno"),
    fedef(".ARTP2",FBT_6BT,FDBARCF+ARCF_ARTP2,35,36,"Archive tape 2 ID"),
    fedef(".ARSF2",FBT_H10,FDBARCF+ARCF_ARSF2,35,36,"Archive tape 2 saveset,,fileno"),
    fedef(".ARODT",FBT_TAD,FDBARCF+ARCF_ARODT,35,36,"Date archived"),
    fedef(".ARPSZ",FBT_V10,FDBARCF+ARCF_ARPSZ,35,36,"File page count")
};
#define nfdbents ((sizeof fdbt)/(sizeof fdbt[0]))


fe_extract(blk, fe)
unsigned char *blk;
struct fdbent *fe;
{
    int woff = WdoffFDB + fe->fe_woff;

    switch (fe->fe_typ) {
	case FBT_V8:	/* Octal value */
	case FBT_V10:	/* Decimal value */
	case FBT_FLG:	/* Boolean flag */
	    fe->fe_val = getfield(blk, woff,
				(fe->fe_boff + 1 - fe->fe_bits),
				fe->fe_bits);
	    break;
	case FBT_H8:	/* Two octal halfwords */
	case FBT_H10:	/* Two decimal halfwords */
	case FBT_6BT:	/* Two SIXBIT halves */
	    fe->fe_lh = getfield(blk, woff, 0, 18);
	    fe->fe_rh = getfield(blk, woff, 18, 18);
	    break;

	case FBT_TOD:	/* T20 TAD value, or interval in days */
	    fe->fe_lh = getfield(blk, woff, 0, 18);
	    if (fe->fe_lh == 0) {		/* If probably not a TAD, */
		fe->fe_rh = getfield(blk, woff, 18, 18);	/* get RH */
		break;
	    }					/* Else drop thru for TAD */
	case FBT_TAD:	/* T20 TAD value */
	    fe->fe_tim = unixtime(blk, woff);
	    break;

	case FBT_AUT:	/* Author string - stored elsewhere in dumped block */
	    fe->fe_lh = getfield(blk, woff, 0, 18);
	    fe->fe_rh = getfield(blk, woff, 18, 18);
	    getstring(blk, festr_aut, FDBAUT_Woff,
				sizeof(festr_aut)-1,STRING);
	    fe->fe_str = festr_aut[0] ? festr_aut : NULL;
	    break;
	case FBT_LWR:	/* Last-Writer string - ditto */
	    fe->fe_lh = getfield(blk, woff, 0, 18);
	    fe->fe_rh = getfield(blk, woff, 18, 18);
	    getstring(blk, festr_lwr, FDBLWR_Woff,
				sizeof(festr_lwr)-1,STRING);
	    fe->fe_str = festr_lwr[0] ? festr_lwr : NULL;
	    break;

	case FBT_ACT:	/* Account string - in filename */
	    fe->fe_lh = getfield(blk, woff, 0, 18);
	    fe->fe_rh = getfield(blk, woff, 18, 18);
	{
	    char *ecp, *cp = festr_attrs;
	    while (cp) {
		if (ecp = strchr(cp, ';'))	/* Tie off string first */
		    *ecp++ = '\0';
		if (*cp++ == 'A')		/* If this is acct string, */
		    break;			/* use it! */
		cp = ecp;			/* Else try to continue */
	    }
	    fe->fe_str = cp;
	}
	    break;
	default:
	    fprintf(stderr, "Internal error - Unknown FBT_ type: %d\n",
			fe->fe_typ);
	    break;
    }
}

fe_show(f, fe)
FILE *f;
struct fdbent *fe;
{
    /* First check to see if value exists, and do nothing if zero */
    switch (fe->fe_typ) {
	case FBT_V8:	/* Octal value */
	case FBT_V10:	/* Decimal value */
	case FBT_FLG:	/* Boolean flag */
	    if (!fe->fe_val) return;
	    break;
	case FBT_H8:	/* Two octal halfwords */
	case FBT_H10:	/* Two decimal halfwords */
	case FBT_6BT:	/* SIXBIT */
	case FBT_TOD:	/* T20 TAD value, or interval in days */
	    if (!fe->fe_lh && !fe->fe_rh) return;
	    break;
	case FBT_TAD:	/* T20 TAD value */
	    if (!fe->fe_tim) return;
	    break;
	case FBT_AUT:	/* Author string - stored elsewhere in dumped block */
	case FBT_LWR:	/* Last-Writer string - ditto */
	case FBT_ACT:	/* Account string - in filename */
	    if (!fe->fe_str) return;
	    break;
	default:
	    fprintf(stderr, "Internal error - Unknown FBT_ type: %d\n",
			fe->fe_typ);
	    return;
    }

    /* Now start printing out the value */
    if (fe->fe_name[0] == '.') {	/* Primary word def */
	fprintf(f, "%2o  %s  -  %s: ", fe->fe_woff, fe->fe_name, fe->fe_show);
    } else {				/* Secondary bit field */
	fprintf(f, "       %s  (%oB%d)  -  %s: ",
		fe->fe_name, masks[fe->fe_bits - 1], fe->fe_boff, fe->fe_show);
    }

    /* Now print the value itself */
    switch (fe->fe_typ) {
	case FBT_V8:  fprintf(f, "%o", fe->fe_val);	break;	/* Octal */
	case FBT_V10: fprintf(f, "%d.", fe->fe_val);	break;	/* Decimal */
	case FBT_FLG: fprintf(f, "%s", fe->fe_val ? "Yes" : "No");/* Boolean */
			break;

	case FBT_H8:	/* Two octal halfwords */
	    if (fe->fe_lh) fprintf(f, "%o,,", fe->fe_lh);
	    fprintf(f, "%o", fe->fe_rh);
	    break;

	case FBT_H10:	/* Two decimal halfwords */
	    if (fe->fe_lh) fprintf(f, "%d.,,", fe->fe_lh);
	    fprintf(f, "%d.", fe->fe_rh);
	    break;

	case FBT_6BT:	/* Two SIXBIT halves */
	    if (fe->fe_lh) fprintf(f, "%o,,", fe->fe_lh);
	    fprintf(f, "%o ==> ", fe->fe_rh);
	    if (hwd6show(f, fe->fe_lh))		/* If LH doesn't end, */
		hwd6show(f, fe->fe_rh);		/* show RH too */
	    break;

	case FBT_TOD:	/* T20 TAD value, or interval in days */
	    if (!fe->fe_lh) {
		fprintf(f, "%d. days", fe->fe_rh);
		break;
	    }
	    /* Fall thru to TAD case */

	case FBT_TAD:	/* Output TAD as "dd-Mon-yy hh:mm:ss" */
	    pftime(f, &fe->fe_tim);
	    break;

	case FBT_AUT:	/* Author string - stored elsewhere in dumped block */
	case FBT_ACT:	/* Account string - in filename */
	    if (fe->fe_lh) fprintf(f, "%o,,", fe->fe_lh);
	    fprintf(f, "%o ", fe->fe_rh);

	case FBT_LWR:	/* Last-Writer string - stored in dumped block. */
	    /* Must skip showing actual FDB value because this is the first
	    ** FDB word not stored by DUMPER.  This and following words are
	    ** instead filled by the strings for FBAUT, FBLWR, and FBARC.
	    */
	    fprintf(f, "==> %s", fe->fe_str ? fe->fe_str : "?");
	    break;
    }
    putc('\n', f);
}

fdb_show(f, blk)
FILE *f;
unsigned char *blk;
{
    struct fdbent *fe;
    int i;

    fprintf(f, "FDB for file %d.%d: %s\n\n", ssno, filenum, festr_fname);
    for (fe = fdbt; fe <= &fdbt[nfdbents-1]; ++fe) {
	fe_extract(blk, fe);
	fe_show(f, fe);
    }
}

hwd6show(f, hwd)
FILE *f;
unsigned long hwd;		/* 18-bit halfword value - 3 SIXBIT chars */
{
    int ch;
    if (ch = (hwd>>12) & 077) {
	fputc(ch + 040, f);
	if (ch = (hwd>>6) & 077) {
	    fputc(ch + 040, f);
	    if (ch = hwd & 077) {
		fputc(ch + 040, f);
		return 1;
	    }
	}
    }
    return 0;
}

pftime(f, atim)
FILE *f;
time_t *atim;
{
    char *cp = ctime(atim)+4;
    fprintf(f, "%.2s-%.3s-%.2s %.8s", cp+4, cp, cp+18, cp+7);
}

doDatablock(block)
char *block;
{
					    /* max is 5 bytes per word */
	static char buf[(512*5)+1];	    /* A page of characters */
	int ct, wct;
	int maxperblock;
	int nout, type;

	if (debug > 10)
		printf("*");
	if (fpFile == NULL)
		return;
					
	maxperblock = 512 * outnbpiw;	/* # output bytes per 512-wd block */

	if (firstblock) type = CONTAINS_BOF;
	else type = 0;

	if (outnbleft > maxperblock)
		ct = maxperblock, wct = 512;
	else {
		ct = outnbleft, wct = outnfw;
		type |= CONTAINS_EOF;
	}

	switch (outmode) {
	case OC_ASCII:
		nout = getstring(block, buf, 6, ct, type);
		fwrite(buf, 1, nout, fpFile);
		break;
	case OC_8BIT:
		getbytes(block, buf, 6, ct);
		fwrite(buf, 1, ct, fpFile);
		break;
	case OC_WORD:	/* No conversion needed */
		fwrite(block + (6*5), 1, ct, fpFile);
		break;
	case OC_PAGE:		/* Cannot do anything for now */
		break;
	}
	if (ferror(fpFile))
		punt(1, "Error writing %s", sunixname);

	firstblock = 0;
	outnbleft -= ct;
	outnfw -= wct;
}

/*ARGSUSED*/
doSaveset(block, contflag)
char *block;
int  contflag;
{
	int ssnoff;
	long hwd;
	char name[140];	/* Size must be mod 5 */
	time_t t;

	if (debug > 10) {
	    printf("\nSaveset header:\n");
	    showblk(stdout, block, 0, 100);
	}
	tapeno = getfield(block, WdoffTapeNum, BtoffTapeNum, BtlenTapeNum);
	ssno = getfield(block, WdoffSaveSetNum, BtoffSaveSetNum,
	    BtlenSaveSetNum);

	/* Find offset to saveset name.  Use same algorithm as DUMPER here.
	*/
	hwd = getfield(block, WdoffSSFormat, 0, 18);	/* Get LH of fmt */
	if (hwd & (0177L<<(18-7))) {
	    tapefmt = 0;		/* Format wd has ASCII - old fmt! */
	    ssnoff = 0;			/* Starts with saveset name */
	} else {
	    /* Else use RH as format number (actually whole wd, but so what) */
	    tapefmt = getfield(block, WdoffSSFormat, 18, 18);
	    ssnoff = getfield(block, WdoffSSNamoff, 18, 18);
	    if (ssnoff >= 512) {	/* Sanity check */
		printf("Saveset header error: name offset of %ld is > 512!\n",
			ssnoff);
		ssnoff = 3;		/* Use usual default instead */
	    }
	}

	getstring(block, name, RecHdrlen+ssnoff, sizeof(name), STRING);
	name[sizeof(name)-1] = '\0';	/* Ensure terminated */
	
	t = unixtime(block, WdoffSSDate);
	if (dodir || verbose) {
		if (showtloc)
			printf("B%ld: ", nblktape);
		printf("Tape %d: %sSaveset %d \"%s\" ",
			tapeno, (contflag ? "Continued " : ""),
			ssno, name);
		pftime(stdout, &t);
		printf("\n");
	}

}

static void folddown(to, from)
register char *to, *from;
{
	for ( ; *from; ++from)
	    *to++ = (isupper(*from) ? tolower(*from) : *from);
	*to = '\0';
}

doFileHeader(block)
char *block;
{
    char *ts;
    static char prt_ar[2] = {'-', 'A'};
    static char prt_inv[2] = {'-', 'I'};
    static char prt_off[2] = {'-', 'O'};

    if (debug > 5)
	printf("File Header block:\n");

    filenum = getfield(block, WdoffFileNum, BtoffFileNum, BtlenFileNum);
    getstring(block, festr_fname, WdoffFLName, sizeof(festr_fname), STRING);
    if (festr_attrs = strchr(festr_fname, ';'))	/* Chop off ;Pprot;Aacct */
	    *festr_attrs++ = 0;
    folddown(topsname, festr_fname);

    fpFile = NULL;

    if ( doallflag ||
	 (patterns && patternmatch()) ||
	 (expression && expmatch()) ||
	 (nselect && fmatch()) ) {
	getfdbinfo(block);
	pageno = getfield(block, WdoffPageNum, BtoffPageNum, BtlenPageNum);

	if (dodir || verbose || fdbflg) {
	    if (fdbflg) {
		printf("\n");
		fdb_show(stdout, block);
		printf("\n");
	    }
	    if (showtloc)
		printf("B%ld: ", nblktape);
	    if (verbose)
		printf("%3d %5d ", ssno, filenum);
	    printf("%c%c%c", prt_ar[archived], prt_off[offline],
	      prt_inv[invisible]);
	    printf(" %4d %8d %2d %06o ", offline ? apgcount : pgcount,
				numbytes, bytesize, tprot);
	    pftime(stdout, &mtime);
	    printf(" %s", topsname);
#if 0 /* KLH: fix this later, currently vars are always zero */
	    if (archived && verbose /* >= 2 */)
		printf(" %x %3d %4d %x %3d %4d", tp1, ss1, tf1, tp2, ss2, tf2);
#endif
	    if (pageno != 0)
		printf(" Split file, part 2");
	}

	if (xflg) {
	    /* Decide bytesize (mode) for file extraction */
	    if (bytesize > 36)
		bytesize = 36;
	    if (bytesize == 0 && numbytes) {
		/* Probably a paged holey file, but if file len set, try 36 */
		bytesize = 36;		/* Cross fingers */
	    }
	    if (!bytesize) {		/* If size still zero, empty file! */
		outnfbpw = 0;		/* # file bytes/wd */
		outnfw = 0;		/* # file words */
	    } else {
		outnfbpw = 36/bytesize;	/* # file bytes/wd */
		outnfw = (numbytes + outnfbpw-1) / outnfbpw; /* # file wds */
	    }
	    outnbpiw = 5;		/* Default output bytes per input wd */

	    if (!bytesize) {
		outmode = OC_PAGE;
		outnbpiw = outnbleft = 0;
	    } else if (wordflg) {
		outmode = OC_WORD;
		outnbpiw = 5;
		outnbleft = outnbpiw * outnfw;
	    } else switch (bytesize) {
		case 7:
		    outmode = OC_ASCII;
		    outnbpiw = 5;
		    outnbleft = numbytes;
		    break;
		case 8:
		    outmode = OC_8BIT;
		    outnbpiw = 4;
		    outnbleft = numbytes;
		    break;
		case 36:
		    if (default_bytesize == 7) {
			outmode = OC_ASCII;
			outnbpiw = 5;
			outnbleft = outnbpiw * outnfw;
			break;
		    } else if (default_bytesize == 8) {
			outmode = OC_8BIT;
			outnbpiw = 4;
			outnbleft = outnbpiw * outnfw;
			break;
		    }
		default:
		    outmode = OC_WORD;
		    outnbpiw = 5;
		    outnbleft = outnbpiw * outnfw;
		    break;
	    }

	    if (outmode != OC_PAGE && !offline) {
		if (pageno != 0) {	/* continued file */
		    long missiwds = pageno * 512;	/* Missing input wds */
		    long missob = missiwds * outnbpiw;	/* Missing out bytes */

		    outnfw -= missiwds;
		    outnbleft -= missob;
		    if (!(dodir || verbose))
			printf("%s: Split file, part 2", topsname);
		    printf(": %ld file words (%ld output bytes) missing.",
					missiwds, missob);
		    if (!(dodir || verbose))
			putchar('\n');
		}
		firstblock = 1;
		fpFile = fopen(unixname(topsname), "w");
		if (fpFile == NULL)
		    punt(1, "Can't open %s for write", sunixname);
		else if (verbose)
		    printf(" Extracted.");
		if (fchmod(fileno(fpFile), t2uprot(tprot) & ~0111) < 0)
		    punt(1, "fchmod on %s", sunixname);
	    } else if (verbose)
		printf(" Skipping -- %s file.",
		  offline ? "offline" : "paged/holey");
	}

	if (dodir || verbose)
	    putchar('\n');
    }
}

/* Return 1 if topsname matches any of the "extraction" strings. */
patternmatch()
{
	register int i;

	for (i = 0; i < numpats; i++)
		if (strstr(topsname, patterns[i]))
			return (1);
	return (0);
}

/* Return 1 if topsname matches the regular expression. */
expmatch()
{
	register int match;

	if (expression) {
		if ((match = re_exec(topsname)) == -1)
			punt(0, "re_exec: internal error on %s", topsname);
		else
			return (match);
	}
	return (0);
}

int widx;

/* Return 1 if current file number matches one selected by arg line. */
fmatch()
{

    while (want[widx].ssnum < ssno)
	widx++;
    if (want[widx].ssnum > ssno)
	return 0;
    while (want[widx].fnum < filenum)
	widx++;
    if (want[widx].fnum > filenum)
	return 0;
    return 1;
}

/*
 * Sets a bunch of global variables to info from the fdb.
 * For some reason the archive tape info is garbage.
 */
getfdbinfo(block)
char *block;
{
	
	mtime = unixtime(block, WdoffFDB_Wrt);
	atime = unixtime(block, WdoffFDB_Ref);

	bytesize = getfield(block, WdoffFDB_BSZ, BtoffFDB_BSZ, BtlenFDB_BSZ);
	numbytes = getfield(block, WdoffFDB_Size, BtoffFDB_Size,BtlenFDB_Size);
	pgcount	 = getfield(block, WdoffFDB_PGC, BtoffFDB_PGC, BtlenFDB_PGC);
	tprot    = getfield(block, WdoffFDB_PRT, BtoffFDB_PRT, BtlenFDB_PRT);

	archived = getfield(block, WdoffFDB_CTL, BtoffFDB_Arc, BtlenFDB_Arc);
	invisible= getfield(block, WdoffFDB_CTL, BtoffFDB_Inv, BtlenFDB_Inv);
	offline  = getfield(block, WdoffFDB_CTL, BtoffFDB_Off, BtlenFDB_Off);
	apgcount = getfield(block, WdoffFDB_PGC_A, BtoffFDB_PGC, BtlenFDB_PGC);
	/* The rest is bogus. */
	tp1 = getfield(block, WdoffFDB_TP1, 0, 36);
	tp2 = getfield(block, WdoffFDB_TP2, 0, 36);
	ss1 = getfield(block, WdoffFDB_SS1, BtoffFDB_SS, BtlenFDB_SS);
	ss2 = getfield(block, WdoffFDB_SS2, BtoffFDB_SS, BtlenFDB_SS);
	tf1 = getfield(block, WdoffFDB_TF1, BtoffFDB_TF, BtlenFDB_TF);
	tf2 = getfield(block, WdoffFDB_TF2, BtoffFDB_TF, BtlenFDB_TF);
}

/*ARGSUSED*/
doFileTrailer(block)
char *block;
{
	if (debug > 10) printf(" File trailer\n");
	if (fpFile != NULL) {
		if (fclose(fpFile) == EOF)
			punt(1, "fclose: write error on %s", sunixname);
		fpFile = NULL;
		utime(sunixname, timep);
		if (outnbleft || outnfw)
			printf("%s: Split file, part 1: %ld file words (%ld output bytes) left\n",
			    topsname, outnfw, outnbleft);
	}
}

/*ARGSUSED*/
doTonext(block)
char *block;
{
    if (dodir || verbose) {
	if (showtloc)
		printf("B%ld: ", nblktape);
	printf("TONEXT record - continued on next tape\n");
    }
}

/*ARGSUSED*/
doTapeTrailer(block)
char *block;
{
    if (dodir || verbose) {
	if (showtloc)
		printf("B%ld: ", nblktape);
	printf("Tape trailer - Saveset End\n");
    }
}


#if READ20_STDC
 void punt(int prterrno, char *fmt, ...)
#else
 void punt(prterrno, fmt, a,b,c,d,e)
int prterrno;		/* TRUE to print errno string */
char *fmt;
#endif
{
    extern int errno, sys_nerr;

    fprintf(stderr, "read20: ");
#if READ20_STDC
    {
	va_list ap;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
    }
#else
    fprintf(stderr, fmt, a,b,c,d,e);
#endif
    if (prterrno) {
	fprintf(stderr, ": ");
	if (errno >= 0 && errno < sys_nerr)
	    perror("");
	else
	    fprintf(stderr, "ERRNO = %d\n", errno);
    }
    else
	fprintf(stderr, "\n");
    exit(1);
}

/*
 * Unpack into buffer 's' the 7 bit string stored in 'block' and
 * append a null char.	Optionally strip CR's from CRLF pairs.	'max'
 * is the max number of 7-bit chars to unpack from 'block', not the
 * max to put into 's' (that's important!).  This only works if
 * getstring() is always called with 'max' mod 5 == 0, except for the
 * last call on "contiguous" blocks.
 * Returns number of chars stored in output buffer.
 */
int
getstring(block, s, wordoff, max, type)
register unsigned char *block;	/* Tape block */
register char *s;	/* Destination string buffer */
int  wordoff;		/* 36-bit offset from start of tape block */
int  max;		/* Max number of characters to xfer into s */
int type;		/* Info about block we are reading (first, last
			   or string) */
{
	register int ct;	/* Number of characters loaded so far */
	register char *op;	/* Output pointer for <CRLF> -> <LF> hacking */
	register int c;
	char *orig = s;		/* Save for debugging */
	unsigned long top4;
	static int state;
#define NORMAL 1
#define LAST_WAS_CR 2

	op = s;

	block += wordoff * 5;

	for (ct = 0; ct < max; ct += 5)
	{
		/* Copy 5 7-bit bytes portably from 8-bit byte frames.
		** This depends on unsigned char being implemented correctly.
		*/
		*op++ =			    block[0] >> 1;
		*op++ = ((block[0] << 6) | (block[1] >> 2)) & 0177;
		*op++ = ((block[1] << 5) | (block[2] >> 3)) & 0177;
		*op++ = ((block[2] << 4) | (block[3] >> 4)) & 0177;
		*op++ = ((block[3] << 3) | ((block[4]&017) >> 1)) & 0177;
		block += 5;
	}

	if (keepcr) return (max);

	op = s;

	if (type & CONTAINS_BOF) state = NORMAL;

	for (ct = 0; ct < max; ct++)
	{
	  c = *s++;

	  switch (state) {
	  case NORMAL:
	    if (c != '\r')
	      *op++ = c;
	    else
	      state = LAST_WAS_CR;

	    continue;

	  case LAST_WAS_CR:
	    if (c == '\n') {
	      *op++ = c;
	      state = NORMAL;
	      continue;
	    }
	    if (c == '\r') {
	      *op++ = c;
	      continue;
	    }
	    *op++ = '\r';
	    *op++ = c;
	    state = NORMAL;
	  }
	}

	if (type & CONTAINS_EOF) {
	  if (state == LAST_WAS_CR)
	    *op++ = '\r';
	}
	if (type & NUL_AT_END) *s++ = 0;

	return (op - orig);
}

/* getbytes: like getstring, but ...
   1) uses 8 bit bytes
   2) doesn't stop on a zero
*/
getbytes(block, s, wordoff, max)
char *block;		/* Tape block */
register char *s;	/* Destination string buffer */
int  wordoff;		/* 36-bit offset from start of tape block */
int  max;		/* Max number of characters to xfer into s */
{
	register int i;		/* Counter for five characters per word */

	int ct = 0;		/* Number of characters loaded so far */
	char *orig = s;		/* Save for debugging */

	block += wordoff * 5;

	for (ct = 0; ct < max; ct += 4)
	{
		(void) memcpy(block, s, 4);
		block += 5;
		s += 4;
	}
}

showblk(f, block, offset, nwds)
register FILE *f;
register unsigned char *block;
{
    register int i;
    register unsigned long lh, rh;
    register int c;
    char str[6];

    str[5] = 0;
    for (i = offset; --nwds >= 0; ++i)
    {
	lh = getfield(block, i,  0, 18);
	rh = getfield(block, i, 18, 18);

	if (!lh && !rh) {
	    fprintf(f, "%4o: 0\n", i);
	    continue;
	}

	/* Convert to word of ASCII */
	c = (lh >> (18- 7)) & 0177;
	str[0] = (isprint(c) ? c : '.');
	c = (lh >> (18-14)) & 0177;
	str[1] = (isprint(c) ? c : '.');
	c = ((lh & 017)<<3) | (rh>>(18-3));
	str[2] = (isprint(c) ? c : '.');
	c = (rh >> (18-10)) & 0177;
	str[3] = (isprint(c) ? c : '.');
	c = (rh >> (18-17)) & 0177;
	str[4] = (isprint(c) ? c : '.');

	fprintf(f, "%4o: %6lo,,%6lo    %s\n", i, lh, rh, str);
    }
}

#define DayBaseDelta 0117213		/* Unix day 0 in Tenex format */

/*
 * This screws up on some of the atime's we see, including, yielding, e.g.
 * Fri Dec 23 23:28:16 1994
 * Fri Dec 23 23:28:16 1994
 * Tue Jan 13 07:57:03 1987
 */
time_t
unixtime(block, wordoff)
char *block;
int  wordoff;
{
    long int t, s;
    double d;

    t = getfield(block, wordoff, 0, 18);	/* LH is day */
    if (t == 0) return(0);			/* 0 means never referenced */

    t -= DayBaseDelta;				/* Switch to unix base */
						/* Now has # days since */
						/* Jan 1, 1970 */
    if (t < 0) {
	  fprintf(stderr, "ERROR - Date earlier than Jan 1,1970!!!\n");
    }

    s = getfield(block, wordoff, 18, 18);	/* RH is fraction of day */

    /* Note that the following calculation must be performed in the order shown
       in order to preserve precision.  It should also be done in double prec.
       floating point to prevent overflows.
    */
    d = ((double)s * (24*60*60))	/* Make it double and pre-multiply */
		/ (1<<18);		/* Then divide by 1<<18 to get secs */
    s = d;				/* Get back into integer form */
    if ((d - s) >= 0.5)			/* Find remainder -- round up? */
	++s;				/* Yup */

    return s + (t*24*60*60);		/* Add day base and return */
}

char *
unixname(name)
char *name;
{
	static FILE *log = NULL;
	register char *t, *p;
	static char lastdir[128];
	struct stat stb;
	int dlen;
	int mask;
	register int newdir = 0;

	if (numflg) {		  /* If numeric filenames */
		if (log == NULL) log = fopen(LOGFILE, "a");
		fprintf(log, "%d is %s\n", number, name);
		sprintf(sunixname, "%d", number++);
		return(sunixname);
	}

	strcpy(sunixname, strchr(name, '<') + 1); /* trim off device */
	t = strrchr(sunixname, '>');		 /* find end of directory */
	*t = '.';
	dlen = t - sunixname;

	if (strncmp(lastdir, sunixname, dlen)) {/* maybe new dir */
	    strncpy(lastdir, sunixname, dlen);	/* remember it */
	    lastdir[dlen] = '\0';		/* Ensure null terminated */
	    newdir = 1;
	}
	for (p = sunixname; p <= t; p++)
	    if (*p == '.' || *p == '/') {
		if (newdir) {
		    *p = '\0';			/* temporarily null it off */
		    if (stat(sunixname, &stb) < 0) {
			mask = umask(2);
			if (mkdir(sunixname, 0777) < 0)
			    punt(1, "mkdir %s failed", sunixname);
			umask(mask);
		    }
		}
		*p = '/';
	    }
	
	for (p = t+1; *p; p++)
	  if (*p == '/') *p = '\\';

	if (!genflg) {
		t = strrchr(sunixname, '.');	/* find last . */
		*t = 0;				/* zap it out */
	}
	return(sunixname);
}

int
t2uprot(prot)
register unsigned prot;
{
    register unsigned tprot, uprot;
    register int tshift;

#ifdef notdef
    if (f->FB_dir) {			/* THIS WON'T WORK! */
	punt(0, "Can't handle directory %s", topsname);
	prot = gtdirprot(_dirnm(jfn));	/* returns 20 fmt protection */
	for (tshift=12, uprot=0; tshift >= 0; tshift -= 6) {
	    tprot = prot >> tshift;	/* pick up next field */
	    uprot <<= 3;
	    if (tprot & DP_rd)
		uprot |= WREAD|WEXEC;	/* world read, world execute */
	    if (tprot & (DP_cn|DP_cf))	/* allow write for either conn. */
		uprot |= WWRITE;	/*   access or add files access */
	}
    }
    else
#endif
    {  /* do it this way so easily modified-- i know it could be faster */
	for (tshift=12, uprot=0; tshift >= 0; tshift -= 6) {
	    tprot = prot >> tshift;
	    uprot <<= 3;
	    uprot |= (tprot >> 3) & 07;		/* just r,w,x */
	}
    }
    return uprot;
}

#ifdef notdef
#define HOUR 3600
#define DAY  (HOUR*24)
#define DAY0 40587     /* number of days between tops20 0 day and Unix 0 day */

#define makeword(l, r)	( ((l) << 18) | (r) )
#define getright(b)	( (b) & 0777777 )
#define getleft(b)	( (b) >> 18 )

/* Convert Tops-20 to Unix time -- curently incomplete due to 32 < 36 bits */
int
_t2utim(t)
unsigned t;
{
    register ticks, rh, secs;

    ticks = t - makeword(DAY0, 0);
    rh = getright(ticks) * DAY;
    secs = rh >> 18;
    if (rh % makeword(1,0) > 0400000)
	secs++;				/* round up */
    return (getleft(ticks) * DAY) + secs;
}
#endif

int
compwant(w1, w2)
register struct want *w1, *w2;
{
    int sdif;

    if (sdif = w1->ssnum - w2->ssnum)
	return sdif;
    return (w1->fnum - w2->fnum);
}

getrec(fd, addr, len)
     int len;
     char *addr;
{
  register int cc, remaining = len;

  while (remaining > 0)
    {
      cc = read(fd, addr, remaining);

/*	fprintf(stderr, "read(0, %x, %d) = %d\n", addr, remaining, cc);*/

      if (cc == remaining) return(len);

      if (cc == 0) return(len - remaining);

      if (cc < 0) {
	fprintf(stderr, "Got an error, errno=%d\n", errno);
	exit(1);
      }

/* Hack hack, cough...	If we are reading from a tape (or a file), then don't
   try to pad the record...
 */
      if (fd != 0) return(cc);

      remaining -= cc;
      addr += cc;
    }
  return(len);
}
