blob: 9e09cd52b870cdf140c6ec75fb50b6a8b77b23c8 [file] [log] [blame]
bigbiff bigbiffe60683a2013-02-22 20:55:50 -05001/*
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>
15
16#include "c.h"
17#include "xalloc.h"
18#include "nls.h"
19
20#define NULL_DEVICE "/dev/null"
21
22void setup_pager(void);
23
24static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
25
26struct child_process {
27 const char **argv;
28 pid_t pid;
29 int in;
30 int out;
31 int err;
32 unsigned no_stdin:1;
33 void (*preexec_cb)(void);
34};
35static struct child_process pager_process;
36
37static inline void close_pair(int fd[2])
38{
39 close(fd[0]);
40 close(fd[1]);
41}
42
bigbiff bigbiffe60683a2013-02-22 20:55:50 -050043static int start_command(struct child_process *cmd)
44{
45 int need_in;
46 int fdin[2];
47
48 /*
49 * In case of errors we must keep the promise to close FDs
50 * that have been passed in via ->in and ->out.
51 */
52 need_in = !cmd->no_stdin && cmd->in < 0;
53 if (need_in) {
54 if (pipe(fdin) < 0) {
55 if (cmd->out > 0)
56 close(cmd->out);
57 return -1;
58 }
59 cmd->in = fdin[1];
60 }
61
62 fflush(NULL);
63 cmd->pid = fork();
64 if (!cmd->pid) {
65 if (need_in) {
bigbiff7b4c7a62015-01-01 19:44:14 -050066 dup2(fdin[0], STDIN_FILENO);
bigbiff bigbiffe60683a2013-02-22 20:55:50 -050067 close_pair(fdin);
68 } else if (cmd->in > 0) {
bigbiff7b4c7a62015-01-01 19:44:14 -050069 dup2(cmd->in, STDIN_FILENO);
bigbiff bigbiffe60683a2013-02-22 20:55:50 -050070 close(cmd->in);
71 }
72
73 cmd->preexec_cb();
74 execvp(cmd->argv[0], (char *const*) cmd->argv);
75 exit(127); /* cmd not found */
76 }
77
78 if (cmd->pid < 0) {
79 if (need_in)
80 close_pair(fdin);
81 else if (cmd->in)
82 close(cmd->in);
83 return -1;
84 }
85
86 if (need_in)
87 close(fdin[0]);
88 else if (cmd->in)
89 close(cmd->in);
90 return 0;
91}
92
93static int wait_or_whine(pid_t pid)
94{
95 for (;;) {
96 int status, code;
97 pid_t waiting = waitpid(pid, &status, 0);
98
99 if (waiting < 0) {
100 if (errno == EINTR)
101 continue;
102 err(EXIT_FAILURE, _("waitpid failed (%s)"), strerror(errno));
103 }
104 if (waiting != pid)
105 return -1;
106 if (WIFSIGNALED(status))
107 return -1;
108
109 if (!WIFEXITED(status))
110 return -1;
111 code = WEXITSTATUS(status);
112 switch (code) {
113 case 127:
114 return -1;
115 case 0:
116 return 0;
117 default:
118 return -1;
119 }
120 }
121}
122
123static int finish_command(struct child_process *cmd)
124{
125 return wait_or_whine(cmd->pid);
126}
127
128static void pager_preexec(void)
129{
130 /*
131 * Work around bug in "less" by not starting it until we
132 * have real input
133 */
134 fd_set in;
135
136 FD_ZERO(&in);
bigbiff7b4c7a62015-01-01 19:44:14 -0500137 FD_SET(STDIN_FILENO, &in);
bigbiff bigbiffe60683a2013-02-22 20:55:50 -0500138 select(1, &in, NULL, &in, NULL);
139
140 setenv("LESS", "FRSX", 0);
141}
142
143static void wait_for_pager(void)
144{
145 fflush(stdout);
146 fflush(stderr);
147 /* signal EOF to pager */
bigbiff7b4c7a62015-01-01 19:44:14 -0500148 close(STDOUT_FILENO);
149 close(STDERR_FILENO);
bigbiff bigbiffe60683a2013-02-22 20:55:50 -0500150 finish_command(&pager_process);
151}
152
153static void wait_for_pager_signal(int signo)
154{
155 wait_for_pager();
156 raise(signo);
157}
158
159void setup_pager(void)
160{
161 const char *pager = getenv("PAGER");
162
bigbiff7b4c7a62015-01-01 19:44:14 -0500163 if (!isatty(STDOUT_FILENO))
bigbiff bigbiffe60683a2013-02-22 20:55:50 -0500164 return;
165
166 if (!pager)
167 pager = "less";
168 else if (!*pager || !strcmp(pager, "cat"))
169 return;
170
171 /* spawn the pager */
172 pager_argv[2] = pager;
173 pager_process.argv = pager_argv;
174 pager_process.in = -1;
175 pager_process.preexec_cb = pager_preexec;
176
177 if (start_command(&pager_process))
178 return;
179
180 /* original process continues, but writes to the pipe */
bigbiff7b4c7a62015-01-01 19:44:14 -0500181 dup2(pager_process.in, STDOUT_FILENO);
182 if (isatty(STDERR_FILENO))
183 dup2(pager_process.in, STDERR_FILENO);
bigbiff bigbiffe60683a2013-02-22 20:55:50 -0500184 close(pager_process.in);
185
186 /* this makes sure that the parent terminates after the pager */
187 signal(SIGINT, wait_for_pager_signal);
188 signal(SIGHUP, wait_for_pager_signal);
189 signal(SIGTERM, wait_for_pager_signal);
190 signal(SIGQUIT, wait_for_pager_signal);
191 signal(SIGPIPE, wait_for_pager_signal);
192
193 atexit(wait_for_pager);
194}
195
196#ifdef TEST_PROGRAM
197
198#define MAX 255
199
200int main(int argc __attribute__ ((__unused__)),
201 char *argv[] __attribute__ ((__unused__)))
202{
203 int i;
204
205 setup_pager();
206 for (i = 0; i < MAX; i++)
207 printf("%d\n", i);
208 return EXIT_SUCCESS;
209}
210#endif /* TEST_PROGRAM */