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