]>
Commit | Line | Data |
---|---|---|
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 | ||
75 | enum { FORWARD = 1, BACKWARD = 2 }; /* search direction */ | |
76 | enum { TOP, MIDDLE, BOTTOM }; /* position of matching line */ | |
77 | ||
bb7ae5bf | 78 | /* States for syntax-aware command line editor. */ |
63cccae4 KZ |
79 | enum { |
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 |
91 | struct { |
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 |
101 | struct { |
102 | int first; | |
103 | int current; | |
104 | int last; | |
105 | } files; | |
106 | ||
bb7ae5bf SK |
107 | void (*oldint) (int); /* old SIGINT handler */ |
108 | void (*oldquit) (int); /* old SIGQUIT handler */ | |
109 | void (*oldterm) (int); /* old SIGTERM handler */ | |
110 | char *tty; /* result of ttyname(1) */ | |
111 | unsigned ontty; /* whether running on tty device */ | |
112 | unsigned exitstatus; /* exit status */ | |
113 | int pagelen = 23; /* lines on a single screen page */ | |
114 | int ttycols = 79; /* screen columns (starting at 0) */ | |
115 | struct termios otio; /* old termios settings */ | |
116 | int tinfostat = -1; /* terminfo routines initialized */ | |
117 | int searchdisplay = TOP; /* matching line position */ | |
118 | regex_t re; /* regular expression to search for */ | |
119 | int remembered; /* have a remembered search string */ | |
120 | int cflag; /* clear screen before each page */ | |
121 | int eflag; /* suppress (EOF) */ | |
122 | int fflag; /* do not split lines */ | |
123 | int nflag; /* no newline for commands required */ | |
124 | int rflag; /* "restricted" pg */ | |
125 | int sflag; /* use standout mode */ | |
126 | const char *pstring = ":"; /* prompt string */ | |
127 | char *searchfor; /* search pattern from argv[] */ | |
128 | int havepagelen; /* page length is manually defined */ | |
129 | long startline; /* start line from argv[] */ | |
130 | int nextfile = 1; /* files to advance */ | |
131 | jmp_buf jmpenv; /* jump from signal handlers */ | |
132 | int canjump; /* jmpenv is valid */ | |
133 | wchar_t wbuf[READBUF]; /* used in several widechar routines */ | |
63cccae4 | 134 | |
b6415f12 SK |
135 | char *copyright; |
136 | const 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\ | |
154 | Many commands accept preceding numbers, for example:\n\ | |
155 | +1<newline> (next page); -1<newline> (previous page); 1<newline> (first page).\n\ | |
156 | \n\ | |
157 | See pg(1) for more information.\n\ | |
7c7f4ff8 | 158 | -------------------------------------------------------\n"); |
63cccae4 | 159 | |
48d7b13a | 160 | #ifndef HAVE_FSEEKO |
bb7ae5bf SK |
161 | static int fseeko(FILE *f, off_t off, int whence) |
162 | { | |
163 | return fseek(f, (long)off, whence); | |
164 | } | |
165 | ||
166 | static 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 |
177 | static 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 | |
186 | typedef void (*my_sighandler_t) (int); | |
187 | static 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. */ |
204 | static 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. */ |
210 | static 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 | 233 | static 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 | 239 | static 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. */ | |
248 | static 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(). */ |
271 | static 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. */ |
278 | static 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. */ |
290 | static 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. */ |
341 | static 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. */ |
350 | static 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. */ |
359 | static 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. */ | |
375 | static 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. */ |
440 | static 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. */ |
490 | static 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. */ |
501 | static 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. */ |
555 | static 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. */ | |
590 | static 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. */ |
742 | static 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. */ |
769 | static 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. */ |
794 | static 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. */ |
814 | static 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. */ |
832 | static 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. */ |
845 | static 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. */ |
865 | static 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. */ |
878 | static 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 | 1433 | static 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 | 1506 | int main(int argc, char **argv) |
63cccae4 KZ |
1507 | { |
1508 | int arg, i; | |
1509 | char *p; | |
63cccae4 | 1510 | |
b6415f12 SK |
1511 | xasprintf(©right, |
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 | } |