1 /*
2 * Copyright (C) 1980 The Regents of the University of California.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms are permitted
6 * provided that the above copyright notice and this paragraph are
7 * duplicated in all such forms and that any documentation,
8 * advertising materials, and other materials related to such
9 * distribution and use acknowledge that the software was developed
10 * by the University of California, Berkeley. The name of the
11 * University may not be used to endorse or promote products derived
12 * from this software without specific prior written permission.
13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16 */
17
18 /* more.c - General purpose tty output filter and file perusal program
19 *
20 * by Eric Shienbrood, UC Berkeley
21 *
22 * modified by Geoff Peck
23 * UCB to add underlining, single spacing
24 * modified by John Foderaro
25 * UCB to add -c and MORE environment variable
26 * modified by Erik Troan <ewt@redhat.com>
27 * to be more posix and so compile on linux/axp.
28 * modified by Kars de Jong <jongk@cs.utwente.nl>
29 * to use terminfo instead of termcap.
30 * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
31 * added Native Language Support
32 * 1999-03-19 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
33 * more nls translatable strings
34 * 1999-05-09 aeb
35 * applied a RedHat patch (setjmp->sigsetjmp); without it a second
36 * ^Z would fail.
37 * 1999-05-09 aeb
38 * undone Kars' work, so that more works without libcurses (and
39 * hence can be in /bin with libcurses being in
40 * /usr/lib which may not be mounted). However, when termcap is not
41 * present curses can still be used.
42 * 2010-10-21 Davidlohr Bueso <dave@gnu.org>
43 * modified mem allocation handling for util-linux
44 */
45
46 #include <stdio.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <stdlib.h> /* for alloca() */
50 #include <stdarg.h> /* for va_start() etc */
51 #include <sys/param.h>
52 #include <ctype.h>
53 #include <signal.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <termios.h>
57 #include <setjmp.h>
58 #include <sys/ioctl.h>
59 #include <sys/stat.h>
60 #include <sys/file.h>
61 #include <sys/wait.h>
62
63 #include "strutils.h"
64 #include "nls.h"
65 #include "xalloc.h"
66 #include "widechar.h"
67 #include "closestream.h"
68
69 #include <regex.h>
70
71 #ifdef TEST_PROGRAM
72 # define NON_INTERACTIVE_MORE 1
73 #endif
74
75 #ifndef XTABS
76 # define XTABS TAB3
77 #endif
78
79 #define VI "vi" /* found on the user's path */
80
81 #define Fopen(s,m) (Currline = 0,file_pos=0,fopen(s,m))
82 #define Ftell(f) file_pos
83 #define Fseek(f,off) (file_pos=off,fseek(f,off,0))
84 #define Getc(f) (++file_pos, getc(f))
85 #define Ungetc(c,f) (--file_pos, ungetc(c,f))
86 #define putcerr(c) fputc(c, stderr)
87 #define putserr(s) fputs(s, stderr)
88 #define putsout(s) fputs(s, stdout)
89
90 #define stty(fd,argp) tcsetattr(fd,TCSANOW,argp)
91
92 /* some function declarations */
93 void initterm(void);
94 void kill_line(void);
95 void doclear(void);
96 void cleareol(void);
97 void clreos(void);
98 void home(void);
99 void more_error(char *mess);
100 void do_shell(char *filename);
101 int colon(char *filename, int cmd, int nlines);
102 int expand(char **outbuf, char *inbuf);
103 void argscan(char *s);
104 void rdline(register FILE *f);
105 void copy_file(register FILE *f);
106 void search(char buf[], FILE *file, register int n);
107 void skipf(register int nskip);
108 void skiplns(register int n, register FILE *f);
109 void screen(register FILE *f, register int num_lines);
110 int command(char *filename, register FILE *f);
111 void erasep(register int col);
112 void show(register char ch);
113 void set_tty(void);
114 void reset_tty(void);
115 void ttyin(char buf[], register int nmax, char pchar);
116 int number(char *cmd);
117 int readch(void);
118 int get_line(register FILE *f, int *length);
119 void prbuf(register char *s, register int n);
120 void execute(char *filename, char *cmd, ...);
121 FILE *checkf(char *, int *);
122 void prepare_line_buffer(void);
123
124 #define TBUFSIZ 1024
125 #define LINSIZ 256 /* minimal Line buffer size */
126 #define ctrl(letter) (letter & 077)
127 #define RUBOUT '\177'
128 #define ESC '\033'
129 #define QUIT '\034'
130 #define SCROLL_LEN 11
131 #define LINES_PER_PAGE 24
132 #define NUM_COLUMNS 80
133 #define TERMINAL_BUF 4096
134 #define INIT_BUF 80
135 #define SHELL_LINE 1000
136 #define COMMAND_BUF 200
137 #define REGERR_BUF NUM_COLUMNS
138
139 struct termios otty, savetty0;
140 long file_pos, file_size;
141 int fnum, no_intty, no_tty, slow_tty;
142 int dum_opt, dlines;
143 void onquit(int), onsusp(int), chgwinsz(int), end_it(int);
144 int nscroll = SCROLL_LEN; /* Number of lines scrolled by 'd' */
145 int fold_opt = 1; /* Fold long lines */
146 int stop_opt = 1; /* Stop after form feeds */
147 int ssp_opt = 0; /* Suppress white space */
148 int ul_opt = 1; /* Underline as best we can */
149 int promptlen;
150 int Currline; /* Line we are currently at */
151 int startup = 1;
152 int firstf = 1;
153 int notell = 1;
154 int docrterase = 0;
155 int docrtkill = 0;
156 int bad_so; /* True if overwriting does not turn
157 off standout */
158 int inwait, Pause, errors;
159 int within; /* true if we are within a file,
160 false if we are between files */
161 int hard, dumb, noscroll, hardtabs, clreol, eatnl;
162 int catch_susp; /* We should catch the SIGTSTP signal */
163 char **fnames; /* The list of file names */
164 int nfiles; /* Number of files left to process */
165 char *shell; /* The name of the shell to use */
166 int shellp; /* A previous shell command exists */
167 sigjmp_buf restore;
168 char *Line; /* Line buffer */
169 size_t LineLen; /* size of Line buffer */
170 int Lpp = LINES_PER_PAGE; /* lines per page */
171 char *Clear; /* clear screen */
172 char *eraseln; /* erase line */
173 char *Senter, *Sexit; /* enter and exit standout mode */
174 char *ULenter, *ULexit; /* enter and exit underline mode */
175 char *chUL; /* underline character */
176 char *chBS; /* backspace character */
177 char *Home; /* go to home */
178 char *cursorm; /* cursor movement */
179 char cursorhome[40]; /* contains cursor movement to home */
180 char *EodClr; /* clear rest of screen */
181 int Mcol = NUM_COLUMNS; /* number of columns */
182 int Wrap = 1; /* set if automargins */
183 int soglitch; /* terminal has standout mode glitch */
184 int ulglitch; /* terminal has underline mode glitch */
185 int pstate = 0; /* current UL state */
186 static int magic(FILE *, char *);
187 char *previousre; /* previous search() buf[] item */
188 struct {
189 long chrctr, line;
190 } context, screen_start;
191 extern char PC; /* pad character */
192
193 #ifdef HAVE_NCURSES_H
194 # include <ncurses.h>
195 #elif defined(HAVE_NCURSES_NCURSES_H)
196 # include <ncurses/ncurses.h>
197 #endif
198
199 #if defined(HAVE_NCURSES_H) || defined(HAVE_NCURSES_NCURSES_H)
200 # include <term.h> /* include after <curses.h> */
201
202 # define TERM_AUTO_RIGHT_MARGIN "am"
203 # define TERM_CEOL "xhp"
204 # define TERM_CLEAR "clear"
205 # define TERM_CLEAR_TO_LINE_END "el"
206 # define TERM_CLEAR_TO_SCREEN_END "ed"
207 # define TERM_COLS "cols"
208 # define TERM_CURSOR_ADDRESS "cup"
209 # define TERM_EAT_NEW_LINE "xenl"
210 # define TERM_ENTER_UNDERLINE "smul"
211 # define TERM_EXIT_STANDARD_MODE "rmso"
212 # define TERM_EXIT_UNDERLINE "rmul"
213 # define TERM_HARD_COPY "hc"
214 # define TERM_HOME "home"
215 # define TERM_LINE_DOWN "cud1"
216 # define TERM_LINES "lines"
217 # define TERM_OVER_STRIKE "os"
218 # define TERM_PAD_CHAR "pad"
219 # define TERM_STANDARD_MODE "smso"
220 # define TERM_STD_MODE_GLITCH "xmc"
221 # define TERM_UNDERLINE_CHAR "uc"
222 # define TERM_UNDERLINE "ul"
223
my_putstring(char * s)224 static void my_putstring(char *s)
225 {
226 tputs(s, fileno(stdout), putchar); /* putp(s); */
227 }
228
my_setupterm(char * term,int fildes,int * errret)229 static void my_setupterm(char *term, int fildes, int *errret)
230 {
231 setupterm(term, fildes, errret);
232 }
233
my_tgetnum(char * s)234 static int my_tgetnum(char *s)
235 {
236 return tigetnum(s);
237 }
238
my_tgetflag(char * s)239 static int my_tgetflag(char *s)
240 {
241 return tigetflag(s);
242 }
243
my_tgetstr(char * s)244 static char *my_tgetstr(char *s)
245 {
246 return tigetstr(s);
247 }
248
my_tgoto(char * cap,int col,int row)249 static char *my_tgoto(char *cap, int col, int row)
250 {
251 return tparm(cap, col, row);
252 }
253
254 #elif defined(HAVE_LIBTERMCAP) /* ncurses not found */
255
256 # include <termcap.h>
257
258 # define TERM_AUTO_RIGHT_MARGIN "am"
259 # define TERM_CEOL "xs"
260 # define TERM_CLEAR "cl"
261 # define TERM_CLEAR_TO_LINE_END "ce"
262 # define TERM_CLEAR_TO_SCREEN_END "cd"
263 # define TERM_COLS "co"
264 # define TERM_CURSOR_ADDRESS "cm"
265 # define TERM_EAT_NEW_LINE "xn"
266 # define TERM_ENTER_UNDERLINE "us"
267 # define TERM_EXIT_STANDARD_MODE "se"
268 # define TERM_EXIT_UNDERLINE "ue"
269 # define TERM_HARD_COPY "hc"
270 # define TERM_HOME "ho"
271 # define TERM_LINE_DOWN "le"
272 # define TERM_LINES "li"
273 # define TERM_OVER_STRIKE "os"
274 # define TERM_PAD_CHAR "pc"
275 # define TERM_STANDARD_MODE "so"
276 # define TERM_STD_MODE_GLITCH "sg"
277 # define TERM_UNDERLINE_CHAR "uc"
278 # define TERM_UNDERLINE "ul"
279
280 char termbuffer[TERMINAL_BUF];
281 char tcbuffer[TERMINAL_BUF];
282 char *strbuf = termbuffer;
283
my_putstring(char * s)284 static void my_putstring(char *s)
285 {
286 tputs(s, fileno(stdout), putchar);
287 }
288
my_setupterm(char * term,int fildes,int * errret)289 static void my_setupterm(char *term, int fildes __attribute__((__unused__)), int *errret)
290 {
291 *errret = tgetent(tcbuffer, term);
292 }
293
my_tgetnum(char * s)294 static int my_tgetnum(char *s)
295 {
296 return tgetnum(s);
297 }
298
my_tgetflag(char * s)299 static int my_tgetflag(char *s)
300 {
301 return tgetflag(s);
302 }
303
my_tgetstr(char * s)304 static char *my_tgetstr(char *s)
305 {
306 return tgetstr(s, &strbuf);
307 }
308
my_tgoto(char * cap,int col,int row)309 static char *my_tgoto(char *cap, int col, int row)
310 {
311 return tgoto(cap, col, row);
312 }
313
314 #endif /* HAVE_LIBTERMCAP */
315
usage(FILE * out)316 static void __attribute__((__noreturn__)) usage(FILE *out)
317 {
318 fputs(USAGE_HEADER, out);
319 fprintf(out, _(" %s [options] <file>...\n"), program_invocation_short_name);
320 fputs(USAGE_OPTIONS, out);
321 fputs(_(" -d display help instead of ringing bell\n"), out);
322 fputs(_(" -f count logical rather than screen lines\n"), out);
323 fputs(_(" -l suppress pause after form feed\n"), out);
324 fputs(_(" -c do not scroll, display text and clean line ends\n"), out);
325 fputs(_(" -p do not scroll, clean screen and display text\n"), out);
326 fputs(_(" -s squeeze multiple blank lines into one\n"), out);
327 fputs(_(" -u suppress underlining\n"), out);
328 fputs(_(" -<number> the number of lines per screenful\n"), out);
329 fputs(_(" +<number> display file beginning from line number\n"), out);
330 fputs(_(" +/<string> display file beginning from search string match\n"), out);
331 fputs(_(" -V display version information and exit\n"), out);
332 fprintf(out, USAGE_MAN_TAIL("more(1)"));
333 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
334 }
335
main(int argc,char ** argv)336 int main(int argc, char **argv)
337 {
338 FILE *f;
339 char *s;
340 int ch;
341 int left;
342 int prnames = 0;
343 int initopt = 0;
344 int srchopt = 0;
345 int clearit = 0;
346 int initline = 0;
347 char *initbuf = NULL;
348
349 setlocale(LC_ALL, "");
350 bindtextdomain(PACKAGE, LOCALEDIR);
351 textdomain(PACKAGE);
352 atexit(close_stdout);
353
354 nfiles = argc;
355 fnames = argv;
356 setlocale(LC_ALL, "");
357 initterm();
358
359 /* Auto set no scroll on when binary is called page */
360 if (!(strcmp(program_invocation_short_name, "page")))
361 noscroll++;
362
363 prepare_line_buffer();
364
365 nscroll = Lpp / 2 - 1;
366 if (nscroll <= 0)
367 nscroll = 1;
368
369 if ((s = getenv("MORE")) != NULL)
370 argscan(s);
371
372 while (--nfiles > 0) {
373 if ((ch = (*++fnames)[0]) == '-') {
374 argscan(*fnames + 1);
375 } else if (ch == '+') {
376 s = *fnames;
377 if (*++s == '/') {
378 srchopt++;
379 initbuf = xstrdup(s + 1);
380 } else {
381 initopt++;
382 for (initline = 0; *s != '\0'; s++)
383 if (isdigit(*s))
384 initline =
385 initline * 10 + *s - '0';
386 --initline;
387 }
388 } else
389 break;
390 }
391 /* allow clreol only if Home and eraseln and EodClr strings are
392 * defined, and in that case, make sure we are in noscroll mode */
393 if (clreol) {
394 if ((Home == NULL) || (*Home == '\0') ||
395 (eraseln == NULL) || (*eraseln == '\0') ||
396 (EodClr == NULL) || (*EodClr == '\0'))
397 clreol = 0;
398 else
399 noscroll = 1;
400 }
401 if (dlines == 0)
402 dlines = Lpp - 1; /* was: Lpp - (noscroll ? 1 : 2) */
403 left = dlines;
404 if (nfiles > 1)
405 prnames++;
406 if (!no_intty && nfiles == 0)
407 usage(stderr);
408 else
409 f = stdin;
410 if (!no_tty) {
411 signal(SIGQUIT, onquit);
412 signal(SIGINT, end_it);
413 #ifdef SIGWINCH
414 signal(SIGWINCH, chgwinsz);
415 #endif
416 if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) {
417 signal(SIGTSTP, onsusp);
418 catch_susp++;
419 }
420 stty(fileno(stderr), &otty);
421 }
422 if (no_intty) {
423 if (no_tty)
424 copy_file(stdin);
425 else {
426 if ((ch = Getc(f)) == '\f')
427 doclear();
428 else {
429 Ungetc(ch, f);
430 if (noscroll && (ch != EOF)) {
431 if (clreol)
432 home();
433 else
434 doclear();
435 }
436 }
437 if (srchopt) {
438 free(previousre);
439 previousre = xstrdup(initbuf);
440 search(initbuf, stdin, 1);
441 if (noscroll)
442 left--;
443 } else if (initopt)
444 skiplns(initline, stdin);
445 screen(stdin, left);
446 }
447 no_intty = 0;
448 prnames++;
449 firstf = 0;
450 }
451
452 while (fnum < nfiles) {
453 if ((f = checkf(fnames[fnum], &clearit)) != NULL) {
454 context.line = context.chrctr = 0;
455 Currline = 0;
456 if (firstf)
457 sigsetjmp(restore, 1);
458 if (firstf) {
459 firstf = 0;
460 if (srchopt) {
461 free(previousre);
462 previousre = xstrdup(initbuf);
463 search(initbuf, f, 1);
464 if (noscroll)
465 left--;
466 } else if (initopt)
467 skiplns(initline, f);
468 } else if (fnum < nfiles && !no_tty) {
469 sigsetjmp(restore, 1);
470 left = command(fnames[fnum], f);
471 }
472 if (left != 0) {
473 if ((noscroll || clearit)
474 && (file_size != LONG_MAX)) {
475 if (clreol)
476 home();
477 else
478 doclear();
479 }
480 if (prnames) {
481 if (bad_so)
482 erasep(0);
483 if (clreol)
484 cleareol();
485 putsout("::::::::::::::");
486 if (promptlen > 14)
487 erasep(14);
488 putchar('\n');
489 if (clreol)
490 cleareol();
491 puts(fnames[fnum]);
492 if (clreol)
493 cleareol();
494 puts("::::::::::::::");
495 if (left > Lpp - 4)
496 left = Lpp - 4;
497 }
498 if (no_tty)
499 copy_file(f);
500 else {
501 within++;
502 screen(f, left);
503 within = 0;
504 }
505 }
506 sigsetjmp(restore, 1);
507 fflush(stdout);
508 fclose(f);
509 screen_start.line = screen_start.chrctr = 0L;
510 context.line = context.chrctr = 0L;
511 }
512 fnum++;
513 firstf = 0;
514 }
515 free(previousre);
516 free(initbuf);
517 free(Line);
518 reset_tty();
519 exit(EXIT_SUCCESS);
520 }
521
argscan(char * s)522 void argscan(char *s)
523 {
524 int seen_num = 0;
525
526 while (*s != '\0') {
527 switch (*s) {
528 case '0':
529 case '1':
530 case '2':
531 case '3':
532 case '4':
533 case '5':
534 case '6':
535 case '7':
536 case '8':
537 case '9':
538 if (!seen_num) {
539 dlines = 0;
540 seen_num = 1;
541 }
542 dlines = dlines * 10 + *s - '0';
543 break;
544 case 'd':
545 dum_opt = 1;
546 break;
547 case 'l':
548 stop_opt = 0;
549 break;
550 case 'f':
551 fold_opt = 0;
552 break;
553 case 'p':
554 noscroll++;
555 break;
556 case 'c':
557 clreol++;
558 break;
559 case 's':
560 ssp_opt = 1;
561 break;
562 case 'u':
563 ul_opt = 0;
564 break;
565 case '-':
566 case ' ':
567 case '\t':
568 break;
569 case 'V':
570 printf(UTIL_LINUX_VERSION);
571 exit(EXIT_SUCCESS);
572 break;
573 default:
574 warnx(_("unknown option -%s"), s);
575 usage(stderr);
576 break;
577 }
578 s++;
579 }
580 }
581
582 /* Check whether the file named by fs is an ASCII file which the user may
583 * access. If it is, return the opened file. Otherwise return NULL. */
checkf(register char * fs,int * clearfirst)584 FILE *checkf(register char *fs, int *clearfirst)
585 {
586 struct stat stbuf;
587 register FILE *f;
588 int c;
589
590 if (stat(fs, &stbuf) == -1) {
591 fflush(stdout);
592 if (clreol)
593 cleareol();
594 perror(fs);
595 return ((FILE *)NULL);
596 }
597 if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
598 printf(_("\n*** %s: directory ***\n\n"), fs);
599 return ((FILE *)NULL);
600 }
601 if ((f = Fopen(fs, "r")) == NULL) {
602 fflush(stdout);
603 perror(fs);
604 return ((FILE *)NULL);
605 }
606 if (magic(f, fs)) {
607 fclose(f);
608 return ((FILE *)NULL);
609 }
610 fcntl(fileno(f), F_SETFD, FD_CLOEXEC);
611 c = Getc(f);
612 *clearfirst = (c == '\f');
613 Ungetc(c, f);
614 if ((file_size = stbuf.st_size) == 0)
615 file_size = LONG_MAX;
616 return (f);
617 }
618
619 /* magic --
620 * check for file magic numbers. This code would best be shared
621 * with the file(1) program or, perhaps, more should not try to be
622 * so smart. */
magic(FILE * f,char * fs)623 static int magic(FILE *f, char *fs)
624 {
625 signed char twobytes[2];
626
627 /* don't try to look ahead if the input is unseekable */
628 if (fseek(f, 0L, SEEK_SET))
629 return 0;
630
631 if (fread(twobytes, 2, 1, f) == 1) {
632 switch (twobytes[0] + (twobytes[1] << 8)) {
633 case 0407: /* a.out obj */
634 case 0410: /* a.out exec */
635 case 0413: /* a.out demand exec */
636 case 0405:
637 case 0411:
638 case 0177545:
639 case 0x457f: /* simple ELF detection */
640 printf(_("\n******** %s: Not a text file ********\n\n"),
641 fs);
642 return 1;
643 }
644 }
645 fseek(f, 0L, SEEK_SET); /* rewind() not necessary */
646 return 0;
647 }
648
649 /* Print out the contents of the file f, one screenful at a time. */
650 #define STOP -10
screen(register FILE * f,register int num_lines)651 void screen(register FILE *f, register int num_lines)
652 {
653 register int c;
654 register int nchars;
655 int length; /* length of current line */
656 static int prev_len = 1; /* length of previous line */
657
658 for (;;) {
659 while (num_lines > 0 && !Pause) {
660 if ((nchars = get_line(f, &length)) == EOF) {
661 if (clreol)
662 clreos();
663 return;
664 }
665 if (ssp_opt && length == 0 && prev_len == 0)
666 continue;
667 prev_len = length;
668 if (bad_so
669 || ((Senter && *Senter == ' ') && (promptlen > 0)))
670 erasep(0);
671 /* must clear before drawing line since tabs on
672 * some terminals do not erase what they tab
673 * over. */
674 if (clreol)
675 cleareol();
676 prbuf(Line, length);
677 if (nchars < promptlen)
678 erasep(nchars); /* erasep () sets promptlen to 0 */
679 else
680 promptlen = 0;
681 /* is this needed?
682 * if (clreol)
683 * cleareol(); * must clear again in case we wrapped *
684 */
685 if (nchars < Mcol || !fold_opt)
686 prbuf("\n", 1); /* will turn off UL if necessary */
687 if (nchars == STOP)
688 break;
689 num_lines--;
690 }
691 if (pstate) {
692 my_putstring(ULexit);
693 pstate = 0;
694 }
695 fflush(stdout);
696 if ((c = Getc(f)) == EOF) {
697 if (clreol)
698 clreos();
699 return;
700 }
701
702 if (Pause && clreol)
703 clreos();
704 Ungetc(c, f);
705 sigsetjmp(restore, 1);
706 Pause = 0;
707 startup = 0;
708 if ((num_lines = command(NULL, f)) == 0)
709 return;
710 if (hard && promptlen > 0)
711 erasep(0);
712 if (noscroll && num_lines >= dlines) {
713 if (clreol)
714 home();
715 else
716 doclear();
717 }
718 screen_start.line = Currline;
719 screen_start.chrctr = Ftell(f);
720 }
721 }
722
723 /* Come here if a quit signal is received */
onquit(int dummy)724 void onquit(int dummy __attribute__((__unused__)))
725 {
726 signal(SIGQUIT, SIG_IGN);
727 if (!inwait) {
728 putchar('\n');
729 if (!startup) {
730 signal(SIGQUIT, onquit);
731 siglongjmp(restore, 1);
732 } else
733 Pause++;
734 } else if (!dum_opt && notell) {
735 promptlen += fprintf(stderr, _("[Use q or Q to quit]"));
736 notell = 0;
737 }
738 signal(SIGQUIT, onquit);
739 }
740
741 /* Come here if a signal for a window size change is received */
742 #ifdef SIGWINCH
chgwinsz(int dummy)743 void chgwinsz(int dummy __attribute__((__unused__)))
744 {
745 struct winsize win;
746
747 signal(SIGWINCH, SIG_IGN);
748 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) != -1) {
749 if (win.ws_row != 0) {
750 Lpp = win.ws_row;
751 nscroll = Lpp / 2 - 1;
752 if (nscroll <= 0)
753 nscroll = 1;
754 dlines = Lpp - 1; /* was: Lpp - (noscroll ? 1 : 2) */
755 }
756 if (win.ws_col != 0)
757 Mcol = win.ws_col;
758 }
759 signal(SIGWINCH, chgwinsz);
760 }
761 #endif /* SIGWINCH */
762
763 /* Clean up terminal state and exit. Also come here if interrupt signal received */
end_it(int dummy)764 void __attribute__((__noreturn__)) end_it(int dummy __attribute__((__unused__)))
765 {
766 reset_tty();
767 if (clreol) {
768 putchar('\r');
769 clreos();
770 fflush(stdout);
771 } else if (!clreol && (promptlen > 0)) {
772 kill_line();
773 fflush(stdout);
774 } else
775 putcerr('\n');
776 free(previousre);
777 free(Line);
778 _exit(EXIT_SUCCESS);
779 }
780
copy_file(register FILE * f)781 void copy_file(register FILE *f)
782 {
783 char buf[BUFSIZ];
784 size_t sz;
785
786 while ((sz = fread(&buf, sizeof(char), sizeof(buf), f)) > 0)
787 fwrite(&buf, sizeof(char), sz, stdout);
788 }
789
790 #define ringbell() putcerr('\007')
791
prompt(char * filename)792 static void prompt(char *filename)
793 {
794 if (clreol)
795 cleareol();
796 else if (promptlen > 0)
797 kill_line();
798 if (!hard) {
799 promptlen = 0;
800 if (Senter && Sexit) {
801 my_putstring(Senter);
802 promptlen += (2 * soglitch);
803 }
804 if (clreol)
805 cleareol();
806 promptlen += printf(_("--More--"));
807 if (filename != NULL) {
808 promptlen += printf(_("(Next file: %s)"), filename);
809 } else if (!no_intty) {
810 promptlen +=
811 printf("(%d%%)",
812 (int)((file_pos * 100) / file_size));
813 }
814 if (dum_opt) {
815 promptlen +=
816 printf(_("[Press space to continue, 'q' to quit.]"));
817 }
818 if (Senter && Sexit)
819 my_putstring(Sexit);
820 if (clreol)
821 clreos();
822 fflush(stdout);
823 } else
824 ringbell();
825 inwait++;
826 }
827
prepare_line_buffer(void)828 void prepare_line_buffer(void)
829 {
830 char *nline;
831 size_t nsz = Mcol * 4;
832
833 if (LineLen >= nsz)
834 return;
835
836 if (nsz < LINSIZ)
837 nsz = LINSIZ;
838
839 /* alloc nsz and extra space for \n\0 */
840 nline = xrealloc(Line, nsz + 2);
841 Line = nline;
842 LineLen = nsz;
843 }
844
845 /* Get a logical line */
get_line(register FILE * f,int * length)846 int get_line(register FILE *f, int *length)
847 {
848 int c;
849 char *p;
850 int column;
851 static int colflg;
852
853 #ifdef HAVE_WIDECHAR
854 size_t i;
855 wchar_t wc;
856 int wc_width;
857 mbstate_t state, state_bak; /* Current status of the stream. */
858 char mbc[MB_LEN_MAX]; /* Buffer for one multibyte char. */
859 size_t mblength; /* Byte length of multibyte char. */
860 size_t mbc_pos = 0; /* Position of the MBC. */
861 int use_mbc_buffer_flag = 0; /* If 1, mbc has data. */
862 int break_flag = 0; /* If 1, exit while(). */
863 long file_pos_bak = Ftell(f);
864
865 memset(&state, '\0', sizeof(mbstate_t));
866 #endif
867
868 prepare_line_buffer();
869
870 p = Line;
871 column = 0;
872 c = Getc(f);
873 if (colflg && c == '\n') {
874 Currline++;
875 c = Getc(f);
876 }
877 while (p < &Line[LineLen - 1]) {
878 #ifdef HAVE_WIDECHAR
879 if (fold_opt && use_mbc_buffer_flag && MB_CUR_MAX > 1) {
880 use_mbc_buffer_flag = 0;
881 state_bak = state;
882 mbc[mbc_pos++] = c;
883 process_mbc:
884 mblength = mbrtowc(&wc, mbc, mbc_pos, &state);
885
886 switch (mblength) {
887 case (size_t)-2: /* Incomplete multibyte character. */
888 use_mbc_buffer_flag = 1;
889 state = state_bak;
890 break;
891
892 case (size_t)-1: /* Invalid as a multibyte character. */
893 *p++ = mbc[0];
894 state = state_bak;
895 column++;
896 file_pos_bak++;
897
898 if (column >= Mcol) {
899 Fseek(f, file_pos_bak);
900 } else {
901 memmove(mbc, mbc + 1, --mbc_pos);
902 if (mbc_pos > 0) {
903 mbc[mbc_pos] = '\0';
904 goto process_mbc;
905 }
906 }
907 break;
908
909 default:
910 wc_width = wcwidth(wc);
911
912 if (column + wc_width > Mcol) {
913 Fseek(f, file_pos_bak);
914 break_flag = 1;
915 } else {
916 for (i = 0; p < &Line[LineLen - 1] &&
917 i < mbc_pos; i++)
918 *p++ = mbc[i];
919 if (wc_width > 0)
920 column += wc_width;
921 }
922 }
923
924 if (break_flag || column >= Mcol)
925 break;
926
927 c = Getc(f);
928 continue;
929 }
930 #endif /* HAVE_WIDECHAR */
931 if (c == EOF) {
932 if (p > Line) {
933 *p = '\0';
934 *length = p - Line;
935 return (column);
936 }
937 *length = p - Line;
938 return (EOF);
939 }
940 if (c == '\n') {
941 Currline++;
942 break;
943 }
944
945 *p++ = c;
946 #if 0
947 if (c == '\033') { /* ESC */
948 c = Getc(f);
949 while (c > ' ' && c < '0' && p < &Line[LineLen - 1]) {
950 *p++ = c;
951 c = Getc(f);
952 }
953 if (c >= '0' && c < '\177' && p < &Line[LineLen - 1]) {
954 *p++ = c;
955 c = Getc(f);
956 continue;
957 }
958 }
959 #endif /* 0 */
960 if (c == '\t') {
961 if (!hardtabs || (column < promptlen && !hard)) {
962 if (hardtabs && eraseln && !dumb) {
963 column = 1 + (column | 7);
964 my_putstring(eraseln);
965 promptlen = 0;
966 } else {
967 for (--p; p < &Line[LineLen - 1];) {
968 *p++ = ' ';
969 if ((++column & 7) == 0)
970 break;
971 }
972 if (column >= promptlen)
973 promptlen = 0;
974 }
975 } else
976 column = 1 + (column | 7);
977 } else if (c == '\b' && column > 0) {
978 column--;
979 } else if (c == '\r') {
980 int next = Getc(f);
981 if (next == '\n') {
982 p--;
983 Currline++;
984 break;
985 }
986 Ungetc(next, f);
987 column = 0;
988 } else if (c == '\f' && stop_opt) {
989 p[-1] = '^';
990 *p++ = 'L';
991 column += 2;
992 Pause++;
993 } else if (c == EOF) {
994 *length = p - Line;
995 return (column);
996 } else {
997 #ifdef HAVE_WIDECHAR
998 if (fold_opt && MB_CUR_MAX > 1) {
999 memset(mbc, '\0', MB_LEN_MAX);
1000 mbc_pos = 0;
1001 mbc[mbc_pos++] = c;
1002 state_bak = state;
1003
1004 mblength = mbrtowc(&wc, mbc, mbc_pos, &state);
1005 /* The value of mblength is always less than 2 here. */
1006 switch (mblength) {
1007 case (size_t)-2:
1008 p--;
1009 file_pos_bak = Ftell(f) - 1;
1010 state = state_bak;
1011 use_mbc_buffer_flag = 1;
1012 break;
1013
1014 case (size_t)-1:
1015 state = state_bak;
1016 column++;
1017 break;
1018
1019 default:
1020 wc_width = wcwidth(wc);
1021 if (wc_width > 0)
1022 column += wc_width;
1023 }
1024 } else
1025 #endif /* HAVE_WIDECHAR */
1026 {
1027 if (isprint(c))
1028 column++;
1029 }
1030 }
1031
1032 if (column >= Mcol && fold_opt)
1033 break;
1034 #ifdef HAVE_WIDECHAR
1035 if (use_mbc_buffer_flag == 0 && p >= &Line[LineLen - 1 - 4])
1036 /* don't read another char if there is no space for
1037 * whole multibyte sequence */
1038 break;
1039 #endif
1040 c = Getc(f);
1041 }
1042 if (column >= Mcol && Mcol > 0) {
1043 if (!Wrap) {
1044 *p++ = '\n';
1045 }
1046 }
1047 colflg = column == Mcol && fold_opt;
1048 if (colflg && eatnl && Wrap) {
1049 *p++ = '\n'; /* simulate normal wrap */
1050 }
1051 *length = p - Line;
1052 *p = 0;
1053 return (column);
1054 }
1055
1056 /* Erase the rest of the prompt, assuming we are starting at column col. */
erasep(register int col)1057 void erasep(register int col)
1058 {
1059
1060 if (promptlen == 0)
1061 return;
1062 if (hard) {
1063 putchar('\n');
1064 } else {
1065 if (col == 0)
1066 putchar('\r');
1067 if (!dumb && eraseln)
1068 my_putstring(eraseln);
1069 else
1070 printf("%*s", promptlen - col, "");
1071 }
1072 promptlen = 0;
1073 }
1074
1075 /* Erase the current line entirely */
kill_line(void)1076 void kill_line(void)
1077 {
1078 erasep(0);
1079 if (!eraseln || dumb)
1080 putchar('\r');
1081 }
1082
1083 /* force clear to end of line */
cleareol(void)1084 void cleareol(void)
1085 {
1086 my_putstring(eraseln);
1087 }
1088
clreos(void)1089 void clreos(void)
1090 {
1091 my_putstring(EodClr);
1092 }
1093
1094 /* Print a buffer of n characters */
prbuf(register char * s,register int n)1095 void prbuf(register char *s, register int n)
1096 {
1097 register char c; /* next output character */
1098 register int state; /* next output char's UL state */
1099 #define wouldul(s,n) ((n) >= 2 && (((s)[0] == '_' && (s)[1] == '\b') || ((s)[1] == '\b' && (s)[2] == '_')))
1100
1101 while (--n >= 0)
1102 if (!ul_opt)
1103 putchar(*s++);
1104 else {
1105 if (*s == ' ' && pstate == 0 && ulglitch
1106 && wouldul(s + 1, n - 1)) {
1107 s++;
1108 continue;
1109 }
1110 if ((state = wouldul(s, n)) != 0) {
1111 c = (*s == '_') ? s[2] : *s;
1112 n -= 2;
1113 s += 3;
1114 } else
1115 c = *s++;
1116 if (state != pstate) {
1117 if (c == ' ' && state == 0 && ulglitch
1118 && wouldul(s, n - 1))
1119 state = 1;
1120 else
1121 my_putstring(state ? ULenter : ULexit);
1122 }
1123 if (c != ' ' || pstate == 0 || state != 0
1124 || ulglitch == 0)
1125 #ifdef HAVE_WIDECHAR
1126 {
1127 wchar_t wc;
1128 size_t mblength;
1129 mbstate_t mbstate;
1130 memset(&mbstate, '\0', sizeof(mbstate_t));
1131 s--;
1132 n++;
1133 mblength = mbrtowc(&wc, s, n, &mbstate);
1134 if (mblength == (size_t)-2
1135 || mblength == (size_t)-1)
1136 mblength = 1;
1137 while (mblength--)
1138 putchar(*s++);
1139 n += mblength;
1140 }
1141 #else
1142 putchar(c);
1143 #endif /* HAVE_WIDECHAR */
1144 if (state && *chUL) {
1145 putsout(chBS);
1146 my_putstring(chUL);
1147 }
1148 pstate = state;
1149 }
1150 }
1151
1152 /* Clear the screen */
doclear(void)1153 void doclear(void)
1154 {
1155 if (Clear && !hard) {
1156 my_putstring(Clear);
1157 /* Put out carriage return so that system doesn't get
1158 * confused by escape sequences when expanding tabs */
1159 putchar('\r');
1160 promptlen = 0;
1161 }
1162 }
1163
1164 /* Go to home position */
home(void)1165 void home(void)
1166 {
1167 my_putstring(Home);
1168 }
1169
1170 static int lastcmd, lastarg, lastp;
1171 static int lastcolon;
1172 char shell_line[SHELL_LINE];
1173
1174 /* Read a command and do it. A command consists of an optional integer
1175 * argument followed by the command character. Return the number of
1176 * lines to display in the next screenful. If there is nothing more to
1177 * display in the current file, zero is returned. */
command(char * filename,register FILE * f)1178 int command(char *filename, register FILE *f)
1179 {
1180 register int nlines;
1181 register int retval = 0;
1182 register int c;
1183 char colonch;
1184 int done;
1185 char comchar, cmdbuf[INIT_BUF];
1186
1187 #define ret(val) retval=val;done++;break
1188
1189 done = 0;
1190 if (!errors)
1191 prompt(filename);
1192 else
1193 errors = 0;
1194 for (;;) {
1195 nlines = number(&comchar);
1196 lastp = colonch = 0;
1197 if (comchar == '.') { /* Repeat last command */
1198 lastp++;
1199 comchar = lastcmd;
1200 nlines = lastarg;
1201 if (lastcmd == ':')
1202 colonch = lastcolon;
1203 }
1204 lastcmd = comchar;
1205 lastarg = nlines;
1206 if ((cc_t) comchar == otty.c_cc[VERASE]) {
1207 kill_line();
1208 prompt(filename);
1209 continue;
1210 }
1211 switch (comchar) {
1212 case ':':
1213 retval = colon(filename, colonch, nlines);
1214 if (retval >= 0)
1215 done++;
1216 break;
1217 case 'b':
1218 case ctrl('B'):
1219 {
1220 register int initline;
1221
1222 if (no_intty) {
1223 ringbell();
1224 return (-1);
1225 }
1226
1227 if (nlines == 0)
1228 nlines++;
1229
1230 putchar('\r');
1231 erasep(0);
1232 putchar('\n');
1233 if (clreol)
1234 cleareol();
1235 printf(P_("...back %d page",
1236 "...back %d pages", nlines),
1237 nlines);
1238 if (clreol)
1239 cleareol();
1240 putchar('\n');
1241
1242 initline = Currline - dlines * (nlines + 1);
1243 if (!noscroll)
1244 --initline;
1245 if (initline < 0)
1246 initline = 0;
1247 Fseek(f, 0L);
1248 Currline = 0; /* skiplns() will make Currline correct */
1249 skiplns(initline, f);
1250 if (!noscroll) {
1251 ret(dlines + 1);
1252 } else {
1253 ret(dlines);
1254 }
1255 }
1256 case ' ':
1257 case 'z':
1258 if (nlines == 0)
1259 nlines = dlines;
1260 else if (comchar == 'z')
1261 dlines = nlines;
1262 ret(nlines);
1263 case 'd':
1264 case ctrl('D'):
1265 if (nlines != 0)
1266 nscroll = nlines;
1267 ret(nscroll);
1268 case 'q':
1269 case 'Q':
1270 end_it(0);
1271 case 's':
1272 case 'f':
1273 case ctrl('F'):
1274 if (nlines == 0)
1275 nlines++;
1276 if (comchar == 'f')
1277 nlines *= dlines;
1278 putchar('\r');
1279 erasep(0);
1280 putchar('\n');
1281 if (clreol)
1282 cleareol();
1283 printf(P_("...skipping %d line",
1284 "...skipping %d lines", nlines),
1285 nlines);
1286
1287 if (clreol)
1288 cleareol();
1289 putchar('\n');
1290
1291 while (nlines > 0) {
1292 while ((c = Getc(f)) != '\n')
1293 if (c == EOF) {
1294 retval = 0;
1295 done++;
1296 goto endsw;
1297 }
1298 Currline++;
1299 nlines--;
1300 }
1301 ret(dlines);
1302 case '\n':
1303 if (nlines != 0)
1304 dlines = nlines;
1305 else
1306 nlines = 1;
1307 ret(nlines);
1308 case '\f':
1309 if (!no_intty) {
1310 doclear();
1311 Fseek(f, screen_start.chrctr);
1312 Currline = screen_start.line;
1313 ret(dlines);
1314 } else {
1315 ringbell();
1316 break;
1317 }
1318 case '\'':
1319 if (!no_intty) {
1320 kill_line();
1321 putsout(_("\n***Back***\n\n"));
1322 Fseek(f, context.chrctr);
1323 Currline = context.line;
1324 ret(dlines);
1325 } else {
1326 ringbell();
1327 break;
1328 }
1329 case '=':
1330 kill_line();
1331 promptlen = printf("%d", Currline);
1332 fflush(stdout);
1333 break;
1334 case 'n':
1335 if (!previousre) {
1336 more_error(_("No previous regular expression"));
1337 break;
1338 }
1339 lastp++;
1340 /* fall through */
1341 case '/':
1342 if (nlines == 0)
1343 nlines++;
1344 kill_line();
1345 putchar('/');
1346 promptlen = 1;
1347 fflush(stdout);
1348 if (lastp) {
1349 putcerr('\r');
1350 search(previousre, f, nlines);
1351 } else {
1352 ttyin(cmdbuf, sizeof(cmdbuf) - 2, '/');
1353 putcerr('\r');
1354 free(previousre);
1355 previousre = xstrdup(cmdbuf);
1356 search(cmdbuf, f, nlines);
1357 }
1358 ret(dlines - 1);
1359 case '!':
1360 do_shell(filename);
1361 break;
1362 case '?':
1363 case 'h':
1364 if (noscroll)
1365 doclear();
1366 putsout(_("\n"
1367 "Most commands optionally preceded by integer argument k. "
1368 "Defaults in brackets.\n"
1369 "Star (*) indicates argument becomes new default.\n"));
1370 puts("---------------------------------------"
1371 "----------------------------------------");
1372 putsout(_
1373 ("<space> Display next k lines of text [current screen size]\n"
1374 "z Display next k lines of text [current screen size]*\n"
1375 "<return> Display next k lines of text [1]*\n"
1376 "d or ctrl-D Scroll k lines [current scroll size, initially 11]*\n"
1377 "q or Q or <interrupt> Exit from more\n"
1378 "s Skip forward k lines of text [1]\n"
1379 "f Skip forward k screenfuls of text [1]\n"
1380 "b or ctrl-B Skip backwards k screenfuls of text [1]\n"
1381 "' Go to place where previous search started\n"
1382 "= Display current line number\n"
1383 "/<regular expression> Search for kth occurrence of regular expression [1]\n"
1384 "n Search for kth occurrence of last r.e [1]\n"
1385 "!<cmd> or :!<cmd> Execute <cmd> in a subshell\n"
1386 "v Start up /usr/bin/vi at current line\n"
1387 "ctrl-L Redraw screen\n"
1388 ":n Go to kth next file [1]\n"
1389 ":p Go to kth previous file [1]\n"
1390 ":f Display current file name and line number\n"
1391 ". Repeat previous command\n"));
1392 puts("---------------------------------------"
1393 "----------------------------------------");
1394 prompt(filename);
1395 break;
1396 case 'v': /* This case should go right before default */
1397 if (!no_intty) {
1398 /* Earlier: call vi +n file. This also
1399 * works for emacs. POSIX: call vi -c n
1400 * file (when editor is vi or ex). */
1401 char *editor, *p;
1402 int n = (Currline - dlines <= 0 ? 1 :
1403 Currline - (dlines + 1) / 2);
1404 int split = 0;
1405
1406 editor = getenv("VISUAL");
1407 if (editor == NULL || *editor == '\0')
1408 editor = getenv("EDITOR");
1409 if (editor == NULL || *editor == '\0')
1410 editor = VI;
1411
1412 p = strrchr(editor, '/');
1413 if (p)
1414 p++;
1415 else
1416 p = editor;
1417 if (!strcmp(p, "vi") || !strcmp(p, "ex")) {
1418 sprintf(cmdbuf, "-c %d", n);
1419 split = 1;
1420 } else {
1421 sprintf(cmdbuf, "+%d", n);
1422 }
1423
1424 kill_line();
1425 printf("%s %s %s", editor, cmdbuf,
1426 fnames[fnum]);
1427 if (split) {
1428 cmdbuf[2] = 0;
1429 execute(filename, editor, editor,
1430 cmdbuf, cmdbuf + 3,
1431 fnames[fnum], (char *)0);
1432 } else
1433 execute(filename, editor, editor,
1434 cmdbuf, fnames[fnum],
1435 (char *)0);
1436 break;
1437 }
1438 /* fall through */
1439 default:
1440 if (dum_opt) {
1441 kill_line();
1442 if (Senter && Sexit) {
1443 my_putstring(Senter);
1444 promptlen =
1445 printf(_
1446 ("[Press 'h' for instructions.]"))
1447 + 2 * soglitch;
1448 my_putstring(Sexit);
1449 } else
1450 promptlen =
1451 printf(_
1452 ("[Press 'h' for instructions.]"));
1453 fflush(stdout);
1454 } else
1455 ringbell();
1456 break;
1457 }
1458 if (done)
1459 break;
1460 }
1461 putchar('\r');
1462 endsw:
1463 inwait = 0;
1464 notell++;
1465 return (retval);
1466 }
1467
1468 static char ch;
1469 /* Execute a colon-prefixed command. Returns <0 if not a command that
1470 * should cause more of the file to be printed. */
colon(char * filename,int cmd,int nlines)1471 int colon(char *filename, int cmd, int nlines)
1472 {
1473 if (cmd == 0)
1474 ch = readch();
1475 else
1476 ch = cmd;
1477 lastcolon = ch;
1478 switch (ch) {
1479 case 'f':
1480 kill_line();
1481 if (!no_intty)
1482 promptlen =
1483 printf(_("\"%s\" line %d"), fnames[fnum], Currline);
1484 else
1485 promptlen = printf(_("[Not a file] line %d"), Currline);
1486 fflush(stdout);
1487 return (-1);
1488 case 'n':
1489 if (nlines == 0) {
1490 if (fnum >= nfiles - 1)
1491 end_it(0);
1492 nlines++;
1493 }
1494 putchar('\r');
1495 erasep(0);
1496 skipf(nlines);
1497 return (0);
1498 case 'p':
1499 if (no_intty) {
1500 ringbell();
1501 return (-1);
1502 }
1503 putchar('\r');
1504 erasep(0);
1505 if (nlines == 0)
1506 nlines++;
1507 skipf(-nlines);
1508 return (0);
1509 case '!':
1510 do_shell(filename);
1511 return (-1);
1512 case 'q':
1513 case 'Q':
1514 end_it(0);
1515 default:
1516 ringbell();
1517 return (-1);
1518 }
1519 }
1520
1521 /* Read a decimal number from the terminal. Set cmd to the non-digit
1522 * which terminates the number. */
number(char * cmd)1523 int number(char *cmd)
1524 {
1525 register int i;
1526
1527 i = 0;
1528 ch = otty.c_cc[VKILL];
1529 for (;;) {
1530 ch = readch();
1531 if (isdigit(ch))
1532 i = i * 10 + ch - '0';
1533 else if ((cc_t) ch == otty.c_cc[VKILL])
1534 i = 0;
1535 else {
1536 *cmd = ch;
1537 break;
1538 }
1539 }
1540 return (i);
1541 }
1542
do_shell(char * filename)1543 void do_shell(char *filename)
1544 {
1545 char cmdbuf[COMMAND_BUF];
1546 int rc;
1547 char *expanded;
1548
1549 kill_line();
1550 putchar('!');
1551 fflush(stdout);
1552 promptlen = 1;
1553 if (lastp)
1554 putsout(shell_line);
1555 else {
1556 ttyin(cmdbuf, sizeof(cmdbuf) - 2, '!');
1557 expanded = 0;
1558 rc = expand(&expanded, cmdbuf);
1559 if (expanded) {
1560 if (strlen(expanded) < sizeof(shell_line))
1561 strcpy(shell_line, expanded);
1562 else
1563 rc = -1;
1564 free(expanded);
1565 }
1566 if (rc < 0) {
1567 putserr(_(" Overflow\n"));
1568 prompt(filename);
1569 return;
1570 } else if (rc > 0) {
1571 kill_line();
1572 promptlen = printf("!%s", shell_line);
1573 }
1574 }
1575 fflush(stdout);
1576 putcerr('\n');
1577 promptlen = 0;
1578 shellp = 1;
1579 execute(filename, shell, shell, "-c", shell_line, 0);
1580 }
1581
1582 /* Search for nth occurrence of regular expression contained in buf in
1583 * the file */
search(char buf[],FILE * file,register int n)1584 void search(char buf[], FILE *file, register int n)
1585 {
1586 long startline = Ftell(file);
1587 register long line1 = startline;
1588 register long line2 = startline;
1589 register long line3;
1590 register int lncount;
1591 int saveln, rc;
1592 regex_t re;
1593
1594 context.line = saveln = Currline;
1595 context.chrctr = startline;
1596 lncount = 0;
1597 if ((rc = regcomp(&re, buf, REG_NOSUB)) != 0) {
1598 char s[REGERR_BUF];
1599 regerror(rc, &re, s, sizeof s);
1600 more_error(s);
1601 }
1602 while (!feof(file)) {
1603 line3 = line2;
1604 line2 = line1;
1605 line1 = Ftell(file);
1606 rdline(file);
1607 lncount++;
1608 if (regexec(&re, Line, 0, NULL, 0) == 0) {
1609 if (--n == 0) {
1610 if (lncount > 3 || (lncount > 1 && no_intty)) {
1611 putchar('\n');
1612 if (clreol)
1613 cleareol();
1614 putsout(_("...skipping\n"));
1615 }
1616 if (!no_intty) {
1617 Currline -=
1618 (lncount >= 3 ? 3 : lncount);
1619 Fseek(file, line3);
1620 if (noscroll) {
1621 if (clreol) {
1622 home();
1623 cleareol();
1624 } else
1625 doclear();
1626 }
1627 } else {
1628 kill_line();
1629 if (noscroll) {
1630 if (clreol) {
1631 home();
1632 cleareol();
1633 } else
1634 doclear();
1635 }
1636 puts(Line);
1637 }
1638 break;
1639 }
1640 }
1641 }
1642 regfree(&re);
1643 if (feof(file)) {
1644 if (!no_intty) {
1645 Currline = saveln;
1646 Fseek(file, startline);
1647 } else {
1648 putsout(_("\nPattern not found\n"));
1649 end_it(0);
1650 }
1651 free(previousre);
1652 previousre = NULL;
1653 more_error(_("Pattern not found"));
1654 }
1655 }
1656
execute(char * filename,char * cmd,...)1657 void execute(char *filename, char *cmd, ...)
1658 {
1659 int id;
1660 int n;
1661 va_list argp;
1662 char *arg;
1663 char **args;
1664 int argcount;
1665
1666 fflush(stdout);
1667 reset_tty();
1668 for (n = 10; (id = fork()) < 0 && n > 0; n--)
1669 sleep(5);
1670 if (id == 0) {
1671 if (!isatty(0)) {
1672 close(0);
1673 open("/dev/tty", 0);
1674 }
1675
1676 va_start(argp, cmd);
1677 arg = va_arg(argp, char *);
1678 argcount = 0;
1679 while (arg) {
1680 argcount++;
1681 arg = va_arg(argp, char *);
1682 }
1683 va_end(argp);
1684
1685 args = alloca(sizeof(char *) * (argcount + 1));
1686 args[argcount] = NULL;
1687
1688 va_start(argp, cmd);
1689 arg = va_arg(argp, char *);
1690 argcount = 0;
1691 while (arg) {
1692 args[argcount] = arg;
1693 argcount++;
1694 arg = va_arg(argp, char *);
1695 }
1696 va_end(argp);
1697
1698 execvp(cmd, args);
1699 putserr(_("exec failed\n"));
1700 exit(EXIT_FAILURE);
1701 }
1702 if (id > 0) {
1703 signal(SIGINT, SIG_IGN);
1704 signal(SIGQUIT, SIG_IGN);
1705 if (catch_susp)
1706 signal(SIGTSTP, SIG_DFL);
1707 while (wait(0) > 0) ;
1708 signal(SIGINT, end_it);
1709 signal(SIGQUIT, onquit);
1710 if (catch_susp)
1711 signal(SIGTSTP, onsusp);
1712 } else
1713 putserr(_("can't fork\n"));
1714 set_tty();
1715 puts("------------------------");
1716 prompt(filename);
1717 }
1718
1719 /* Skip n lines in the file f */
skiplns(register int n,register FILE * f)1720 void skiplns(register int n, register FILE *f)
1721 {
1722 register int c;
1723
1724 while (n > 0) {
1725 while ((c = Getc(f)) != '\n')
1726 if (c == EOF)
1727 return;
1728 n--;
1729 Currline++;
1730 }
1731 }
1732
1733 /* Skip nskip files in the file list (from the command line). Nskip may
1734 * be negative. */
skipf(register int nskip)1735 void skipf(register int nskip)
1736 {
1737 if (nskip == 0)
1738 return;
1739 if (nskip > 0) {
1740 if (fnum + nskip > nfiles - 1)
1741 nskip = nfiles - fnum - 1;
1742 } else if (within)
1743 ++fnum;
1744 fnum += nskip;
1745 if (fnum < 0)
1746 fnum = 0;
1747 puts(_("\n...Skipping "));
1748 if (clreol)
1749 cleareol();
1750 if (nskip > 0)
1751 putsout(_("...Skipping to file "));
1752 else
1753 putsout(_("...Skipping back to file "));
1754 puts(fnames[fnum]);
1755 if (clreol)
1756 cleareol();
1757 putchar('\n');
1758 --fnum;
1759 }
1760
1761 /*----------------------------- Terminal I/O -------------------------------*/
initterm(void)1762 void initterm(void)
1763 {
1764 int ret;
1765 char *padstr;
1766 char *term;
1767 struct winsize win;
1768
1769 #ifdef do_SIGTTOU
1770 retry:
1771 #endif
1772
1773 #ifndef NON_INTERACTIVE_MORE
1774 no_tty = tcgetattr(fileno(stdout), &otty);
1775 #endif
1776 if (!no_tty) {
1777 docrterase = (otty.c_cc[VERASE] != 255);
1778 docrtkill = (otty.c_cc[VKILL] != 255);
1779 #ifdef do_SIGTTOU
1780 {
1781 int tgrp;
1782 /* Wait until we're in the foreground before we
1783 * save the terminal modes. */
1784 if ((tgrp = tcgetpgrp(fileno(stdout))) < 0) {
1785 perror("tcgetpgrp");
1786 exit(EXIT_FAILURE);
1787 }
1788 if (tgrp != getpgrp(0)) {
1789 kill(0, SIGTTOU);
1790 goto retry;
1791 }
1792 }
1793 #endif /* do_SIGTTOU */
1794 if ((term = getenv("TERM")) == NULL) {
1795 dumb++;
1796 ul_opt = 0;
1797 }
1798 my_setupterm(term, 1, &ret);
1799 if (ret <= 0) {
1800 dumb++;
1801 ul_opt = 0;
1802 } else {
1803 #ifdef TIOCGWINSZ
1804 if (ioctl(fileno(stdout), TIOCGWINSZ, &win) < 0) {
1805 #endif
1806 Lpp = my_tgetnum(TERM_LINES);
1807 Mcol = my_tgetnum(TERM_COLS);
1808 #ifdef TIOCGWINSZ
1809 } else {
1810 if ((Lpp = win.ws_row) == 0)
1811 Lpp = my_tgetnum(TERM_LINES);
1812 if ((Mcol = win.ws_col) == 0)
1813 Mcol = my_tgetnum(TERM_COLS);
1814 }
1815 #endif
1816 if ((Lpp <= 0) || my_tgetflag(TERM_HARD_COPY)) {
1817 hard++; /* Hard copy terminal */
1818 Lpp = LINES_PER_PAGE;
1819 }
1820
1821 if (my_tgetflag(TERM_EAT_NEW_LINE))
1822 /* Eat newline at last column + 1; dec, concept */
1823 eatnl++;
1824 if (Mcol <= 0)
1825 Mcol = NUM_COLUMNS;
1826
1827 Wrap = my_tgetflag(TERM_AUTO_RIGHT_MARGIN);
1828 bad_so = my_tgetflag(TERM_CEOL);
1829 eraseln = my_tgetstr(TERM_CLEAR_TO_LINE_END);
1830 Clear = my_tgetstr(TERM_CLEAR);
1831 Senter = my_tgetstr(TERM_STANDARD_MODE);
1832 Sexit = my_tgetstr(TERM_EXIT_STANDARD_MODE);
1833 if ((soglitch = my_tgetnum(TERM_STD_MODE_GLITCH)) < 0)
1834 soglitch = 0;
1835
1836 /* Set up for underlining: some terminals don't
1837 * need it; others have start/stop sequences,
1838 * still others have an underline char sequence
1839 * which is assumed to move the cursor forward
1840 * one character. If underline sequence isn't
1841 * available, settle for standout sequence. */
1842 if (my_tgetflag(TERM_UNDERLINE)
1843 || my_tgetflag(TERM_OVER_STRIKE))
1844 ul_opt = 0;
1845 if ((chUL = my_tgetstr(TERM_UNDERLINE_CHAR)) == NULL)
1846 chUL = "";
1847 if (((ULenter =
1848 my_tgetstr(TERM_ENTER_UNDERLINE)) == NULL
1849 || (ULexit =
1850 my_tgetstr(TERM_EXIT_UNDERLINE)) == NULL)
1851 && !*chUL) {
1852 if ((ULenter = Senter) == NULL
1853 || (ULexit = Sexit) == NULL) {
1854 ULenter = "";
1855 ULexit = "";
1856 } else
1857 ulglitch = soglitch;
1858 } else {
1859 ulglitch = 0;
1860 }
1861
1862 if ((padstr = my_tgetstr(TERM_PAD_CHAR)) != NULL)
1863 PC = *padstr;
1864 Home = my_tgetstr(TERM_HOME);
1865 if (Home == NULL || *Home == '\0') {
1866 if ((cursorm =
1867 my_tgetstr(TERM_CURSOR_ADDRESS)) != NULL) {
1868 const char *t =
1869 (const char *)my_tgoto(cursorm, 0,
1870 0);
1871 xstrncpy(cursorhome, t,
1872 sizeof(cursorhome));
1873 Home = cursorhome;
1874 }
1875 }
1876 EodClr = my_tgetstr(TERM_CLEAR_TO_SCREEN_END);
1877 if ((chBS = my_tgetstr(TERM_LINE_DOWN)) == NULL)
1878 chBS = "\b";
1879
1880 }
1881 if ((shell = getenv("SHELL")) == NULL)
1882 shell = "/bin/sh";
1883 }
1884 no_intty = tcgetattr(fileno(stdin), &otty);
1885 tcgetattr(fileno(stderr), &otty);
1886 savetty0 = otty;
1887 slow_tty = cfgetispeed(&otty) < B1200;
1888 hardtabs = (otty.c_oflag & TABDLY) != XTABS;
1889 if (!no_tty) {
1890 otty.c_lflag &= ~(ICANON | ECHO);
1891 otty.c_cc[VMIN] = 1;
1892 otty.c_cc[VTIME] = 0;
1893 }
1894 }
1895
readch(void)1896 int readch(void)
1897 {
1898 unsigned char c;
1899
1900 errno = 0;
1901 if (read(fileno(stderr), &c, 1) <= 0) {
1902 if (errno != EINTR)
1903 end_it(0);
1904 else
1905 c = otty.c_cc[VKILL];
1906 }
1907 return (c);
1908 }
1909
1910 static char *BS = "\b";
1911 static char *BSB = "\b \b";
1912 static char *CARAT = "^";
1913 #define ERASEONECOLUMN \
1914 if (docrterase) \
1915 putserr(BSB); \
1916 else \
1917 putserr(BS);
1918
ttyin(char buf[],register int nmax,char pchar)1919 void ttyin(char buf[], register int nmax, char pchar)
1920 {
1921 char *sp;
1922 int c;
1923 int slash = 0;
1924 int maxlen;
1925
1926 sp = buf;
1927 maxlen = 0;
1928 while (sp - buf < nmax) {
1929 if (promptlen > maxlen)
1930 maxlen = promptlen;
1931 c = readch();
1932 if (c == '\\') {
1933 slash++;
1934 } else if (((cc_t) c == otty.c_cc[VERASE]) && !slash) {
1935 if (sp > buf) {
1936 #ifdef HAVE_WIDECHAR
1937 if (MB_CUR_MAX > 1) {
1938 wchar_t wc;
1939 size_t pos = 0, mblength;
1940 mbstate_t state, state_bak;
1941
1942 memset(&state, '\0', sizeof(mbstate_t));
1943
1944 while (1) {
1945 state_bak = state;
1946 mblength =
1947 mbrtowc(&wc, buf + pos,
1948 sp - buf, &state);
1949
1950 state = (mblength == (size_t)-2
1951 || mblength ==
1952 (size_t)-1) ? state_bak
1953 : state;
1954 mblength =
1955 (mblength == (size_t)-2
1956 || mblength == (size_t)-1
1957 || mblength ==
1958 0) ? 1 : mblength;
1959
1960 if (buf + pos + mblength >= sp)
1961 break;
1962
1963 pos += mblength;
1964 }
1965
1966 if (mblength == 1) {
1967 ERASEONECOLUMN} else {
1968 int wc_width;
1969 wc_width = wcwidth(wc);
1970 wc_width =
1971 (wc_width <
1972 1) ? 1 : wc_width;
1973 while (wc_width--) {
1974 ERASEONECOLUMN}
1975 }
1976
1977 while (mblength--) {
1978 --promptlen;
1979 --sp;
1980 }
1981 } else
1982 #endif /* HAVE_WIDECHAR */
1983 {
1984 --promptlen;
1985 ERASEONECOLUMN-- sp;
1986 }
1987
1988 if ((*sp < ' ' && *sp != '\n') || *sp == RUBOUT) {
1989 --promptlen;
1990 ERASEONECOLUMN}
1991 continue;
1992 } else {
1993 if (!eraseln)
1994 promptlen = maxlen;
1995 siglongjmp(restore, 1);
1996 }
1997 } else if (((cc_t) c == otty.c_cc[VKILL]) && !slash) {
1998 if (hard) {
1999 show(c);
2000 putchar('\n');
2001 putchar(pchar);
2002 } else {
2003 putchar('\r');
2004 putchar(pchar);
2005 if (eraseln)
2006 erasep(1);
2007 else if (docrtkill)
2008 while (promptlen-- > 1)
2009 putserr(BSB);
2010 promptlen = 1;
2011 }
2012 sp = buf;
2013 fflush(stdout);
2014 continue;
2015 }
2016 if (slash && ((cc_t) c == otty.c_cc[VKILL]
2017 || (cc_t) c == otty.c_cc[VERASE])) {
2018 ERASEONECOLUMN-- sp;
2019 }
2020 if (c != '\\')
2021 slash = 0;
2022 *sp++ = c;
2023 if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) {
2024 c += (c == RUBOUT) ? -0100 : 0100;
2025 putserr(CARAT);
2026 promptlen++;
2027 }
2028 if (c != '\n' && c != ESC) {
2029 putcerr(c);
2030 promptlen++;
2031 } else
2032 break;
2033 }
2034 *--sp = '\0';
2035 if (!eraseln)
2036 promptlen = maxlen;
2037 if (sp - buf >= nmax - 1)
2038 more_error(_("Line too long"));
2039 }
2040
2041 /* return: 0 - unchanged, 1 - changed, -1 - overflow (unchanged) */
expand(char ** outbuf,char * inbuf)2042 int expand(char **outbuf, char *inbuf)
2043 {
2044 char *inpstr;
2045 char *outstr;
2046 char c;
2047 char *temp;
2048 int changed = 0;
2049 int tempsz, xtra, offset;
2050
2051 xtra = strlen(fnames[fnum]) + strlen(shell_line) + 1;
2052 tempsz = 200 + xtra;
2053 temp = xmalloc(tempsz);
2054 inpstr = inbuf;
2055 outstr = temp;
2056 while ((c = *inpstr++) != '\0') {
2057 offset = outstr - temp;
2058 if (tempsz - offset - 1 < xtra) {
2059 tempsz += 200 + xtra;
2060 temp = xrealloc(temp, tempsz);
2061 outstr = temp + offset;
2062 }
2063 switch (c) {
2064 case '%':
2065 if (!no_intty) {
2066 strcpy(outstr, fnames[fnum]);
2067 outstr += strlen(fnames[fnum]);
2068 changed++;
2069 } else
2070 *outstr++ = c;
2071 break;
2072 case '!':
2073 if (!shellp)
2074 more_error(_
2075 ("No previous command to substitute for"));
2076 strcpy(outstr, shell_line);
2077 outstr += strlen(shell_line);
2078 changed++;
2079 break;
2080 case '\\':
2081 if (*inpstr == '%' || *inpstr == '!') {
2082 *outstr++ = *inpstr++;
2083 break;
2084 }
2085 default:
2086 *outstr++ = c;
2087 }
2088 }
2089 *outstr++ = '\0';
2090 *outbuf = temp;
2091 return (changed);
2092 }
2093
show(char c)2094 void show(char c)
2095 {
2096 if ((c < ' ' && c != '\n' && c != ESC) || c == RUBOUT) {
2097 c += (c == RUBOUT) ? -0100 : 0100;
2098 putserr(CARAT);
2099 promptlen++;
2100 }
2101 putcerr(c);
2102 promptlen++;
2103 }
2104
more_error(char * mess)2105 void more_error(char *mess)
2106 {
2107 if (clreol)
2108 cleareol();
2109 else
2110 kill_line();
2111 promptlen += strlen(mess);
2112 if (Senter && Sexit) {
2113 my_putstring(Senter);
2114 putsout(mess);
2115 my_putstring(Sexit);
2116 } else
2117 putsout(mess);
2118 fflush(stdout);
2119 errors++;
2120 siglongjmp(restore, 1);
2121 }
2122
set_tty(void)2123 void set_tty(void)
2124 {
2125 otty.c_lflag &= ~(ICANON | ECHO);
2126 otty.c_cc[VMIN] = 1; /* read at least 1 char */
2127 otty.c_cc[VTIME] = 0; /* no timeout */
2128 stty(fileno(stderr), &otty);
2129 }
2130
ourputch(int c)2131 static int ourputch(int c)
2132 {
2133 return putc(c, stdout);
2134 }
2135
reset_tty(void)2136 void reset_tty(void)
2137 {
2138 if (no_tty)
2139 return;
2140 if (pstate) {
2141 /* putchar - if that isn't a macro */
2142 tputs(ULexit, fileno(stdout), ourputch);
2143 fflush(stdout);
2144 pstate = 0;
2145 }
2146 otty.c_lflag |= ICANON | ECHO;
2147 otty.c_cc[VMIN] = savetty0.c_cc[VMIN];
2148 otty.c_cc[VTIME] = savetty0.c_cc[VTIME];
2149 stty(fileno(stderr), &savetty0);
2150 }
2151
rdline(register FILE * f)2152 void rdline(register FILE *f)
2153 {
2154 register int c;
2155 register char *p;
2156
2157 prepare_line_buffer();
2158
2159 p = Line;
2160 while ((c = Getc(f)) != '\n' && c != EOF
2161 && (size_t)(p - Line) < LineLen - 1)
2162 *p++ = c;
2163 if (c == '\n')
2164 Currline++;
2165 *p = '\0';
2166 }
2167
2168 /* Come here when we get a suspend signal from the terminal */
onsusp(int dummy)2169 void onsusp(int dummy __attribute__((__unused__)))
2170 {
2171 sigset_t signals, oldmask;
2172
2173 /* ignore SIGTTOU so we don't get stopped if csh grabs the tty */
2174 signal(SIGTTOU, SIG_IGN);
2175 reset_tty();
2176 fflush(stdout);
2177 signal(SIGTTOU, SIG_DFL);
2178 /* Send the TSTP signal to suspend our process group */
2179 signal(SIGTSTP, SIG_DFL);
2180
2181 /* unblock SIGTSTP or we won't be able to suspend ourself */
2182 sigemptyset(&signals);
2183 sigaddset(&signals, SIGTSTP);
2184 sigprocmask(SIG_UNBLOCK, &signals, &oldmask);
2185
2186 kill(0, SIGTSTP);
2187 /* Pause for station break */
2188
2189 sigprocmask(SIG_SETMASK, &oldmask, NULL);
2190
2191 /* We're back */
2192 signal(SIGTSTP, onsusp);
2193 set_tty();
2194 if (inwait)
2195 siglongjmp(restore, 1);
2196 }
2197