X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FSubprocess.cpp;h=c17e8849e8f080a2a722b84a35b5317a6a68b2df;hb=fd915b73606e09a5f46a1bca0a5d3643a1567014;hp=da69e5368e4649f1da07317e9b432077b9b6ffdb;hpb=86d219c38847965714df9eba03b07fa2f6e30ecc;p=folly.git diff --git a/folly/Subprocess.cpp b/folly/Subprocess.cpp index da69e536..c17e8849 100644 --- a/folly/Subprocess.cpp +++ b/folly/Subprocess.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2013 Facebook, Inc. + * Copyright 2014 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,19 @@ * limitations under the License. */ -#include "folly/Subprocess.h" +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + +#if __linux__ +#include +#endif #include #include + #include -#include #include #include @@ -30,13 +37,17 @@ #include -#include "folly/Conv.h" -#include "folly/ScopeGuard.h" -#include "folly/String.h" -#include "folly/io/Cursor.h" +#include +#include +#include +#include +#include extern char** environ; +constexpr int kExecFailure = 127; +constexpr int kChildFailure = 126; + namespace folly { ProcessReturnCode::State ProcessReturnCode::state() const { @@ -48,9 +59,12 @@ ProcessReturnCode::State ProcessReturnCode::state() const { "Invalid ProcessReturnCode: ", rawStatus_)); } -void ProcessReturnCode::enforce(State s) const { - if (state() != s) { - throw std::logic_error(to("Invalid state ", s)); +void ProcessReturnCode::enforce(State expected) const { + State s = state(); + if (s != expected) { + throw std::logic_error(to( + "Bad use of ProcessReturnCode; state is ", s, " expected ", expected + )); } } @@ -89,6 +103,16 @@ CalledProcessError::CalledProcessError(ProcessReturnCode rc) what_(returnCode_.str()) { } +SubprocessSpawnError::SubprocessSpawnError(const char* executable, + int errCode, + int errnoValue) + : errnoValue_(errnoValue), + what_(to(errCode == kExecFailure ? + "failed to execute " : + "error preparing to execute ", + executable, ": ", errnoStr(errnoValue))) { +} + namespace { // Copy pointers to the given strings in a format suitable for posix_spawn @@ -101,39 +125,6 @@ std::unique_ptr cloneStrings(const std::vector& s) { return d; } -// Helper to throw std::system_error -void throwSystemError(int err, const char* msg) __attribute__((noreturn)); -void throwSystemError(int err, const char* msg) { - throw std::system_error(err, std::system_category(), msg); -} - -// Helper to throw std::system_error from errno -void throwSystemError(const char* msg) __attribute__((noreturn)); -void throwSystemError(const char* msg) { - throwSystemError(errno, msg); -} - -// Check a Posix return code (0 on success, error number on error), throw -// on error. -void checkPosixError(int err, const char* msg) { - if (err != 0) { - throwSystemError(err, msg); - } -} - -// Check a traditional Uinx return code (-1 and sets errno on error), throw -// on error. -void checkUnixError(ssize_t ret, const char* msg) { - if (ret == -1) { - throwSystemError(msg); - } -} -void checkUnixError(ssize_t ret, int savedErrno, const char* msg) { - if (ret == -1) { - throwSystemError(savedErrno, msg); - } -} - // Check a wait() status, throw on non-successful void checkStatus(ProcessReturnCode returnCode) { if (returnCode.state() != ProcessReturnCode::EXITED || @@ -198,12 +189,29 @@ Subprocess::Subprocess( Subprocess::~Subprocess() { CHECK_NE(returnCode_.state(), ProcessReturnCode::RUNNING) << "Subprocess destroyed without reaping child"; + closeAll(); } namespace { void closeChecked(int fd) { checkUnixError(::close(fd), "close"); } + +struct ChildErrorInfo { + int errCode; + int errnoValue; +}; + +FOLLY_NORETURN void childError(int errFd, int errCode, int errnoValue); +void childError(int errFd, int errCode, int errnoValue) { + ChildErrorInfo info = {errCode, errnoValue}; + // Write the error information over the pipe to our parent process. + // We can't really do anything else if this write call fails. + writeNoInt(errFd, &info, sizeof(info)); + // exit + _exit(errCode); +} + } // namespace void Subprocess::closeAll() { @@ -236,13 +244,97 @@ void Subprocess::spawn( // Make a copy, we'll mutate options Options options(optionsIn); + // On error, close all of the pipes_ + auto pipesGuard = makeGuard([&] { + for (auto& p : this->pipes_) { + CHECK_ERR(::close(p.parentFd)); + } + }); + + // Create a pipe to use to receive error information from the child, + // in case it fails before calling exec() + int errFds[2]; +#if FOLLY_HAVE_PIPE2 + checkUnixError(::pipe2(errFds, O_CLOEXEC), "pipe2"); +#else + checkUnixError(::pipe(errFds), "pipe"); +#endif + SCOPE_EXIT { + CHECK_ERR(::close(errFds[0])); + if (errFds[1] >= 0) { + CHECK_ERR(::close(errFds[1])); + } + }; + +#if !FOLLY_HAVE_PIPE2 + // Ask the child to close the read end of the error pipe. + checkUnixError(fcntl(errFds[0], F_SETFD, FD_CLOEXEC), "set FD_CLOEXEC"); + // Set the close-on-exec flag on the write side of the pipe. + // This way the pipe will be closed automatically in the child if execve() + // succeeds. If the exec fails the child can write error information to the + // pipe. + checkUnixError(fcntl(errFds[1], F_SETFD, FD_CLOEXEC), "set FD_CLOEXEC"); +#endif + + // Perform the actual work of setting up pipes then forking and + // executing the child. + spawnInternal(std::move(argv), executable, options, env, errFds[1]); + + // After spawnInternal() returns the 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. + + // Close writable side of the errFd pipe in the parent process + CHECK_ERR(::close(errFds[1])); + errFds[1] = -1; + + // Read from the errFd pipe, to tell if the child ran into any errors before + // calling exec() + readChildErrorPipe(errFds[0], executable); + + // We have fully succeeded now, so release the guard on pipes_ + pipesGuard.dismiss(); +} + +void Subprocess::spawnInternal( + std::unique_ptr argv, + const char* executable, + Options& options, + const std::vector* env, + int errFd) { // Parent work, pre-fork: create pipes std::vector 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]; - int r = ::pipe(fds); + // We're setting both ends of the pipe as close-on-exec. The child + // doesn't need to reset the flag on its end, as we always dup2() the fd, + // and dup2() fds don't share the close-on-exec flag. +#if FOLLY_HAVE_PIPE2 + r = ::pipe2(fds, O_CLOEXEC); + checkUnixError(r, "pipe2"); +#else + r = ::pipe(fds); checkUnixError(r, "pipe"); + r = fcntl(fds[0], F_SETFD, FD_CLOEXEC); + checkUnixError(r, "set FD_CLOEXEC"); + r = fcntl(fds[1], F_SETFD, FD_CLOEXEC); + checkUnixError(r, "set FD_CLOEXEC"); +#endif PipeInfo pinfo; pinfo.direction = p.second; int cfd; @@ -293,82 +385,84 @@ void Subprocess::spawn( // // The parent also unblocks all signals as soon as vfork() returns. sigset_t allBlocked; - int r = ::sigfillset(&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 + }; + // Call c_str() here, as it's not necessarily safe after fork. + const char* childDir = + options.childDir_.empty() ? nullptr : options.childDir_.c_str(); 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); + int errnoValue = prepareChild(options, &oldSignals, childDir); + if (errnoValue != 0) { + childError(errFd, kChildFailure, errnoValue); } - // 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(); + errnoValue = runChild(executable, argVec, envVec, options); + // If we get here, exec() failed. + childError(errFd, kExecFailure, errnoValue); } - // 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; + // In parent. Make sure vfork() succeeded. + checkUnixError(pid, errno, "vfork"); - // 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 + // 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); - - // Parent work, post-fork: close child's ends of pipes - for (int f : childFds) { - closeChecked(f); - } - - checkPosixError(r, "pthread_sigmask"); } -namespace { - -// Checked version of close() to use in the child: abort() on error -void childClose(int fd) { - int r = ::close(fd); - if (r == -1) abort(); -} - -// Checked version of dup2() to use in the child: abort() on error -void childDup2(int oldfd, int newfd) { - int r = ::dup2(oldfd, newfd); - if (r == -1) abort(); -} +int Subprocess::prepareChild(const Options& options, + const sigset_t* sigmask, + const char* childDir) const { + // While all signals are blocked, we must reset their + // dispositions to default. + for (int sig = 1; sig < NSIG; ++sig) { + ::signal(sig, SIG_DFL); + } -} // namespace + { + // Unblock signals; restore signal mask. + int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr); + if (r != 0) { + return r; // pthread_sigmask() returns an errno value + } + } -void Subprocess::runChild(const char* executable, - char** argv, char** env, - const Options& options) const { - // Close parent's ends of all pipes - for (auto& p : pipes_) { - childClose(p.parentFd); + // Change the working directory, if one is given + if (childDir) { + if (::chdir(childDir) == -1) { + return errno; + } } + // We don't have to explicitly close the parent's end of all pipes, + // as they all have the FD_CLOEXEC flag set and will be closed at + // exec time. + // Close all fds that we're supposed to close. - // Note that we're ignoring errors here, in case some of these - // fds were set to close on exec. for (auto& p : options.fdActions_) { if (p.second == CLOSE) { - ::close(p.first); - } else { - childDup2(p.second, p.first); + if (::close(p.first) == -1) { + return errno; + } + } else if (p.second != p.first) { + if (::dup2(p.second, p.first) == -1) { + return errno; + } } } @@ -383,16 +477,57 @@ void Subprocess::runChild(const char* executable, } } +#if __linux__ + // Opt to receive signal on parent death, if requested + if (options.parentDeathSignal_ != 0) { + if (prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0) == -1) { + return errno; + } + } +#endif + + return 0; +} + +int Subprocess::runChild(const char* executable, + char** argv, char** env, + const Options& options) const { // Now, finally, exec. - int r; if (options.usePath_) { ::execvp(executable, argv); } else { ::execve(executable, argv, env); } + return errno; +} + +void Subprocess::readChildErrorPipe(int pfd, const char* executable) { + ChildErrorInfo info; + auto rc = readNoInt(pfd, &info, sizeof(info)); + if (rc == 0) { + // No data means the child executed successfully, and the pipe + // was closed due to the close-on-exec flag being set. + return; + } else if (rc != sizeof(ChildErrorInfo)) { + // An error occurred trying to read from the pipe, or we got a partial read. + // Neither of these cases should really occur in practice. + // + // We can't get any error data from the child in this case, and we don't + // know if it is successfully running or not. All we can do is to return + // normally, as if the child executed successfully. If something bad + // happened the caller should at least get a non-normal exit status from + // the child. + LOG(ERROR) << "unexpected error trying to read from child error pipe " << + "rc=" << rc << ", errno=" << errno; + return; + } + + // We got error data from the child. The child should exit immediately in + // this case, so wait on it to clean up. + wait(); - // If we're here, something's wrong. - abort(); + // Throw to signal the error + throw SubprocessSpawnError(executable, info.errCode, info.errnoValue); } ProcessReturnCode Subprocess::poll() { @@ -427,6 +562,7 @@ ProcessReturnCode Subprocess::wait() { checkUnixError(found, "waitpid"); DCHECK_EQ(found, pid_); returnCode_ = ProcessReturnCode(status); + pid_ = -1; return returnCode_; } @@ -441,6 +577,10 @@ void Subprocess::sendSignal(int signal) { checkUnixError(r, "kill"); } +pid_t Subprocess::pid() const { + return pid_; +} + namespace { std::pair queueFront(const IOBufQueue& queue) { @@ -457,10 +597,7 @@ bool handleWrite(int fd, IOBufQueue& queue) { return true; // EOF } - ssize_t n; - do { - n = ::write(fd, p.first, p.second); - } while (n == -1 && errno == EINTR); + ssize_t n = writeNoInt(fd, p.first, p.second); if (n == -1 && errno == EAGAIN) { return false; } @@ -473,10 +610,7 @@ bool handleWrite(int fd, IOBufQueue& queue) { bool handleRead(int fd, IOBufQueue& queue) { for (;;) { auto p = queue.preallocate(100, 65000); - ssize_t n; - do { - n = ::read(fd, p.first, p.second); - } while (n == -1 && errno == EINTR); + ssize_t n = readNoInt(fd, p.first, p.second); if (n == -1 && errno == EAGAIN) { return false; } @@ -494,10 +628,7 @@ bool discardRead(int fd) { static std::unique_ptr buf(new char[bufSize]); for (;;) { - ssize_t n; - do { - n = ::read(fd, buf.get(), bufSize); - } while (n == -1 && errno == EINTR); + ssize_t n = readNoInt(fd, buf.get(), bufSize); if (n == -1 && errno == EAGAIN) { return false; } @@ -511,12 +642,11 @@ bool discardRead(int fd) { } // namespace std::pair Subprocess::communicate( - const CommunicateFlags& flags, - StringPiece data) { - IOBufQueue dataQueue; - dataQueue.wrapBuffer(data.data(), data.size()); + StringPiece input) { + IOBufQueue inputQueue; + inputQueue.wrapBuffer(input.data(), input.size()); - auto outQueues = communicateIOBuf(flags, std::move(dataQueue)); + auto outQueues = communicateIOBuf(std::move(inputQueue)); auto outBufs = std::make_pair(outQueues.first.move(), outQueues.second.move()); std::pair out; @@ -534,14 +664,21 @@ std::pair Subprocess::communicate( } std::pair Subprocess::communicateIOBuf( - const CommunicateFlags& flags, - IOBufQueue data) { + IOBufQueue input) { + // If the user supplied a non-empty input buffer, make sure + // that stdin is a pipe so we can write the data. + if (!input.empty()) { + // findByChildFd() will throw std::invalid_argument if no pipe for + // STDIN_FILENO exists + findByChildFd(STDIN_FILENO); + } + std::pair out; auto readCallback = [&] (int pfd, int cfd) -> bool { - if (cfd == 1 && flags.readStdout_) { + if (cfd == STDOUT_FILENO) { return handleRead(pfd, out.first); - } else if (cfd == 2 && flags.readStderr_) { + } else if (cfd == STDERR_FILENO) { return handleRead(pfd, out.second); } else { // Don't close the file descriptor, the child might not like SIGPIPE, @@ -551,11 +688,11 @@ std::pair Subprocess::communicateIOBuf( }; auto writeCallback = [&] (int pfd, int cfd) -> bool { - if (cfd == 0 && flags.writeStdin_) { - return handleWrite(pfd, data); + if (cfd == STDIN_FILENO) { + return handleWrite(pfd, input); } else { // If we don't want to write to this fd, just close it. - return false; + return true; } }; @@ -583,7 +720,15 @@ void Subprocess::communicate(FdCallback readCallback, pfd.fd = p.parentFd; // Yes, backwards, PIPE_IN / PIPE_OUT are defined from the // child's point of view. - pfd.events = (p.direction == PIPE_IN ? POLLOUT : POLLIN); + 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); } @@ -607,7 +752,9 @@ void Subprocess::communicate(FdCallback readCallback, } } - if (events & POLLIN) { + // Call read callback on POLLHUP, to give it a chance to read (and act + // on) end of file + if (events & (POLLIN | POLLHUP)) { DCHECK(!(events & POLLOUT)); if (readCallback(p.parentFd, p.childFd)) { toClose.push_back(i); @@ -630,6 +777,14 @@ void Subprocess::communicate(FdCallback readCallback, } } +void Subprocess::enableNotifications(int childFd, bool enabled) { + pipes_[findByChildFd(childFd)].enabled = enabled; +} + +bool Subprocess::notificationsEnabled(int childFd) const { + return pipes_[findByChildFd(childFd)].enabled; +} + int Subprocess::findByChildFd(int childFd) const { auto pos = std::lower_bound( pipes_.begin(), pipes_.end(), childFd, @@ -662,4 +817,3 @@ Initializer initializer; } // namespace } // namespace folly -