Remove/make private the default ***Holder constructor to allow compile time detection...
[folly.git] / folly / Subprocess.h
index cd6afa13c784dc7574fbf802d848dbf47dc0b131..44e3fad6b08cbfd8e85ff7a21e010600d06e17df 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  *
  * A thread-safe [1] version of popen() (type="r", to read from the child):
  *    Subprocess proc(cmd, Subprocess::pipeStdout());
- *    // read from proc.stdout()
+ *    // read from proc.stdoutFd()
  *    proc.wait();
  *
  * A thread-safe [1] version of popen() (type="w", to write to the child):
  *    Subprocess proc(cmd, Subprocess::pipeStdin());
- *    // write to proc.stdin()
+ *    // write to proc.stdinFd()
  *    proc.wait();
  *
  * If you want to redirect both stdin and stdout to pipes, you can, but note
@@ -90,8 +90,8 @@
  * make sure to serialize your signals (i.e. kill()) with the waits --
  * either wait & signal from the same thread, or use a mutex.
  */
-#ifndef FOLLY_SUBPROCESS_H_
-#define FOLLY_SUBPROCESS_H_
+
+#pragma once
 
 #include <sys/types.h>
 #include <signal.h>
 
 #include <boost/container/flat_map.hpp>
 #include <boost/operators.hpp>
-#include <boost/noncopyable.hpp>
 
+#include <folly/Exception.h>
 #include <folly/File.h>
 #include <folly/FileUtil.h>
+#include <folly/Function.h>
 #include <folly/gen/String.h>
 #include <folly/io/IOBufQueue.h>
 #include <folly/MapUtil.h>
