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