[net.sources] magtape package for ANSII standard labelled tapes

dick@tjalk.UUCP (Dick Grune) (12/18/84)

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
echo x \a\n\s\i\w\.\c
cat >\a\n\s\i\w\.\c <<'<<>EndOfFile<>>'
#define	MSGUSE	"Usage: ansiw [-cfhlmignpv] [ file ... ]"
#include	"ansi.h"
#include	<sys/types.h>
#include	<pwd.h>
#include	<time.h>
#include	<ident.h>	/* declares char myname[] */

/*
 * Name: ansiw, write ANSI standard labelled tape
 * Author: Dick Grune
 * Version: 820314
 */

#define	MAXBLK	2048
#define	MINBLK	18
#define	FILLER	'^'

extern time_t time();
extern struct tm *localtime();
extern struct passwd *getpwuid();

char iflag = FALSE, gflag = FALSE, pflag = FALSE, vflag = FALSE;
char block[MAXBLK];

struct DD	{
	long size;
	long lrecl;
	char ascii95;
	char example;
} dd;
struct DD empty_dd = {0L, 0L, TRUE, EOS};

#define	divis(m, n)	((n)!=0&&(m)%(n)==0)

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 'l':
				nmdns = TP_DENL;
				break;
			case 'm':
				unit = *++pp - '0';
				break;
			case 'n':
				unit = TP_IMAG;
				nmdns = "/dev/null";
				break;
			case 'p':
				pflag = TRUE;
				break;
			case 'v':
				vflag = TRUE;
				break;
			default:
				goto Lbad;
			}
			argc--; argv++;
		}
	}
	tf = tpopen(unit, nmdns, "w");

	check_args(argc, argv);

	lblVOL1(iflag || vflag ? ASK_SUG : ASK_NO);

	while (argc-- != 0)	{
		strcpy(filename, argv[filseqnum]);
		filseqnum++;
		blkcount = 0;

		opendd(filename);
		if (file == NULL)	{
			printf(">>> File %s ", filename);
			errors("has suddenly disappeared!!!");
		}
		lblHDR1(iflag ? ASK_SUG : ASK_NO);
		lblHDR2(iflag ? ASK_SUG : ASK_NO);
		wrt_tm();
		copy();
		wrt_tm();
		lblEOF1();
		lblEOF2();
		wrt_tm();
	}

	wrt_tm();
	tpclose(tf);
	exit(0);

Lbad:
	errors(MSGUSE);
}

check_args(c, v) char *v[];	{
	char ok = TRUE;

	while (c-- > 0)	{
		int f = open(*v, 0);

		if (f < 0)	{
			printf("Cannot open %s\n", *v);
			ok = FALSE;
		}
		else	VOID(close(f));
		v++;
	}
	if (!ok)
		errors("Stop");
}

wrt_tm() /*writes a tapemark*/	{
	tpwrite(tf, "", 0);
}

long
tab(p) long p;	{	/* the position in which a tab from p would land */
	return (p/8+1)*8;
}

/*
 * `opendd' opens the file `fn' and determines its `dd' parameters
 */

opendd(fn) char *fn;	{
	int ch;
	long lr;

	dd = empty_dd;
	if ((file = fopen(fn, "r")) == NULL)
		return;

	lr = 0L;
	while ((ch = getc(file)) != EOF)	{
		dd.size++;
		if (ch == NL)	{
			if (lr > dd.lrecl)
				dd.lrecl = lr;
			lr = 0L;
		}
		else
		if (ch == TAB)	{
			lr = tab(lr);
		}
		else	{
			lr++;
			if (!Ascii95(ch))	{
				dd.ascii95 = FALSE;
				dd.example = ch;
			}
		}
	}
	if (lr > dd.lrecl)
		dd.lrecl = lr;

	VOID(fclose(file));
	file = fopen(fn, "r");
}

/*
 * the writing of labels
 */

lblVOL1(md)	{
	struct passwd *pwd = getpwuid(getuid());

	str2fld("", Whole(VOL1buf));
	str2fld("VOL1", Labidf(VOL1buf));

	enq_fld("\
	The `volume serial number' is the six-character identification\n\
	number of the tape itself, as it should appear on the sticker on\n\
	the reel. When in doubt, use the default %s.\n",
		md, "222222", Volidf(VOL1buf));
	enq_fld("\
	The `volume accessibility symbol' is a single character,\n\
	recorded on the tape, which indicates how publicly accessible the\n\
	whole tape is. It is not well defined, but a single space is\n\
	generally taken to mean: accessible by anybody.\n",
		md, " ", Volacc(VOL1buf));
	enq_fld("\
	The `owner identification' is the name of the owner, as recorded\n\
	on the tape. On some systems it interacts with the file\n\
	accessibility symbol. When in doubt, specify a short string of\n\
	letters.\n",
		pwd == NULL ? ASK_YES : md, pwd->pw_name, Ownidf(VOL1buf));
	str2fld("1", Labvers(VOL1buf));

	tpwrite(tf, Whole(VOL1buf));
}

lblHDR1(md)	{
	time_t tnow = time((time_t*)0);
	struct tm *timeptr = localtime(&tnow);

	str2fld("", Whole(HDR1buf));
	str2fld("HDR1", Labidf(HDR1buf));
	enq_fld("\
	The `file identifier' is the name of the file, as recorded on\n\
	the tape.  When in doubt, specify a six-letter mnemonic name.\n",
		md, filename, Fileidf(HDR1buf));

	fld2fld(Volidf(VOL1buf), Filesetidf(HDR1buf));
	int2fld(filsecnum, Filsecnum(HDR1buf));
	int2fld(filseqnum, Filseqnum(HDR1buf));
	enq_num("\
	The `generation number' is a counter that some operating systems\n\
	attach to a file and that is increased each time the file is\n\
	updated. Use 1.\n",
		gflag ? md : ASK_NO, 1, Gennum(HDR1buf));
	enq_num("\
	The `generation version number' tells how often an attempt to\n\
	write the file to tape has failed. Use 0.\n",
		gflag ? md : ASK_NO, 0, Genversnum(HDR1buf));
	dat2fld(timeptr->tm_year, timeptr->tm_yday+1, Creatdate(HDR1buf));
	enq_dat("\
	The `expiration date' is the date, recorded on the tape, after\n\
	which the file may be overwritten. The format is 2 digits for\n\
	the year, followed by 3 digits for the day in the year, e.g.,\n\
	82365 for the last day of 1982.  When in doubt, use today's date,\n\
	%s, to make the receiver's life easier.\n",
		md, timeptr->tm_year, timeptr->tm_yday+1, Expirdate(HDR1buf));
	enq_fld("\
	The `file accessibility symbol' is a single character, recorded\n\
	on the tape, which indicates how publicly accessible the file is.\n\
	It is not well defined, but a single space is generally taken to\n\
	mean: accessible by anybody.\n",
		md, " ", Fileacc(HDR1buf));
	int2fld(blkcount, Blkcount(HDR1buf));

	str2fld(myname /* from ident.h */, Syscode(HDR1buf));

	tpwrite(tf, Whole(HDR1buf));
}

