diff options
Diffstat (limited to 'src/subprocess-posix.cc')
-rw-r--r-- | src/subprocess-posix.cc | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc new file mode 100644 index 0000000..1c47fd1 --- /dev/null +++ b/src/subprocess-posix.cc @@ -0,0 +1,300 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "subprocess.h" + +#include <algorithm> +#include <map> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/wait.h> + +// Older versions of glibc (like 2.4) won't find this in <poll.h>. glibc +// 2.4 keeps it in <asm-generic/poll.h>, though attempting to include that +// will redefine the pollfd structure. +#ifndef POLLRDHUP +#define POLLRDHUP 0x2000 +#endif + +#include "util.h" + +Subprocess::Subprocess() : fd_(-1), pid_(-1) { +} +Subprocess::~Subprocess() { + if (fd_ >= 0) + close(fd_); + // Reap child if forgotten. + if (pid_ != -1) + Finish(); +} + +bool Subprocess::Start(SubprocessSet* set, const string& command) { + int output_pipe[2]; + if (pipe(output_pipe) < 0) + Fatal("pipe: %s", strerror(errno)); + fd_ = output_pipe[0]; +#if !defined(linux) + // On linux we use ppoll in DoWork(); elsewhere we use pselect and so must + // avoid overly-large FDs. + if (fd_ >= static_cast<int>(FD_SETSIZE)) + Fatal("pipe: %s", strerror(EMFILE)); +#endif // !linux + SetCloseOnExec(fd_); + + pid_ = fork(); + if (pid_ < 0) + Fatal("fork: %s", strerror(errno)); + + if (pid_ == 0) { + close(output_pipe[0]); + + // Track which fd we use to report errors on. + int error_pipe = output_pipe[1]; + do { + if (setpgid(0, 0) < 0) + break; + + if (sigaction(SIGINT, &set->old_act_, 0) < 0) + break; + if (sigprocmask(SIG_SETMASK, &set->old_mask_, 0) < 0) + break; + + // Open /dev/null over stdin. + int devnull = open("/dev/null", O_WRONLY); + if (devnull < 0) + break; + if (dup2(devnull, 0) < 0) + break; + close(devnull); + + if (dup2(output_pipe[1], 1) < 0 || + dup2(output_pipe[1], 2) < 0) + break; + + // Now can use stderr for errors. + error_pipe = 2; + close(output_pipe[1]); + + execl("/bin/sh", "/bin/sh", "-c", command.c_str(), (char *) NULL); + } while (false); + + // If we get here, something went wrong; the execl should have + // replaced us. + char* err = strerror(errno); + if (write(error_pipe, err, strlen(err)) < 0) { + // If the write fails, there's nothing we can do. + // But this block seems necessary to silence the warning. + } + _exit(1); + } + + close(output_pipe[1]); + return true; +} + +void Subprocess::OnPipeReady() { + char buf[4 << 10]; + ssize_t len = read(fd_, buf, sizeof(buf)); + if (len > 0) { + buf_.append(buf, len); + } else { + if (len < 0) + Fatal("read: %s", strerror(errno)); + close(fd_); + fd_ = -1; + } +} + +ExitStatus Subprocess::Finish() { + assert(pid_ != -1); + int status; + if (waitpid(pid_, &status, 0) < 0) + Fatal("waitpid(%d): %s", pid_, strerror(errno)); + pid_ = -1; + + if (WIFEXITED(status)) { + int exit = WEXITSTATUS(status); + if (exit == 0) + return ExitSuccess; + } else if (WIFSIGNALED(status)) { + if (WTERMSIG(status) == SIGINT) + return ExitInterrupted; + } + return ExitFailure; +} + +bool Subprocess::Done() const { + return fd_ == -1; +} + +const string& Subprocess::GetOutput() const { + return buf_; +} + +bool SubprocessSet::interrupted_; + +void SubprocessSet::SetInterruptedFlag(int signum) { + (void) signum; + interrupted_ = true; +} + +SubprocessSet::SubprocessSet() { + interrupted_ = false; + + sigset_t set; + sigemptyset(&set); + sigaddset(&set, SIGINT); + if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0) + Fatal("sigprocmask: %s", strerror(errno)); + + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SetInterruptedFlag; + if (sigaction(SIGINT, &act, &old_act_) < 0) + Fatal("sigaction: %s", strerror(errno)); +} + +SubprocessSet::~SubprocessSet() { + Clear(); + + if (sigaction(SIGINT, &old_act_, 0) < 0) + Fatal("sigaction: %s", strerror(errno)); + if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0) + Fatal("sigprocmask: %s", strerror(errno)); +} + +Subprocess *SubprocessSet::Add(const string& command) { + Subprocess *subprocess = new Subprocess; + if (!subprocess->Start(this, command)) { + delete subprocess; + return 0; + } + running_.push_back(subprocess); + return subprocess; +} + +#ifdef linux +bool SubprocessSet::DoWork() { + vector<pollfd> fds; + nfds_t nfds = 0; + + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ++i) { + int fd = (*i)->fd_; + if (fd < 0) + continue; + pollfd pfd = { fd, POLLIN | POLLPRI | POLLRDHUP, 0 }; + fds.push_back(pfd); + ++nfds; + } + + int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_); + if (ret == -1) { + if (errno != EINTR) { + perror("ninja: ppoll"); + return false; + } + bool interrupted = interrupted_; + interrupted_ = false; + return interrupted; + } + + nfds_t cur_nfd = 0; + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ) { + int fd = (*i)->fd_; + if (fd < 0) + continue; + assert(fd == fds[cur_nfd].fd); + if (fds[cur_nfd++].revents) { + (*i)->OnPipeReady(); + if ((*i)->Done()) { + finished_.push(*i); + i = running_.erase(i); + continue; + } + } + ++i; + } + + return false; +} + +#else // linux +bool SubprocessSet::DoWork() { + fd_set set; + int nfds = 0; + FD_ZERO(&set); + + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ++i) { + int fd = (*i)->fd_; + if (fd >= 0) { + FD_SET(fd, &set); + if (nfds < fd+1) + nfds = fd+1; + } + } + + int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_); + if (ret == -1) { + if (errno != EINTR) { + perror("ninja: pselect"); + return false; + } + bool interrupted = interrupted_; + interrupted_ = false; + return interrupted; + } + + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ) { + int fd = (*i)->fd_; + if (fd >= 0 && FD_ISSET(fd, &set)) { + (*i)->OnPipeReady(); + if ((*i)->Done()) { + finished_.push(*i); + i = running_.erase(i); + continue; + } + } + ++i; + } + + return false; +} +#endif // linux + +Subprocess* SubprocessSet::NextFinished() { + if (finished_.empty()) + return NULL; + Subprocess* subproc = finished_.front(); + finished_.pop(); + return subproc; +} + +void SubprocessSet::Clear() { + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ++i) + kill(-(*i)->pid_, SIGINT); + for (vector<Subprocess*>::iterator i = running_.begin(); + i != running_.end(); ++i) + delete *i; + running_.clear(); +} |