github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/src/subprocess-posix.cc (about) 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 43 Subprocess::~Subprocess() { 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 62 SetCloseOnExec(fd_); 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 137 void Subprocess::OnPipeReady() { 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 150 ExitStatus Subprocess::Finish() { 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 187 int SubprocessSet::interrupted_; 188 189 void SubprocessSet::SetInterruptedFlag(int signum) { 190 interrupted_ = signum; 191 } 192 193 void SubprocessSet::HandlePendingInterruption() { 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 208 SubprocessSet::SubprocessSet() { 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 228 SubprocessSet::~SubprocessSet() { 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 276 HandlePendingInterruption(); 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) 302 bool SubprocessSet::DoWork() { 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 327 HandlePendingInterruption(); 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 349 Subprocess* SubprocessSet::NextFinished() { 350 if (finished_.empty()) 351 return NULL; 352 Subprocess* subproc = finished_.front(); 353 finished_.pop(); 354 return subproc; 355 } 356 357 void SubprocessSet::Clear() { 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 }