/* * 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 #include #include #include /* For ctime */ #include /* For malloc */ #include /* For strchr, strstr, memcpy, etc */ #include #include #include #if READ20_STDC # include /* 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 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 Only process saveset \n\ -e Only process filenames matching (one -e only)\n\ -F ... Only process files numbered ...\n\ -V Show FDB info for files extracted or listed\n\ -s 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 Use numeric filenames for extracts, starting with \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 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 -> 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); }