]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/pager.c
Merge branch 'lsfd--bpf-name#in-flight' of https://github.com/masatake/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 if (cmd->preexec_cb)
89 cmd->preexec_cb();
90
91 execvp(cmd->argv[0], (char *const*) cmd->argv);
92 errexec(cmd->argv[0]);
93 }
94
95 if (cmd->pid < 0) {
96 if (need_in)
97 close_pair(fdin);
98 else if (0 <= cmd->in)
99 close(cmd->in);
100 return -1;
101 }
102
103 if (need_in)
104 close(fdin[0]);
105 else if (0 <= cmd->in)
106 close(cmd->in);
107 return 0;
108 }
109
110 static int wait_or_whine(pid_t pid)
111 {
112 for (;;) {
113 int status, code;
114 pid_t waiting = waitpid(pid, &status, 0);
115
116 if (waiting < 0) {
117 if (errno == EINTR)
118 continue;
119 ul_sig_err(EXIT_FAILURE, "waitpid failed");
120 }
121 if (waiting != pid)
122 return -1;
123 if (WIFSIGNALED(status))
124 return -1;
125
126 if (!WIFEXITED(status))
127 return -1;
128 code = WEXITSTATUS(status);
129 switch (code) {
130 case 127:
131 return -1;
132 case 0:
133 return 0;
134 default:
135 return -1;
136 }
137 }
138 }
139
140 static int finish_command(struct child_process *cmd)
141 {
142 return wait_or_whine(cmd->pid);
143 }
144
145 static void pager_preexec_less(void)
146 {
147 /*
148 * Work around bug in "less" by not starting it until we
149 * have real input
150 */
151 fd_set in, ex;
152
153 FD_ZERO(&in);
154 FD_SET(STDIN_FILENO, &in);
155 ex = in;
156
157 select(STDIN_FILENO + 1, &in, NULL, &ex, NULL);
158
159 if (setenv("LESS", "FRSX", 0) != 0)
160 warn(_("failed to set the %s environment variable"), "LESS");
161 }
162
163 static void wait_for_pager(void)
164 {
165 if (pager_process.pid == 0)
166 return;
167
168 /* signal EOF to pager */
169 close(STDOUT_FILENO);
170 close(STDERR_FILENO);
171 finish_command(&pager_process);
172 }
173
174 static void wait_for_pager_signal(int signo)
175 {
176 wait_for_pager();
177 raise(signo);
178 }
179
180 static int has_command(const char *cmd)
181 {
182 const char *path;
183 char *b, *c, *p, *s;
184 int rc = 0;
185
186 if (!cmd)
187 goto done;
188
189 c = xstrdup(cmd);
190 if (!c)
191 goto done;
192 b = strtok(c, " "); /* cmd may contain options */
193 if (!b)
194 goto cleanup;
195
196 if (*b == '/') {
197 rc = access(b, X_OK) == 0;
198 goto cleanup;
199 }
200
201 path = getenv("PATH");
202 if (!path)
203 goto cleanup;
204 p = xstrdup(path);
205 if (!p)
206 goto cleanup;
207
208 for (s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
209 int fd = open(s, O_RDONLY|O_CLOEXEC);
210 if (fd < 0)
211 continue;
212 rc = faccessat(fd, b, X_OK, 0) == 0;
213 close(fd);
214 if (rc)
215 break;
216 }
217 free(p);
218 cleanup:
219 free(c);
220 done:
221 /*fprintf(stderr, "has PAGER '%s': rc=%d\n", cmd, rc);*/
222 return rc;
223 }
224
225 static void __setup_pager(void)
226 {
227 const char *pager = getenv("PAGER");
228 struct sigaction sa;
229
230 if (!isatty(STDOUT_FILENO))
231 return;
232
233 if (!pager)
234 pager = "less";
235 else if (!*pager || !strcmp(pager, "cat"))
236 return;
237
238 if (!has_command(pager))
239 return;
240
241 /* spawn the pager */
242 pager_argv[2] = pager;
243 pager_process.argv = pager_argv;
244 pager_process.in = -1;
245
246 if (!strncmp(pager, "less", 4))
247 pager_process.preexec_cb = pager_preexec_less;
248 else
249 pager_process.preexec_cb = NULL;
250
251 if (start_command(&pager_process))
252 return;
253
254 /* original process continues, but writes to the pipe */
255 dup2(pager_process.in, STDOUT_FILENO);
256 setvbuf(stdout, NULL, _IOLBF, 0);
257 if (isatty(STDERR_FILENO)) {
258 dup2(pager_process.in, STDERR_FILENO);
259 setvbuf(stderr, NULL, _IOLBF, 0);
260 }
261 close(pager_process.in);
262
263 memset(&sa, 0, sizeof(sa));
264 sa.sa_handler = wait_for_pager_signal;
265
266 /* this makes sure that the parent terminates after the pager */
267 sigaction(SIGINT, &sa, &pager_process.orig_sigint);
268 sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
269 sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
270 sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
271 sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
272 }
273
274 /* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
275 */
276 void pager_redirect(void)
277 {
278 if (pager_process.pid)
279 return; /* already running */
280
281 __setup_pager();
282
283 atexit(wait_for_pager);
284 }
285
286 /* Setup pager and redirect output, the pager may be closed by pager_close().
287 */
288 void pager_open(void)
289 {
290 if (pager_process.pid)
291 return; /* already running */
292
293 pager_process.org_out = dup(STDOUT_FILENO);
294 pager_process.org_err = dup(STDERR_FILENO);
295
296 __setup_pager();
297 }
298
299 /* Close pager and restore original std{out,err}.
300 */
301 void pager_close(void)
302 {
303 if (pager_process.pid == 0)
304 return;
305
306 wait_for_pager();
307
308 /* restore original output */
309 dup2(pager_process.org_out, STDOUT_FILENO);
310 dup2(pager_process.org_err, STDERR_FILENO);
311
312 close(pager_process.org_out);
313 close(pager_process.org_err);
314
315 /* restore original segnals setting */
316 sigaction(SIGINT, &pager_process.orig_sigint, NULL);
317 sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
318 sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
319 sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
320 sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
321
322 memset(&pager_process, 0, sizeof(pager_process));
323 }
324
325 #ifdef TEST_PROGRAM_PAGER
326
327 #define MAX 255
328
329 int main(int argc __attribute__ ((__unused__)),
330 char *argv[] __attribute__ ((__unused__)))
331 {
332 int i;
333
334 pager_redirect();
335 for (i = 0; i < MAX; i++)
336 printf("%d\n", i);
337 return EXIT_SUCCESS;
338 }
339 #endif /* TEST_PROGRAM_PAGER */