lblHDR2(md)	{

	str2fld("", Whole(HDR2buf));
	str2fld("HDR2", Labidf(HDR2buf));

	if (!iflag)	{
		str2fld("F", Whole(rectype));
		blklength = 1920;
		reclength = 80;
	}

	if (!dd.ascii95)	{
		printf("`%s' contains non-ASCII95 characters, e.g., %s\n",
			filename, char2str(dd.example));
		printf("Perhaps the file code should have been BINARY");
		str2fld("U", Whole(rectype));
	}

	if (dd.lrecl > MAXBLK)	{
		printf("`%s' has a record length > %d\n", filename, MAXBLK);
		printf("Only U-format is possible\n");
		str2fld("U", Whole(rectype));
		recformat = format('U');
	}
	else
	inmood (!dd.ascii95 ? ASK_SUG : md)
		char *rct = enq_str("\
	The `record format' tells how lines from the disk file should be\n\
	converted to records to be packed into blocks to be recorded on\n\
	tape. Although many formats exist, only two are any good in\n\
	Information Interchange:\n\
	F (Fixed): spaces are added at the end of the line until its\n\
	    length is `record length', and `block length'/`record length'\n\
	    of these records form a block;\n\
	U (Undefined): `block length' characters are stored in a block.\n\
	Unless the disk file contains non-ASCII characters, use F.\n",
			mood, fld2str(Whole(rectype)));

		iferr (strlen(rct) != 1
			|| (recformat = format(rct[0])) == NULL
			|| recformat->cpblk == NULL)
			printf(
			"Only F- and U-formats are allowed for portability\n"
			);
		enderr;

		str2fld(rct, Whole(rectype));
	endmood;

	(*recformat->checkpar)(md);

	fld2fld(Whole(rectype), Recformat(HDR2buf));
	int2fld(blklength, Blklength(HDR2buf));
	int2fld(reclength, Reclength(HDR2buf));
	int2fld(bufoffset, Bufoffset(HDR2buf));

	tpwrite(tf, Whole(HDR2buf));
}

checkF(md)	{
	int lr;

	inmood (md)
		getBlklength(mood);
		iferr (blklength < reclength)
			printf("Block length < phys. record length (=%D)\n",
				dd.lrecl);
		enderr;
	endmood;

	lr = dd.lrecl;
	while (!divis(blklength, lr))
		lr++;
	if (lr < 80 && divis(blklength, 80))
		lr = 80;
	reclength = lr;

	inmood (md)
		reclength = enq_int("\
	The `record length' is the number of characters into which each\n\
	line (record) is stretched before it is written to tape. It must\n\
	divide the block length. Unless the receiver has been very\n\
	specific, use %s.\n",
			mood, reclength, blklength);
		iferr (reclength == 0 || !divis(blklength, reclength))
			printf(
		"The block length is not a multiple of the record length\n"
			);
		enderr;
		iferr (reclength < dd.lrecl)
			printf("Record length < phys. record length (=%D)\n",
				dd.lrecl);
		enderr;
	endmood;
}

checkU(md)	{
	getBlklength(md);
	reclength = blklength;
}

getBlklength(md)	{
	inmood (md)
		blklength =
			enq_int("\
	The `block length' is the number of characters in each physical\n\
	block written to tape. Unless the receiver has specified\n\
	something else, use %s.\n",
				mood,
				rectype[0] == 'F' && dd.lrecl > 1920 ? MAXBLK :
					!iflag ? 1920 :
					blklength,
				MAXBLK);
		iferr (blklength < MINBLK)
			printf("The minimum block length is %d\n", MINBLK);
		enderr;
	endmood;
}


lblEOF1()	{
	str2fld("EOF1", Labidf(HDR1buf));
	int2fld(blkcount, Blkcount(HDR1buf));
	tpwrite(tf, Whole(HDR1buf));
}

lblEOF2()	{
	str2fld("EOF2", Labidf(HDR2buf));
	tpwrite(tf, Whole(HDR2buf));
}

/*
 * the copying of the file
 */

copy()	{
	int size;

	blkcount = reccount = 0;

	while ((size = (*recformat->cpblk)()) > 0)	{
		while (size < MINBLK)
			block[size++] = FILLER;
		tpwrite(tf, block, size);
		++blkcount;
	}
	VOID(fclose(file));
	if (pflag)	{
		printf("File name: %s\n", filename);
		printf("Record format: %s\n", rectype);
		printf("Block length: %d; number of blocks: %d\n",
			blklength, blkcount);
		printf("Record length: %d; number of records: %d\n\n",
			reclength, reccount);
	}
}

int
cpFblk()	{
	int ch;
	int count = 0;

	while (count < blklength && (ch = getc(file)) != EOF)	{
		int rpos = 0;
		reccount++;
		while (ch != NL && ch != EOF)	{
			if (ch == TAB)	{
				int nrpos = (int)tab((long)rpos);
				while (rpos < nrpos)	{
					block[count++] = SP; rpos++;
				}
			}
			else	{
				block[count++] = ch; rpos++;
			}
			ch = getc(file);
		}
		while (rpos < reclength)	{
			block[count++] = SP; rpos++;
		}
	}
	return count;
}

int
cpUblk()	{
	int ch;
	int count = 0;

	while (count < blklength && (ch = getc(file)) != EOF)	{
		block[count++] = ch;
	}
	if (count > 0)
		reccount++;
	return count;
}

FORMAT formats[] =	{
	{'F', checkF, cpFblk},
	{'U', checkU, cpUblk},
	{'D', NULL, NULL},
	{'S', NULL, NULL},
	{EOS, NULL, NULL}
};

/*
 * the setting of fields
 */

int2fld(n, addr, size) char *addr;	{

	addr += size;
	while (size-- > 0)	{
		*--addr = n % 10 + '0';
		n = n / 10;
	}
}

dat2fld(y, d, date, size) char *date;	{

	if (size != 6)
		abort();
	str2fld("", Sp(date));
	int2fld(y, Year(date));
	int2fld(d, Yday(date));
}


