dick@tjalk.UUCP (Dick Grune) (12/23/84)
This is a reposting of my magtape handling package; the previous one seems to have lost its tail somewhere. I personally don't think 90000 characters to be excessively much for a file, but some uucp's seem to disagree. Well, anyway, this is part 1 of 2. Hope for better luck this time. -------------- Reposting: The following magtape handling package will read and write ANSI standard labelled tapes in various formats, copy tapes even when you have only one tape unit, survey odd tapes, read Cyber NOS system format tapes and do some other useful things. It has been used extensively on a PDP11/45 and on a VAX 750, UNIX V7 and 4.1BSD. The following sharchive (excusez le mot) contains, among the usual files, one file which is a demonstration tape image; it is pure ASCII96, and thus it does not contain newlines. I do not know how well it will survive the various uucp's on its journey, but it is the last file in the sharchive and it is not essential to the package. Dick Grune Vrije Universiteit de Boelelaan 1081 1081 HV Amsterdam the Netherlands ... and my name isn't Richard! : ------------------------------------------ cut here and feed to /bin/sh echo x \R\E\A\D\_\M\E cat >\R\E\A\D\_\M\E <<'<<>EndOfFile<>>' SALES TALK If you have one or more magtape units and want to do more with them than just run `tar', this is for you. On the `shell' level this package offers programs for - getting a quick look at a tape, - making exact copies of tapes even if you've got only one magtape unit, - extracting arbitrary portions from tapes and - the reading and writing of ANSI standard labelled tapes. All these programs work equally well on real tapes and on tape images on disk. On the C-level it supplies routines for handling real tapes and tape images on disk as a unified concept (`generalized magtape'). INSTALLATION Take a look at the 4 small files tploc.h tprdloc.c tpwloc.c tpwtmloc.c and decide if they look reasonable on your system. If not, change them or use the files `tp*LOC.?' which come from the PDP11/45. Then call `make all' and the shell commands as described in mag(I) will appear. To install the shell commands, change the macro USR (and possibly BIN, LIB, and INC) in the `makefile' and do `make install'. To install the C-routine library `libt.a', do `make crout'. Now do ansir -fp test.image to get a feel for what it does. (Then do it a second time!). CYBER If you happen to have a Control Data Cyber around, running SCOPE or NOS/BE, call `make NOS' to get `NOSsplit', a program for reading Cyber SI-format tapes, and `NOStr' which converts from various Cyber character codes. <<>EndOfFile<>> if test "33765 2" != "`sum \R\E\A\D\_\M\E`" then echo Checksum error; fi echo x \M\a\k\e\f\i\l\e cat >\M\a\k\e\f\i\l\e <<'<<>EndOfFile<>>' # A system for handling magtapes, real or in tape image form. # Copyright Dick Grune # make all: make all the shell commands # make install: install all the shell commands # make crout: install C-routine library # make NOS: install NOSsplit et al. for reading Cyber tapes # make man: install the manuals # make lint: `lint' everything # make clean: clean up *.o # USR=/user1/dick BIN=$(USR)/bin LIB=$(USR)/lib INC=$(USR)/src MAN1=/user1/dick/man MAN3=/user1/dick/man OWN=dick GRP=staff CFLAGS=-s -O all: survey rawtp cptp ansir ansiw NOSsplit NOStr install: survey rawtp cptp ansir ansiw mv survey $(BIN)/survey - /etc/chown $(OWN) $(BIN)/survey - /etc/chgrp $(GRP) $(BIN)/survey mv rawtp $(BIN)/rawtp - /etc/chown $(OWN) $(BIN)/rawtp - /etc/chgrp $(GRP) $(BIN)/rawtp mv cptp $(BIN)/cptp - /etc/chown $(OWN) $(BIN)/cptp - /etc/chgrp $(GRP) $(BIN)/cptp mv ansir $(BIN)/ansir - /etc/chown $(OWN) $(BIN)/ansir - /etc/chgrp $(GRP) $(BIN)/ansir mv ansiw $(BIN)/ansiw - /etc/chown $(OWN) $(BIN)/ansiw - /etc/chgrp $(GRP) $(BIN)/ansiw crout: tp.h libt.a cp tp.h $(INC)/tp.h - /etc/chown $(OWN) $(INC)/tp.h - /etc/chgrp $(GRP) $(INC)/tp.h mv libt.a $(LIB)/libt.a - /etc/chown $(OWN) $(LIB)/libt.a - /etc/chgrp $(GRP) $(LIB)/libt.a NOS: NOSsplit NOStr mv NOSsplit $(BIN)/NOSsplit - /etc/chown $(OWN) $(BIN)/NOSsplit - /etc/chgrp $(GRP) $(BIN)/NOSsplit mv NOStr $(BIN)/NOStr - /etc/chown $(OWN) $(BIN)/NOStr - /etc/chgrp $(GRP) $(BIN)/NOStr ansir: ansir.o libt.a $(CC) $(CFLAGS) ansir.o libt.a -o ansir ansiw: ansiw.o libt.a $(CC) $(CFLAGS) ansiw.o libt.a -o ansiw ansir.o ansiw.o: ansi.h cptp: cptp.o libt.a $(CC) $(CFLAGS) cptp.o libt.a -o cptp rawtp: rawtp.o libt.a $(CC) $(CFLAGS) rawtp.o libt.a -o rawtp survey: survey.o libt.a $(CC) $(CFLAGS) survey.o libt.a -o survey survey.o rawtp.o cptp.o ansir.o ansiw.o: tp.h tploc.h NOSsplit: NOSsplit.o libt.a $(CC) $(CFLAGS) NOSsplit.o libt.a -o NOSsplit NOSsplit.o: tp.h tploc.h NOStr: NOStr.o $(CC) $(CFLAGS) NOStr.o -o NOStr NOStr.o: tp.h OBJ=tpread.o tpwrite.o tpopen.o tpclose.o tpname.o tpwtmloc.o \ etoa.o tprdloc.o tpwloc.o $(OBJ): tp.h tploc.h libt.a: $(OBJ) ar cr libt.a $(OBJ) ranlib libt.a man: cp NOSsplit.1 $(MAN1)/NOSsplit.1 - /etc/chown $(OWN) $(MAN1)/NOSsplit.1 - /etc/chgrp $(GRP) $(MAN1)/NOSsplit.1 cp NOStr.1 $(MAN1)/NOStr.1 - /etc/chown $(OWN) $(MAN1)/NOStr.1 - /etc/chgrp $(GRP) $(MAN1)/NOStr.1 cp ansir.1 $(MAN1)/ansir.1 - /etc/chown $(OWN) $(MAN1)/ansir.1 - /etc/chgrp $(GRP) $(MAN1)/ansir.1 cp ansiw.1 $(MAN1)/ansiw.1 - /etc/chown $(OWN) $(MAN1)/ansiw.1 - /etc/chgrp $(GRP) $(MAN1)/ansiw.1 cp cptp.1 $(MAN1)/cptp.1 - /etc/chown $(OWN) $(MAN1)/cptp.1 - /etc/chgrp $(GRP) $(MAN1)/cptp.1 cp mag.1 $(MAN1)/mag.1 - /etc/chown $(OWN) $(MAN1)/mag.1 - /etc/chgrp $(GRP) $(MAN1)/mag.1 cp mag.3 $(MAN3)/mag.3 - /etc/chown $(OWN) $(MAN1)/mag.3 - /etc/chgrp $(GRP) $(MAN1)/mag.3 cp rawtp.1 $(MAN1)/rawtp.1 - /etc/chown $(OWN) $(MAN1)/rawtp.1 - /etc/chgrp $(GRP) $(MAN1)/rawtp.1 cp survey.1 $(MAN1)/survey.1 - /etc/chown $(OWN) $(MAN1)/survey.1 - /etc/chgrp $(GRP) $(MAN1)/survey.1 lint: lint survey.c llib-lmag lint rawtp.c llib-lmag lint cptp.c llib-lmag lint ansir.c llib-lmag lint ansiw.c llib-lmag lint NOSsplit.c llib-lmag lint NOStr.c llib-lmag clean: rm *.o <<>EndOfFile<>> if test "39833 4" != "`sum \M\a\k\e\f\i\l\e`" then echo Checksum error; fi echo x \N\O\S\s\p\l\i\t\.\1 cat >\N\O\S\s\p\l\i\t\.\1 <<'<<>EndOfFile<>>' .TH NOSSPLIT I .SH NAME NOSsplit \- split Cyber system tape .SH SYNOPSIS .B NOSsplit [ .B \-s N ] [ name ] .SH DESCRIPTION .I NOSsplit reads a Control Data Cyber system tape (SI-format) as written under NOS/BE or SCOPE by COPY, COPYBF or COPYBR. Each Cyber record is written to a separate file. The EOR-levels appear on standard output. .PP A Cyber record consists of an integral number of Cyber words of 60 bits each, whereas a UNIX file consists of an integral number of bytes of 8 bits each; so, if the Cyber record has an odd number of words, a bit-to-bit mapping won't work. Therefore, a mapping is used in which each Cyber machine-word (60 bits) maps onto 10 characters, each containing 6 bits (again 60 bits). Such a 6-bit file can be processed by .RI NOStr (I), which can do translation from DISPLAY code and Z-type record recognition. .PP The file names are .IR name 00000, .IR name 00001, etc. The default .I name is .BR x . .PP The program accepts the usual .B \-cfhlm parameters to describe the tape (see .IR mag (I)). .PP When the .B \-s option is given, the first .I N records are skipped. .SH SEE ALSO mag(I), NOStr(I) .SH BUGS There is no .I NOScombine to put Humpty Dumpty together again. <<>EndOfFile<>> if test "06156 2" != "`sum \N\O\S\s\p\l\i\t\.\1`" then echo Checksum error; fi echo x \N\O\S\s\p\l\i\t\.\c cat >\N\O\S\s\p\l\i\t\.\c <<'<<>EndOfFile<>>' #define MSGUSE "Usage is: NOSsplit [-cfhlm[s N]] [out_name]\n" #include <stdio.h> #include "tp.h" extern char *sprintf(); /* * Name: NOSsplit, split Cyber system tape (data format is SI) * Author: Dick Grune * Version: 820314 */ #define BSIZE 3840 /* blocksize binary files */ #define CSIZE 960 /* blocksize coded files */ #define SIZE 3840 /* maximum blocksize */ #define EORM "\055\023\035\052\027\054\000" #define EORL 7 #define EOS '\0' #define lastblock(n) ((n)!=BSIZE && (n)!=CSIZE) #define cybln(n) ((n)*8/60*10) int fnumber = 0; char *ofil = "x"; char fname[128]; FILE *outf; char buff[SIZE]; int size; /* number of chars in `buff' */ int bpos; /* number of bits consumed by `get6bits' */ int bstat; int unit = 0; int skipf = 0; /* number of logical records to skip (forwards) */ char *nmdns = TP_DENN; TPFILE *tf; extern FILE *tperr; char *progname; main(argc, argv) char *argv[]; { progname = argv[0]; argc--; argv++; if (argc > 0) { if (argv[0][0] == '-') { char *pp = argv[0]; while (*++pp) switch (*pp) { case 'c': unit = TP_CDEV; if (argc < 2) goto Lbad; nmdns = argv[1]; argc--; argv++; break; case 'f': unit = TP_IMAG; if (argc < 2) goto Lbad; nmdns = argv[1]; argc--; argv++; break; case 'h': nmdns = TP_DENH; break; case 'l': nmdns = TP_DENL; break; case 'm': unit = *++pp - '0'; break; case 's': if (argc < 2) goto Lbad; if (sscanf(argv[1], "%d", &skipf) != 1) goto Lbad; argc--; argv++; break; default: goto Lbad; } argc--; argv++; } } if (argc == 1) { ofil = argv[0]; argc--; argv++; } if (argc != 0) goto Lbad; read_tape(); return 0; Lbad: fprintf(stderr, MSGUSE); return 1; } read_tape() { int i; int bcnt; tf = tpopen(unit, nmdns, "rx"); tperr = stdout; fillbuff(); if (size == 80 && strncmp(buff, "VOL1", 4) == 0) { printf("This is a labelled tape\n"); printf("For label information use `ansir -p'\n"); while (size > 0) fillbuff(); if (size == 0) fillbuff(); } else printf("This is a non-labelled tape\n"); for (i = 0; i < skipf; i++) { if (size <= 0) { printf("%d record%s missing\n", english(skipf-i)); exit(1); } fnumber++; while (!lastblock(size)) fillbuff(); fillbuff(); } if (skipf > 0) printf("%d logical record%s skipped\n", english(skipf)); while (size > 0) { newcreat(fnumber++); bcnt = 1; while (putbuff(cybln(size)), !lastblock(size)) { fillbuff(); bcnt++; } proc_eor(bcnt); VOID(fclose(outf)); fillbuff(); } printf("%d record%s retrieved\n", english(fnumber-skipf)); } newcreat(fn) { int i; for(i=0; ofil[i] != EOS; i++) fname[i] = ofil[i]; VOID(sprintf(&fname[i], "%04d", fn)); outf = fopen(fname, "w"); if (outf == NULL) { printf("%s: cannot create `%s'\n", progname, fname); exit(1); } } fillbuff() { do size = tpread(tf, buff, sizeof buff); while (size > 0 && size < 6 /* noise record */); bpos = bstat = 0; } putbuff(n) int n; /* number of 6bit chars */ { while (n-- > 0) putc(get6bits(), outf); } proc_eor(bcnt) { char eor[EORL]; int i; for (i = 0; i < EORL; i++) eor[i] = get6bits(); printf("%s: ", fname); printf("%d block%s, ", english(bcnt)); if (strncmp(eor, EORM, EORL) != 0) printf("no proper EOR\n"); else printf("EOR%2o\n", get6bits()); } #define left(c,n) (((c)&((077<<(8-(n)))&0377))>>(8-(n))) #define right(c,n) (((c)&((077>>(6-(n)))&0377))<<(6-(n))) /* * These forms are constructed through program transformations; the * author cannot, by any stretch of imagination, guess why they work. */ int get6bits() { int res = 0; switch (bstat++) { case 0: res = left(buff[bpos+0], 6); break; case 1: res = right(buff[bpos+0], 2) + left(buff[bpos+1], 4); break; case 2: res = right(buff[bpos+1], 4) + left(buff[bpos+2], 2); break; case 3: res = right(buff[bpos+2], 6); break; } if (bstat == 4) { bpos += 3; bstat = 0; } return res; } <<>EndOfFile<>> if test "34353 4" != "`sum \N\O\S\s\p\l\i\t\.\c`" then echo Checksum error; fi echo x \N\O\S\t\r\.\1 cat >\N\O\S\t\r\.\1 <<'<<>EndOfFile<>>' .TH NOSTR I .SH NAME NOStr \- translate Cyber 6-bits files .SH SYNOPSIS .BR "NOStr -" C [ file ] .SH DESCRIPTION .I NOStr accepts as input a 6-bits Cyber file, as created, e.g., by .IR NOSsplit (I). Such a file contains a mapping of an integral number of 60-bits Cyber words; each Cyber word is mapped onto 10 UNIX characters, each containing 6 bits in the right-most 6 positions. Depending on the interpretation that should be given to the original Cyber words .I NOStr performs the appropriate conversion indicated by .IR C . .PP Three conversions have been implemented: .TP .B d the input is interpreted as originating from Z-type records in DISPLAY code (6 bits to a character, EOR marked by two or more 00 characters in the lower end of a word) .TP .B a Z-type records in ASCII95 (8-bit characters right aligned in 12 bits, EOR marked by one or more NULL characters in the lower end) .TP .B b `binary', i.e., ASCII256 (8-bit characters right aligned in 12 bits, no further processing); since this code is not well standardized, parity checking, CR-removal and further preening are left to the user. .SH SEE ALSO NOSsplit (I) .SH BUGS As with .I NOSsplit the inverse does not exist. <<>EndOfFile<>> if test "61477 2" != "`sum \N\O\S\t\r\.\1`" then echo Checksum error; fi echo x \N\O\S\t\r\.\c cat >\N\O\S\t\r\.\c <<'<<>EndOfFile<>>' #define MSGUSE "Usage is: NOStr -C [file]" #include <stdio.h> #include "tp.h" /* * Program: NOStr, translates a Cyber 6-bit file into a UNIX ASCII file. * Author: Dick Grune * Version: 820314 */ #define MAXERR 40 #define EOS '\0' FILE *ifile; /* input file */ char *iname; /* input name */ int displZ(), asciiZ(), binary(); struct conv { char *name; int (*command)(); char *expl; } conv[] = { {"d", displZ, "Z-type records in DISPLAY code"}, {"a", asciiZ, "Z-type records in ASCII95"}, {"b", binary, "binary, = ASCII256"} }; main(argc, argv) char *argv[]; { int p; if (argc < 2 || argc > 3 || argv[1][0] != '-' || strlen(argv[1]) != 2) goto Bad_usage; if (argc == 2 || strcmp(argv[2], "-") == 0) { iname = "standard input"; ifile = stdin; } else { iname = argv[2]; ifile = fopen(iname, "r"); if (ifile == NULL) { fprintf(stderr, "%s: cannot open %s\n", argv[0], iname); return 1; } } for (p = 0; p < n_items(conv); p++) if (strcmp(&argv[1][1], conv[p].name) == 0) { (*conv[p].command)(); return 0; } Bad_usage: fprintf(stderr, MSGUSE); fprintf(stderr, "\nwhere the conversion code C is one of the following:\n"); for (p = 0; p < n_items(conv); p++) fprintf(stderr, "`%s': %s\n", conv[p].name, conv[p].expl); return 1; } #define NL '\n' #define TEN 10 int cwrd[TEN]; long lcnt = 1; long icnt = 0; displZ() { /* recognize Z-type records in DISPLAY code */ int nch; int zpend = 0; while (nch = get_cwrd(TEN), nch != 0) { int i; if (nch != TEN) VOID(complain("short word", 0)); while (nch != 0 && cwrd[nch-1] == 0) nch--; if (zpend && nch > 0) putdispl(0); /* zero pending */ for (i = 0; i < nch; i++) putdispl(cwrd[i]); if (nch < TEN-1) putascii(NL); zpend = nch == TEN-1; } } #define FIVE 5 asciiZ() { /* recognize Z-type records in ASCII95 */ int nch; while (nch = get_cwrd(FIVE), nch != 0) { int i; if (nch != FIVE) VOID(complain("short word", 0)); while (nch != 0 && cwrd[nch-1] == 0) nch--; for (i = 0; i < nch; i++) putascii(cwrd[i]); if (nch < FIVE) putascii(NL); } } binary() { /* binary, also ASCII256 */ int ch; while (ch = get12bits(), ch != EOF) putascii(ch); } int get_cwrd(n) { /* gather n (60/n)-bit bytes into a Cyber word */ int i; for (i = 0; i < n; i++) { cwrd[i] = n == TEN ? get6bits() : n == FIVE ? get12bits() : abort(); if (cwrd[i] == EOF) break; } return i; } int get12bits() { int ch1, ch2; static errcnt = 0; ch1 = get6bits(); if (ch1 == EOF) return EOF; ch2 = get6bits(); if (ch2 == EOF) ch2 = 0; ch1 = (ch1 << 6) + ch2; if (ch1 >> 8) errcnt = complain("composed char wider than 8 bits", errcnt); return ch1; } int get6bits() { int ch = getc(ifile); static errcnt = 0; if (ch == EOF) return EOF; icnt++; if (ch >> 6) errcnt = complain("input char wider than 6 bits", errcnt); return ch; } putdispl(ch) { /* convert display to ASCII */ putchar( "%ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-*/()$= ,.#[]:\"_!&'?<>@\\^;" [ch]); } putascii(ch) char ch; { putchar(ch); if (ch == NL) lcnt++; } int complain(s, n) char *s; { if (n > MAXERR) return n; fprintf(stderr, "At input char %D, at output line %D, %s in file `%s'\n", icnt, lcnt, s, iname); if (n == MAXERR) fprintf(stderr, "After %d complaints, further complaints of this type suppressed\n", MAXERR); return n+1; } <<>EndOfFile<>> if test "09249 4" != "`sum \N\O\S\t\r\.\c`" then echo Checksum error; fi echo x \a\n\s\i\.\h cat >\a\n\s\i\.\h <<'<<>EndOfFile<>>' #include <stdio.h> #include "tp.h" extern char *strcpy(); extern char *sprintf(); extern char *index(); #define NL '\n' #define TAB '\t' #define SP ' ' #define EOS '\0' #define Ascii95(c) (040 <= (c) && (c) <= 0176) #define TRUE 1 #define FALSE 0 #define MAXSTR 128 /* * It is tempting to use structs for the declaration of the label data * structures, access the fields through selectors and let the C-compiler * do the offset calculation. There are, however, two problems: * 1. alignment is different on different machines (VAX vs. PDP11), * 2. according to the book the fields may even be arranged in inverse * order. * So structs are out and defines are in. * A field is implemented as a pair: an address and a length. */ /* the general fields */ #define Whole(p) &(p)[0], sizeof (p) #define Labidf(p) &(p)[0], 4 /* the VOL1 label */ #define Volidf(p) &(p)[4], 6 #define Volacc(p) &(p)[10], 1 #define Sp1(p) &(p)[11], 26 #define Ownidf(p) &(p)[37], 14 #define Sp2(p) &(p)[51], 28 #define Labvers(p) &(p)[79], 1 /* the date */ #define Sp(p) &(p)[0], 1 #define Year(p) &(p)[1], 2 #define Yday(p) &(p)[3], 3 /* the HDR1 label */ #define Fileidf(p) &(p)[4], 17 #define Filesetidf(p) &(p)[21], 6 #define Filsecnum(p) &(p)[27], 4 #define Filseqnum(p) &(p)[31], 4 #define Gennum(p) &(p)[35], 4 #define Genversnum(p) &(p)[39], 2 #define Creatdate(p) &(p)[41], 6 #define Expirdate(p) &(p)[47], 6 #define Fileacc(p) &(p)[53], 1 #define Blkcount(p) &(p)[54], 6 #define Syscode(p) &(p)[60], 13 #define Sp3(p) &(p)[73], 7 /* the HDR2 label */ #define Recformat(p) &(p)[4], 1 #define Blklength(p) &(p)[5], 5 #define Reclength(p) &(p)[10], 5 #define Syssoftw(p) &(p)[15], 35 #define Bufoffset(p) &(p)[50], 2 #define Sp4(p) &(p)[52], 28 /* the USRn label */ #define Contents(p) &(p)[4], 76 int unit = 0; char *nmdns = TP_DENN; TPFILE *tf = NULL; #define ASK_NO 0 /* use default and check */ #define ASK_YES 1 /* ask and check */ #define ASK_SUG 2 /* suggest default and check */ #define ASK_ERR 3 /* ask again and check */ #define inmood(md) {int mood = md; for (;;mood=ASK_ERR){ #define iferr(cond) if(cond){locate(); #define enderr continue;} #define endmood break;}} char * sps2txt(p) { return p == 0 ? "<empty>" : p == 1 ? "<space>" : "<spaces>"; } char * str2txt(s) char *s; { int p = 0; while (s[p] != EOS) { if (s[p] != SP) return s; p++; } return sps2txt(p); } /* An expl(anation) is an explanatory string which contains the name of the * object being explained as the first item between ` and ' . The expl may * contain one %s which is replaced by the default (string) value. */ char * /* transient */ expl2str(expl) char *expl; { static char str[MAXSTR]; char *nm, cnt = 0; for (nm = index(expl, '`') + 1; *nm != EOS && *nm != '\''; nm++) if (cnt < MAXSTR-1) str[cnt++] = *nm; str[cnt] = EOS; return str; } char * /* transient */ tty_line() { static char ans[MAXSTR]; int cnt = 0; int ch; while ((ch = getchar()) != NL) { if (ch == EOF) errors("\n*** No interaction!!!"); if (cnt < MAXSTR-1) ans[cnt++] = ch; } ans[cnt] = EOS; return ans; } char * /* transient */ tty_str(prompt, expl, md, def) char *prompt, *expl, md, *def; { static char conversation = FALSE; char *str; int err = 0; if (!conversation) { prf_s("\n\ I shall have to ask some questions; to get more information,\n\ answer a question with a single question mark (?).\n\n", ""); conversation = TRUE; } while (err < 3) { printf(prompt, expl2str(expl)); str= tty_line(); if (strcmp(str, "?") == 0) { printf("\n"); prf_s(expl, def); printf("\n"); } else if (strlen(str) > 0) return str; else if (md == ASK_SUG) return NULL; else { printf("I do need an answer!\n"); err++; } } errors("\nSorry"); return str; } char * /* transient */ enq_str(expl, md, def) char *expl, *def; { char *str; switch (md) { case ASK_NO: str = def; break; case ASK_YES: locate(); str = tty_str("Please type your %s: ", expl, md, def); break; case ASK_SUG: locate(); printf("Default %s: ", expl2str(expl)); prf_s("%s\n", str2txt(def)); str = tty_str("Please type a new %s, or press RETURN: ", expl, md, def); if (str == NULL) str = def; break; case ASK_ERR: str = tty_str("Please type a new %s: ", expl, md, def); break; } return str; } int enq_int(expl, md, def, max) char *expl; { int res; char deftxt[MAXSTR]; VOID(sprintf(deftxt, "%d", def)); inmood (md) char *str = enq_str(expl, mood, deftxt); char *ptr = str; char ch; res = 0; while ((ch = *ptr++) != EOS) { int dig = char2int(ch) -'0'; if (dig < 0 || dig > 9) { res = -1; break; } if (res > max/10 || res*10 > max - dig) { /* airtight, waterproof and overflow-resistant */ res = -2; break; } res = res*10 + dig; } iferr (res == -1) printf("The %s `%s' is not numeric\n", expl2str(expl), str); enderr; iferr (res == -2) printf("The %s `%s' is too large\n", expl2str(expl), str); printf("The maximum value is %d\n", max); enderr; endmood; return res; } int isascstr(str) char *str; { char ch; while ((ch = *str++) != EOS) if (!Ascii95(ch)) return FALSE; return TRUE; } int fld2int(addr, size) char *addr; { int res = 0, i; for (i = 0; i < size; i++) { char ch = addr[i]; int dig = char2int(ch) - '0'; if (ch == SP) continue; if (dig < 0 || dig > 9) return -1; res = res * 10 + dig; } return res; } char * /* transient */ fld2str(addr, size) char *addr; { static char str[MAXSTR]; int i; for (i = 0; i < size; i++) if (i < MAXSTR-1) str[i] = addr[i]; while (i > 0 && str[i-1] == SP) i--; str[i] = EOS; return str; } fld2fld(str, s1, addr, s2) char *str, *addr; { if (s1 != s2) abort(); while (s1-- > 0) *addr++ = *str++; } str2fld(str, addr, size) char *str, *addr; { while (size-- > 0) *addr++ = *str != EOS ? *str++ : SP; } char * /* transient */ char2str(ch) { static char buff[7]; if (Ascii95(ch)) VOID(sprintf(buff, "%c", ch)); else VOID(sprintf(buff, "\\[%03o]", char2int(ch))); return buff; } /* * `prf_s' prints a possibly ugly string `s' under a format `f' that may * be so long that it normally blows up the `printf' routine. */ prf_s(f, s) char *f, *s; { char fch; while ((fch = *f++) != EOS) { if (fch != '%') putchar(fch); else { int sch; f++; while ((sch = *s++) != EOS) printf("%s", char2str(sch)); } } } errors(str) char *str; { printf("%s\n", str); if (tf != NULL) tpclose(tf); exit(1); } struct format { char type; int (*checkpar)(); int (*cpblk)(); }; #define FORMAT struct format extern FORMAT formats[]; FORMAT * format(ch) char ch; { FORMAT *fm; for (fm = &formats[0]; fm->type != EOS; fm++) if (fm->type == ch) return fm; return NULL; } char filename[MAXSTR]; FILE *file = NULL; int filseqnum = 0; int filsecnum = 1; char rectype[1] = 'F'; FORMAT *recformat; int blklength = 1920; int reclength = 80; int bufoffset = 0; int reccount; int blkcount; char VOL1buf[80]; char HDR1buf[80]; char HDR2buf[80]; <<>EndOfFile<>> if test "03998 7" != "`sum \a\n\s\i\.\h`" then echo Checksum error; fi echo x \a\n\s\i\r\.\1 cat >\a\n\s\i\r\.\1 <<'<<>EndOfFile<>>' .TH ANSIR I .SH NAME ansir \- read ANSI standard labelled tape .SH SYNOPSIS .B ansir [ .B \-ijnpg ] [ name ... ] .SH DESCRIPTION .I Ansir reads a single volume multi-file ANSI Standard Labelled Tape or anything that looks remotely like it. It can handle 'F', 'U', 'D', 'S' and 'V' formats ('V' is for IBM tapes). Labels can be in ASCII or in EBCDIC; .I ansir assumes the files to be in the same character code as the labels, except for 'U' format files which are supposed to be in BINARY (i.e. untranslated). The record separator for ASCII or EBCDIC is the newline; the record separator for BINARY is determined interactively. All these rules can be overridden through the .B \-i and .B \-j parameters. .PP Bad characters, i.e., those not in the ASCII95 set, are reported and show up in the format \e[777]. This may necessitate some postprocessing but too much light is better than being left in the dark. .PP .I Ansir may consult the user on missing information; when a question is answered with a single question mark, .I ansir supplies more information. .PP The program accepts the usual .B \-cfhlm parameters to describe the tape (see .IR mag (I)). .PP There are a number of additional options: .TP .B \-i the UNIX file name and character code are determined interactively. A minus .B \- for a file name will result in skipping the corresponding tape file. .TP .B \-j as i-option, but including record format, block length, record length and buffer offset. .TP .B \-n no execution: the files will not be extracted. .TP .B \-p information from the labels is printed. .TP .B \-g if a .B \-p is given, the information about generation number and version number is also printed. .PP If .I name parameters are given, only the named files are treated. .SH SEE ALSO mag(I) .SH BUGS The 'S' format has never been tried. .PP The .B \-p option causes too much output. <<>EndOfFile<>> if test "38580 2" != "`sum \a\n\s\i\r\.\1`" then echo Checksum error; fi echo x \a\n\s\i\r\.\c cat >\a\n\s\i\r\.\c <<'<<>EndOfFile<>>' #define MSGUSE "Usage: ansir [-cfhlmijnpg] [ name ... ]" #include "ansi.h" #define MAXBLK TP_MAXB #define MINSIZE 6 /* smaller is a noise block */ #define CRECSEP "\n" /* record separator for coded files */ /* * Name: ansir, read ANSI standard labelled tape * Author: Dick Grune * Version: 820314 */ char iflag = FALSE, jflag = FALSE, nflag = FALSE, pflag = FALSE, gflag = FALSE; char ok_fln, ok_rct, ok_blk, ok_rec, ok_bfo, ok_loc, skip_it; char **filelist = NULL; char *labcode = NULL; char *filecode = NULL; /* may be `ascii', `ebcdic' or `binary' */ char *ascii = "ASCII"; char *ebcdic = "EBCDIC"; char *binary = "BINARY"; char binrecsep[MAXSTR]; int bsize = 0; char block[MAXBLK]; #define is_block (bsize>0) #define is_tm (bsize==0) #define MAXUGLY 99 /* upper limit for tallying */ char uglies[256]; /* for tallying non-UNIX-ASCII chars */ long tot_uglies; int HDR1blc; /* block count as found in HDR1-label */ main(argc, argv) int argc; char *argv[]; { argc--; argv++; if (argc > 0) { if (argv[0][0] == '-') { char *pp = argv[0]; while (*++pp) switch (*pp) { case 'c': unit = TP_CDEV; if (argc < 2) goto Lbad; nmdns = argv[1]; argc--; argv++; break; case 'f': unit = TP_IMAG; if (argc < 2) goto Lbad; nmdns = argv[1]; argc--; argv++; break; case 'g': gflag = TRUE; break; case 'h': nmdns = TP_DENH; break; case 'i': iflag = TRUE; break; case 'j': iflag = jflag = TRUE; break; case 'l': nmdns = TP_DENL; break; case 'm': unit = *++pp - '0'; break; case 'n': nflag = TRUE; break; case 'p': pflag = TRUE; break; default: goto Lbad; } argc--; argv++; } } if (argc > 0) filelist = argv; tf = tpopen(unit, nmdns, "rx"); nextblk(); lblVOL1(); lblUSR("UVL"); while (is_block) { clearflags(); lblHDR1(); lblHDR2(); lblUSR("HDR"); lblUSR("UHL"); read_tm(); copy(); lblEOF1(); lblEOF2(); lblUSR("EOF"); lblUSR("UTL"); read_tm(); } if (pflag) printf("\nEnd of tape\n"); tpclose(tf); exit(0); Lbad: errors(MSGUSE); } clearflags() { ok_fln = ok_rct = ok_blk = ok_rec = ok_bfo = ok_loc = skip_it = FALSE; } /* * Reads the VOL1-label and prints its contents */ lblVOL1() { if (!label("VOL1", Whole(VOL1buf))) { missing("VOL1"); return; } if (pflag) { prhead(Labidf(VOL1buf)); printf("The labels are in %s\n", labcode); prfield("Volume serial number", Volidf(VOL1buf)); prfield("Volume accessibility", Volacc(VOL1buf)); prunused(Sp1(VOL1buf)); prfield("Owner identification", Ownidf(VOL1buf)); prunused(Sp2(VOL1buf)); prfield("Label version", Labvers(VOL1buf)); } } lblUSR(idf) char *idf; { char USRnbuf[80]; while (label(idf, Whole(USRnbuf))) if (pflag && !skip_it) { prhead(Labidf(USRnbuf)); prfield("Contents", Contents(USRnbuf)); } } /* * Reads the HDR1-label, prints its contents, and sets `filename' */ lblHDR1() { filseqnum++; if (!label("HDR1", Whole(HDR1buf))) { missing("HDR1"); ok_fln = FALSE; return; } strcpy(filename, fld2str(Fileidf(HDR1buf))); ok_fln = TRUE; if (!interesting(filename)) { skip_it = TRUE; return; } if (pflag) prHDR1(); if (fld2int(Filsecnum(HDR1buf)) != filsecnum) { locate(); printf("File section number not %d\n", filsecnum); } if (fld2int(Filseqnum(HDR1buf)) != filseqnum) { locate(); printf("File sequence number not in order\n"); } HDR1blc = fld2int(Blkcount(HDR1buf)); if (chk_int("block count", HDR1blc) && HDR1blc != 0) { locate(); printf("Block count starts from %d\n", HDR1blc); } } /* * Reads the HDR2-label, prints its contents and sets * `recformat', `blklength', `reclength' and `bufoffset'. */ lblHDR2() { int found = label("HDR2", Whole(HDR2buf)); if (skip_it) return; if (!found) { missing("HDR2"); ok_rct = ok_blk = ok_rec = ok_bfo = FALSE; return; } if (pflag) prHDR2(); fld2fld(Recformat(HDR2buf), Whole(rectype)); ok_rct = TRUE; blklength = fld2int(Blklength(HDR2buf)); ok_blk = chk_int("block length", blklength); reclength = fld2int(Reclength(HDR2buf)); ok_rec = chk_int("record length", reclength); bufoffset = fld2int(Bufoffset(HDR2buf)); ok_bfo = chk_int("buffer offset", bufoffset); } int label(idf, addr, size) char *idf, *addr; { if (!is_block) return FALSE; if (labcode != NULL) return is_lab(idf, addr, size); labcode = ascii; if (is_lab(idf, addr, size)) return TRUE; labcode = ebcdic; if (is_lab(idf, addr, size)) return TRUE; labcode = NULL; return FALSE; } int is_lab(idf, addr, size) char *idf, *addr; { int sz = bsize < size ? bsize : size; strncpy(addr, block, sz); if (labcode == ebcdic) conv(addr, sz); str2fld("", addr+sz, size-sz); if (strhead(idf, addr)) { nextblk(); return TRUE; } return FALSE; } int strhead(s1, s2) char *s1, *s2; { while (*s1) if (*s1++ != *s2++) return FALSE; return TRUE; } read_tm() { if (is_tm) nextblk(); else { locate(); printf("Tape mark missing\n"); } } nextblk() { do bsize = tpread(tf, block, MAXBLK); while (bsize > 0 && bsize < MINSIZE); } int interesting(fn) char *fn; { char **lst = filelist, *nm; if (lst == NULL) return TRUE; while ((nm = *lst++) != NULL) if (strcmp(nm, fn) == 0) return TRUE; return FALSE; } conv(addr, size) char *addr; { while (size-- > 0) { *addr = ebc2asc(*addr); addr++; } } char asc2ebc(ch) char ch; { char ch1 = 0; while (ch != ebc2asc(ch1)) ch1++; return ch1; } /* * copy one file */ copy() { init_copy(); do copypart(); while (change_tape()); end_copy(); } init_copy() { int i; blkcount = reccount = 0; tot_uglies = 0L; for (i = 0; i < n_items(uglies); i++) uglies[i] = 0; getpars(); } end_copy() { if (pflag && !skip_it) { printf("\n"); if (file != NULL) printf("Record count: %d\n", reccount); printf("Block count: %d\n", blkcount); if (file != NULL) prf_s("File copied: %s\n", filename); else printf("File skipped\n"); } if (file != NULL) { VOID(fclose(file)); if (tot_uglies != 0L) { int i; locate(); printf("File contained %D non-ASCII character%s:\n", english(tot_uglies)); for (i = 0; i < n_items(uglies); i++) { int n = char2int(uglies[i]); if (n != 0) { if (n <= MAXUGLY) printf("%s: %d\n", char2str(i), n); else printf("%s: >%d\n", char2str(i), MAXUGLY); } } } } } /* * getpars sets `filename', `filecode', `recformat' * and as many further parameters as is necessary */ extern FORMAT fdummy; getpars() { if (nflag || skip_it) { recformat = &fdummy; return; } inmood (!ok_fln ? ASK_YES : iflag ? ASK_SUG : ASK_NO) strcpy(filename, enq_str("\ The `file name' is the name under which the tape file will be\n\ stored on disk. The named file must not already exist.\n\ Use a minus - to skip the file.\n", mood, filename)); if (strcmp(filename, "-") == 0) { recformat = &fdummy; return; } iferr (!isascstr(filename)) prf_s("Filename %s contains non-printing chars\n", filename); enderr; iferr ((file = fopen(filename, "r")) != NULL) prf_s("File %s already exists\n", filename); VOID(fclose(file)); enderr; iferr ((file = fopen(filename, "w")) == NULL) prf_s("Cannot create %s\n", filename); enderr; endmood; inmood (labcode == NULL && filecode == NULL ? ASK_YES : labcode == NULL || iflag ? ASK_SUG : ASK_NO) filecode = enq_str("\ The `character code' is the code of the file on tape; it may be\n\ ASCII, EBCDIC (usual for IBM-tapes) or BINARY (no conversion).\n\ When in doubt, use %s.\n", mood, filecode != NULL ? filecode : labcode != NULL ? labcode : ascii); switch (filecode[0]) { case 'A': filecode = ascii; break; case 'B': filecode = binary; break; case 'E': filecode = ebcdic; break; default: printf("`%s' is not a character code\n", filecode); filecode = NULL; } iferr (filecode == NULL) printf("Specify %s, %s or %s\n", ascii, ebcdic, binary); enderr; endmood; if (filecode == binary) strcpy(binrecsep, enq_str("\ The `record separator' only applies to the character code\n\ BINARY, where it specifies what character(s) should be written\n\ to disk for each end-of-record on the tape. You will probably\n\ want it to be empty, but, when in doubt, try a recognizable\n\ string like }}}} and examine the results.\n", ASK_SUG, binrecsep)); inmood (!ok_rct || jflag ? ASK_SUG : ASK_NO) char *rct = enq_str("\ The `record format' tells how the block on tape must be cut into\n\ text records (lines). There are five standard ways to do so:\n\ F: each N consecutive characters form a record, where N is the\n\ `record length',\n\ D: a header in each record tells its length,\n\ U: each block is one record,\n\ S: a special format in which records may be longer than blocks,\n\ V: IBM Variable format.\n\ When in doubt, use %s and examine the results, or try them all.\n", mood, !ok_rct ? "U" : fld2str(Whole(rectype))); iferr (strlen(rct) != 1 || (recformat = format(rct[0])) == NULL) printf("`%s' is not an ANSI format\n", rct); printf("Please specify F, D, U, S or V\n"); enderr; iferr (recformat->cpblk == NULL) printf("%s-format not implemented\n", rct); enderr; str2fld(rct, Whole(rectype)); endmood; inmood (!ok_blk || jflag ? ASK_SUG : ASK_NO) blklength = enq_int("\ The `block length' is the maximum number of significant\n\ characters in any physical block (as demarcated by two\n\ interrecord gaps) on the tape. Unless you know the exact value,\n\ use a large number like %s.\n", mood, !ok_blk ? MAXBLK : blklength, MAXBLK); iferr (blklength == 0) printf("Block length cannot be zero\n"); enderr; endmood; (*recformat->checkpar)(); bufoffset = enq_int("\ The `buffer offset' is the position in each block on the tape at\n\ which the real information starts. Unless you know the exact\n\ value, use 0.\n", !ok_bfo || jflag ? ASK_SUG : ASK_NO, !ok_bfo ? 0 : bufoffset, blklength - 1); } copypart() { while (is_block) { int used; ++blkcount; if (bsize > blklength) bsize = blklength; if (filecode == ebcdic) conv(block, bsize); used = bufoffset; used += (*recformat->cpblk) (&block[used], bsize-used); if (!filler(&block[used], bsize-used)) { locate(); printf("At block %d, after record %d:", blkcount, reccount); printf(" %d char%s garbage ignored\n", english(bsize-used)); } nextblk(); } read_tm(); } int change_tape() { return FALSE; /* not yet implemented */ } checkF() { inmood (!ok_rec || jflag ? ASK_SUG : ASK_NO) reclength = enq_int("\ The `record length' is a number N such that a <newline> is\n\ assumed after each N characters read from tape. When in doubt,\n\ use %s and examine the results for a better value.\n", mood, !ok_rec || blklength < reclength ? blklength : reclength, blklength); iferr (reclength == 0) printf("Record length cannot be zero\n"); enderr; endmood; } int cpFblk(buf, size) char *buf; { int i = 0; while (i <= size - reclength) { char *rec = &buf[i]; int sz = reclength; reccount++; if (filecode != binary) { char pad = rec[sz-1]; if (!Ascii95(pad) || pad == SP) /* liberal */ while (sz > 0 && rec[sz-1] == pad) sz--; } put_rec(rec, sz); put_eor(); i += reclength; } return i; } int cpUblk(buf, size) char *buf; { reccount++; put_rec(buf, size); put_eor(); return size; } #define DVPREF 4 /* size of D or V length prefix */ int cpDVblk(buf, size) char *buf; { int i = rectype[0] == 'V' ? DVPREF : 0; while (size - i >= DVPREF) { char *rec = &buf[i]; int sz = DVlength(rec); if (sz < DVPREF || size - i - sz < 0) break; reccount++; put_rec(rec + DVPREF, sz - DVPREF); put_eor(); i += sz; } return i; } int DVlength(buf) char *buf; { int res = 0; if (rectype[0] == 'V') { int i; for (i = 0; i <= 1; i++) { char ch = buf[i]; res = res*256 + char2int(filecode == ebcdic ? asc2ebc(ch) : ch); } } else res = fld2int(buf, DVPREF); return res; } #define SPREF 5 /* size of S length prefix */ int cpSblk(buf, size) char *buf; { int i = 0; while (size - i >= SPREF) { char *rec = &buf[i]; char ind = rec[0]; int sz = fld2int(rec+1, SPREF-1); if (sz < SPREF || size - i - sz < 0) break; if (ind == '0' || ind == '1') reccount++; put_rec(rec + SPREF, sz - SPREF); if (ind == '0' || ind == '3') put_eor(); i += sz; } return i; } skip() { return; } int skpblk(buf, size) char *buf; { VOID(buf); return size; } int filler(addr, size) char *addr; { char ch = *addr; while (size--) if (ch != *addr++) return FALSE; return TRUE; } FORMAT formats[] = { {'F', checkF, cpFblk}, {'U', skip, cpUblk}, {'D', skip, cpDVblk}, {'V', skip, cpDVblk}, /* to cater for you know whom */ {'S', skip, cpSblk}, {EOS, NULL, NULL} }; FORMAT fdummy = {EOS, skip, skpblk}; put_rec(rec, size) char *rec; { int i; for (i = 0; i < size; i++) { int ch = char2int(rec[i]); if (filecode == binary || Ascii95(ch)) putc(ch, file); else { fprintf(file, "%s", char2str(ch)); if (uglies[ch] <= MAXUGLY) uglies[ch]++; tot_uglies++; } } } put_eor() { char *sep = filecode == binary ? binrecsep : CRECSEP; char ch; while ((ch = *sep++) != EOS) putc(ch, file); } /* * Reads the EOF1-label and checks block count */ lblEOF1() { int bcount; int found = label("EOF1", Whole(HDR1buf)); if (skip_it) return; if (!found) { missing("EOF1"); return; } bcount = fld2int(Blkcount(HDR1buf)); if (pflag) prHDR1(); if (HDR1blc >= 0 && bcount != blkcount + HDR1blc) { locate(); printf( "File holds %d block%s whereas labels report %d block%s\n", english(blkcount), english(bcount - HDR1blc)); } } /* * Read EOF2-label */ lblEOF2() { int found = label("EOF2", Whole(HDR2buf)); if (skip_it) return; if (!found) { missing("EOF2"); return; } if (pflag) prHDR2(); } /* * Print routines */ prHDR1() { prhead(Labidf(HDR1buf)); prfield("File identifier", Fileidf(HDR1buf)); prfield("Set identifier", Filesetidf(HDR1buf)); prfield("File section number", Filsecnum(HDR1buf)); prfield("File sequence number", Filseqnum(HDR1buf)); if (gflag) { prfield("Generation number", Gennum(HDR1buf)); prfield("Generation version number", Genversnum(HDR1buf)); } prfield("Creation date", Creatdate(HDR1buf)); prfield("Expiration date", Expirdate(HDR1buf)); prfield("File accessibility", Fileacc(HDR1buf)); prfield("Block count", Blkcount(HDR1buf)); prfield("System code", Syscode(HDR1buf)); prunused(Sp3(HDR1buf)); } prHDR2() { prhead(Labidf(HDR2buf)); prfield("Record format", Recformat(HDR2buf)); prfield("Block length", Blklength(HDR2buf)); prfield("Record length", Reclength(HDR2buf)); prfield("System software", Syssoftw(HDR2buf)); prfield("Buffer offset", Bufoffset(HDR2buf)); prunused(Sp4(HDR2buf)); } prhead(addr, size) char *addr; { printf("\nInformation from the %s-label\n", fld2str(addr, size)); } prfield(nm, addr, size) char *nm, *addr; { char *str = fld2str(addr, size); printf("%s: ", nm); prf_s("%s\n", *str == EOS ? sps2txt(size) : str); } prunused(addr, size) char *addr; { char *str = fld2str(addr, size); if (strlen(str) > 0) prf_s("Unused field: %s\n", str); } missing(idf) char *idf; { locate(); printf("%s-label missing\n", idf); } int chk_int(nm, val) char *nm; { if (val < 0) { locate(); printf("Garbage in %s field\n", nm); return FALSE; } else return TRUE; } locate() { if (ok_loc || pflag) return; if (ok_fln) prf_s("\n*** At file `%s':\n", filename); else printf("\n*** At file number %d:\n", filseqnum); ok_loc = TRUE; } <<>EndOfFile<>> if test "09745 16" != "`sum \a\n\s\i\r\.\c`" then echo Checksum error; fi echo x \a\n\s\i\w\.\1 cat >\a\n\s\i\w\.\1 <<'<<>EndOfFile<>>' .TH ANSIW I .SH NAME ansiw \- write ANSI standard labelled tape .SH SYNOPSIS .B ansiw [ .B \-ignpv ] [ file ... ] .SH DESCRIPTION .I Ansiw writes a single volume multi-file ANSI Standard Labelled Tape in F- or U-format. .PP If no option is given the tape has the following properties: .br the volume serial number is 222222, .br the owner is the user id, .br the file identifier is the UNIX filename, .br the expiration date is the current date, .br the accessibility symbol is ' '. .PP The preferred record format is 'F' with block length 1920 and record length 80. Each .I file is scanned before it is written to tape. If it is found to be incompatible with the preferred record format, .I ansiw tries to find a better one. If it fails the user is consulted. .PP The program accepts the usual .B \-cfhlm parameters to describe the tape (see .IR mag (I)). .PP There are a number of additional options. .TP .B \-i the user is consulted about the tape properties. .TP .B \-g if a .B \-i is given the user is also consulted about the generation number and the version number. .TP .B \-n no execution: the tape-file is not created. .TP .B \-p a short report is printed for each file. .TP .B \-v the user is only consulted about the Volume Label. .PP The 'D' and 'S' formats are not implemented since their portability value is considered low. .SH SEE ALSO mag(I) <<>EndOfFile<>> if test "36035 2" != "`sum \a\n\s\i\w\.\1`" then echo Checksum error; fi