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