enq_fld(expl, md, def, addr, size) char *expl, *def, *addr;	{
	char *ans;

	inmood (md)
		ans = enq_str(expl, mood, def);
		iferr (strlen(ans) == 0)
			printf("No empty answer allowed\n");
		enderr;
		iferr (strlen(ans) > size)
			printf("The %s is too long\n", expl2str(expl));
			printf("The maximum length is %d character%s\n",
				english(size));
		enderr;
		iferr (!isascstr(ans))
			printf("The %s `%s' contains non-printing chars\n",
				expl2str(expl), ans);
		enderr;
	endmood;
	str2fld(ans, addr, size);
}

enq_num(expl, md, n, addr, size) char *expl, *addr;	{
	int2fld(enq_int(expl, md, n, tento(size)-1), addr, size);
}

int
tento(n)	{
	int res = 1;

	while (n--)
		res *= 10;
	return res;
}

enq_dat(expl, md, y, d, date, size) char *expl; char *date;	{

	dat2fld(y, d, date, size);
	inmood (md)
		char *ans = enq_str(expl, mood, fld2str(date, size));
		char *adt = (*ans == SP ? ans : ans - 1);

		y = fld2int(Year(adt));
		d = fld2int(Yday(adt));
		iferr (y < 0 || d <= 0 || d > (divis(y, 4) ? 366 : 365))
			printf("`%s' is not an acceptable date\n", ans);
		enderr;
	endmood;
	dat2fld(y, d, date, size);
}

locate()	{
	return;
}
<<>EndOfFile<>>
if test "43395    12" != "`sum \a\n\s\i\w\.\c`"
then echo Checksum error; fi
echo x \c\p\t\p\.\1
cat >\c\p\t\p\.\1 <<'<<>EndOfFile<>>'
.TH CPTP I
.SH NAME
cptp \- copy from tape to tape
.SH SYNOPSIS
.B cptp
[
.B \-x
] [
.BR of= file
|
.BR if= file
]
.SH DESCRIPTION
.I Cptp
converts between real tapes and tape images on disk.
The
.B of=
parameter causes
.I cptp
to read the real magtape and produce a tape image on the indicated file.
A call with an
.B if=
parameter will write back the tape image from the indicated file to the
real magtape.
.PP
The program accepts the usual
.B \-cfhlm
parameters to describe the tape (see
.IR mag (I)).
If the
.BI \-x -option
is given,
copying will continue regardless of read-errors or consecutive tape marks
(default is stopping after 4 consecutive TMs).
.SH "SEE ALSO"
mag(I),
rawtp(I),
survey(I)
<<>EndOfFile<>>
if test "58332     1" != "`sum \c\p\t\p\.\1`"
then echo Checksum error; fi
echo x \c\p\t\p\.\c
cat >\c\p\t\p\.\c <<'<<>EndOfFile<>>'
#define	MSGUSE	"Usage is: cptp [-cfhlmx] [of=file | if=file]"
#include	<stdio.h>
#include	"tp.h"

/*
 * Name: cptp, copy tape
 * Author: Dick Grune
 * Version: 820314
 *
 * `Cptp' converts between real tapes and tape images on disk.
 */

int unit = 0;
char *nmdns = TP_DENN;
char *rx = "r";
TPFILE *from, *to;
extern FILE *tperr;
char *filename;
int size;
char buff[TP_MAXB];

main (argc, argv) char *argv[];	{
	char *arg;

	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 'x':
				rx = "rx";
				break;
			default:
				goto Lbad;
			}
			argc--; argv++;
		}
	}
	if (argc != 1)
		goto Lbad;
	arg = argv[0];
	if (arg[0] == '\0' || arg[1] != 'f' || arg[2] != '=')
		goto Lbad;
	filename = &arg[3];
	tperr = stdout;
	switch (arg[0])	{
	case 'o':
		if (open(filename, 0) > 0)
			error("Output file already exists");
		from = tpopen(unit, nmdns, rx);
		to = tpopen(TP_IMAG, filename, "w");
		break;
	case 'i':
		from = tpopen(TP_IMAG, filename, "r");
		to = tpopen(unit, nmdns, "w");
		break;
	default:
		goto Lbad;
	}

	while ((size = tpread(from, buff, TP_MAXB)) != EOF)	{
		if (size == TP_MAXB)
			printf("Block too long; information may be lost\n");
		tpwrite(to, buff, size);
	}
	tpclose(from);
	tpclose(to);
	exit(0);

Lbad:
	error(MSGUSE);
}

error(str)	char *str;	{

	fprintf(stderr, "%s\n", str);
	exit(1);
}
<<>EndOfFile<>>
if test "38362     2" != "`sum \c\p\t\p\.\c`"
then echo Checksum error; fi
echo x \e\t\o\a\.\c
cat >\e\t\o\a\.\c <<'<<>EndOfFile<>>'
char _etoa[] =	{
	0000, 0001, 0002, 0003, 0234, 0011, 0206, 0177,
	0227, 0215, 0216, 0013, 0014, 0015, 0016, 0017,
	0020, 0021, 0022, 0023, 0235, 0205, 0010, 0207,
	0030, 0031, 0222, 0217, 0034, 0035, 0036, 0037,
	0200, 0201, 0202, 0203, 0204, 0012, 0027, 0033,
	0210, 0211, 0212, 0213, 0214, 0005, 0006, 0007,
	0220, 0221, 0026, 0223, 0224, 0225, 0226, 0004,
	0230, 0231, 0232, 0233, 0024, 0025, 0236, 0032,
	0040, 0240, 0241, 0242, 0243, 0244, 0245, 0246,
	0247, 0250, 0133, 0056, 0074, 0050, 0053, 0041,
	0046, 0251, 0252, 0253, 0254, 0255, 0256, 0257,
	0260, 0261, 0135, 0044, 0052, 0051, 0073, 0136,
	0055, 0057, 0262, 0263, 0264, 0265, 0266, 0267,
	0270, 0271, 0174, 0054, 0045, 0137, 0076, 0077,
	0272, 0273, 0274, 0275, 0276, 0277, 0300, 0301,
	0302, 0140, 0072, 0043, 0100, 0047, 0075, 0042,
	0303, 0141, 0142, 0143, 0144, 0145, 0146, 0147,
	0150, 0151, 0304, 0305, 0306, 0307, 0310, 0311,
	0312, 0152, 0153, 0154, 0155, 0156, 0157, 0160,
	0161, 0162, 0313, 0314, 0315, 0316, 0317, 0320,
	0321, 0176, 0163, 0164, 0165, 0166, 0167, 0170,
	0171, 0172, 0322, 0323, 0324, 0325, 0326, 0327,
	0330, 0331, 0332, 0333, 0334, 0335, 0336, 0337,
	0340, 0341, 0342, 0343, 0344, 0345, 0346, 0347,
	0173, 0101, 0102, 0103, 0104, 0105, 0106, 0107,
	0110, 0111, 0350, 0351, 0352, 0353, 0354, 0355,
	0175, 0112, 0113, 0114, 0115, 0116, 0117, 0120,
	0121, 0122, 0356, 0357, 0360, 0361, 0362, 0363,
	0134, 0237, 0123, 0124, 0125, 0126, 0127, 0130,
	0131, 0132, 0364, 0365, 0366, 0367, 0370, 0371,
	0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067,
	0070, 0071, 0372, 0373, 0374, 0375, 0376, 0377
};
<<>EndOfFile<>>
if test "55504     2" != "`sum \e\t\o\a\.\c`"
then echo Checksum error; fi
echo x \l\l\i\b\-\l\m\a\g
cat >\l\l\i\b\-\l\m\a\g <<'<<>EndOfFile<>>'
/*LINTLIBRARY*/
#include	"etoa.c"
#include	"tpread.c"
#include	"tpwrite.c"
#include	"tpopen.c"
#include	"tpclose.c"
#include	"tpname.c"
#include	"tpwtmloc.c"
#include	"tprdloc.c"
#include	"tpwloc.c"

