xref: /titanic_50/usr/src/cmd/pg/pg.c (revision 004388ebfdfe2ed7dfd2d153a876dfcc22d2c006)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
29 /*	  All Rights Reserved  	*/
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <signal.h>
34 #include <setjmp.h>
35 #include <sys/types.h>
36 #include <sys/dirent.h>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <wchar.h>
42 #include <curses.h>
43 #include <term.h>
44 #include <errno.h>
45 #include <stdlib.h>
46 #include <regexpr.h>
47 #include <limits.h>
48 #include <locale.h>
49 #include <wctype.h> /* iswprint() */
50 #include <string.h>
51 #include <unistd.h>
52 #include <wait.h>
53 #include <libw.h>
54 #include <regexpr.h>
55 
56 
57 /*
58  *	pg -- paginator for crt terminals
59  *
60  *	Includes the ability to display pages that have
61  *	already passed by. Also gives the user the ability
62  *	to search forward and backwards for regular expressions.
63  *	This works for piped input by copying to a temporary file,
64  *	and resolving backreferences from there.
65  *
66  *	Note:	The reason that there are so many commands to do
67  *		the same types of things is to try to accommodate
68  *		users of other paginators.
69  */
70 
71 #define	LINSIZ	1024
72 #define	QUIT	'\034'
73 #define	BOF	(EOF - 1)	/* Begining of File */
74 #define	STOP    (EOF - 2)
75 #define	PROMPTSIZE	256
76 
77 /*
78  * Function definitions
79  */
80 static	void	lineset(int);
81 static	char	*setprompt();
82 static	int	set_state(int *, wchar_t, char *);
83 static	void	help();
84 static	void	copy_file(FILE *, FILE *);
85 static	void	re_error(int);
86 static	void	save_input(FILE *);
87 static	void	save_pipe();
88 static	void	newdol(FILE *);
89 static	void	erase_line(int);
90 static	void	kill_line();
91 static	void	doclear();
92 static	void	sopr(char *, int);
93 static	void	prompt(char *);
94 static	void	error(char *);
95 static	void	terminit();
96 static	void	compact();
97 static	off_t	getline(FILE *);
98 static	int	mrdchar();
99 static	off_t	find(int, off_t);
100 static	int	search(char *, off_t);
101 static	FILE	*checkf(char *);
102 static	int	skipf(int);
103 static	int	readch();
104 static	int	ttyin();
105 static	int	number();
106 static	int	command(char *);
107 static	int	screen(char *);
108 static	int	fgetputc();
109 static 	char	*pg_strchr();
110 
111 
112 struct line {			/* how line addresses are stored */
113 	off_t	l_addr;		/* file offset */
114 	off_t	l_no;		/* line number in file */
115 };
116 
117 typedef	struct line	LINE;
118 
119 static	LINE	*zero = NULL,	/* first line */
120 		*dot,		/* current line */
121 		*dol,		/* last line */
122 		*contig;	/* where contiguous (non-aged) lines start */
123 static	long	nlall;		/* room for how many LINEs in memory */
124 
125 static	FILE	*in_file,	/* current input stream */
126 		*tmp_fin,	/* pipe temporary file in */
127 		*tmp_fou;	/* pipe temporary file out */
128 static	char	tmp_name[] = "/tmp/pgXXXXXX";
129 
130 static	short	sign;		/* sign of command input */
131 
132 static	int	fnum,		/* which file argument we're in */
133 		pipe_in,	/* set when stdin is a pipe */
134 		out_is_tty;	/* set if stdout is a tty */
135 static	gid_t	my_pgid;
136 
137 static	void	on_brk(),
138 		end_it();
139 static	short	brk_hit;	/* interrupt handling is pending flag */
140 
141 static	int	window = 0;	/* window size in lines */
142 static	short	eof_pause = 1;	/* pause w/ prompt at end of files */
143 static	short	rmode = 0;	/* deny shell escape in restricted mode */
144 static	short	soflag = 0;	/* output all messages in standout mode */
145 static	short	promptlen;	/* length of the current prompt */
146 static	short	firstf = 1;	/* set before first file has been processed */
147 static	short	inwait,		/* set while waiting for user input */
148 		errors;		/* set if error message has been printed. */
149 				/* if so, need to erase it and prompt */
150 
151 static	char	**fnames;
152 static	short	status = 0;	/* set > 0 if error detected */
153 static	short	fflag = 0;	/* set if the f option is used */
154 static	short	nflag = 0;	/* set for "no newline" input option */
155 static	short	clropt = 0;	/* set if the clear option is used */
156 static	int	initopt = 0;	/* set if the line option is used */
157 static	int	srchopt = 0;	/* set if the search option is used */
158 static	int	initline;
159 static	char	initbuf[BUFSIZ];
160 static	wchar_t	leave_search = L't';
161 				/* where on the page to leave a found string */
162 static	short	nfiles;
163 static	char	*shell;
164 static	char	*promptstr = ":";
165 static  off_t	nchars;			/* return from getline in find() */
166 static	jmp_buf	restore;
167 static	char	Line[LINSIZ+2];
168 
169 static	int	catch_susp;
170 
171 static	void	onsusp();
172 
173 struct screen_stat {
174 	off_t	first_line;
175 	off_t	last_line;
176 	short	is_eof;
177 	};
178 
179 static	struct screen_stat old_ss = { 0, 0, 0 };
180 static	struct screen_stat new_ss;
181 static	struct termio otty;	/* to save old terminal settings */
182 
183 static	short	termflg = 0;	/* set once terminal is initialized */
184 static	short	eoflag;		/* set whenever at end of current file */
185 static	short	doliseof;	/* set when last line of file is known */
186 static	off_t	eofl_no;	/* what the last line of the file is */
187 static	void	usage(void);
188 static FILE	*pg_stdin;
189 
190 int
191 main(int argc, char **argv)
192 {
193 	char	*s;
194 	char	*p;
195 	int		prnames = 0;
196 	int		opt;
197 	int		i;
198 
199 	(void) setlocale(LC_ALL, "");
200 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
201 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
202 #endif
203 	(void) textdomain(TEXT_DOMAIN);
204 
205 	/* check for non-standard "-#" option */
206 	for (i = 1; i < argc; i++) {
207 		if (strcmp(argv[i], "--") == 0)
208 			break;
209 
210 		if ((argv[i][0] == '-') && isdigit(argv[i][1])) {
211 			if (strlen(&argv[i][1]) !=
212 			    strspn(&argv[i][1], "0123456789")) {
213 				(void) fprintf(stderr, gettext(
214 				    "pg: Badly formed number\n"));
215 				usage();
216 			}
217 
218 			window = (int)strtol(&argv[i][1], (char **)NULL, 10);
219 
220 			while (i < argc) {
221 				argv[i] = argv[i + 1];
222 				i++;
223 			}
224 			i--;
225 			argc--;
226 		}
227 	}
228 
229 	/* check for non-standard + option */
230 	for (i = 1; i < argc; i++) {
231 		if (strcmp(argv[i], "--") == 0)
232 		break;
233 
234 		if (argv[i][0] == '+') {
235 			if (argv[i][1] == '/') {
236 				srchopt++;
237 				initopt = 0;
238 				for (s = &argv[i][2], p = initbuf; *s != '\0'; )
239 					if (p < initbuf + sizeof (initbuf))
240 						*p++ = *s++;
241 					else {
242 						(void) fprintf(stderr, gettext(
243 						    "pg: pattern too long\n"));
244 						return (1);
245 					}
246 				*p = '\0';
247 			} else {
248 				initopt++;
249 				srchopt = 0;
250 				s = &argv[i][2];
251 				for (; isdigit(*s); s++)
252 					initline = initline*10 + *s -'0';
253 				if (*s != '\0')
254 					usage();
255 			}
256 
257 			while (i < argc) {
258 				argv[i] = argv[i + 1];
259 				i++;
260 			}
261 			i--;
262 			argc--;
263 		}
264 	}
265 
266 	while ((opt = getopt(argc, argv, "cefnrsp:")) != EOF) {
267 		switch (opt) {
268 		case 'c':
269 			clropt = 1;
270 			break;
271 
272 		case 'e':
273 			eof_pause = 0;
274 			break;
275 
276 		case 'f':
277 			fflag = 1;
278 			break;
279 
280 		case 'n':
281 			nflag = 1;
282 			break;
283 
284 		case 'r':
285 			rmode = 1;	/* restricted mode */
286 			break;
287 
288 		case 's':
289 			soflag = 1;	/* standout mode */
290 			break;
291 
292 		case 'p':
293 			promptstr = setprompt(optarg);
294 			break;
295 
296 		default:
297 			usage();
298 		}
299 	}
300 
301 	nfiles = argc - optind;
302 	fnames = &argv[optind];
303 
304 	(void) signal(SIGQUIT, end_it);
305 	(void) signal(SIGINT, end_it);
306 	out_is_tty = isatty(1);
307 	my_pgid = getpgrp();
308 	if (out_is_tty) {
309 		terminit();
310 		(void) signal(SIGQUIT, on_brk);
311 		(void) signal(SIGINT, on_brk);
312 		if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) {
313 			(void) signal(SIGTSTP, onsusp);
314 			catch_susp++;
315 		}
316 	}
317 	if (window == 0)
318 		window = lines - 1;
319 	if (window <= 1)
320 		window = 2;
321 	if (initline <= 0)
322 		initline = 1;
323 	if (nfiles > 1)
324 		prnames++;
325 
326 	if (nfiles == 0) {
327 		fnames[0] = "-";
328 		nfiles++;
329 	}
330 	while (fnum < nfiles) {
331 		if (strcmp(fnames[fnum], "") == 0)
332 			fnames[fnum] = "-";
333 		if ((in_file = checkf(fnames[fnum])) == NULL) {
334 			status = 2;
335 			fnum++;
336 		} else {
337 			status = 0;
338 			if (out_is_tty)
339 				fnum += screen(fnames[fnum]);
340 			else {
341 				if (prnames) {
342 					(void) fputs("::::::::::::::\n",
343 					    stdout);
344 					(void) fputs(fnames[fnum], stdout);
345 					(void) fputs("\n::::::::::::::\n",
346 					    stdout);
347 				}
348 				copy_file(in_file, stdout);
349 				fnum++;
350 			}
351 			(void) fflush(stdout);
352 			if (pipe_in)
353 				save_pipe();
354 			else
355 			if (in_file != tmp_fin)
356 				(void) fclose(in_file);
357 		}
358 	}
359 	end_it();
360 
361 	/*NOTREACHED*/
362 	return (0);
363 }
364 
365 static	char *
366 setprompt(s)
367 char *s;
368 {
369 	int i = 0;
370 	int pct_d = 0;
371 	static char pstr[PROMPTSIZE];
372 
373 	while (i < PROMPTSIZE - 2)
374 		switch (pstr[i++] = *s++) {
375 		case '\0':
376 			return (pstr);
377 		case '%':
378 			if (*s == 'd' && !pct_d) {
379 				pct_d++;
380 			} else if (*s != '%')
381 				pstr[i++] = '%';
382 			if ((pstr[i++] = *s++) == '\0')
383 				return (pstr);
384 			break;
385 		default:
386 			break;
387 		}
388 	(void) fprintf(stderr, gettext("pg: prompt too long\n"));
389 	exit(1);
390 	/*NOTREACHED*/
391 }
392 
393 
394 /*
395  * Print out the contents of the file f, one screenful at a time.
396  */
397 
398 static int
399 screen(file_name)
400 char *file_name;
401 {
402 	int cmd_ret = 0;
403 	off_t start;
404 	short hadchance = 0;
405 
406 	old_ss.is_eof = 0;
407 	old_ss.first_line = 0;
408 	old_ss.last_line = 0;
409 	new_ss = old_ss;
410 	if (!firstf)
411 		cmd_ret = command(file_name);
412 	else {
413 		firstf = 0;
414 		if (initopt) {
415 			initopt = 0;
416 			new_ss.first_line = initline;
417 			new_ss.last_line = initline + (off_t)window - 1;
418 		} else if (srchopt) {
419 			srchopt = 0;
420 			if (!search(initbuf, (off_t)1))
421 				cmd_ret = command(file_name);
422 		} else {
423 			new_ss.first_line = 1;
424 			new_ss.last_line = (off_t)window;
425 		}
426 	}
427 
428 	for (;;) {
429 		if (cmd_ret)
430 			return (cmd_ret);
431 		if (hadchance && new_ss.last_line >= eofl_no)
432 			return (1);
433 		hadchance = 0;
434 
435 		if (new_ss.last_line < (off_t)window)
436 			new_ss.last_line = (off_t)window;
437 		if (find(0, new_ss.last_line + 1) != EOF)
438 			new_ss.is_eof = 0;
439 		else {
440 			new_ss.is_eof = 1;
441 			new_ss.last_line = eofl_no - 1;
442 			new_ss.first_line = new_ss.last_line -
443 			    (off_t)window + 1;
444 		}
445 
446 		if (new_ss.first_line < 1)
447 			new_ss.first_line = 1;
448 		if (clropt) {
449 			doclear();
450 			start = new_ss.first_line;
451 		} else {
452 			if (new_ss.first_line == old_ss.last_line)
453 				start = new_ss.first_line + 1;
454 			else
455 			if (new_ss.first_line > old_ss.last_line)
456 				start = new_ss.first_line;
457 			else
458 			if (old_ss.first_line < new_ss.first_line)
459 				start = old_ss.last_line + 1;
460 			else
461 				start = new_ss.first_line;
462 
463 			if (start < old_ss.first_line)
464 				sopr(gettext("...skipping backward\n"), 0);
465 			else
466 			if (start > old_ss.last_line + 1)
467 				sopr(gettext("...skipping forward\n"), 0);
468 		}
469 
470 		for (; start <= new_ss.last_line; start++) {
471 			(void) find(0, start);
472 			(void) fputs(Line, stdout);
473 			if (brk_hit) {
474 				new_ss.last_line = find(1, 0);
475 				new_ss.is_eof = 0;
476 				break;
477 			}
478 		}
479 
480 		brk_hit = 0;
481 		(void) fflush(stdout);
482 		if (new_ss.is_eof) {
483 			if (!eof_pause || eofl_no == 1)
484 				return (1);
485 			hadchance++;
486 			error("(EOF)");
487 		}
488 		old_ss = new_ss;
489 		cmd_ret = command((char *)NULL);
490 	}
491 }
492 
493 static	char	cmdbuf[LINSIZ], *cmdptr;
494 #define	BEEP()		if (bell) { (void) putp(bell); (void) fflush(stdout); }
495 #define	BLANKS(p)	while (*p == ' ' || *p == '\t') p++
496 #define	CHECKEND()	BLANKS(cmdptr); if (*cmdptr) { BEEP(); break; }
497 
498 /*
499  * Read a command and do it. A command consists of an optional integer
500  * argument followed by the command character.  Return the number of files
501  * to skip, 0 if we're still talking about the same file.
502  */
503 
504 static int
505 command(filename)
506 char *filename;
507 {
508 	off_t nlines;
509 	FILE *sf;
510 	char *cmdend;
511 	pid_t id;
512 	int skip;
513 	int	len;
514 	wchar_t	wc;
515 	wchar_t	wc_e;
516 	wchar_t	wc_e1;
517 	char	*p;
518 
519 	for (;;) {
520 		/*
521 		 * Wait for output to drain before going on.
522 		 * This is done so that the user will not hit
523 		 * break and quit before he has seen the prompt.
524 		 */
525 		(void) ioctl(1, TCSBRK, 1);
526 		if (setjmp(restore) > 0)
527 			end_it();
528 		inwait = 1;
529 		brk_hit = 0;
530 		if (errors)
531 			errors = 0;
532 		else {
533 			kill_line();
534 			prompt(filename);
535 		}
536 		(void) fflush(stdout);
537 		if (ttyin())
538 			continue;
539 		cmdptr = cmdbuf;
540 		nlines = number();
541 		BLANKS(cmdptr);
542 
543 		if ((len = mbtowc(&wc, cmdptr, MB_CUR_MAX)) <= 0) {
544 			wc = *cmdptr;
545 			len = 1;
546 		}
547 		cmdptr += len;
548 		switch (wc) {
549 		case 'h':
550 			CHECKEND();
551 			help();
552 			break;
553 		case '\014': /* ^L */
554 		case '.':	/* redisplay current window */
555 			CHECKEND();
556 			new_ss.first_line = old_ss.first_line;
557 			new_ss.last_line = old_ss.last_line;
558 			inwait = 0;
559 			return (0);
560 		case 'w':	/* set window size */
561 		case 'z':
562 			if (sign == -1) {
563 				BEEP();
564 				break;
565 			}
566 			CHECKEND();
567 			if (nlines == 0)
568 				nlines = (off_t)window;
569 			else
570 			if (nlines > 1)
571 				window = (int)nlines;
572 			else {
573 				BEEP();
574 				break;
575 			}
576 			new_ss.first_line = old_ss.last_line;
577 			new_ss.last_line = new_ss.first_line +
578 			    (off_t)window - 1;
579 			inwait = 0;
580 			return (0);
581 		case '\004': /* ^D */
582 		case 'd':
583 			CHECKEND();
584 			if (sign == 0)
585 				sign = 1;
586 			new_ss.last_line = old_ss.last_line +
587 			    (off_t)sign*window/2;
588 			new_ss.first_line = new_ss.last_line -
589 			    (off_t)window + 1;
590 			inwait = 0;
591 			return (0);
592 		case 's':
593 			/*
594 			 * save input in filename.
595 			 * Check for filename, access, etc.
596 			 */
597 			BLANKS(cmdptr);
598 			if (!*cmdptr) {
599 				BEEP();
600 				break;
601 			}
602 			if (setjmp(restore) > 0) {
603 				BEEP();
604 			} else {
605 				char outstr[PROMPTSIZE];
606 				if ((sf = fopen(cmdptr, "w")) == NULL) {
607 					error("cannot open save file");
608 					break;
609 				}
610 				kill_line();
611 				(void) sprintf(outstr, gettext(
612 				    "saving file %s"), cmdptr);
613 				sopr(outstr, 1);
614 				(void) fflush(stdout);
615 				save_input(sf);
616 				error("saved");
617 			}
618 			(void) fclose(sf);
619 			break;
620 		case 'q':
621 		case 'Q':
622 			CHECKEND();
623 			inwait = 0;
624 			end_it();
625 			/*FALLTHROUGH*/
626 
627 		case 'f':	/* skip forward screenfuls */
628 			CHECKEND();
629 			if (sign == 0)
630 				sign++;	/* skips are always relative */
631 			if (nlines == 0)
632 				nlines++;
633 			nlines = nlines * (window - 1);
634 			if (sign == 1)
635 				new_ss.first_line = old_ss.last_line + nlines;
636 			else
637 				new_ss.first_line = old_ss.first_line - nlines;
638 			new_ss.last_line = new_ss.first_line +
639 			    (off_t)window - 1;
640 			inwait = 0;
641 			return (0);
642 		case 'l':	/* get a line */
643 			CHECKEND();
644 			if (nlines == 0) {
645 				nlines++;
646 				if (sign == 0)
647 					sign = 1;
648 			}
649 			switch (sign) {
650 			case 1:
651 				new_ss.last_line = old_ss.last_line + nlines;
652 				new_ss.first_line =
653 				    new_ss.last_line - (off_t)window + 1;
654 				break;
655 			case 0:  /* leave addressed line at top */
656 				new_ss.first_line = nlines;
657 				new_ss.last_line = nlines + (off_t)window - 1;
658 				break;
659 			case -1:
660 				new_ss.first_line = old_ss.first_line - nlines;
661 				new_ss.last_line =
662 				    new_ss.first_line + (off_t)window - 1;
663 				break;
664 			}
665 			inwait = 0;
666 			return (0);
667 		case '\0': /* \n or blank */
668 			if (nlines == 0) {
669 				nlines++;
670 				if (sign == 0)
671 					sign = 1;
672 			}
673 			nlines = (nlines - 1) * (window - 1);
674 			switch (sign) {
675 			case 1:
676 				new_ss.first_line = old_ss.last_line + nlines;
677 				new_ss.last_line =
678 				    new_ss.first_line + (off_t)window - 1;
679 				break;
680 			case 0:
681 				new_ss.first_line = nlines + 1;
682 				new_ss.last_line = nlines + (off_t)window;
683 				/*
684 				 * This if statement is to fix the obscure bug
685 				 * where you have a file that has less lines
686 				 * than a screen holds, and the user types '1',
687 				 * expecting to have the 1st page (re)displayed.
688 				 * If we didn't set the new last_line to
689 				 * eofl_no-1, the screen() routine
690 				 * would cause pg to exit.
691 				 */
692 				if (new_ss.first_line == 1 &&
693 				    new_ss.last_line >= eofl_no)
694 					new_ss.last_line = eofl_no - 1;
695 				break;
696 			case -1:
697 				new_ss.last_line = old_ss.first_line - nlines;
698 				new_ss.first_line =
699 				    new_ss.last_line - (off_t)window + 1;
700 				break;
701 			}
702 			inwait = 0;
703 			return (0);
704 		case 'n':	/* switch to next file in arglist */
705 			CHECKEND();
706 			if (sign == 0)
707 				sign = 1;
708 			if (nlines == 0)
709 				nlines++;
710 			if ((skip = skipf(sign *nlines)) == 0) {
711 				BEEP();
712 				break;
713 			}
714 			inwait = 0;
715 			return (skip);
716 		case 'p':	/* switch to previous file in arglist */
717 			CHECKEND();
718 			if (sign == 0)
719 				sign = 1;
720 			if (nlines == 0)
721 				nlines++;
722 			if ((skip = skipf(-sign * nlines)) == 0) {
723 				BEEP();
724 				break;
725 			}
726 			inwait = 0;
727 			return (skip);
728 		case '$':	/* go to end of file */
729 			CHECKEND();
730 			sign = 1;
731 			while (find(1, (off_t)10000) != EOF)
732 				/* any large number will do */;
733 			new_ss.last_line = eofl_no - 1;
734 			new_ss.first_line = eofl_no - (off_t)window;
735 			inwait = 0;
736 			return (0);
737 		case '/':	/* search forward for r.e. */
738 		case '?':	/*   "  backwards */
739 		case '^':	/* this ones a ? for regent100s */
740 			if (sign < 0) {
741 				BEEP();
742 				break;
743 			}
744 			if (nlines == 0)
745 				nlines++;
746 			cmdptr--;
747 			cmdend = cmdptr + (strlen(cmdptr) - 1);
748 			wc_e1 = -1;
749 			wc_e = -1;
750 			for (p = cmdptr; p <= cmdend; p += len) {
751 				wc_e1 = wc_e;
752 				if ((len = mbtowc(&wc_e, p, MB_CUR_MAX)) <= 0) {
753 					wc_e = *p;
754 					len = 1;
755 				}
756 			}
757 
758 			if (cmdend > cmdptr + 1) {
759 				if ((wc_e1 == *cmdptr) &&
760 				    ((wc_e == L't') ||
761 					(wc_e == L'm') || (wc_e == L'b'))) {
762 					leave_search = wc_e;
763 					wc_e = wc_e1;
764 					cmdend--;
765 				}
766 			}
767 			if ((cmdptr < cmdend) && (wc_e == *cmdptr))
768 				*cmdend = '\0';
769 			if (*cmdptr != '/')  /* signify back search by - */
770 				nlines = -nlines;
771 			if (!search(++cmdptr, (off_t)nlines))
772 				break;
773 			else {
774 				inwait = 0;
775 				return (0);
776 			}
777 		case '!':	/* shell escape */
778 			if (rmode) {	/* restricted mode */
779 				(void) fprintf(stderr, gettext(
780 				"!command not allowed in restricted mode.\n"));
781 				break;
782 			}
783 			if (!hard_copy) { /* redisplay the command */
784 				(void) fputs(cmdbuf, stdout);
785 				(void) fputs("\n", stdout);
786 			}
787 			if ((id = fork()) < 0) {
788 				error("cannot fork, try again later");
789 				break;
790 			}
791 			if (id == (pid_t)0) {
792 				/*
793 				 * if stdin is a pipe, need to close it so
794 				 * that the terminal is really stdin for
795 				 * the command
796 				 */
797 				(void) fclose(stdin);
798 				(void) fclose(pg_stdin);
799 				(void) dup(fileno(stdout));
800 				(void) execl(shell, shell, "-c", cmdptr, 0);
801 				(void) perror("exec");
802 				exit(1);
803 			}
804 			(void) signal(SIGINT, SIG_IGN);
805 			(void) signal(SIGQUIT, SIG_IGN);
806 			if (catch_susp)
807 				(void) signal(SIGTSTP, SIG_DFL);
808 			while (wait((int *)0) != id);
809 			{
810 				if (errno == ECHILD)
811 					break;
812 				else
813 					errno = 0;
814 			}
815 			(void) fputs("!\n", stdout);
816 			(void) fflush(stdout);
817 			(void) signal(SIGINT, on_brk);
818 			(void) signal(SIGQUIT, on_brk);
819 			if (catch_susp)
820 				(void) signal(SIGTSTP, onsusp);
821 			break;
822 		default:
823 			BEEP();
824 			break;
825 		}
826 	}
827 }
828 
829 static int
830 number()
831 {
832 	int i;
833 	char *p;
834 
835 	i = 0;
836 	sign = 0;
837 	p = cmdptr;
838 	BLANKS(p);
839 	if (*p == '+') {
840 		p++;
841 		sign = 1;
842 	}
843 	else
844 	if (*p == '-') {
845 		p++;
846 		sign = -1;
847 	}
848 	while (isdigit(*p))
849 		i = i * 10 + *p++ - '0';
850 	cmdptr = p;
851 	return (i);
852 }
853 
854 static int
855 ttyin()
856 {
857 	char *sptr, *p;
858 	wchar_t ch;
859 	int slash = 0;
860 	int state = 0;
861 	int width, length;
862 	char multic[MB_LEN_MAX];
863 	static	int	readch();
864 	int 	len;
865 
866 	(void) fixterm();
867 	/* initialize state processing */
868 	(void) set_state(&state, ' ', (char *)0);
869 	sptr = cmdbuf;
870 	while (state != 10) {
871 		if ((ch = readch()) < 0 || !iswascii(ch) && !iswprint(ch)) {
872 			BEEP();
873 			continue;
874 		}
875 
876 		if ((length = wctomb(multic, ch)) < 0)
877 			length = 0;
878 		multic[length] = 0;
879 
880 		if (ch == '\n' && !slash)
881 			break;
882 		if (ch == erasechar() && !slash) {
883 			if (sptr > cmdbuf) {
884 				char *oldp = cmdbuf;
885 				wchar_t wchar;
886 				p = cmdbuf;
887 				while (p  < sptr) {
888 					oldp = p;
889 					len = mbtowc(&wchar, p, MB_CUR_MAX);
890 					if (len <= 0) {
891 						wchar = (unsigned char)*p;
892 						len = 1;
893 					}
894 					p += len;
895 				}
896 				if ((width = wcwidth(wchar)) <= 0)
897 					/* ascii control character */
898 					width = 2;
899 				promptlen -= width;
900 				while (width--)
901 					(void) fputs("\b \b", stdout);
902 				sptr = oldp;
903 			}
904 			(void) set_state(&state, ch, sptr);
905 			(void) fflush(stdout);
906 			continue;
907 		}
908 		else
909 		if (ch == killchar() && !slash) {
910 			if (hard_copy)
911 				(void) putwchar(ch);
912 			(void) resetterm();
913 			return (1);
914 		}
915 		if (ch < ' ')
916 			width = 2;
917 		else
918 			if ((width = wcwidth(ch)) <= 0)
919 				width = 0;
920 		if (slash) {
921 			slash = 0;
922 			(void) fputs("\b \b", stdout);
923 			sptr--;
924 			promptlen--;
925 		} else /* is there room to keep this character? */
926 		if (sptr >= cmdbuf + sizeof (cmdbuf) ||
927 		    promptlen + width >= columns) {
928 			BEEP();
929 			continue;
930 		}
931 		else
932 		if (ch == '\\')
933 			slash++;
934 		if (set_state(&state, ch, sptr) == 0) {
935 			BEEP();
936 			continue;
937 		}
938 		(void) strncpy(sptr, multic, (size_t)length);
939 		sptr += length;
940 		if (ch < ' ') {
941 			ch += 0100;
942 			multic[0] = '^';
943 			multic[1] = ch;
944 			length = 2;
945 		}
946 		p = multic;
947 		while (length--)
948 			(void) putchar(*p++);
949 		promptlen += width;
950 		(void) fflush(stdout);
951 	}
952 
953 	*sptr = '\0';
954 	kill_line();
955 	(void) fflush(stdout);
956 	(void) resetterm();
957 	return (0);
958 }
959 
960 static	int
961 set_state(pstate, c, pc)
962 int *pstate;
963 wchar_t c;
964 char *pc;
965 {
966 	static char *psign;
967 	static char *pnumber;
968 	static char *pcommand;
969 	static int slash;
970 
971 	if (*pstate == 0) {
972 		psign = (char *)NULL;
973 		pnumber = (char *)NULL;
974 		pcommand = (char *)NULL;
975 		*pstate = 1;
976 		slash = 0;
977 		return (1);
978 	}
979 	if (c == '\\' && !slash) {
980 		slash++;
981 		return (1);
982 	}
983 	if (c == erasechar() && !slash)
984 		switch (*pstate) {
985 		case 4:
986 			if (pc > pcommand)
987 				return (1);
988 			pcommand = (char *)NULL;
989 			/*FALLTHROUGH*/
990 
991 		case 3:
992 			if (pnumber && pc > pnumber) {
993 				*pstate = 3;
994 				return (1);
995 			}
996 			pnumber = (char *)NULL;
997 			/*FALLTHROUGH*/
998 
999 		case 2:
1000 			if (psign && pc > psign) {
1001 				*pstate = 2;
1002 				return (1);
1003 			}
1004 			psign = (char *)NULL;
1005 			/*FALLTHROUGH*/
1006 
1007 		case 1:
1008 			*pstate = 1;
1009 			return (1);
1010 		}
1011 
1012 	slash = 0;
1013 	switch (*pstate) {
1014 	case 1: /* before recieving anything interesting */
1015 		if (c == '\t' || (!nflag && c == ' '))
1016 			return (1);
1017 		if (c == '+' || c == '-') {
1018 			psign = pc;
1019 			*pstate = 2;
1020 			return (1);
1021 		}
1022 		/*FALLTHROUGH*/
1023 
1024 	case 2: /* recieved sign, waiting for digit */
1025 		if (iswascii(c) && isdigit(c)) {
1026 			pnumber = pc;
1027 			*pstate = 3;
1028 			return (1);
1029 		}
1030 		/*FALLTHROUGH*/
1031 
1032 	case 3: /* recieved digit, waiting for the rest of the number */
1033 		if (iswascii(c) && isdigit(c))
1034 			return (1);
1035 		if (iswascii(c) && pg_strchr("h\014.wz\004dqQfl np$", c)) {
1036 			pcommand = pc;
1037 			if (nflag)
1038 				*pstate = 10;
1039 			else
1040 				*pstate = 4;
1041 			return (1);
1042 		}
1043 		if (iswascii(c) && pg_strchr("s/^?!", c)) {
1044 			pcommand = pc;
1045 			*pstate = 4;
1046 			return (1);
1047 		}
1048 		return (0);
1049 	case 4:
1050 		return (1);
1051 	}
1052 	return (0);
1053 }
1054 
1055 static	int
1056 readch()
1057 {
1058 	return (fgetwc(pg_stdin));
1059 }
1060 
1061 static void
1062 help()
1063 {
1064 	if (clropt)
1065 		doclear();
1066 
1067 	(void) fputs(gettext(
1068 "-------------------------------------------------------\n"
1069 "  h                     help\n"
1070 "  q or Q                quit\n"
1071 "  <blank> or <newline>  next page\n"
1072 "  l                     next line\n"
1073 "  d or <^D>             display half a page more\n"
1074 "  . or <^L>             redisplay current page\n"
1075 "  f                     skip the next page forward\n"
1076 "  n                     next file\n"
1077 "  p                     previous file\n"
1078 "  $                     last page\n"
1079 "  w or z                set window size and display next page\n"
1080 "  s savefile            save current file in savefile\n"
1081 "  /pattern/             search forward for pattern\n"
1082 "  ?pattern? or\n"
1083 "  ^pattern^             search backward for pattern\n"
1084 "  !command              execute command\n"
1085 "\n"
1086 "Most commands can be preceeded by a number, as in:\n"
1087 "+1<newline> (next page); -1<newline> (previous page); 1<newline> (page 1).\n"
1088 "\n"
1089 "See the manual page for more detail.\n"
1090 "-------------------------------------------------------\n"),
1091 	    stdout);
1092 }
1093 
1094 /*
1095  * Skip nskip files in the file list (from the command line). Nskip may be
1096  * negative.
1097  */
1098 
1099 static int
1100 skipf(nskip)
1101 int nskip;
1102 {
1103 	if (fnum + nskip < 0) {
1104 		nskip = -fnum;
1105 		if (nskip == 0)
1106 			error("No previous file");
1107 	}
1108 	else
1109 	if (fnum + nskip > nfiles - 1) {
1110 		nskip = (nfiles - 1) - fnum;
1111 		if (nskip == 0)
1112 			error("No next file");
1113 	}
1114 	return (nskip);
1115 }
1116 
1117 /*
1118  * Check whether the file named by fs is a file which the user may
1119  * access.  If it is, return the opened file. Otherwise return NULL.
1120  */
1121 
1122 static FILE *
1123 checkf(fs)
1124 char *fs;
1125 {
1126 	struct stat stbuf;
1127 	FILE *f;
1128 	int fd;
1129 	int f_was_opened;
1130 
1131 	pipe_in = 0;
1132 	if (strcmp(fs, "-") == 0) {
1133 		if (tmp_fin == NULL)
1134 			f = stdin;
1135 		else {
1136 			rewind(tmp_fin);
1137 			f = tmp_fin;
1138 		}
1139 		f_was_opened = 0;
1140 	} else {
1141 		if ((f = fopen(fs, "r")) == (FILE *)NULL) {
1142 			(void) fflush(stdout);
1143 			perror(fs);
1144 			return ((FILE *)NULL);
1145 		}
1146 		f_was_opened = 1;
1147 	}
1148 	if (fstat(fileno(f), &stbuf) == -1) {
1149 		if (f_was_opened)
1150 			(void) fclose(f);
1151 		(void) fflush(stdout);
1152 		perror(fs);
1153 		return ((FILE *)NULL);
1154 	}
1155 	if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
1156 		if (f_was_opened)
1157 			(void) fclose(f);
1158 		(void) fprintf(stderr, "pg: ");
1159 		(void) fprintf(stderr, gettext("%s is a directory\n"), fs);
1160 		return ((FILE *)NULL);
1161 	}
1162 	if ((stbuf.st_mode & S_IFMT) == S_IFREG) {
1163 		if (f == stdin)		/* It may have been read from */
1164 			rewind(f);	/* already, and not reopened  */
1165 	} else {
1166 		if (f != stdin) {
1167 			if (f_was_opened)
1168 				(void) fclose(f);
1169 			(void) fprintf(stderr, "pg: ");
1170 			(void) fprintf(stderr, gettext(
1171 			"special files only handled as standard input\n"));
1172 			return ((FILE *)NULL);
1173 		} else {
1174 			if ((fd = mkstemp(tmp_name)) < 0) {
1175 			    (void) perror(tmp_name);
1176 			    return ((FILE *)NULL);
1177 			}
1178 			(void) close(fd);
1179 			if ((tmp_fou = fopen(tmp_name, "w")) == NULL) {
1180 				(void) perror(tmp_name);
1181 				return ((FILE *)NULL);
1182 			}
1183 			if ((tmp_fin = fopen(tmp_name, "r")) == NULL) {
1184 				(void) perror(tmp_name);
1185 				return ((FILE *)NULL);
1186 			}
1187 			pipe_in = 1;
1188 		}
1189 	}
1190 	lineset(BOF);
1191 	return (f);
1192 }
1193 
1194 static void
1195 copy_file(f, out)
1196 FILE *f, *out;
1197 {
1198 	int c;
1199 
1200 	while ((c = getc(f)) != EOF)
1201 		(void) putc(c, out);
1202 
1203 }
1204 
1205 static void
1206 re_error(i)
1207 int i;
1208 {
1209 	int j;
1210 	static struct messages {
1211 		char *message;
1212 		int number;
1213 		} re_errmsg[] = {
1214 		"Pattern not found",				1,
1215 		"Range endpoint too large",			11,
1216 		"Bad number",					16,
1217 		"`\\digit' out of range",			25,
1218 		"No remembered search string",  		41,
1219 		"\\( \\) imbalance",				42,
1220 		"Too many \\(",					43,
1221 		"More than two numbers given in \\{ \\}",  	44,
1222 		"} expected after \\",				45,
1223 		"First number exceeds second in \\{ \\}",  	46,
1224 		"[] imbalance",					49,
1225 		"Regular expression overflow",			50,
1226 		"Illegal byte sequence",			67,
1227 		"Bad regular expression",			0
1228 		};
1229 
1230 	for (j = 0; re_errmsg[j].number != 0; j++)
1231 		if (re_errmsg[j].number == i)
1232 			break;
1233 	error(re_errmsg[j].message);
1234 	longjmp(restore, 1);  /* restore to search() */
1235 }
1236 
1237 /*
1238  * Search for nth ocurrence of regular expression contained in buf in the file
1239  *	negative n implies backward search
1240  *	n 'guaranteed' non-zero
1241  */
1242 
1243 
1244 static int
1245 search(buf, n)
1246 char buf[];
1247 off_t n;
1248 {
1249 	int direction;
1250 	static char *expbuf;
1251 	char *nexpbuf;
1252 	int END_COND;
1253 
1254 	if (setjmp(restore) <= 0) {
1255 		nexpbuf = compile(buf, (char *)0, (char *)0);
1256 		if (regerrno) {
1257 			if (regerrno != 41 || expbuf == NULL)
1258 				re_error(regerrno);
1259 		} else {
1260 			if (expbuf)
1261 				free(expbuf);
1262 			expbuf = nexpbuf;
1263 		}
1264 
1265 		if (n < 0) {	/* search back */
1266 			direction = -1;
1267 			(void) find(0, old_ss.first_line);
1268 			END_COND = BOF;
1269 		} else {
1270 			direction = 1;
1271 			(void) find(0, old_ss.last_line);
1272 			END_COND = EOF;
1273 		}
1274 
1275 		while (find(1, direction) != END_COND) {
1276 			if (brk_hit)
1277 				break;
1278 			if (step(Line, expbuf))
1279 				if ((n -= direction) == 0) {
1280 					switch (leave_search) {
1281 					case 't':
1282 						new_ss.first_line =
1283 						    find(1, (off_t)0);
1284 						new_ss.last_line =
1285 						    new_ss.first_line +
1286 						    (off_t)window
1287 						    - 1;
1288 						break;
1289 					case 'b':
1290 						new_ss.last_line =
1291 						    find(1, (off_t)0);
1292 						new_ss.first_line =
1293 						    new_ss.last_line -
1294 						    (off_t)window
1295 						    + 1;
1296 						break;
1297 					case 'm':
1298 						new_ss.first_line =
1299 						    find(1, (off_t)0) -
1300 						    ((off_t)window - 1)/2;
1301 						new_ss.last_line =
1302 						    new_ss.first_line +
1303 						    (off_t)window
1304 						    - 1;
1305 						break;
1306 					}
1307 					return (1);
1308 				}
1309 		}
1310 		re_error(1); /* Pattern not found */
1311 	}
1312 	BEEP();
1313 	return (0);
1314 }
1315 
1316 /*
1317  *	find -- find line in file f, subject to certain constraints.
1318  *
1319  *	This is the reason for all the funny stuff with sign and nlines.
1320  *	We need to be able to differentiate between relative and abosolute
1321  *	address specifications.
1322  *
1323  *	So...there are basically three cases that this routine
1324  *	handles. Either line is zero, which  means there is to be
1325  *	no motion (because line numbers start at one), or
1326  *	how and line specify a number, or line itself is negative,
1327  *	which is the same as having how == -1 and line == abs(line).
1328  *
1329  *	Then, figure where exactly it is that we are going (an absolute
1330  *	line number). Find out if it is within what we have read,
1331  *	if so, go there without further ado. Otherwise, do some
1332  *	magic to get there, saving all the intervening lines,
1333  *	in case the user wants to see them some time later.
1334  *
1335  *	In any case, return the line number that we end up at.
1336  *	(This is used by search() and screen()). If we go past EOF,
1337  *	return EOF.
1338  *	This EOF will go away eventually, as pg is expanded to
1339  *	handle multiple files as one huge one. Then EOF will
1340  *	mean we have run off the file list.
1341  *	If the requested line number is too far back, return BOF.
1342  */
1343 
1344 static off_t
1345 find(how, line)	/* find the line and seek there */
1346 int how;
1347 off_t line;
1348 {
1349 	/* no compacted memory yet */
1350 	FILE *f = in_file;
1351 	off_t where;
1352 
1353 	if (how == 0)
1354 		where = line;
1355 	else
1356 		if (dot == zero - 1)
1357 			where = how * line;
1358 		else
1359 			where = how * line + dot->l_no;
1360 
1361 	/* now, where is either at, before, or after dol */
1362 	/* most likely case is after, so do it first */
1363 
1364 	eoflag = 0;
1365 	if (where >= dol->l_no) {
1366 		if (doliseof) {
1367 			dot = dol;
1368 			eoflag++;
1369 			return (EOF);
1370 		}
1371 		if (pipe_in)
1372 			in_file = f = stdin;
1373 		else
1374 			(void) fseeko(f, (off_t)dol->l_addr, SEEK_SET);
1375 		dot = dol - 1;
1376 		while ((nchars = getline(f)) != EOF) {
1377 			dot++;
1378 			newdol(f);
1379 			if (where == dot->l_no || brk_hit)
1380 				break;
1381 		}
1382 		if (nchars != EOF)
1383 			return (dot->l_no);
1384 		else { /* EOF */
1385 			dot = dol;
1386 			eoflag++;
1387 			doliseof++;
1388 			eofl_no = dol->l_no;
1389 			return (EOF);
1390 		}
1391 	} else { /* where < dol->l_no */
1392 		if (pipe_in) {
1393 			(void) fflush(tmp_fou);
1394 			in_file = f = tmp_fin;
1395 		}
1396 		if (where < zero->l_no) {
1397 			(void) fseeko(f, (off_t)zero->l_addr, SEEK_SET);
1398 			dot = zero - 1;
1399 			return (BOF);
1400 		} else {
1401 			dot = zero + where - 1;
1402 			(void) fseeko(f, (off_t)dot->l_addr, SEEK_SET);
1403 			nchars = getline(f);
1404 			return (dot->l_no);
1405 		}
1406 	}
1407 }
1408 
1409 static FILE *fileptr;
1410 static int (*rdchar)();
1411 
1412 static int
1413 mrdchar()
1414 {
1415 	return (rdchar(fileptr));
1416 }
1417 
1418 /*
1419  * Get a logical line
1420  */
1421 
1422 static off_t
1423 getline(f)
1424 FILE *f;
1425 {
1426 	char	*p;
1427 	int	column;
1428 	static char multic[MB_LEN_MAX];
1429 	static int savlength;
1430 	wchar_t c;
1431 	int length, width;
1432 
1433 	if (pipe_in && f == stdin)
1434 		rdchar = fgetputc;
1435 	else
1436 		rdchar = (int (*)())fgetwc;
1437 
1438 	fileptr = f;
1439 	/* copy overlap from previous call to getline */
1440 	if (savlength)
1441 		(void) strncpy(Line, multic, (size_t)savlength);
1442 	for (column = 0, p = Line + savlength; ; ) {
1443 		if ((c = mrdchar()) <= 0) {
1444 			clearerr(f);
1445 			if (p > Line) {	/* last line doesn't have '\n', */
1446 				*p++ = '\n';
1447 				*p = '\0';	/* print it any way */
1448 				return (column);
1449 			}
1450 			return (EOF);
1451 		}
1452 		length = wctomb(multic, c);
1453 		if (length < 0) {
1454 			length = -length;
1455 			c = 0;
1456 		}
1457 		if ((width = wcwidth(c)) < 0)
1458 			width = 0;
1459 		if (column + width > columns && !fflag)
1460 			break;
1461 
1462 		if (p + length > &Line[LINSIZ - 2] && c != '\n')
1463 			break;
1464 		(void) strncpy(p, multic, (size_t)length);
1465 		p += length;
1466 		column += width;
1467 		/* don't have any overlap here */
1468 		length = 0;
1469 		switch (c) {
1470 		case '\t': /* just a guess */
1471 			column = 1 + (column | 7);
1472 			break;
1473 		case '\b':
1474 			if (column > 0)
1475 				column--;
1476 			break;
1477 		case '\r':
1478 			column = 0;
1479 			break;
1480 		}
1481 		if (c == '\n')
1482 			break;
1483 		if (column >= columns && !fflag)
1484 			break;
1485 	}
1486 	if (c != '\n') { /* We're stopping in the middle of the line */
1487 		if (column != columns || !auto_right_margin)
1488 			*p++ = '\n';	/* for the display */
1489 		/* save overlap for next call to getline */
1490 		savlength = length;
1491 		if (savlength == 0) {
1492 			/*
1493 			 * check if following byte is newline and get
1494 			 * it if it is
1495 			 */
1496 			c = fgetwc(f);
1497 			if (c == '\n') {
1498 				/* gobble and copy (if necessary) newline */
1499 				(void) ungetwc(c, f);
1500 				(void) (*rdchar)(f);
1501 			} else if (c == EOF)
1502 				clearerr(f);
1503 			else
1504 				(void) ungetwc(c, f);
1505 		}
1506 	} else
1507 		savlength = 0;
1508 	*p = 0;
1509 	return (column);
1510 }
1511 
1512 static void
1513 save_input(f)
1514 FILE *f;
1515 {
1516 	if (pipe_in) {
1517 		save_pipe();
1518 		in_file = tmp_fin;
1519 		pipe_in = 0;
1520 	}
1521 	(void) fseeko(in_file, (off_t)0, SEEK_SET);
1522 	copy_file(in_file, f);
1523 }
1524 
1525 static void
1526 save_pipe()
1527 {
1528 	if (!doliseof)
1529 		while (fgetputc(stdin) != EOF)
1530 			if (brk_hit) {
1531 				brk_hit = 0;
1532 				error("Piped input only partially saved");
1533 				break;
1534 			}
1535 	(void) fclose(tmp_fou);
1536 }
1537 
1538 static int
1539 fgetputc(f)	/* copy anything read from a pipe to tmp_fou */
1540 FILE *f;
1541 {
1542 	int c;
1543 
1544 	if ((c = fgetwc(f)) != EOF)
1545 		(void) fputwc(c, tmp_fou);
1546 	return (c);
1547 }
1548 
1549 static	void
1550 lineset(how)	/* initialize line memory */
1551 int how;
1552 {
1553 	if (zero == NULL) {
1554 		nlall = 128;
1555 		zero = (LINE *) malloc(nlall * sizeof (LINE));
1556 	}
1557 	dol = contig = zero;
1558 	zero->l_no = 1;
1559 	zero->l_addr = 0l;
1560 	if (how == BOF) {
1561 		dot = zero - 1;
1562 		eoflag = 0;
1563 		doliseof = 0;
1564 		eofl_no = -1;
1565 	} else {
1566 		dot = dol;
1567 		eoflag = 1;
1568 		doliseof = 1;
1569 		eofl_no = 1;
1570 	}
1571 }
1572 
1573 static void
1574 newdol(f)	/* add address of new 'dol' */
1575 		/* assumes that f is currently at beginning of said line */
1576 		/* updates dol */
1577 FILE *f;
1578 {
1579 	int diff;
1580 
1581 	if ((dol - zero) + 1 >= nlall) {
1582 		LINE *ozero = zero;
1583 
1584 		nlall += 512;
1585 		if ((zero = (LINE *)realloc((char *)zero,
1586 		    (unsigned)(nlall * sizeof (LINE)))) == NULL) {
1587 			zero = ozero;
1588 			compact();
1589 		}
1590 		diff = (int)((int)zero - (int)ozero);
1591 		dot = (LINE *)((int)dot + diff);
1592 		dol = (LINE *)((int)dol + diff);
1593 		contig = (LINE *)((int)contig + diff);
1594 	}
1595 	dol++;
1596 	if (!pipe_in)
1597 		dol->l_addr = (off_t)ftello(f);
1598 	else {
1599 		(void) fflush(tmp_fou);
1600 		dol->l_addr = (off_t)ftello(tmp_fou);
1601 	}
1602 	dol->l_no = (dol-1)->l_no + 1;
1603 }
1604 
1605 static void
1606 compact()
1607 {
1608 	(void) perror("realloc");
1609 	end_it();
1610 
1611 }
1612 
1613 static void
1614 terminit()	/* set up terminal dependencies from termlib */
1615 {
1616 	int err_ret;
1617 	struct termio ntty;
1618 
1619 	for (;;) {
1620 		gid_t my_tgid;
1621 		my_tgid = (gid_t)tcgetpgrp(1);
1622 		if (my_tgid < (gid_t)0 || my_tgid == my_pgid)
1623 			break;
1624 		(void) kill(-my_pgid, SIGTTOU);
1625 	}
1626 
1627 	if ((freopen("/dev/tty", "r+", stdout)) == NULL) {
1628 		(void) perror("open");
1629 		exit(1);
1630 	}
1631 	(void) ioctl(fileno(stdout), TCGETA, &otty);
1632 	termflg = 1;
1633 
1634 	(void) setupterm(0, fileno(stdout), &err_ret);
1635 	(void) ioctl(fileno(stdout), TCGETA, &ntty);
1636 	ntty.c_lflag &= ~(ECHONL | ECHO | ICANON);
1637 	ntty.c_cc[VMIN] = 1;
1638 	ntty.c_cc[VTIME] = 1;
1639 	(void) ioctl(fileno(stdout), TCSETAW, &ntty);
1640 	pg_stdin = fdopen(dup(fileno(stdout)), "r");
1641 	(void) saveterm();
1642 	(void) resetterm();
1643 	if (lines <= 0 || hard_copy) {
1644 		hard_copy = 1;
1645 		lines = 24;
1646 	}
1647 	if (columns <= 0)
1648 		columns = 80;
1649 	if (clropt && !clear_screen)
1650 		clropt = 0;
1651 	if ((shell = getenv("SHELL")) == (char *)NULL)
1652 			shell = "/usr/bin/sh";
1653 }
1654 
1655 static void
1656 error(mess)
1657 char *mess;
1658 {
1659 	kill_line();
1660 	sopr(gettext(mess), 1);
1661 	prompt((char *)NULL);
1662 	errors++;
1663 }
1664 
1665 static void
1666 prompt(filename)
1667 char *filename;
1668 {
1669 	char outstr[PROMPTSIZE+6];
1670 	int pagenum;
1671 	if (filename != NULL) {
1672 		/*
1673 		 * TRANSLATION_NOTE
1674 		 * 	%s is a filename.
1675 		 */
1676 		(void) sprintf(outstr, gettext("(Next file: %s)"), filename);
1677 	} else {
1678 		if ((pagenum = (int)((new_ss.last_line-2)/(window-1)+1))
1679 						> 999999)
1680 			pagenum = 999999;
1681 		(void) sprintf(outstr, promptstr, pagenum);
1682 	}
1683 	sopr(outstr, 1);
1684 	(void) fflush(stdout);
1685 }
1686 
1687 /*
1688  *  sopr puts out the message (please no \n's) surrounded by standout
1689  *  begins and ends
1690  */
1691 
1692 static void
1693 sopr(m, count)
1694 	char *m;
1695 	int count;
1696 {
1697 	wchar_t	wc;
1698 	int	len, n;
1699 	char	*p;
1700 
1701 	if (count) {
1702 		p = m;
1703 		for (; *p; p += len) {
1704 			if ((len = mbtowc(&wc, p, MB_CUR_MAX)) <= 0) {
1705 				len = 1;
1706 				continue;
1707 			}
1708 			if ((n = wcwidth(wc)) > 0)
1709 				promptlen += n;
1710 		}
1711 	}
1712 	if (soflag && enter_standout_mode && exit_standout_mode) {
1713 		(void) putp(enter_standout_mode);
1714 		(void) fputs(m, stdout);
1715 		(void) putp(exit_standout_mode);
1716 	}
1717 	else
1718 		(void) fputs(m, stdout);
1719 }
1720 
1721 static void
1722 doclear()
1723 {
1724 	if (clear_screen)
1725 		(void) putp(clear_screen);
1726 	(void) putchar('\r');  /* this resets the terminal drivers character */
1727 			/* count in case it is trying to expand tabs  */
1728 
1729 }
1730 
1731 static void
1732 kill_line()
1733 {
1734 	erase_line(0);
1735 	if (!clr_eol) (void) putchar('\r');
1736 
1737 }
1738 
1739 /* erase from after col to end of prompt */
1740 static void
1741 erase_line(col)
1742 int col;
1743 {
1744 
1745 	if (promptlen == 0)
1746 		return;
1747 	if (hard_copy)
1748 		(void) putchar('\n');
1749 	else {
1750 		if (col == 0)
1751 			(void) putchar('\r');
1752 		if (clr_eol) {
1753 			(void) putp(clr_eol);
1754 			/* for the terminal driver again */
1755 			(void) putchar('\r');
1756 		}
1757 		else
1758 			for (col = promptlen - col; col > 0; col--)
1759 				(void) putchar(' ');
1760 	}
1761 	promptlen = 0;
1762 }
1763 
1764 /*
1765  * Come here if a quit or interrupt signal is received
1766  */
1767 
1768 static void
1769 on_brk(sno)
1770 	int sno;	/* signal number generated */
1771 {
1772 	(void) signal(sno, on_brk);
1773 	if (!inwait) {
1774 		BEEP();
1775 		brk_hit = 1;
1776 	} else {
1777 		brk_hit = 0;
1778 		longjmp(restore, 1);
1779 	}
1780 }
1781 
1782 /*
1783  * Clean up terminal state and exit.
1784  */
1785 
1786 void
1787 end_it()
1788 {
1789 
1790 	if (out_is_tty) {
1791 		kill_line();
1792 		(void) resetterm();
1793 		if (termflg)
1794 			(void) ioctl(fileno(stdout), TCSETAW, &otty);
1795 	}
1796 	if (tmp_fin)
1797 		(void) fclose(tmp_fin);
1798 	if (tmp_fou)
1799 		(void) fclose(tmp_fou);
1800 	if (tmp_fou || tmp_fin)
1801 		(void) unlink(tmp_name);
1802 	exit(status);
1803 }
1804 
1805 void
1806 onsusp()
1807 {
1808 	int ttou_is_dfl;
1809 
1810 	/* ignore SIGTTOU so following resetterm and flush works */
1811 	ttou_is_dfl = (signal(SIGTTOU, SIG_IGN) == SIG_DFL);
1812 	(void) resetterm();
1813 	(void) fflush(stdout);
1814 	if (ttou_is_dfl)
1815 		(void) signal(SIGTTOU, SIG_DFL);
1816 
1817 	/* send SIGTSTP to stop this process group */
1818 	(void) signal(SIGTSTP, SIG_DFL);
1819 	(void) kill(-my_pgid, SIGTSTP);
1820 
1821 	/* continued - reset the terminal */
1822 #ifdef __STDC__
1823 	(void) signal(SIGTSTP, (void (*)(int))onsusp);
1824 #else
1825 	(void) signal(SIGTSTP, (void (*))onsusp);
1826 #endif
1827 	(void) resetterm();
1828 	if (inwait)
1829 		longjmp(restore, -1);
1830 
1831 }
1832 
1833 static char *
1834 pg_strchr(str, c)
1835 char	*str;
1836 wchar_t	c;
1837 {
1838 	while (*str) {
1839 		if (c == *str)
1840 			return (str);
1841 		str++;
1842 	}
1843 	return (0);
1844 }
1845 
1846 void
1847 usage()
1848 {
1849 	(void) fprintf(stderr, gettext(
1850 "Usage: pg [-number] [-p string] [-cefnrs] [+line] [+/pattern/] files\n"));
1851 	exit(1);
1852 }
1853