fix fbstring move assignment operator
[folly.git] / folly / Subprocess.cpp
index 034165c374c8bfedaff53d7d339cf103bf9e336c..8613402531a2d040040f6eab44dfaee14702e85a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2013 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 #include "folly/Subprocess.h"
 
+#include <sys/prctl.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <unistd.h>
 #include <glog/logging.h>
 
 #include "folly/Conv.h"
+#include "folly/Exception.h"
 #include "folly/ScopeGuard.h"
 #include "folly/String.h"
-#include "folly/experimental/io/Cursor.h"
+#include "folly/io/Cursor.h"
 
 extern char** environ;
 
+constexpr int kExecFailure = 127;
+constexpr int kChildFailure = 126;
+
 namespace folly {
 
 ProcessReturnCode::State ProcessReturnCode::state() const {
@@ -101,34 +106,6 @@ std::unique_ptr<const char*[]> cloneStrings(const std::vector<std::string>& 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);
-  }
-}
-
 // Check a wait() status, throw on non-successful
 void checkStatus(ProcessReturnCode returnCode) {
   if (returnCode.state() != ProcessReturnCode::EXITED ||
@@ -191,22 +168,8 @@ Subprocess::Subprocess(
 }
 
 Subprocess::~Subprocess() {
-  if (returnCode_.state() == ProcessReturnCode::RUNNING) {
-    LOG(ERROR) << "Subprocess destroyed without reaping; killing child.";
-    try {
-      kill();
-      wait();
-    } catch (...) {
-      LOG(FATAL) << "Killing child failed, terminating: "
-                 << exceptionStr(std::current_exception());
-    }
-  }
-  try {
-    closeAll();
-  } catch (...) {
-    LOG(FATAL) << "close failed, terminating: "
-               << exceptionStr(std::current_exception());
-  }
+  CHECK_NE(returnCode_.state(), ProcessReturnCode::RUNNING)
+    << "Subprocess destroyed without reaping child";
 }
 
 namespace {
@@ -247,6 +210,18 @@ void Subprocess::spawn(
 
   // Parent work, pre-fork: create pipes
   std::vector<int> childFds;
+
+  // If we throw, don't leak file descriptors
+  auto guard = makeGuard([&] {
+    // These are only pipes, closing them shouldn't fail
+    for (int cfd : childFds) {
+      CHECK_ERR(::close(cfd));
+    }
+    for (auto& p : this->pipes_) {
+      CHECK_ERR(::close(p.parentFd));
+    }
+  });
+
   for (auto& p : options.fdActions_) {
     if (p.second == PIPE_IN || p.second == PIPE_OUT) {
       int fds[2];
@@ -288,36 +263,76 @@ void Subprocess::spawn(
     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) _exit(kChildFailure);
+
     runChild(executable, argVec, envVec, options);
     // This should never return, but there's nothing else we can do here.
-    abort();
+    _exit(kExecFailure);
   }
+  // 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!
+  r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
+  CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r);  // shouldn't fail
+  checkUnixError(pid, savedErrno, "vfork");
 
-  // In parent
-  checkUnixError(pid, "vfork");
+  // Child is alive.  We can't throw any more, as we can't figure out
+  // what to do with the child.
+  guard.dismiss();
   pid_ = pid;
   returnCode_ = ProcessReturnCode(RV_RUNNING);
 
-  // Parent work, post-fork: close child's ends of pipes
+  // Parent work, post-fork: close child's ends of pipes; closing them
+  // shouldn't fail.
   for (int f : childFds) {
-    closeChecked(f);
+    CHECK_ERR(::close(f));
   }
 }
 
 namespace {
 
-// Checked version of close() to use in the child: abort() on error
+// Checked version of close() to use in the child: _exit(126) on error
 void childClose(int fd) {
   int r = ::close(fd);
-  if (r == -1) abort();
+  if (r == -1) _exit(kChildFailure);
 }
 
-// Checked version of dup2() to use in the child: abort() on error
+// Checked version of dup2() to use in the child: _exit(126) on error
 void childDup2(int oldfd, int newfd) {
   int r = ::dup2(oldfd, newfd);
-  if (r == -1) abort();
+  if (r == -1) _exit(kChildFailure);
 }
 
 }  // namespace
@@ -352,6 +367,14 @@ void Subprocess::runChild(const char* executable,
     }
   }
 
+  // Opt to receive signal on parent death, if requested
+  if (options.parentDeathSignal_ != 0) {
+    int r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
+    if (r == -1) {
+      _exit(kChildFailure);
+    }
+  }
+
   // Now, finally, exec.
   int r;
   if (options.usePath_) {
@@ -359,9 +382,6 @@ void Subprocess::runChild(const char* executable,
   } else {
     ::execve(executable, argv, env);
   }
-
-  // If we're here, something's wrong.
-  abort();
 }
 
 ProcessReturnCode Subprocess::poll() {
@@ -389,9 +409,14 @@ ProcessReturnCode Subprocess::wait() {
   returnCode_.enforce(ProcessReturnCode::RUNNING);
   DCHECK_GT(pid_, 0);
   int status;
-  pid_t found = ::waitpid(pid_, &status, 0);
+  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_;
 }
 
@@ -406,14 +431,12 @@ void Subprocess::sendSignal(int signal) {
   checkUnixError(r, "kill");
 }
 
-namespace {
-void setNonBlocking(int fd) {
-  int flags = ::fcntl(fd, F_GETFL);
-  checkUnixError(flags, "fcntl");
-  int r = ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-  checkUnixError(r, "fcntl");
+pid_t Subprocess::pid() const {
+  return pid_;
 }
 
+namespace {
+
 std::pair<const uint8_t*, size_t> queueFront(const IOBufQueue& queue) {
   auto* p = queue.front();
   if (!p) return std::make_pair(nullptr, 0);
@@ -482,7 +505,7 @@ bool discardRead(int fd) {
 }  // namespace
 
 std::pair<std::string, std::string> Subprocess::communicate(
-    int flags,
+    const CommunicateFlags& flags,
     StringPiece data) {
   IOBufQueue dataQueue;
   dataQueue.wrapBuffer(data.data(), data.size());
@@ -505,14 +528,14 @@ std::pair<std::string, std::string> Subprocess::communicate(
 }
 
 std::pair<IOBufQueue, IOBufQueue> Subprocess::communicateIOBuf(
-    int flags,
+    const CommunicateFlags& flags,
     IOBufQueue data) {
   std::pair<IOBufQueue, IOBufQueue> out;
 
-  auto readCallback = [&, flags] (int pfd, int cfd) {
-    if (cfd == 1 && (flags & READ_STDOUT)) {
+  auto readCallback = [&] (int pfd, int cfd) -> bool {
+    if (cfd == 1 && flags.readStdout_) {
       return handleRead(pfd, out.first);
-    } else if (cfd == 2 && (flags & READ_STDERR)) {
+    } else if (cfd == 2 && flags.readStderr_) {
       return handleRead(pfd, out.second);
     } else {
       // Don't close the file descriptor, the child might not like SIGPIPE,
@@ -521,8 +544,8 @@ std::pair<IOBufQueue, IOBufQueue> Subprocess::communicateIOBuf(
     }
   };
 
-  auto writeCallback = [&, flags] (int pfd, int cfd) {
-    if (cfd == 0 && (flags & WRITE_STDIN)) {
+  auto writeCallback = [&] (int pfd, int cfd) -> bool {
+    if (cfd == 0 && flags.writeStdin_) {
       return handleWrite(pfd, data);
     } else {
       // If we don't want to write to this fd, just close it.