printf(f, p1, p2, p3, p4, p5, p6, p7, p8, p9)
char *f;
int p1, p2, p3, p4, p5, p6, p7, p8, p9;
{return;}

fprintf(ff, f, p1, p2, p3, p4, p5, p6, p7, p8, p9)
FILE *ff;
char *f;
int p1, p2, p3, p4, p5, p6, p7, p8, p9;
{return;}
<<>EndOfFile<>>
if test "40648     1" != "`sum \l\l\i\b\-\l\m\a\g`"
then echo Checksum error; fi
echo x \m\a\g\.\1
cat >\m\a\g\.\1 <<'<<>EndOfFile<>>'
.TH MAG I
.SH NAME
mag \- generalized magnetic tape
.SH SYNOPSIS
.PP
.B cptp
[
.B -x
] [
.BR of= file
|
.BR if= file
]
.PP
.B rawtp
name [
.B \-x
] [ param ... ]
.PP
.B survey
[
.B \-px
]
.PP
.B ansir
[
.B \-ijnpg
] [ name ... ]
.PP
.B ansiw
[
.B \-ignpv
] [ file ... ]
.PP
.B NOSsplit
[
.B \-s
N ] [ name ]
.SH DESCRIPTION
These programs have in common that they use a 'generalized magtape'.
Such a generalized magtape may correspond to a real magtape or to a tape image
i.e., a normal
file with a specific structure which contains all information present on
a tape, including block sizes and tape marks. Conversion between real tapes
and tape images is done by
.IR cptp (I).
.PP
The generalized magtape is described by the following parameters which
apply to all the above programs.
.TP
.BI \-m d
the magtape is real and on unit
.IR d . 
.TP
.B \-h
the magtape is real and in high density.
.TP
.B \-l
the magtape is real and in low density.
.TP
.BI \-f " name"
the magtape is a tape image on file
.IR name .
.TP
.BI \-c " name"
the magtape is the character-device
.IR name .
.SH AUTHOR
Dick Grune.
.SH BUGS
Conflicts between options are not detected.
<<>EndOfFile<>>
if test "59389     2" != "`sum \m\a\g\.\1`"
then echo Checksum error; fi
echo x \m\a\g\.\3
cat >\m\a\g\.\3 <<'<<>EndOfFile<>>'
.TH MAG III
.SH NAME
mag \- implement generalized magtape
.SH SYNOPSIS
.PP
.B "#include <tp.h>"
.PP
.B "TPFILE *tpopen(unit, nmdns, rwx) char *nmdns, *rwx;"
.PP
.B "tpclose(tf) TPFILE *tf;"
.PP
.B "int tpread(tf, buf, size) TPFILE *tf; char *buf;"
.PP
.B "tpwrite(tf, buf, size) TPFILE *tf; char *buf;"
.PP
.B "FILE *tperr;"
.SH DESCRIPTION
These routines implement the generalized magtape described in
.IR mag (I).
Such a generalized magtape is viewed as a sequence of blocks each of which
is written and read in one piece. A tape mark is represented as a block of
length 0. The blocks correspond to physical blocks on tape (as separated
by Interrecord Gaps). On a tape image each block is preceded by its length
in format "%08d".
.PP
A generalized magtape is handled by these routines as a pointer to a
TPFILE. Such a pointer is obtained from
.IR tpopen .
The
.I unit
and the
.I nmdns
describe the generalized magtape; these are the possibilities:
.PP
a real magtape:
.I unit
is a small non-negative integer (the unit number);
.I nmdns
is TP_DENL, TP_DENN or TP_DENH (for low, normal or high density).
.PP
a tape image:
.I unit
is TP_IMAG;
.I nmdns
is the file name.
.PP
a character device:
.I unit
is TP_CDEV;
.I nmdns
is the device name.
.PP
The third parameter
.I rwx
is either "r" or "rx" for reading or "w" for writing; "rx" suppresses
the recognition of end-of-file in real magtapes (see
.IR tpread ).
.PP
.I Tpclose
closes a generalized magtape; this causes a rewind and makes room for
other such files, since only a limited number (specified in _TP_MOPEN)
can be open at the same time.
.PP
.I Tpread
reads one block into
.I buf
to a maximum of
.I size
characters. The rest of the block, if present, is skipped.
It returns the number of
characters read, or 0 for a tape mark, or EOF for an end-of-file.
On a tape image file the end-of-file coincides with the actual end-of-file.
On a real magtape or on a character device three conditions can cause
an end-of-file:
.IP
4 consecutive tape marks,
.br
2 consecutive read errors, or
.br
1 read error preceded by a tape mark,
.PP
unless "rx" was specified in the call of
.IR tpopen ,
in which case only 100 consecutive read errors will cause an end-of-file
(to prevent the program from looping on end-of-reel).
.PP
.I Tpwrite
writes one block from
.I buf
if
.I size
> 0, or a tape mark if
.I size
= 0.
.SH DIAGNOSTICS
If an error condition (other than end-of-file) is found, a message is
printed on the stream
.I tperr
(default is
.IR stderr ),
and the program exits.
.SH AUTHOR
Dick Grune.
<<>EndOfFile<>>
if test "43710     3" != "`sum \m\a\g\.\3`"
then echo Checksum error; fi
echo x \r\a\w\t\p\.\1
cat >\r\a\w\t\p\.\1 <<'<<>EndOfFile<>>'
.TH RAWTP I
.SH NAME
rawtp \- read raw tape
.SH SYNOPSIS
.B rawtp
name [
.B \-x
] [ param ... ]
.SH DESCRIPTION
.I Rawtp
extracts arbitrary portions from a magtape.
.PP
The program accepts the usual
.B \-cfhlm
parameters to describe the tape (see
.IR mag (I)).
The additional
.BR \-x -option
will cause
.I rawtp
to continue reading regardless of read-errors or consecutive tape marks
(normally
.I rawtp
stops after 4 consecutive tape marks).
.PP
The tape is considered as a series of files, each terminated by a
tape mark (TM); a file is considered as a series of blocks, each
terminated by an InterRecord Gap (IRG); a block consists of characters.
.PP
An instruction
.I +t.i.c
or
.I \-t.i.c
moves the tape over
.I t
TM's,
.I i
IRG's (but not over a TM) and
.I c
characters (but not over an IRG). If the instruction begins with a
.I +
the contents are copied to a file, a
.I \-
just skips the contents.
.PP
Instructions may be concatenated into an instruction series. If an
instruction sequence is followed by
.BI x n
the effect is repeated
.I n
times. If
.I n
is absent or 0, the instruction series is repeated until it becomes
ineffective. E.g.,
.I +.1\-1x
will give you the first block of each file on tape.
Default parameter is
.I +1x
which splits the tape into its separate files.
.PP
The produced files are named
.I namepprrrii
where 
.I name
is the first argument,
.I pp
is the two-digit parameter number,
.I rrr
is a three-letter counter counting the number of repetitions of the
parameter, and
.I ii
is the two-digit instruction number within the parameter.
.PP
Example:

