]> git.ipfire.org Git - thirdparty/util-linux.git/blob - text-utils/pg.c
wipefs: add --lock and LOCK_BLOCK_DEVICE
[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 }
991
992 if (nobuf)
993 fseeko(f, fpos, SEEK_SET);
994 canjump = 1;
995 p = fgets(b, READBUF, f);
996 if (nobuf)
997 if ((fpos = ftello(f)) == -1)
998 warn("%s", name);
999 canjump = 0;
1000
1001 if (p == NULL || *b == '\0') {
1002 if (ferror(f))
1003 warn("%s", name);
1004 eofline = fline;
1005 eof = 1;
1006 break;
1007 }
1008
1009 if (!nobuf)
1010 fputs(b, fbuf);
1011 fwrite_all(&pos, sizeof pos, 1, find);
1012 if (!fflag) {
1013 oldpos = pos;
1014 p = b;
1015 while (*(p = endline(ttycols,
1016 p))
1017 != '\0') {
1018 pos = oldpos + (p - b);
1019 fwrite_all(&pos,
1020 sizeof pos,
1021 1, find);
1022 fline++;
1023 bline++;
1024 }
1025 }
1026 fline++;
1027 } while (line > bline++);
1028 } else {
1029 /* eofline != 0 */
1030 eof = 1;
1031 }
1032 if (search == FORWARD && remembered == 1) {
1033 if (eof) {
1034 line = oldline;
1035 search = searchcount = 0;
1036 mesg(_("Pattern not found"));
1037 eof = 0;
1038 goto newcmd;
1039 }
1040 line++;
1041 colb(b);
1042 if (regexec(&re, b, 0, NULL, 0) == 0) {
1043 searchcount--;
1044 }
1045 if (searchcount == 0) {
1046 search = dline = 0;
1047 switch (searchdisplay) {
1048 case TOP:
1049 line -= 1;
1050 break;
1051 case MIDDLE:
1052 line -= pagelen / 2 + 1;
1053 break;
1054 case BOTTOM:
1055 line -= pagelen;
1056 break;
1057 }
1058 skip(1);
1059 }
1060 continue;
1061 }
1062
1063 if (eof) {
1064 /* We are not searching. */
1065 line = bline;
1066 } else if (*b != '\0') {
1067 if (cflag && clear_screen) {
1068 switch (dline) {
1069 case 0:
1070 tputs(clear_screen, STDOUT_FILENO,
1071 outcap);
1072 dline = 0;
1073 }
1074 }
1075 line++;
1076 if (eofline && line == eofline)
1077 eof = 1;
1078 dline++;
1079 if ((sig = setjmp(jmpenv)) != 0) {
1080 /* We got a signal. */
1081 canjump = 0;
1082 my_sigrelse(sig);
1083 dline = pagelen;
1084 } else {
1085 p = endline(ttycols, b);
1086 sz = p - b;
1087 makeprint(b, sz);
1088 canjump = 1;
1089 write_all(STDOUT_FILENO, b, sz);
1090 canjump = 0;
1091 }
1092 }
1093 if (dline >= pagelen || eof) {
1094 /* Time for prompting! */
1095 if (eof && seekeof) {
1096 eof = seekeof = 0;
1097 if (line >= pagelen)
1098 line -= pagelen;
1099 else
1100 line = 0;
1101 dline = -1;
1102 continue;
1103 }
1104 newcmd:
1105 if (eof) {
1106 if (fline == 0 || eflag)
1107 break;
1108 mesg(_("(EOF)"));
1109 }
1110 prompt((line - 1) / pagelen + 1);
1111 switch (cmd.key) {
1112 case '/':
1113 /* Search forward. */
1114 search = FORWARD;
1115 oldline = line;
1116 searchcount = cmd.count;
1117 p = makepat();
1118 if (p != NULL && *p) {
1119 if (remembered == 1)
1120 regfree(&re);
1121 rerror = regcomp(&re, p,
1122 REG_NOSUB |
1123 REG_NEWLINE);
1124 if (rerror != 0) {
1125 mesg(_("RE error: "));
1126 sz = regerror(rerror, &re,
1127 b, READBUF);
1128 mesg(b);
1129 goto newcmd;
1130 }
1131 remembered = 1;
1132 } else if (remembered == 0) {
1133 mesg(_("No remembered search string"));
1134 goto newcmd;
1135 }
1136 continue;
1137 case '?':
1138 case '^':
1139 /* Search backward. */
1140 search = BACKWARD;
1141 oldline = line;
1142 searchcount = cmd.count;
1143 p = makepat();
1144 if (p != NULL && *p) {
1145 if (remembered == 1)
1146 regfree(&re);
1147 rerror = regcomp(&re, p,
1148 REG_NOSUB |
1149 REG_NEWLINE);
1150 if (rerror != 0) {
1151 mesg(_("RE error: "));
1152 regerror(rerror, &re,
1153 b, READBUF);
1154 mesg(b);
1155 goto newcmd;
1156 }
1157 remembered = 1;
1158 } else if (remembered == 0) {
1159 mesg(_("No remembered search string"));
1160 goto newcmd;
1161 }
1162 line -= pagelen;
1163 if (line <= 0)
1164 goto notfound_bw;
1165 while (line) {
1166 fseeko(find, --line * sizeof pos,
1167 SEEK_SET);
1168 if (fread(&pos, sizeof pos, 1, find) ==
1169 0)
1170 tmperr(find, "index");
1171 fseeko(find, (off_t)0, SEEK_END);
1172 fseeko(fbuf, pos, SEEK_SET);
1173 if (fgets(b, READBUF, fbuf) == NULL)
1174 tmperr(fbuf, "buffer");
1175 colb(b);
1176 if (regexec(&re, b, 0, NULL, 0) == 0)
1177 searchcount--;
1178 if (searchcount == 0)
1179 goto found_bw;
1180 }
1181 notfound_bw:
1182 line = oldline;
1183 search = searchcount = 0;
1184 mesg(_("Pattern not found"));
1185 goto newcmd;
1186 found_bw:
1187 eof = search = dline = 0;
1188 skip(-1);
1189 switch (searchdisplay) {
1190 case TOP:
1191 /* line -= 1; */
1192 break;
1193 case MIDDLE:
1194 line -= pagelen / 2;
1195 break;
1196 case BOTTOM:
1197 if (line != 0)
1198 dline = -1;
1199 line -= pagelen;
1200 break;
1201 }
1202 if (line < 0)
1203 line = 0;
1204 continue;
1205 case 's':
1206 /* Save to file. */
1207 p = cmd.cmdline;
1208 while (*++p == ' ') ;
1209 if (*p == '\0')
1210 goto newcmd;
1211 save = fopen(p, "wb");
1212 if (save == NULL) {
1213 cmd.count = errno;
1214 mesg(_("cannot open "));
1215 mesg(p);
1216 mesg(": ");
1217 mesg(strerror(cmd.count));
1218 goto newcmd;
1219 }
1220 /* Advance to EOF. */
1221 fseeko(find, (off_t)0, SEEK_END);
1222 for (;;) {
1223 if (!nobuf)
1224 fseeko(fbuf, (off_t)0,
1225 SEEK_END);
1226 pos = ftello(fbuf);
1227 if (fgets(b, READBUF, f) == NULL) {
1228 eofline = fline;
1229 break;
1230 }
1231 if (!nobuf)
1232 fputs(b, fbuf);
1233 fwrite_all(&pos, sizeof pos, 1, find);
1234 if (!fflag) {
1235 oldpos = pos;
1236 p = b;
1237 while (*(p = endline(ttycols,
1238 p))
1239 != '\0') {
1240 pos = oldpos + (p - b);
1241 fwrite_all(&pos,
1242 sizeof pos,
1243 1, find);
1244 fline++;
1245 bline++;
1246 }
1247 }
1248 fline++;
1249 bline++;
1250 }
1251 fseeko(fbuf, (off_t)0, SEEK_SET);
1252 while ((sz = fread(b, sizeof *b, READBUF,
1253 fbuf)) != 0) {
1254 /* No error check for compat. */
1255 fwrite_all(b, sizeof *b, sz, save);
1256 }
1257 if (close_stream(save) != 0) {
1258 cmd.count = errno;
1259 mesg(_("write failed"));
1260 mesg(": ");
1261 mesg(p);
1262 mesg(strerror(cmd.count));
1263 goto newcmd;
1264 }
1265 fseeko(fbuf, (off_t)0, SEEK_END);
1266 mesg(_("saved"));
1267 goto newcmd;
1268 case 'l':
1269 /* Next line. */
1270 if (*cmd.cmdline != 'l')
1271 eof = 0;
1272 if (cmd.count == 0)
1273 cmd.count = 1; /* compat */
1274 if (isdigit(cuc(*cmd.cmdline))) {
1275 line = cmd.count - 2;
1276 dline = 0;
1277 } else {
1278 if (cmd.count != 1) {
1279 line += cmd.count - 1 - pagelen;
1280 dline = -1;
1281 skip(cmd.count);
1282 }
1283 /* Nothing to do if (count == 1) */
1284 }
1285 break;
1286 case 'd':
1287 /* Half screen forward. */
1288 case '\004': /* ^D */
1289 if (*cmd.cmdline != cmd.key)
1290 eof = 0;
1291 if (cmd.count == 0)
1292 cmd.count = 1; /* compat */
1293 line += (cmd.count * pagelen / 2)
1294 - pagelen - 1;
1295 dline = -1;
1296 skip(cmd.count);
1297 break;
1298 case 'f':
1299 /* Skip forward. */
1300 if (cmd.count <= 0)
1301 cmd.count = 1; /* compat */
1302 line += cmd.count * pagelen - 2;
1303 if (eof)
1304 line += 2;
1305 if (*cmd.cmdline != 'f')
1306 eof = 0;
1307 else if (eof)
1308 break;
1309 if (eofline && line >= eofline)
1310 line -= pagelen;
1311 dline = -1;
1312 skip(cmd.count);
1313 break;
1314 case '\0':
1315 /* Just a number, or '-', or <newline>. */
1316 if (cmd.count == 0)
1317 cmd.count = 1; /* compat */
1318 if (isdigit(cuc(*cmd.cmdline)))
1319 line = (cmd.count - 1) * pagelen - 2;
1320 else
1321 line += (cmd.count - 1)
1322 * (pagelen - 1) - 2;
1323 if (*cmd.cmdline != '\0')
1324 eof = 0;
1325 if (cmd.count != 1) {
1326 skip(cmd.count);
1327 dline = -1;
1328 } else {
1329 dline = 1;
1330 line += 2;
1331 }
1332 break;
1333 case '$':
1334 /* Advance to EOF. */
1335 if (!eof)
1336 skip(1);
1337 eof = 0;
1338 line = LONG_MAX;
1339 seekeof = 1;
1340 dline = -1;
1341 break;
1342 case '.':
1343 case '\014': /* ^L */
1344 /* Repaint screen. */
1345 eof = 0;
1346 if (line >= pagelen)
1347 line -= pagelen;
1348 else
1349 line = 0;
1350 dline = 0;
1351 break;
1352 case '!':
1353 /* Shell escape. */
1354 if (rflag) {
1355 mesg(program_invocation_short_name);
1356 mesg(_(": !command not allowed in "
1357 "rflag mode.\n"));
1358 } else {
1359 pid_t cpid;
1360
1361 write_all(STDOUT_FILENO, cmd.cmdline,
1362 strlen(cmd.cmdline));
1363 write_all(STDOUT_FILENO, "\n", 1);
1364 my_sigset(SIGINT, SIG_IGN);
1365 my_sigset(SIGQUIT, SIG_IGN);
1366 switch (cpid = fork()) {
1367 case 0:
1368 {
1369 const char *sh = getenv("SHELL");
1370 if (!sh)
1371 sh = "/bin/sh";
1372 if (!nobuf)
1373 fclose(fbuf);
1374 fclose(find);
1375 if (isatty(0) == 0) {
1376 close(0);
1377 open(tty, O_RDONLY);
1378 } else {
1379 fclose(f);
1380 }
1381 my_sigset(SIGINT, oldint);
1382 my_sigset(SIGQUIT, oldquit);
1383 my_sigset(SIGTERM, oldterm);
1384 execl(sh, sh, "-c",
1385 cmd.cmdline + 1, NULL);
1386 errexec(sh);
1387 break;
1388 }
1389 case -1:
1390 mesg(_("fork() failed, "
1391 "try again later\n"));
1392 break;
1393 default:
1394 while (wait(NULL) != cpid) ;
1395 }
1396 my_sigset(SIGINT, sighandler);
1397 my_sigset(SIGQUIT, sighandler);
1398 mesg("!\n");
1399 }
1400 goto newcmd;
1401 case 'h':
1402 {
1403 /* Help! */
1404 const char *help = _(helpscreen);
1405 write_all(STDOUT_FILENO, copyright,
1406 strlen(copyright));
1407 write_all(STDOUT_FILENO, help,
1408 strlen(help));
1409 goto newcmd;
1410 }
1411 case 'n':
1412 /* Next file. */
1413 if (cmd.count == 0)
1414 cmd.count = 1;
1415 nextfile = cmd.count;
1416 if (checkf()) {
1417 nextfile = 1;
1418 goto newcmd;
1419 }
1420 eof = 1;
1421 break;
1422 case 'p':
1423 /* Previous file. */
1424 if (cmd.count == 0)
1425 cmd.count = 1;
1426 nextfile = 0 - cmd.count;
1427 if (checkf()) {
1428 nextfile = 1;
1429 goto newcmd;
1430 }
1431 eof = 1;
1432 break;
1433 case 'q':
1434 case 'Q':
1435 /* Exit pg. */
1436 quit(exitstatus);
1437 /* NOTREACHED */
1438 case 'w':
1439 case 'z':
1440 /* Set window size. */
1441 if (cmd.count < 0)
1442 cmd.count = 0;
1443 if (*cmd.cmdline != cmd.key)
1444 pagelen = ++cmd.count;
1445 dline = 1;
1446 break;
1447 }
1448 if (line <= 0) {
1449 line = 0;
1450 dline = 0;
1451 }
1452 if (cflag && dline == 1) {
1453 dline = 0;
1454 line--;
1455 }
1456 }
1457 if (eof)
1458 break;
1459 }
1460 fclose(find);
1461 if (!nobuf)
1462 fclose(fbuf);
1463 }
1464
1465 static int parse_arguments(int arg, int argc, char **argv)
1466 {
1467 FILE *input;
1468
1469 files.first = arg;
1470 files.last = arg + argc - 1;
1471 for (; argv[arg]; arg += nextfile) {
1472 nextfile = 1;
1473 files.current = arg;
1474 if (argc > 2) {
1475 static int firsttime;
1476 firsttime++;
1477 if (firsttime > 1) {
1478 mesg(_("(Next file: "));
1479 mesg(argv[arg]);
1480 mesg(")");
1481 newfile:
1482 if (ontty) {
1483 prompt(-1);
1484 switch (cmd.key) {
1485 case 'n':
1486 /* Next file. */
1487 if (cmd.count == 0)
1488 cmd.count = 1;
1489 nextfile = cmd.count;
1490 if (checkf()) {
1491 nextfile = 1;
1492 mesg(":");
1493 goto newfile;
1494 }
1495 continue;
1496 case 'p':
1497 /* Previous file. */
1498 if (cmd.count == 0)
1499 cmd.count = 1;
1500 nextfile = 0 - cmd.count;
1501 if (checkf()) {
1502 nextfile = 1;
1503 mesg(":");
1504 goto newfile;
1505 }
1506 continue;
1507 case 'q':
1508 case 'Q':
1509 quit(exitstatus);
1510 }
1511 } else
1512 mesg("\n");
1513 }
1514 }
1515 if (strcmp(argv[arg], "-") == 0)
1516 input = stdin;
1517 else {
1518 input = fopen(argv[arg], "r");
1519 if (input == NULL) {
1520 warn("%s", argv[arg]);
1521 exitstatus++;
1522 continue;
1523 }
1524 }
1525 if (ontty == 0 && argc > 2) {
1526 /* Use the prefix as specified by SUSv2. */
1527 write_all(STDOUT_FILENO, "::::::::::::::\n", 15);
1528 write_all(STDOUT_FILENO, argv[arg], strlen(argv[arg]));
1529 write_all(STDOUT_FILENO, "\n::::::::::::::\n", 16);
1530 }
1531 pgfile(input, argv[arg]);
1532 if (input != stdin)
1533 fclose(input);
1534 }
1535 return exitstatus;
1536 }
1537
1538 int main(int argc, char **argv)
1539 {
1540 int arg, i;
1541 char *p;
1542
1543 xasprintf(&copyright,
1544 _("%s %s Copyright (c) 2000-2001 Gunnar Ritter. All rights reserved.\n"),
1545 program_invocation_short_name, PACKAGE_VERSION);
1546
1547 setlocale(LC_ALL, "");
1548 bindtextdomain(PACKAGE, LOCALEDIR);
1549 textdomain(PACKAGE);
1550 close_stdout_atexit();
1551
1552 if (tcgetattr(STDOUT_FILENO, &otio) == 0) {
1553 ontty = 1;
1554 oldint = my_sigset(SIGINT, sighandler);
1555 oldquit = my_sigset(SIGQUIT, sighandler);
1556 oldterm = my_sigset(SIGTERM, sighandler);
1557 setlocale(LC_CTYPE, "");
1558 setlocale(LC_COLLATE, "");
1559 tty = ttyname(STDOUT_FILENO);
1560 setupterm(NULL, STDOUT_FILENO, &tinfostat);
1561 getwinsize();
1562 helpscreen = _(helpscreen);
1563 }
1564 for (arg = 1; argv[arg]; arg++) {
1565 if (*argv[arg] == '+')
1566 continue;
1567 if (*argv[arg] != '-' || argv[arg][1] == '\0')
1568 break;
1569 argc--;
1570
1571 if (!strcmp(argv[arg], "--help")) {
1572 usage();
1573 }
1574
1575 if (!strcmp(argv[arg], "--version")) {
1576 print_version(EXIT_SUCCESS);
1577 return EXIT_SUCCESS;
1578 }
1579
1580 for (i = 1; argv[arg][i]; i++) {
1581 switch (argv[arg][i]) {
1582 case '-':
1583 if (i != 1 || argv[arg][i + 1])
1584 invopt(&argv[arg][i]);
1585 goto endargs;
1586 case '1':
1587 case '2':
1588 case '3':
1589 case '4':
1590 case '5':
1591 case '6':
1592 case '7':
1593 case '8':
1594 case '9':
1595 case '0':
1596 pagelen = strtol_or_err(argv[arg] + 1,
1597 _("failed to parse number of lines per page"));
1598 havepagelen = 1;
1599 goto nextarg;
1600 case 'c':
1601 cflag = 1;
1602 break;
1603 case 'e':
1604 eflag = 1;
1605 break;
1606 case 'f':
1607 fflag = 1;
1608 break;
1609 case 'n':
1610 nflag = 1;
1611 break;
1612 case 'p':
1613 if (argv[arg][i + 1]) {
1614 pstring = &argv[arg][i + 1];
1615 } else if (argv[++arg]) {
1616 --argc;
1617 pstring = argv[arg];
1618 } else
1619 needarg("-p");
1620 goto nextarg;
1621 case 'r':
1622 rflag = 1;
1623 break;
1624 case 's':
1625 sflag = 1;
1626 break;
1627
1628 case 'h':
1629 usage();
1630 case 'V':
1631 print_version(EXIT_SUCCESS);
1632 default:
1633 invopt(&argv[arg][i]);
1634 }
1635 }
1636 nextarg:
1637 ;
1638 }
1639 endargs:
1640 for (arg = 1; argv[arg]; arg++) {
1641 if (*argv[arg] == '-') {
1642 if (argv[arg][1] == '-') {
1643 arg++;
1644 break;
1645 }
1646 if (argv[arg][1] == '\0')
1647 break;
1648 if (argv[arg][1] == 'p' && argv[arg][2] == '\0')
1649 arg++;
1650 continue;
1651 }
1652 if (*argv[arg] != '+')
1653 break;
1654 argc--;
1655 switch (*(argv[arg] + 1)) {
1656 case '\0':
1657 needarg("+");
1658 /*NOTREACHED*/
1659 case '1':
1660 case '2':
1661 case '3':
1662 case '4':
1663 case '5':
1664 case '6':
1665 case '7':
1666 case '8':
1667 case '9':
1668 case '0':
1669 startline = strtol_or_err(argv[arg] + 1,
1670 _("failed to parse number of lines per page"));
1671 break;
1672 case '/':
1673 searchfor = argv[arg] + 2;
1674 if (*searchfor == '\0')
1675 needarg("+/");
1676 p = searchfor + strlen(searchfor) - 1;
1677 if (*p == '/')
1678 *p = '\0';
1679 if (*searchfor == '\0')
1680 needarg("+/");
1681 break;
1682 default:
1683 invopt(argv[arg]);
1684 }
1685 }
1686 if (argc == 1)
1687 pgfile(stdin, "stdin");
1688 else
1689 exitstatus = parse_arguments(arg, argc, argv);
1690
1691 quit(exitstatus);
1692 /* NOTREACHED */
1693 return 0;
1694 }