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