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