comparison pager.c @ 17:a08ef0674d8e

Page long output in interactive mode
author Guido Berhoerster <guido+pwm@berhoerster.name>
date Sat, 12 Aug 2017 10:41:52 +0200
parents
children 1e39a251cbe9
comparison
equal deleted inserted replaced
16:a07665727c19 17:a08ef0674d8e
1 /*
2 * Copyright (C) 2017 Guido Berhoerster <guido+pwm@berhoerster.name>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #include "compat.h"
25
26 #ifdef HAVE_ERR_H
27 #include <err.h>
28 #endif /* HAVE_ERR_H */
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <limits.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/ioctl.h>
36 #ifdef HAVE_SYS_QUEUE_H
37 #include <sys/queue.h>
38 #endif /* HAVE_SYS_QUEUE_H */
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <termios.h>
42 #include <unistd.h>
43 #include <wchar.h>
44
45 #include "pwm.h"
46 #include "pager.h"
47 #include "util.h"
48
49 #ifndef TCSASOFT
50 #define TCSASOFT 0
51 #endif /* !TCSASOFT */
52
53 #ifndef _NSIG
54 #ifdef NSIG
55 #define _NSIG NSIG
56 #else /* NSIG */
57 #define _NSIG 128
58 #endif /* NSIG */
59 #endif /* !_NSIG */
60
61 #ifndef _PATH_TTY
62 #define _PATH_TTY "/dev/tty"
63 #endif
64
65 struct pager {
66 TAILQ_HEAD(lines_head, lines_entry) lines_head;
67 FILE *fp;
68 size_t buf_size;
69 char *buf;
70 };
71
72 struct lines_entry {
73 TAILQ_ENTRY(lines_entry) entry;
74 char *line;
75 };
76
77 static volatile sig_atomic_t signals[_NSIG];
78
79 static void
80 sig_handler(int signo)
81 {
82 signals[signo] = 1;
83 }
84
85 static int
86 getch_prompt(const char *prompt)
87 {
88 int retval = -1;
89 int signo;
90 int fd;
91 struct termios saved_term;
92 struct termios term;
93 struct sigaction sa;
94 struct sigaction saved_sa_hup;
95 struct sigaction saved_sa_int;
96 struct sigaction saved_sa_quit;
97 struct sigaction saved_sa_pipe;
98 struct sigaction saved_sa_alrm;
99 struct sigaction saved_sa_term;
100 struct sigaction saved_sa_tstp;
101 struct sigaction saved_sa_ttin;
102 struct sigaction saved_sa_ttou;
103 char c;
104 int need_restart = 0;
105
106 printf("%s", prompt);
107 fflush(stdout);
108
109 restart:
110 for (signo = 0; signo < _NSIG; signo++) {
111 signals[signo] = 0;
112 }
113
114 fd = open(_PATH_TTY, O_RDONLY);
115 if (fd < 0) {
116 return (-1);
117 }
118
119 if (tcgetattr(fd, &saved_term) != 0) {
120 close(fd);
121 return (-1);
122 }
123 memcpy(&term, &saved_term, sizeof (struct termios));
124
125 /* enter raw mode and turn echo off */
126 term.c_lflag &= ~(ICANON | ECHO);
127 while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) < 0) &&
128 (errno != EINTR)) {
129 continue;
130 }
131
132 /* install temporary signal handler */
133 sigemptyset(&sa.sa_mask);
134 sa.sa_flags = 0;
135 sa.sa_handler = sig_handler;
136 sigaction(SIGHUP, &sa, &saved_sa_hup);
137 sigaction(SIGINT, &sa, &saved_sa_int);
138 sigaction(SIGQUIT, &sa, &saved_sa_quit);
139 sigaction(SIGPIPE, &sa, &saved_sa_pipe);
140 sigaction(SIGALRM, &sa, &saved_sa_alrm);
141 sigaction(SIGTERM, &sa, &saved_sa_term);
142 sigaction(SIGTSTP, &sa, &saved_sa_tstp);
143 sigaction(SIGTTIN, &sa, &saved_sa_ttin);
144 sigaction(SIGTTOU, &sa, &saved_sa_ttou);
145
146 /* read key */
147 if (read(fd, &c, 1) == sizeof (c)) {
148 retval = c;
149 }
150
151 /* restore previous mode and echo setting */
152 do {
153 while ((tcsetattr(fd, TCSAFLUSH | TCSASOFT,
154 &saved_term) < 0) && (errno != EINTR)) {
155 continue;
156 }
157 } while ((tcgetattr(fd, &term) == 0) &&
158 (term.c_lflag != saved_term.c_lflag));
159
160 /* restore previous signal handlers */
161 sigaction(SIGHUP, &saved_sa_hup, &sa);
162 sigaction(SIGINT, &saved_sa_int, &sa);
163 sigaction(SIGQUIT, &saved_sa_quit, &sa);
164 sigaction(SIGPIPE, &saved_sa_pipe, &sa);
165 sigaction(SIGALRM, &saved_sa_alrm, &sa);
166 sigaction(SIGTERM, &saved_sa_term, &sa);
167 sigaction(SIGTSTP, &saved_sa_tstp, &sa);
168 sigaction(SIGTTIN, &saved_sa_ttin, &sa);
169 sigaction(SIGTTOU, &saved_sa_ttou, &sa);
170
171 /* erase prompt */
172 printf("\r%*s\r", (int)strlen(prompt), "");
173 fflush(stdout);
174
175 while ((close(fd) < 0) && (errno != EINTR)) {
176 continue;
177 }
178
179 /* re-raise signals received */
180 for (signo = 0; signo < _NSIG; signo++) {
181 if (signals[signo]) {
182 raise(signo);
183 if ((signo == SIGTSTP) || (signo == SIGTTIN) ||
184 (signo == SIGTTOU)) {
185 need_restart = 1;
186 }
187 }
188 }
189 if (need_restart) {
190 goto restart;
191 }
192
193 return (retval);
194 }
195
196 struct pager *
197 pager_create(FILE *fp)
198 {
199 struct pager *pager;
200
201 pager = xmalloc(sizeof (struct pager));
202 TAILQ_INIT(&pager->lines_head);
203 pager->fp = fp;
204 pager->buf_size = BUFSIZ;
205 pager->buf = xmalloc(BUFSIZ);
206
207 return (pager);
208 }
209
210 void
211 pager_destroy(struct pager *pager)
212 {
213 struct lines_entry *entry;
214 struct lines_entry *entry_tmp;
215
216 if (pager == NULL) {
217 return;
218 }
219
220 TAILQ_FOREACH_SAFE(entry, &pager->lines_head, entry, entry_tmp) {
221 TAILQ_REMOVE(&pager->lines_head, entry, entry);
222 free(entry->line);
223 free(entry);
224 }
225 free(pager->buf);
226 free(pager);
227 }
228
229 int
230 pager_vprintf(struct pager *pager, const char *fmt, va_list args)
231 {
232 int len;
233 va_list args_new;
234 char *p;
235 size_t line_len;
236 struct lines_entry *entry;
237 size_t line_offset;
238
239 va_copy(args_new, args);
240
241 /* format multibyte string in buffer */
242 len = vsnprintf(NULL, 0, fmt, args);
243 if (len < 0) {
244 err(1, "vsnprintf");
245 }
246
247 if (pager->buf_size < (size_t)len + 1) {
248 pager->buf_size = len + 1;
249 pager->buf = xrealloc(pager->buf, pager->buf_size);
250 }
251
252 len = vsnprintf(pager->buf, pager->buf_size, fmt, args_new);
253 if (len < 0) {
254 err(1, "vsnprintf");
255 }
256
257 /* split buffer into lines */
258 for (p = pager->buf; *p != '\0'; p += line_len) {
259 line_len = strcspn(p, "\n");
260 if (p[line_len] == '\n') {
261 line_len++;
262 }
263
264 entry = TAILQ_LAST(&pager->lines_head, lines_head);
265 line_offset = (entry != NULL) ? strlen(entry->line) : 0;
266 if ((entry != NULL) && (entry->line[line_offset - 1] != '\n')) {
267 /*
268 * append to the last line if it doesn't end with a
269 * newline
270 */
271 entry->line = xrealloc(entry->line, line_offset +
272 line_len + 1);
273 memcpy(entry->line + line_offset, p, line_len);
274 entry->line[line_offset + line_len] = '\0';
275 } else {
276 entry = xmalloc(sizeof (struct lines_entry));
277 entry->line = xmalloc(line_len + 1);
278 memcpy(entry->line, p, line_len);
279 entry->line[line_len] = '\0';
280 TAILQ_INSERT_TAIL(&pager->lines_head, entry, entry);
281 }
282 }
283
284 va_end(args_new);
285
286 return (len);
287 }
288
289 int
290 pager_printf(struct pager *pager, const char *fmt, ...)
291 {
292 int len;
293 va_list args;
294
295 va_start(args, fmt);
296 len = pager_vprintf(pager, fmt, args);
297 va_end(args);
298
299 return (len);
300 }
301
302 static unsigned int
303 mbsnprint(unsigned int cols, const char *s, FILE *fp)
304 {
305 const char *p = s;
306 unsigned int col = 0;
307 int mb_len;
308 wchar_t wc;
309 int width;
310 char mb_buf[MB_LEN_MAX];
311
312 while ((*p != '\n') && (*p != '\0') && (col < cols)) {
313 mb_len = mbtowc(&wc, p, MB_CUR_MAX);
314 if (mb_len != -1) {
315 width = wcwidth(wc);
316 if (width != -1) {
317 if (snprintf(mb_buf, sizeof (mb_buf), "%.*s",
318 mb_len, p) != mb_len) {
319 err(1, "snprintf");
320 }
321 } else {
322 /*
323 * non-printable character, print
324 * replacement character
325 */
326 width = 1;
327 if (snprintf(mb_buf, sizeof (mb_buf),
328 "\357\277\275") != 3) {
329 err(1, "snprintf");
330 }
331 }
332 } else {
333 /*
334 * decoding failed, reset state and skip one
335 * byte
336 */
337 mbtowc(NULL, NULL, MB_CUR_MAX);
338 mb_len = 1;
339
340 /* print replacement character */
341 width = 1;
342 if (snprintf(mb_buf, sizeof (mb_buf),
343 "\357\277\275") != 3) {
344 err(1, "snprintf");
345 }
346 }
347
348 p += mb_len;
349 col += width;
350 if (col <= cols) {
351 if (fputs(mb_buf, fp) == EOF) {
352 err(1, "fputs");
353 }
354 }
355 }
356
357 fputc('\n', fp);
358 fflush(fp);
359
360 return (col);
361 }
362
363 void
364 pager_show(struct pager *pager)
365 {
366 int is_interactive;
367 unsigned int rows = 24;
368 unsigned int cols = 80;
369 #ifdef TIOCGWINSZ
370 struct winsize ws;
371 #endif /* TIOCGWINSZ */
372 unsigned int row = 0;
373 struct lines_entry *entry;
374
375 is_interactive = (isatty(STDIN_FILENO) && (pager->fp == stdout));
376
377 #ifdef TIOCGWINSZ
378 if (is_interactive) {
379 /* determine terminal size */
380 if (ioctl(fileno(pager->fp), TIOCGWINSZ, &ws) == 0) {
381 rows = (ws.ws_row > 0) ? ws.ws_row : rows;
382 cols = (ws.ws_col > 0) ? ws.ws_col : cols;
383 }
384 }
385 #endif /* TIOCGWINSZ */
386
387 TAILQ_FOREACH(entry, &pager->lines_head, entry) {
388 if (is_interactive) {
389 mbsnprint(cols, entry->line, pager->fp);
390 row++;
391 if (row == rows - 1) {
392 getch_prompt("--More--");
393 row = 0;
394 }
395 } else {
396 fprintf(pager->fp, "%s", entry->line);
397 fflush(pager->fp);
398 }
399 }
400 }