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