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 
34 extern char** environ;
35 
36 #include "util.h"
37 
38 using namespace std;
39 
40 namespace {
41  ExitStatus ParseExitStatus(int status);
42 }
43 
44 Subprocess::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 
56 bool 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 
164 bool 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 
186 namespace {
187 
188 ExitStatus 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 
214 bool 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 
222 const string& Subprocess::GetOutput() const {
223  return buf_;
224 }
225 
226 volatile sig_atomic_t SubprocessSet::interrupted_;
227 volatile sig_atomic_t SubprocessSet::s_sigchld_received;
228 
230  interrupted_ = signum;
231 }
232 
233 void SubprocessSet::SigChldHandler(int signo, siginfo_t* info, void* context) {
234  s_sigchld_received = 1;
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.
284  if (!s_sigchld_received)
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 
311 Subprocess *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
322 bool SubprocessSet::DoWork() {
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;
344  s_sigchld_received = 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;
407  s_sigchld_received = 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
SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
Definition: subprocess.h:101
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
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