]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/pg.c
Imported from util-linux-2.11y tarball.
[thirdparty/util-linux.git] / text-utils / pg.c
1 /*
2 * pg - A clone of the System V CRT paging utility.
3 *
4 * Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. [deleted]
15 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 /* Sccsid @(#)pg.c 1.44 (gritter) 2/8/02 - modified for util-linux */
33
34 /*
35 * #define _XOPEN_SOURCE 500L
36 *
37 * Adding this define gives us the correct prototypes for fseeko, ftello,
38 * but (for some glibc versions) conflicting prototype for wcwidth.
39 * So, avoid defining _XOPEN_SOURCE, and give prototypes for fseeko, ftello
40 * by hand.
41 */
42
43 #include <sys/types.h>
44 #include <sys/wait.h>
45 #include <sys/stat.h>
46 #ifndef TIOCGWINSZ
47 #include <sys/ioctl.h>
48 #endif
49 #include <sys/termios.h>
50 #include <fcntl.h>
51 #include <regex.h>
52 #include <stdio.h>
53 #include <string.h>
54 #include <stdlib.h>
55 #include <limits.h>
56 #include <ctype.h>
57 #include <errno.h>
58 #include <unistd.h>
59 #include <signal.h>
60 #include <setjmp.h>
61 #include <locale.h>
62 #include <nl_types.h>
63 #include <libgen.h>
64 #if NCH
65 #include <ncurses.h>
66 #else
67 #include <curses.h>
68 #endif
69 #include <term.h>
70
71 #include "nls.h"
72 #include "widechar.h"
73 #include "../defines.h" /* for HAVE_fseeko */
74
75 #define READBUF LINE_MAX /* size of input buffer */
76 #define CMDBUF 255 /* size of command buffer */
77 #define TABSIZE 8 /* spaces consumed by tab character */
78
79 /*
80 * Avoid the message "`var' might be clobbered by `longjmp' or `vfork'"
81 */
82 #define CLOBBGRD(a) (void)(&(a));
83
84 #define cuc(c) ((c) & 0377)
85
86 enum { FORWARD = 1, BACKWARD = 2 }; /* search direction */
87 enum { TOP, MIDDLE, BOTTOM }; /* position of matching line */
88
89 /*
90 * States for syntax-aware command line editor.
91 */
92 enum {
93 COUNT,
94 SIGN,
95 CMD_FIN,
96 SEARCH,
97 SEARCH_FIN,
98 ADDON_FIN,
99 STRING,
100 INVALID
101 };
102
103 /*
104 * Current command
105 */
106 struct {
107 char cmdline[CMDBUF];
108 size_t cmdlen;
109 int count;
110 int key;
111 char pattern[CMDBUF];
112 char addon;
113 } cmd;
114
115 /*
116 * Position of file arguments on argv[] to main()
117 */
118 struct {
119 int first;
120 int current;
121 int last;
122 } files;
123
124 void (*oldint)(int); /* old SIGINT handler */
125 void (*oldquit)(int); /* old SIGQUIT handler */
126 void (*oldterm)(int); /* old SIGTERM handler */
127 char *tty; /* result of ttyname(1) */
128 char *progname; /* program name */
129 unsigned ontty; /* whether running on tty device */
130 unsigned exitstatus; /* exit status */
131 int pagelen = 23; /* lines on a single screen page */
132 int ttycols = 79; /* screen columns (starting at 0) */
133 struct termios otio; /* old termios settings */
134 int tinfostat = -1; /* terminfo routines initialized */
135 int searchdisplay = TOP; /* matching line position */
136 regex_t re; /* regular expression to search for */
137 int remembered; /* have a remembered search string */
138 int cflag; /* clear screen before each page */
139 int eflag; /* suppress (EOF) */
140 int fflag; /* do not split lines */
141 int nflag; /* no newline for commands required */
142 int rflag; /* "restricted" pg */
143 int sflag; /* use standout mode */
144 char *pstring = ":"; /* prompt string */
145 char *searchfor; /* search pattern from argv[] */
146 int havepagelen; /* page length is manually defined */
147 long startline; /* start line from argv[] */
148 int nextfile = 1; /* files to advance */
149 jmp_buf jmpenv; /* jump from signal handlers */
150 int canjump; /* jmpenv is valid */
151 wchar_t wbuf[READBUF]; /* used in several widechar routines */
152
153 const char *copyright =
154 "@(#)pg 1.44 2/8/02. Copyright (c) 2000-2001 Gunnar Ritter. ";
155 const char *helpscreen = "All rights reserved.\n\
156 -------------------------------------------------------\n\
157 h this screen\n\
158 q or Q quit program\n\
159 <newline> next page\n\
160 f skip a page forward\n\
161 d or ^D next halfpage\n\
162 l next line\n\
163 $ last page\n\
164 /regex/ search forward for regex\n\
165 ?regex? or ^regex^ search backward for regex\n\
166 . or ^L redraw screen\n\
167 w or z set page size and go to next page\n\
168 s filename save current file to filename\n\
169 !command shell escape\n\
170 p go to previous file\n\
171 n go to next file\n\
172 \n\
173 Many commands accept preceding numbers, for example:\n\
174 +1<newline> (next page); -1<newline> (previous page); 1<newline> (first page).\n\
175 \n\
176 See pg(1) for more information.\n\
177 -------------------------------------------------------\n";
178
179 #ifdef HAVE_fseeko
180 #if defined (_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS) == 64
181 extern int fseeko64(FILE *f, off_t off, int whence);
182 extern off_t ftello64(FILE *f);
183 #define my_fseeko fseeko64
184 #define my_ftello ftello64
185 #else
186 extern int fseeko(FILE *f, off_t off, int whence);
187 extern off_t ftello(FILE *f);
188 #define my_fseeko fseeko
189 #define my_ftello ftello
190 #endif
191 #else
192 static int my_fseeko(FILE *f, off_t off, int whence) {
193 return fseek(f, (long) off, whence);
194 }
195 static off_t my_ftello(FILE *f) {
196 return (off_t) ftell(f);
197 }
198 #endif
199
200 #ifdef USE_SIGSET /* never defined */
201 /* sigset and sigrelse are obsolete - use when POSIX stuff is unavailable */
202 #define my_sigset sigset
203 #define my_sigrelse sigrelse
204 #else
205 static int my_sigrelse(int sig) {
206 sigset_t sigs;
207
208 if (sigemptyset(&sigs) || sigaddset(&sigs, sig))
209 return -1;
210 return sigprocmask(SIG_UNBLOCK, &sigs, NULL);
211 }
212 typedef void (*my_sighandler_t)(int);
213 static my_sighandler_t my_sigset(int sig, my_sighandler_t disp) {
214 struct sigaction act, oact;
215
216 act.sa_handler = disp;
217 if (sigemptyset(&act.sa_mask))
218 return SIG_ERR;
219 act.sa_flags = 0;
220 if (sigaction(sig, &act, &oact))
221 return SIG_ERR;
222 if (my_sigrelse(sig))
223 return SIG_ERR;
224 return oact.sa_handler;
225 }
226 #endif
227
228 /*
229 * Quit pg.
230 */
231 static void
232 quit(int status)
233 {
234 exit(status < 0100 ? status : 077);
235 }
236
237 /*
238 * Memory allocator including check.
239 */
240 static char *
241 smalloc(size_t s)
242 {
243 char *m = (char *)malloc(s);
244 if (m == NULL) {
245 write(2, "Out of memory\n", 14);
246 quit(++exitstatus);
247 }
248 return m;
249 }
250
251 /*
252 * Usage message and similar routines.
253 */
254 static void
255 usage(void)
256 {
257 fprintf(stderr, _("%s: Usage: %s [-number] [-p string] [-cefnrs] "
258 "[+line] [+/pattern/] [files]\n"),
259 progname, progname);
260 quit(2);
261 }
262
263 static void
264 needarg(char *s)
265 {
266 fprintf(stderr, _("%s: option requires an argument -- %s\n"),
267 progname, s);
268 usage();
269 }
270
271 static void
272 invopt(char *s)
273 {
274 fprintf(stderr, _("%s: illegal option -- %s\n"), progname, s);
275 usage();
276 }
277
278 #ifdef ENABLE_WIDECHAR
279 /*
280 * A mbstowcs()-alike function that transparently handles invalid sequences.
281 */
282 static size_t
283 xmbstowcs(wchar_t *pwcs, const char *s, size_t nwcs)
284 {
285 size_t n = nwcs;
286 int c;
287
288 mbtowc(pwcs, NULL, MB_CUR_MAX);
289 while (*s && n) {
290 if ((c = mbtowc(pwcs, s, MB_CUR_MAX)) < 0) {
291 s++;
292 *pwcs = L'?';
293 } else
294 s += c;
295 pwcs++;
296 n--;
297 }
298 if (n)
299 *pwcs = L'\0';
300 mbtowc(pwcs, NULL, MB_CUR_MAX);
301 return nwcs - n;
302 }
303 #endif
304
305 /*
306 * Helper function for tputs().
307 */
308 static int
309 outcap(int i)
310 {
311 char c = i;
312 return write(1, &c, 1);
313 }
314
315 /*
316 * Write messages to terminal.
317 */
318 static void
319 mesg(char *message)
320 {
321 if (ontty == 0)
322 return;
323 if (*message != '\n' && sflag)
324 vidputs(A_STANDOUT, outcap);
325 write(1, message, strlen(message));
326 if (*message != '\n' && sflag)
327 vidputs(A_NORMAL, outcap);
328 }
329
330 /*
331 * Get the window size.
332 */
333 static void
334 getwinsize(void)
335 {
336 static int initialized, envlines, envcols, deflines, defcols;
337 #ifdef TIOCGWINSZ
338 struct winsize winsz;
339 int badioctl;
340 #endif
341 char *p;
342
343 if (initialized == 0) {
344 if ((p = getenv("LINES")) != NULL && *p != '\0')
345 if ((envlines = atoi(p)) < 0)
346 envlines = 0;
347 if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
348 if ((envcols = atoi(p)) < 0)
349 envcols = 0;
350 /* terminfo values. */
351 if (tinfostat != 1 || columns == 0)
352 defcols = 24;
353 else
354 defcols = columns;
355 if (tinfostat != 1 || lines == 0)
356 deflines = 80;
357 else
358 deflines = lines;
359 initialized = 1;
360 }
361 #ifdef TIOCGWINSZ
362 badioctl = ioctl(1, TIOCGWINSZ, &winsz);
363 #endif
364 if (envcols)
365 ttycols = envcols - 1;
366 #ifdef TIOCGWINSZ
367 else if (!badioctl)
368 ttycols = winsz.ws_col - 1;
369 #endif
370 else
371 ttycols = defcols - 1;
372 if (havepagelen == 0) {
373 if (envlines)
374 pagelen = envlines - 1;
375 #ifdef TIOCGWINSZ
376 else if (!badioctl)
377 pagelen = winsz.ws_row - 1;
378 #endif
379 else
380 pagelen = deflines - 1;
381 }
382 }
383
384 /*
385 * Message if skipping parts of files.
386 */
387 static void
388 skip(int direction)
389 {
390 if (direction > 0)
391 mesg(_("...skipping forward\n"));
392 else
393 mesg(_("...skipping backward\n"));
394 }
395
396 /*
397 * Signal handler while reading from input file.
398 */
399 static void
400 sighandler(int signum)
401 {
402 if (canjump && (signum == SIGINT || signum == SIGQUIT))
403 longjmp(jmpenv, signum);
404 tcsetattr(1, TCSADRAIN, &otio);
405 quit(exitstatus);
406 }
407
408 /*
409 * Check whether the requested file was specified on the command line.
410 */
411 static int
412 checkf(void)
413 {
414 if (files.current + nextfile >= files.last) {
415 mesg(_("No next file"));
416 return 1;
417 }
418 if (files.current + nextfile < files.first) {
419 mesg(_("No previous file"));
420 return 1;
421 }
422 return 0;
423 }
424
425 #ifdef ENABLE_WIDECHAR
426 /*
427 * Return the last character that will fit on the line at col columns
428 * in case MB_CUR_MAX > 1.
429 */
430 static char *
431 endline_for_mb(unsigned col, char *s)
432 {
433 unsigned pos = 0;
434 wchar_t *p = wbuf;
435 wchar_t *end;
436 size_t wl;
437 char *t = s;
438
439 if ((wl = xmbstowcs(wbuf, t, sizeof wbuf - 1)) == (size_t)-1)
440 return s + 1;
441 wbuf[wl] = L'\0';
442 while (*p != L'\0') {
443 switch (*p) {
444 /*
445 * Cursor left.
446 */
447 case L'\b':
448 if (pos > 0)
449 pos--;
450 break;
451 /*
452 * No cursor movement.
453 */
454 case L'\a':
455 break;
456 /*
457 * Special.
458 */
459 case L'\r':
460 pos = 0;
461 break;
462 case L'\n':
463 end = p + 1;
464 goto ended;
465 /*
466 * Cursor right.
467 */
468 case L'\t':
469 pos += TABSIZE - (pos % TABSIZE);
470 break;
471 default:
472 pos += wcwidth(*p);
473 }
474 if (pos > col) {
475 if (*p == L'\t')
476 p++;
477 else if (pos > col + 1)
478 /*
479 * wcwidth() found a character that
480 * has multiple columns. What happens
481 * now? Assume the terminal will print
482 * the entire character onto the next
483 * row.
484 */
485 p--;
486 if (*++p == L'\n')
487 p++;
488 end = p;
489 goto ended;
490 }
491 p++;
492 }
493 end = p;
494 ended:
495 *end = L'\0';
496 p = wbuf;
497 if ((pos = wcstombs(NULL, p, READBUF)) == -1)
498 return s + 1;
499 return s + pos;
500 }
501 #endif
502
503 /*
504 * Return the last character that will fit on the line at col columns.
505 */
506 static char *
507 endline(unsigned col, char *s)
508 {
509 unsigned pos = 0;
510 char *t = s;
511
512 #ifdef ENABLE_WIDECHAR
513 if (MB_CUR_MAX > 1)
514 return endline_for_mb(col, s);
515 #endif
516
517 while (*s != '\0') {
518 switch (*s) {
519 /*
520 * Cursor left.
521 */
522 case '\b':
523 if (pos > 0)
524 pos--;
525 break;
526 /*
527 * No cursor movement.
528 */
529 case '\a':
530 break;
531 /*
532 * Special.
533 */
534 case '\r':
535 pos = 0;
536 break;
537 case '\n':
538 t = s + 1;
539 goto cend;
540 /*
541 * Cursor right.
542 */
543 case '\t':
544 pos += TABSIZE - (pos % TABSIZE);
545 break;
546 default:
547 pos++;
548 }
549 if (pos > col) {
550 if (*s == '\t')
551 s++;
552 if (*++s == '\n')
553 s++;
554 t = s;
555 goto cend;
556 }
557 s++;
558 }
559 t = s;
560 cend:
561 return t;
562 }
563
564 /*
565 * Clear the current line on the terminal's screen.
566 */
567 static void
568 cline(void)
569 {
570 char *buf = (char *)smalloc(ttycols + 2);
571 memset(buf, ' ', ttycols + 2);
572 buf[0] = '\r';
573 buf[ttycols + 1] = '\r';
574 write(1, buf, ttycols + 2);
575 free(buf);
576 }
577
578 /*
579 * Evaluate a command character's semantics.
580 */
581 static int
582 getstate(int c)
583 {
584 switch (c) {
585 case '1': case '2': case '3': case '4': case '5':
586 case '6': case '7': case '8': case '9': case '0':
587 case '\0':
588 return COUNT;
589 case '-': case '+':
590 return SIGN;
591 case 'l': case 'd': case '\004': case 'f': case 'z':
592 case '.': case '\014': case '$': case 'n': case 'p':
593 case 'w': case 'h': case 'q': case 'Q':
594 return CMD_FIN;
595 case '/': case '?': case '^':
596 return SEARCH;
597 case 's': case '!':
598 return STRING;
599 case 'm': case 'b': case 't':
600 return ADDON_FIN;
601 default:
602 #ifndef PGNOBELL
603 if (bell)
604 tputs(bell, 1, outcap);
605 #endif /* PGNOBELL */
606 return INVALID;
607 }
608 }
609
610 /*
611 * Get the count and ignore last character of string.
612 */
613 static int
614 getcount(char *cmdstr)
615 {
616 char *buf;
617 char *p;
618 int i;
619
620 if (*cmdstr == '\0')
621 return 1;
622 buf = (char *)smalloc(strlen(cmdstr) + 1);
623 strcpy(buf, cmdstr);
624 if (cmd.key != '\0') {
625 if (cmd.key == '/' || cmd.key == '?' || cmd.key == '^') {
626 if ((p = strchr(buf, cmd.key)) != NULL)
627 *p = '\0';
628 } else
629 *(buf + strlen(buf) - 1) = '\0';
630 }
631 if (*buf == '\0')
632 return 1;
633 if (buf[0] == '-' && buf[1] == '\0') {
634 i = -1;
635 } else {
636 if (*buf == '+')
637 i = atoi(buf + 1);
638 else
639 i = atoi(buf);
640 }
641 free(buf);
642 return i;
643 }
644
645 /*
646 * Read what the user writes at the prompt. This is tricky because
647 * we check for valid input.
648 */
649 static void
650 prompt(long long pageno)
651 {
652 struct termios tio;
653 char key;
654 int state = COUNT;
655 int escape = 0;
656 char b[LINE_MAX], *p;
657
658 if (pageno != -1) {
659 if ((p = strstr(pstring, "%d")) == NULL) {
660 mesg(pstring);
661 } else {
662 strcpy(b, pstring);
663 sprintf(b + (p - pstring), "%lld", pageno);
664 strcat(b, p + 2);
665 mesg(b);
666 }
667 }
668 cmd.key = cmd.addon = cmd.cmdline[0] = '\0';
669 cmd.cmdlen = 0;
670 tcgetattr(1, &tio);
671 tio.c_lflag &= ~(ICANON | ECHO);
672 tio.c_cc[VMIN] = 1;
673 tio.c_cc[VTIME] = 0;
674 tcsetattr(1, TCSADRAIN, &tio);
675 tcflush(1, TCIFLUSH);
676 for (;;) {
677 switch (read(1, &key, 1)) {
678 case 0: quit(0);
679 /*NOTREACHED*/
680 case -1: quit(1);
681 }
682 if (key == tio.c_cc[VERASE]) {
683 if (cmd.cmdlen) {
684 write(1, "\b \b", 3);
685 cmd.cmdline[--cmd.cmdlen] = '\0';
686 switch (state) {
687 case ADDON_FIN:
688 state = SEARCH_FIN;
689 cmd.addon = '\0';
690 break;
691 case CMD_FIN:
692 cmd.key = '\0';
693 state = COUNT;
694 break;
695 case SEARCH_FIN:
696 state = SEARCH;
697 /*FALLTHRU*/
698 case SEARCH:
699 if (cmd.cmdline[cmd.cmdlen - 1]
700 == '\\') {
701 escape = 1;
702 while(cmd.cmdline[cmd.cmdlen
703 - escape - 1]
704 == '\\') escape++;
705 escape %= 2;
706 }
707 else {
708 escape = 0;
709 if (strchr(cmd.cmdline, cmd.key)
710 == NULL) {
711 cmd.key = '\0';
712 state = COUNT;
713 }
714 }
715 break;
716 }
717 }
718 if (cmd.cmdlen == 0) {
719 state = COUNT;
720 cmd.key = '\0';
721 }
722 continue;
723 }
724 if (key == tio.c_cc[VKILL]) {
725 cline();
726 cmd.cmdlen = 0;
727 cmd.cmdline[0] = '\0';
728 state = COUNT;
729 cmd.key = '\0';
730 continue;
731 }
732 if (key == '\n' || (nflag && state == COUNT && key == ' '))
733 break;
734 if (cmd.cmdlen >= CMDBUF - 1)
735 continue;
736 switch (state) {
737 case STRING:
738 break;
739 case SEARCH:
740 if (!escape) {
741 if (key == cmd.key)
742 state = SEARCH_FIN;
743 if (key == '\\')
744 escape = 1;
745 } else
746 escape = 0;
747 break;
748 case SEARCH_FIN:
749 if (getstate(key) != ADDON_FIN)
750 continue;
751 state = ADDON_FIN;
752 cmd.addon = key;
753 switch (key) {
754 case 't':
755 searchdisplay = TOP;
756 break;
757 case 'm':
758 searchdisplay = MIDDLE;
759 break;
760 case 'b':
761 searchdisplay = BOTTOM;
762 break;
763 }
764 break;
765 case CMD_FIN:
766 case ADDON_FIN:
767 continue;
768 default:
769 state = getstate(key);
770 switch (state) {
771 case SIGN:
772 if (cmd.cmdlen != 0) {
773 state = INVALID;
774 continue;
775 }
776 state = COUNT;
777 /*FALLTHRU*/
778 case COUNT:
779 break;
780 case ADDON_FIN:
781 case INVALID:
782 continue;
783 default:
784 cmd.key = key;
785 }
786 }
787 write(1, &key, 1);
788 cmd.cmdline[cmd.cmdlen++] = key;
789 cmd.cmdline[cmd.cmdlen] = '\0';
790 if (nflag && state == CMD_FIN)
791 goto endprompt;
792 }
793 endprompt:
794 tcsetattr(1, TCSADRAIN, &otio);
795 cline();
796 cmd.count = getcount(cmd.cmdline);
797 }
798
799 #ifdef ENABLE_WIDECHAR
800 /*
801 * Remove backspace formatting, for searches
802 * in case MB_CUR_MAX > 1.
803 */
804 static char *
805 colb_for_mb(char *s)
806 {
807 char *p = s;
808 wchar_t *wp, *wq;
809 size_t l = strlen(s), wl;
810 unsigned i;
811
812 if ((wl = xmbstowcs(wbuf, p, sizeof wbuf)) == (size_t)-1)
813 return s;
814 for (wp = wbuf, wq = wbuf, i = 0; *wp != L'\0' && i < wl;
815 wp++, wq++) {
816 if (*wp == L'\b') {
817 if (wq != wbuf)
818 wq -= 2;
819 else
820 wq--;
821 } else
822 *wq = *wp;
823 }
824 *wq = L'\0';
825 wp = wbuf;
826 wcstombs(s, wp, l + 1);
827
828 return s;
829 }
830 #endif
831
832 /*
833 * Remove backspace formatting, for searches.
834 */
835 static char *
836 colb(char *s)
837 {
838 char *p = s, *q;
839
840 #ifdef ENABLE_WIDECHAR
841 if (MB_CUR_MAX > 1)
842 return colb_for_mb(s);
843 #endif
844
845 for (q = s; *p != '\0'; p++, q++) {
846 if (*p == '\b') {
847 if (q != s)
848 q -= 2;
849 else
850 q--;
851 } else
852 *q = *p;
853 }
854 *q = '\0';
855
856 return s;
857 }
858
859 #ifdef ENABLE_WIDECHAR
860 /*
861 * Convert nonprintable characters to spaces
862 * in case MB_CUR_MAX > 1.
863 */
864 static void
865 makeprint_for_mb(char *s, size_t l)
866 {
867 char *t = s;
868 wchar_t *wp = wbuf;
869 size_t wl;
870
871 if ((wl = xmbstowcs(wbuf, t, sizeof wbuf)) == (size_t)-1)
872 return;
873 while (wl--) {
874 if (!iswprint(*wp) && *wp != L'\n' && *wp != L'\r'
875 && *wp != L'\b' && *wp != L'\t')
876 *wp = L'?';
877 wp++;
878 }
879 wp = wbuf;
880 wcstombs(s, wp, l);
881 }
882 #endif
883
884 /*
885 * Convert nonprintable characters to spaces.
886 */
887 static void
888 makeprint(char *s, size_t l)
889 {
890 #ifdef ENABLE_WIDECHAR
891 if (MB_CUR_MAX > 1)
892 return makeprint_for_mb(s, l);
893 #endif
894
895 while (l--) {
896 if (!isprint(cuc(*s)) && *s != '\n' && *s != '\r'
897 && *s != '\b' && *s != '\t')
898 *s = '?';
899 s++;
900 }
901 }
902
903 /*
904 * Strip backslash characters from the given string.
905 */
906 static void
907 striprs(char *s)
908 {
909 char *p = s;
910
911 do {
912 if (*s == '\\') {
913 s++;
914 }
915 *p++ = *s;
916 } while (*s++ != '\0');
917 }
918
919 /*
920 * Extract the search pattern off the command line.
921 */
922 static char *
923 makepat(void)
924 {
925 char *p;
926
927 if (cmd.addon == '\0')
928 p = cmd.cmdline + strlen(cmd.cmdline) - 1;
929 else
930 p = cmd.cmdline + strlen(cmd.cmdline) - 2;
931 if (*p == cmd.key)
932 *p = '\0';
933 else
934 *(p + 1) = '\0';
935 if ((p = strchr(cmd.cmdline, cmd.key)) != NULL) {
936 p++;
937 striprs(p);
938 }
939 return p;
940 }
941
942 /*
943 * Process errors that occurred in temporary file operations.
944 */
945 static void
946 tmperr(FILE *f, char *ftype)
947 {
948 if (ferror(f))
949 fprintf(stderr, _("%s: Read error from %s file\n"),
950 progname, ftype);
951 else if (feof(f))
952 /*
953 * Most likely '\0' in input.
954 */
955 fprintf(stderr, _("%s: Unexpected EOF in %s file\n"),
956 progname, ftype);
957 else
958 fprintf(stderr, _("%s: Unknown error in %s file\n"),
959 progname, ftype);
960 quit(++exitstatus);
961 }
962
963 /*
964 * perror()-like, but showing the program's name.
965 */
966 static void
967 pgerror(int eno, char *string)
968 {
969 fprintf(stderr, "%s: %s: %s\n", progname, string, strerror(eno));
970 }
971
972 /*
973 * Read the file and respond to user input.
974 * Beware: long and ugly.
975 */
976 static void
977 pgfile(FILE *f, char *name)
978 {
979 off_t pos, oldpos, fpos;
980 off_t line = 0, fline = 0, bline = 0, oldline = 0, eofline = 0;
981 int dline = 0;
982 /*
983 * These are the line counters:
984 * line the line desired to display
985 * fline the current line of the input file
986 * bline the current line of the file buffer
987 * oldline the line before a search was started
988 * eofline the last line of the file if it is already reached
989 * dline the line on the display
990 */
991 int search = 0;
992 unsigned searchcount = 0;
993 /*
994 * Advance to EOF immediately.
995 */
996 int seekeof = 0;
997 /*
998 * EOF has been reached by `line'.
999 */
1000 int eof = 0;
1001 /*
1002 * f and fbuf refer to the same file.
1003 */
1004 int nobuf = 0;
1005 int sig;
1006 int rerror;
1007 size_t sz;
1008 char b[READBUF + 1];
1009 char *p;
1010 /*
1011 * fbuf an exact copy of the input file as it gets read
1012 * find index table for input, one entry per line
1013 * save for the s command, to save to a file
1014 */
1015 FILE *fbuf, *find, *save;
1016
1017 /* silence compiler - it may warn about longjmp() */
1018 CLOBBGRD(line);
1019 CLOBBGRD(fline);
1020 CLOBBGRD(bline);
1021 CLOBBGRD(oldline);
1022 CLOBBGRD(eofline);
1023 CLOBBGRD(dline);
1024 CLOBBGRD(ttycols);
1025 CLOBBGRD(search);
1026 CLOBBGRD(searchcount);
1027 CLOBBGRD(seekeof);
1028 CLOBBGRD(eof);
1029 CLOBBGRD(fpos);
1030 CLOBBGRD(nobuf);
1031 CLOBBGRD(fbuf);
1032
1033 if (ontty == 0) {
1034 /*
1035 * Just copy stdin to stdout.
1036 */
1037 while ((sz = fread(b, sizeof *b, READBUF, f)) != 0)
1038 write(1, b, sz);
1039 if (ferror(f)) {
1040 pgerror(errno, name);
1041 exitstatus++;
1042 }
1043 return;
1044 }
1045 if ((fpos = my_fseeko(f, (off_t)0, SEEK_SET)) == -1)
1046 fbuf = tmpfile();
1047 else {
1048 fbuf = f;
1049 nobuf = 1;
1050 }
1051 find = tmpfile();
1052 if (fbuf == NULL || find == NULL) {
1053 fprintf(stderr, _("%s: Cannot create tempfile\n"), progname);
1054 quit(++exitstatus);
1055 }
1056 if (searchfor) {
1057 search = FORWARD;
1058 oldline = 0;
1059 searchcount = 1;
1060 rerror = regcomp(&re, searchfor, REG_NOSUB | REG_NEWLINE);
1061 if (rerror != 0) {
1062 mesg(_("RE error: "));
1063 regerror(rerror, &re, b, READBUF);
1064 mesg(b);
1065 goto newcmd;
1066 }
1067 remembered = 1;
1068 }
1069
1070 for (line = startline; ; ) {
1071 /*
1072 * Get a line from input file or buffer.
1073 */
1074 if (line < bline) {
1075 my_fseeko(find, line * sizeof pos, SEEK_SET);
1076 if (fread(&pos, sizeof pos, 1, find) == 0)
1077 tmperr(find, "index");
1078 my_fseeko(find, (off_t)0, SEEK_END);
1079 my_fseeko(fbuf, pos, SEEK_SET);
1080 if (fgets(b, READBUF, fbuf) == NULL)
1081 tmperr(fbuf, "buffer");
1082 } else if (eofline == 0) {
1083 my_fseeko(find, (off_t)0, SEEK_END);
1084 do {
1085 if (!nobuf)
1086 my_fseeko(fbuf, (off_t)0, SEEK_END);
1087 pos = my_ftello(fbuf);
1088 if ((sig = setjmp(jmpenv)) != 0) {
1089 /*
1090 * We got a signal.
1091 */
1092 canjump = 0;
1093 my_sigrelse(sig);
1094 my_fseeko(fbuf, pos, SEEK_SET);
1095 *b = '\0';
1096 dline = pagelen;
1097 break;
1098 } else {
1099 if (nobuf)
1100 my_fseeko(f, fpos, SEEK_SET);
1101 canjump = 1;
1102 p = fgets(b, READBUF, f);
1103 if (nobuf)
1104 if ((fpos = my_ftello(f)) == -1)
1105 pgerror(errno, name);
1106 canjump = 0;
1107 }
1108 if (p == NULL || *b == '\0') {
1109 if (ferror(f))
1110 pgerror(errno, name);
1111 eofline = fline;
1112 eof = 1;
1113 break;
1114 } else {
1115 if (!nobuf)
1116 fputs(b, fbuf);
1117 fwrite(&pos, sizeof pos, 1, find);
1118 if (!fflag) {
1119 oldpos = pos;
1120 p = b;
1121 while (*(p = endline(ttycols,
1122 p))
1123 != '\0') {
1124 pos = oldpos + (p - b);
1125 fwrite(&pos,
1126 sizeof pos,
1127 1, find);
1128 fline++;
1129 bline++;
1130 }
1131 }
1132 fline++;
1133 }
1134 } while (line > bline++);
1135 } else {
1136 /*
1137 * eofline != 0
1138 */
1139 eof = 1;
1140 }
1141 if (search == FORWARD) {
1142 if (eof) {
1143 line = oldline;
1144 search = searchcount = 0;
1145 mesg(_("Pattern not found"));
1146 eof = 0;
1147 goto newcmd;
1148 }
1149 line++;
1150 colb(b);
1151 if (regexec(&re, b, 0, NULL, 0) == 0) {
1152 searchcount--;
1153 }
1154 if (searchcount == 0) {
1155 search = dline = 0;
1156 switch (searchdisplay) {
1157 case TOP:
1158 line -= 1;
1159 break;
1160 case MIDDLE:
1161 line -= pagelen / 2 + 1;
1162 break;
1163 case BOTTOM:
1164 line -= pagelen;
1165 break;
1166 }
1167 skip(1);
1168 }
1169 continue;
1170 } else if (eof) { /*
1171 * We are not searching.
1172 */
1173 line = bline;
1174 } else if (*b != '\0') {
1175 if (cflag && clear_screen) {
1176 switch (dline) {
1177 case 0:
1178 tputs(clear_screen, 1, outcap);
1179 dline = 0;
1180 }
1181 }
1182 line++;
1183 if (eofline && line == eofline)
1184 eof = 1;
1185 dline++;
1186 if ((sig = setjmp(jmpenv)) != 0) {
1187 /*
1188 * We got a signal.
1189 */
1190 canjump = 0;
1191 my_sigrelse(sig);
1192 dline = pagelen;
1193 } else {
1194 p = endline(ttycols, b);
1195 sz = p - b;
1196 makeprint(b, sz);
1197 canjump = 1;
1198 write(1, b, sz);
1199 canjump = 0;
1200 }
1201 }
1202 if (dline >= pagelen || eof) {
1203 /*
1204 * Time for prompting!
1205 */
1206 if (eof && seekeof) {
1207 eof = seekeof = 0;
1208 if (line >= pagelen)
1209 line -= pagelen;
1210 else
1211 line = 0;
1212 dline = -1;
1213 continue;
1214 }
1215 newcmd:
1216 if (eof) {
1217 if (fline == 0 || eflag)
1218 break;
1219 mesg(_("(EOF)"));
1220 }
1221 prompt((line - 1) / pagelen + 1);
1222 switch (cmd.key) {
1223 case '/':
1224 /*
1225 * Search forward.
1226 */
1227 search = FORWARD;
1228 oldline = line;
1229 searchcount = cmd.count;
1230 p = makepat();
1231 if (p != NULL && *p) {
1232 if (remembered == 1)
1233 regfree(&re);
1234 rerror = regcomp(&re, p,
1235 REG_NOSUB | REG_NEWLINE);
1236 if (rerror != 0) {
1237 mesg(_("RE error: "));
1238 sz = regerror(rerror, &re,
1239 b, READBUF);
1240 mesg(b);
1241 goto newcmd;
1242 }
1243 remembered = 1;
1244 } else if (remembered == 0) {
1245 mesg(_("No remembered search string"));
1246 goto newcmd;
1247 }
1248 continue;
1249 case '?':
1250 case '^':
1251 /*
1252 * Search backward.
1253 */
1254 search = BACKWARD;
1255 oldline = line;
1256 searchcount = cmd.count;
1257 p = makepat();
1258 if (p != NULL && *p) {
1259 if (remembered == 1)
1260 regfree(&re);
1261 rerror = regcomp(&re, p,
1262 REG_NOSUB | REG_NEWLINE);
1263 if (rerror != 0) {
1264 mesg("RE error: ");
1265 regerror(rerror, &re,
1266 b, READBUF);
1267 mesg(b);
1268 goto newcmd;
1269 }
1270 remembered = 1;
1271 } else if (remembered == 0) {
1272 mesg("No remembered search string");
1273 goto newcmd;
1274 }
1275 line -= pagelen;
1276 if (line <= 0)
1277 goto notfound_bw;
1278 while (line) {
1279 my_fseeko(find, --line * sizeof pos,
1280 SEEK_SET);
1281 if(fread(&pos, sizeof pos, 1,find)==0)
1282 tmperr(find, "index");
1283 my_fseeko(find, (off_t)0, SEEK_END);
1284 my_fseeko(fbuf, pos, SEEK_SET);
1285 if (fgets(b, READBUF, fbuf) == NULL)
1286 tmperr(fbuf, "buffer");
1287 colb(b);
1288 if (regexec(&re, b, 0, NULL, 0) == 0)
1289 searchcount--;
1290 if (searchcount == 0)
1291 goto found_bw;
1292 }
1293 notfound_bw:
1294 line = oldline;
1295 search = searchcount = 0;
1296 mesg(_("Pattern not found"));
1297 goto newcmd;
1298 found_bw:
1299 eof = search = dline = 0;
1300 skip(-1);
1301 switch (searchdisplay) {
1302 case TOP:
1303 /* line -= 1; */
1304 break;
1305 case MIDDLE:
1306 line -= pagelen / 2;
1307 break;
1308 case BOTTOM:
1309 if (line != 0)
1310 dline = -1;
1311 line -= pagelen;
1312 break;
1313 }
1314 if (line < 0)
1315 line = 0;
1316 continue;
1317 case 's':
1318 /*
1319 * Save to file.
1320 */
1321 p = cmd.cmdline;
1322 while (*++p == ' ');
1323 if (*p == '\0')
1324 goto newcmd;
1325 save = fopen(p, "wb");
1326 if (save == NULL) {
1327 cmd.count = errno;
1328 mesg(_("Cannot open "));
1329 mesg(p);
1330 mesg(": ");
1331 mesg(strerror(cmd.count));
1332 goto newcmd;
1333 }
1334 /*
1335 * Advance to EOF.
1336 */
1337 my_fseeko(find, (off_t)0, SEEK_END);
1338 for (;;) {
1339 if (!nobuf)
1340 my_fseeko(fbuf,(off_t)0,SEEK_END);
1341 pos = my_ftello(fbuf);
1342 if (fgets(b, READBUF, f) == NULL) {
1343 eofline = fline;
1344 break;
1345 }
1346 if (!nobuf)
1347 fputs(b, fbuf);
1348 fwrite(&pos, sizeof pos, 1, find);
1349 if (!fflag) {
1350 oldpos = pos;
1351 p = b;
1352 while (*(p = endline(ttycols,
1353 p))
1354 != '\0') {
1355 pos = oldpos + (p - b);
1356 fwrite(&pos,
1357 sizeof pos,
1358 1, find);
1359 fline++;
1360 bline++;
1361 }
1362 }
1363 fline++;
1364 bline++;
1365 }
1366 my_fseeko(fbuf, (off_t)0, SEEK_SET);
1367 while ((sz = fread(b, sizeof *b, READBUF,
1368 fbuf)) != 0) {
1369 /*
1370 * No error check for compat.
1371 */
1372 fwrite(b, sizeof *b, sz, save);
1373 }
1374 fclose(save);
1375 my_fseeko(fbuf, (off_t)0, SEEK_END);
1376 mesg(_("saved"));
1377 goto newcmd;
1378 case 'l':
1379 /*
1380 * Next line.
1381 */
1382 if (*cmd.cmdline != 'l')
1383 eof = 0;
1384 if (cmd.count == 0)
1385 cmd.count = 1; /* compat */
1386 if (isdigit(cuc(*cmd.cmdline))) {
1387 line = cmd.count - 2;
1388 dline = 0;
1389 } else {
1390 if (cmd.count != 1) {
1391 line += cmd.count - 1
1392 - pagelen;
1393 dline = -1;
1394 skip(cmd.count);
1395 }
1396 /*
1397 * Nothing to do if count==1.
1398 */
1399 }
1400 break;
1401 case 'd':
1402 /*
1403 * Half screen forward.
1404 */
1405 case '\004': /* ^D */
1406 if (*cmd.cmdline != cmd.key)
1407 eof = 0;
1408 if (cmd.count == 0)
1409 cmd.count = 1; /* compat */
1410 line += (cmd.count * pagelen / 2)
1411 - pagelen - 1;
1412 dline = -1;
1413 skip(cmd.count);
1414 break;
1415 case 'f':
1416 /*
1417 * Skip forward.
1418 */
1419 if (cmd.count <= 0)
1420 cmd.count = 1; /* compat */
1421 line += cmd.count * pagelen - 2;
1422 if (eof)
1423 line += 2;
1424 if (*cmd.cmdline != 'f')
1425 eof = 0;
1426 else if (eof)
1427 break;
1428 if (eofline && line >= eofline)
1429 line -= pagelen;
1430 dline = -1;
1431 skip(cmd.count);
1432 break;
1433 case '\0':
1434 /*
1435 * Just a number, or '-', or <newline>.
1436 */
1437 if (cmd.count == 0)
1438 cmd.count = 1; /* compat */
1439 if (isdigit(cuc(*cmd.cmdline)))
1440 line = (cmd.count - 1) * pagelen - 2;
1441 else
1442 line += (cmd.count - 1)
1443 * (pagelen - 1) - 2;
1444 if (*cmd.cmdline != '\0')
1445 eof = 0;
1446 if (cmd.count != 1) {
1447 skip(cmd.count);
1448 dline = -1;
1449 } else {
1450 dline = 1;
1451 line += 2;
1452 }
1453 break;
1454 case '$':
1455 /*
1456 * Advance to EOF.
1457 */
1458 if (!eof)
1459 skip(1);
1460 eof = 0;
1461 line = LONG_MAX;
1462 seekeof = 1;
1463 dline = -1;
1464 break;
1465 case '.':
1466 case '\014': /* ^L */
1467 /*
1468 * Repaint screen.
1469 */
1470 eof = 0;
1471 if (line >= pagelen)
1472 line -= pagelen;
1473 else
1474 line = 0;
1475 dline = 0;
1476 break;
1477 case '!':
1478 /*
1479 * Shell escape.
1480 */
1481 if (rflag) {
1482 mesg(progname);
1483 mesg(_(": !command not allowed in "
1484 "rflag mode.\n"));
1485 } else {
1486 pid_t cpid;
1487
1488 write(1, cmd.cmdline,
1489 strlen(cmd.cmdline));
1490 write(1, "\n", 1);
1491 my_sigset(SIGINT, SIG_IGN);
1492 my_sigset(SIGQUIT, SIG_IGN);
1493 switch (cpid = fork()) {
1494 case 0:
1495 p = getenv("SHELL");
1496 if (p == NULL) p = "/bin/sh";
1497 if (!nobuf)
1498 fclose(fbuf);
1499 fclose(find);
1500 if (isatty(0) == 0) {
1501 close(0);
1502 open(tty, O_RDONLY);
1503 } else {
1504 fclose(f);
1505 }
1506 my_sigset(SIGINT, oldint);
1507 my_sigset(SIGQUIT, oldquit);
1508 my_sigset(SIGTERM, oldterm);
1509 execl(p, p, "-c",
1510 cmd.cmdline + 1, NULL);
1511 pgerror(errno, p);
1512 _exit(0177);
1513 /*NOTREACHED*/
1514 case -1:
1515 mesg(_("fork() failed, "
1516 "try again later\n"));
1517 break;
1518 default:
1519 while (wait(NULL) != cpid);
1520 }
1521 my_sigset(SIGINT, sighandler);
1522 my_sigset(SIGQUIT, sighandler);
1523 mesg("!\n");
1524 }
1525 goto newcmd;
1526 case 'h':
1527 /*
1528 * Help!
1529 */
1530 write(1, copyright + 4, strlen(copyright + 4));
1531 write(1, helpscreen, strlen(helpscreen));
1532 goto newcmd;
1533 case 'n':
1534 /*
1535 * Next file.
1536 */
1537 if (cmd.count == 0)
1538 cmd.count = 1;
1539 nextfile = cmd.count;
1540 if (checkf()) {
1541 nextfile = 1;
1542 goto newcmd;
1543 }
1544 eof = 1;
1545 break;
1546 case 'p':
1547 /*
1548 * Previous file.
1549 */
1550 if (cmd.count == 0)
1551 cmd.count = 1;
1552 nextfile = 0 - cmd.count;
1553 if (checkf()) {
1554 nextfile = 1;
1555 goto newcmd;
1556 }
1557 eof = 1;
1558 break;
1559 case 'q':
1560 case 'Q':
1561 /*
1562 * Exit pg.
1563 */
1564 quit(exitstatus);
1565 /*NOTREACHED*/
1566 case 'w':
1567 case 'z':
1568 /*
1569 * Set window size.
1570 */
1571 if (cmd.count < 0)
1572 cmd.count = 0;
1573 if (*cmd.cmdline != cmd.key)
1574 pagelen = ++cmd.count;
1575 dline = 1;
1576 break;
1577 }
1578 if (line <= 0) {
1579 line = 0;
1580 dline = 0;
1581 }
1582 if (cflag && dline == 1) {
1583 dline = 0;
1584 line--;
1585 }
1586 }
1587 if (eof)
1588 break;
1589 }
1590 fclose(find);
1591 if (!nobuf)
1592 fclose(fbuf);
1593 }
1594
1595 int
1596 main(int argc, char **argv)
1597 {
1598 int arg, i;
1599 char *p;
1600 FILE *input;
1601
1602 progname = basename(argv[0]);
1603
1604 setlocale(LC_MESSAGES, "");
1605 bindtextdomain(PACKAGE, LOCALEDIR);
1606 textdomain(PACKAGE);
1607
1608 if (tcgetattr(1, &otio) == 0) {
1609 ontty = 1;
1610 oldint = my_sigset(SIGINT, sighandler);
1611 oldquit = my_sigset(SIGQUIT, sighandler);
1612 oldterm = my_sigset(SIGTERM, sighandler);
1613 setlocale(LC_CTYPE, "");
1614 setlocale(LC_COLLATE, "");
1615 tty = ttyname(1);
1616 setupterm(NULL, 1, &tinfostat);
1617 getwinsize();
1618 helpscreen = _(helpscreen);
1619 }
1620 for (arg = 1; argv[arg]; arg++) {
1621 if (*argv[arg] == '+')
1622 continue;
1623 if (*argv[arg] != '-' || argv[arg][1] == '\0')
1624 break;
1625 argc--;
1626 for (i = 1; argv[arg][i]; i++) {
1627 switch (argv[arg][i]) {
1628 case '-':
1629 if (i != 1 || argv[arg][i + 1])
1630 invopt(&argv[arg][i]);
1631 goto endargs;
1632 case '1': case '2': case '3': case '4': case '5':
1633 case '6': case '7': case '8': case '9': case '0':
1634 pagelen = atoi(argv[arg] + i);
1635 havepagelen = 1;
1636 goto nextarg;
1637 case 'c':
1638 cflag = 1;
1639 break;
1640 case 'e':
1641 eflag = 1;
1642 break;
1643 case 'f':
1644 fflag = 1;
1645 break;
1646 case 'n':
1647 nflag = 1;
1648 break;
1649 case 'p':
1650 if (argv[arg][i + 1]) {
1651 pstring = &argv[arg][i + 1];
1652 } else if (argv[++arg]) {
1653 --argc;
1654 pstring = argv[arg];
1655 } else
1656 needarg("-p");
1657 goto nextarg;
1658 case 'r':
1659 rflag = 1;
1660 break;
1661 case 's':
1662 sflag = 1;
1663 break;
1664 default:
1665 invopt(&argv[arg][i]);
1666 }
1667 }
1668 nextarg:
1669 ;
1670 }
1671 endargs:
1672 for (arg = 1; argv[arg]; arg++) {
1673 if (*argv[arg] == '-') {
1674 if (argv[arg][1] == '-') {
1675 arg++;
1676 break;
1677 }
1678 if (argv[arg][1] == '\0')
1679 break;
1680 if (argv[arg][1] == 'p' && argv[arg][2] == '\0')
1681 arg++;
1682 continue;
1683 }
1684 if (*argv[arg] != '+')
1685 break;
1686 argc--;
1687 switch (*(argv[arg] + 1)) {
1688 case '\0':
1689 needarg("+");
1690 /*NOTREACHED*/
1691 case '1': case '2': case '3': case '4': case '5':
1692 case '6': case '7': case '8': case '9': case '0':
1693 startline = atoi(argv[arg] + 1);
1694 break;
1695 case '/':
1696 searchfor = argv[arg] + 2;
1697 if (*searchfor == '\0')
1698 needarg("+/");
1699 p = searchfor + strlen(searchfor) - 1;
1700 if (*p == '/') *p = '\0';
1701 if (*searchfor == '\0')
1702 needarg("+/");
1703 break;
1704 default:
1705 invopt(argv[arg]);
1706 }
1707 }
1708 if (argc == 1) {
1709 pgfile(stdin, "stdin");
1710 } else {
1711 files.first = arg;
1712 files.last = arg + argc - 1;
1713 for ( ; argv[arg]; arg += nextfile) {
1714 nextfile = 1;
1715 files.current = arg;
1716 if (argc > 2) {
1717 static int firsttime;
1718 firsttime++;
1719 if (firsttime > 1) {
1720 mesg(_("(Next file: "));
1721 mesg(argv[arg]);
1722 mesg(")");
1723 newfile:
1724 if (ontty) {
1725 prompt(-1);
1726 switch(cmd.key) {
1727 case 'n':
1728 /*
1729 * Next file.
1730 */
1731 if (cmd.count == 0)
1732 cmd.count = 1;
1733 nextfile = cmd.count;
1734 if (checkf()) {
1735 nextfile = 1;
1736 mesg(":");
1737 goto newfile;
1738 }
1739 continue;
1740 case 'p':
1741 /*
1742 * Previous file.
1743 */
1744 if (cmd.count == 0)
1745 cmd.count = 1;
1746 nextfile = 0 - cmd.count;
1747 if (checkf()) {
1748 nextfile = 1;
1749 mesg(":");
1750 goto newfile;
1751 }
1752 continue;
1753 case 'q':
1754 case 'Q':
1755 quit(exitstatus);
1756 }
1757 } else mesg("\n");
1758 }
1759 }
1760 if (strcmp(argv[arg], "-") == 0)
1761 input = stdin;
1762 else {
1763 input = fopen(argv[arg], "r");
1764 if (input == NULL) {
1765 pgerror(errno, argv[arg]);
1766 exitstatus++;
1767 continue;
1768 }
1769 }
1770 if (ontty == 0 && argc > 2) {
1771 /*
1772 * Use the prefix as specified by SUSv2.
1773 */
1774 write(1, "::::::::::::::\n", 15);
1775 write(1, argv[arg], strlen(argv[arg]));
1776 write(1, "\n::::::::::::::\n", 16);
1777 }
1778 pgfile(input, argv[arg]);
1779 if (input != stdin)
1780 fclose(input);
1781 }
1782 }
1783 quit(exitstatus);
1784 /*NOTREACHED*/
1785 return 0;
1786 }