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