.br
	rawtp  tp  \-10  +..80\-3x

will skip 10 files, and then give the first 80 characters of the first
block of every third file on the files
.IR tp02aaa01 ,
.IR tp02aab01 ,
etc, (if present.)
.SH "SEE ALSO"
mag(I),
cptp(I),
survey(I)
<<>EndOfFile<>>
if test "03147     2" != "`sum \r\a\w\t\p\.\1`"
then echo Checksum error; fi
echo x \r\a\w\t\p\.\c
cat >\r\a\w\t\p\.\c <<'<<>EndOfFile<>>'
#define	MSGUSE	"Usage is: rawtp [-cfhlmx] XX [ param ... ]"
#include	<stdio.h>
#include	"tp.h"
/*
 * Name: rawtp, read raw tape
 * Author: Dick Grune
 * Version: 820314
 *
   Selected portions are read from tape and written to files.

*/

#define	TRUE	1
#define	FALSE	0
#define	EOS	'\0'

#define	EOB	0	/* End Of Block */
#define	EOX	-1	/* End Of File (to avoid confusion with EOF) */
#define	EOT	-2	/* End Of Tape */
#define	AT_EOB	(ilength <= EOB)
#define	AT_EOX	(ilength <= EOX)
#define	AT_EOT	(ilength <= EOT)

char name [128];
char *eoname = &name[0];
FILE *ofile = NULL;

TPFILE *tape;
extern FILE *tperr;
int unit = 0;
char *nmdns = TP_DENN;
char *rx = "r";
char buff[TP_MAXB];

char *strins();

main(argc, argv) char **argv;	{
	extern int ilength;

	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 'x':
				rx = "rx";
				break;
			default:
				goto Lbad;
			}
			argc--; argv++;
		}
	}
	if (argc < 1)
		goto Lbad;
	if (**argv == '+' || **argv == '-')
		goto Lbad;
	set_name(argv);
	argc--; argv++;

	ilength = EOT;	/* fake empty tape to test parameters */
	params(argc, argv);

	tape = tpopen(unit, nmdns, rx);
	tperr = stdout;
	ilength = EOB;	/* and now for keeps */
	skipIRG();
	params(argc, argv);
	tpclose(tape);
	exit(0);

Lbad:
	error(MSGUSE, "");
}

set_name(argv) char **argv;	{
	register char *pt;

	eoname = strins(eoname, *argv);
	eoname = strins(eoname, "01aaa01");
	*eoname = EOS;
	for (pt = eoname; pt > name; pt--)
		if (pt[-1] == '/') break;
	if (eoname - pt > 14)
		error("%s: file name too long", name);
}

params(argc, argv) char **argv;	{

	VOID(strins(eoname-7, "01"));
	if (!argc)
		param("+1x");
	else
		while (argc--)	{
			param(*argv++);
			incr(eoname-6);
		}
}

char *ppar;	/* parameter being processed */

param(arg) char *arg;	{
	register int repl;

	ppar = arg;
	repl = getxrepl(ppar);
	if (repl == 0) repl--;
	VOID(strins(eoname-5, "aaa"));
	while (repl-- && instr())
		incr(eoname-3);
}

int moved;

int
instr()	{
	char *p = ppar;

	moved = FALSE;
	VOID(strins(eoname-2, "01"));
	while (simp_instr(&p))
		incr(eoname-1);
	return moved;
}

int copy = FALSE;

int
simp_instr(pp) char **pp;	{
	register int cnt;

	switch (**pp)	{
	case EOS:
	case 'x':
		return FALSE;
	case '+':
		copy = TRUE;
		break;
	case '-':
		copy = FALSE;
		break;
	default:
		error("%s: bad parameter", ppar);
	}
	(*pp)++;
	cnt = getint(pp);
	while (cnt-- && copyfile()) {}
	if (**pp == '.')
		(*pp)++;
	cnt = getint(pp);
	while (cnt-- && copyblock()) {}
	if (**pp == '.')
		(*pp)++;
	cnt = getint(pp);
	while (cnt-- && copychar()) {}
	if (copy)
		dropfile();
	return TRUE;
}

int ilength;

/* ilength contains the number of characters the tape is ahead of the user;
 * or it is EOX or EOT
 */
char *iptr;

int
copyfile()	{

	if (AT_EOT)
		return FALSE;
	while (copyblock())	{}
	skipTM();
	return TRUE;
}

int
copyblock()	{

	if (AT_EOX)
		return FALSE;
	if (!copy)
		ilength = EOB;
	else
		while (copychar())	{}
	skipIRG();
	return TRUE;
}

int
copychar()	{

	if (AT_EOB)
		return FALSE;
	outchar(*iptr);
	iptr++;
	ilength--;
	return TRUE;
}

outchar(c)	{

	if (!copy)
		return;
	if (ofile == NULL)	{
		getfile();
		moved = TRUE;
	}
	putc(c, ofile);
}

