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 "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 
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
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 
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 
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 
188 
190  interrupted_ = signum;
191 }
192 
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 
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 
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 
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)
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 
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 
350  if (finished_.empty())
351  return NULL;
352  Subprocess* subproc = finished_.front();
353  finished_.pop();
354  return subproc;
355 }
356 
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 }
Subprocess * Add(const std::string &command, bool use_console=false)
SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
Definition: subprocess.h:82
std::queue< Subprocess * > finished_
Definition: subprocess.h:92
bool use_console_
Definition: subprocess.h:74
static void SetInterruptedFlag(int signum)
Subprocess * NextFinished()
void SetCloseOnExec(int fd)
Mark a file descriptor to not be inherited on exec()s.
Definition: util.cc:377
ExitStatus Finish()
Returns ExitSuccess on successful process exit, ExitInterrupted if the process was interrupted...
bool Start(struct SubprocessSet *set, const std::string &command)
struct sigaction old_int_act_
Definition: subprocess.h:106
Subprocess wraps a single async subprocess.
Definition: subprocess.h:42
static bool IsInterrupted()
Definition: subprocess.h:104
static int interrupted_
Store the signal number that causes the interruption.
Definition: subprocess.h:102
struct sigaction old_hup_act_
Definition: subprocess.h:108
ExitStatus
Definition: exit_status.h:18
Subprocess(bool use_console)
sigset_t old_mask_
Definition: subprocess.h:109
void Fatal(const char *msg,...)
Log a fatal message and exit.
Definition: util.cc:59
pid_t pid_
Definition: subprocess.h:72
std::string buf_
Definition: subprocess.h:58
static void HandlePendingInterruption()
char ** environ
const std::string & GetOutput() const
bool Done() const
std::vector< Subprocess * > running_
Definition: subprocess.h:91
struct sigaction old_term_act_
Definition: subprocess.h:107