1 | // Copyright 2012 Google Inc. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | #include "subprocess.h" |
16 | |
17 | #include <sys/select.h> |
18 | #include <assert.h> |
19 | #include <errno.h> |
20 | #include <fcntl.h> |
21 | #include <unistd.h> |
22 | #include <stdio.h> |
23 | #include <string.h> |
24 | #include <sys/wait.h> |
25 | #include <spawn.h> |
26 | |
27 | #if defined(USE_PPOLL) |
28 | #include <poll.h> |
29 | #else |
30 | #include <sys/select.h> |
31 | #endif |
32 | |
33 | extern char** environ; |
34 | |
35 | #include "util.h" |
36 | |
37 | using namespace std; |
38 | |
39 | Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1), |
40 | use_console_(use_console) { |
41 | } |
42 | |
43 | Subprocess::~Subprocess() { |
44 | if (fd_ >= 0) |
45 | close(fd_); |
46 | // Reap child if forgotten. |
47 | if (pid_ != -1) |
48 | Finish(); |
49 | } |
50 | |
51 | bool Subprocess::Start(SubprocessSet* set, const string& command) { |
52 | int output_pipe[2]; |
53 | if (pipe(output_pipe) < 0) |
54 | Fatal("pipe: %s" , strerror(errno)); |
55 | fd_ = output_pipe[0]; |
56 | #if !defined(USE_PPOLL) |
57 | // If available, we use ppoll in DoWork(); otherwise we use pselect |
58 | // and so must avoid overly-large FDs. |
59 | if (fd_ >= static_cast<int>(FD_SETSIZE)) |
60 | Fatal("pipe: %s" , strerror(EMFILE)); |
61 | #endif // !USE_PPOLL |
62 | SetCloseOnExec(fd_); |
63 | |
64 | posix_spawn_file_actions_t action; |
65 | int err = posix_spawn_file_actions_init(&action); |
66 | if (err != 0) |
67 | Fatal("posix_spawn_file_actions_init: %s" , strerror(err)); |
68 | |
69 | err = posix_spawn_file_actions_addclose(&action, output_pipe[0]); |
70 | if (err != 0) |
71 | Fatal("posix_spawn_file_actions_addclose: %s" , strerror(err)); |
72 | |
73 | posix_spawnattr_t attr; |
74 | err = posix_spawnattr_init(&attr); |
75 | if (err != 0) |
76 | Fatal("posix_spawnattr_init: %s" , strerror(err)); |
77 | |
78 | short flags = 0; |
79 | |
80 | flags |= POSIX_SPAWN_SETSIGMASK; |
81 | err = posix_spawnattr_setsigmask(&attr, &set->old_mask_); |
82 | if (err != 0) |
83 | Fatal("posix_spawnattr_setsigmask: %s" , strerror(err)); |
84 | // Signals which are set to be caught in the calling process image are set to |
85 | // default action in the new process image, so no explicit |
86 | // POSIX_SPAWN_SETSIGDEF parameter is needed. |
87 | |
88 | if (!use_console_) { |
89 | // Put the child in its own process group, so ctrl-c won't reach it. |
90 | flags |= POSIX_SPAWN_SETPGROUP; |
91 | // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default. |
92 | |
93 | // Open /dev/null over stdin. |
94 | err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null" , O_RDONLY, |
95 | 0); |
96 | if (err != 0) { |
97 | Fatal("posix_spawn_file_actions_addopen: %s" , strerror(err)); |
98 | } |
99 | |
100 | err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1); |
101 | if (err != 0) |
102 | Fatal("posix_spawn_file_actions_adddup2: %s" , strerror(err)); |
103 | err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2); |
104 | if (err != 0) |
105 | Fatal("posix_spawn_file_actions_adddup2: %s" , strerror(err)); |
106 | err = posix_spawn_file_actions_addclose(&action, output_pipe[1]); |
107 | if (err != 0) |
108 | Fatal("posix_spawn_file_actions_addclose: %s" , strerror(err)); |
109 | // In the console case, output_pipe is still inherited by the child and |
110 | // closed when the subprocess finishes, which then notifies ninja. |
111 | } |
112 | #ifdef POSIX_SPAWN_USEVFORK |
113 | flags |= POSIX_SPAWN_USEVFORK; |
114 | #endif |
115 | |
116 | err = posix_spawnattr_setflags(&attr, flags); |
117 | if (err != 0) |
118 | Fatal("posix_spawnattr_setflags: %s" , strerror(err)); |
119 | |
120 | const char* spawned_args[] = { "/bin/sh" , "-c" , command.c_str(), NULL }; |
121 | err = posix_spawn(&pid_, "/bin/sh" , &action, &attr, |
122 | const_cast<char**>(spawned_args), environ); |
123 | if (err != 0) |
124 | Fatal("posix_spawn: %s" , strerror(err)); |
125 | |
126 | err = posix_spawnattr_destroy(&attr); |
127 | if (err != 0) |
128 | Fatal("posix_spawnattr_destroy: %s" , strerror(err)); |
129 | err = posix_spawn_file_actions_destroy(&action); |
130 | if (err != 0) |
131 | Fatal("posix_spawn_file_actions_destroy: %s" , strerror(err)); |
132 | |
133 | close(output_pipe[1]); |
134 | return true; |
135 | } |
136 | |
137 | void Subprocess::OnPipeReady() { |
138 | char buf[4 << 10]; |
139 | ssize_t len = read(fd_, buf, sizeof(buf)); |
140 | if (len > 0) { |
141 | buf_.append(buf, len); |
142 | } else { |
143 | if (len < 0) |
144 | Fatal("read: %s" , strerror(errno)); |
145 | close(fd_); |
146 | fd_ = -1; |
147 | } |
148 | } |
149 | |
150 | ExitStatus Subprocess::Finish() { |
151 | assert(pid_ != -1); |
152 | int status; |
153 | if (waitpid(pid_, &status, 0) < 0) |
154 | Fatal("waitpid(%d): %s" , pid_, strerror(errno)); |
155 | pid_ = -1; |
156 | |
157 | #ifdef _AIX |
158 | if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) { |
159 | // Map the shell's exit code used for signal failure (128 + signal) to the |
160 | // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike |
161 | // other systems, uses a different bit layout. |
162 | int signal = WEXITSTATUS(status) & 0x7f; |
163 | status = (signal << 16) | signal; |
164 | } |
165 | #endif |
166 | |
167 | if (WIFEXITED(status)) { |
168 | int exit = WEXITSTATUS(status); |
169 | if (exit == 0) |
170 | return ExitSuccess; |
171 | } else if (WIFSIGNALED(status)) { |
172 | if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM |
173 | || WTERMSIG(status) == SIGHUP) |
174 | return ExitInterrupted; |
175 | } |
176 | return ExitFailure; |
177 | } |
178 | |
179 | bool Subprocess::Done() const { |
180 | return fd_ == -1; |
181 | } |
182 | |
183 | const string& Subprocess::GetOutput() const { |
184 | return buf_; |
185 | } |
186 | |
187 | int SubprocessSet::interrupted_; |
188 | |
189 | void SubprocessSet::SetInterruptedFlag(int signum) { |
190 | interrupted_ = signum; |
191 | } |
192 | |
193 | void SubprocessSet::HandlePendingInterruption() { |
194 | sigset_t pending; |
195 | sigemptyset(&pending); |
196 | if (sigpending(&pending) == -1) { |
197 | perror("ninja: sigpending" ); |
198 | return; |
199 | } |
200 | if (sigismember(&pending, SIGINT)) |
201 | interrupted_ = SIGINT; |
202 | else if (sigismember(&pending, SIGTERM)) |
203 | interrupted_ = SIGTERM; |
204 | else if (sigismember(&pending, SIGHUP)) |
205 | interrupted_ = SIGHUP; |
206 | } |
207 | |
208 | SubprocessSet::SubprocessSet() { |
209 | sigset_t set; |
210 | sigemptyset(&set); |
211 | sigaddset(&set, SIGINT); |
212 | sigaddset(&set, SIGTERM); |
213 | sigaddset(&set, SIGHUP); |
214 | if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0) |
215 | Fatal("sigprocmask: %s" , strerror(errno)); |
216 | |
217 | struct sigaction act; |
218 | memset(&act, 0, sizeof(act)); |
219 | act.sa_handler = SetInterruptedFlag; |
220 | if (sigaction(SIGINT, &act, &old_int_act_) < 0) |
221 | Fatal("sigaction: %s" , strerror(errno)); |
222 | if (sigaction(SIGTERM, &act, &old_term_act_) < 0) |
223 | Fatal("sigaction: %s" , strerror(errno)); |
224 | if (sigaction(SIGHUP, &act, &old_hup_act_) < 0) |
225 | Fatal("sigaction: %s" , strerror(errno)); |
226 | } |
227 | |
228 | SubprocessSet::~SubprocessSet() { |
229 | Clear(); |
230 | |
231 | if (sigaction(SIGINT, &old_int_act_, 0) < 0) |
232 | Fatal("sigaction: %s" , strerror(errno)); |
233 | if (sigaction(SIGTERM, &old_term_act_, 0) < 0) |
234 | Fatal("sigaction: %s" , strerror(errno)); |
235 | if (sigaction(SIGHUP, &old_hup_act_, 0) < 0) |
236 | Fatal("sigaction: %s" , strerror(errno)); |
237 | if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0) |
238 | Fatal("sigprocmask: %s" , strerror(errno)); |
239 | } |
240 | |
241 | Subprocess *SubprocessSet::Add(const string& command, bool use_console) { |
242 | Subprocess *subprocess = new Subprocess(use_console); |
243 | if (!subprocess->Start(this, command)) { |
244 | delete subprocess; |
245 | return 0; |
246 | } |
247 | running_.push_back(subprocess); |
248 | return subprocess; |
249 | } |
250 | |
251 | #ifdef USE_PPOLL |
252 | bool SubprocessSet::DoWork() { |
253 | vector<pollfd> fds; |
254 | nfds_t nfds = 0; |
255 | |
256 | for (vector<Subprocess*>::iterator i = running_.begin(); |
257 | i != running_.end(); ++i) { |
258 | int fd = (*i)->fd_; |
259 | if (fd < 0) |
260 | continue; |
261 | pollfd pfd = { fd, POLLIN | POLLPRI, 0 }; |
262 | fds.push_back(pfd); |
263 | ++nfds; |
264 | } |
265 | |
266 | interrupted_ = 0; |
267 | int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_); |
268 | if (ret == -1) { |
269 | if (errno != EINTR) { |
270 | perror("ninja: ppoll" ); |
271 | return false; |
272 | } |
273 | return IsInterrupted(); |
274 | } |
275 | |
276 | HandlePendingInterruption(); |
277 | if (IsInterrupted()) |
278 | return true; |
279 | |
280 | nfds_t cur_nfd = 0; |
281 | for (vector<Subprocess*>::iterator i = running_.begin(); |
282 | i != running_.end(); ) { |
283 | int fd = (*i)->fd_; |
284 | if (fd < 0) |
285 | continue; |
286 | assert(fd == fds[cur_nfd].fd); |
287 | if (fds[cur_nfd++].revents) { |
288 | (*i)->OnPipeReady(); |
289 | if ((*i)->Done()) { |
290 | finished_.push(*i); |
291 | i = running_.erase(i); |
292 | continue; |
293 | } |
294 | } |
295 | ++i; |
296 | } |
297 | |
298 | return IsInterrupted(); |
299 | } |
300 | |
301 | #else // !defined(USE_PPOLL) |
302 | bool SubprocessSet::DoWork() { |
303 | fd_set set; |
304 | int nfds = 0; |
305 | FD_ZERO(&set); |
306 | |
307 | for (vector<Subprocess*>::iterator i = running_.begin(); |
308 | i != running_.end(); ++i) { |
309 | int fd = (*i)->fd_; |
310 | if (fd >= 0) { |
311 | FD_SET(fd, &set); |
312 | if (nfds < fd+1) |
313 | nfds = fd+1; |
314 | } |
315 | } |
316 | |
317 | interrupted_ = 0; |
318 | int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_); |
319 | if (ret == -1) { |
320 | if (errno != EINTR) { |
321 | perror("ninja: pselect" ); |
322 | return false; |
323 | } |
324 | return IsInterrupted(); |
325 | } |
326 | |
327 | HandlePendingInterruption(); |
328 | if (IsInterrupted()) |
329 | return true; |
330 | |
331 | for (vector<Subprocess*>::iterator i = running_.begin(); |
332 | i != running_.end(); ) { |
333 | int fd = (*i)->fd_; |
334 | if (fd >= 0 && FD_ISSET(fd, &set)) { |
335 | (*i)->OnPipeReady(); |
336 | if ((*i)->Done()) { |
337 | finished_.push(*i); |
338 | i = running_.erase(i); |
339 | continue; |
340 | } |
341 | } |
342 | ++i; |
343 | } |
344 | |
345 | return IsInterrupted(); |
346 | } |
347 | #endif // !defined(USE_PPOLL) |
348 | |
349 | Subprocess* SubprocessSet::NextFinished() { |
350 | if (finished_.empty()) |
351 | return NULL; |
352 | Subprocess* subproc = finished_.front(); |
353 | finished_.pop(); |
354 | return subproc; |
355 | } |
356 | |
357 | void SubprocessSet::Clear() { |
358 | for (vector<Subprocess*>::iterator i = running_.begin(); |
359 | i != running_.end(); ++i) |
360 | // Since the foreground process is in our process group, it will receive |
361 | // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us. |
362 | if (!(*i)->use_console_) |
363 | kill(-(*i)->pid_, interrupted_); |
364 | for (vector<Subprocess*>::iterator i = running_.begin(); |
365 | i != running_.end(); ++i) |
366 | delete *i; |
367 | running_.clear(); |
368 | } |
369 | |