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