xref: /openbsd/distrib/special/more/more.c (revision 8529ddd3)
1 /*	$OpenBSD: more.c,v 1.35 2015/01/16 06:39:33 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2003 Todd C. Miller <Todd.Miller@courtesan.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  *
18  * Sponsored in part by the Defense Advanced Research Projects
19  * Agency (DARPA) and Air Force Research Laboratory, Air Force
20  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21  */
22 /*-
23  * Copyright (c) 1980 The Regents of the University of California.
24  * All rights reserved.
25  *
26  * Redistribution and use in source and binary forms, with or without
27  * modification, are permitted provided that the following conditions
28  * are met:
29  * 1. Redistributions of source code must retain the above copyright
30  *    notice, this list of conditions and the following disclaimer.
31  * 2. Redistributions in binary form must reproduce the above copyright
32  *    notice, this list of conditions and the following disclaimer in the
33  *    documentation and/or other materials provided with the distribution.
34  * 3. Neither the name of the University nor the names of its contributors
35  *    may be used to endorse or promote products derived from this software
36  *    without specific prior written permission.
37  *
38  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
39  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
40  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
41  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
42  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
43  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
44  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
45  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
46  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
47  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  */
50 
51 /*
52  * more.c - General purpose tty output filter and file perusal program
53  *
54  *	by Eric Shienbrood, UC Berkeley
55  *
56  *	modified by Geoff Peck, UCB to add underlining, single spacing
57  *	modified by John Foderaro, UCB to add -c and MORE environment variable
58  */
59 
60 /*
61  * TODO (millert)
62  *  o POSIX compliance
63  */
64 
65 #include <sys/types.h>
66 #include <sys/exec.h>
67 #include <sys/ioctl.h>
68 #include <sys/file.h>
69 #include <sys/stat.h>
70 #include <sys/wait.h>
71 
72 #include <ctype.h>
73 #include <curses.h>
74 #include <errno.h>
75 #include <locale.h>
76 #include <regex.h>
77 #include <signal.h>
78 #include <stdarg.h>
79 #include <stdio.h>
80 #include <stdlib.h>
81 #include <string.h>
82 #include <termios.h>
83 #include <unistd.h>
84 #include <limits.h>
85 #include <paths.h>
86 
87 #define Fopen(s, m)	(Currline = 0, file_pos = 0, fopen(s,m))
88 #define Ftell(f)	(file_pos)
89 #define Fseek(f, off)	(file_pos = off, fseeko(f, off, SEEK_SET))
90 #define Getc(f)		(++file_pos, getc(f))
91 #define Ungetc(c, f)	(--file_pos, ungetc(c, f))
92 
93 #define	cleareol()	(tputs(eraseln, 1, putch))
94 #define	clreos()	(tputs(EodClr, 1, putch))
95 #define	home()		(tputs(Home, 1, putch))
96 
97 #define TBUFSIZ		1024
98 #define LINSIZ		256
99 #define ctrl(letter)	(letter & 077)
100 #define RUBOUT		'\177'
101 #define ESC		'\033'
102 #define QUIT		'\034'
103 
104 #define	DUM_PROMPT	"[Press space to continue, 'q' to quit.]"
105 #define	DUM_ERROR	"[Press 'h' for instructions.]"
106 #define	QUIT_IT		"[Use q or Q to quit]"
107 
108 #include "morehelp.h"
109 
110 struct termios	otty, ntty;
111 off_t		file_pos, file_size;
112 int		fnum, no_intty, no_tty, slow_tty;
113 int		dum_opt, dlines;
114 int		nscroll = 11;	/* Number of lines scrolled by 'd' */
115 int		fold_opt = 1;	/* Fold long lines */
116 int		stop_opt = 1;	/* Stop after form feeds */
117 int		ssp_opt = 0;	/* Suppress white space */
118 int		ul_opt = 1;	/* Underline as best we can */
119 int		promptlen;
120 off_t		Currline;	/* Line we are currently at */
121 int		startup = 1;
122 int		firstf = 1;
123 int		notell = 1;
124 int		docrterase = 0;
125 int		docrtkill = 0;
126 int		bad_so;	/* True if overwriting does not turn off standout */
127 int		inwait, Pause, errors;
128 int		within;		/* true if we are within a file,
129 				   false if we are between files */
130 int		hard, dumb, noscroll, hardtabs, clreol, eatnl;
131 int		catch_susp;	/* We should catch the SIGTSTP signal */
132 char		**fnames;	/* The list of file names */
133 int		nfiles;		/* Number of files left to process */
134 char		*shell;		/* The name of the shell to use */
135 int		shellp;		/* A previous shell command exists */
136 char		Lineb[LINSIZ];	/* Line buffer */
137 char		*Line = Lineb;	/* Line pointer */
138 int		Lpp = 24;	/* lines per page */
139 char		*Clear;		/* clear screen */
140 char		*eraseln;	/* erase line */
141 char		*Senter, *Sexit;/* enter and exit standout mode */
142 char		*ULenter, *ULexit; /* enter and exit underline mode */
143 char		*chUL;		/* underline character */
144 char		*chBS;		/* backspace character */
145 char		*Home;		/* go to home */
146 char		*cursorm;	/* cursor movement */
147 char		cursorhome[40];	/* contains cursor movement to home */
148 char		*EodClr;	/* clear rest of screen */
149 int		Mcol = 80;	/* number of columns */
150 int		Wrap = 1;	/* set if automargins */
151 int		soglitch;	/* terminal has standout mode glitch */
152 int		ulglitch;	/* terminal has underline mode glitch */
153 int		pstate = 0;	/* current UL state */
154 int		altscr = 0;	/* terminal supports an alternate screen */
155 size_t		linsize = LINSIZ;
156 
157 volatile sig_atomic_t signo[_NSIG];	/* signals received */
158 
159 struct {
160 	off_t chrctr, line;
161 } context, screen_start;
162 
163 extern char	PC;		/* pad character (termcap) */
164 extern char	*__progname;	/* program name (crt0) */
165 
166 int   colon(char *, int, int);
167 int   command(char *, FILE *);
168 int   do_shell(char *);
169 int   expand(char *, size_t, char *);
170 int   get_line(FILE *, int *);
171 int   magic(FILE *, char *);
172 int   number(char *);
173 int   readch(void);
174 int   search(char *, FILE *, int);
175 int   ttyin(char *, int, char);
176 void  argscan(char *);
177 void  copy_file(FILE *);
178 void  doclear(void);
179 void  end_it(void);
180 void  erasep(int);
181 void  error(char *);
182 void  execute(char *filename, char *cmd, char *, char *, char *);
183 void  initterm(void);
184 void  kill_line(void);
185 void  onsignal(int);
186 void  prbuf(char *, int);
187 void  putch(int);
188 void  rdline(FILE *);
189 void  reset_tty(void);
190 void  screen(FILE *, int);
191 void  set_tty(void);
192 void  show(int);
193 void  skipf(int);
194 void  skiplns(int, FILE *);
195 char *resize_line(char *);
196 FILE *checkf(char *, int *);
197 __dead void usage(void);
198 struct sigaction sa;
199 
200 int
201 main(int argc, char **argv)
202 {
203 	FILE * volatile f;
204 	char		*s;
205 	volatile int	left;
206 	volatile off_t	initline;
207 	volatile int	prnames = 0;
208 	volatile int	initopt = 0;
209 	volatile int	srchopt = 0;
210 	int		clearit = 0;
211 	int		ch;
212 	char		initbuf[80];
213 
214 	setlocale(LC_ALL, "");
215 
216 	/* all signals just use a stub handler and interrupt syscalls */
217 	sigemptyset(&sa.sa_mask);
218 	sa.sa_flags = 0;
219 	sa.sa_handler = onsignal;
220 
221 	nfiles = argc;
222 	fnames = argv;
223 	initterm();
224 	nscroll = Lpp/2 - 1;
225 	if (nscroll <= 0)
226 		nscroll = 1;
227 	if ((s = getenv("MORE")) != NULL && *s != '\0')
228 		argscan(s);
229 	while (--nfiles > 0) {
230 		if ((ch = (*++fnames)[0]) == '-')
231 			argscan(*fnames + 1);
232 		else if (ch == '+') {
233 			s = *fnames;
234 			if (*++s == '/') {
235 				srchopt++;
236 				(void)strlcpy(initbuf, ++s, sizeof(initbuf));
237 			} else {
238 				initopt++;
239 				for (initline = 0; *s != '\0'; s++) {
240 					if (isdigit((unsigned char)*s))
241 						initline =
242 						    initline * 10 + *s - '0';
243 				}
244 				--initline;
245 			}
246 		} else
247 			break;
248 	}
249 	/*
250 	 * Allow clreol only if Home and eraseln and EodClr strings are
251 	 * defined, and in that case, make sure we are in noscroll mode.
252 	 */
253 	if (clreol) {
254 		if (Home == NULL || *Home == '\0' || eraseln == NULL ||
255 		    *eraseln == '\0' || EodClr == NULL || *EodClr == '\0')
256 			clreol = 0;
257 		else
258 			noscroll = 1;
259 	}
260 	if (dlines == 0)
261 		dlines = Lpp - 1;
262 	left = dlines;
263 	if (nfiles > 1)
264 		prnames++;
265 	if (!no_intty && nfiles == 0)
266 		usage();
267 	else
268 		f = stdin;
269 	if (!no_tty) {
270 		struct sigaction osa;
271 
272 		(void)sigaction(SIGQUIT, &sa, NULL);
273 		(void)sigaction(SIGINT, &sa, NULL);
274 		(void)sigaction(SIGWINCH, &sa, NULL);
275 		if (sigaction(SIGTSTP, &osa, NULL) == 0 &&
276 		    osa.sa_handler == SIG_DFL) {
277 			(void)sigaction(SIGTSTP, &sa, NULL);
278 			(void)sigaction(SIGTTIN, &sa, NULL);
279 			(void)sigaction(SIGTTOU, &sa, NULL);
280 			catch_susp++;
281 		}
282 		set_tty();
283 	}
284 	if (no_intty) {
285 		if (no_tty)
286 			copy_file(stdin);
287 		else {
288 			if ((ch = Getc(f)) == '\f')
289 				doclear();
290 			else {
291 				Ungetc(ch, f);
292 				if (noscroll && ch != EOF) {
293 					if (clreol)
294 						home();
295 					else
296 						doclear();
297 				}
298 			}
299 			if (srchopt) {
300 				if (search(initbuf, stdin, 1) == 0 && noscroll)
301 					left--;
302 			} else if (initopt)
303 				skiplns(initline, stdin);
304 			screen(stdin, left);
305 		}
306 		no_intty = 0;
307 		dup2(STDERR_FILENO, STDIN_FILENO);	/* stderr is a tty */
308 		prnames++;
309 		firstf = 0;
310 	}
311 
312 	while (fnum < nfiles) {
313 		if ((f = checkf(fnames[fnum], &clearit)) != NULL) {
314 			context.line = context.chrctr = 0;
315 			Currline = 0;
316 		restart:
317 			if (firstf) {
318 				firstf = 0;
319 				if (srchopt) {
320 					if (search(initbuf, f, 1) < 0)
321 						goto restart;
322 					if (noscroll)
323 						left--;
324 				} else if (initopt)
325 					skiplns(initline, f);
326 			} else if (fnum < nfiles && !no_tty)
327 				left = command(fnames[fnum], f);
328 			if (left != 0) {
329 				if ((noscroll || clearit) &&
330 				    (file_size != LONG_MAX)) {
331 					if (clreol)
332 						home();
333 					else
334 						doclear();
335 				}
336 				if (prnames) {
337 					if (bad_so)
338 						erasep(0);
339 					if (clreol)
340 						cleareol();
341 					fputs("::::::::::::::", stdout);
342 					if (promptlen > 14)
343 						erasep(14);
344 					putchar('\n');
345 					if (clreol)
346 						cleareol();
347 					printf("%s\n", fnames[fnum]);
348 					if (clreol)
349 						cleareol();
350 					fputs("::::::::::::::\n", stdout);
351 					if (left > Lpp - 4)
352 						left = Lpp - 4;
353 				}
354 				if (no_tty)
355 					copy_file(f);
356 				else {
357 					within++;
358 					screen(f, left);
359 					within = 0;
360 				}
361 			}
362 			fflush(stdout);
363 			fclose(f);
364 			screen_start.line = screen_start.chrctr = 0L;
365 			context.line = context.chrctr = 0L;
366 		}
367 		fnum++;
368 		firstf = 0;
369 	}
370 	reset_tty();
371 	exit(0);
372 }
373 
374 void
375 argscan(char *s)
376 {
377 	int seen_num = 0;
378 
379 	while (*s != '\0') {
380 		switch (*s) {
381 		case '0': case '1': case '2':
382 		case '3': case '4': case '5':
383 		case '6': case '7': case '8':
384 		case '9':
385 			if (!seen_num) {
386 				dlines = 0;
387 				seen_num = 1;
388 			}
389 			dlines = (dlines * 10) + (*s - '0');
390 			break;
391 		case 'd':
392 			dum_opt = 1;
393 			break;
394 		case 'l':
395 			stop_opt = 0;
396 			break;
397 		case 'f':
398 			fold_opt = 0;
399 			break;
400 		case 'p':
401 			noscroll++;
402 			break;
403 		case 'c':
404 			clreol++;
405 			break;
406 		case 's':
407 			ssp_opt = 1;
408 			break;
409 		case 'u':
410 			ul_opt = 0;
411 			break;
412 		case 'X':
413 		case 'E':
414 		case '-':
415 		case ' ':
416 		case '\t':
417 			break;
418 		default:
419 			fprintf(stderr, "%s: unknown option \"-%c\"\n",
420 			    __progname, *s);
421 			usage();
422 		}
423 		s++;
424 	}
425 }
426 
427 /*
428  * Check whether the file named by fs is an ASCII file which the user may
429  * access.  If it is, return the opened file. Otherwise return NULL.
430  */
431 FILE *
432 checkf(char *fs, int *clearfirst)
433 {
434 	struct stat stbuf;
435 	FILE *f;
436 	int ch;
437 
438 	if (stat(fs, &stbuf) == -1) {
439 		(void)fflush(stdout);
440 		if (clreol)
441 			cleareol();
442 		perror(fs);
443 		return (NULL);
444 	}
445 	if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
446 		printf("\n*** %s: directory ***\n\n", fs);
447 		return (NULL);
448 	}
449 	if ((f = Fopen(fs, "r")) == NULL) {
450 		(void)fflush(stdout);
451 		perror(fs);
452 		return (NULL);
453 	}
454 	if (magic(f, fs))
455 		return (NULL);
456 	ch = Getc(f);
457 	*clearfirst = (ch == '\f');
458 	Ungetc(ch, f);
459 	if ((file_size = stbuf.st_size) == 0)
460 		file_size = LONG_MAX;
461 	return (f);
462 }
463 
464 /*
465  * magic --
466  *	Check for file magic numbers.  This code would best be shared with
467  *	the file(1) program or, perhaps, more should not try and be so smart?
468  */
469 int
470 magic(FILE *f, char *fs)
471 {
472 	char twobytes[2];
473 
474 	/* don't try to look ahead if the input is unseekable */
475 	if (fseeko(f, (off_t)0, SEEK_SET))
476 		return (0);
477 
478 	if (fread(twobytes, 2, 1, f) == 1) {
479 		switch(twobytes[0] + (twobytes[1]<<8)) {
480 		case OMAGIC:
481 		case NMAGIC:
482 		case ZMAGIC:
483 		case 0405:
484 		case 0411:
485 		case 0x457f:
486 		case 0177545:
487 			printf("\n******** %s: Not a text file ********\n\n",
488 			    fs);
489 			(void)fclose(f);
490 			return (1);
491 		}
492 	}
493 	(void)fseeko(f, (off_t)0, SEEK_SET);	/* rewind() not necessary */
494 	return (0);
495 }
496 
497 /*
498  * A real function (not a macro), for the tputs() routine in termlib
499  */
500 void
501 putch(int ch)
502 {
503 	putchar(ch);
504 }
505 
506 #define	STOP	(-10)
507 
508 /*
509  * Print out the contents of the file f, one screenful at a time.
510  */
511 void
512 screen(FILE *f, int num_lines)
513 {
514 	int ch;
515 	int nchars;
516 	int length;			/* length of current line */
517 	static int prev_len = 1;	/* length of previous line */
518 
519 	for (;;) {
520 		while (num_lines > 0 && !Pause) {
521 			if ((nchars = get_line(f, &length)) == EOF) {
522 				if (clreol)
523 					clreos();
524 				return;
525 			}
526 			if (ssp_opt && length == 0 && prev_len == 0)
527 				continue;
528 			prev_len = length;
529 			if (bad_so ||
530 			    (Senter && *Senter == ' ' && promptlen > 0))
531 				erasep(0);
532 			/*
533 			 * Must clear before drawing line since tabs on some
534 			 * terminals do not erase what they tab over.
535 			 */
536 			if (clreol)
537 				cleareol();
538 			prbuf(Line, length);
539 			if (nchars < promptlen) {
540 				/* erasep() sets promptlen to 0 */
541 				erasep(nchars);
542 			} else
543 				promptlen = 0;
544 #if 0
545 			/* XXX - is this needed? */
546 			if (clreol) {
547 				/* must clear again in case we wrapped * */
548 				cleareol();
549 			}
550 #endif
551 			if (nchars < Mcol || !fold_opt) {
552 				/* will turn off UL if necessary */
553 				prbuf("\n", 1);
554 			}
555 			if (nchars == STOP)
556 				break;
557 			num_lines--;
558 		}
559 		if (pstate) {
560 			tputs(ULexit, 1, putch);
561 			pstate = 0;
562 		}
563 		fflush(stdout);
564 		if ((ch = Getc(f)) == EOF) {
565 			if (clreol)
566 				clreos();
567 			return;
568 		}
569 
570 		if (Pause && clreol)
571 			clreos();
572 		Ungetc(ch, f);
573 		Pause = 0;
574 		startup = 0;
575 		if ((num_lines = command(NULL, f)) == 0)
576 			return;
577 		if (hard && promptlen > 0)
578 			erasep(0);
579 		if (noscroll && num_lines >= dlines) {
580 			if (clreol)
581 				home();
582 			else
583 				doclear();
584 		}
585 		/*
586 		 * XXX - should store the *first* line on the screen,
587 		 * not the last (but we don't know the file position).
588 		 * Fixing this requires keeping an arry of dline off_ts
589 		 * and updating each one when a new line is started.
590 		 */
591 		screen_start.line = Currline;
592 		screen_start.chrctr = Ftell(f);
593 	}
594 }
595 
596 /*
597  * Clean up terminal state and exit. Also come here if interrupt signal received
598  */
599 void
600 end_it(void)
601 {
602 	reset_tty();
603 	if (clreol) {
604 		putchar('\r');
605 		clreos();
606 		fflush(stdout);
607 	} else if (promptlen > 0) {
608 		kill_line();
609 		fflush(stdout);
610 	} else
611 		write(STDERR_FILENO, "\n", 1);
612 	_exit(0);
613 }
614 
615 void
616 copy_file(FILE *f)
617 {
618 	int ch;
619 
620 	while ((ch = getc(f)) != EOF)
621 		putchar(ch);
622 }
623 
624 static char bell = ctrl('G');
625 
626 void
627 prompt(char *filename)
628 {
629 	if (clreol)
630 		cleareol();
631 	else if (promptlen > 0)
632 		kill_line();
633 	if (!hard) {
634 		promptlen = 8;
635 		if (Senter && Sexit) {
636 			tputs(Senter, 1, putch);
637 			promptlen += (2 * soglitch);
638 		}
639 		if (clreol)
640 			cleareol();
641 		fputs("--More--", stdout);
642 		if (filename != NULL)
643 			promptlen += printf("(Next file: %s)", filename);
644 		else if (!no_intty)
645 			promptlen += printf("(%d%%)",
646 			    (int)((file_pos * 100) / file_size));
647 		if (dum_opt) {
648 			fputs(DUM_PROMPT, stdout);
649 			promptlen += sizeof(DUM_PROMPT) - 1;
650 		}
651 		if (Senter && Sexit)
652 			tputs(Sexit, 1, putch);
653 		if (clreol)
654 			clreos();
655 		fflush(stdout);
656 	} else
657 		write(STDERR_FILENO, &bell, 1);
658 	inwait++;
659 }
660 
661 /*
662  * Get a logical line.
663  */
664 int
665 get_line(FILE *f, int *length)
666 {
667 	int		ch, lastch;
668 	char		*p, *ep;
669 	int		column;
670 	static int	colflg;
671 
672 	p = Line;
673 	ep = Line + linsize - 1;
674 	column = 0;
675 	ch = Getc(f);
676 	if (colflg && ch == '\n') {
677 		Currline++;
678 		ch = Getc(f);
679 	}
680 	for (;;) {
681 		if (p >= ep) {
682 			p = resize_line(p);
683 			ep = Line + linsize - 1;
684 		}
685 		if (ch == EOF) {
686 			if (p > Line) {
687 				*p = '\0';
688 				*length = p - Line;
689 				return (column);
690 			}
691 			*length = p - Line;
692 			return (EOF);
693 		}
694 		if (ch == '\n') {
695 			Currline++;
696 			break;
697 		}
698 		*p++ = (char)ch;
699 		if (ch == '\t') {
700 			if (!hardtabs || (column < promptlen && !hard)) {
701 				if (hardtabs && eraseln && !dumb) {
702 					column = 1 + (column | 7);
703 					tputs(eraseln, 1, putch);
704 					promptlen = 0;
705 				} else {
706 					for (--p;;) {
707 						if (p >= ep) {
708 							p = resize_line(p);
709 							ep = Line + linsize - 1;
710 						}
711 						*p++ = ' ';
712 						if ((++column & 7) == 0)
713 							break;
714 					}
715 					if (column >= promptlen)
716 						promptlen = 0;
717 				}
718 			} else
719 				column = 1 + (column | 7);
720 		} else if (ch == '\b' && column > 0)
721 			column--;
722 		else if (ch == '\f' && stop_opt) {
723 			p[-1] = '^';
724 			*p++ = 'L';
725 			column += 2;
726 			Pause++;
727 		} else if (ch == EOF) {
728 			*length = p - Line;
729 			return (column);
730 		} else if (ch >= ' ' && ch != RUBOUT)
731 			column++;
732 		if (column >= Mcol && fold_opt)
733 			break;
734 		lastch = ch;
735 		ch = Getc(f);
736 		if (lastch == '\r') {
737 			/*
738 			 * Reset column to 0 for carriage return unless
739 			 * immediately followed by a newline.
740 			 */
741 			if (ch != '\n')
742 				column = 0;
743 			else
744 				p--;
745 		}
746 	}
747 	/* XXX - potential oflow */
748 	if (column >= Mcol && Mcol > 0 && !Wrap)
749 		*p++ = '\n';
750 	colflg = (column == Mcol && fold_opt);
751 	if (colflg && eatnl && Wrap)
752 		*p++ = '\n';	/* simulate normal wrap */
753 	*length = p - Line;
754 	*p = '\0';
755 	return (column);
756 }
757 
758 /*
759  * Erase the rest of the prompt, assuming we are starting at column col.
760  */
761 void
762 erasep(int col)
763 {
764 	if (promptlen == 0)
765 		return;
766 	if (hard)
767 		putchar('\n');
768 	else {
769 		if (col == 0)
770 			putchar('\r');
771 		if (!dumb && eraseln)
772 			tputs(eraseln, 1, putch);
773 		else {
774 			for (col = promptlen - col; col > 0; col--)
775 				putchar(' ');
776 		}
777 	}
778 	promptlen = 0;
779 }
780 
781 /*
782  * Erase the current line entirely
783  */
784 void
785 kill_line(void)
786 {
787 	erasep(0);
788 	if (!eraseln || dumb)
789 		putchar('\r');
790 }
791 
792 /*
793  * Print a buffer of n characters.
794  */
795 void
796 prbuf(char *s, int n)
797 {
798 	char c;			/* next output character */
799 	int state;		/* next output char's UL state */
800 #define wouldul(s,n)	((n) >= 2 && (((s)[0] == '_' && (s)[1] == '\b') || ((s)[1] == '\b' && (s)[2] == '_')))
801 
802 	while (--n >= 0) {
803 		if (!ul_opt)
804 			putchar(*s++);
805 		else {
806 			if (*s == ' ' && pstate == 0 && ulglitch &&
807 			    wouldul(s + 1, n - 1)) {
808 				s++;
809 				continue;
810 			}
811 			if ((state = wouldul(s, n))) {
812 				c = (*s == '_')? s[2] : *s ;
813 				n -= 2;
814 				s += 3;
815 			} else
816 				c = *s++;
817 			if (state != pstate) {
818 				if (c == ' ' && state == 0 && ulglitch &&
819 				    wouldul(s, n - 1))
820 					state = 1;
821 				else
822 					tputs(state ? ULenter : ULexit, 1, putch);
823 			}
824 			if (c != ' ' || pstate == 0 || state != 0 ||
825 			    ulglitch == 0)
826 				putchar(c);
827 			if (state && *chUL) {
828 				fputs(chBS, stdout);
829 				tputs(chUL, 1, putch);
830 			}
831 			pstate = state;
832 		}
833 	}
834 }
835 
836 /*
837  * Clear the screen
838  */
839 void
840 doclear(void)
841 {
842 	if (Clear && !hard) {
843 		tputs(Clear, 1, putch);
844 
845 		/*
846 		 * Put out carriage return so that system doesn't
847 		 * get confused by escape sequences when expanding tabs.
848 		 */
849 		putchar('\r');
850 		promptlen = 0;
851 	}
852 }
853 
854 static int lastcmd, lastarg, lastp;
855 static int lastcolon;
856 char shell_line[BUFSIZ];
857 
858 /*
859  * Read a command and do it. A command consists of an optional integer
860  * argument followed by the command character.  Return the number of lines
861  * to display in the next screenful.  If there is nothing more to display
862  * in the current file, zero is returned.
863  */
864 int
865 command(char *filename, FILE *f)
866 {
867 	int nlines;
868 	int retval;
869 	int ch;
870 	char colonch;
871 	int done;
872 	char comchar, cmdbuf[80], *p;
873 
874 #define ret(val) retval=val;done++;break
875 
876 	retval = done = 0;
877 	if (!errors)
878 		prompt(filename);
879 	else
880 		errors = 0;
881 	for (;;) {
882 		nlines = number(&comchar);
883 		lastp = colonch = 0;
884 		if (comchar == '.') {	/* Repeat last command */
885 			lastp++;
886 			comchar = lastcmd;
887 			nlines = lastarg;
888 			if (lastcmd == ':')
889 				colonch = lastcolon;
890 		}
891 		lastcmd = comchar;
892 		lastarg = nlines;
893 		if (comchar == otty.c_cc[VERASE]) {
894 			kill_line();
895 			prompt(filename);
896 			continue;
897 		}
898 		switch (comchar) {
899 		case ':':
900 			retval = colon(filename, colonch, nlines);
901 			if (retval >= 0)
902 				done++;
903 			break;
904 		case 'b':
905 		case ctrl('B'):
906 		    {
907 			int initline;
908 
909 			if (no_intty) {
910 				write(STDERR_FILENO, &bell, 1);
911 				return (-1);
912 			}
913 
914 			if (nlines == 0)
915 				nlines++;
916 
917 			putchar('\r');
918 			erasep(0);
919 			putchar('\n');
920 			if (clreol)
921 				cleareol();
922 			printf("...back %d page", nlines);
923 			if (nlines > 1)
924 				fputs("s\n", stdout);
925 			else
926 				putchar('\n');
927 
928 			if (clreol)
929 				cleareol();
930 			putchar('\n');
931 
932 			initline = Currline - (off_t)dlines * (nlines + 1);
933 			if (!noscroll)
934 				--initline;
935 			if (initline < 0)
936 				initline = 0;
937 			Fseek(f, (off_t)0);
938 			Currline = 0; /* skiplns() will make Currline correct */
939 			skiplns(initline, f);
940 			ret(dlines);
941 		    }
942 		case ' ':
943 		case 'z':
944 			if (nlines == 0)
945 				nlines = dlines;
946 			else if (comchar == 'z')
947 				dlines = nlines;
948 			ret(nlines);
949 		case 'd':
950 		case ctrl('D'):
951 			if (nlines != 0)
952 				nscroll = nlines;
953 			ret(nscroll);
954 		case 'q':
955 		case 'Q':
956 			end_it();
957 		case 's':
958 		case 'f':
959 			if (nlines == 0)
960 				nlines++;
961 			if (comchar == 'f')
962 				nlines *= dlines;
963 			putchar('\r');
964 			erasep(0);
965 			putchar('\n');
966 			if (clreol)
967 				cleareol();
968 			printf("...skipping %d line", nlines);
969 			if (nlines > 1)
970 				fputs("s\n", stdout);
971 			else
972 				putchar('\n');
973 
974 			if (clreol)
975 				cleareol();
976 			putchar('\n');
977 
978 			while (nlines > 0) {
979 				while ((ch = Getc(f)) != '\n') {
980 					if (ch == EOF) {
981 						retval = 0;
982 						done++;
983 						goto endsw;
984 					}
985 				}
986 				Currline++;
987 				nlines--;
988 			}
989 			ret(dlines);
990 		case '\n':
991 			if (nlines != 0)
992 				dlines = nlines;
993 			else
994 				nlines = 1;
995 			ret(nlines);
996 		case '\f':
997 			if (!no_intty) {
998 				doclear();
999 				Fseek(f, screen_start.chrctr);
1000 				Currline = screen_start.line;
1001 				ret(dlines);
1002 			} else {
1003 				write(STDERR_FILENO, &bell, 1);
1004 				break;
1005 			}
1006 		case '\'':
1007 			if (!no_intty) {
1008 				kill_line();
1009 				fputs("\n***Back***\n\n", stdout);
1010 				Fseek(f, context.chrctr);
1011 				Currline = context.line;
1012 				ret(dlines);
1013 			} else {
1014 				write(STDERR_FILENO, &bell, 1);
1015 				break;
1016 			}
1017 		case '=':
1018 			kill_line();
1019 			promptlen = printf("%lld", (long long)Currline);
1020 			fflush(stdout);
1021 			break;
1022 		case 'n':
1023 			lastp++;
1024 		case '/':
1025 			if (nlines == 0)
1026 				nlines++;
1027 			kill_line();
1028 			putchar('/');
1029 			promptlen = 1;
1030 			fflush(stdout);
1031 			if (lastp) {
1032 				/* Use previous r.e. */
1033 				write(STDERR_FILENO, "\r", 1);
1034 				if (search(NULL, f, nlines) < 0)
1035 					break;
1036 			} else {
1037 				if (ttyin(cmdbuf, sizeof(cmdbuf) - 2, '/') < 0) {
1038 					kill_line();
1039 					prompt(filename);
1040 					continue;
1041 				}
1042 				write(STDERR_FILENO, "\r", 1);
1043 				if (search(cmdbuf, f, nlines) < 0)
1044 					break;
1045 			}
1046 			ret(dlines-1);
1047 		case '!':
1048 			if (do_shell(filename) < 0) {
1049 				kill_line();
1050 				prompt(filename);
1051 				continue;
1052 			}
1053 			break;
1054 		case '?':
1055 		case 'h':
1056 			if (noscroll)
1057 				doclear();
1058 			fputs(more_help, stdout);
1059 			prompt(filename);
1060 			break;
1061 		case 'v':	/* This case should go right before default */
1062 			if (!no_intty) {
1063 				char *editor;
1064 
1065 				editor = getenv("VISUAL");
1066 				if (editor == NULL || *editor == '\0')
1067 					editor = getenv("EDITOR");
1068 				if (editor == NULL || *editor == '\0')
1069 					editor = _PATH_VI;
1070 				if ((p = strrchr(editor, '/')) != NULL)
1071 					p++;
1072 				else
1073 					p = editor;
1074 				kill_line();
1075 				snprintf(cmdbuf, sizeof(cmdbuf), "+%lld",
1076 				    (long long)Currline);
1077 				if (!altscr)
1078 					printf("%s %s %s", p, cmdbuf,
1079 					    fnames[fnum]);
1080 				execute(filename, editor, p, cmdbuf,
1081 				    fnames[fnum]);
1082 				break;
1083 			}
1084 		default:
1085 			if (dum_opt) {
1086 				kill_line();
1087 				if (Senter && Sexit) {
1088 					tputs(Senter, 1, putch);
1089 					fputs(DUM_ERROR, stdout);
1090 					promptlen = sizeof(DUM_ERROR) - 1 +
1091 					    (2 * soglitch);
1092 					tputs(Sexit, 1, putch);
1093 				} else {
1094 					fputs(DUM_ERROR, stdout);
1095 					promptlen = sizeof(DUM_ERROR) - 1;
1096 				}
1097 				fflush(stdout);
1098 			} else
1099 				write(STDERR_FILENO, &bell, 1);
1100 			break;
1101 		}
1102 		if (done)
1103 			break;
1104 	}
1105 	putchar('\r');
1106 endsw:
1107 	inwait = 0;
1108 	notell++;
1109 	return (retval);
1110 }
1111 
1112 /*
1113  * Execute a colon-prefixed command.
1114  * Returns <0 if not a command that should cause
1115  * more of the file to be printed.
1116  */
1117 int
1118 colon(char *filename, int cmd, int nlines)
1119 {
1120 	int ch;
1121 
1122 	if (cmd == 0)
1123 		ch = readch();
1124 	else
1125 		ch = cmd;
1126 	lastcolon = ch;
1127 	switch (ch) {
1128 	case 'f':
1129 		kill_line();
1130 		if (!no_intty)
1131 			promptlen =
1132 			    printf("\"%s\" line %lld", fnames[fnum],
1133 				(long long)Currline);
1134 		else
1135 			promptlen = printf("[Not a file] line %lld",
1136 			    (long long)Currline);
1137 		fflush(stdout);
1138 		return (-1);
1139 	case 'n':
1140 		if (nlines == 0) {
1141 			if (fnum >= nfiles - 1)
1142 				end_it();
1143 			nlines++;
1144 		}
1145 		putchar('\r');
1146 		erasep(0);
1147 		skipf(nlines);
1148 		return (0);
1149 	case 'p':
1150 		if (no_intty) {
1151 			write(STDERR_FILENO, &bell, 1);
1152 			return (-1);
1153 		}
1154 		putchar('\r');
1155 		erasep(0);
1156 		if (nlines == 0)
1157 			nlines++;
1158 		skipf (-nlines);
1159 		return (0);
1160 	case '!':
1161 		if (do_shell(filename) < 0) {
1162 			kill_line();
1163 			prompt(filename);
1164 		}
1165 		return (-1);
1166 	case 'q':
1167 	case 'Q':
1168 		end_it();
1169 		/*FALLTHROUGH*/
1170 	default:
1171 		write(STDERR_FILENO, &bell, 1);
1172 		return (-1);
1173 	}
1174 }
1175 
1176 /*
1177  * Read a decimal number from the terminal. Set cmd to the non-digit which
1178  * terminates the number.
1179  */
1180 int
1181 number(char *cmd)
1182 {
1183 	int ch, i;
1184 
1185 	ch = otty.c_cc[VKILL];
1186 	i = 0;
1187 	for (;;) {
1188 		ch = readch();
1189 		if (isdigit(ch))
1190 			i = i*10 + ch - '0';
1191 		else if (ch == otty.c_cc[VKILL])
1192 			i = 0;
1193 		else {
1194 			*cmd = ch;
1195 			break;
1196 		}
1197 	}
1198 	return (i);
1199 }
1200 
1201 int
1202 do_shell(char *filename)
1203 {
1204 	char cmdbuf[200];
1205 
1206 	kill_line();
1207 	putchar('!');
1208 	fflush(stdout);
1209 	promptlen = 1;
1210 	if (lastp)
1211 		fputs(shell_line, stdout);
1212 	else {
1213 		if (ttyin(cmdbuf, sizeof(cmdbuf) - 2, '!') < 0)
1214 			return (-1);
1215 		if (expand(shell_line, sizeof(shell_line), cmdbuf)) {
1216 			kill_line();
1217 			promptlen = printf("!%s", shell_line);
1218 		}
1219 	}
1220 	fflush(stdout);
1221 	write(STDERR_FILENO, "\n", 1);
1222 	promptlen = 0;
1223 	shellp = 1;
1224 	execute(filename, shell, shell, "-c", shell_line);
1225 }
1226 
1227 /*
1228  * Search for nth occurrence of regular expression contained in buf in the file
1229  */
1230 int
1231 search(char *buf, FILE *file, int n)
1232 {
1233 	off_t startline = Ftell(file);
1234 	off_t line1 = startline;
1235 	off_t line2 = startline;
1236 	off_t line3 = startline;
1237 	off_t saveln;
1238 	int lncount, rv;
1239 	char ebuf[BUFSIZ];
1240 	static regex_t reg;
1241 	static int initialized;
1242 
1243 	context.line = saveln = Currline;
1244 	context.chrctr = startline;
1245 	lncount = 0;
1246 	if (buf != NULL && *buf != '\0') {
1247 		if ((rv = regcomp(&reg, buf, REG_NOSUB)) != 0) {
1248 			initialized = 0;
1249 			regerror(rv, &reg, ebuf, sizeof(ebuf));
1250 			regfree(&reg);
1251 			error(ebuf);
1252 			return (-1);
1253 		}
1254 		initialized = 1;
1255 	} else if (!initialized) {
1256 		error("No previous regular expression");
1257 		return (-1);
1258 	}
1259 	while (!feof(file)) {
1260 		line3 = line2;
1261 		line2 = line1;
1262 		line1 = Ftell(file);
1263 		rdline(file);
1264 		lncount++;
1265 		if ((rv = regexec(&reg, Line, 0, NULL, 0)) == 0) {
1266 			if (--n == 0) {
1267 				if (lncount > 3 || (lncount > 1 && no_intty)) {
1268 					putchar('\n');
1269 					if (clreol)
1270 						cleareol();
1271 					fputs("...skipping\n", stdout);
1272 				}
1273 				if (!no_intty) {
1274 					Currline -= (lncount >= 3 ? 3 : lncount);
1275 					Fseek(file, line3);
1276 					if (noscroll) {
1277 						if (clreol) {
1278 							home();
1279 							cleareol();
1280 						} else
1281 							doclear();
1282 					}
1283 				} else {
1284 					kill_line();
1285 					if (noscroll) {
1286 					    if (clreol) {
1287 						    home();
1288 						    cleareol();
1289 					    } else
1290 						    doclear();
1291 					}
1292 					fputs(Line, stdout);
1293 					putchar('\n');
1294 				}
1295 				break;
1296 			}
1297 		} else if (rv != REG_NOMATCH) {
1298 			regerror(rv, &reg, ebuf, sizeof(ebuf));
1299 			error(ebuf);
1300 			return (-1);
1301 		}
1302 	}
1303 	if (feof(file)) {
1304 		if (!no_intty) {
1305 			Currline = saveln;
1306 			Fseek(file, startline);
1307 		} else {
1308 			fputs("\nPattern not found\n", stdout);
1309 			end_it();
1310 		}
1311 		error("Pattern not found");
1312 		return (-1);
1313 	}
1314 	return (0);
1315 }
1316 
1317 void
1318 execute(char *filename, char *cmd, char *av0, char *av1, char *av2)
1319 {
1320 	int id;
1321 	int n;
1322 	char *argp[4];
1323 
1324 	argp[0] = av0;
1325 	argp[1] = av1;
1326 	argp[2] = av2;
1327 	argp[3] = NULL;
1328 
1329 	fflush(stdout);
1330 	reset_tty();
1331 	for (n = 10; (id = fork()) < 0 && n > 0; n--)
1332 		sleep(5);
1333 	if (id == 0) {
1334 		execvp(cmd, argp);
1335 		write(STDERR_FILENO, "exec failed\n", 12);
1336 		exit(1);
1337 	}
1338 	if (id > 0) {
1339 		sa.sa_flags = SA_RESTART;
1340 		sa.sa_handler = SIG_IGN;
1341 		(void)sigaction(SIGINT, &sa, NULL);
1342 		(void)sigaction(SIGQUIT, &sa, NULL);
1343 		if (catch_susp) {
1344 			sa.sa_handler = SIG_DFL;
1345 			(void)sigaction(SIGTSTP, &sa, NULL);
1346 			(void)sigaction(SIGTTIN, &sa, NULL);
1347 			(void)sigaction(SIGTTOU, &sa, NULL);
1348 		}
1349 		while (wait(NULL) > 0)
1350 			continue;
1351 		sa.sa_flags = 0;
1352 		sa.sa_handler = onsignal;
1353 		(void)sigaction(SIGINT, &sa, NULL);
1354 		(void)sigaction(SIGQUIT, &sa, NULL);
1355 		if (catch_susp) {
1356 			(void)sigaction(SIGTSTP, &sa, NULL);
1357 			(void)sigaction(SIGTTIN, &sa, NULL);
1358 			(void)sigaction(SIGTTOU, &sa, NULL);
1359 		}
1360 	} else
1361 		write(STDERR_FILENO, "can't fork\n", 11);
1362 	set_tty();
1363 	if (!altscr)
1364 		fputs("------------------------\n", stdout);
1365 	prompt(filename);
1366 }
1367 
1368 /*
1369  * Skip n lines in the file f
1370  */
1371 void
1372 skiplns(int n, FILE *f)
1373 {
1374 	int ch;
1375 
1376 	while (n > 0) {
1377 		while ((ch = Getc(f)) != '\n') {
1378 			if (ch == EOF)
1379 				return;
1380 		}
1381 		n--;
1382 		Currline++;
1383 	}
1384 }
1385 
1386 /*
1387  * Skip nskip files in the file list (from the command line).
1388  * Nskip may be negative.
1389  */
1390 void
1391 skipf(int nskip)
1392 {
1393 	if (nskip == 0)
1394 		return;
1395 	if (nskip > 0) {
1396 		if (fnum + nskip > nfiles - 1)
1397 			nskip = nfiles - fnum - 1;
1398 	}
1399 	else if (within)
1400 		++fnum;
1401 	fnum += nskip;
1402 	if (fnum < 0)
1403 		fnum = 0;
1404 	fputs("\n...Skipping \n", stdout); /* XXX huh? */
1405 	if (clreol)
1406 		cleareol();
1407 	printf("...Skipping %sto file %s\n", nskip > 0 ? "" : "back ",
1408 	    fnames[fnum]);
1409 	if (clreol)
1410 		cleareol();
1411 	putchar('\n');
1412 	--fnum;
1413 }
1414 
1415 /*
1416  * Terminal I/O
1417  */
1418 void
1419 initterm(void)
1420 {
1421 	char		buf[TBUFSIZ];
1422 	static char	clearbuf[TBUFSIZ];
1423 	char		*clearptr, *padstr;
1424 	char		*term;
1425 	int		tgrp;
1426 	struct winsize	win;
1427 
1428 retry:
1429 	if (!(no_tty = tcgetattr(STDOUT_FILENO, &otty))) {
1430 		docrterase = (otty.c_cc[VERASE] != _POSIX_VDISABLE);
1431 		docrtkill =  (otty.c_cc[VKILL] != _POSIX_VDISABLE);
1432 		/*
1433 		 * Wait until we're in the foreground before we save the
1434 		 * the terminal modes.
1435 		 */
1436 		if ((tgrp = tcgetpgrp(STDOUT_FILENO)) < 0) {
1437 			perror("tcgetpgrp");
1438 			exit(1);
1439 		}
1440 		if (tgrp != getpgrp()) {
1441 			kill(0, SIGTTOU);
1442 			goto retry;
1443 		}
1444 		if ((term = getenv("TERM")) == 0 || tgetent(buf, term) <= 0) {
1445 			dumb++; ul_opt = 0;
1446 		} else {
1447 			if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) < 0) {
1448 				Lpp = tgetnum("li");
1449 				Mcol = tgetnum("co");
1450 			} else {
1451 				if ((Lpp = win.ws_row) == 0)
1452 					Lpp = tgetnum("li");
1453 				if ((Mcol = win.ws_col) == 0)
1454 					Mcol = tgetnum("co");
1455 			}
1456 			if (Lpp <= 0 || tgetflag("hc")) {
1457 				hard++;		/* Hard copy terminal */
1458 				Lpp = 24;
1459 			}
1460 			if (tgetflag("xn")) {
1461 				/* Eat newline at last column + 1 */
1462 				eatnl++;
1463 			}
1464 			if (Mcol <= 0)
1465 				Mcol = 80;
1466 
1467 			if (strcmp(__progname, "page") == 0 ||
1468 			    (!hard && tgetflag("ns")))
1469 				noscroll++;
1470 			Wrap = tgetflag("am");
1471 			bad_so = tgetflag ("xs");
1472 			clearptr = clearbuf;
1473 			eraseln = tgetstr("ce", &clearptr);
1474 			Clear = tgetstr("cl", &clearptr);
1475 			Senter = tgetstr("so", &clearptr);
1476 			Sexit = tgetstr("se", &clearptr);
1477 			if ((soglitch = tgetnum("sg")) < 0)
1478 				soglitch = 0;
1479 
1480 			/*
1481 			 * Setup for underlining.  Some terminals don't need it,
1482 			 * others have start/stop sequences, still others have
1483 			 * an underline char sequence which is assumed to move
1484 			 * the cursor forward one character.  If underline seq
1485 			 * isn't available, settle for standout sequence.
1486 			 */
1487 			if (tgetflag("ul") || tgetflag("os"))
1488 				ul_opt = 0;
1489 			if ((chUL = tgetstr("uc", &clearptr)) == NULL)
1490 				chUL = "";
1491 			if (((ULenter = tgetstr("us", &clearptr)) == NULL ||
1492 			    (ULexit = tgetstr("ue", &clearptr)) == NULL) &&
1493 			    !*chUL) {
1494 				if ((ULenter = Senter) == NULL ||
1495 				    (ULexit = Sexit) == NULL) {
1496 					ULenter = "";
1497 					ULexit = "";
1498 				} else
1499 					ulglitch = soglitch;
1500 			} else {
1501 				if ((ulglitch = tgetnum("ug")) < 0)
1502 					ulglitch = 0;
1503 			}
1504 
1505 			if ((padstr = tgetstr("pc", &clearptr)))
1506 				PC = *padstr;
1507 			Home = tgetstr("ho", &clearptr);
1508 			if (Home == 0 || *Home == '\0') {
1509 				cursorm = tgetstr("cm", &clearptr);
1510 				if (cursorm != NULL) {
1511 					strlcpy(cursorhome,
1512 					    tgoto(cursorm, 0, 0),
1513 					    sizeof(cursorhome));
1514 					Home = cursorhome;
1515 				}
1516 			}
1517 			EodClr = tgetstr("cd", &clearptr);
1518 			if ((chBS = tgetstr("bc", &clearptr)) == NULL)
1519 				chBS = "\b";
1520 			if (tgetstr("te", &clearptr) != NULL &&
1521 			    tgetstr("ti", &clearptr) != NULL)
1522 				altscr = 1;
1523 		}
1524 		if ((shell = getenv("SHELL")) == NULL)
1525 			shell = _PATH_BSHELL;
1526 	}
1527 	no_intty = !isatty(STDIN_FILENO);
1528 	tcgetattr(STDERR_FILENO, &otty);
1529 	slow_tty = cfgetospeed(&otty) < B1200;
1530 	hardtabs = !(otty.c_oflag & OXTABS);
1531 	ntty = otty;
1532 	if (!no_tty) {
1533 		ntty.c_lflag &= ~(ICANON|ECHO);
1534 		ntty.c_cc[VMIN] = 1;	/* read at least 1 char */
1535 		ntty.c_cc[VTIME] = 0;	/* no timeout */
1536 	}
1537 }
1538 
1539 int
1540 handle_signal(void)
1541 {
1542 	int sig, ch = -1;
1543 
1544 	for (sig = 0; sig < _NSIG; sig++) {
1545 		if (signo[sig] == 0)
1546 			continue;
1547 		signo[sig] = 0;
1548 
1549 		switch (sig) {
1550 		case SIGQUIT:
1551 			if (!inwait) {
1552 				putchar('\n');
1553 				if (startup)
1554 					Pause++;
1555 			} else if (!dum_opt && notell) {
1556 				write(STDERR_FILENO, QUIT_IT,
1557 				    sizeof(QUIT_IT) - 1);
1558 				promptlen += sizeof(QUIT_IT) - 1;
1559 				notell = 0;
1560 			}
1561 			break;
1562 		case SIGTSTP:
1563 		case SIGTTIN:
1564 		case SIGTTOU:
1565 			/* XXX - should use saved values instead of SIG_DFL */
1566 			sa.sa_handler = SIG_DFL;
1567 			sa.sa_flags = SA_RESTART;
1568 			(void)sigaction(SIGTSTP, &sa, NULL);
1569 			(void)sigaction(SIGTTIN, &sa, NULL);
1570 			(void)sigaction(SIGTTOU, &sa, NULL);
1571 			reset_tty();
1572 			kill(getpid(), sig);
1573 
1574 			sa.sa_handler = onsignal;
1575 			sa.sa_flags = 0;
1576 			(void)sigaction(SIGTSTP, &sa, NULL);
1577 			(void)sigaction(SIGTTIN, &sa, NULL);
1578 			(void)sigaction(SIGTTOU, &sa, NULL);
1579 			set_tty();
1580 			if (!no_intty)
1581 				ch = '\f';	/* force redraw */
1582 			break;
1583 		case SIGINT:
1584 			end_it();
1585 			break;
1586 		case SIGWINCH: {
1587 			struct winsize win;
1588 
1589 			if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) != 0)
1590 				break;
1591 			if (win.ws_row != 0) {
1592 				Lpp = win.ws_row;
1593 				nscroll = Lpp/2 - 1;
1594 				if (nscroll <= 0)
1595 					nscroll = 1;
1596 				dlines = Lpp - 1;
1597 			}
1598 			if (win.ws_col != 0)
1599 				Mcol = win.ws_col;
1600 			if (!no_intty)
1601 				ch = '\f';	/* force redraw */
1602 			break;
1603 		} default:
1604 			/* NOTREACHED */
1605 			break;
1606 		}
1607 	}
1608 	return (ch);
1609 }
1610 
1611 int
1612 readch(void)
1613 {
1614 	unsigned char ch;
1615 	int r;
1616 
1617 	/* We know stderr is hooked up to /dev/tty so this is safe. */
1618 again:
1619 	switch (read(STDERR_FILENO, &ch, 1)) {
1620 	case 1:
1621 		return (ch);
1622 	case -1:
1623 		if (errno != EINTR)
1624 			end_it();
1625 
1626 		r = handle_signal();
1627 		if (r == -1)
1628 			goto again;
1629 		return (r);		/* redraw, continue, etc */
1630 	case 0:
1631 		end_it();
1632 	}
1633 }
1634 
1635 static char BS1 = '\b';
1636 static char BSB[] = "\b \b";
1637 static char CARAT = '^';
1638 #define	ERASEONECHAR	do {					\
1639 	if (docrterase)						\
1640 		write(STDERR_FILENO, BSB, sizeof(BSB) - 1);	\
1641 	else							\
1642 		write(STDERR_FILENO, &BS1, 1);			\
1643 } while (0)
1644 
1645 int
1646 ttyin(char *buf, int nmax, char pchar)
1647 {
1648 	char	cbuf, ch, *sptr;
1649 	int	maxlen, slash;
1650 
1651 	sptr = buf;
1652 	slash = maxlen = 0;
1653 	while (sptr - buf < nmax) {
1654 		if (promptlen > maxlen)
1655 			maxlen = promptlen;
1656 		ch = readch();
1657 		if (ch == '\\')
1658 			slash++;
1659 		else if ((ch == otty.c_cc[VERASE]) && !slash) {
1660 			if (sptr > buf) {
1661 				--promptlen;
1662 				ERASEONECHAR;
1663 				--sptr;
1664 				if ((*sptr < ' ' && *sptr != '\n') ||
1665 				    *sptr == RUBOUT) {
1666 					--promptlen;
1667 					ERASEONECHAR;
1668 				}
1669 				continue;
1670 			} else {
1671 				if (!eraseln)
1672 					promptlen = maxlen;
1673 				return (-1);
1674 			}
1675 		} else if ((ch == otty.c_cc[VKILL]) && !slash) {
1676 			if (hard) {
1677 				show(ch);
1678 				putchar('\n');
1679 				putchar(pchar);
1680 			} else {
1681 				putchar('\r');
1682 				putchar(pchar);
1683 				if (eraseln)
1684 					erasep(1);
1685 				else if (docrtkill) {
1686 					while (promptlen-- > 1)
1687 						write(STDERR_FILENO, BSB,
1688 						    sizeof(BSB) - 1);
1689 				}
1690 				promptlen = 1;
1691 			}
1692 			sptr = buf;
1693 			fflush(stdout);
1694 			continue;
1695 		}
1696 		if (slash && (ch == otty.c_cc[VKILL] ||
1697 		    ch == otty.c_cc[VERASE])) {
1698 			ERASEONECHAR;
1699 			--sptr;
1700 		}
1701 		if (ch != '\\')
1702 			slash = 0;
1703 		*sptr++ = ch;
1704 		if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) {
1705 			ch += ch == RUBOUT ? -0100 : 0100;
1706 			write(STDERR_FILENO, &CARAT, 1);
1707 			promptlen++;
1708 		}
1709 		cbuf = ch;
1710 		if (ch != '\n' && ch != ESC) {
1711 			write(STDERR_FILENO, &cbuf, 1);
1712 			promptlen++;
1713 		} else
1714 			break;
1715 	}
1716 	*--sptr = '\0';
1717 	if (!eraseln)
1718 		promptlen = maxlen;
1719 	if (sptr - buf >= nmax - 1)
1720 		error("Line too long");
1721 
1722 	return (0);
1723 }
1724 
1725 int
1726 expand(char *outbuf, size_t olen, char *inbuf)
1727 {
1728 	size_t len;
1729 	char *instr;
1730 	char *outstr;
1731 	char c;
1732 	char temp[200];
1733 	int changed = 0;
1734 
1735 	instr = inbuf;
1736 	outstr = temp;
1737 	while ((c = *instr++) != '\0') {
1738 		switch (c) {
1739 		case '%':
1740 			if (!no_intty) {
1741 				len = strlcpy(outstr, fnames[fnum],
1742 				    temp + sizeof(temp) - outstr);
1743 				if (len >= temp + sizeof(temp) - outstr)
1744 					len = temp + sizeof(temp) - outstr - 1;
1745 				outstr += len;
1746 				changed++;
1747 			} else
1748 				*outstr++ = c;
1749 			break;
1750 		case '!':
1751 			if (!shellp)
1752 				error("No previous command to substitute for");
1753 			len = strlcpy(outstr, shell_line,
1754 			    temp + sizeof(temp) - outstr);
1755 			if (len >= temp + sizeof(temp) - outstr)
1756 				len = temp + sizeof(temp) - outstr - 1;
1757 			outstr += len;
1758 			changed++;
1759 			break;
1760 		case '\\':
1761 			if (*instr == '%' || *instr == '!') {
1762 				*outstr++ = *instr++;
1763 				break;
1764 			}
1765 		default:
1766 			*outstr++ = c;
1767 			break;
1768 		}
1769 	}
1770 	*outstr++ = '\0';
1771 	strlcpy(outbuf, temp, olen);
1772 	return (changed);
1773 }
1774 
1775 void
1776 show(int ch)
1777 {
1778 	char cbuf;
1779 
1780 	if ((ch < ' ' && ch != '\n' && ch != ESC) || ch == RUBOUT) {
1781 		ch += ch == RUBOUT ? -0100 : 0100;
1782 		write(STDERR_FILENO, &CARAT, 1);
1783 		promptlen++;
1784 	}
1785 	cbuf = ch;
1786 	write(STDERR_FILENO, &cbuf, 1);
1787 	promptlen++;
1788 }
1789 
1790 void
1791 error(char *mess)
1792 {
1793 	if (clreol)
1794 		cleareol();
1795 	else
1796 		kill_line();
1797 	promptlen += strlen (mess);
1798 	if (Senter && Sexit) {
1799 		tputs(Senter, 1, putch);
1800 		fputs(mess, stdout);
1801 		tputs(Sexit, 1, putch);
1802 	} else
1803 		fputs(mess, stdout);
1804 	fflush(stdout);
1805 	errors++;
1806 }
1807 
1808 void
1809 set_tty(void)
1810 {
1811 	tcsetattr(STDERR_FILENO, TCSANOW, &ntty);
1812 }
1813 
1814 void
1815 reset_tty(void)
1816 {
1817 	if (no_tty)
1818 		return;
1819 	if (pstate) {
1820 		tputs(ULexit, 1, putch);
1821 		fflush(stdout);
1822 		pstate = 0;
1823 	}
1824 	tcsetattr(STDERR_FILENO, TCSANOW, &otty);
1825 }
1826 
1827 void
1828 rdline(FILE *f)
1829 {
1830 	int ch;
1831 	char *p, *ep;
1832 
1833 	p = Line;
1834 	ep = Line + linsize - 1;
1835 	while ((ch = Getc(f)) != '\n' && ch != EOF) {
1836 		if (p >= ep) {
1837 			p = resize_line(p);
1838 			ep = Line + linsize - 1;
1839 		}
1840 		*p++ = (char)ch;
1841 	}
1842 	if (ch == '\n')
1843 		Currline++;
1844 	*p = '\0';
1845 }
1846 
1847 char *
1848 resize_line(char *pos)
1849 {
1850 	char *np;
1851 
1852 	linsize *= 2;
1853 	if (Line != Lineb)
1854 		np = realloc(Line, linsize);
1855 	else if ((np = malloc(linsize)) != NULL)
1856 		memcpy(np, Lineb, sizeof(Lineb));
1857 	if (np == NULL) {
1858 		kill_line();
1859 		fputs("out of memory!\n", stdout);
1860 		reset_tty();
1861 		exit(1);
1862 	}
1863 	pos = np + (pos - Line);
1864 	Line = np;
1865 
1866 	return (pos);
1867 }
1868 
1869 /*
1870  * Come here when we get a signal we can handle.
1871  */
1872 void
1873 onsignal(int sig)
1874 {
1875 	signo[sig] = 1;
1876 }
1877 
1878 __dead void
1879 usage(void)
1880 {
1881 	fprintf(stderr,
1882 	    "usage: %s [-dfln] [+linenum | +/pattern] name1 name2 ...\n",
1883 	    __progname);
1884 	exit(1);
1885 }
1886