X-Git-Url: http://demsky.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2FSubprocess.cpp;h=c17e8849e8f080a2a722b84a35b5317a6a68b2df;hb=fd915b73606e09a5f46a1bca0a5d3643a1567014;hp=96049d65dc086f37e9018877d09fea9729e2160b;hpb=22afce906d7e98d95f8c45c3301072d9fd891d41;p=folly.git diff --git a/folly/Subprocess.cpp b/folly/Subprocess.cpp index 96049d65..c17e8849 100644 --- a/folly/Subprocess.cpp +++ b/folly/Subprocess.cpp @@ -14,13 +14,18 @@ * limitations under the License. */ -#include "folly/Subprocess.h" +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include #if __linux__ #include #endif #include #include + #include #include @@ -32,12 +37,11 @@ #include -#include "folly/Conv.h" -#include "folly/Exception.h" -#include "folly/FileUtil.h" -#include "folly/ScopeGuard.h" -#include "folly/String.h" -#include "folly/io/Cursor.h" +#include +#include +#include +#include +#include extern char** environ; @@ -58,8 +62,9 @@ ProcessReturnCode::State ProcessReturnCode::state() const { void ProcessReturnCode::enforce(State expected) const { State s = state(); if (s != expected) { - throw std::logic_error(to("Invalid state ", s, - " expected ", expected)); + throw std::logic_error(to( + "Bad use of ProcessReturnCode; state is ", s, " expected ", expected + )); } } @@ -197,7 +202,7 @@ struct ChildErrorInfo { int errnoValue; }; -void childError(int errFd, int errCode, int errnoValue) FOLLY_NORETURN; +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. @@ -249,22 +254,27 @@ void Subprocess::spawn( // Create a pipe to use to receive error information from the child, // in case it fails before calling exec() int errFds[2]; - int r = ::pipe(errFds); - checkUnixError(r, "pipe"); +#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. - options.fdActions_[errFds[0]] = CLOSE; + 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. - r = fcntl(errFds[1], F_SETFD, FD_CLOEXEC); - checkUnixError(r, "set FD_CLOEXEC"); + 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. @@ -311,8 +321,20 @@ void Subprocess::spawnInternal( for (auto& p : options.fdActions_) { if (p.second == PIPE_IN || p.second == PIPE_OUT) { int fds[2]; + // 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; @@ -375,9 +397,12 @@ void Subprocess::spawnInternal( 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) { - int errnoValue = prepareChild(options, &oldSignals); + int errnoValue = prepareChild(options, &oldSignals, childDir); if (errnoValue != 0) { childError(errFd, kChildFailure, errnoValue); } @@ -401,35 +426,41 @@ void Subprocess::spawnInternal( } int Subprocess::prepareChild(const Options& options, - const sigset_t* sigmask) const { + 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); } - // Unblock signals; restore signal mask. - int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr); - if (r != 0) { - return r; // pthread_sigmask() returns an errno value + + { + // Unblock signals; restore signal mask. + int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr); + if (r != 0) { + return r; // pthread_sigmask() returns an errno value + } } - // Close parent's ends of all pipes - for (auto& p : pipes_) { - r = ::close(p.parentFd); - if (r == -1) { + // 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 { - r = ::dup2(p.second, p.first); - if (r == -1) { + if (::close(p.first) == -1) { + return errno; + } + } else if (p.second != p.first) { + if (::dup2(p.second, p.first) == -1) { return errno; } } @@ -449,8 +480,7 @@ int Subprocess::prepareChild(const Options& options, #if __linux__ // Opt to receive signal on parent death, if requested if (options.parentDeathSignal_ != 0) { - r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0); - if (r == -1) { + if (prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0) == -1) { return errno; } } @@ -463,7 +493,6 @@ 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 { @@ -568,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; } @@ -584,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; } @@ -605,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; } @@ -700,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); } @@ -724,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); @@ -747,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, @@ -779,4 +817,3 @@ Initializer initializer; } // namespace } // namespace folly -