Copyright 2014->2015
[folly.git] / folly / Subprocess.cpp
index 96049d65dc086f37e9018877d09fea9729e2160b..36c824c3cfe2a773bcf1868c55ea4df952083a4d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#include "folly/Subprocess.h"
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <folly/Subprocess.h>
 
 #if __linux__
 #include <sys/prctl.h>
 #endif
 #include <fcntl.h>
 #include <poll.h>
+
 #include <unistd.h>
 
 #include <array>
 
 #include <glog/logging.h>
 
-#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 <folly/Conv.h>
+#include <folly/Exception.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/io/Cursor.h>
 
 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<std::string>("Invalid state ", s,
-                                           " expected ", expected));
+    throw std::logic_error(to<std::string>(
+      "Bad use of ProcessReturnCode; state is ", s, " expected ", expected
+    ));
   }
 }
 
@@ -113,7 +118,7 @@ namespace {
 // Copy pointers to the given strings in a format suitable for posix_spawn
 std::unique_ptr<const char*[]> cloneStrings(const std::vector<std::string>& s) {
   std::unique_ptr<const char*[]> d(new const char*[s.size() + 1]);
-  for (int i = 0; i < s.size(); i++) {
+  for (size_t i = 0; i < s.size(); i++) {
     d[i] = s[i].c_str();
   }
   d[s.size()] = nullptr;
@@ -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,13 +480,18 @@ 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;
     }
   }
 #endif
 
+  if (options.processGroupLeader_) {
+    if (setpgrp() == -1) {
+      return errno;
+    }
+  }
+
   return 0;
 }
 
@@ -463,7 +499,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 +603,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 +616,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 +634,7 @@ bool discardRead(int fd) {
   static std::unique_ptr<char[]> 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 +726,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);
     }
 
@@ -710,7 +744,7 @@ void Subprocess::communicate(FdCallback readCallback,
     } while (r == -1 && errno == EINTR);
     checkUnixError(r, "poll");
 
-    for (int i = 0; i < pipes_.size(); ++i) {
+    for (size_t i = 0; i < pipes_.size(); ++i) {
       auto& p = pipes_[i];
       DCHECK_EQ(fds[i].fd, p.parentFd);
       short events = fds[i].revents;
@@ -724,7 +758,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 +783,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 +823,3 @@ Initializer initializer;
 }  // namespace
 
 }  // namespace folly
-