/* physical tape movers */

skipTM()	{

	if (AT_EOT)
		return;
	ilength = EOB;
	skipIRG();
}

skipIRG()	{
	int size;

	if (AT_EOX)
		return;
	size = tpread(tape, buff, TP_MAXB);
	ilength = size == EOF ? EOT : size == 0 ? EOX : size;
	iptr = buff;
	if (!AT_EOT)
		moved = TRUE;
}

/* output file registration */

getfile()	{

	if ((ofile = fopen(name, "w")) == NULL)
		error("%s: cannot create", name);
}

dropfile()	{

	if (ofile != NULL)
		VOID(fclose(ofile));
	ofile = NULL;
}

/* service routines */

char *
strins(s1, s2) char *s1, *s2;	{

	while (*s2 != EOS)
		*s1++ = *s2++;
	return s1;
}

int
getint(pp) char **pp;	{
	register int val, res = 0;

	for (;;)	{
		val = **pp - '0';
		if (val < 0 || val > 9)
			return res;
		(*pp)++;
		res = res*10 + val;
	}
}

incr(p) char *p;	{

	(*p)++;
	if (*p == '9' + 1)	{
		*p = '0'; incr(p-1);
	}
	else
	if (*p == 'z' + 1)	{
		*p = 'a'; incr(p-1);
	}
}

int
getxrepl(p) char *p;	{
	register int r;

	while (*p != 'x')
		if (!*p++)
			return 1;
	p++;
	r = getint(&p);
	if (*p)
		error("%s: bad replicator", p);
	return r;
}

error(p1, p2) char *p1, *p2;	{

	printf(p1, p2);
	printf("\n");
	exit(1);
}
<<>EndOfFile<>>
if test "12914     5" != "`sum \r\a\w\t\p\.\c`"
then echo Checksum error; fi
echo x \s\u\r\v\e\y\.\1
cat >\s\u\r\v\e\y\.\1 <<'<<>EndOfFile<>>'
.TH SURVEY I
.SH NAME
survey \- survey contents of a magtape
.SH SYNOPSIS
.B survey
[
.B \-px
]
.SH DESCRIPTION
.I Survey
lists the lengths of the blocks on a magtape. If the
.B \-p
option is given, the first characters of each block are displayed
in ASCII, EBCDIC and hexadecimal.
.PP
The program accepts the usual
.B \-cfhlm
parameters to describe the tape (see
.IR mag (I)).
The additional
.BR \-x -option
will cause
.I rawtp
to continue reading regardless of read-errors or consecutive tape marks
(normally
.I rawtp
stops after 4 consecutive tape marks).
.SH SEE ALSO
mag(I)
<<>EndOfFile<>>
if test "11624     1" != "`sum \s\u\r\v\e\y\.\1`"
then echo Checksum error; fi
echo x \s\u\r\v\e\y\.\c
cat >\s\u\r\v\e\y\.\c <<'<<>EndOfFile<>>'
#define	MSGUSE	"Usage is: survey [-cfhlmpx]\n"
#include	"tp.h"
#include	<ctype.h>
#define	WIDTH	64

/*
 * Name: survey, survey contents of magtape
 * Author: Dick Grune
 * Version: 820314
 */

int unit = 0;
char *nmdns = TP_DENN;
char *rx = "r";
TPFILE *tf;
extern FILE *tperr;
char buff[TP_MAXB];
int size;

char pflag = 0;

main(argc, argv) 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 'h':
				nmdns = TP_DENH;
				break;
			case 'l':
				nmdns = TP_DENL;
				break;
			case 'm':
				unit = *++pp - '0';
				break;
			case 'p':
				pflag = 1;
				break;
			case 'x':
				rx = "rx";
				break;
			default:
				goto Lbad;
			}
			argc--; argv++;
		}
	}

	if (argc != 0)
		goto Lbad;

	tf = tpopen(unit, nmdns, rx);

	while ((size = tpread(tf, buff, TP_MAXB)) != EOF)	{
		printf("%6d", size);
		if (pflag)
			expose();
		printf("\n");
	}
	exit(0);

Lbad:
	fprintf(stderr, MSGUSE);
	exit(1);
}

int
hex(c)	char c;	{
	return "0123456789ABCDEF"[c&017];
}

expose()	{
	if (size == 0)
		printf("\t* * * TAPE MARK * * *\n");
	else	{
		int i;
		printf("\t");
		for (i = 0; i < WIDTH && i < size; i++)	{
			char c = buff[i];
			printf("%c", isascii(c) &&
				(isprint(c) || c == ' ') ? c : '?');
		}
		printf("\n  EBC:\t");
		for (i = 0; i < WIDTH && i < size; i++)	{
			char c = ebc2asc(buff[i]);
			printf("%c", isascii(c) &&
				(isprint(c) || c == ' ') ? c : '?');
		}
		printf("\n  HEX:\t");
		for (i = 0; i < WIDTH/2 && i < size; i++)	{
			char c = buff[i];
			printf("%c%c", hex(c>>4), hex(c));
		}
		printf("\n");
	}
}
<<>EndOfFile<>>
if test "33446     2" != "`sum \s\u\r\v\e\y\.\c`"
then echo Checksum error; fi
echo x \t\p\.\h
cat >\t\p\.\h <<'<<>EndOfFile<>>'
#ifndef TPFILE
#ifndef FILE
#include	<stdio.h>
#endif

#define	TP_CDEV	(-1)
#define	TP_IMAG	(-2)
#define	_TP_PREF	8

struct tpdes	{
	int _tp_unit;		/* unit number or TP_IMAG  or TP_CDEV  */
	char *_tp_nmdns;	/* density     or filename or filename */
	char _tp_rw;		/* 'r' for reading, 'w' for writing */
	char _tp_x;		/* 'x' for persistent, ' ' for normal */
	char _tp_fildes;	/* file descriptor after `open' */

	long _tp_blkc;		/* block counter */
	int _tp_mkc;		/* accumulative TM counter */
	int _tp_mc;		/* consecutive TM counter */
	char _tp_eof;		/* EOF-flag */
};
#define	TPFILE	struct tpdes

#define	char2int(c)	((c)&0377)
#define	ebc2asc(c)	(_etoa[char2int(c)])
#define	english(i)	(i), (i) == 1 ? "" : "s"	/* plural */
#define	n_items(a)	(sizeof (a)/sizeof (a)[0])
extern TPFILE *tpopen();
extern char *_tpname();
extern char _etoa[];
extern FILE *tperr;		/* for error messages */

