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 <poll.h>
22 #include <unistd.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/wait.h>
26 #include <spawn.h>
27 
28 extern char** environ;
29 
30 #include "util.h"
31 
32 Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1),
33  use_console_(use_console) {
34 }
35 
37  if (fd_ >= 0)
38  close(fd_);
39  // Reap child if forgotten.
40  if (pid_ != -1)
41  Finish();
42 }
43 
44 bool Subprocess::Start(SubprocessSet* set, const string& command) {
45  int output_pipe[2];
46  if (pipe(output_pipe) < 0)
47  Fatal("pipe: %s", strerror(errno));
48  fd_ = output_pipe[0];
49 #if !defined(USE_PPOLL)
50  // If available, we use ppoll in DoWork(); otherwise we use pselect
51  // and so must avoid overly-large FDs.
52  if (fd_ >= static_cast<int>(FD_SETSIZE))
53  Fatal("pipe: %s", strerror(EMFILE));
54 #endif // !USE_PPOLL
56 
57  posix_spawn_file_actions_t action;
58  int err = posix_spawn_file_actions_init(&action);
59  if (err != 0)
60  Fatal("posix_spawn_file_actions_init: %s", strerror(err));
61 
62  err = posix_spawn_file_actions_addclose(&action, output_pipe[0]);
63  if (err != 0)
64  Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
65 
66  posix_spawnattr_t attr;
67  err = posix_spawnattr_init(&attr);
68  if (err != 0)
69  Fatal("posix_spawnattr_init: %s", strerror(err));
70 
71  short flags = 0;
72 
73  flags |= POSIX_SPAWN_SETSIGMASK;
74  err = posix_spawnattr_setsigmask(&attr, &set->old_mask_);
75  if (err != 0)
76  Fatal("posix_spawnattr_setsigmask: %s", strerror(err));
77  // Signals which are set to be caught in the calling process image are set to
78  // default action in the new process image, so no explicit
79  // POSIX_SPAWN_SETSIGDEF parameter is needed.
80 
81  if (!use_console_) {
82  // Put the child in its own process group, so ctrl-c won't reach it.
83  flags |= POSIX_SPAWN_SETPGROUP;
84  // No need to posix_spawnattr_setpgroup(&attr, 0), it's the default.
85 
86  // Open /dev/null over stdin.
87  err = posix_spawn_file_actions_addopen(&action, 0, "/dev/null", O_RDONLY,
88  0);
89  if (err != 0) {
90  Fatal("posix_spawn_file_actions_addopen: %s", strerror(err));
91  }
92 
93  err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 1);
94  if (err != 0)
95  Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
96  err = posix_spawn_file_actions_adddup2(&action, output_pipe[1], 2);
97  if (err != 0)
98  Fatal("posix_spawn_file_actions_adddup2: %s", strerror(err));
99  err = posix_spawn_file_actions_addclose(&action, output_pipe[1]);
100  if (err != 0)
101  Fatal("posix_spawn_file_actions_addclose: %s", strerror(err));
102  // In the console case, output_pipe is still inherited by the child and
103  // closed when the subprocess finishes, which then notifies ninja.
104  }
105 #ifdef POSIX_SPAWN_USEVFORK
106  flags |= POSIX_SPAWN_USEVFORK;
107 #endif
108 
109  err = posix_spawnattr_setflags(&attr, flags);
110  if (err != 0)
111  Fatal("posix_spawnattr_setflags: %s", strerror(err));
112 
113  const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL };
114  err = posix_spawn(&pid_, "/bin/sh", &action, &attr,
115  const_cast<char**>(spawned_args), environ);
116  if (err != 0)
117  Fatal("posix_spawn: %s", strerror(err));
118 
119  err = posix_spawnattr_destroy(&attr);
120  if (err != 0)
121  Fatal("posix_spawnattr_destroy: %s", strerror(err));
122  err = posix_spawn_file_actions_destroy(&action);
123  if (err != 0)
124  Fatal("posix_spawn_file_actions_destroy: %s", strerror(err));
125 
126  close(output_pipe[1]);
127  return true;
128 }
129 
131  char buf[4 << 10];
132  ssize_t len = read(fd_, buf, sizeof(buf));
133  if (len > 0) {
134  buf_.append(buf, len);
135  } else {
136  if (len < 0)
137  Fatal("read: %s", strerror(errno));
138  close(fd_);
139  fd_ = -1;
140  }
141 }
142 
144  assert(pid_ != -1);
145  int status;
146  if (waitpid(pid_, &status, 0) < 0)
147  Fatal("waitpid(%d): %s", pid_, strerror(errno));
148  pid_ = -1;
149 
150  if (WIFEXITED(status)) {
151  int exit = WEXITSTATUS(status);
152  if (exit == 0)
153  return ExitSuccess;
154  } else if (WIFSIGNALED(status)) {
155  if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM
156  || WTERMSIG(status) == SIGHUP)
157  return ExitInterrupted;
158  }
159  return ExitFailure;
160 }
161 
162 bool Subprocess::Done() const {
163  return fd_ == -1;
164 }
165 
166 const string& Subprocess::GetOutput() const {
167  return buf_;
168 }
169 
171 
173  interrupted_ = signum;
174 }
175 
177  sigset_t pending;
178  sigemptyset(&pending);
179  if (sigpending(&pending) == -1) {
180  perror("ninja: sigpending");
181  return;
182  }
183  if (sigismember(&pending, SIGINT))
184  interrupted_ = SIGINT;
185  else if (sigismember(&pending, SIGTERM))
186  interrupted_ = SIGTERM;
187  else if (sigismember(&pending, SIGHUP))
188  interrupted_ = SIGHUP;
189 }
190 
192  sigset_t set;
193  sigemptyset(&set);
194  sigaddset(&set, SIGINT);
195  sigaddset(&set, SIGTERM);
196  sigaddset(&set, SIGHUP);
197  if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0)
198  Fatal("sigprocmask: %s", strerror(errno));
199 
200  struct sigaction act;
201  memset(&act, 0, sizeof(act));
202  act.sa_handler = SetInterruptedFlag;
203  if (sigaction(SIGINT, &act, &old_int_act_) < 0)
204  Fatal("sigaction: %s", strerror(errno));
205  if (sigaction(SIGTERM, &act, &old_term_act_) < 0)
206  Fatal("sigaction: %s", strerror(errno));
207  if (sigaction(SIGHUP, &act, &old_hup_act_) < 0)
208  Fatal("sigaction: %s", strerror(errno));
209 }
210 
212  Clear();
213 
214  if (sigaction(SIGINT, &old_int_act_, 0) < 0)
215  Fatal("sigaction: %s", strerror(errno));
216  if (sigaction(SIGTERM, &old_term_act_, 0) < 0)
217  Fatal("sigaction: %s", strerror(errno));
218  if (sigaction(SIGHUP, &old_hup_act_, 0) < 0)
219  Fatal("sigaction: %s", strerror(errno));
220  if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0)
221  Fatal("sigprocmask: %s", strerror(errno));
222 }
223 
224 Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
225  Subprocess *subprocess = new Subprocess(use_console);
226  if (!subprocess->Start(this, command)) {
227  delete subprocess;
228  return 0;
229  }
230  running_.push_back(subprocess);
231  return subprocess;
232 }
233 
234 #ifdef USE_PPOLL
235 bool SubprocessSet::DoWork() {
236  vector<pollfd> fds;
237  nfds_t nfds = 0;
238 
239  for (vector<Subprocess*>::iterator i = running_.begin();
240  i != running_.end(); ++i) {
241  int fd = (*i)->fd_;
242  if (fd < 0)
243  continue;
244  pollfd pfd = { fd, POLLIN | POLLPRI, 0 };
245  fds.push_back(pfd);
246  ++nfds;
247  }
248 
249  interrupted_ = 0;
250  int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_);
251  if (ret == -1) {
252  if (errno != EINTR) {
253  perror("ninja: ppoll");
254  return false;
255  }
256  return IsInterrupted();
257  }
258 
260  if (IsInterrupted())
261  return true;
262 
263  nfds_t cur_nfd = 0;
264  for (vector<Subprocess*>::iterator i = running_.begin();
265  i != running_.end(); ) {
266  int fd = (*i)->fd_;
267  if (fd < 0)
268  continue;
269  assert(fd == fds[cur_nfd].fd);
270  if (fds[cur_nfd++].revents) {
271  (*i)->OnPipeReady();
272  if ((*i)->Done()) {
273  finished_.push(*i);
274  i = running_.erase(i);
275  continue;
276  }
277  }
278  ++i;
279  }
280 
281  return IsInterrupted();
282 }
283 
284 #else // !defined(USE_PPOLL)
286  fd_set set;
287  int nfds = 0;
288  FD_ZERO(&set);
289 
290  for (vector<Subprocess*>::iterator i = running_.begin();
291  i != running_.end(); ++i) {
292  int fd = (*i)->fd_;
293  if (fd >= 0) {
294  FD_SET(fd, &set);
295  if (nfds < fd+1)
296  nfds = fd+1;
297  }
298  }
299 
300  interrupted_ = 0;
301  int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_);
302  if (ret == -1) {
303  if (errno != EINTR) {
304  perror("ninja: pselect");
305  return false;
306  }
307  return IsInterrupted();
308  }
309 
311  if (IsInterrupted())
312  return true;
313 
314  for (vector<Subprocess*>::iterator i = running_.begin();
315  i != running_.end(); ) {
316  int fd = (*i)->fd_;
317  if (fd >= 0 && FD_ISSET(fd, &set)) {
318  (*i)->OnPipeReady();
319  if ((*i)->Done()) {
320  finished_.push(*i);
321  i = running_.erase(i);
322  continue;
323  }
324  }
325  ++i;
326  }
327 
328  return IsInterrupted();
329 }
330 #endif // !defined(USE_PPOLL)
331 
333  if (finished_.empty())
334  return NULL;
335  Subprocess* subproc = finished_.front();
336  finished_.pop();
337  return subproc;
338 }
339 
341  for (vector<Subprocess*>::iterator i = running_.begin();
342  i != running_.end(); ++i)
343  // Since the foreground process is in our process group, it will receive
344  // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us.
345  if (!(*i)->use_console_)
346  kill(-(*i)->pid_, interrupted_);
347  for (vector<Subprocess*>::iterator i = running_.begin();
348  i != running_.end(); ++i)
349  delete *i;
350  running_.clear();
351 }
SubprocessSet runs a ppoll/pselect() loop around a set of Subprocesses.
Definition: subprocess.h:83
bool use_console_
Definition: subprocess.h:75
static void SetInterruptedFlag(int signum)
Subprocess * NextFinished()
vector< Subprocess * > running_
Definition: subprocess.h:92
void SetCloseOnExec(int fd)
Mark a file descriptor to not be inherited on exec()s.
Definition: util.cc:375
string buf_
Definition: subprocess.h:59
ExitStatus Finish()
Returns ExitSuccess on successful process exit, ExitInterrupted if the process was interrupted...
struct sigaction old_int_act_
Definition: subprocess.h:107
Subprocess * Add(const string &command, bool use_console=false)
Subprocess wraps a single async subprocess.
Definition: subprocess.h:43
static bool IsInterrupted()
Definition: subprocess.h:105
static int interrupted_
Store the signal number that causes the interruption.
Definition: subprocess.h:103
struct sigaction old_hup_act_
Definition: subprocess.h:109
ExitStatus
Definition: exit_status.h:18
Subprocess(bool use_console)
sigset_t old_mask_
Definition: subprocess.h:110
bool Start(struct SubprocessSet *set, const string &command)
void Fatal(const char *msg,...)
Log a fatal message and exit.
Definition: util.cc:57
pid_t pid_
Definition: subprocess.h:73
static void HandlePendingInterruption()
char ** environ
const string & GetOutput() const
bool Done() const
queue< Subprocess * > finished_
Definition: subprocess.h:93
struct sigaction old_term_act_
Definition: subprocess.h:108