blob: 5cf8c03b5ef0fd77a841ce778596b3df3f3c964e [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
43static inline void dup_devnull(int to)
44{
45 int fd = open(NULL_DEVICE, O_RDWR);
46
47 if (fd < 0)
48 err(EXIT_FAILURE, _("cannot open %s"), NULL_DEVICE);
49 dup2(fd, to);
50 close(fd);
51}
52
53static int start_command(struct child_process *cmd)
54{
55 int need_in;
56 int fdin[2];
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) {
76 dup2(fdin[0], 0);
77 close_pair(fdin);
78 } else if (cmd->in > 0) {
79 dup2(cmd->in, 0);
80 close(cmd->in);
81 }
82
83 cmd->preexec_cb();
84 execvp(cmd->argv[0], (char *const*) cmd->argv);
85 exit(127); /* cmd not found */
86 }
87
88 if (cmd->pid < 0) {
89 if (need_in)
90 close_pair(fdin);
91 else if (cmd->in)
92 close(cmd->in);
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 */
144 fd_set in;
145
146 FD_ZERO(&in);
147 FD_SET(0, &in);
148 select(1, &in, NULL, &in, NULL);
149
150 setenv("LESS", "FRSX", 0);
151}
152
153static void wait_for_pager(void)
154{
155 fflush(stdout);
156 fflush(stderr);
157 /* signal EOF to pager */
158 close(1);
159 close(2);
160 finish_command(&pager_process);
161}
162
163static void wait_for_pager_signal(int signo)
164{
165 wait_for_pager();
166 raise(signo);
167}
168
169void setup_pager(void)
170{
171 const char *pager = getenv("PAGER");
172
173 if (!isatty(1))
174 return;
175
176 if (!pager)
177 pager = "less";
178 else if (!*pager || !strcmp(pager, "cat"))
179 return;
180
181 /* spawn the pager */
182 pager_argv[2] = pager;
183 pager_process.argv = pager_argv;
184 pager_process.in = -1;
185 pager_process.preexec_cb = pager_preexec;
186
187 if (start_command(&pager_process))
188 return;
189
190 /* original process continues, but writes to the pipe */
191 dup2(pager_process.in, 1);
192 if (isatty(2))
193 dup2(pager_process.in, 2);
194 close(pager_process.in);
195
196 /* this makes sure that the parent terminates after the pager */
197 signal(SIGINT, wait_for_pager_signal);
198 signal(SIGHUP, wait_for_pager_signal);
199 signal(SIGTERM, wait_for_pager_signal);
200 signal(SIGQUIT, wait_for_pager_signal);
201 signal(SIGPIPE, wait_for_pager_signal);
202
203 atexit(wait_for_pager);
204}
205
206#ifdef TEST_PROGRAM
207
208#define MAX 255
209
210int main(int argc __attribute__ ((__unused__)),
211 char *argv[] __attribute__ ((__unused__)))
212{
213 int i;
214
215 setup_pager();
216 for (i = 0; i < MAX; i++)
217 printf("%d\n", i);
218 return EXIT_SUCCESS;
219}
220#endif /* TEST_PROGRAM */