/* * This version of my FLX * runs on VAX/VMS. I converted it * because I think there is an illegal * character in one of the names on the * 68000 compiler source tape. This program * is based on the old one, but has a lot of * the stupid stuff having to do with the * way names were handled fixed. */ #include #include #include #include #include #include #define NBLOCK 512 /* Size of a tape block */ #define NNAME 20 /* # of names to extract */ #define MINREC 14 /* Smallest tape record */ #define TRUE 1 /* Truth */ #define FALSE 0 /* Falsehood */ #define NUL 0x00 /* Null byte */ #define CR 0x0D /* Carriage return */ #define GOOD (SS$_NORMAL) /* Exit status */ #define NASTY (STS$M_INHIB_MSG|SS$_ABORT) /* Exit status */ typedef struct label { /* FLX tape file label */ short l_name0[2]; /* Two radix 50 name words */ short l_type[1]; /* Radix 50 type */ char l_prog; /* Programmer number */ char l_proj; /* Project number */ short l_prot; /* Protection code */ short l_date; /* Date (in some format) */ short l_name1[1]; /* One radix 50 name word */ } LABEL; typedef struct name { /* Decoded name block */ short n_flag; /* Some wildcard flags */ char n_proj; /* Project number */ char n_prog; /* Programmer number */ short n_name[3]; /* 3 radix 50 name words */ short n_type[1]; /* 1 radix 50 file type */ } NAME; #define NWPROJ 0x0001 /* Project number is wild */ #define NWPROG 0x0002 /* Programmer number is wild */ #define NWNAME 0x0004 /* Name is wild */ #define NWTYPE 0x0008 /* Type is wild */ char bbuf[NBLOCK]; /* Block buffer for tape I/O */ char *bbufpt; /* Pointer into above buffer */ NAME name[NNAME]; /* Array of names to extract */ short mtchan; /* Tape I/O channel */ int func = 0; /* Function, initialze to none */ int aflag = FALSE; /* ASCII mode flag */ int vflag = FALSE; /* Verify flag */ char *tape = "MFA0:"; /* Tape drive name */ int estat = GOOD; /* Exit status */ int nname = 0; /* # of entries in NAME table */ int proj = 1; /* Project code */ int prog = 1; /* Programmer code */ main(argc, argv) char *argv[]; { register char *p; register int c; register int i; if (argc < 2) fatal(NULL, "no key"); i = 2; p = argv[1]; while ((c = *p++) != 0) { switch (c) { case 'c': case 't': case 'x': if (func != 0) fatal(NULL, "two things at once"); func = c; break; case 'm': if (i >= argc) fatal(NULL, "missing tape name"); tape = argv[i++]; break; case 'u': if (i >= argc) fatal(NULL, "missing ppn"); projprog(argv[i++]); break; case 'v': vflag = TRUE; break; case 'a': aflag = TRUE; break; default: fatal(NULL, "bad key"); } } switch (func) { case 'c': addfile(i, argc, argv); break; case 't': table(); break; case 'x': getfile(i, argc, argv); break; default: fatal(NULL, "no key"); } exit(estat); } /* * The argument `cp' is a pointer * to a string of the form `[nnn,nnn]'; * i.e. a project-programmer number. * Reset the external variables `proj' * and `prog' to agree with the arg. * Quit if the format is bad. Project * and programmer numbers are in octal. */ projprog(cp) register char *cp; { register int c; if (*cp++ != '[') fatal(NULL, "bad ppn"); proj = 0; while ((c = *cp++)>='0' && c<='7') proj = 8*proj + c - '0'; if (c != ',') fatal(NULL, "bad ppn"); prog = 0; while ((c = *cp++)>='0' && c<='7') prog = 8*prog + c - '0'; if (c != ']') fatal(NULL, "bad ppn"); } /* * Process the table command. * Read through the FLX tape, printing out * the information in the headers. The data * blocks are counted as they are read, just * in case this is `tv'. Quit on the double * tapemark at the end. */ table() { register int n; register int nblocks; LABEL lb; opentape(FALSE, TRUE); while (readtape((char *)&lb, sizeof(lb)) != 0) { nblocks = 0; while (readtape(bbuf, sizeof(bbuf)) != 0) ++nblocks; n = printname(&lb); if (vflag != FALSE) { while (n < 14) { putchar(' '); ++n; } printf("%5d", nblocks); printf(" [%03o,%03o]", lb.l_proj&0xFF, lb.l_prog&0xFF); printdate(lb.l_date); printf(" <%03o>", lb.l_prot&0xFF); } putchar('\n'); } closetape(FALSE); } /* * Given a pointer to a structure * containing the FLX file label record, * convert the name from RADIX 50 to * ASCII and write it to the standard output. * The number of characters written out is * returned, just in case this is `tv'. */ printname(lbp) LABEL *lbp; { register char *p; register int c; register int n; char b[9]; r50toa(&b[0], lbp->l_name0, 2); r50toa(&b[6], lbp->l_name1, 1); n = 0; p = &b[0]; while (p<&b[9] && (c = *p++)!=' ') { putchar(c); ++n; } if (lbp->l_type[0] != 0) { putchar('.'); ++n; r50toa(&b[0], lbp->l_type, 1); p = &b[0]; while (p<&b[3] && (c = *p++)!=' ') { putchar(c); ++n; } } return (n); } /* * Convert `nr50' words worth of * RADIX 50 data, pointed to by the argument * `r50p', into `3*nr50' bytes of Ascii and * store the characters into the buffer * pointed to by the `cp' argument. The output * string is in lower case. The illegal code * in RADIX 50 is converted to a `?'. */ r50toa(cp, r50p, nr50) register char *cp; register short *r50p; { register long r50; register int acx; static char ctable[] = { " abcdefghijklmnopqrstuvwxyz$.?0123456789" }; while (nr50--) { r50 = *r50p++; r50 &= 0xFFFF; acx = 0; while (r50 >= 03100) { r50 -= 03100; ++acx; } *cp++ = ctable[acx]; acx = 0; while (r50 >= 050) { r50 -= 050; ++acx; } *cp++ = ctable[acx]; *cp++ = ctable[r50]; } } /* * Take a date word, convert the * date to a human being format and print * it on the standard output. The date word * is encoded as `1000*(year-1970)+daynum', * where January 1 is day number 1. */ printdate(date) { register int month; register int day; register int year; static char monlen[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static char *monname[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; year = (date/1000) + 1970; day = date%1000; monlen[1] = ((year%4)==0 && (year%100)!=0) ? 29 : 28; month = 0; while (day > 31) day -= monlen[month++]; printf(" %02d-%s-%02d", day, monname[month], year-1900); } /* * Extract files from the * tape. Read through the tape, looking * for files mentioned in the list of * names. Copy the bytes to the output file * on the VAX. If ASCII mode, delete any * NUL bytes and any CR bytes (the ASCII file * will have LF characters in it, which look * like newlines to the C I/O package!). */ getfile(i, argc, argv) char *argv[]; { register char *p; register int c; register int n; register int fd; register FILE *ofp; LABEL lb; char ofn[40]; while (i < argc) savename(argv[i++]); if (nname == 0) savename("[*,*]*.*"); opentape(FALSE, TRUE); while (readtape((char *)&lb, sizeof(lb)) != 0) { ofp = NULL; if (named(&lb) != FALSE) { if (vflag != FALSE) { makename(ofn, &lb, TRUE); printf("Extract: %s\n", ofn); } makename(ofn, &lb, FALSE); if ((fd=creat(ofn, 0666, "rat=cr", "rfm=var")) < 0 || (ofp=fdopen(fd, "w")) == NULL) fatal(ofn, "cannot create output file"); } else if (vflag != FALSE) { makename(ofn, &lb, TRUE); printf("Skipped: %s\n", ofn); } while ((n=readtape(bbuf, sizeof(bbuf))) != 0) { if (ofp != NULL) { for (p = &bbuf[0]; n--; ) { c = (*p++) & 0xFF; if (aflag==FALSE || (c!=NUL && c!=CR)) putc(c, ofp); } } } if (ofp != NULL) fclose(ofp); } closetape(FALSE); } /* * Parse a file name. Save * the parsed name, with the appropriate * wildcard flags, into the external array of * names. If the name array isn't latge enough, * or if the name is bad, abort. */ savename(ifn) char *ifn; { register char *cp1; register char *cp2; register NAME *np; register int c; char b[9]; if (nname >= NNAME) fatal(NULL, "too many names"); np = &name[nname++]; np->n_flag = 0; np->n_proj = proj; np->n_prog = prog; np->n_type[0] = 0; cp1 = ifn; if (*cp1 == '[') { ++cp1; if ((c = *cp1++) == '*') { np->n_flag |= NWPROJ; c = *cp1++; } else { np->n_proj = 0; while (c>='0' && c<='7') { np->n_proj = 8*np->n_proj + c - '0'; c = *cp1++; } } if (c != ',') fatal(ifn, "bad ppn"); if ((c = *cp1++) == '*') { np->n_flag |= NWPROG; c = *cp1++; } else { np->n_prog = 0; while (c>='0' && c<='7') { np->n_prog = 8*np->n_prog + c - '0'; c = *cp1++; } } if (c != ']') fatal(ifn, "bad ppn"); } cp2 = &b[0]; while ((c = *cp1++)!='\0' && c!='.') { if (cp2 < &b[9]) *cp2++ = c; } if (b[0]=='*' && cp2==&b[1]) np->n_flag |= NWNAME; else { while (cp2 < &b[9]) *cp2++ = ' '; ator50(&np->n_name[0], 3, &b[0]); } if (c == '.') { cp2 = &b[0]; while ((c = *cp1++) != '\0') { if (cp2 < &b[3]) *cp2++ = c; } if (b[0]=='*' && cp2==&b[1]) np->n_flag |= NWTYPE; else { while (cp2 < &b[3]) *cp2++ = ' '; ator50(&np->n_type[0], 1, &b[0]); } } } /* * Look through the list of * saved names and return true if * the name in the LABEL structure * pointed to by "lp" is there. */ named(lp) register LABEL *lp; { register NAME *np; for (np = &name[0]; np < &name[nname]; ++np) { if ((np->n_flag&NWPROJ)==0 && np->n_proj!=lp->l_proj) continue; if ((np->n_flag&NWPROG)==0 && np->n_prog!=lp->l_prog) continue; if ((np->n_flag&NWNAME) == 0) { if (np->n_name[0] != lp->l_name0[0]) continue; if (np->n_name[1] != lp->l_name0[1]) continue; if (np->n_name[2] != lp->l_name1[0]) continue; } if ((np->n_flag&NWTYPE)==0 && np->n_type[0]!=lp->l_type[0]) continue; return (TRUE); } return (FALSE); } /* * Take a LABEL read from the * tape, and handcraft a file name * string. Store the name in the * supplied buffer. If the "ppnflag" * is TRUE, expand the ppn. */ makename(cp, lp, ppnflag) register char *cp; register LABEL *lp; { register char *bp; register int c; char b[9]; if (ppnflag != FALSE) { sprintf(cp, "[%03o,%03o]", lp->l_proj&0xFF, lp->l_prog&0xFF); while (*cp != '\0') ++cp; } r50toa(&b[0], lp->l_name0, 2); r50toa(&b[6], lp->l_name1, 1); bp = &b[0]; while (bp<&b[9] && (c = *bp++)!=' ') *cp++ = c; if (lp->l_type[0] != 0) { *cp++ = '.'; r50toa(&b[0], lp->l_type, 1); bp = &b[0]; while (bp<&b[3] && (c = *bp++)!=' ') *cp++ = c; } *cp = '\0'; } /* * Append files to the * tape. The tape is assumed to be * positioned correctly. This lets you * mount a tape, rewind it, and then * append files onto it. */ addfile(i, argc, argv) char *argv[]; { register FILE *ifp; register int c; register int nbytes; register char *ifn; LABEL lb; if (i == argc) fatal(NULL, "no files"); while (i < argc) { ifn = argv[i++]; if ((ifp=fopen(ifn, "r")) == NULL) fatal(ifn, "cannot open file"); if (vflag != FALSE) printf("Creating: %s\n", ifn); opentape(TRUE, FALSE); makelabel(&lb, ifn); writetape((char *)&lb, sizeof(lb)); nbytes = 0; bbufpt = &bbuf[0]; while ((c=getc(ifp)) != EOF) { if (aflag!=FALSE && c=='\n') { byte(CR); ++nbytes; } byte(c); ++nbytes; } if ((nbytes&0x01) != 0) byte(NUL); flushtape(); closetape(TRUE); fclose(ifp); } } /* * Store a byte into the * tape buffer. If there is no * room in the buffer flush the data * out to the tape. */ byte(b) register int b; { if (bbufpt >= &bbuf[NBLOCK]) { flushtape(); bbufpt = &bbuf[0]; } *bbufpt++ = b; } /* * Write any data still in the * block buffer to the tape. Avoid writing * out short records; the tape drive is allowed * to throw them away. Short records are padded * out with null bytes. */ flushtape() { register int n; if ((n=bbufpt-&bbuf[0]) != 0) { while (n < MINREC) { *bbufpt++ = 0; ++n; } writetape(bbuf, n); } } /* * Format a FLX tape header * record, given the file name. The * date is set to todays date. The access * mode is always set to "233". */ makelabel(lp, ifn) LABEL *lp; char *ifn; { register char *cp2; register char *cp3; register c; long tod; struct tm *tmp; struct tm *localtime(); char b[12]; cp2 = ifn; while (*cp2 != 0) ++cp2; while (cp2!=ifn && cp2[-1]!=':' && cp2[-1]!=']') --cp2; cp3 = &b[0]; while ((c = *cp2++)!=0 && c!='.') { if (cp3 < &b[9]) *cp3++ = c; } while (cp3 < &b[9]) *cp3++ = ' '; if (c == '.') { while ((c = *cp2++) != 0) { if (cp3 < &b[12]) *cp3++ = c; } } while (cp3 < &b[12]) *cp3++ = ' '; ator50(lp->l_name0, 2, &b[0]); ator50(lp->l_name1, 1, &b[6]); ator50(lp->l_type, 1, &b[9]); time(&tod); tmp = localtime(&tod); lp->l_date = 1000*(tmp->tm_year-70) + tmp->tm_yday + 1; lp->l_proj = proj; lp->l_prog = prog; lp->l_prot = 0233; } /* * Convert ASCII to radix 50. * The "r50p" points to an array of * 16 bit words that get the radix 50 * encoded name. The "cp" points to a * block of ASCII data. The ASCII data * must be 3*nr50 bytes long. */ ator50(r50p, nr50, cp) register short *r50p; register char *cp; { register int r50; while (nr50--) { r50 = 050*050*r50char(cp[0]); r50 += 050*r50char(cp[1]); r50 += r50char(cp[2]); cp += 3; *r50p++ = r50; } } /* * Convert character "c" to * its radix 50 encoding. All illegal * characters get mapped to "x". Earlier * versions of this routine mapped all * illegal characters to 035, which * was unused in the normal radix 50 * encoding. If you do this, then standard * FLX on the VAX won't read the tape. */ r50char(c) register int c; { if (c == ' ') return (0); if (c>='A' && c<='Z') return (c - 'A' + 01); if (c>='a' && c<='z') return (c - 'a' + 01); if (c == '$') return (033); if (c == '.') return (034); if (c>='0' && c<='9') return (c - '0' + 036); return ('X' - 'A' + 01); } /* * Open up the tape. * Translate the logical name and * assign the channel. The two flags * indicate what type of operation is * going to be performed. The "wflag" * flag is true if you are going to try * and write on the tape. The "rflag" is * true if you want the tape rewound. */ opentape(wflag, rflag) { struct dsc$descriptor idsc; struct dsc$descriptor odsc; char oname[40]; int iosb[2]; int status; odsc.dsc$a_pointer = tape; odsc.dsc$w_length = strlen(tape); odsc.dsc$b_dtype = DSC$K_DTYPE_T; odsc.dsc$b_class = DSC$K_CLASS_S; idsc.dsc$b_dtype = DSC$K_DTYPE_T; idsc.dsc$b_class = DSC$K_CLASS_S; do { idsc.dsc$a_pointer = odsc.dsc$a_pointer; idsc.dsc$w_length = odsc.dsc$w_length; odsc.dsc$a_pointer = &oname[0]; odsc.dsc$w_length = sizeof(oname); status = LIB$SYS_TRNLOG(&idsc, &odsc.dsc$w_length, &odsc); if (status!=SS$_NORMAL && status!=SS$_NOTRAN) check(status, "translate name"); if (oname[0] == 0x1B) { odsc.dsc$a_pointer += 4; /* Skip over */ odsc.dsc$w_length -= 4; /* the kludge */ } } while (status == SS$_NORMAL); status = SYS$ASSIGN(&odsc, &mtchan, 0, 0); check(status, "assign of tape drive"); /* Check if tape? */ /* Check write enable if wflag */ if (rflag != FALSE) mtqiow("rewind", IO$_REWIND, iosb, 0, 0); } /* * Close the tape. * If the "wflag" is true write a * double tapemark, and backspace to * between them. */ closetape(wflag) { int status; int iosb[2]; if (wflag != FALSE) { mtqiow("write EOF #1", IO$_WRITEOF, iosb, 0, 0); mtqiow("write EOF #2", IO$_WRITEOF, iosb, 0, 0); mtqiow("backspace", IO$_SKIPRECORD, iosb, 0x0000FFFF, 0); } status = SYS$DASSGN(mtchan); check(status, "deassign of tape drive"); } /* * Read from the tape. * The address of the buffer, and * the size of the buffer is supplied. * Return the number of bytes read. * Return 0 if a tape mark is encountered. * Return -1 on any errors. The details * of the error are tossed away. */ readtape(bufp, nbuf) char *bufp; { int status; int iosb[2]; status = mtqiow(NULL, IO$_READVBLK, iosb, bufp, nbuf); if (status == SS$_NORMAL) return ((iosb[0]>>16) & 0xFFFF); if (status == SS$_ENDOFFILE) return (0); check(status, "read"); } /* * Write a block to the tape. * All errors are fatal, as there is * not a lot you can do. */ writetape(bufp, nbuf) char *bufp; { int status; int iosb[2]; status = mtqiow(NULL, IO$_WRITEVBLK, iosb, bufp, nbuf); if (status == SS$_NORMAL) status = iosb[0]&0xFFFF; if (status!=SS$_NORMAL || ((iosb[0]>>16)&0xFFFF)!=nbuf) check(status, "write"); } /* * This routine issues * general I/O commands to the * tape. It is just an interface layer * that defaults a number of the * parameters. If the "msg" is not null * it quits on errors. */ mtqiow(msg, fn, iosb, p1, p2) char *msg; int iosb[]; { int status; status = SYS$QIOW(0, mtchan, fn, iosb, 0, 0, p1, p2, 0, 0, 0, 0); if (status == SS$_NORMAL) status = iosb[0]&0xFFFF; if (msg!=NULL && status!=SS$_NORMAL) check(status, msg); return (status); } /* * This routine puts out * fatal error message for just * about everyone. The first argument * may be NULL; it is an optional * file name. */ fatal(p1, p2) char *p1; char *p2; { fprintf(stderr, "flx: "); if (p1 != NULL) fprintf(stderr, "%s: ", p1); fprintf(stderr, "%s\n", p2); exit(NASTY); } /* * Check an I/O status. * If good, just return. Otherwise * ask the system for the error message * text, print it, and die. */ check(status, msg) char *msg; { struct dsc$descriptor odsc; char buf[256]; if (status != SS$_NORMAL) { odsc.dsc$a_pointer = buf; odsc.dsc$w_length = sizeof(buf); odsc.dsc$b_dtype = DSC$K_DTYPE_T; odsc.dsc$b_class = DSC$K_CLASS_S; status = SYS$GETMSG(status, &odsc.dsc$w_length, &odsc, 1, 0); if (status != SS$_NORMAL) exit(status); buf[odsc.dsc$w_length] = 0; fatal(msg, buf); } }