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