busybox/util-linux/more.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini more implementation for busybox
   4 *
   5 * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>.
   6 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
   7 *
   8 * Latest version blended together by Erik Andersen <andersen@codepoet.org>,
   9 * based on the original more implementation by Bruce, and code from the
  10 * Debian boot-floppies team.
  11 *
  12 * Termios corrects by Vladimir Oleynik <dzo@simtreas.ru>
  13 *
  14 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  15 */
  16
  17//usage:#define more_trivial_usage
  18//usage:       "[FILE]..."
  19//usage:#define more_full_usage "\n\n"
  20//usage:       "View FILE (or stdin) one screenful at a time"
  21//usage:
  22//usage:#define more_example_usage
  23//usage:       "$ dmesg | more\n"
  24
  25#include "libbb.h"
  26
  27/* Support for FEATURE_USE_TERMIOS */
  28
  29struct globals {
  30        int cin_fileno;
  31        struct termios initial_settings;
  32        struct termios new_settings;
  33} FIX_ALIASING;
  34#define G (*(struct globals*)bb_common_bufsiz1)
  35#define INIT_G() ((void)0)
  36#define initial_settings (G.initial_settings)
  37#define new_settings     (G.new_settings    )
  38#define cin_fileno       (G.cin_fileno      )
  39
  40#define setTermSettings(fd, argp) \
  41do { \
  42        if (ENABLE_FEATURE_USE_TERMIOS) \
  43                tcsetattr(fd, TCSANOW, argp); \
  44} while (0)
  45#define getTermSettings(fd, argp) tcgetattr(fd, argp)
  46
  47static void gotsig(int sig UNUSED_PARAM)
  48{
  49        /* bb_putchar_stderr doesn't use stdio buffering,
  50         * therefore it is safe in signal handler */
  51        bb_putchar_stderr('\n');
  52        setTermSettings(cin_fileno, &initial_settings);
  53        _exit(EXIT_FAILURE);
  54}
  55
  56#define CONVERTED_TAB_SIZE 8
  57
  58int more_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  59int more_main(int argc UNUSED_PARAM, char **argv)
  60{
  61        int c = c; /* for compiler */
  62        int lines;
  63        int input = 0;
  64        int spaces = 0;
  65        int please_display_more_prompt;
  66        struct stat st;
  67        FILE *file;
  68        FILE *cin;
  69        int len;
  70        unsigned terminal_width;
  71        unsigned terminal_height;
  72
  73        INIT_G();
  74
  75        argv++;
  76        /* Another popular pager, most, detects when stdout
  77         * is not a tty and turns into cat. This makes sense. */
  78        if (!isatty(STDOUT_FILENO))
  79                return bb_cat(argv);
  80        cin = fopen_for_read(CURRENT_TTY);
  81        if (!cin)
  82                return bb_cat(argv);
  83
  84        if (ENABLE_FEATURE_USE_TERMIOS) {
  85                cin_fileno = fileno(cin);
  86                getTermSettings(cin_fileno, &initial_settings);
  87                new_settings = initial_settings;
  88                new_settings.c_lflag &= ~(ICANON | ECHO);
  89                new_settings.c_cc[VMIN] = 1;
  90                new_settings.c_cc[VTIME] = 0;
  91                setTermSettings(cin_fileno, &new_settings);
  92                bb_signals(0
  93                        + (1 << SIGINT)
  94                        + (1 << SIGQUIT)
  95                        + (1 << SIGTERM)
  96                        , gotsig);
  97        }
  98
  99        do {
 100                file = stdin;
 101                if (*argv) {
 102                        file = fopen_or_warn(*argv, "r");
 103                        if (!file)
 104                                continue;
 105                }
 106                st.st_size = 0;
 107                fstat(fileno(file), &st);
 108
 109                please_display_more_prompt = 0;
 110                /* never returns w, h <= 1 */
 111                get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height);
 112                terminal_height -= 1;
 113
 114                len = 0;
 115                lines = 0;
 116                while (spaces || (c = getc(file)) != EOF) {
 117                        int wrap;
 118                        if (spaces)
 119                                spaces--;
 120 loop_top:
 121                        if (input != 'r' && please_display_more_prompt) {
 122                                len = printf("--More-- ");
 123                                if (st.st_size != 0) {
 124                                        uoff_t d = (uoff_t)st.st_size / 100;
 125                                        if (d == 0)
 126                                                d = 1;
 127                                        len += printf("(%u%% of %"OFF_FMT"u bytes)",
 128                                                (int) ((uoff_t)ftello(file) / d),
 129                                                st.st_size);
 130                                }
 131                                fflush_all();
 132
 133                                /*
 134                                 * We've just displayed the "--More--" prompt, so now we need
 135                                 * to get input from the user.
 136                                 */
 137                                for (;;) {
 138                                        input = getc(cin);
 139                                        input = tolower(input);
 140                                        if (!ENABLE_FEATURE_USE_TERMIOS)
 141                                                printf("\033[A"); /* cursor up */
 142                                        /* Erase the last message */
 143                                        printf("\r%*s\r", len, "");
 144
 145                                        /* Due to various multibyte escape
 146                                         * sequences, it's not ok to accept
 147                                         * any input as a command to scroll
 148                                         * the screen. We only allow known
 149                                         * commands, else we show help msg. */
 150                                        if (input == ' ' || input == '\n' || input == 'q' || input == 'r')
 151                                                break;
 152                                        len = printf("(Enter:next line Space:next page Q:quit R:show the rest)");
 153                                }
 154                                len = 0;
 155                                lines = 0;
 156                                please_display_more_prompt = 0;
 157
 158                                if (input == 'q')
 159                                        goto end;
 160
 161                                /* The user may have resized the terminal.
 162                                 * Re-read the dimensions. */
 163                                if (ENABLE_FEATURE_USE_TERMIOS) {
 164                                        get_terminal_width_height(cin_fileno, &terminal_width, &terminal_height);
 165                                        terminal_height -= 1;
 166                                }
 167                        }
 168
 169                        /* Crudely convert tabs into spaces, which are
 170                         * a bajillion times easier to deal with. */
 171                        if (c == '\t') {
 172                                spaces = ((unsigned)~len) % CONVERTED_TAB_SIZE;
 173                                c = ' ';
 174                        }
 175
 176                        /*
 177                         * There are two input streams to worry about here:
 178                         *
 179                         * c    : the character we are reading from the file being "mored"
 180                         * input: a character received from the keyboard
 181                         *
 182                         * If we hit a newline in the _file_ stream, we want to test and
 183                         * see if any characters have been hit in the _input_ stream. This
 184                         * allows the user to quit while in the middle of a file.
 185                         */
 186                        wrap = (++len > terminal_width);
 187                        if (c == '\n' || wrap) {
 188                                /* Then outputting this character
 189                                 * will move us to a new line. */
 190                                if (++lines >= terminal_height || input == '\n')
 191                                        please_display_more_prompt = 1;
 192                                len = 0;
 193                        }
 194                        if (c != '\n' && wrap) {
 195                                /* Then outputting this will also put a character on
 196                                 * the beginning of that new line. Thus we first want to
 197                                 * display the prompt (if any), so we skip the putchar()
 198                                 * and go back to the top of the loop, without reading
 199                                 * a new character. */
 200                                goto loop_top;
 201                        }
 202                        /* My small mind cannot fathom backspaces and UTF-8 */
 203                        putchar(c);
 204                        die_if_ferror_stdout(); /* if tty was destroyed (closed xterm, etc) */
 205                }
 206                fclose(file);
 207                fflush_all();
 208        } while (*argv && *++argv);
 209 end:
 210        setTermSettings(cin_fileno, &initial_settings);
 211        return 0;
 212}
 213