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