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