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