*: make GNU licensing statement forms more regular
[platform/upstream/busybox.git] / util-linux / more.c
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 #include "libbb.h"
18
19 /* Support for FEATURE_USE_TERMIOS */
20
21 struct globals {
22         int cin_fileno;
23         struct termios initial_settings;
24         struct termios new_settings;
25 } FIX_ALIASING;
26 #define G (*(struct globals*)bb_common_bufsiz1)
27 #define INIT_G() ((void)0)
28 #define initial_settings (G.initial_settings)
29 #define new_settings     (G.new_settings    )
30 #define cin_fileno       (G.cin_fileno      )
31
32 #define setTermSettings(fd, argp) do { \
33                 if (ENABLE_FEATURE_USE_TERMIOS) tcsetattr(fd, TCSANOW, argp); \
34         } while (0)
35 #define getTermSettings(fd, argp) tcgetattr(fd, argp)
36
37 static void gotsig(int sig UNUSED_PARAM)
38 {
39         bb_putchar('\n');
40         setTermSettings(cin_fileno, &initial_settings);
41         exit(EXIT_FAILURE);
42 }
43
44 #define CONVERTED_TAB_SIZE 8
45
46 int more_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
47 int more_main(int argc UNUSED_PARAM, char **argv)
48 {
49         int c = c; /* for compiler */
50         int lines;
51         int input = 0;
52         int spaces = 0;
53         int please_display_more_prompt;
54         struct stat st;
55         FILE *file;
56         FILE *cin;
57         int len;
58         unsigned terminal_width;
59         unsigned terminal_height;
60
61         INIT_G();
62
63         argv++;
64         /* Another popular pager, most, detects when stdout
65          * is not a tty and turns into cat. This makes sense. */
66         if (!isatty(STDOUT_FILENO))
67                 return bb_cat(argv);
68         cin = fopen_for_read(CURRENT_TTY);
69         if (!cin)
70                 return bb_cat(argv);
71
72         if (ENABLE_FEATURE_USE_TERMIOS) {
73                 cin_fileno = fileno(cin);
74                 getTermSettings(cin_fileno, &initial_settings);
75                 new_settings = initial_settings;
76                 new_settings.c_lflag &= ~ICANON;
77                 new_settings.c_lflag &= ~ECHO;
78                 new_settings.c_cc[VMIN] = 1;
79                 new_settings.c_cc[VTIME] = 0;
80                 setTermSettings(cin_fileno, &new_settings);
81                 bb_signals(0
82                         + (1 << SIGINT)
83                         + (1 << SIGQUIT)
84                         + (1 << SIGTERM)
85                         , gotsig);
86         }
87
88         do {
89                 file = stdin;
90                 if (*argv) {
91                         file = fopen_or_warn(*argv, "r");
92                         if (!file)
93                                 continue;
94                 }
95                 st.st_size = 0;
96                 fstat(fileno(file), &st);
97
98                 please_display_more_prompt = 0;
99                 /* never returns w, h <= 1 */
100                 get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height);
101                 terminal_height -= 1;
102
103                 len = 0;
104                 lines = 0;
105                 while (spaces || (c = getc(file)) != EOF) {
106                         int wrap;
107                         if (spaces)
108                                 spaces--;
109  loop_top:
110                         if (input != 'r' && please_display_more_prompt) {
111                                 len = printf("--More-- ");
112                                 if (st.st_size > 0) {
113                                         len += printf("(%u%% of %"OFF_FMT"u bytes)",
114                                                 (int) (ftello(file)*100 / st.st_size),
115                                                 st.st_size);
116                                 }
117                                 fflush_all();
118
119                                 /*
120                                  * We've just displayed the "--More--" prompt, so now we need
121                                  * to get input from the user.
122                                  */
123                                 for (;;) {
124                                         input = getc(cin);
125                                         input = tolower(input);
126                                         if (!ENABLE_FEATURE_USE_TERMIOS)
127                                                 printf("\033[A"); /* cursor up */
128                                         /* Erase the last message */
129                                         printf("\r%*s\r", len, "");
130
131                                         /* Due to various multibyte escape
132                                          * sequences, it's not ok to accept
133                                          * any input as a command to scroll
134                                          * the screen. We only allow known
135                                          * commands, else we show help msg. */
136                                         if (input == ' ' || input == '\n' || input == 'q' || input == 'r')
137                                                 break;
138                                         len = printf("(Enter:next line Space:next page Q:quit R:show the rest)");
139                                 }
140                                 len = 0;
141                                 lines = 0;
142                                 please_display_more_prompt = 0;
143
144                                 if (input == 'q')
145                                         goto end;
146
147                                 /* The user may have resized the terminal.
148                                  * Re-read the dimensions. */
149                                 if (ENABLE_FEATURE_USE_TERMIOS) {
150                                         get_terminal_width_height(cin_fileno, &terminal_width, &terminal_height);
151                                         terminal_height -= 1;
152                                 }
153                         }
154
155                         /* Crudely convert tabs into spaces, which are
156                          * a bajillion times easier to deal with. */
157                         if (c == '\t') {
158                                 spaces = CONVERTED_TAB_SIZE - 1;
159                                 c = ' ';
160                         }
161
162                         /*
163                          * There are two input streams to worry about here:
164                          *
165                          * c    : the character we are reading from the file being "mored"
166                          * input: a character received from the keyboard
167                          *
168                          * If we hit a newline in the _file_ stream, we want to test and
169                          * see if any characters have been hit in the _input_ stream. This
170                          * allows the user to quit while in the middle of a file.
171                          */
172                         wrap = (++len > terminal_width);
173                         if (c == '\n' || wrap) {
174                                 /* Then outputting this character
175                                  * will move us to a new line. */
176                                 if (++lines >= terminal_height || input == '\n')
177                                         please_display_more_prompt = 1;
178                                 len = 0;
179                         }
180                         if (c != '\n' && wrap) {
181                                 /* Then outputting this will also put a character on
182                                  * the beginning of that new line. Thus we first want to
183                                  * display the prompt (if any), so we skip the putchar()
184                                  * and go back to the top of the loop, without reading
185                                  * a new character. */
186                                 goto loop_top;
187                         }
188                         /* My small mind cannot fathom backspaces and UTF-8 */
189                         putchar(c);
190                 }
191                 fclose(file);
192                 fflush_all();
193         } while (*argv && *++argv);
194  end:
195         setTermSettings(cin_fileno, &initial_settings);
196         return 0;
197 }