summaryrefslogtreecommitdiffstats
path: root/src/subprocess-posix.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/subprocess-posix.cc')
-rw-r--r--src/subprocess-posix.cc300
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();
+}