void Subprocess::closeParentFd(int childFd) { int idx = findByChildFd(childFd); closeChecked(pipes_[idx].parentFd); pipes_.erase(pipes_.begin() + idx); }
void Subprocess::closeAll() { for (auto& p : pipes_) { closeChecked(p.parentFd); } pipes_.clear(); }
void Subprocess::communicate(FdCallback readCallback, FdCallback writeCallback) { returnCode_.enforce(ProcessReturnCode::RUNNING); setAllNonBlocking(); std::vector<pollfd> fds; fds.reserve(pipes_.size()); std::vector<int> toClose; toClose.reserve(pipes_.size()); while (!pipes_.empty()) { fds.clear(); toClose.clear(); for (auto& p : pipes_) { pollfd pfd; pfd.fd = p.parentFd; // Yes, backwards, PIPE_IN / PIPE_OUT are defined from the // child's point of view. if (!p.enabled) { // Still keeping fd in watched set so we get notified of POLLHUP / // POLLERR pfd.events = 0; } else if (p.direction == PIPE_IN) { pfd.events = POLLOUT; } else { pfd.events = POLLIN; } fds.push_back(pfd); } int r; do { r = ::poll(fds.data(), fds.size(), -1); } while (r == -1 && errno == EINTR); checkUnixError(r, "poll"); for (int i = 0; i < pipes_.size(); ++i) { auto& p = pipes_[i]; DCHECK_EQ(fds[i].fd, p.parentFd); short events = fds[i].revents; bool closed = false; if (events & POLLOUT) { DCHECK(!(events & POLLIN)); if (writeCallback(p.parentFd, p.childFd)) { toClose.push_back(i); closed = true; } } if (events & POLLIN) { DCHECK(!(events & POLLOUT)); if (readCallback(p.parentFd, p.childFd)) { toClose.push_back(i); closed = true; } } if ((events & (POLLHUP | POLLERR)) && !closed) { toClose.push_back(i); closed = true; } } // Close the fds in reverse order so the indexes hold after erase() for (int idx : boost::adaptors::reverse(toClose)) { auto pos = pipes_.begin() + idx; closeChecked(pos->parentFd); pipes_.erase(pos); } } }
void Subprocess::spawn( std::unique_ptr<const char*[]> argv, const char* executable, const Options& optionsIn, const std::vector<std::string>* env) { if (optionsIn.usePath_ && env) { throw std::invalid_argument( "usePath() not allowed when overriding environment"); } // Make a copy, we'll mutate options Options options(optionsIn); // Parent work, pre-fork: create pipes std::vector<int> childFds; for (auto& p : options.fdActions_) { if (p.second == PIPE_IN || p.second == PIPE_OUT) { int fds[2]; int r = ::pipe(fds); checkUnixError(r, "pipe"); PipeInfo pinfo; pinfo.direction = p.second; int cfd; if (p.second == PIPE_IN) { // Child gets reading end pinfo.parentFd = fds[1]; cfd = fds[0]; } else { pinfo.parentFd = fds[0]; cfd = fds[1]; } p.second = cfd; // ensure it gets dup2()ed pinfo.childFd = p.first; childFds.push_back(cfd); pipes_.push_back(pinfo); } } // This should already be sorted, as options.fdActions_ is DCHECK(std::is_sorted(pipes_.begin(), pipes_.end())); // Note that the const casts below are legit, per // http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html char** argVec = const_cast<char**>(argv.get()); // Set up environment std::unique_ptr<const char*[]> envHolder; char** envVec; if (env) { envHolder = cloneStrings(*env); envVec = const_cast<char**>(envHolder.get()); } else { envVec = environ; } // Block all signals around vfork; see http://ewontfix.com/7/. // // As the child may run in the same address space as the parent until // the actual execve() system call, any (custom) signal handlers that // the parent has might alter parent's memory if invoked in the child, // with undefined results. So we block all signals in the parent before // vfork(), which will cause them to be blocked in the child as well (we // rely on the fact that Linux, just like all sane implementations, only // clones the calling thread). Then, in the child, we reset all signals // to their default dispositions (while still blocked), and unblock them // (so the exec()ed process inherits the parent's signal mask) // // The parent also unblocks all signals as soon as vfork() returns. sigset_t allBlocked; int r = ::sigfillset(&allBlocked); checkUnixError(r, "sigfillset"); sigset_t oldSignals; r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals); checkPosixError(r, "pthread_sigmask"); pid_t pid = vfork(); if (pid == 0) { // While all signals are blocked, we must reset their // dispositions to default. for (int sig = 1; sig < NSIG; ++sig) { ::signal(sig, SIG_DFL); } // Unblock signals; restore signal mask. int r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr); if (r != 0) abort(); runChild(executable, argVec, envVec, options); // This should never return, but there's nothing else we can do here. abort(); } // In parent. We want to restore the signal mask even if vfork fails, // so we'll save errno here, restore the signal mask, and only then // throw. int savedErrno = errno; // Restore signal mask; do this even if vfork fails! // We only check for errors from pthread_sigmask after we recorded state // that the child is alive, so we know to reap it. r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr); checkUnixError(pid, savedErrno, "vfork"); // Child is alive pid_ = pid; returnCode_ = ProcessReturnCode(RV_RUNNING); // Parent work, post-fork: close child's ends of pipes for (int f : childFds) { closeChecked(f); } checkPosixError(r, "pthread_sigmask"); }