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