]>
Commit | Line | Data |
---|---|---|
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 |
25 | static const char *pager_argv[] = { "sh", "-c", NULL, NULL }; |
26 | ||
27 | struct 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 | 45 | static struct child_process pager_process; |
35717a57 DB |
46 | |
47 | static inline void close_pair(int fd[2]) | |
48 | { | |
49 | close(fd[0]); | |
50 | close(fd[1]); | |
51 | } | |
52 | ||
35717a57 DB |
53 | static 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 | ||
103 | static 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 | ||
133 | static int finish_command(struct child_process *cmd) | |
134 | { | |
135 | return wait_or_whine(cmd->pid); | |
136 | } | |
137 | ||
138 | static 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 | ||
156 | static 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 | ||
169 | static void wait_for_pager_signal(int signo) | |
170 | { | |
171 | wait_for_pager(); | |
172 | raise(signo); | |
173 | } | |
174 | ||
535a4090 KZ |
175 | static 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); | |
205 | done: | |
206 | /*fprintf(stderr, "has PAGER %s rc=%d\n", cmd, rc);*/ | |
207 | return rc; | |
208 | } | |
209 | ||
e215d467 | 210 | static 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 | */ | |
254 | void 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 | */ | |
266 | void 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 | */ | |
279 | void 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 |
307 | int 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 */ |