#ifdef	lint
int __void__;	/* to tell `lint' not to care */
#define	VOID(x)	(__void__ = (int)(x))
#else	lint
#define	VOID(x)	(x)
#endif	lint

#include	"tploc.h"

#endif
<<>EndOfFile<>>
if test "21318     2" != "`sum \t\p\.\h`"
then echo Checksum error; fi
echo x \t\p\L\O\C\.\h
cat >\t\p\L\O\C\.\h <<'<<>EndOfFile<>>'
/* #define's which may require local modification */
/* 
 * The macros TP_DEN[LNH] define the names of the low-density,
 * normal-density and high-density tape devices, as double-strings.
 * The first string indicates the read name, the second the write name;
 * a ? is replaced by the unit number.
 */
#define	TP_DENL	"/dev/rmt?\0/dev/nrmt?"
#define	TP_DENN	TP_DENL
#define	TP_DENH	"/dev/rmth\0/dev/nrmth"

#define	TP_MAXB	32766	/* largest size parameter to `read' & `write' */
#define	_TP_MOPEN 2	/* maximum number of simultaneously open tape-files */
<<>EndOfFile<>>
if test "11998     1" != "`sum \t\p\L\O\C\.\h`"
then echo Checksum error; fi
echo x \t\p\c\l\o\s\e\.\c
cat >\t\p\c\l\o\s\e\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"

tpclose(tf) TPFILE *tf;	{

	VOID(close(tf->_tp_fildes));
	if (tf->_tp_rw == 'w')	{
		tf->_tp_rw = 'r';
		VOID(close(open(_tpname(tf), 0)));
	}
	tf->_tp_fildes = 0;
}
<<>EndOfFile<>>
if test "57249     1" != "`sum \t\p\c\l\o\s\e\.\c`"
then echo Checksum error; fi
echo x \t\p\l\o\c\.\h
cat >\t\p\l\o\c\.\h <<'<<>EndOfFile<>>'
/* #define's which may require local modification */
/* 
 * The macros TP_DEN[LNH] define the names of the low-density,
 * normal-density and high-density tape devices, as double-strings.
 * The first string indicates the read name, the second the write name;
 * a ? is replaced by the unit number.
 */
#define	TP_DENL	"/dev/rmt0\0/dev/nrmt0"
#define	TP_DENN	TP_DENL
#define	TP_DENH	"/dev/rmt8\0/dev/nrmt8"

#define	TP_MAXB	32766	/* largest size parameter to `read' & `write' */
#define	_TP_MOPEN 2	/* maximum number of simultaneously open tape-files */
<<>EndOfFile<>>
if test "46780     1" != "`sum \t\p\l\o\c\.\h`"
then echo Checksum error; fi
echo x \t\p\n\a\m\e\.\c
cat >\t\p\n\a\m\e\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"

char * /* transient */
_tpname(tf) TPFILE *tf;	{
	static char name[20];
	int unit = tf->_tp_unit;
	char *nmdns = tf->_tp_nmdns;
	char *nm = name;

	if (unit == TP_IMAG || unit == TP_CDEV)
		return nmdns;

	if (tf->_tp_rw == 'w')
		while (*nmdns++) {}
	do
		*nm++ = *nmdns == '?' ? unit + '0' : *nmdns;
	while (*nmdns++);

	return name;
}
<<>EndOfFile<>>
if test "46315     1" != "`sum \t\p\n\a\m\e\.\c`"
then echo Checksum error; fi
echo x \t\p\o\p\e\n\.\c
cat >\t\p\o\p\e\n\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"

TPFILE *
tpopen(unit, nmdns, rwx) char *nmdns, *rwx;	{
	char *name = NULL, *err;
	static TPFILE tflist[_TP_MOPEN];
	TPFILE *tf;

	for (tf = &tflist[0]; tf->_tp_fildes != 0; tf++)
		if (tf == &tflist[_TP_MOPEN-1])	{
			err = "Too many tapes";
			goto Lerr;
		}

	tf->_tp_unit = unit; tf->_tp_nmdns = nmdns;
	tf->_tp_rw = 'r'; tf->_tp_x = ' ';
	while (*rwx)	switch (*rwx++)	{
	case 'r':
		tf->_tp_rw = 'r';
		break;
	case 'w':
		tf->_tp_rw = 'w';
		break;
	case 'x':
		tf->_tp_x = 'x';
		break;
	default:
		err = "Bad option in tpopen";
		goto Lerr;
	}
	tf->_tp_blkc = tf->_tp_mkc = tf->_tp_mc = tf->_tp_eof = 0;

	name = _tpname(tf);

	if (tf->_tp_rw == 'w')	{
		if ((tf->_tp_fildes = creat(name, 0666)) < 0)	{
			err = "cannot create";
			goto Lerr;
		}
	}
       	else	{
	       	if ((tf->_tp_fildes = open(name, 0)) < 0)	{
	       		err = "cannot open";
			goto Lerr;
		}
	}

	return tf;

Lerr:
	if (name != NULL)
		fprintf(tperr, "%s: ", name);
	fprintf(tperr, "%s\n", err);
	exit(1);
	return NULL;
}

FILE *tperr = stderr;

_tprwerr(msg, tf) char *msg; TPFILE *tf;	{
	fprintf(tperr,
		"After %d tape mark%s, after %D block%s: %s on %s\n",
		english(tf->_tp_mkc), english(tf->_tp_blkc), msg, _tpname(tf));
}
<<>EndOfFile<>>
if test "55927     2" != "`sum \t\p\o\p\e\n\.\c`"
then echo Checksum error; fi
echo x \t\p\r\d\L\O\C\.\c
cat >\t\p\r\d\L\O\C\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"
/*
 * _tprdloc reads one block from a character device.
 * It must take care of local restrictions.
 *
 * This is the PDP11/45 version (even block size).
 */

int
_tprdloc(tf, buf, size) TPFILE *tf; char *buf;	{
	char ch = buf[size];
	int sz = read(tf->_tp_fildes, buf, size % 2 ? size + 1 : size);
	buf[size] = ch;
	return sz > size ? size : sz;
}
<<>EndOfFile<>>
if test "24065     1" != "`sum \t\p\r\d\L\O\C\.\c`"
then echo Checksum error; fi
echo x \t\p\r\d\l\o\c\.\c
cat >\t\p\r\d\l\o\c\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"
/*
 * _tprdloc reads one block from a character device.
 * It must take care of local restrictions.
 *
 * This is the VAX version (anything goes).
 */

