ProcessReturnCode Subprocess::wait() { returnCode_.enforce(ProcessReturnCode::RUNNING); DCHECK_GT(pid_, 0); int status; pid_t found; do { found = ::waitpid(pid_, &status, 0); } while (found == -1 && errno == EINTR); checkUnixError(found, "waitpid"); DCHECK_EQ(found, pid_); returnCode_ = ProcessReturnCode(status); pid_ = -1; return returnCode_; }
ProcessReturnCode Subprocess::poll() { returnCode_.enforce(ProcessReturnCode::RUNNING); DCHECK_GT(pid_, 0); int status; pid_t found = ::waitpid(pid_, &status, WNOHANG); checkUnixError(found, "waitpid"); if (found != 0) { // Though the child process had quit, this call does not close the pipes // since its descendants may still be using them. returnCode_ = ProcessReturnCode(status); pid_ = -1; } return returnCode_; }
ProcessReturnCode Subprocess::wait() { returnCode_.enforce(ProcessReturnCode::RUNNING); DCHECK_GT(pid_, 0); int status; pid_t found; do { found = ::waitpid(pid_, &status, 0); } while (found == -1 && errno == EINTR); checkUnixError(found, "waitpid"); // Though the child process had quit, this call does not close the pipes // since its descendants may still be using them. DCHECK_EQ(found, pid_); returnCode_ = ProcessReturnCode(status); pid_ = -1; return returnCode_; }
void Subprocess::spawnInternal( std::unique_ptr<const char*[]> argv, const char* executable, Options& options, const std::vector<std::string>* env, int errFd) { // Parent work, pre-fork: create pipes std::vector<int> childFds; // Close all of the childFds as we leave this scope SCOPE_EXIT { // These are only pipes, closing them shouldn't fail for (int cfd : childFds) { CHECK_ERR(::close(cfd)); } }; int r; for (auto& p : options.fdActions_) { if (p.second == PIPE_IN || p.second == PIPE_OUT) { int fds[2]; 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; r = sigfillset(&allBlocked); checkUnixError(r, "sigfillset"); sigset_t oldSignals; r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals); checkPosixError(r, "pthread_sigmask"); SCOPE_EXIT { // Restore signal mask r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr); CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail }; pid_t pid = vfork(); if (pid == 0) { int errnoValue = prepareChild(options, &oldSignals); if (errnoValue != 0) { childError(errFd, kChildFailure, errnoValue); } errnoValue = runChild(executable, argVec, envVec, options); // If we get here, exec() failed. childError(errFd, kExecFailure, errnoValue); } // In parent. Make sure vfork() succeeded. checkUnixError(pid, errno, "vfork"); // Child is alive. We have to be very careful about throwing after this // point. We are inside the constructor, so if we throw the Subprocess // object will have never existed, and the destructor will never be called. // // We should only throw if we got an error via the errFd, and we know the // child has exited and can be immediately waited for. In all other cases, // we have no way of cleaning up the child. pid_ = pid; returnCode_ = ProcessReturnCode(RV_RUNNING); }
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"); }