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