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 wait_for_pager();
175 raise(signo);
176 }
177
178 static int has_command(const char *cmd)
179 {
180 const char *path;
181 char *p, *s;
182 int rc = 0;
183
184 if (!cmd)
185 goto done;
186 if (*cmd == '/') {
187 rc = access(cmd, X_OK) == 0;
188 goto done;
189 }
190
191 path = getenv("PATH");
192 if (!path)
193 goto done;
194 p = xstrdup(path);
195 if (!p)
196 goto done;
197
198 for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) {
199 int fd = open(s, O_RDONLY|O_CLOEXEC);
200 if (fd < 0)
201 continue;
202 rc = faccessat(fd, cmd, X_OK, 0) == 0;
203 close(fd);
204 if (rc)
205 break;
206 }
207 free(p);
208 done:
209 /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/
210 return rc;
211 }
212
213 static void __setup_pager(void)
214 {
215 const char *pager = getenv("PAGER");
216 struct sigaction sa;
217
218 if (!isatty(STDOUT_FILENO))
219 return;
220
221 if (!pager)
222 pager = "less";
223 else if (!*pager || !strcmp(pager, "cat"))
224 return;
225
226 if (!has_command(pager))
227 return;
228
229 /* spawn the pager */
230 pager_argv[2] = pager;
231 pager_process.argv = pager_argv;
232 pager_process.in = -1;
233 pager_process.preexec_cb = pager_preexec;
234
235 if (start_command(&pager_process))
236 return;
237
238 /* original process continues, but writes to the pipe */
239 dup2(pager_process.in, STDOUT_FILENO);
240 setvbuf(stdout, NULL, _IOLBF, 0);
241 if (isatty(STDERR_FILENO)) {
242 dup2(pager_process.in, STDERR_FILENO);
243 setvbuf(stderr, NULL, _IOLBF, 0);
244 }
245 close(pager_process.in);
246
247 memset(&sa, 0, sizeof(sa));
248 sa.sa_handler = wait_for_pager_signal;
249
250 /* this makes sure that the parent terminates after the pager */
251 sigaction(SIGINT, &sa, &pager_process.orig_sigint);
252 sigaction(SIGHUP, &sa, &pager_process.orig_sighup);
253 sigaction(SIGTERM, &sa, &pager_process.orig_sigterm);
254 sigaction(SIGQUIT, &sa, &pager_process.orig_sigquit);
255 sigaction(SIGPIPE, &sa, &pager_process.orig_sigpipe);
256 }
257
258 /* Setup pager and redirects output to the $PAGER. The pager is closed at exit.
259 */
260 void pager_redirect(void)
261 {
262 if (pager_process.pid)
263 return; /* already running */
264
265 __setup_pager();
266
267 atexit(wait_for_pager);
268 }
269
270 /* Setup pager and redirect output, the pager may be closed by pager_close().
271 */
272 void pager_open(void)
273 {
274 if (pager_process.pid)
275 return; /* already running */
276
277 pager_process.org_out = dup(STDOUT_FILENO);
278 pager_process.org_err = dup(STDERR_FILENO);
279
280 __setup_pager();
281 }
282
283 /* Close pager and restore original std{out,err}.
284 */
285 void pager_close(void)
286 {
287 if (pager_process.pid == 0)
288 return;
289
290 wait_for_pager();
291
292 /* restore original output */
293 dup2(pager_process.org_out, STDOUT_FILENO);
294 dup2(pager_process.org_err, STDERR_FILENO);
295
296 close(pager_process.org_out);
297 close(pager_process.org_err);
298
299 /* restore original segnals setting */
300 sigaction(SIGINT, &pager_process.orig_sigint, NULL);
301 sigaction(SIGHUP, &pager_process.orig_sighup, NULL);
302 sigaction(SIGTERM, &pager_process.orig_sigterm, NULL);
303 sigaction(SIGQUIT, &pager_process.orig_sigquit, NULL);
304 sigaction(SIGPIPE, &pager_process.orig_sigpipe, NULL);
305
306 memset(&pager_process, 0, sizeof(pager_process));
307 }
308
309 #ifdef TEST_PROGRAM_PAGER
310
311 #define MAX 255
312
313 int main(int argc __attribute__ ((__unused__)),
314 char *argv[] __attribute__ ((__unused__)))
315 {
316 int i;
317
318 pager_redirect();
319 for (i = 0; i < MAX; i++)
320 printf("%d\n", i);
321 return EXIT_SUCCESS;
322 }
323 #endif /* TEST_PROGRAM_PAGER */