Ninja
subprocess-posix.cc
Go to the documentation of this file.
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 "exit_status.h"
16#include "subprocess.h"
17
18#include <sys/select.h>
19#include <assert.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <unistd.h>
23#include <stdio.h>
24#include <string.h>
25#include <sys/wait.h>
26#include <spawn.h>
27
28#if defined(USE_PPOLL)
29#include <poll.h>
30#else
31#include <sys/select.h>
32#endif
33
34extern char** environ;
35
36#include "util.h"
37
38using namespace std;
39
40namespace {
41 ExitStatus ParseExitStatus(int status);
42}
43
44Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1),
45 use_console_(use_console) {
46}
47
49 if (fd_ >= 0)
50 close(fd_);
51 // Reap child if forgotten.
52 if (pid_ != -1)
53 Finish();
54}
55
56bool Subprocess::Start(SubprocessSet* set, const string& command) {
57 int subproc_stdout_fd = -1;
58 if (use_console_) {
59 fd_ = -1;
60 } else {
61 int output_pipe[2];
62 if (pipe(output_pipe) < 0)
63 Fatal("pipe: %s", strerror(errno));
64 fd_ = output_pipe[0];
65 subproc_stdout_fd = output_pipe[1];
66#if !defined(USE_PPOLL)
67 // If available, we use ppoll in DoWork(); otherwise we use pselect
68 // and so must avoid overly-large FDs.
69 if (fd_ >= static_cast<int>(FD_SETSIZE))
70 Fatal("pipe: %s", strerror(EMFILE));
71#endif // !USE_PPOLL
73 }
74
75 posix_spawn_file_actions_t action;
76 int err = posix_spawn_file_actions_init(&action);
77 if (err != 0)
78 Fatal("posix_spawn_file_actions_init: %s", strerror(err));
79
80 if (!use_console_) {
81 err = posix_spawn_file_actions_addclose(&action, fd_);
82 if (err != 0)
83 Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
84 }
85
86 posix_spawnattr_t attr;
87 err = posix_spawnattr_init(&attr);
88 if (err != 0)
89 Fatal("posix_spawnattr_init: %s", strerror(err));
90
91 short flags = 0;
92
93 flags |= POSIX_SPAWN_SETSIGMASK;
94 err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
95 if (err != 0)
96 Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
97 // Signals which are set to be caught in the calling process image are set to
98 // default action in the new process image, so no explicit
99 // POSIX_SPAWN_SETSIGDEF parameter is needed.
100
101 if (!use_console_) {
102 // Put the child in its own process group, so ctrl-c won't reach it.
103 flags |= POSIX_SPAWN_SETPGROUP;
104 // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
105
106 // Open /dev/null over stdin.
107 err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
108 0);
109 if (err != 0) {
110 Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
111 }
112
113 err = posix_spawn_file_actions_adddup2(&action, subproc_stdout_fd, 1);
114 if (err != 0)
115 Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
116 err = posix_spawn_file_actions_adddup2(&action, subproc_stdout_fd, 2);
117 if (err != 0)
118 Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
119 err = posix_spawn_file_actions_addclose(&action, subproc_stdout_fd);
120 if (err != 0)
121 Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
122 }
123
124#ifdef POSIX_SPAWN_USEVFORK
125 flags |= POSIX_SPAWN_USEVFORK;
126#endif
127
128 err = posix_spawnattr_setflags(&attr, flags);
129 if (err != 0)
130 Fatal("posix_spawnattr_setflags: %s", strerror(err));
131
132 const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
133 err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
134 const_cast<char**>(spawned_args), environ);
135 if (err != 0)
136 Fatal("posix_spawn: %s", strerror(err));
137
138 err = posix_spawnattr_destroy(&attr);
139 if (err != 0)
140 Fatal("posix_spawnattr_destroy: %s", strerror(err));
141 err = posix_spawn_file_actions_destroy(&action);
142 if (err != 0)
143 Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
144
145 if (!use_console_)
146 close(subproc_stdout_fd);
147 return true;
148}
149
151 char buf[4 << 10];
152 ssize_t len = read(fd_, buf, sizeof(buf));
153 if (len > 0) {
154 buf_.append(buf, len);
155 } else {
156 if (len < 0)
157 Fatal("read: %s", strerror(errno));
158 close(fd_);
159 fd_ = -1;
160 }
161}
162
163
164bool Subprocess::TryFinish(int waitpid_options) {
165 assert(pid_ != -1);
166 int status, ret;
167 while ((ret = waitpid(pid_, &status, waitpid_options)) < 0) {
168 if (errno != EINTR)
169 Fatal("waitpid(%d): %s", pid_, strerror(errno));
170 }
171 if (ret == 0)
172 return false; // Subprocess is alive (WNOHANG-only).
173 pid_ = -1;
174 exit_status_ = ParseExitStatus(status);
175 return true; // Subprocess has terminated.
176}
177
179 if (pid_ != -1) {
180 TryFinish(0);
181 assert(pid_ == -1);
182 }
183 return exit_status_;
184}
185
186namespace {
187
188ExitStatus ParseExitStatus(int status) {
189#ifdef _AIX
190 if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) {
191 // Map the shell's exit code used for signal failure (128 + signal) to the
192 // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike
193 // other systems, uses a different bit layout.
194 int signal = WEXITSTATUS(status) & 0x7f;
195 status = (signal << 16) | signal;
196 }
197#endif
198
199 if (WIFEXITED(status)) {
200 // propagate the status transparently
201 return static_cast<ExitStatus>(WEXITSTATUS(status));
202 }
203 if (WIFSIGNALED(status)) {
204 if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM
205 || WTERMSIG(status) == SIGHUP)
206 return ExitInterrupted;
207 }
208 // At this point, we exit with any other signal+128
209 return static_cast<ExitStatus>(status + 128);
210}
211
212} // anonymous namespace
213
214bool Subprocess::Done() const {
215 // Console subprocesses share console with ninja, and we consider them done
216 // when they exit.
217 // For other processes, we consider them done when we have consumed all their
218 // output and closed their associated pipe.
219 return (use_console_ && pid_ == -1) || (!use_console_ && fd_ == -1);
220}
221
222const string& Subprocess::GetOutput() const {
223 return buf_;
224}
225
226volatile sig_atomic_t SubprocessSet::interrupted_;
227volatile sig_atomic_t SubprocessSet::s_sigchld_received;
228
230 interrupted_ = signum;
231}
232
233void SubprocessSet::SigChldHandler(int signo, siginfo_t* info, void* context) {
235}
236
238 sigset_t pending;
239 sigemptyset(&pending);
240 if (sigpending(&pending) == -1) {
241 perror("ninja: sigpending");
242 return;
243 }
244 if (sigismember(&pending, SIGINT))
245 interrupted_ = SIGINT;
246 else if (sigismember(&pending, SIGTERM))
247 interrupted_ = SIGTERM;
248 else if (sigismember(&pending, SIGHUP))
249 interrupted_ = SIGHUP;
250}
251
253 // Block all these signals.
254 // Their handlers will only be enabled during ppoll/pselect().
255 sigset_t set;
256 sigemptyset(&set);
257 sigaddset(&set, SIGINT);
258 sigaddset(&set, SIGTERM);
259 sigaddset(&set, SIGHUP);
260 sigaddset(&set, SIGCHLD);
261 if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0)
262 Fatal("sigprocmask: %s", strerror(errno));
263
264 struct sigaction act;
265 memset(&act, 0, sizeof(act));
266 act.sa_handler = SetInterruptedFlag;
267 if (sigaction(SIGINT, &act, &old_int_act_) < 0)
268 Fatal("sigaction: %s", strerror(errno));
269 if (sigaction(SIGTERM, &act, &old_term_act_) < 0)
270 Fatal("sigaction: %s", strerror(errno));
271 if (sigaction(SIGHUP, &act, &old_hup_act_) < 0)
272 Fatal("sigaction: %s", strerror(errno));
273
274 memset(&act, 0, sizeof(act));
275 act.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
276 act.sa_sigaction = SigChldHandler;
277 if (sigaction(SIGCHLD, &act, &old_chld_act_) < 0)
278 Fatal("sigaction: %s", strerror(errno));
279}
280
281// Reaps console processes that have exited and moves them from the running set
282// to the finished set.
285 return;
286 for (auto i = running_.begin(); i != running_.end(); ) {
287 if ((*i)->use_console_ && (*i)->TryFinish(WNOHANG)) {
288 finished_.push(*i);
289 i = running_.erase(i);
290 } else {
291 ++i;
292 }
293 }
294}
295
297 Clear();
298
299 if (sigaction(SIGINT, &old_int_act_, 0) < 0)
300 Fatal("sigaction: %s", strerror(errno));
301 if (sigaction(SIGTERM, &old_term_act_, 0) < 0)
302 Fatal("sigaction: %s", strerror(errno));
303 if (sigaction(SIGHUP, &old_hup_act_, 0) < 0)
304 Fatal("sigaction: %s", strerror(errno));
305 if (sigaction(SIGCHLD, &old_chld_act_, 0) < 0)
306 Fatal("sigaction: %s", strerror(errno));
307 if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0)
308 Fatal("sigprocmask: %s", strerror(errno));
309}
310
311Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
312 Subprocess *subprocess = new Subprocess(use_console);
313 if (!subprocess->Start(this, command)) {
314 delete subprocess;
315 return 0;
316 }
317 running_.push_back(subprocess);
318 return subprocess;
319}
320
321#ifdef USE_PPOLL
323 vector<pollfd> fds;
324 nfds_t nfds = 0;
325
326 for (vector<Subprocess*>::iterator i = running_.begin();
327 i != running_.end(); ++i) {
328 int fd = (*i)->fd_;
329 if (fd < 0)
330 continue;
331 pollfd pfd = { fd, POLLIN | POLLPRI, 0 };
332 fds.push_back(pfd);
333 ++nfds;
334 }
335 if (nfds == 0) {
336 // Add a dummy entry to prevent using an empty pollfd vector.
337 // ppoll() allows to do this by setting fd < 0.
338 pollfd pfd = { -1, 0, 0 };
339 fds.push_back(pfd);
340 ++nfds;
341 }
342
343 interrupted_ = 0;
345 int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
346 // Note: This can remove console processes from the running set, but that is
347 // not a problem for the pollfd set, as console processes are not part of the
348 // pollfd set (they don't have a fd).
350 if (ret == -1) {
351 if (errno != EINTR) {
352 perror("ninja: ppoll");
353 return false;
354 }
355 return IsInterrupted();
356 }
357
358 // ppoll/pselect prioritizes file descriptor events over a signal delivery.
359 // However, if the user is trying to quit ninja, we should react as fast as
360 // possible.
362 if (IsInterrupted())
363 return true;
364
365 // Iterate through both the pollfd set and the running set.
366 // All valid fds in the running set are in the pollfd, in the same order.
367 nfds_t cur_nfd = 0;
368 for (vector<Subprocess*>::iterator i = running_.begin();
369 i != running_.end(); ) {
370 int fd = (*i)->fd_;
371 if (fd < 0) {
372 ++i;
373 continue;
374 }
375 assert(fd == fds[cur_nfd].fd);
376 if (fds[cur_nfd++].revents) {
377 (*i)->OnPipeReady();
378 if ((*i)->Done()) {
379 finished_.push(*i);
380 i = running_.erase(i);
381 continue;
382 }
383 }
384 ++i;
385 }
386
387 return IsInterrupted();
388}
389
390#else // !defined(USE_PPOLL)
392 fd_set set;
393 int nfds = 0;
394 FD_ZERO(&set);
395
396 for (vector<Subprocess*>::iterator i = running_.begin();
397 i != running_.end(); ++i) {
398 int fd = (*i)->fd_;
399 if (fd >= 0) {
400 FD_SET(fd, &set);
401 if (nfds < fd+1)
402 nfds = fd+1;
403 }
404 }
405
406 interrupted_ = 0;
408 int ret = pselect(nfds, (nfds > 0 ? &set : nullptr), 0, 0, 0, &old_mask_);
410 if (ret == -1) {
411 if (errno != EINTR) {
412 perror("ninja: pselect");
413 return false;
414 }
415 return IsInterrupted();
416 }
417
418 // ppoll/pselect prioritizes file descriptor events over a signal delivery.
419 // However, if the user is trying to quit ninja, we should react as fast as
420 // possible.
422 if (IsInterrupted())
423 return true;
424
425 for (vector<Subprocess*>::iterator i = running_.begin();
426 i != running_.end(); ) {
427 int fd = (*i)->fd_;
428 if (fd >= 0 && FD_ISSET(fd, &set)) {
429 (*i)->OnPipeReady();
430 if ((*i)->Done()) {
431 finished_.push(*i);
432 i = running_.erase(i);
433 continue;
434 }
435 }
436 ++i;
437 }
438
439 return IsInterrupted();
440}
441#endif // !defined(USE_PPOLL)
442
444 if (finished_.empty())
445 return NULL;
446 Subprocess* subproc = finished_.front();
447 finished_.pop();
448 return subproc;
449}
450
452 for (vector<Subprocess*>::iterator i = running_.begin();
453 i != running_.end(); ++i)
454 // Since the foreground process is in our process group, it will receive
455 // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us.
456 if (!(*i)->use_console_)
457 kill(-(*i)->pid_, interrupted_);
458 for (vector<Subprocess*>::iterator i = running_.begin();
459 i != running_.end(); ++i)
460 delete *i;
461 running_.clear();
462}
ExitStatus
Definition exit_status.h:27
@ ExitInterrupted
Definition exit_status.h:30
Definition hash_map.h:26
void CheckConsoleProcessTerminated()
std::queue< Subprocess * > finished_
Definition subprocess.h:111
static void HandlePendingInterruption()
Subprocess * NextFinished()
static volatile sig_atomic_t interrupted_
Store the signal number that causes the interruption.
Definition subprocess.h:122
static volatile sig_atomic_t s_sigchld_received
Initialized to 0 before ppoll/pselect().
Definition subprocess.h:129
struct sigaction old_hup_act_
Definition subprocess.h:134
static void SetInterruptedFlag(int signum)
sigset_t old_mask_
Definition subprocess.h:136
struct sigaction old_chld_act_
Definition subprocess.h:135
std::vector< Subprocess * > running_
Definition subprocess.h:110
struct sigaction old_int_act_
Definition subprocess.h:132
struct sigaction old_term_act_
Definition subprocess.h:133
static void SigChldHandler(int signo, siginfo_t *info, void *context)
static bool IsInterrupted()
Whether ninja should quit. Set on SIGINT, SIGTERM or SIGHUP reception.
Definition subprocess.h:124
Subprocess * Add(const std::string &command, bool use_console=false)
Subprocess wraps a single async subprocess.
Definition subprocess.h:42
bool Start(struct SubprocessSet *set, const std::string &command)
bool TryFinish(int waitpid_options)
Call waitpid() on the subprocess with the provided options and update the pid_ and exit_status_ field...
bool Done() const
ExitStatus exit_status_
In POSIX platforms it is necessary to use waitpid(WNOHANG) to know whether a certain subprocess has f...
Definition subprocess.h:86
Subprocess(bool use_console)
int fd_
The file descriptor that will be used in ppoll/pselect() for this process, if any.
Definition subprocess.h:78
bool use_console_
Definition subprocess.h:93
ExitStatus Finish()
Returns ExitSuccess on successful process exit, ExitInterrupted if the process was interrupted,...
const std::string & GetOutput() const
friend struct SubprocessSet
Definition subprocess.h:95
std::string buf_
Definition subprocess.h:58
pid_t pid_
PID of the subprocess. Set to -1 when the subprocess is reaped.
Definition subprocess.h:80
char ** environ
void SetCloseOnExec(int fd)
Mark a file descriptor to not be inherited on exec()s.
Definition util.cc:480
void Fatal(const char *msg,...)
Log a fatal message and exit.
Definition util.cc:67