]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/pager.c
Merge branch 'awk' of https://github.com/t-8ch/util-linux
[thirdparty/util-linux.git] / lib / pager.c
1 /*
2 * SPDX-License-Identifier: GPL-2.0-or-later
3 *
4 * This file may be redistributed under the terms of the GNU Public
5 * License.
6 *
7 * Based on linux-perf/git scm
8 *
9 * Some modifications and simplifications for util-linux
10 * by Davidlohr Bueso <dave@xxxxxxx> - March 2012.
11 */
12
13 #include <unistd.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <err.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <sys/wait.h>
20 #include <signal.h>
21
22 #include "c.h"
23 #include "xalloc.h"
24 #include "nls.h"
25 #include "ttyutils.h"
26 #include "pager.h"
27
28 #define NULL_DEVICE "/dev/null"
29
30 static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
31
32 struct child_process {
33 const char **argv;
34 pid_t pid;
35 int in;
36 int out;
37 int err;
38
39 int org_err;
40 int org_out;
41 struct sigaction orig_sigint;
42 struct sigaction orig_sighup;
43 struct sigaction orig_sigterm;
44 struct sigaction orig_sigquit;
45 struct sigaction orig_sigpipe;
46
47 unsigned no_stdin:1;
48 void (*preexec_cb)(void);
49 };
50 static struct child_process pager_process;
51
52 static inline void close_pair(int fd[2])
53 {
54 close(fd[0]);
55 close(fd[1]);
56 }
57
58 static int start_command(struct child_process *cmd)
59 {
60 int need_in;
61 int fdin[2];
62
63 /*
64 * In case of errors we must keep the promise to close FDs
65 * that have been passed in via ->in and ->out.
66 */
67 need_in = !cmd->no_stdin && cmd->in < 0;
68 if (need_in) {
69 if (pipe(fdin) < 0) {
70 if (cmd->out > 0)
71 close(cmd->out);
72 return -1;
73 }
74 cmd->in = fdin[1];
75 }
76
77 fflush(NULL);
78 cmd->pid = fork();
79 if (!cmd->pid) {
80 if (need_in) {
81 dup2(fdin[0], STDIN_FILENO);
82 close_pair(fdin);
83 } else if (cmd->in > 0) {
84 dup2(cmd->in, STDIN_FILENO);
85 close(cmd->in);
86 }
87
88 cmd->preexec_cb();
89 execvp(cmd->argv[0], (char *const*) cmd->argv);
90 errexec(cmd->argv[0]);
91 }
92
93 if (cmd->pid < 0) {
94 if (need_in)
95 close_pair(fdin);
96 else if (0 <= cmd->in)
97 close(cmd->in);
98 return -1;
99 }
100
101 if (need_in)
102 close(fdin[0]);
103 else if (0 <= cmd->in)
104 close(cmd->in);
105 return 0;
106 }
107
108 static int wait_or_whine(pid_t pid)
109 {
110 for (;;) {
111 int status, code;
112 pid_t waiting = waitpid(pid, &status, 0);
113
114 if (waiting < 0) {
115 if (errno == EINTR)
116 continue;
117 ul_sig_err(EXIT_FAILURE, "waitpid failed");
118 }
119 if (waiting != pid)
120 return -1;
121 if (WIFSIGNALED(status))
122 return -1;
123
124 if (!WIFEXITED(status))
125 return -1;
126 code = WEXITSTATUS(status);
127 switch (code) {
128 case 127:
129 return -1;
130 case 0:
131 return 0;
132 default:
133 return -1;
134 }
135 }
136 }
137
138 static int finish_command(struct child_process *cmd)
139 {
140 return wait_or_whine(cmd->pid);
141 }
142
143 static void pager_preexec(void)
144 {
145 /*
146 * Work around bug in "less" by not starting it until we
147 * have real input
148 */
149 fd_set in, ex;
150
151 FD_ZERO(&in);
152 FD_SET(STDIN_FILENO, &in);
153 ex = in;
154
155 select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
156
157 if (setenv("LESS", "FRSX", 0) != 0)
158 warn(_("failed to set the %s environment variable"), "LESS");
159 }
160
161 static void wait_for_pager(void)
162 {
163 if (pager_process.pid == 0)
164 return;
165
166 /* signal EOF to pager */
167 close(STDOUT_FILENO);
168 close(STDERR_FILENO);
169 finish_command(&pager_process);
170 }
171
172 static void wait_for_pager_signal(int signo)
173 {
174 UL_PROTECT_ERRNO;
175 wait_for_pager();
176 raise(signo);
177 }
178
179 static int has_command(const char *cmd)
180 {
181 const char *path;
182 char *b, *c, *p, *s;
183 int rc = 0;
184
185 if (!cmd)
186 goto done;
187
188 c = xstrdup(cmd);
189 if (!c)
190 goto done;
191 b = strtok(c, " "); /* cmd may contain options */
192 if (!b)
193 goto cleanup;
194
195 if (*b == '/') {
196 rc = access(b, X_OK) == 0;
197 goto cleanup;
198 }
199
200 path = getenv("PATH");
201 if (!path)
202 goto cleanup;
203 p = xstrdup(path);
204 if (!p)
205 goto cleanup;
206
207 for (s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
208 int fd = open(s, O_RDONLY|O_CLOEXEC);
209 if (fd < 0)
210 continue;
211 rc = faccessat(fd, b, X_OK, 0) == 0;
212 close(fd);
213 if (rc)
214 break;
215 }
216 free(p);
217 cleanup:
218 free(c);
219 done:
220 /*fprintf(stderr, "has PAGER '%s': rc=%d\n", cmd, rc);*/
221 return rc;
222 }
223
224 static void __setup_pager(void)
225 {
226 const char *pager = getenv("PAGER");
227 struct sigaction sa;
228
229 if (!isatty(STDOUT_FILENO))
230 return;
231
232 if (!pager)
233 pager = "less";
234 else if (!*pager || !strcmp(pager, "cat"))
235 return;
236
237 if (!has_command(pager))
238 return;
239
240 /* spawn the pager */
241 pager_argv[2] = pager;
242 pager_process.argv = pager_argv;
243 pager_process.in = -1;
244 pager_process.preexec_cb = pager_preexec;
245
246 if (start_command(&pager_process))
247 return;
248
249 /* original process continues, but writes to the pipe */
250 dup2(pager_process.in, STDOUT_FILENO);
251 setvbuf(stdout, NULL, _IOLBF, 0);
252 if (isatty(STDERR_FILENO)) {
253 dup2(pager_process.in, STDERR_FILENO);
254 setvbuf(stderr, NULL, _IOLBF, 0);
255 }
256 close(pager_process.in);
257
258 memset(&sa, 0, sizeof(sa));
259 sa.sa_handler = wait_for_pager_signal;
260
261 /* this makes sure that the parent terminates after the pager */
262 sigaction(SIGINT, &sa, &pager_process.orig_sigint);
263 sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
264 sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
265 sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
266 sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
267 }
268
269 /* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
270 */
271 void pager_redirect(void)
272 {
273 if (pager_process.pid)
274 return; /* already running */
275
276 __setup_pager();
277
278 atexit(wait_for_pager);
279 }
280
281 /* Setup pager and redirect output, the pager may be closed by pager_close().
282 */
283 void pager_open(void)
284 {
285 if (pager_process.pid)
286 return; /* already running */
287
288 pager_process.org_out = dup(STDOUT_FILENO);
289 pager_process.org_err = dup(STDERR_FILENO);
290
291 __setup_pager();
292 }
293
294 /* Close pager and restore original std{out,err}.
295 */
296 void pager_close(void)
297 {
298 if (pager_process.pid == 0)
299 return;
300
301 wait_for_pager();
302
303 /* restore original output */
304 dup2(pager_process.org_out, STDOUT_FILENO);
305 dup2(pager_process.org_err, STDERR_FILENO);
306
307 close(pager_process.org_out);
308 close(pager_process.org_err);
309
310 /* restore original segnals setting */
311 sigaction(SIGINT, &pager_process.orig_sigint, NULL);
312 sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
313 sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
314 sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
315 sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
316
317 memset(&pager_process, 0, sizeof(pager_process));
318 }
319
320 #ifdef TEST_PROGRAM_PAGER
321
322 #define MAX 255
323
324 int main(int argc __attribute__ ((__unused__)),
325 char *argv[] __attribute__ ((__unused__)))
326 {
327 int i;
328
329 pager_redirect();
330 for (i = 0; i < MAX; i++)
331 printf("%d\n", i);
332 return EXIT_SUCCESS;
333 }
334 #endif /* TEST_PROGRAM_PAGER */