source: MondoRescue/branches/stable/mindi-busybox/miscutils/less.c @ 1770

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 12 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod? silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian? keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René? Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

File size: 30.9 KB
Line 
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 the GPL v2 or later, see the file LICENSE in this tarball.
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#include <sched.h>  /* sched_yield() */
25
26#include "libbb.h"
27#if ENABLE_FEATURE_LESS_REGEXP
28#include "xregex.h"
29#endif
30
31/* FIXME: currently doesn't work right */
32#undef ENABLE_FEATURE_LESS_FLAGCS
33#define ENABLE_FEATURE_LESS_FLAGCS 0
34
35/* The escape codes for highlighted and normal text */
36#define HIGHLIGHT "\033[7m"
37#define NORMAL "\033[0m"
38/* The escape code to clear the screen */
39#define CLEAR "\033[H\033[J"
40/* The escape code to clear to end of line */
41#define CLEAR_2_EOL "\033[K"
42
43/* These are the escape sequences corresponding to special keys */
44enum {
45    REAL_KEY_UP = 'A',
46    REAL_KEY_DOWN = 'B',
47    REAL_KEY_RIGHT = 'C',
48    REAL_KEY_LEFT = 'D',
49    REAL_PAGE_UP = '5',
50    REAL_PAGE_DOWN = '6',
51    REAL_KEY_HOME = '7', // vt100? linux vt? or what?
52    REAL_KEY_END = '8',
53    REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
54    REAL_KEY_END_ALT = '4', // ESC [4~
55    REAL_KEY_HOME_XTERM = 'H',
56    REAL_KEY_END_XTERM = 'F',
57
58/* These are the special codes assigned by this program to the special keys */
59    KEY_UP = 20,
60    KEY_DOWN = 21,
61    KEY_RIGHT = 22,
62    KEY_LEFT = 23,
63    PAGE_UP = 24,
64    PAGE_DOWN = 25,
65    KEY_HOME = 26,
66    KEY_END = 27,
67
68/* Absolute max of lines eaten */
69    MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
70
71/* This many "after the end" lines we will show (at max) */
72    TILDES = 1,
73};
74
75/* Command line options */
76enum {
77    FLAG_E = 1,
78    FLAG_M = 1 << 1,
79    FLAG_m = 1 << 2,
80    FLAG_N = 1 << 3,
81    FLAG_TILDE = 1 << 4,
82/* hijack command line options variable for internal state vars */
83    LESS_STATE_MATCH_BACKWARDS = 1 << 15,
84};
85
86#if !ENABLE_FEATURE_LESS_REGEXP
87enum { pattern_valid = 0 };
88#endif
89
90struct globals {
91    int cur_fline; /* signed */
92    int kbd_fd;  /* fd to get input from */
93/* last position in last line, taking into account tabs */
94    size_t linepos;
95    unsigned max_displayed_line;
96    unsigned max_fline;
97    unsigned max_lineno; /* this one tracks linewrap */
98    unsigned width;
99    ssize_t eof_error; /* eof if 0, error if < 0 */
100    size_t readpos;
101    size_t readeof;
102    const char **buffer;
103    const char **flines;
104    const char *empty_line_marker;
105    unsigned num_files;
106    unsigned current_file;
107    char *filename;
108    char **files;
109#if ENABLE_FEATURE_LESS_MARKS
110    unsigned num_marks;
111    unsigned mark_lines[15][2];
112#endif
113#if ENABLE_FEATURE_LESS_REGEXP
114    unsigned *match_lines;
115    int match_pos; /* signed! */
116    unsigned num_matches;
117    regex_t pattern;
118    smallint pattern_valid;
119#endif
120    smallint terminated;
121    struct termios term_orig, term_less;
122};
123#define G (*ptr_to_globals)
124#define cur_fline           (G.cur_fline         )
125#define kbd_fd              (G.kbd_fd            )
126#define linepos             (G.linepos           )
127#define max_displayed_line  (G.max_displayed_line)
128#define max_fline           (G.max_fline         )
129#define max_lineno          (G.max_lineno        )
130#define width               (G.width             )
131#define eof_error           (G.eof_error         )
132#define readpos             (G.readpos           )
133#define readeof             (G.readeof           )
134#define buffer              (G.buffer            )
135#define flines              (G.flines            )
136#define empty_line_marker   (G.empty_line_marker )
137#define num_files           (G.num_files         )
138#define current_file        (G.current_file      )
139#define filename            (G.filename          )
140#define files               (G.files             )
141#define num_marks           (G.num_marks         )
142#define mark_lines          (G.mark_lines        )
143#if ENABLE_FEATURE_LESS_REGEXP
144#define match_lines         (G.match_lines       )
145#define match_pos           (G.match_pos         )
146#define num_matches         (G.num_matches       )
147#define pattern             (G.pattern           )
148#define pattern_valid       (G.pattern_valid     )
149#endif
150#define terminated          (G.terminated        )
151#define term_orig           (G.term_orig         )
152#define term_less           (G.term_less         )
153#define INIT_G() do { \
154        PTR_TO_GLOBALS = xzalloc(sizeof(G)); \
155        empty_line_marker = "~"; \
156        num_files = 1; \
157        current_file = 1; \
158        eof_error = 1; \
159        terminated = 1; \
160    } while (0)
161
162/* Reset terminal input to normal */
163static void set_tty_cooked(void)
164{
165    fflush(stdout);
166    tcsetattr(kbd_fd, TCSANOW, &term_orig);
167}
168
169/* Exit the program gracefully */
170static void less_exit(int code)
171{
172    /* TODO: We really should save the terminal state when we start,
173     * and restore it when we exit. Less does this with the
174     * "ti" and "te" termcap commands; can this be done with
175     * only termios.h? */
176    putchar('\n');
177    fflush_stdout_and_exit(code);
178}
179
180/* Move the cursor to a position (x,y), where (0,0) is the
181   top-left corner of the console */
182static void move_cursor(int line, int row)
183{
184    printf("\033[%u;%uH", line, row);
185}
186
187static void clear_line(void)
188{
189    printf("\033[%u;0H" CLEAR_2_EOL, max_displayed_line + 2);
190}
191
192static void print_hilite(const char *str)
193{
194    printf(HIGHLIGHT"%s"NORMAL, str);
195}
196
197static void print_statusline(const char *str)
198{
199    clear_line();
200    printf(HIGHLIGHT"%.*s"NORMAL, width - 1, str);
201}
202
203#if ENABLE_FEATURE_LESS_REGEXP
204static void fill_match_lines(unsigned pos);
205#else
206#define fill_match_lines(pos) ((void)0)
207#endif
208
209/* Devilishly complex routine.
210 *
211 * Has to deal with EOF and EPIPE on input,
212 * with line wrapping, with last line not ending in '\n'
213 * (possibly not ending YET!), with backspace and tabs.
214 * It reads input again if last time we got an EOF (thus supporting
215 * growing files) or EPIPE (watching output of slow process like make).
216 *
217 * Variables used:
218 * flines[] - array of lines already read. Linewrap may cause
219 *      one source file line to occupy several flines[n].
220 * flines[max_fline] - last line, possibly incomplete.
221 * terminated - 1 if flines[max_fline] is 'terminated'
222 *      (if there was '\n' [which isn't stored itself, we just remember
223 *      that it was seen])
224 * max_lineno - last line's number, this one doesn't increment
225 *      on line wrap, only on "real" new lines.
226 * readbuf[0..readeof-1] - small preliminary buffer.
227 * readbuf[readpos] - next character to add to current line.
228 * linepos - screen line position of next char to be read
229 *      (takes into account tabs and backspaces)
230 * eof_error - < 0 error, == 0 EOF, > 0 not EOF/error
231 */
232static void read_lines(void)
233{
234#define readbuf bb_common_bufsiz1
235    char *current_line, *p;
236    USE_FEATURE_LESS_REGEXP(unsigned old_max_fline = max_fline;)
237    int w = width;
238    char last_terminated = terminated;
239
240    if (option_mask32 & FLAG_N)
241        w -= 8;
242
243    current_line = xmalloc(w);
244    p = current_line;
245    max_fline += last_terminated;
246    if (!last_terminated) {
247        const char *cp = flines[max_fline];
248        if (option_mask32 & FLAG_N)
249            cp += 8;
250        strcpy(current_line, cp);
251        p += strlen(current_line);
252        /* linepos is still valid from previous read_lines() */
253    } else {
254        linepos = 0;
255    }
256
257    while (1) {
258 again:
259        *p = '\0';
260        terminated = 0;
261        while (1) {
262            char c;
263            /* if no unprocessed chars left, eat more */
264            if (readpos >= readeof) {
265                smallint yielded = 0;
266
267                ndelay_on(0);
268 read_again:
269                eof_error = safe_read(0, readbuf, sizeof(readbuf));
270                readpos = 0;
271                readeof = eof_error;
272                if (eof_error < 0) {
273                    if (errno == EAGAIN && !yielded) {
274            /* We can hit EAGAIN while searching for regexp match.
275             * Yield is not 100% reliable solution in general,
276             * but for less it should be good enough -
277             * we give stdin supplier some CPU time to produce
278             * more input. We do it just once.
279             * Currently, we do not stop when we found the Nth
280             * occurrence we were looking for. We read till end
281             * (or double EAGAIN). TODO? */
282                        sched_yield();
283                        yielded = 1;
284                        goto read_again;
285                    }
286                    readeof = 0;
287                    if (errno != EAGAIN)
288                        print_statusline("read error");
289                }
290                ndelay_off(0);
291
292                if (eof_error <= 0) {
293                    goto reached_eof;
294                }
295            }
296            c = readbuf[readpos];
297            /* backspace? [needed for manpages] */
298            /* <tab><bs> is (a) insane and */
299            /* (b) harder to do correctly, so we refuse to do it */
300            if (c == '\x8' && linepos && p[-1] != '\t') {
301                readpos++; /* eat it */
302                linepos--;
303            /* was buggy (p could end up <= current_line)... */
304                *--p = '\0';
305                continue;
306            }
307            {
308                size_t new_linepos = linepos + 1;
309                if (c == '\t') {
310                    new_linepos += 7;
311                    new_linepos &= (~7);
312                }
313                if (new_linepos >= w)
314                    break;
315                linepos = new_linepos;
316            }
317            /* ok, we will eat this char */
318            readpos++;
319            if (c == '\n') {
320                terminated = 1;
321                linepos = 0;
322                break;
323            }
324            /* NUL is substituted by '\n'! */
325            if (c == '\0') c = '\n';
326            *p++ = c;
327            *p = '\0';
328        }
329        /* Corner case: linewrap with only "" wrapping to next line */
330        /* Looks ugly on screen, so we do not store this empty line */
331        if (!last_terminated && !current_line[0]) {
332            last_terminated = 1;
333            max_lineno++;
334            goto again;
335        }
336 reached_eof:
337        last_terminated = terminated;
338        flines = xrealloc(flines, (max_fline+1) * sizeof(char *));
339        if (option_mask32 & FLAG_N) {
340            /* Width of 7 preserves tab spacing in the text */
341            flines[max_fline] = xasprintf(
342                (max_lineno <= 9999999) ? "%7u %s" : "%07u %s",
343                max_lineno % 10000000, current_line);
344            free(current_line);
345            if (terminated)
346                max_lineno++;
347        } else {
348            flines[max_fline] = xrealloc(current_line, strlen(current_line)+1);
349        }
350        if (max_fline >= MAXLINES) {
351            eof_error = 0; /* Pretend we saw EOF */
352            break;
353        }
354        if (max_fline > cur_fline + max_displayed_line)
355            break;
356        if (eof_error <= 0) {
357            if (eof_error < 0 && errno == EAGAIN) {
358                /* not yet eof or error, reset flag (or else
359                 * we will hog CPU - select() will return
360                 * immediately */
361                eof_error = 1;
362            }
363            break;
364        }
365        max_fline++;
366        current_line = xmalloc(w);
367        p = current_line;
368        linepos = 0;
369    }
370    fill_match_lines(old_max_fline);
371#undef readbuf
372}
373
374#if ENABLE_FEATURE_LESS_FLAGS
375/* Interestingly, writing calc_percent as a function saves around 32 bytes
376 * on my build. */
377static int calc_percent(void)
378{
379    unsigned p = (100 * (cur_fline+max_displayed_line+1) + max_fline/2) / (max_fline+1);
380    return p <= 100 ? p : 100;
381}
382
383/* Print a status line if -M was specified */
384static void m_status_print(void)
385{
386    int percentage;
387
388    clear_line();
389    printf(HIGHLIGHT"%s", filename);
390    if (num_files > 1)
391        printf(" (file %i of %i)", current_file, num_files);
392    printf(" lines %i-%i/%i ",
393            cur_fline + 1, cur_fline + max_displayed_line + 1,
394            max_fline + 1);
395    if (cur_fline >= max_fline - max_displayed_line) {
396        printf("(END)"NORMAL);
397        if (num_files > 1 && current_file != num_files)
398            printf(HIGHLIGHT" - next: %s"NORMAL, files[current_file]);
399        return;
400    }
401    percentage = calc_percent();
402    printf("%i%%"NORMAL, percentage);
403}
404#endif
405
406/* Print the status line */
407static void status_print(void)
408{
409    const char *p;
410
411    /* Change the status if flags have been set */
412#if ENABLE_FEATURE_LESS_FLAGS
413    if (option_mask32 & (FLAG_M|FLAG_m)) {
414        m_status_print();
415        return;
416    }
417    /* No flags set */
418#endif
419
420    clear_line();
421    if (cur_fline && cur_fline < max_fline - max_displayed_line) {
422        putchar(':');
423        return;
424    }
425    p = "(END)";
426    if (!cur_fline)
427        p = filename;
428    if (num_files > 1) {
429        printf(HIGHLIGHT"%s (file %i of %i)"NORMAL,
430                p, current_file, num_files);
431        return;
432    }
433    print_hilite(p);
434}
435
436static void cap_cur_fline(int nlines)
437{
438    int diff;
439    if (cur_fline < 0)
440        cur_fline = 0;
441    if (cur_fline + max_displayed_line > max_fline + TILDES) {
442        cur_fline -= nlines;
443        if (cur_fline < 0)
444            cur_fline = 0;
445        diff = max_fline - (cur_fline + max_displayed_line) + TILDES;
446        /* As the number of lines requested was too large, we just move
447        to the end of the file */
448        if (diff > 0)
449            cur_fline += diff;
450    }
451}
452
453static const char controls[] ALIGN1 =
454    /* NUL: never encountered; TAB: not converted */
455    /**/"\x01\x02\x03\x04\x05\x06\x07\x08"  "\x0a\x0b\x0c\x0d\x0e\x0f"
456    "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
457    "\x7f\x9b"; /* DEL and infamous Meta-ESC :( */
458static const char ctrlconv[] ALIGN1 =
459    /* '\n': it's a former NUL - subst with '@', not 'J' */
460    "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x40\x4b\x4c\x4d\x4e\x4f"
461    "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f";
462
463#if ENABLE_FEATURE_LESS_REGEXP
464static void print_found(const char *line)
465{
466    int match_status;
467    int eflags;
468    char *growline;
469    regmatch_t match_structs;
470
471    char buf[width];
472    const char *str = line;
473    char *p = buf;
474    size_t n;
475
476    while (*str) {
477        n = strcspn(str, controls);
478        if (n) {
479            if (!str[n]) break;
480            memcpy(p, str, n);
481            p += n;
482            str += n;
483        }
484        n = strspn(str, controls);
485        memset(p, '.', n);
486        p += n;
487        str += n;
488    }
489    strcpy(p, str);
490
491    /* buf[] holds quarantined version of str */
492
493    /* Each part of the line that matches has the HIGHLIGHT
494       and NORMAL escape sequences placed around it.
495       NB: we regex against line, but insert text
496       from quarantined copy (buf[]) */
497    str = buf;
498    growline = NULL;
499    eflags = 0;
500    goto start;
501
502    while (match_status == 0) {
503        char *new = xasprintf("%s%.*s"HIGHLIGHT"%.*s"NORMAL,
504                growline ? : "",
505                match_structs.rm_so, str,
506                match_structs.rm_eo - match_structs.rm_so,
507                        str + match_structs.rm_so);
508        free(growline); growline = new;
509        str += match_structs.rm_eo;
510        line += match_structs.rm_eo;
511        eflags = REG_NOTBOL;
512 start:
513        /* Most of the time doesn't find the regex, optimize for that */
514        match_status = regexec(&pattern, line, 1, &match_structs, eflags);
515    }
516
517    if (!growline) {
518        printf(CLEAR_2_EOL"%s\n", str);
519        return;
520    }
521    printf(CLEAR_2_EOL"%s%s\n", growline, str);
522    free(growline);
523}
524#else
525void print_found(const char *line);
526#endif
527
528static void print_ascii(const char *str)
529{
530    char buf[width];
531    char *p;
532    size_t n;
533
534    printf(CLEAR_2_EOL);
535    while (*str) {
536        n = strcspn(str, controls);
537        if (n) {
538            if (!str[n]) break;
539            printf("%.*s", (int) n, str);
540            str += n;
541        }
542        n = strspn(str, controls);
543        p = buf;
544        do {
545            if (*str == 0x7f)
546                *p++ = '?';
547            else if (*str == (char)0x9b)
548            /* VT100's CSI, aka Meta-ESC. Who's inventor? */
549            /* I want to know who committed this sin */
550                *p++ = '{';
551            else
552                *p++ = ctrlconv[(unsigned char)*str];
553            str++;
554        } while (--n);
555        *p = '\0';
556        print_hilite(buf);
557    }
558    puts(str);
559}
560
561/* Print the buffer */
562static void buffer_print(void)
563{
564    int i;
565
566    move_cursor(0, 0);
567    for (i = 0; i <= max_displayed_line; i++)
568        if (pattern_valid)
569            print_found(buffer[i]);
570        else
571            print_ascii(buffer[i]);
572    status_print();
573}
574
575static void buffer_fill_and_print(void)
576{
577    int i;
578    for (i = 0; i <= max_displayed_line && cur_fline + i <= max_fline; i++) {
579        buffer[i] = flines[cur_fline + i];
580    }
581    for (; i <= max_displayed_line; i++) {
582        buffer[i] = empty_line_marker;
583    }
584    buffer_print();
585}
586
587/* Move the buffer up and down in the file in order to scroll */
588static void buffer_down(int nlines)
589{
590    cur_fline += nlines;
591    read_lines();
592    cap_cur_fline(nlines);
593    buffer_fill_and_print();
594}
595
596static void buffer_up(int nlines)
597{
598    cur_fline -= nlines;
599    if (cur_fline < 0) cur_fline = 0;
600    read_lines();
601    buffer_fill_and_print();
602}
603
604static void buffer_line(int linenum)
605{
606    if (linenum < 0)
607        linenum = 0;
608    cur_fline = linenum;
609    read_lines();
610    if (linenum + max_displayed_line > max_fline)
611        linenum = max_fline - max_displayed_line + TILDES;
612    if (linenum < 0)
613        linenum = 0;
614    cur_fline = linenum;
615    buffer_fill_and_print();
616}
617
618static void open_file_and_read_lines(void)
619{
620    if (filename) {
621        int fd = xopen(filename, O_RDONLY);
622        dup2(fd, 0);
623        if (fd) close(fd);
624    } else {
625        /* "less" with no arguments in argv[] */
626        /* For status line only */
627        filename = xstrdup(bb_msg_standard_input);
628    }
629    readpos = 0;
630    readeof = 0;
631    linepos = 0;
632    terminated = 1;
633    read_lines();
634}
635
636/* Reinitialize everything for a new file - free the memory and start over */
637static void reinitialize(void)
638{
639    int i;
640
641    if (flines) {
642        for (i = 0; i <= max_fline; i++)
643            free((void*)(flines[i]));
644        free(flines);
645        flines = NULL;
646    }
647
648    max_fline = -1;
649    cur_fline = 0;
650    max_lineno = 0;
651    open_file_and_read_lines();
652    buffer_fill_and_print();
653}
654
655static void getch_nowait(char* input, int sz)
656{
657    ssize_t rd;
658    fd_set readfds;
659 again:
660    fflush(stdout);
661
662    /* NB: select returns whenever read will not block. Therefore:
663     * (a) with O_NONBLOCK'ed fds select will return immediately
664     * (b) if eof is reached, select will also return
665     *     because read will immediately return 0 bytes.
666     * Even if select says that input is available, read CAN block
667     * (switch fd into O_NONBLOCK'ed mode to avoid it)
668     */
669    FD_ZERO(&readfds);
670    if (max_fline <= cur_fline + max_displayed_line
671     && eof_error > 0 /* did NOT reach eof yet */
672    ) {
673        /* We are interested in stdin */
674        FD_SET(0, &readfds);
675    }
676    FD_SET(kbd_fd, &readfds);
677    tcsetattr(kbd_fd, TCSANOW, &term_less);
678    select(kbd_fd + 1, &readfds, NULL, NULL, NULL);
679
680    input[0] = '\0';
681    ndelay_on(kbd_fd);
682    rd = read(kbd_fd, input, sz);
683    ndelay_off(kbd_fd);
684    if (rd < 0) {
685        /* No keyboard input, but we have input on stdin! */
686        if (errno != EAGAIN) /* Huh?? */
687            return;
688        read_lines();
689        buffer_fill_and_print();
690        goto again;
691    }
692}
693
694/* Grab a character from input without requiring the return key. If the
695 * character is ASCII \033, get more characters and assign certain sequences
696 * special return codes. Note that this function works best with raw input. */
697static int less_getch(void)
698{
699    char input[16];
700    unsigned i;
701 again:
702    memset(input, 0, sizeof(input));
703    getch_nowait(input, sizeof(input));
704
705    /* Detect escape sequences (i.e. arrow keys) and handle
706     * them accordingly */
707    if (input[0] == '\033' && input[1] == '[') {
708        set_tty_cooked();
709        i = input[2] - REAL_KEY_UP;
710        if (i < 4)
711            return 20 + i;
712        i = input[2] - REAL_PAGE_UP;
713        if (i < 4)
714            return 24 + i;
715        if (input[2] == REAL_KEY_HOME_XTERM)
716            return KEY_HOME;
717        if (input[2] == REAL_KEY_HOME_ALT)
718            return KEY_HOME;
719        if (input[2] == REAL_KEY_END_XTERM)
720            return KEY_END;
721        if (input[2] == REAL_KEY_END_ALT)
722            return KEY_END;
723        return 0;
724    }
725    /* Reject almost all control chars */
726    i = input[0];
727    if (i < ' ' && i != 0x0d && i != 8) goto again;
728    set_tty_cooked();
729    return i;
730}
731
732static char* less_gets(int sz)
733{
734    char c;
735    int i = 0;
736    char *result = xzalloc(1);
737    while (1) {
738        fflush(stdout);
739
740        /* I be damned if I know why is it needed *repeatedly*,
741         * but it is needed. Is it because of stdio? */
742        tcsetattr(kbd_fd, TCSANOW, &term_less);
743
744        c = '\0';
745        read(kbd_fd, &c, 1);
746        if (c == 0x0d)
747            return result;
748        if (c == 0x7f)
749            c = 8;
750        if (c == 8 && i) {
751            printf("\x8 \x8");
752            i--;
753        }
754        if (c < ' ')
755            continue;
756        if (i >= width - sz - 1)
757            continue; /* len limit */
758        putchar(c);
759        result[i++] = c;
760        result = xrealloc(result, i+1);
761        result[i] = '\0';
762    }
763}
764
765static void examine_file(void)
766{
767    print_statusline("Examine: ");
768    free(filename);
769    filename = less_gets(sizeof("Examine: ")-1);
770    /* files start by = argv. why we assume that argv is infinitely long??
771    files[num_files] = filename;
772    current_file = num_files + 1;
773    num_files++; */
774    files[0] = filename;
775    num_files = current_file = 1;
776    reinitialize();
777}
778
779/* This function changes the file currently being paged. direction can be one of the following:
780 * -1: go back one file
781 *  0: go to the first file
782 *  1: go forward one file */
783static void change_file(int direction)
784{
785    if (current_file != ((direction > 0) ? num_files : 1)) {
786        current_file = direction ? current_file + direction : 1;
787        free(filename);
788        filename = xstrdup(files[current_file - 1]);
789        reinitialize();
790    } else {
791        print_statusline(direction > 0 ? "No next file" : "No previous file");
792    }
793}
794
795static void remove_current_file(void)
796{
797    int i;
798
799    if (num_files < 2)
800        return;
801
802    if (current_file != 1) {
803        change_file(-1);
804        for (i = 3; i <= num_files; i++)
805            files[i - 2] = files[i - 1];
806        num_files--;
807    } else {
808        change_file(1);
809        for (i = 2; i <= num_files; i++)
810            files[i - 2] = files[i - 1];
811        num_files--;
812        current_file--;
813    }
814}
815
816static void colon_process(void)
817{
818    int keypress;
819
820    /* Clear the current line and print a prompt */
821    print_statusline(" :");
822
823    keypress = less_getch();
824    switch (keypress) {
825    case 'd':
826        remove_current_file();
827        break;
828    case 'e':
829        examine_file();
830        break;
831#if ENABLE_FEATURE_LESS_FLAGS
832    case 'f':
833        m_status_print();
834        break;
835#endif
836    case 'n':
837        change_file(1);
838        break;
839    case 'p':
840        change_file(-1);
841        break;
842    case 'q':
843        less_exit(0);
844        break;
845    case 'x':
846        change_file(0);
847        break;
848    }
849}
850
851#if ENABLE_FEATURE_LESS_REGEXP
852static void normalize_match_pos(int match)
853{
854    if (match >= num_matches)
855        match = num_matches - 1;
856    if (match < 0)
857        match = 0;
858    match_pos = match;
859}
860
861static void goto_match(int match)
862{
863    int sv;
864
865    if (!pattern_valid)
866        return;
867    if (match < 0)
868        match = 0;
869    sv = cur_fline;
870    /* Try to find next match if eof isn't reached yet */
871    if (match >= num_matches && eof_error > 0) {
872        cur_fline = MAXLINES; /* look as far as needed */
873        read_lines();
874    }
875    if (num_matches) {
876        cap_cur_fline(cur_fline);
877        normalize_match_pos(match);
878        buffer_line(match_lines[match_pos]);
879    } else {
880        cur_fline = sv;
881        print_statusline("No matches found");
882    }
883}
884
885static void fill_match_lines(unsigned pos)
886{
887    if (!pattern_valid)
888        return;
889    /* Run the regex on each line of the current file */
890    while (pos <= max_fline) {
891        /* If this line matches */
892        if (regexec(&pattern, flines[pos], 0, NULL, 0) == 0
893        /* and we didn't match it last time */
894         && !(num_matches && match_lines[num_matches-1] == pos)
895        ) {
896            match_lines = xrealloc(match_lines, (num_matches+1) * sizeof(int));
897            match_lines[num_matches++] = pos;
898        }
899        pos++;
900    }
901}
902
903static void regex_process(void)
904{
905    char *uncomp_regex, *err;
906
907    /* Reset variables */
908    free(match_lines);
909    match_lines = NULL;
910    match_pos = 0;
911    num_matches = 0;
912    if (pattern_valid) {
913        regfree(&pattern);
914        pattern_valid = 0;
915    }
916
917    /* Get the uncompiled regular expression from the user */
918    clear_line();
919    putchar((option_mask32 & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/');
920    uncomp_regex = less_gets(1);
921    if (!uncomp_regex[0]) {
922        free(uncomp_regex);
923        buffer_print();
924        return;
925    }
926
927    /* Compile the regex and check for errors */
928    err = regcomp_or_errmsg(&pattern, uncomp_regex, 0);
929    free(uncomp_regex);
930    if (err) {
931        print_statusline(err);
932        free(err);
933        return;
934    }
935
936    pattern_valid = 1;
937    match_pos = 0;
938    fill_match_lines(0);
939    while (match_pos < num_matches) {
940        if (match_lines[match_pos] > cur_fline)
941            break;
942        match_pos++;
943    }
944    if (option_mask32 & LESS_STATE_MATCH_BACKWARDS)
945        match_pos--;
946
947    /* It's possible that no matches are found yet.
948     * goto_match() will read input looking for match,
949     * if needed */
950    goto_match(match_pos);
951}
952#endif
953
954static void number_process(int first_digit)
955{
956    int i = 1;
957    int num;
958    char num_input[sizeof(int)*4]; /* more than enough */
959    char keypress;
960
961    num_input[0] = first_digit;
962
963    /* Clear the current line, print a prompt, and then print the digit */
964    clear_line();
965    printf(":%c", first_digit);
966
967    /* Receive input until a letter is given */
968    while (i < sizeof(num_input)-1) {
969        num_input[i] = less_getch();
970        if (!num_input[i] || !isdigit(num_input[i]))
971            break;
972        putchar(num_input[i]);
973        i++;
974    }
975
976    /* Take the final letter out of the digits string */
977    keypress = num_input[i];
978    num_input[i] = '\0';
979    num = bb_strtou(num_input, NULL, 10);
980    /* on format error, num == -1 */
981    if (num < 1 || num > MAXLINES) {
982        buffer_print();
983        return;
984    }
985
986    /* We now know the number and the letter entered, so we process them */
987    switch (keypress) {
988    case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
989        buffer_down(num);
990        break;
991    case KEY_UP: case 'b': case 'w': case 'y': case 'u':
992        buffer_up(num);
993        break;
994    case 'g': case '<': case 'G': case '>':
995        cur_fline = num + max_displayed_line;
996        read_lines();
997        buffer_line(num - 1);
998        break;
999    case 'p': case '%':
1000        num = num * (max_fline / 100); /* + max_fline / 2; */
1001        cur_fline = num + max_displayed_line;
1002        read_lines();
1003        buffer_line(num);
1004        break;
1005#if ENABLE_FEATURE_LESS_REGEXP
1006    case 'n':
1007        goto_match(match_pos + num);
1008        break;
1009    case '/':
1010        option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1011        regex_process();
1012        break;
1013    case '?':
1014        option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1015        regex_process();
1016        break;
1017#endif
1018    }
1019}
1020
1021#if ENABLE_FEATURE_LESS_FLAGCS
1022static void flag_change(void)
1023{
1024    int keypress;
1025
1026    clear_line();
1027    putchar('-');
1028    keypress = less_getch();
1029
1030    switch (keypress) {
1031    case 'M':
1032        option_mask32 ^= FLAG_M;
1033        break;
1034    case 'm':
1035        option_mask32 ^= FLAG_m;
1036        break;
1037    case 'E':
1038        option_mask32 ^= FLAG_E;
1039        break;
1040    case '~':
1041        option_mask32 ^= FLAG_TILDE;
1042        break;
1043    }
1044}
1045
1046static void show_flag_status(void)
1047{
1048    int keypress;
1049    int flag_val;
1050
1051    clear_line();
1052    putchar('_');
1053    keypress = less_getch();
1054
1055    switch (keypress) {
1056    case 'M':
1057        flag_val = option_mask32 & FLAG_M;
1058        break;
1059    case 'm':
1060        flag_val = option_mask32 & FLAG_m;
1061        break;
1062    case '~':
1063        flag_val = option_mask32 & FLAG_TILDE;
1064        break;
1065    case 'N':
1066        flag_val = option_mask32 & FLAG_N;
1067        break;
1068    case 'E':
1069        flag_val = option_mask32 & FLAG_E;
1070        break;
1071    default:
1072        flag_val = 0;
1073        break;
1074    }
1075
1076    clear_line();
1077    printf(HIGHLIGHT"The status of the flag is: %u"NORMAL, flag_val != 0);
1078}
1079#endif
1080
1081static void save_input_to_file(void)
1082{
1083    const char *msg = "";
1084    char *current_line;
1085    int i;
1086    FILE *fp;
1087
1088    print_statusline("Log file: ");
1089    current_line = less_gets(sizeof("Log file: ")-1);
1090    if (strlen(current_line) > 0) {
1091        fp = fopen(current_line, "w");
1092        if (!fp) {
1093            msg = "Error opening log file";
1094            goto ret;
1095        }
1096        for (i = 0; i <= max_fline; i++)
1097            fprintf(fp, "%s\n", flines[i]);
1098        fclose(fp);
1099        msg = "Done";
1100    }
1101 ret:
1102    print_statusline(msg);
1103    free(current_line);
1104}
1105
1106#if ENABLE_FEATURE_LESS_MARKS
1107static void add_mark(void)
1108{
1109    int letter;
1110
1111    print_statusline("Mark: ");
1112    letter = less_getch();
1113
1114    if (isalpha(letter)) {
1115        /* If we exceed 15 marks, start overwriting previous ones */
1116        if (num_marks == 14)
1117            num_marks = 0;
1118
1119        mark_lines[num_marks][0] = letter;
1120        mark_lines[num_marks][1] = cur_fline;
1121        num_marks++;
1122    } else {
1123        print_statusline("Invalid mark letter");
1124    }
1125}
1126
1127static void goto_mark(void)
1128{
1129    int letter;
1130    int i;
1131
1132    print_statusline("Go to mark: ");
1133    letter = less_getch();
1134    clear_line();
1135
1136    if (isalpha(letter)) {
1137        for (i = 0; i <= num_marks; i++)
1138            if (letter == mark_lines[i][0]) {
1139                buffer_line(mark_lines[i][1]);
1140                break;
1141            }
1142        if (num_marks == 14 && letter != mark_lines[14][0])
1143            print_statusline("Mark not set");
1144    } else
1145        print_statusline("Invalid mark letter");
1146}
1147#endif
1148
1149#if ENABLE_FEATURE_LESS_BRACKETS
1150static char opp_bracket(char bracket)
1151{
1152    switch (bracket) {
1153    case '{': case '[':
1154        return bracket + 2;
1155    case '(':
1156        return ')';
1157    case '}': case ']':
1158        return bracket - 2;
1159    case ')':
1160        return '(';
1161    }
1162    return 0;
1163}
1164
1165static void match_right_bracket(char bracket)
1166{
1167    int bracket_line = -1;
1168    int i;
1169
1170    if (strchr(flines[cur_fline], bracket) == NULL) {
1171        print_statusline("No bracket in top line");
1172        return;
1173    }
1174    for (i = cur_fline + 1; i < max_fline; i++) {
1175        if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1176            bracket_line = i;
1177            break;
1178        }
1179    }
1180    if (bracket_line == -1)
1181        print_statusline("No matching bracket found");
1182    buffer_line(bracket_line - max_displayed_line);
1183}
1184
1185static void match_left_bracket(char bracket)
1186{
1187    int bracket_line = -1;
1188    int i;
1189
1190    if (strchr(flines[cur_fline + max_displayed_line], bracket) == NULL) {
1191        print_statusline("No bracket in bottom line");
1192        return;
1193    }
1194
1195    for (i = cur_fline + max_displayed_line; i >= 0; i--) {
1196        if (strchr(flines[i], opp_bracket(bracket)) != NULL) {
1197            bracket_line = i;
1198            break;
1199        }
1200    }
1201    if (bracket_line == -1)
1202        print_statusline("No matching bracket found");
1203    buffer_line(bracket_line);
1204}
1205#endif  /* FEATURE_LESS_BRACKETS */
1206
1207static void keypress_process(int keypress)
1208{
1209    switch (keypress) {
1210    case KEY_DOWN: case 'e': case 'j': case 0x0d:
1211        buffer_down(1);
1212        break;
1213    case KEY_UP: case 'y': case 'k':
1214        buffer_up(1);
1215        break;
1216    case PAGE_DOWN: case ' ': case 'z':
1217        buffer_down(max_displayed_line + 1);
1218        break;
1219    case PAGE_UP: case 'w': case 'b':
1220        buffer_up(max_displayed_line + 1);
1221        break;
1222    case 'd':
1223        buffer_down((max_displayed_line + 1) / 2);
1224        break;
1225    case 'u':
1226        buffer_up((max_displayed_line + 1) / 2);
1227        break;
1228    case KEY_HOME: case 'g': case 'p': case '<': case '%':
1229        buffer_line(0);
1230        break;
1231    case KEY_END: case 'G': case '>':
1232        cur_fline = MAXLINES;
1233        read_lines();
1234        buffer_line(cur_fline);
1235        break;
1236    case 'q': case 'Q':
1237        less_exit(0);
1238        break;
1239#if ENABLE_FEATURE_LESS_MARKS
1240    case 'm':
1241        add_mark();
1242        buffer_print();
1243        break;
1244    case '\'':
1245        goto_mark();
1246        buffer_print();
1247        break;
1248#endif
1249    case 'r': case 'R':
1250        buffer_print();
1251        break;
1252    /*case 'R':
1253        full_repaint();
1254        break;*/
1255    case 's':
1256        save_input_to_file();
1257        break;
1258    case 'E':
1259        examine_file();
1260        break;
1261#if ENABLE_FEATURE_LESS_FLAGS
1262    case '=':
1263        m_status_print();
1264        break;
1265#endif
1266#if ENABLE_FEATURE_LESS_REGEXP
1267    case '/':
1268        option_mask32 &= ~LESS_STATE_MATCH_BACKWARDS;
1269        regex_process();
1270        break;
1271    case 'n':
1272        goto_match(match_pos + 1);
1273        break;
1274    case 'N':
1275        goto_match(match_pos - 1);
1276        break;
1277    case '?':
1278        option_mask32 |= LESS_STATE_MATCH_BACKWARDS;
1279        regex_process();
1280        break;
1281#endif
1282#if ENABLE_FEATURE_LESS_FLAGCS
1283    case '-':
1284        flag_change();
1285        buffer_print();
1286        break;
1287    case '_':
1288        show_flag_status();
1289        break;
1290#endif
1291#if ENABLE_FEATURE_LESS_BRACKETS
1292    case '{': case '(': case '[':
1293        match_right_bracket(keypress);
1294        break;
1295    case '}': case ')': case ']':
1296        match_left_bracket(keypress);
1297        break;
1298#endif
1299    case ':':
1300        colon_process();
1301        break;
1302    }
1303
1304    if (isdigit(keypress))
1305        number_process(keypress);
1306}
1307
1308static void sig_catcher(int sig ATTRIBUTE_UNUSED)
1309{
1310    set_tty_cooked();
1311    exit(1);
1312}
1313
1314int less_main(int argc, char **argv);
1315int less_main(int argc, char **argv)
1316{
1317    int keypress;
1318
1319    INIT_G();
1320
1321    /* TODO: -x: do not interpret backspace, -xx: tab also */
1322    /* -xxx: newline also */
1323    /* -w N: assume width N (-xxx -w 32: hex viewer of sorts) */
1324    getopt32(argv, "EMmN~");
1325    argc -= optind;
1326    argv += optind;
1327    num_files = argc;
1328    files = argv;
1329
1330    /* Another popular pager, most, detects when stdout
1331     * is not a tty and turns into cat. This makes sense. */
1332    if (!isatty(STDOUT_FILENO))
1333        return bb_cat(argv);
1334    kbd_fd = open(CURRENT_TTY, O_RDONLY);
1335    if (kbd_fd < 0)
1336        return bb_cat(argv);
1337
1338    if (!num_files) {
1339        if (isatty(STDIN_FILENO)) {
1340            /* Just "less"? No args and no redirection? */
1341            bb_error_msg("missing filename");
1342            bb_show_usage();
1343        }
1344    } else
1345        filename = xstrdup(files[0]);
1346
1347    get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
1348    /* 20: two tabstops + 4 */
1349    if (width < 20 || max_displayed_line < 3)
1350        bb_error_msg_and_die("too narrow here");
1351    max_displayed_line -= 2;
1352
1353    buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
1354    if (option_mask32 & FLAG_TILDE)
1355        empty_line_marker = "";
1356
1357    tcgetattr(kbd_fd, &term_orig);
1358    signal(SIGTERM, sig_catcher);
1359    signal(SIGINT, sig_catcher);
1360    term_less = term_orig;
1361    term_less.c_lflag &= ~(ICANON | ECHO);
1362    term_less.c_iflag &= ~(IXON | ICRNL);
1363    /*term_less.c_oflag &= ~ONLCR;*/
1364    term_less.c_cc[VMIN] = 1;
1365    term_less.c_cc[VTIME] = 0;
1366
1367    /* Want to do it just once, but it doesn't work, */
1368    /* so we are redoing it (see code above). Mystery... */
1369    /*tcsetattr(kbd_fd, TCSANOW, &term_less);*/
1370
1371    reinitialize();
1372    while (1) {
1373        keypress = less_getch();
1374        keypress_process(keypress);
1375    }
1376}
Note: See TracBrowser for help on using the repository browser.