@@ -127,12 +128,26 @@ class ProcessReturnCode {
   friend class Subprocess;
  public:
   enum State {
+    // Subprocess starts in the constructor, so this state designates only
+    // default-initialized or moved-out ProcessReturnCodes.
     NOT_STARTED,
     RUNNING,
     EXITED,
     KILLED
   };
 
+  // Default-initialized for convenience. Subprocess::returnCode() will
+  // never produce this value.
+  ProcessReturnCode() : ProcessReturnCode(RV_NOT_STARTED) {}
+
+  // Trivially copyable
+  ProcessReturnCode(const ProcessReturnCode& p) = default;
+  ProcessReturnCode& operator=(const ProcessReturnCode& p) = default;
+  // Non-default move: In order for Subprocess to be movable, the "moved
+  // out" state must not be "running", or ~Subprocess() will abort.
+  ProcessReturnCode(ProcessReturnCode&& p) noexcept;
+  ProcessReturnCode& operator=(ProcessReturnCode&& p) noexcept;
+
   /**
    * Process state.  One of:
    * NOT_STARTED: process hasn't been started successfully
@@ -201,8 +216,8 @@ class SubprocessError : public std::exception {};
 class CalledProcessError : public SubprocessError {
  public:
   explicit CalledProcessError(ProcessReturnCode rc);
-  ~CalledProcessError() throw() { }
-  const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
+  ~CalledProcessError() throw() = default;
+  const char* what() const throw() override { return what_.c_str(); }
   ProcessReturnCode returnCode() const { return returnCode_; }
  private:
   ProcessReturnCode returnCode_;
@@ -215,8 +230,8 @@ class CalledProcessError : public SubprocessError {
 class SubprocessSpawnError : public SubprocessError {
  public:
   SubprocessSpawnError(const char* executable, int errCode, int errnoValue);
-  ~SubprocessSpawnError() throw() {}
-  const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
+  ~SubprocessSpawnError() throw() = default;
+  const char* what() const throw() override { return what_.c_str(); }
   int errnoValue() const { return errnoValue_; }
 
  private:
@@ -227,13 +242,30 @@ class SubprocessSpawnError : public SubprocessError {
 /**
  * Subprocess.
  */
-class Subprocess : private boost::noncopyable {
+class Subprocess {
  public:
   static const int CLOSE = -1;
   static const int PIPE = -2;
   static const int PIPE_IN = -3;
   static const int PIPE_OUT = -4;
 
+  /**
+   * See Subprocess::Options::dangerousPostForkPreExecCallback() for usage.
+   * Every derived class should include the following warning:
+   *
+   * DANGER: This class runs after fork in a child processes. Be fast, the
+   * parent thread is waiting, but remember that other parent threads are
+   * running and may mutate your state.  Avoid mutating any data belonging to
+   * the parent.  Avoid interacting with non-POD data that originated in the
+   * parent.  Avoid any libraries that may internally reference non-POD data.
+   * Especially beware parent mutexes -- for example, glog's LOG() uses one.
+   */
+  struct DangerousPostForkPreExecCallback {
+    virtual ~DangerousPostForkPreExecCallback() {}
+    // This must return 0 on success, or an `errno` error code.
+    virtual int operator()() = 0;
+  };
+
   /**
    * Class representing various options: file descriptor behavior, and
    * whether to use $PATH for searching for the executable,
@@ -266,19 +298,19 @@ class Subprocess : private boost::noncopyable {
     /**
      * Shortcut to change the action for standard input.
      */
-    Options& stdin(int action) { return fd(STDIN_FILENO, action); }
+    Options& stdinFd(int action) { return fd(STDIN_FILENO, action); }
 
     /**
      * Shortcut to change the action for standard output.
      */
-    Options& stdout(int action) { return fd(STDOUT_FILENO, action); }
+    Options& stdoutFd(int action) { return fd(STDOUT_FILENO, action); }
 
     /**
      * Shortcut to change the action for standard error.
      * Note that stderr(1) will redirect the standard error to the same
      * file descriptor as standard output; the equivalent of bash's "2>&1"
      */
-    Options& stderr(int action) { return fd(STDERR_FILENO, action); }
+    Options& stderrFd(int action) { return fd(STDERR_FILENO, action); }
 
     Options& pipeStdin() { return fd(STDIN_FILENO, PIPE_IN); }
     Options& pipeStdout() { return fd(STDOUT_FILENO, PIPE_OUT); }
@@ -328,6 +360,43 @@ class Subprocess : private boost::noncopyable {
       return *this;
     }
 
+    /**
+     * *** READ THIS WHOLE DOCBLOCK BEFORE USING ***
+     *
+     * Run this callback in the child after the fork, just before the
+     * exec(), and after the child's state has been completely set up:
+     *  - signal handlers have been reset to default handling and unblocked
+     *  - the working directory was set
+     *  - closed any file descriptors specified via Options()
+     *  - set child process flags (see code)
+     *
+     * This is EXTREMELY DANGEROUS. For example, this innocuous-looking code
+     * can cause a fraction of your Subprocess launches to hang forever:
+     *
+     *   LOG(INFO) << "Hello from the child";
+     *
+     * The reason is that glog has an internal mutex. If your fork() happens
+     * when the parent has the mutex locked, the child will wait forever.
+     *
+     * == GUIDELINES ==
+     *
+     * - Be quick -- the parent thread is blocked until you exit.
+     * - Remember that other parent threads are running, and may mutate your
+     *   state.
+     * - Avoid mutating any data belonging to the parent.
+     * - Avoid interacting with non-POD data that came from the parent.
+     * - Avoid any libraries that may internally reference non-POD state.
+     * - Especially beware parent mutexes, e.g. LOG() uses a global mutex.
+     * - Avoid invoking the parent's destructors (you can accidentally
+     *   delete files, terminate network connections, etc).
+     * - Read http://ewontfix.com/7/
+     */
+    Options& dangerousPostForkPreExecCallback(
+        DangerousPostForkPreExecCallback* cob) {
+      dangerousPostForkPreExecCallback_ = cob;
+      return *this;
+    }
+
     /**
      * Helpful way to combine Options.
      */
@@ -343,11 +412,27 @@ class Subprocess : private boost::noncopyable {
     int parentDeathSignal_{0};
 #endif
     bool processGroupLeader_{false};
+    DangerousPostForkPreExecCallback*
+      dangerousPostForkPreExecCallback_{nullptr};
   };
 
-  static Options pipeStdin() { return Options().stdin(PIPE); }
-  static Options pipeStdout() { return Options().stdout(PIPE); }
-  static Options pipeStderr() { return Options().stderr(PIPE); }
+  static Options pipeStdin() { return Options().stdinFd(PIPE); }
+  static Options pipeStdout() { return Options().stdoutFd(PIPE); }
+  static Options pipeStderr() { return Options().stderrFd(PIPE); }
+
+  // Non-copiable, but movable
+  Subprocess(const Subprocess&) = delete;
+  Subprocess& operator=(const Subprocess&) = delete;
+  Subprocess(Subprocess&&) = default;
+  Subprocess& operator=(Subprocess&&) = default;
+
+  /**
+   * Create an uninitialized subprocess.
+   *
+   * In this state it can only be destroyed, or assigned to using the move
+   * assignment operator.
+   */
+  Subprocess();
 
   /**
    * Create a subprocess from the given arguments.  argv[0] must be listed.
@@ -371,6 +456,7 @@ class Subprocess : private boost::noncopyable {
    * The shell to use is taken from the environment variable $SHELL,
    * or /bin/sh if $SHELL is unset.
    */
+  FOLLY_DEPRECATED("Prefer not running in a shell or use `shellify`.")
   explicit Subprocess(
       const std::string& cmd,
       const Options& options = Options(),
@@ -397,11 +483,12 @@ class Subprocess : private boost::noncopyable {
   ProcessReturnCode returnCode() const { return returnCode_; }
 
   /**
-   * Poll the child's status and return it, return -1 if the process
-   * is still running.  NOTE that it is illegal to call poll again after
-   * poll indicated that the process has terminated, or to call poll on a
-   * process that hasn't been successfully started (the constructor threw an
-   * exception).
+   * Poll the child's status and return it. Return the exit status if the
+   * subprocess had quit, or RUNNING otherwise.  Throws an std::logic_error
+   * if called on a Subprocess whose status is no longer RUNNING.  No other
+   * exceptions are possible.  Aborts on egregious violations of contract,
+   * e.g. if you wait for the underlying process without going through this
+   * Subprocess instance.
    */
   ProcessReturnCode poll();
 
@@ -413,9 +500,10 @@ class Subprocess : private boost::noncopyable {
   bool pollChecked();
 
   /**
-   * Wait for the process to terminate and return its status.
-   * Similarly to poll, it is illegal to call wait after the process
-   * has already been reaped or if the process has not successfully started.
+   * Wait for the process to terminate and return its status.  Like poll(),
+   * the only exception this can throw is std::logic_error if you call this
+   * on a Subprocess whose status is RUNNING.  Aborts on egregious
+   * violations of contract, like an out-of-band waitpid(p.pid(), 0, 0).
    */
   ProcessReturnCode wait();
 
@@ -535,7 +623,7 @@ class Subprocess : private boost::noncopyable {
    * expensive implementation choice, in order to make closeParentFd()
    * thread-safe.
    */
-  typedef std::function<bool(int, int)> FdCallback;
+  using FdCallback = folly::Function<bool(int, int)>;
   void communicate(FdCallback readCallback, FdCallback writeCallback);
 
   /**
@@ -544,17 +632,13 @@ class Subprocess : private boost::noncopyable {
    * descriptors.  Use the readLinesCallback() helper to get template
    * deduction.  For example:
    *
-   *   auto read_cb = Subprocess::readLinesCallback(
-   *     [](int fd, folly::StringPiece s) {
-   *       std::cout << fd << " said: " << s;
-   *       return false;  // Keep reading from the child
-   *     }
-   *   );
    *   subprocess.communicate(
-   *     // ReadLinesCallback contains StreamSplitter contains IOBuf, making
-   *     // it noncopyable, whereas std::function must be copyable.  So, we
-   *     // keep the callback in a local, and instead pass a reference.
-   *     std::ref(read_cb),
+   *     Subprocess::readLinesCallback(
+   *       [](int fd, folly::StringPiece s) {
+   *         std::cout << fd << " said: " << s;
+   *         return false;  // Keep reading from the child
+   *       }
+   *     ),
    *     [](int pdf, int cfd){ return true; }  // Don't write to the child
    *   );
    *
@@ -587,7 +671,7 @@ class Subprocess : private boost::noncopyable {
       uint64_t maxLineLength = 0,  // No line length limit by default
       char delimiter = '\n',
       uint64_t bufSize = 1024
-    ) : fdLineCb_(std::move(fdLineCb)),
+    ) : fdLineCb_(std::forward<Callback>(fdLineCb)),
         maxLineLength_(maxLineLength),
         delimiter_(delimiter),
         bufSize_(bufSize) {}
@@ -606,6 +690,7 @@ class Subprocess : private boost::noncopyable {
         if (ret == -1 && errno == EAGAIN) {  // No more data for now
           return false;
         }
+        checkUnixError(ret, "read");
         if (ret == 0) {  // Reached end-of-file
           splitter.flush();  // Ignore return since the file is over anyway
           return true;
@@ -627,14 +712,14 @@ class Subprocess : private boost::noncopyable {
 
   // Helper to enable template deduction
   template <class Callback>
-  static ReadLinesCallback<Callback> readLinesCallback(
+  static auto readLinesCallback(
       Callback&& fdLineCb,
-      uint64_t maxLineLength = 0,  // No line length limit by default
+      uint64_t maxLineLength = 0, // No line length limit by default
       char delimiter = '\n',
-      uint64_t bufSize = 1024) {
-    return ReadLinesCallback<Callback>(
-      std::move(fdLineCb), maxLineLength, delimiter, bufSize
-    );
+      uint64_t bufSize = 1024)
+      -> ReadLinesCallback<typename std::decay<Callback>::type> {
+    return ReadLinesCallback<typename std::decay<Callback>::type>(
+        std::forward<Callback>(fdLineCb), maxLineLength, delimiter, bufSize);
   }
 
   /**
@@ -683,9 +768,9 @@ class Subprocess : private boost::noncopyable {
   int parentFd(int childFd) const {
     return pipes_[findByChildFd(childFd)].pipe.fd();
   }
-  int stdin() const { return parentFd(0); }
-  int stdout() const { return parentFd(1); }
-  int stderr() const { return parentFd(2); }
+  int stdinFd() const { return parentFd(0); }
+  int stdoutFd() const { return parentFd(1); }
+  int stderrFd() const { return parentFd(2); }
 
   /**
    * The child's pipes are logically separate from the process metadata
@@ -701,6 +786,7 @@ class Subprocess : private boost::noncopyable {
    * No, you may NOT call this from a communicate() callback.
    */
   struct ChildPipe {
+    ChildPipe(int fd, folly::File&& ppe) : childFd(fd), pipe(std::move(ppe)) {}
     int childFd;
     folly::File pipe;  // Owns the parent FD
   };
@@ -744,9 +830,8 @@ class Subprocess : private boost::noncopyable {
   // Returns an index into pipes_. Throws std::invalid_argument if not found.
   size_t findByChildFd(const int childFd) const;
 
-
-  pid_t pid_;
-  ProcessReturnCode returnCode_;
+  pid_t pid_{-1};
+  ProcessReturnCode returnCode_{RV_NOT_STARTED};
 
   /**
    * Represents a pipe between this process, and the child process (or its
@@ -791,5 +876,3 @@ inline Subprocess::Options& Subprocess::Options::operator|=(
 }
 
 }  // namespace folly
-
-#endif /* FOLLY_SUBPROCESS_H_ */