busybox/miscutils/less.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini less implementation for busybox
   4 *
   5 * Copyright (C) 2005 by Rob Sullivan <cogito.ergo.cogito@gmail.com>
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   8 */
   9
  10/*
  11 * TODO:
  12 * - Add more regular expression support - search modifiers, certain matches, etc.
  13 * - Add more complex bracket searching - currently, nested brackets are
  14 *   not considered.
  15 * - Add support for "F" as an input. This causes less to act in
  16 *   a similar way to tail -f.
  17 * - Allow horizontal scrolling.
  18 *
  19 * Notes:
  20 * - the inp file pointer is used so that keyboard input works after
  21 *   redirected input has been read from stdin
  22 */
  23
  24//config:config LESS
  25//config:       bool "less"
  26//config:       default y
  27//config:       help
  28//config:         'less' is a pager, meaning that it displays text files. It possesses
  29//config:         a wide array of features, and is an improvement over 'more'.
  30//config:
  31//config:config FEATURE_LESS_MAXLINES
  32//config:       int "Max number of input lines less will try to eat"
  33//config:       default 9999999
  34//config:       depends on LESS
  35//config:
  36//config:config FEATURE_LESS_BRACKETS
  37//config:       bool "Enable bracket searching"
  38//config:       default y
  39//config:       depends on LESS
  40//config:       help
  41//config:         This option adds the capability to search for matching left and right
  42//config:         brackets, facilitating programming.
  43//config:
  44//config:config FEATURE_LESS_FLAGS
  45//config:       bool "Enable -m/-M"
  46//config:       default y
  47//config:       depends on LESS
  48//config:       help
  49//config:         The -M/-m flag enables a more sophisticated status line.
  50//config:
  51//config:config FEATURE_LESS_TRUNCATE
  52//config:       bool "Enable -S"
  53//config:       default y
  54//config:       depends on LESS
  55//config:       help
  56//config:         The -S flag causes long lines to be truncated rather than
  57//config:         wrapped.
  58//config:
  59//config:config FEATURE_LESS_MARKS
  60//config:       bool "Enable marks"
  61//config:       default y
  62//config:       depends on LESS
  63//config:       help
  64//config:         Marks enable positions in a file to be stored for easy reference.
  65//config:
  66//config:config FEATURE_LESS_REGEXP
  67//config:       bool "Enable regular expressions"
  68//config:       default y
  69//config:       depends on LESS
  70//config:       help
  71//config:         Enable regular expressions, allowing complex file searches.
  72//config:
  73//config:config FEATURE_LESS_WINCH
  74//config:       bool "Enable automatic resizing on window size changes"
  75//config:       default y
  76//config:       depends on LESS
  77//config:       help
  78//config:         Makes less track window size changes.
  79//config:
  80//config:config FEATURE_LESS_ASK_TERMINAL
  81//config:       bool "Use 'tell me cursor position' ESC sequence to measure window"
  82//config:       default y
  83//config:       depends on FEATURE_LESS_WINCH
  84//config:       help
  85//config:         Makes less track window size changes.
  86//config:         If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
  87//config:         this option makes less perform a last-ditch effort to find it:
  88//config:         position cursor to 999,999 and ask terminal to report real
  89//config:         cursor position using "ESC [ 6 n" escape sequence, then read stdin.
  90//config:
  91//config:         This is not clean but helps a lot on serial lines and such.
  92//config:
  93//config:config FEATURE_LESS_DASHCMD
  94//config:       bool "Enable flag changes ('-' command)"
  95//config:       default y
  96//config:       depends on LESS
  97//config:       help
  98//config:         This enables the ability to change command-line flags within
  99//config:         less itself ('-' keyboard command).
 100//config:
 101//config:config FEATURE_LESS_LINENUMS
 102//config:       bool "Enable dynamic switching of line numbers"
 103//config:       default y
 104//config:       depends on FEATURE_LESS_DASHCMD
 105//config:       help
 106//config:         Enables "-N" command.
 107
 108//usage:#define less_trivial_usage
 109//usage:       "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm")
 110//usage:       "N" IF_FEATURE_LESS_TRUNCATE("S") "h~] [FILE]..."
 111//usage:#define less_full_usage "\n\n"
 112//usage:       "View FILE (or stdin) one screenful at a time\n"
 113//usage:     "\n        -E      Quit once the end of a file is reached"
 114//usage:        IF_FEATURE_LESS_REGEXP(
 115//usage:     "\n        -I      Ignore case in all searches"
 116//usage:        )
 117//usage:        IF_FEATURE_LESS_FLAGS(
 118//usage:     "\n        -M,-m   Display status line with line numbers"
 119//usage:     "\n                and percentage through the file"
 120//usage:        )
 121//usage:     "\n        -N      Prefix line number to each line"
 122//usage:        IF_FEATURE_LESS_TRUNCATE(
 123//usage:     "\n        -S      Truncate long lines"
 124//usage:        )
 125//usage:     "\n        -~      Suppress ~s displayed past EOF"
 126
 127#include <sched.h>  /* sched_yield() */
 128
 129#include "libbb.h"
 130#if ENABLE_FEATURE_LESS_REGEXP
 131#include "xregex.h"
 132#endif
 133
 134
 135#define ESC "\033"
 136/* The escape codes for highlighted and normal text */
 137#define HIGHLIGHT   ESC"[7m"
 138#define NORMAL      ESC"[0m"
 139/* The escape code to home and clear to the end of screen */
 140#define CLEAR       ESC"[H\033[J"
 141/* The escape code to clear to the end of line */
 142#define CLEAR_2_EOL ESC"[K"
 143
 144enum {
 145/* Absolute max of lines eaten */
 146        MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
 147/* This many "after the end" lines we will show (at max) */
 148        TILDES = 1,
 149};
 150
 151/* Command line options */
 152enum {
 153        FLAG_E = 1 << 0,
 154        FLAG_M = 1 << 1,
 155        FLAG_m = 1 << 2,
 156        FLAG_N = 1 << 3,
 157        FLAG_TILDE = 1 << 4,
 158        FLAG_I = 1 << 5,
 159        FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE,
 160/* hijack command line options variable for internal state vars */
 161        LESS_STATE_MATCH_BACKWARDS = 1 << 15,
 162};
 163
 164#if !ENABLE_FEATURE_LESS_REGEXP
 165enum { pattern_valid = 0 };
 166#endif
 167
 168struct globals {
 169        int cur_fline; /* signed */
 170        int kbd_fd;  /* fd to get input from */
 171        int less_gets_pos;
 172/* last position in last line, taking into account tabs */
 173        size_t last_line_pos;
 174        unsigned max_fline;
 175        unsigned max_lineno; /* this one tracks linewrap */
 176        unsigned max_displayed_line;
 177        unsigned width;
 178#if ENABLE_FEATURE_LESS_WINCH
 179        unsigned winch_counter;
 180#endif
 181        ssize_t eof_error; /* eof if 0, error if < 0 */
 182        ssize_t readpos;
 183        ssize_t readeof; /* must be signed */
 184        const char **buffer;
 185        const char **flines;
 186        const char *empty_line_marker;
 187        unsigned num_files;
 188        unsigned current_file;
 189        char *filename;
 190        char **files;
 191#if ENABLE_FEATURE_LESS_FLAGS
 192        int num_lines; /* a flag if < 0, line count if >= 0 */
 193# define REOPEN_AND_COUNT (-1)
 194# define REOPEN_STDIN     (-2)
 195# define NOT_REGULAR_FILE (-3)
 196#endif
 197#if ENABLE_FEATURE_LESS_MARKS
 198        unsigned num_marks;
 199        unsigned mark_lines[15][2];
 200#endif
 201#if ENABLE_FEATURE_LESS_REGEXP
 202        unsigned *match_lines;
 203        int match_pos; /* signed! */
 204        int wanted_match; /* signed! */
 205        int num_matches;
 206        regex_t pattern;
 207        smallint pattern_valid;
 208#endif
 209#if ENABLE_FEATURE_LESS_ASK_TERMINAL
 210        smallint winsize_err;
 211#endif
 212        smallint terminated;
 213        struct termios term_orig, term_less;
 214        char kbd_input[KEYCODE_BUFFER_SIZE];
 215};
 216#define G (*ptr_to_globals)
 217#define cur_fline           (G.cur_fline         )
 218#define kbd_fd              (G.kbd_fd            )
 219#define less_gets_pos       (G.less_gets_pos     )
 220#define last_line_pos       (G.last_line_pos     )
 221#define max_fline           (G.max_fline         )
 222#define max_lineno          (G.max_lineno        )
 223#define max_displayed_line  (G.max_displayed_line)
 224#define width               (G.width             )
 225#define winch_counter       (G.winch_counter     )
 226/* This one is 100% not cached by compiler on read access */
 227#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
 228#define eof_error           (G.eof_error         )
 229#define readpos             (G.readpos           )
 230#define readeof             (G.readeof           )
 231#define buffer              (G.buffer            )
 232#define flines              (G.flines            )
 233#define empty_line_marker   (G.empty_line_marker )
 234#define num_files           (G.num_files         )
 235#define current_file        (G.current_file      )
 236#define filename            (G.filename          )
 237#define files               (G.files             )
 238#define num_lines           (G.num_lines         )
 239#define num_marks           (G.num_marks         )
 240#define mark_lines          (G.mark_lines        )
 241#if ENABLE_FEATURE_LESS_REGEXP
 242#define match_lines         (G.match_lines       )
 243#define match_pos           (G.match_pos         )
 244#define num_matches         (G.num_matches       )
 245#define wanted_match        (G.wanted_match      )
 246#define pattern             (G.pattern           )
 247#define pattern_valid       (G.pattern_valid     )
 248#endif
 249#define terminated          (G.terminated        )
 250#define term_orig           (G.term_orig         )
 251#define term_less           (G.term_less         )
 252#define kbd_input           (G.kbd_input         )
 253#define INIT_G() do { \
 254        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 255        less_gets_pos = -1; \
 256        empty_line_marker = "~"; \
 257        num_files = 1; \
 258        current_file = 1; \
 259        eof_error = 1; \
 260        terminated = 1; \
 261        IF_FEATURE_LESS_REGEXP(wanted_match = -1;) \
 262} while (0)
 263
 264/* flines[] are lines read from stdin, each in malloc'ed buffer.
 265 * Line numbers are stored as uint32_t prepended to each line.
 266 * Pointer is adjusted so that flines[i] points directly past
 267 * line number. Accesor: */
 268#define MEMPTR(p) ((char*)(p) - 4)
 269#define LINENO(p) (*(uint32_t*)((p) - 4))
 270
 271
 272/* Reset terminal input to normal */
 273static void set_tty_cooked(void)
 274{
 275        fflush_all();
 276        tcsetattr(kbd_fd, TCSANOW, &term_orig);
 277}
 278
 279/* Move the cursor to a position (x,y), where (0,0) is the
 280   top-left corner of the console */
 281static void move_cursor(int line, int row)
 282{
 283        printf(ESC"[%u;%uH", line, row);
 284}
 285
 286static void clear_line(void)
 287{
 288        printf(ESC"[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
 289}
 290
 291static void print_hilite(const char *str)
 292{
 293        printf(HIGHLIGHT"%s"NORMAL, str);
 294}
 295
 296static void print_statusline(const char *str)
 297{
 298        clear_line();
 299        printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
 300}
 301
 302/* Exit the program gracefully */
 303static void less_exit(int code)
 304{
 305        set_tty_cooked();
 306        clear_line();
 307        if (code < 0)
 308                kill_myself_with_sig(- code); /* does not return */
 309        exit(code);
 310}
 311
 312#if (ENABLE_FEATURE_LESS_DASHCMD && ENABLE_FEATURE_LESS_LINENUMS) \
 313 || ENABLE_FEATURE_LESS_WINCH
 314static void re_wrap(void)
 315{
 316        int w = width;
 317        int new_line_pos;
 318        int src_idx;
 319        int dst_idx;
 320        int new_cur_fline = 0;
 321        uint32_t lineno;
 322        char linebuf[w + 1];
 323        const char **old_flines = flines;
 324        const char *s;
 325        char **new_flines = NULL;
 326        char *d;
 327
 328        if (option_mask32 & FLAG_N)
 329                w -= 8;
 330
 331        src_idx = 0;
 332        dst_idx = 0;
 333        s = old_flines[0];
 334        lineno = LINENO(s);
 335        d = linebuf;
 336        new_line_pos = 0;
 337        while (1) {
 338                *d = *s;
 339                if (*d != '\0') {
 340                        new_line_pos++;
 341                        if (*d == '\t') { /* tab */
 342                                new_line_pos += 7;
 343                                new_line_pos &= (~7);
 344                        }
 345                        s++;
 346                        d++;
 347                        if (new_line_pos >= w) {
 348                                int sz;
 349                                /* new line is full, create next one */
 350                                *d = '\0';
 351 next_new:
 352                                sz = (d - linebuf) + 1; /* + 1: NUL */
 353                                d = ((char*)xmalloc(sz + 4)) + 4;
 354                                LINENO(d) = lineno;
 355                                memcpy(d, linebuf, sz);
 356                                new_flines = xrealloc_vector(new_flines, 8, dst_idx);
 357                                new_flines[dst_idx] = d;
 358                                dst_idx++;
 359                                if (new_line_pos < w) {
 360                                        /* if we came here thru "goto next_new" */
 361                                        if (src_idx > max_fline)
 362                                                break;
 363                                        lineno = LINENO(s);
 364                                }
 365                                d = linebuf;
 366                                new_line_pos = 0;
 367                        }
 368                        continue;
 369                }
 370                /* *d == NUL: old line ended, go to next old one */
 371                free(MEMPTR(old_flines[src_idx]));
 372                /* btw, convert cur_fline... */
 373                if (cur_fline == src_idx)
 374                        new_cur_fline = dst_idx;
 375                src_idx++;
 376                /* no more lines? finish last new line (and exit the loop) */
 377                if (src_idx > max_fline)
 378                        goto next_new;
 379                s = old_flines[src_idx];
 380                if (lineno != LINENO(s)) {
 381                        /* this is not a continuation line!
 382                         * create next _new_ line too */
 383                        goto next_new;
 384                }
 385        }
 386
 387        free(old_flines);
 388        flines = (const char **)new_flines;
 389
 390        max_fline = dst_idx - 1;
 391        last_line_pos = new_line_pos;
 392        cur_fline = new_cur_fline;
 393        /* max_lineno is screen-size independent */
 394#if ENABLE_FEATURE_LESS_REGEXP
 395        pattern_valid = 0;
 396#endif
 397}
 398#endif
 399
 400#if ENABLE_FEATURE_LESS_REGEXP
 401static void fill_match_lines(unsigned pos);
 402#else
 403#define fill_match_lines(pos) ((void)0)
 404#endif
 405
 406static int at_end(void)
 407{
 408        return (option_mask32 & FLAG_S)
 409                ? !(cur_fline <= max_fline &&
 410                        max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
 411                : !(max_fline > cur_fline + max_displayed_line);
 412}
 413
 414/* Devilishly complex routine.
 415 *
 416 * Has to deal with EOF and EPIPE on input,
 417 * with line wrapping, with last line not ending in '\n'
 418 * (possibly not ending YET!), with backspace and tabs.
 419 * It reads input again if last time we got an EOF (thus supporting
 420 * growing files) or EPIPE (watching output of slow process like make).
 421 *
 422 * Variables used:
 423 * flines[] - array of lines already read. Linewrap may cause
 424 *      one source file line to occupy several flines[n].
 425 * flines[max_fline] - last line, possibly incomplete.
 426 * terminated - 1 if flines[max_fline] is 'terminated'
 427 *      (if there was '\n' [which isn't stored itself, we just remember
 428 *      that it was seen])
 429 * max_lineno - last line's number, this one doesn't increment
 430 *      on line wrap, only on "real" new lines.
 431 * readbuf[0..readeof-1] - small preliminary buffer.
 432 * readbuf[readpos] - next character to add to current line.
 433 * last_line_pos - screen line position of next char to be read
 434 *      (takes into account tabs and backspaces)
 435 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
 436 *
 437 * "git log -p | less -m" on the kernel git tree is a good test for EAGAINs,
 438 * "/search on very long input" and "reaching max line count" corner cases.
 439 */
 440static void read_lines(void)
 441{
 442#define readbuf bb_common_bufsiz1
 443        char *current_line, *p;
 444        int w = width;
 445        char last_terminated = terminated;
 446        time_t last_time = 0;
 447        int retry_EAGAIN = 2;
 448#if ENABLE_FEATURE_LESS_REGEXP
 449        unsigned old_max_fline = max_fline;
 450#endif
 451
 452        /* (careful: max_fline can be -1) */
 453        if (max_fline + 1 > MAXLINES)
 454                return;
 455
 456        if (option_mask32 & FLAG_N)
 457                w -= 8;
 458
 459        p = current_line = ((char*)xmalloc(w + 5)) + 4;
 460        if (!last_terminated) {
 461                const char *cp = flines[max_fline];
 462                p = stpcpy(p, cp);
 463                free(MEMPTR(cp));
 464                /* last_line_pos is still valid from previous read_lines() */
 465        } else {
 466                max_fline++;
 467                last_line_pos = 0;
 468        }
 469
 470        while (1) { /* read lines until we reach cur_fline or wanted_match */
 471                *p = '\0';
 472                terminated = 0;
 473                while (1) { /* read chars until we have a line */
 474                        char c;
 475                        /* if no unprocessed chars left, eat more */
 476                        if (readpos >= readeof) {
 477                                int flags = ndelay_on(0);
 478
 479                                while (1) {
 480                                        time_t t;
 481
 482                                        errno = 0;
 483                                        eof_error = safe_read(STDIN_FILENO, readbuf, sizeof(readbuf));
 484                                        if (errno != EAGAIN)
 485                                                break;
 486                                        t = time(NULL);
 487                                        if (t != last_time) {
 488                                                last_time = t;
 489                                                if (--retry_EAGAIN < 0)
 490                                                        break;
 491                                        }
 492                                        sched_yield();
 493                                }
 494                                fcntl(0, F_SETFL, flags); /* ndelay_off(0) */
 495                                readpos = 0;
 496                                readeof = eof_error;
 497                                if (eof_error <= 0)
 498                                        goto reached_eof;
 499                                retry_EAGAIN = 1;
 500                        }
 501                        c = readbuf[readpos];
 502                        /* backspace? [needed for manpages] */
 503                        /* <tab><bs> is (a) insane and */
 504                        /* (b) harder to do correctly, so we refuse to do it */
 505                        if (c == '\x8' && last_line_pos && p[-1] != '\t') {
 506                                readpos++; /* eat it */
 507                                last_line_pos--;
 508                        /* was buggy (p could end up <= current_line)... */
 509                                *--p = '\0';
 510                                continue;
 511                        }
 512                        {
 513                                size_t new_last_line_pos = last_line_pos + 1;
 514                                if (c == '\t') {
 515                                        new_last_line_pos += 7;
 516                                        new_last_line_pos &= (~7);
 517                                }
 518                                if ((int)new_last_line_pos > w)
 519                                        break;
 520                                last_line_pos = new_last_line_pos;
 521                        }
 522                        /* ok, we will eat this char */
 523                        readpos++;
 524                        if (c == '\n') {
 525                                terminated = 1;
 526                                last_line_pos = 0;
 527                                break;
 528                        }
 529                        /* NUL is substituted by '\n'! */
 530                        if (c == '\0') c = '\n';
 531                        *p++ = c;
 532                        *p = '\0';
 533                } /* end of "read chars until we have a line" loop */
 534#if 0
 535//BUG: also triggers on this:
 536// { printf "\nfoo\n"; sleep 1; printf "\nbar\n"; } | less
 537// (resulting in lost empty line between "foo" and "bar" lines)
 538// the "terminated" logic needs fixing (or explaining)
 539                /* Corner case: linewrap with only "" wrapping to next line */
 540                /* Looks ugly on screen, so we do not store this empty line */
 541                if (!last_terminated && !current_line[0]) {
 542                        last_terminated = 1;
 543                        max_lineno++;
 544                        continue;
 545                }
 546#endif
 547 reached_eof:
 548                last_terminated = terminated;
 549                flines = xrealloc_vector(flines, 8, max_fline);
 550
 551                flines[max_fline] = (char*)xrealloc(MEMPTR(current_line), strlen(current_line) + 1 + 4) + 4;
 552                LINENO(flines[max_fline]) = max_lineno;
 553                if (terminated)
 554                        max_lineno++;
 555
 556                if (max_fline >= MAXLINES) {
 557                        eof_error = 0; /* Pretend we saw EOF */
 558                        break;
 559                }
 560                if (!at_end()) {
 561#if !ENABLE_FEATURE_LESS_REGEXP
 562                        break;
 563#else
 564                        if (wanted_match >= num_matches) { /* goto_match called us */
 565                                fill_match_lines(old_max_fline);
 566                                old_max_fline = max_fline;
 567                        }
 568                        if (wanted_match < num_matches)
 569                                break;
 570#endif
 571                }
 572                if (eof_error <= 0) {
 573                        break;
 574                }
 575                max_fline++;
 576                current_line = ((char*)xmalloc(w + 5)) + 4;
 577                p = current_line;
 578                last_line_pos = 0;
 579        } /* end of "read lines until we reach cur_fline" loop */
 580
 581        if (eof_error < 0) {
 582                if (errno == EAGAIN) {
 583                        eof_error = 1;
 584                } else {
 585                        print_statusline(bb_msg_read_error);
 586                }
 587        }
 588#if ENABLE_FEATURE_LESS_FLAGS
 589        else if (eof_error == 0)
 590                num_lines = max_lineno;
 591#endif
 592
 593        fill_match_lines(old_max_fline);
 594#if ENABLE_FEATURE_LESS_REGEXP
 595        /* prevent us from being stuck in search for a match */
 596        wanted_match = -1;
 597#endif
 598#undef readbuf
 599}
 600
 601#if ENABLE_FEATURE_LESS_FLAGS
 602static int safe_lineno(int fline)
 603{
 604        if (fline >= max_fline)
 605                fline = max_fline - 1;
 606
 607        /* also catches empty file (max_fline == 0) */
 608        if (fline < 0)
 609                return 0;
 610
 611        return LINENO(flines[fline]) + 1;
 612}
 613
 614/* count number of lines in file */
 615static void update_num_lines(void)
 616{
 617        int count, fd;
 618        struct stat stbuf;
 619        ssize_t len, i;
 620        char buf[4096];
 621
 622        /* only do this for regular files */
 623        if (num_lines == REOPEN_AND_COUNT || num_lines == REOPEN_STDIN) {
 624                count = 0;
 625                fd = open("/proc/self/fd/0", O_RDONLY);
 626                if (fd < 0 && num_lines == REOPEN_AND_COUNT) {
 627                        /* "filename" is valid only if REOPEN_AND_COUNT */
 628                        fd = open(filename, O_RDONLY);
 629                }
 630                if (fd < 0) {
 631                        /* somebody stole my file! */
 632                        num_lines = NOT_REGULAR_FILE;
 633                        return;
 634                }
 635                if (fstat(fd, &stbuf) != 0 || !S_ISREG(stbuf.st_mode)) {
 636                        num_lines = NOT_REGULAR_FILE;
 637                        goto do_close;
 638                }
 639                while ((len = safe_read(fd, buf, sizeof(buf))) > 0) {
 640                        for (i = 0; i < len; ++i) {
 641                                if (buf[i] == '\n' && ++count == MAXLINES)
 642                                        goto done;
 643                        }
 644                }
 645 done:
 646                num_lines = count;
 647 do_close:
 648                close(fd);
 649        }
 650}
 651
 652/* Print a status line if -M was specified */
 653static void m_status_print(void)
 654{
 655        int first, last;
 656        unsigned percent;
 657
 658        if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
 659                return;
 660
 661        clear_line();
 662        printf(HIGHLIGHT"%s", filename);
 663        if (num_files > 1)
 664                printf(" (file %i of %i)", current_file, num_files);
 665
 666        first = safe_lineno(cur_fline);
 667        last = (option_mask32 & FLAG_S)
 668                        ? MIN(first + max_displayed_line, max_lineno)
 669                        : safe_lineno(cur_fline + max_displayed_line);
 670        printf(" lines %i-%i", first, last);
 671
 672        update_num_lines();
 673        if (num_lines >= 0)
 674                printf("/%i", num_lines);
 675
 676        if (at_end()) {
 677                printf(" (END)");
 678                if (num_files > 1 && current_file != num_files)
 679                        printf(" - next: %s", files[current_file]);
 680        } else if (num_lines > 0) {
 681                percent = (100 * last + num_lines/2) / num_lines;
 682                printf(" %i%%", percent <= 100 ? percent : 100);
 683        }
 684        printf(NORMAL);
 685}
 686#endif
 687
 688/* Print the status line */
 689static void status_print(void)
 690{
 691        const char *p;
 692
 693        if (less_gets_pos >= 0) /* don't touch statusline while input is done! */
 694                return;
 695
 696        /* Change the status if flags have been set */
 697#if ENABLE_FEATURE_LESS_FLAGS
 698        if (option_mask32 & (FLAG_M|FLAG_m)) {
 699                m_status_print();
 700                return;
 701        }
 702        /* No flags set */
 703#endif
 704
 705        clear_line();
 706        if (cur_fline && !at_end()) {
 707                bb_putchar(':');
 708                return;
 709        }
 710        p = "(END)";
 711        if (!cur_fline)
 712                p = filename;
 713        if (num_files > 1) {
 714                printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
 715                                p, current_file, num_files);
 716                return;
 717        }
 718        print_hilite(p);
 719}
 720
 721static const char controls[] ALIGN1 =
 722        /* NUL: never encountered; TAB: not converted */
 723        /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
 724        "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
 725        "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
 726static const char ctrlconv[] ALIGN1 =
 727        /* why 40 instead of 4a below? - it is a replacement for '\n'.
 728         * '\n' is a former NUL - we subst it with @, not J */
 729        "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
 730        "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
 731
 732static void print_lineno(const char *line)
 733{
 734        const char *fmt = "        ";
 735        unsigned n = n; /* for compiler */
 736
 737        if (line != empty_line_marker) {
 738                /* Width of 7 preserves tab spacing in the text */
 739                fmt = "%7u ";
 740                n = LINENO(line) + 1;
 741                if (n > 9999999 && MAXLINES > 9999999) {
 742                        n %= 10000000;
 743                        fmt = "%07u ";
 744                }
 745        }
 746        printf(fmt, n);
 747}
 748
 749
 750#if ENABLE_FEATURE_LESS_REGEXP
 751static void print_found(const char *line)
 752{
 753        int match_status;
 754        int eflags;
 755        char *growline;
 756        regmatch_t match_structs;
 757
 758        char buf[width+1];
 759        const char *str = line;
 760        char *p = buf;
 761        size_t n;
 762
 763        while (*str) {
 764                n = strcspn(str, controls);
 765                if (n) {
 766                        if (!str[n]) break;
 767                        memcpy(p, str, n);
 768                        p += n;
 769                        str += n;
 770                }
 771                n = strspn(str, controls);
 772                memset(p, '.', n);
 773                p += n;
 774                str += n;
 775        }
 776        strcpy(p, str);
 777
 778        /* buf[] holds quarantined version of str */
 779
 780        /* Each part of the line that matches has the HIGHLIGHT
 781         * and NORMAL escape sequences placed around it.
 782         * NB: we regex against line, but insert text
 783         * from quarantined copy (buf[]) */
 784        str = buf;
 785        growline = NULL;
 786        eflags = 0;
 787        goto start;
 788
 789        while (match_status == 0) {
 790                char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
 791                                growline ? growline : "",
 792                                (int)match_structs.rm_so, str,
 793                                (int)(match_structs.rm_eo - match_structs.rm_so),
 794                                                str + match_structs.rm_so);
 795                free(growline);
 796                growline = new;
 797                str += match_structs.rm_eo;
 798                line += match_structs.rm_eo;
 799                eflags = REG_NOTBOL;
 800 start:
 801                /* Most of the time doesn't find the regex, optimize for that */
 802                match_status = regexec(&pattern, line, 1, &match_structs, eflags);
 803                /* if even "" matches, treat it as "not a match" */
 804                if (match_structs.rm_so >= match_structs.rm_eo)
 805                        match_status = 1;
 806        }
 807
 808        printf("%s%s\n", growline ? growline : "", str);
 809        free(growline);
 810}
 811#else
 812void print_found(const char *line);
 813#endif
 814
 815static void print_ascii(const char *str)
 816{
 817        char buf[width+1];
 818        char *p;
 819        size_t n;
 820
 821        while (*str) {
 822                n = strcspn(str, controls);
 823                if (n) {
 824                        if (!str[n]) break;
 825                        printf("%.*s", (int) n, str);
 826                        str += n;
 827                }
 828                n = strspn(str, controls);
 829                p = buf;
 830                do {
 831                        if (*str == 0x7f)
 832                                *p++ = '?';
 833                        else if (*str == (char)0x9b)
 834                        /* VT100's CSI, aka Meta-ESC. Who's inventor? */
 835                        /* I want to know who committed this sin */
 836                                *p++ = '{';
 837                        else
 838                                *p++ = ctrlconv[(unsigned char)*str];
 839                        str++;
 840                } while (--n);
 841                *p = '\0';
 842                print_hilite(buf);
 843        }
 844        puts(str);
 845}
 846
 847/* Print the buffer */
 848static void buffer_print(void)
 849{
 850        unsigned i;
 851
 852        move_cursor(0, 0);
 853        for (i = 0; i <= max_displayed_line; i++) {
 854                printf(CLEAR_2_EOL);
 855                if (option_mask32 & FLAG_N)
 856                        print_lineno(buffer[i]);
 857                if (pattern_valid)
 858                        print_found(buffer[i]);
 859                else
 860                        print_ascii(buffer[i]);
 861        }
 862        if ((option_mask32 & FLAG_E)
 863         && eof_error <= 0
 864         && (max_fline - cur_fline) <= max_displayed_line
 865        ) {
 866                less_exit(EXIT_SUCCESS);
 867        }
 868        status_print();
 869}
 870
 871static void buffer_fill_and_print(void)
 872{
 873        unsigned i;
 874#if ENABLE_FEATURE_LESS_TRUNCATE
 875        int fpos = cur_fline;
 876
 877        if (option_mask32 & FLAG_S) {
 878                /* Go back to the beginning of this line */
 879                while (fpos && LINENO(flines[fpos]) == LINENO(flines[fpos-1]))
 880                        fpos--;
 881        }
 882
 883        i = 0;
 884        while (i <= max_displayed_line && fpos <= max_fline) {
 885                int lineno = LINENO(flines[fpos]);
 886                buffer[i] = flines[fpos];
 887                i++;
 888                do {
 889                        fpos++;
 890                } while ((fpos <= max_fline)
 891                      && (option_mask32 & FLAG_S)
 892                      && lineno == LINENO(flines[fpos])
 893                );
 894        }
 895#else
 896        for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
 897                buffer[i] = flines[cur_fline + i];
 898        }
 899#endif
 900        for (; i <= max_displayed_line; i++) {
 901                buffer[i] = empty_line_marker;
 902        }
 903        buffer_print();
 904}
 905
 906/* move cur_fline to a given line number, reading lines if necessary */
 907static void goto_lineno(int target)
 908{
 909        if (target <= 0 ) {
 910                cur_fline = 0;
 911        }
 912        else if (target > LINENO(flines[cur_fline])) {
 913 retry:
 914                while (LINENO(flines[cur_fline]) != target && cur_fline < max_fline)
 915                        ++cur_fline;
 916                /* target not reached but more input is available */
 917                if (LINENO(flines[cur_fline]) != target && eof_error > 0) {
 918                        read_lines();
 919                        goto retry;
 920                }
 921        }
 922        else {
 923                /* search backwards through already-read lines */
 924                while (LINENO(flines[cur_fline]) != target && cur_fline > 0)
 925                        --cur_fline;
 926        }
 927}
 928
 929static void cap_cur_fline(void)
 930{
 931        if ((option_mask32 & FLAG_S)) {
 932                if (cur_fline > max_fline)
 933                        cur_fline = max_fline;
 934                if (LINENO(flines[cur_fline]) + max_displayed_line > max_lineno + TILDES) {
 935                        goto_lineno(max_lineno - max_displayed_line + TILDES);
 936                        read_lines();
 937                }
 938        }
 939        else {
 940                if (cur_fline + max_displayed_line > max_fline + TILDES)
 941                        cur_fline = max_fline - max_displayed_line + TILDES;
 942                if (cur_fline < 0)
 943                        cur_fline = 0;
 944        }
 945}
 946
 947/* Move the buffer up and down in the file in order to scroll */
 948static void buffer_down(int nlines)
 949{
 950        if ((option_mask32 & FLAG_S))
 951                goto_lineno(LINENO(flines[cur_fline]) + nlines);
 952        else
 953                cur_fline += nlines;
 954        read_lines();
 955        cap_cur_fline();
 956        buffer_fill_and_print();
 957}
 958
 959static void buffer_up(int nlines)
 960{
 961        if ((option_mask32 & FLAG_S)) {
 962                goto_lineno(LINENO(flines[cur_fline]) - nlines);
 963        }
 964        else {
 965                cur_fline -= nlines;
 966                if (cur_fline < 0)
 967                        cur_fline = 0;
 968        }
 969        read_lines();
 970        buffer_fill_and_print();
 971}
 972
 973/* display a given line where the argument can be either an index into
 974 * the flines array or a line number */
 975static void buffer_to_line(int linenum, int is_lineno)
 976{
 977        if (linenum <= 0)
 978                cur_fline = 0;
 979        else if (is_lineno)
 980                goto_lineno(linenum);
 981        else
 982                cur_fline = linenum;
 983        read_lines();
 984        cap_cur_fline();
 985        buffer_fill_and_print();
 986}
 987
 988static void buffer_line(int linenum)
 989{
 990        buffer_to_line(linenum, FALSE);
 991}
 992
 993static void buffer_lineno(int lineno)
 994{
 995        buffer_to_line(lineno, TRUE);
 996}
 997
 998static void open_file_and_read_lines(void)
 999{
1000        if (filename) {
1001                xmove_fd(xopen(filename, O_RDONLY), STDIN_FILENO);
1002#if ENABLE_FEATURE_LESS_FLAGS
1003                num_lines = REOPEN_AND_COUNT;
1004#endif
1005        } else {
1006                /* "less" with no arguments in argv[] */
1007                /* For status line only */
1008                filename = xstrdup(bb_msg_standard_input);
1009#if ENABLE_FEATURE_LESS_FLAGS
1010                num_lines = REOPEN_STDIN;
1011#endif
1012        }
1013        readpos = 0;
1014        readeof = 0;
1015        last_line_pos = 0;
1016        terminated = 1;
1017        read_lines();
1018}
1019
1020/* Reinitialize everything for a new file - free the memory and start over */
1021static void reinitialize(void)
1022{
1023        unsigned i;
1024
1025        if (flines) {
1026                for (i = 0; i <= max_fline; i++)
1027                        free(MEMPTR(flines[i]));
1028                free(flines);
1029                flines = NULL;
1030        }
1031
1032        max_fline = -1;
1033        cur_fline = 0;
1034        max_lineno = 0;
1035        open_file_and_read_lines();
1036#if ENABLE_FEATURE_LESS_ASK_TERMINAL
1037        if (G.winsize_err)
1038                printf("\033[999;999H" "\033[6n");
1039#endif
1040        buffer_fill_and_print();
1041}
1042
1043static int64_t getch_nowait(void)
1044{
1045        int rd;
1046        int64_t key64;
1047        struct pollfd pfd[2];
1048
1049        pfd[0].fd = STDIN_FILENO;
1050        pfd[0].events = POLLIN;
1051        pfd[1].fd = kbd_fd;
1052        pfd[1].events = POLLIN;
1053 again:
1054        tcsetattr(kbd_fd, TCSANOW, &term_less);
1055        /* NB: select/poll returns whenever read will not block. Therefore:
1056         * if eof is reached, select/poll will return immediately
1057         * because read will immediately return 0 bytes.
1058         * Even if select/poll says that input is available, read CAN block
1059         * (switch fd into O_NONBLOCK'ed mode to avoid it)
1060         */
1061        rd = 1;
1062        /* Are we interested in stdin? */
1063        if (at_end()) {
1064                if (eof_error > 0) /* did NOT reach eof yet */
1065                        rd = 0; /* yes, we are interested in stdin */
1066        }
1067        /* Position cursor if line input is done */
1068        if (less_gets_pos >= 0)
1069                move_cursor(max_displayed_line + 2, less_gets_pos + 1);
1070        fflush_all();
1071
1072        if (kbd_input[0] == 0) { /* if nothing is buffered */
1073#if ENABLE_FEATURE_LESS_WINCH
1074                while (1) {
1075                        int r;
1076                        /* NB: SIGWINCH interrupts poll() */
1077                        r = poll(pfd + rd, 2 - rd, -1);
1078                        if (/*r < 0 && errno == EINTR &&*/ winch_counter)
1079                                return '\\'; /* anything which has no defined function */
1080                        if (r) break;
1081                }
1082#else
1083                safe_poll(pfd + rd, 2 - rd, -1);
1084#endif
1085        }
1086
1087        /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
1088         * would not block even if there is no input available */
1089        key64 = read_key(kbd_fd, kbd_input, /*timeout off:*/ -2);
1090        if ((int)key64 == -1) {
1091                if (errno == EAGAIN) {
1092                        /* No keyboard input available. Since poll() did return,
1093                         * we should have input on stdin */
1094                        read_lines();
1095                        buffer_fill_and_print();
1096                        goto again;
1097                }
1098                /* EOF/error (ssh session got killed etc) */
1099                less_exit(0);
1100        }
1101        set_tty_cooked();
1102        return key64;
1103}
1104
1105/* Grab a character from input without requiring the return key.
1106 * May return KEYCODE_xxx values.
1107 * Note that this function works best with raw input. */
1108static int64_t less_getch(int pos)
1109{
1110        int64_t key64;
1111        int key;
1112
1113 again:
1114        less_gets_pos = pos;
1115        key = key64 = getch_nowait();
1116        less_gets_pos = -1;
1117
1118        /* Discard Ctrl-something chars.
1119         * (checking only lower 32 bits is a size optimization:
1120         * upper 32 bits are used only by KEYCODE_CURSOR_POS)
1121         */
1122        if (key >= 0 && key < ' ' && key != 0x0d && key != 8)
1123                goto again;
1124
1125        return key64;
1126}
1127
1128static char* less_gets(int sz)
1129{
1130        int c;
1131        unsigned i = 0;
1132        char *result = xzalloc(1);
1133
1134        while (1) {
1135                c = '\0';
1136                less_gets_pos = sz + i;
1137                c = getch_nowait();
1138                if (c == 0x0d) {
1139                        result[i] = '\0';
1140                        less_gets_pos = -1;
1141                        return result;
1142                }
1143                if (c == 0x7f)
1144                        c = 8;
1145                if (c == 8 && i) {
1146                        printf("\x8 \x8");
1147                        i--;
1148                }
1149                if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
1150                        continue;
1151                if (i >= width - sz - 1)
1152                        continue; /* len limit */
1153                bb_putchar(c);
1154                result[i++] = c;
1155                result = xrealloc(result, i+1);
1156        }
1157}
1158
1159static void examine_file(void)
1160{
1161        char *new_fname;
1162
1163        print_statusline("Examine: ");
1164        new_fname = less_gets(sizeof("Examine: ") - 1);
1165        if (!new_fname[0]) {
1166                status_print();
1167 err:
1168                free(new_fname);
1169                return;
1170        }
1171        if (access(new_fname, R_OK) != 0) {
1172                print_statusline("Cannot read this file");
1173                goto err;
1174        }
1175        free(filename);
1176        filename = new_fname;
1177        /* files start by = argv. why we assume that argv is infinitely long??
1178        files[num_files] = filename;
1179        current_file = num_files + 1;
1180        num_files++; */
1181        files[0] = filename;
1182        num_files = current_file = 1;
1183        reinitialize();
1184}
1185
1186/* This function changes the file currently being paged. direction can be one of the following:
1187 * -1: go back one file
1188 *  0: go to the first file
1189 *  1: go forward one file */
1190static void change_file(int direction)
1191{
1192        if (current_file != ((direction > 0) ? num_files : 1)) {
1193                current_file = direction ? current_file + direction : 1;
1194                free(filename);
1195                filename = xstrdup(files[current_file - 1]);
1196                reinitialize();
1197        } else {
1198                print_statusline(direction > 0 ? "No next file" : "No previous file");
1199        }
1200}
1201
1202static void remove_current_file(void)
1203{
1204        unsigned i;
1205
1206        if (num_files < 2)
1207                return;
1208
1209        if (current_file != 1) {
1210                change_file(-1);
1211                for (i = 3; i <= num_files; i++)
1212                        files[i - 2] = files[i - 1];
1213                num_files--;
1214        } else {
1215                change_file(1);
1216                for (i = 2; i <= num_files; i++)
1217                        files[i - 2] = files[i - 1];
1218                num_files--;
1219                current_file--;
1220        }
1221}
1222
1223static void colon_process(void)
1224{
1225        int keypress;
1226
1227        /* Clear the current line and print a prompt */
1228        print_statusline(" :");
1229
1230        keypress = less_getch(2);
1231        switch (keypress) {
1232        case 'd':
1233                remove_current_file();
1234                break;
1235        case 'e':
1236                examine_file();
1237                break;
1238#if ENABLE_FEATURE_LESS_FLAGS
1239        case 'f':
1240                m_status_print();
1241                break;
1242#endif
1243        case 'n':
1244                change_file(1);
1245                break;
1246        case 'p':
1247                change_file(-1);
1248                break;
1249        case 'q':
1250                less_exit(EXIT_SUCCESS);
1251                break;
1252        case 'x':
1253                change_file(0);
1254                break;
1255        }
1256}
1257
1258#if ENABLE_FEATURE_LESS_REGEXP
1259static void normalize_match_pos(int match)
1260{
1261        if (match >= num_matches)
1262                match = num_matches - 1;
1263        if (match < 0)
1264                match = 0;
1265        match_pos = match;
1266}
1267
1268static void goto_match(int match)
1269{
1270        if (!pattern_valid)
1271                return;
1272        if (match < 0)
1273                match = 0;
1274        /* Try to find next match if eof isn't reached yet */
1275        if (match >= num_matches && eof_error > 0) {
1276                wanted_match = match; /* "I want to read until I see N'th match" */
1277                read_lines();
1278        }
1279        if (num_matches) {
1280                normalize_match_pos(match);
1281                buffer_line(match_lines[match_pos]);
1282        } else {
1283                print_statusline("No matches found");
1284        }
1285}
1286
1287static void fill_match_lines(unsigned pos)
1288{
1289        if (!pattern_valid)
1290                return;
1291        /* Run the regex on each line of the current file */
1292        while (pos <= max_fline) {
1293                /* If this line matches */
1294                if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
1295                /* and we didn't match it last time */
1296                 && !(num_matches && match_lines[num_matches-1] == pos)
1297                ) {
1298                        match_lines = xrealloc_vector(match_lines, 4, num_matches);
1299                        match_lines[num_matches++] = pos;
1300                }
1301                pos++;
1302        }
1303}
1304
1305static void regex_process(void)
1306{
1307        char *uncomp_regex, *err;
1308
1309        /* Reset variables */
1310        free(match_lines);
1311        match_lines = NULL;
1312        match_pos = 0;
1313        num_matches = 0;
1314        if (pattern_valid) {
1315                regfree(&pattern);
1316                pattern_valid = 0;
1317        }
1318
1319        /* Get the uncompiled regular expression from the user */
1320        clear_line();
1321        bb_putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
1322        uncomp_regex = less_gets(1);
1323        if (!uncomp_regex[0]) {
1324                free(uncomp_regex);
1325                buffer_print();
1326                return;
1327        }
1328
1329        /* Compile the regex and check for errors */
1330        err = regcomp_or_errmsg(&pattern, uncomp_regex,
1331                                (option_mask32 & FLAG_I) ? REG_ICASE : 0);
1332        free(uncomp_regex);
1333        if (err) {
1334                print_statusline(err);
1335                free(err);
1336                return;
1337        }
1338
1339        pattern_valid = 1;
1340        match_pos = 0;
1341        fill_match_lines(0);
1342        while (match_pos < num_matches) {
1343                if ((int)match_lines[match_pos] > cur_fline)
1344                        break;
1345                match_pos++;
1346        }
1347        if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
1348                match_pos--;
1349
1350        /* It's possible that no matches are found yet.
1351         * goto_match() will read input looking for match,
1352         * if needed */
1353        goto_match(match_pos);
1354}
1355#endif
1356
1357static void number_process(int first_digit)
1358{
1359        unsigned i;
1360        int num;
1361        int keypress;
1362        char num_input[sizeof(int)*4]; /* more than enough */
1363
1364        num_input[0] = first_digit;
1365
1366        /* Clear the current line, print a prompt, and then print the digit */
1367        clear_line();
1368        printf(":%c", first_digit);
1369
1370        /* Receive input until a letter is given */
1371        i = 1;
1372        while (i < sizeof(num_input)-1) {
1373                keypress = less_getch(i + 1);
1374                if ((unsigned)keypress > 255 || !isdigit(keypress))
1375                        break;
1376                num_input[i] = keypress;
1377                bb_putchar(keypress);
1378                i++;
1379        }
1380
1381        num_input[i] = '\0';
1382        num = bb_strtou(num_input, NULL, 10);
1383        /* on format error, num == -1 */
1384        if (num < 1 || num > MAXLINES) {
1385                buffer_print();
1386                return;
1387        }
1388
1389        /* We now know the number and the letter entered, so we process them */
1390        switch (keypress) {
1391        case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
1392                buffer_down(num);
1393                break;
1394        case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
1395                buffer_up(num);
1396                break;
1397        case 'g': case '<': case 'G': case '>':
1398                buffer_lineno(num - 1);
1399                break;
1400        case 'p': case '%':
1401#if ENABLE_FEATURE_LESS_FLAGS
1402                update_num_lines();
1403                num = num * (num_lines > 0 ? num_lines : max_lineno) / 100;
1404#else
1405                num = num * max_lineno / 100;
1406#endif
1407                buffer_lineno(num);
1408                break;
1409#if ENABLE_FEATURE_LESS_REGEXP
1410        case 'n':
1411                goto_match(match_pos + num);
1412                break;
1413        case '/':
1414                option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1415                regex_process();
1416                break;
1417        case '?':
1418                option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1419                regex_process();
1420                break;
1421#endif
1422        }
1423}
1424
1425#if ENABLE_FEATURE_LESS_DASHCMD
1426static void flag_change(void)
1427{
1428        int keypress;
1429
1430        clear_line();
1431        bb_putchar('-');
1432        keypress = less_getch(1);
1433
1434        switch (keypress) {
1435        case 'M':
1436                option_mask32 ^= FLAG_M;
1437                break;
1438        case 'm':
1439                option_mask32 ^= FLAG_m;
1440                break;
1441        case 'E':
1442                option_mask32 ^= FLAG_E;
1443                break;
1444        case '~':
1445                option_mask32 ^= FLAG_TILDE;
1446                break;
1447#if ENABLE_FEATURE_LESS_TRUNCATE
1448        case 'S':
1449                option_mask32 ^= FLAG_S;
1450                buffer_fill_and_print();
1451                break;
1452#endif
1453#if ENABLE_FEATURE_LESS_LINENUMS
1454        case 'N':
1455                option_mask32 ^= FLAG_N;
1456                re_wrap();
1457                buffer_fill_and_print();
1458                break;
1459#endif
1460        }
1461}
1462
1463#ifdef BLOAT
1464static void show_flag_status(void)
1465{
1466        int keypress;
1467        int flag_val;
1468
1469        clear_line();
1470        bb_putchar('_');
1471        keypress = less_getch(1);
1472
1473        switch (keypress) {
1474        case 'M':
1475                flag_val = option_mask32 & FLAG_M;
1476                break;
1477        case 'm':
1478                flag_val = option_mask32 & FLAG_m;
1479                break;
1480        case '~':
1481                flag_val = option_mask32 & FLAG_TILDE;
1482                break;
1483        case 'N':
1484                flag_val = option_mask32 & FLAG_N;
1485                break;
1486        case 'E':
1487                flag_val = option_mask32 & FLAG_E;
1488                break;
1489        default:
1490                flag_val = 0;
1491                break;
1492        }
1493
1494        clear_line();
1495        printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1496}
1497#endif
1498
1499#endif /* ENABLE_FEATURE_LESS_DASHCMD */
1500
1501static void save_input_to_file(void)
1502{
1503        const char *msg = "";
1504        char *current_line;
1505        unsigned i;
1506        FILE *fp;
1507
1508        print_statusline("Log file: ");
1509        current_line = less_gets(sizeof("Log file: ")-1);
1510        if (current_line[0]) {
1511                fp = fopen_for_write(current_line);
1512                if (!fp) {
1513                        msg = "Error opening log file";
1514                        goto ret;
1515                }
1516                for (i = 0; i <= max_fline; i++)
1517                        fprintf(fp, "%s\n", flines[i]);
1518                fclose(fp);
1519                msg = "Done";
1520        }
1521 ret:
1522        print_statusline(msg);
1523        free(current_line);
1524}
1525
1526#if ENABLE_FEATURE_LESS_MARKS
1527static void add_mark(void)
1528{
1529        int letter;
1530
1531        print_statusline("Mark: ");
1532        letter = less_getch(sizeof("Mark: ") - 1);
1533
1534        if (isalpha(letter)) {
1535                /* If we exceed 15 marks, start overwriting previous ones */
1536                if (num_marks == 14)
1537                        num_marks = 0;
1538
1539                mark_lines[num_marks][0] = letter;
1540                mark_lines[num_marks][1] = cur_fline;
1541                num_marks++;
1542        } else {
1543                print_statusline("Invalid mark letter");
1544        }
1545}
1546
1547static void goto_mark(void)
1548{
1549        int letter;
1550        int i;
1551
1552        print_statusline("Go to mark: ");
1553        letter = less_getch(sizeof("Go to mark: ") - 1);
1554        clear_line();
1555
1556        if (isalpha(letter)) {
1557                for (i = 0; i <= num_marks; i++)
1558                        if (letter == mark_lines[i][0]) {
1559                                buffer_line(mark_lines[i][1]);
1560                                break;
1561                        }
1562                if (num_marks == 14 && letter != mark_lines[14][0])
1563                        print_statusline("Mark not set");
1564        } else
1565                print_statusline("Invalid mark letter");
1566}
1567#endif
1568
1569#if ENABLE_FEATURE_LESS_BRACKETS
1570static char opp_bracket(char bracket)
1571{
1572        switch (bracket) {
1573                case '{': case '[': /* '}' == '{' + 2. Same for '[' */
1574                        bracket++;
1575                case '(':           /* ')' == '(' + 1 */
1576                        bracket++;
1577                        break;
1578                case '}': case ']':
1579                        bracket--;
1580                case ')':
1581                        bracket--;
1582                        break;
1583        };
1584        return bracket;
1585}
1586
1587static void match_right_bracket(char bracket)
1588{
1589        unsigned i;
1590
1591        if (strchr(flines[cur_fline], bracket) == NULL) {
1592                print_statusline("No bracket in top line");
1593                return;
1594        }
1595        bracket = opp_bracket(bracket);
1596        for (i = cur_fline + 1; i < max_fline; i++) {
1597                if (strchr(flines[i], bracket) != NULL) {
1598                        buffer_line(i);
1599                        return;
1600                }
1601        }
1602        print_statusline("No matching bracket found");
1603}
1604
1605static void match_left_bracket(char bracket)
1606{
1607        int i;
1608
1609        if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1610                print_statusline("No bracket in bottom line");
1611                return;
1612        }
1613
1614        bracket = opp_bracket(bracket);
1615        for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1616                if (strchr(flines[i], bracket) != NULL) {
1617                        buffer_line(i);
1618                        return;
1619                }
1620        }
1621        print_statusline("No matching bracket found");
1622}
1623#endif  /* FEATURE_LESS_BRACKETS */
1624
1625static void keypress_process(int keypress)
1626{
1627        switch (keypress) {
1628        case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
1629                buffer_down(1);
1630                break;
1631        case KEYCODE_UP: case 'y': case 'k':
1632                buffer_up(1);
1633                break;
1634        case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
1635                buffer_down(max_displayed_line + 1);
1636                break;
1637        case KEYCODE_PAGEUP: case 'w': case 'b':
1638                buffer_up(max_displayed_line + 1);
1639                break;
1640        case 'd':
1641                buffer_down((max_displayed_line + 1) / 2);
1642                break;
1643        case 'u':
1644                buffer_up((max_displayed_line + 1) / 2);
1645                break;
1646        case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
1647                buffer_line(0);
1648                break;
1649        case KEYCODE_END: case 'G': case '>':
1650                cur_fline = MAXLINES;
1651                read_lines();
1652                buffer_line(cur_fline);
1653                break;
1654        case 'q': case 'Q':
1655                less_exit(EXIT_SUCCESS);
1656                break;
1657#if ENABLE_FEATURE_LESS_MARKS
1658        case 'm':
1659                add_mark();
1660                buffer_print();
1661                break;
1662        case '\'':
1663                goto_mark();
1664                buffer_print();
1665                break;
1666#endif
1667        case 'r': case 'R':
1668                /* TODO: (1) also bind ^R, ^L to this?
1669                 * (2) re-measure window size?
1670                 */
1671                buffer_print();
1672                break;
1673        /*case 'R':
1674                full_repaint();
1675                break;*/
1676        case 's':
1677                save_input_to_file();
1678                break;
1679        case 'E':
1680                examine_file();
1681                break;
1682#if ENABLE_FEATURE_LESS_FLAGS
1683        case '=':
1684                m_status_print();
1685                break;
1686#endif
1687#if ENABLE_FEATURE_LESS_REGEXP
1688        case '/':
1689                option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1690                regex_process();
1691                break;
1692        case 'n':
1693                goto_match(match_pos + 1);
1694                break;
1695        case 'N':
1696                goto_match(match_pos - 1);
1697                break;
1698        case '?':
1699                option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1700                regex_process();
1701                break;
1702#endif
1703#if ENABLE_FEATURE_LESS_DASHCMD
1704        case '-':
1705                flag_change();
1706                buffer_print();
1707                break;
1708#ifdef BLOAT
1709        case '_':
1710                show_flag_status();
1711                break;
1712#endif
1713#endif
1714#if ENABLE_FEATURE_LESS_BRACKETS
1715        case '{': case '(': case '[':
1716                match_right_bracket(keypress);
1717                break;
1718        case '}': case ')': case ']':
1719                match_left_bracket(keypress);
1720                break;
1721#endif
1722        case ':':
1723                colon_process();
1724                break;
1725        }
1726
1727        if (isdigit(keypress))
1728                number_process(keypress);
1729}
1730
1731static void sig_catcher(int sig)
1732{
1733        less_exit(- sig);
1734}
1735
1736#if ENABLE_FEATURE_LESS_WINCH
1737static void sigwinch_handler(int sig UNUSED_PARAM)
1738{
1739        winch_counter++;
1740}
1741#endif
1742
1743int less_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1744int less_main(int argc, char **argv)
1745{
1746        char *tty_name;
1747        int tty_fd;
1748
1749        INIT_G();
1750
1751        /* TODO: -x: do not interpret backspace, -xx: tab also
1752         * -xxx: newline also
1753         * -w N: assume width N (-xxx -w 32: hex viewer of sorts)
1754         * -s: condense many empty lines to one
1755         *     (used by some setups for manpage display)
1756         */
1757        getopt32(argv, "EMmN~I" IF_FEATURE_LESS_TRUNCATE("S") /*ignored:*/"s");
1758        argc -= optind;
1759        argv += optind;
1760        num_files = argc;
1761        files = argv;
1762
1763        /* Another popular pager, most, detects when stdout
1764         * is not a tty and turns into cat. This makes sense. */
1765        if (!isatty(STDOUT_FILENO))
1766                return bb_cat(argv);
1767
1768        if (!num_files) {
1769                if (isatty(STDIN_FILENO)) {
1770                        /* Just "less"? No args and no redirection? */
1771                        bb_error_msg("missing filename");
1772                        bb_show_usage();
1773                }
1774        } else {
1775                filename = xstrdup(files[0]);
1776        }
1777
1778        if (option_mask32 & FLAG_TILDE)
1779                empty_line_marker = "";
1780
1781        /* Some versions of less can survive w/o controlling tty,
1782         * try to do the same. This also allows to specify an alternative
1783         * tty via "less 1<>TTY".
1784         * We don't try to use STDOUT_FILENO directly,
1785         * since we want to set this fd to non-blocking mode,
1786         * and not bother with restoring it on exit.
1787         */
1788        tty_name = xmalloc_ttyname(STDOUT_FILENO);
1789        if (tty_name) {
1790                tty_fd = open(tty_name, O_RDONLY);
1791                free(tty_name);
1792                if (tty_fd < 0)
1793                        goto try_ctty;
1794        } else {
1795                /* Try controlling tty */
1796 try_ctty:
1797                tty_fd = open(CURRENT_TTY, O_RDONLY);
1798                if (tty_fd < 0)
1799                        return bb_cat(argv);
1800        }
1801        ndelay_on(tty_fd);
1802        kbd_fd = tty_fd; /* save in a global */
1803
1804        tcgetattr(kbd_fd, &term_orig);
1805        term_less = term_orig;
1806        term_less.c_lflag &= ~(ICANON | ECHO);
1807        term_less.c_iflag &= ~(IXON | ICRNL);
1808        /*term_less.c_oflag &= ~ONLCR;*/
1809        term_less.c_cc[VMIN] = 1;
1810        term_less.c_cc[VTIME] = 0;
1811
1812        IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1813        /* 20: two tabstops + 4 */
1814        if (width < 20 || max_displayed_line < 3)
1815                return bb_cat(argv);
1816        max_displayed_line -= 2;
1817
1818        /* We want to restore term_orig on exit */
1819        bb_signals(BB_FATAL_SIGS, sig_catcher);
1820#if ENABLE_FEATURE_LESS_WINCH
1821        signal(SIGWINCH, sigwinch_handler);
1822#endif
1823
1824        buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1825        reinitialize();
1826        while (1) {
1827                int64_t keypress;
1828
1829#if ENABLE_FEATURE_LESS_WINCH
1830                while (WINCH_COUNTER) {
1831 again:
1832                        winch_counter--;
1833                        IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1834 IF_FEATURE_LESS_ASK_TERMINAL(got_size:)
1835                        /* 20: two tabstops + 4 */
1836                        if (width < 20)
1837                                width = 20;
1838                        if (max_displayed_line < 3)
1839                                max_displayed_line = 3;
1840                        max_displayed_line -= 2;
1841                        free(buffer);
1842                        buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1843                        /* Avoid re-wrap and/or redraw if we already know
1844                         * we need to do it again. These ops are expensive */
1845                        if (WINCH_COUNTER)
1846                                goto again;
1847                        re_wrap();
1848                        if (WINCH_COUNTER)
1849                                goto again;
1850                        buffer_fill_and_print();
1851                        /* This took some time. Loop back and check,
1852                         * were there another SIGWINCH? */
1853                }
1854                keypress = less_getch(-1); /* -1: do not position cursor */
1855# if ENABLE_FEATURE_LESS_ASK_TERMINAL
1856                if ((int32_t)keypress == KEYCODE_CURSOR_POS) {
1857                        uint32_t rc = (keypress >> 32);
1858                        width = (rc & 0x7fff);
1859                        max_displayed_line = ((rc >> 16) & 0x7fff);
1860                        goto got_size;
1861                }
1862# endif
1863#else
1864                keypress = less_getch(-1); /* -1: do not position cursor */
1865#endif
1866                keypress_process(keypress);
1867        }
1868}
1869
1870/*
1871Help text of less version 418 is below.
1872If you are implementing something, keeping
1873key and/or command line switch compatibility is a good idea:
1874
1875
1876                   SUMMARY OF LESS COMMANDS
1877
1878      Commands marked with * may be preceded by a number, N.
1879      Notes in parentheses indicate the behavior if N is given.
1880  h  H                 Display this help.
1881  q  :q  Q  :Q  ZZ     Exit.
1882 ---------------------------------------------------------------------------
1883                           MOVING
1884  e  ^E  j  ^N  CR  *  Forward  one line   (or N lines).
1885  y  ^Y  k  ^K  ^P  *  Backward one line   (or N lines).
1886  f  ^F  ^V  SPACE  *  Forward  one window (or N lines).
1887  b  ^B  ESC-v      *  Backward one window (or N lines).
1888  z                 *  Forward  one window (and set window to N).
1889  w                 *  Backward one window (and set window to N).
1890  ESC-SPACE         *  Forward  one window, but don't stop at end-of-file.
1891  d  ^D             *  Forward  one half-window (and set half-window to N).
1892  u  ^U             *  Backward one half-window (and set half-window to N).
1893  ESC-)  RightArrow *  Left  one half screen width (or N positions).
1894  ESC-(  LeftArrow  *  Right one half screen width (or N positions).
1895  F                    Forward forever; like "tail -f".
1896  r  ^R  ^L            Repaint screen.
1897  R                    Repaint screen, discarding buffered input.
1898        ---------------------------------------------------
1899        Default "window" is the screen height.
1900        Default "half-window" is half of the screen height.
1901 ---------------------------------------------------------------------------
1902                          SEARCHING
1903  /pattern          *  Search forward for (N-th) matching line.
1904  ?pattern          *  Search backward for (N-th) matching line.
1905  n                 *  Repeat previous search (for N-th occurrence).
1906  N                 *  Repeat previous search in reverse direction.
1907  ESC-n             *  Repeat previous search, spanning files.
1908  ESC-N             *  Repeat previous search, reverse dir. & spanning files.
1909  ESC-u                Undo (toggle) search highlighting.
1910        ---------------------------------------------------
1911        Search patterns may be modified by one or more of:
1912        ^N or !  Search for NON-matching lines.
1913        ^E or *  Search multiple files (pass thru END OF FILE).
1914        ^F or @  Start search at FIRST file (for /) or last file (for ?).
1915        ^K       Highlight matches, but don't move (KEEP position).
1916        ^R       Don't use REGULAR EXPRESSIONS.
1917 ---------------------------------------------------------------------------
1918                           JUMPING
1919  g  <  ESC-<       *  Go to first line in file (or line N).
1920  G  >  ESC->       *  Go to last line in file (or line N).
1921  p  %              *  Go to beginning of file (or N percent into file).
1922  t                 *  Go to the (N-th) next tag.
1923  T                 *  Go to the (N-th) previous tag.
1924  {  (  [           *  Find close bracket } ) ].
1925  }  )  ]           *  Find open bracket { ( [.
1926  ESC-^F <c1> <c2>  *  Find close bracket <c2>.
1927  ESC-^B <c1> <c2>  *  Find open bracket <c1>
1928        ---------------------------------------------------
1929        Each "find close bracket" command goes forward to the close bracket
1930          matching the (N-th) open bracket in the top line.
1931        Each "find open bracket" command goes backward to the open bracket
1932          matching the (N-th) close bracket in the bottom line.
1933  m<letter>            Mark the current position with <letter>.
1934  '<letter>            Go to a previously marked position.
1935  ''                   Go to the previous position.
1936  ^X^X                 Same as '.
1937        ---------------------------------------------------
1938        A mark is any upper-case or lower-case letter.
1939        Certain marks are predefined:
1940             ^  means  beginning of the file
1941             $  means  end of the file
1942 ---------------------------------------------------------------------------
1943                        CHANGING FILES
1944  :e [file]            Examine a new file.
1945  ^X^V                 Same as :e.
1946  :n                *  Examine the (N-th) next file from the command line.
1947  :p                *  Examine the (N-th) previous file from the command line.
1948  :x                *  Examine the first (or N-th) file from the command line.
1949  :d                   Delete the current file from the command line list.
1950  =  ^G  :f            Print current file name.
1951 ---------------------------------------------------------------------------
1952                    MISCELLANEOUS COMMANDS
1953  -<flag>              Toggle a command line option [see OPTIONS below].
1954  --<name>             Toggle a command line option, by name.
1955  _<flag>              Display the setting of a command line option.
1956  __<name>             Display the setting of an option, by name.
1957  +cmd                 Execute the less cmd each time a new file is examined.
1958  !command             Execute the shell command with $SHELL.
1959  |Xcommand            Pipe file between current pos & mark X to shell command.
1960  v                    Edit the current file with $VISUAL or $EDITOR.
1961  V                    Print version number of "less".
1962 ---------------------------------------------------------------------------
1963                           OPTIONS
1964        Most options may be changed either on the command line,
1965        or from within less by using the - or -- command.
1966        Options may be given in one of two forms: either a single
1967        character preceded by a -, or a name preceded by --.
1968  -?  ........  --help
1969                  Display help (from command line).
1970  -a  ........  --search-skip-screen
1971                  Forward search skips current screen.
1972  -b [N]  ....  --buffers=[N]
1973                  Number of buffers.
1974  -B  ........  --auto-buffers
1975                  Don't automatically allocate buffers for pipes.
1976  -c  ........  --clear-screen
1977                  Repaint by clearing rather than scrolling.
1978  -d  ........  --dumb
1979                  Dumb terminal.
1980  -D [xn.n]  .  --color=xn.n
1981                  Set screen colors. (MS-DOS only)
1982  -e  -E  ....  --quit-at-eof  --QUIT-AT-EOF
1983                  Quit at end of file.
1984  -f  ........  --force
1985                  Force open non-regular files.
1986  -F  ........  --quit-if-one-screen
1987                  Quit if entire file fits on first screen.
1988  -g  ........  --hilite-search
1989                  Highlight only last match for searches.
1990  -G  ........  --HILITE-SEARCH
1991                  Don't highlight any matches for searches.
1992  -h [N]  ....  --max-back-scroll=[N]
1993                  Backward scroll limit.
1994  -i  ........  --ignore-case
1995                  Ignore case in searches that do not contain uppercase.
1996  -I  ........  --IGNORE-CASE
1997                  Ignore case in all searches.
1998  -j [N]  ....  --jump-target=[N]
1999                  Screen position of target lines.
2000  -J  ........  --status-column
2001                  Display a status column at left edge of screen.
2002  -k [file]  .  --lesskey-file=[file]
2003                  Use a lesskey file.
2004  -L  ........  --no-lessopen
2005                  Ignore the LESSOPEN environment variable.
2006  -m  -M  ....  --long-prompt  --LONG-PROMPT
2007                  Set prompt style.
2008  -n  -N  ....  --line-numbers  --LINE-NUMBERS
2009                  Don't use line numbers.
2010  -o [file]  .  --log-file=[file]
2011                  Copy to log file (standard input only).
2012  -O [file]  .  --LOG-FILE=[file]
2013                  Copy to log file (unconditionally overwrite).
2014  -p [pattern]  --pattern=[pattern]
2015                  Start at pattern (from command line).
2016  -P [prompt]   --prompt=[prompt]
2017                  Define new prompt.
2018  -q  -Q  ....  --quiet  --QUIET  --silent --SILENT
2019                  Quiet the terminal bell.
2020  -r  -R  ....  --raw-control-chars  --RAW-CONTROL-CHARS
2021                  Output "raw" control characters.
2022  -s  ........  --squeeze-blank-lines
2023                  Squeeze multiple blank lines.
2024  -S  ........  --chop-long-lines
2025                  Chop long lines.
2026  -t [tag]  ..  --tag=[tag]
2027                  Find a tag.
2028  -T [tagsfile] --tag-file=[tagsfile]
2029                  Use an alternate tags file.
2030  -u  -U  ....  --underline-special  --UNDERLINE-SPECIAL
2031                  Change handling of backspaces.
2032  -V  ........  --version
2033                  Display the version number of "less".
2034  -w  ........  --hilite-unread
2035                  Highlight first new line after forward-screen.
2036  -W  ........  --HILITE-UNREAD
2037                  Highlight first new line after any forward movement.
2038  -x [N[,...]]  --tabs=[N[,...]]
2039                  Set tab stops.
2040  -X  ........  --no-init
2041                  Don't use termcap init/deinit strings.
2042                --no-keypad
2043                  Don't use termcap keypad init/deinit strings.
2044  -y [N]  ....  --max-forw-scroll=[N]
2045                  Forward scroll limit.
2046  -z [N]  ....  --window=[N]
2047                  Set size of window.
2048  -" [c[c]]  .  --quotes=[c[c]]
2049                  Set shell quote characters.
2050  -~  ........  --tilde
2051                  Don't display tildes after end of file.
2052  -# [N]  ....  --shift=[N]
2053                  Horizontal scroll amount (0 = one half screen width)
2054
2055 ---------------------------------------------------------------------------
2056                          LINE EDITING
2057        These keys can be used to edit text being entered
2058        on the "command line" at the bottom of the screen.
2059 RightArrow                       ESC-l     Move cursor right one character.
2060 LeftArrow                        ESC-h     Move cursor left one character.
2061 CNTL-RightArrow  ESC-RightArrow  ESC-w     Move cursor right one word.
2062 CNTL-LeftArrow   ESC-LeftArrow   ESC-b     Move cursor left one word.
2063 HOME                             ESC-0     Move cursor to start of line.
2064 END                              ESC-$     Move cursor to end of line.
2065 BACKSPACE                                  Delete char to left of cursor.
2066 DELETE                           ESC-x     Delete char under cursor.
2067 CNTL-BACKSPACE   ESC-BACKSPACE             Delete word to left of cursor.
2068 CNTL-DELETE      ESC-DELETE      ESC-X     Delete word under cursor.
2069 CNTL-U           ESC (MS-DOS only)         Delete entire line.
2070 UpArrow                          ESC-k     Retrieve previous command line.
2071 DownArrow                        ESC-j     Retrieve next command line.
2072 TAB                                        Complete filename & cycle.
2073 SHIFT-TAB                        ESC-TAB   Complete filename & reverse cycle.
2074 CNTL-L                                     Complete filename, list all.
2075*/
2076