[net.sources] magtape handling, part 1 of 2, reposting

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