V8/usr/src/cmd/pr.c
#include <stdio.h>
/*
* PR command (print files in pages and columns, with headings)
* 2+head+2+page[56]+5
*/
#define ESC '\033'
#define LENGTH 66
#define LINEW 72
#define NUMW 5
#define MARGIN 10
#define DEFTAB 8
FILE *fopen(), *mustopen();
char nulls[] = "";
typedef struct { FILE *f_f; char *f_name; int f_nextc; } FILS;
FILS *Files;
int Multi = 0, Nfiles = 0, Error = 0, Balance = 0, onintr();
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
typedef char CHAR;
typedef int ANY;
typedef unsigned UNS;
#define NFILES 10
int Mode;
char *ttyname(), *Ttyout, obuf[BUFSIZ];
#define istty(F) ttyname(fileno(F))
/* ARGSUSED */
fixtty(argc, argv) char **argv;
{
struct stat sbuf;
setbuf(stdout, obuf);
if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, onintr);
if (Ttyout= istty(stdout)) {
stat(Ttyout, &sbuf);
Mode = sbuf.st_mode&0777;
chmod(Ttyout, 0600);
}
return (argc);
}
#define done() if (Ttyout) chmod(Ttyout, Mode)
#define INTREXIT _exit
char *GETDATE() /* return date file was last modified */
{
char *ctime();
static char *now = NULL;
static struct stat sbuf, nbuf;
if (Nfiles > 1 || Files->f_name == nulls) {
if (now == NULL)
{ time(&nbuf.st_mtime); now = ctime(&nbuf.st_mtime); }
return (now);
} else {
stat(Files->f_name, &sbuf);
return (ctime(&sbuf.st_mtime));
}
}
#define CADDID()
#define HEAD "%12.12s %4.4s %s Page %d\n\n\n", date+4, date+20, head, Page
#define TOLOWER(c) (isupper(c) ? tolower(c) : c) /* ouch! */
#define cerror(S) fprintf(stderr, "pr: %s", S)
char *ffiler(s) char *s;
{
static char buf[100];
sprintf(buf, "can't open %s", s);
return (buf);
}
#define STDINNAME() nulls
#define TTY "/dev/tty", "r"
#define PROMPT() putc('\7', stderr) /* BEL */
#define TABS(N,C) if ((N = intopt(argv, &C)) < 0) N = DEFTAB
#define INSTABS() if (Itabn == 0 && Ttyout) Itabn = DEFTAB
#define ETABS (Inpos % Etabn)
#define ITABS (Itabn > 0 && Nspace >= (nc = Itabn - Outpos % Itabn))
#define NSEPC '\t'
ANY *getspace();
main(argc, argv) char *argv[];
{
FILS fstr[NFILES];
int nfdone = 0;
Files = fstr;
for (argc = findopt(argc, argv); argc > 0; --argc, ++argv)
if (Multi == 'm') {
if (Nfiles >= NFILES - 1) die("too many files");
if (mustopen(*argv, &Files[Nfiles++]) == NULL)
++nfdone; /* suppress printing */
} else {
if (print(*argv))
fclose(Files->f_f);
++nfdone;
}
if (!nfdone) /* no files named, use stdin */
print(nulls); /* on GCOS, use current file, if any */
errprint(); /* print accumulated error reports */
exit(Error);
}
long Lnumb = 0;
FILE *Ttyin = stdin;
int Dblspace = 1, Fpage = 1, Formfeed = 0,
Length = LENGTH, Linew = 0, Offset = 0, Ncols = 1, Pause = 0, Sepc = 0,
Colw, Plength, Margin = MARGIN, Numw, Nsepc = NSEPC, Report = 1,
Etabn = 0, Etabc = '\t', Itabn = 0, Itabc = '\t';
char *Head = NULL;
CHAR *Buffer = NULL, *Bufend;
typedef struct { CHAR *c_ptr, *c_ptr0; long c_lno; } *COLP;
COLP Colpts;
findopt(argc, argv) char *argv[];
{
char **eargv = argv;
int eargc = 0, c;
argc = fixtty(argc, argv);
while (--argc > 0) {
switch (c = **++argv) {
case '-':
if ((c = *++*argv) == '\0') break;
case '+':
do {
if (isdigit(c))
{ --*argv; Ncols = atoix(argv); }
else switch (c = TOLOWER(c)) {
case '+': if ((Fpage = atoix(argv)) < 1)
Fpage = 1;
continue;
case 'd': Dblspace = 2; continue;
case 'e': TABS(Etabn, Etabc); continue;
case 'f': ++Formfeed; continue;
case 'h': if (--argc > 0) Head = argv[1];
continue;
case 'i': TABS(Itabn, Itabc); continue;
case 'l': Length = atoix(argv); continue;
case 'a':
case 'm': Multi = c; continue;
case 'o': Offset = atoix(argv); continue;
case 'p': ++Pause; continue;
case 'r': Report = 0; continue;
case 's':
if ((Sepc = (*argv)[1]) != '\0') ++*argv;
else Sepc = '\t';
continue;
case 't': Margin = 0; continue;
case 'w': Linew = atoix(argv); continue;
case 'n':
case 'x': /* retained for historical reasons */
++Lnumb;
if ((Numw = intopt(argv, &Nsepc)) <= 0)
Numw = NUMW;
case 'b': Balance = 1; continue;
case 'q': /* retained for historical reasons */
case 'j': /* ignore GCOS jprint option */
continue;
default : die("bad option");
}
} while ((c = *++*argv) != '\0');
if (Head == argv[1]) ++argv;
continue;
}
*eargv++ = *argv;
++eargc;
}
if (Length == 0) Length = LENGTH;
if (Length <= Margin) Margin = 0;
Plength = Length - Margin/2;
if (Multi == 'm') Ncols = eargc;
switch (Ncols) {
case 0:
Ncols = 1;
case 1:
break;
default:
if (Etabn == 0) /* respect explicit tab specification */
Etabn = DEFTAB;
INSTABS();
}
if (Linew == 0) Linew = Ncols != 1 && Sepc == 0 ? LINEW : 512;
if (Lnumb) Linew -= Multi == 'm' ? Numw : Numw * Ncols;
if ((Colw = (Linew - Ncols + 1)/Ncols) < 1)
die("width too small");
if (Ncols != 1 && Multi == 0) {
UNS buflen = ((UNS)(Plength/Dblspace + 1))*(Linew+1)*sizeof(CHAR);
Buffer = (CHAR *)getspace(buflen);
Bufend = &Buffer[buflen];
Colpts = (COLP)getspace((UNS)((Ncols+1)*sizeof(*Colpts)));
}
if (Ttyout && (Pause || Formfeed) && !istty(stdin))
Ttyin = fopen(TTY);
return (eargc);
}
intopt(argv, optp) char *argv[]; int *optp;
{
int c;
if ((c = (*argv)[1]) != '\0' && !isdigit(c)) { *optp = c; ++*argv; }
return ((c = atoix(argv)) != 0 ? c : -1);
}
int Page, C = '\0', Nspace, Inpos;
print(name) char *name;
{
static int notfirst = 0;
char *date = NULL, *head = NULL;
int c;
if (Multi != 'm' && mustopen(name, &Files[0]) == NULL) return (0);
if (Buffer) ungetc(Files->f_nextc, Files->f_f);
if (Lnumb) Lnumb = 1;
for (Page = 0; ; putpage()) {
if (C == EOF) break;
if (Buffer) nexbuf();
Inpos = 0;
if (get(0) == EOF) break;
fflush(stdout);
if (++Page >= Fpage) {
if (Ttyout && (Pause || Formfeed && !notfirst++)) {
PROMPT(); /* prompt with bell and pause */
while ((c = getc(Ttyin)) != EOF && c != '\n') ;
}
if (Margin == 0) continue;
CADDID();
if (date == NULL) date = GETDATE();
if (head == NULL) head = Head != NULL ? Head :
Nfiles < 2 ? Files->f_name : nulls;
printf("\n\n");
Nspace = Offset;
putspace();
printf(HEAD);
}
}
C = '\0';
return (1);
}
int Outpos, Lcolpos, Pcolpos, Line;
putpage()
{
register int colno;
for (Line = Margin/2; ; get(0)) {
for (Nspace = Offset, colno = 0, Outpos = 0; C != '\f'; ) {
if (Lnumb && C != EOF && (colno == 0 || Multi == 'a')) {
if (Page >= Fpage) {
putspace();
printf("%*ld", Numw, Buffer ?
Colpts[colno].c_lno++ : Lnumb);
Outpos += Numw;
put(Nsepc);
}
++Lnumb;
}
for (Lcolpos = 0, Pcolpos = 0;
C != '\n' && C != '\f' && C != EOF; get(colno))
put(C);
if (C == EOF || ++colno == Ncols ||
C == '\n' && get(colno) == EOF) break;
if (Sepc) put(Sepc);
else if ((Nspace += Colw - Lcolpos + 1) < 1) Nspace = 1;
}
/*
if (C == EOF) {
if (Margin != 0) break;
if (colno != 0) put('\n');
return;
}
*/
if (C == EOF && colno == 0) {
if (Margin != 0) break;
return;
}
if (C == '\f') break;
put('\n');
if (Dblspace == 2 && Line < Plength) put('\n');
if (Line >= Plength) break;
}
if (Formfeed) put('\f');
else while (Line < Length) put('\n');
}
nexbuf()
{
register CHAR *s = Buffer;
register COLP p = Colpts;
int j, c, bline = 0;
for ( ; ; ) {
p->c_ptr0 = p->c_ptr = s;
if (p == &Colpts[Ncols]) return;
(p++)->c_lno = Lnumb + bline;
for (j = (Length - Margin)/Dblspace; --j >= 0; ++bline)
for (Inpos = 0; ; ) {
if ((c = getc(Files->f_f)) == EOF) {
for (*s = EOF; p <= &Colpts[Ncols]; ++p)
p->c_ptr0 = p->c_ptr = s;
if (Balance)
balance(bline);
return;
}
if (isprint(c)) ++Inpos;
if (Inpos <= Colw || c == '\n') {
*s = c;
if (++s >= Bufend)
die("page-buffer overflow");
}
if (c == '\n') break;
switch (c) {
case '\b': if (Inpos == 0) --s;
case ESC: if (Inpos > 0) --Inpos;
}
}
}
}
balance(bline) /* line balancing for last page */
{
register CHAR *s = Buffer;
register COLP p = Colpts;
int colno = 0, j, c, l;
c = bline % Ncols;
l = (bline + Ncols - 1)/Ncols;
bline = 0;
do {
for (j = 0; j < l; ++j)
while (*s++ != '\n') ;
(++p)->c_lno = Lnumb + (bline += l);
p->c_ptr0 = p->c_ptr = s;
if (++colno == c) --l;
} while (colno < Ncols - 1);
}
get(colno)
{
static int peekc = 0;
register COLP p;
register FILS *q;
register int c;
if (peekc)
{ peekc = 0; c = Etabc; }
else if (Buffer) {
p = &Colpts[colno];
if (p->c_ptr >= (p+1)->c_ptr0) c = EOF;
else if ((c = *p->c_ptr) != EOF) ++p->c_ptr;
} else if ((c =
(q = &Files[Multi == 'a' ? 0 : colno])->f_nextc) == EOF) {
for (q = &Files[Nfiles]; --q >= Files && q->f_nextc == EOF; ) ;
if (q >= Files) c = '\n';
} else
q->f_nextc = getc(q->f_f);
if (Etabn != 0 && c == Etabc) {
++Inpos;
peekc = ETABS;
c = ' ';
} else if (isprint(c))
++Inpos;
else
switch (c) {
case '\b':
case ESC:
if (Inpos > 0) --Inpos;
break;
case '\f':
if (Ncols == 1) break;
c = '\n';
case '\n':
case '\r':
Inpos = 0;
}
return (C = c);
}
put(c)
{
int move;
switch (c) {
case ' ':
++Nspace; ++Lcolpos; return;
case '\b':
if (Lcolpos == 0) return;
if (Nspace > 0) { --Nspace; --Lcolpos; return; }
if (Lcolpos > Pcolpos) { --Lcolpos; return; }
case ESC:
move = -1;
break;
case '\n':
++Line;
case '\r':
case '\f':
Pcolpos = 0; Lcolpos = 0; Nspace = 0; Outpos = 0;
default:
move = (isprint(c) != 0);
}
if (Page < Fpage) return;
if (Lcolpos > 0 || move > 0) Lcolpos += move;
if (Lcolpos <= Colw) {
putspace();
putchar(c);
Pcolpos = Lcolpos;
Outpos += move;
}
}
putspace()
{
int nc;
for ( ; Nspace > 0; Outpos += nc, Nspace -= nc)
if (ITABS)
putchar(Itabc);
else {
nc = 1;
putchar(' ');
}
}
atoix(p) register char **p;
{
register int n = 0, c;
while (isdigit(c = *++*p)) n = 10*n + c - '0';
--*p;
return (n);
}
/* Defer message about failure to open file to prevent messing up
alignment of page with tear perforations or form markers.
Treat empty file as special case and report as diagnostic.
*/
#define EMPTY 14 /* length of " -- empty file" */
typedef struct err { struct err *e_nextp; char *e_mess; } ERR;
ERR *Err = NULL, *Lasterr = (ERR *)&Err;
FILE *mustopen(s, f) char *s; register FILS *f;
{
if (*s == '\0') {
f->f_name = STDINNAME();
f->f_f = stdin;
} else if ((f->f_f = fopen(f->f_name = s, "r")) == NULL) {
char *strcpy();
s = ffiler(f->f_name);
s = strcpy((char *)getspace((UNS)(strlen(s) + 1)), s);
}
if (f->f_f != NULL) {
if ((f->f_nextc = getc(f->f_f)) != EOF || Multi == 'm')
return (f->f_f);
sprintf(s = (char *)getspace((UNS)(strlen(f->f_name) + 1 + EMPTY)),
"%s -- empty file", f->f_name);
fclose(f->f_f);
}
Error = 1;
if (Report)
if (Ttyout) { /* accumulate error reports */
Lasterr = Lasterr->e_nextp = (ERR *)getspace((UNS)sizeof(ERR));
Lasterr->e_nextp = NULL;
Lasterr->e_mess = s;
} else { /* ok to print error report now */
cerror(s);
putc('\n', stderr);
}
return ((FILE *)NULL);
}
ANY *getspace(n) UNS n;
{
ANY *t;
if ((t = (ANY *)malloc(n)) == NULL) die("out of space");
return (t);
}
die(s) char *s;
{
++Error;
errprint();
cerror(s);
putc('\n', stderr);
exit(1);
}
onintr()
{
++Error;
errprint();
INTREXIT(1);
}
errprint() /* print accumulated error reports */
{
fflush(stdout);
for ( ; Err != NULL; Err = Err->e_nextp) {
cerror(Err->e_mess);
putc('\n', stderr);
}
done();
}