int
_tprdloc(tf, buf, size) TPFILE *tf; char *buf;	{
	return read(tf->_tp_fildes, buf, size);
}
<<>EndOfFile<>>
if test "65025     1" != "`sum \t\p\r\d\l\o\c\.\c`"
then echo Checksum error; fi
echo x \t\p\r\e\a\d\.\c
cat >\t\p\r\e\a\d\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"

int
tpread(tf, buf, size) TPFILE *tf; char *buf;	{
	int res;

	if (tf->_tp_eof)
		return EOF;

	if (tf->_tp_unit != TP_IMAG && tf->_tp_x == ' ' && tf->_tp_mc >= 4)
		res = EOF;
	else
		res = _tp_rd(tf, buf, size);

	if (res > 0)	{
		tf->_tp_blkc++;
		tf->_tp_mc = 0;
	}
	else
	if (res == 0)	{
		tf->_tp_mkc++;
		tf->_tp_mc++;
		tf->_tp_blkc = 0;
	}
	else
	if (res == EOF)	{
		tf->_tp_mc = 0;
	}

	return size < res ? size : res;
}

int
_tp_rd(tf, buf, size) TPFILE *tf; char *buf;	{
	int sz;
	char ch;

	if (size <= 0)	{
		buf = &ch;
		size = 1;
	}

	if (tf->_tp_unit == TP_IMAG)	{
		char pref[_TP_PREF];
		int n;
		char ch;

		n = read(tf->_tp_fildes, pref, _TP_PREF);
		if (n == 0)
			return EOF;
		if (n != _TP_PREF)
			goto Lformerr;
		sz = 0;
		for (n = 0; n < _TP_PREF; n++)	{
			int dig = pref[n] - '0';
			if (dig < 0 || dig > 9)
				goto Lformerr;
			sz = sz*10 + dig;
		}
		n = sz < size ? sz : size;
		if (n > 0)
			if (read(tf->_tp_fildes, buf, n) != n)
				goto Lformerr;
		while (sz-- > size)
			if (read(tf->_tp_fildes, &ch, 1) != 1)
				goto Lformerr;
		return n;

	Lformerr:
		_tprwerr("tape image error", tf);
		exit(1);
	}
	else	{
		int erc = 0;

		while ((sz = _tprdloc(tf, buf, size)) < 0)	{
			_tprwerr("garbage", tf);
			erc++;
			if (	(tf->_tp_x == ' ' &&
					(tf->_tp_mc > 0 || erc >= 2))
				||
				(tf->_tp_x == 'x' &&
					erc >= 100)
			)	return EOF;
		}
		return sz;
	}
	return EOF;	/* stupid `lint' */
}
<<>EndOfFile<>>
if test "27746     2" != "`sum \t\p\r\e\a\d\.\c`"
then echo Checksum error; fi
echo x \t\p\w\L\O\C\.\c
cat >\t\p\w\L\O\C\.\c <<'<<>EndOfFile<>>'

#include	"tp.h"
/*
 * _tpwloc writes one block of non-zero length to a character device.
 * It must take care of local restrictions.
 *
 * This is the PDP11/45 version (even block size).
 */

int
_tpwloc(tf, buf, size) TPFILE *tf; char *buf;	{
	int sz = write(tf->_tp_fildes, buf, size % 2 ? size + 1 : size);
	return sz > size ? size : sz;
}
<<>EndOfFile<>>
if test "13756     1" != "`sum \t\p\w\L\O\C\.\c`"
then echo Checksum error; fi
echo x \t\p\w\l\o\c\.\c
cat >\t\p\w\l\o\c\.\c <<'<<>EndOfFile<>>'

#include	"tp.h"
/*
 * _tpwloc writes one block of non-zero length to a character device.
 * It must take care of local restrictions.
 *
 * This is the VAX version (anything goes).
 */

int
_tpwloc(tf, buf, size) TPFILE *tf; char *buf;	{
	return write(tf->_tp_fildes, buf, size);
}
<<>EndOfFile<>>
if test "64955     1" != "`sum \t\p\w\l\o\c\.\c`"
then echo Checksum error; fi
echo x \t\p\w\r\i\t\e\.\c
cat >\t\p\w\r\i\t\e\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"

tpwrite(tf, buf, size) TPFILE *tf; char *buf;	{

	if (tf->_tp_unit == TP_IMAG)	{
		int i, n = size;
		char pref[_TP_PREF];
		for (i = _TP_PREF; i--; )	{
			pref[i] = n % 10 + '0';
			n = n / 10;
		}
		if (write(tf->_tp_fildes, pref, _TP_PREF) != _TP_PREF)
			goto Lerr;
		if (size > 0)
			if (write(tf->_tp_fildes, buf, size) != size)
				goto Lerr;
	}
	else	{
		if (size == 0)
			_tpwtmloc(tf);
		else
		if (_tpwloc(tf, buf, size) != size)
			goto Lerr;
	}

	if (size == 0)	{
		tf->_tp_mkc++;
		tf->_tp_blkc = 0;
	}
	else	{
		tf->_tp_blkc++;
	}

	return;

Lerr:
	_tprwerr("write error", tf);
	exit(1);
}
<<>EndOfFile<>>
if test "61518     1" != "`sum \t\p\w\r\i\t\e\.\c`"
then echo Checksum error; fi
echo x \t\p\w\t\m\l\o\c\.\c
cat >\t\p\w\t\m\l\o\c\.\c <<'<<>EndOfFile<>>'
#include	"tp.h"
/*
 * _tpwtmloc writes a tape mark to a character device.
 * It must take care of the local restrictions.
 */

_tpwtmloc(tf) TPFILE *tf;	{

	VOID(close(tf->_tp_fildes));
	tf->_tp_fildes = open(_tpname(tf), 1);
}
<<>EndOfFile<>>
if test "33727     1" != "`sum \t\p\w\t\m\l\o\c\.\c`"
then echo Checksum error; fi
echo x \t\e\s\t\.\i\m\a\g\e
cat >\t\e\s\t\.\i\m\a\g\e <<'<<>EndOfFile<>>'
00000080VOL1222222                           dick                                      100000080HDR1READ_ME.TEST     22222200010001000100 82106 82106 000000Math. Centre        00000080HDR2F0192000080                                   00                            0000000000001920SALES 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.           00001200        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.                                                          0000000000000080EOF1READ_ME.TEST     22222200010001000100 82106 82106 000002Math. Centre        00000080EOF2F0192000080                                   00                            0000000000000000
<<>EndOfFile<>>
if test "11352     4" != "`sum \t\e\s\t\.\i\m\a\g\e`"
then echo Checksum error; fi
-- 
					Dick Grune
					Vrije Universiteit
					de Boelelaan 1081
					1081 HV  Amsterdam
					the Netherlands