Support linux namespace clone flags
authorAravind Anbudurai <aru7@fb.com>
Wed, 12 Apr 2017 08:36:29 +0000 (01:36 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 12 Apr 2017 08:54:48 +0000 (01:54 -0700)
Summary:
This diffs adds supports on folly::Subprocess to be able to take clone flags and
use them to call clone(2) instead of the default vfork()

I checked that all tests pass when I replace vfork with fork on trunk. So there
isn't anything built assuming the parent is paused for an execve. Correct me if
I am wrong here.

(Note: this ignores all push blocking failures!)

Reviewed By: snarkmaster

Differential Revision: D4853169

fbshipit-source-id: 7e5851df3a49996a4a5dc1945457686dd042e1f4

folly/Subprocess.cpp
folly/Subprocess.h
folly/test/SubprocessTest.cpp

index 6c04e91..b8070c0 100644 (file)
@@ -22,6 +22,8 @@
 
 #if __linux__
 #include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
 #endif
 #include <fcntl.h>
 
@@ -389,7 +391,19 @@ void Subprocess::spawnInternal(
   // 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();
+
+  pid_t pid;
+#ifdef __linux__
+  if (options.cloneFlags_) {
+    pid = syscall(SYS_clone, *options.cloneFlags_, 0, nullptr, nullptr);
+    checkUnixError(pid, errno, "clone");
+  } else {
+#endif
+    pid = vfork();
+    checkUnixError(pid, errno, "vfork");
+#ifdef __linux__
+  }
+#endif
   if (pid == 0) {
     int errnoValue = prepareChild(options, &oldSignals, childDir);
     if (errnoValue != 0) {
@@ -400,8 +414,6 @@ void Subprocess::spawnInternal(
     // If we get here, exec() failed.
     childError(errFd, kExecFailure, errnoValue);
   }
-  // In parent.  Make sure vfork() succeeded.
-  checkUnixError(pid, errno, "vfork");
 
   // 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
index 44e3fad..86ca6bf 100644 (file)
 #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>
+#include <folly/Optional.h>
 #include <folly/Portability.h>
 #include <folly/Range.h>
+#include <folly/gen/String.h>
+#include <folly/io/IOBufQueue.h>
 
 namespace folly {
 
@@ -397,6 +398,29 @@ class Subprocess {
       return *this;
     }
 
+#if __linux__
+    /**
+     * This is an experimental feature, it is best you don't use it at this
+     * point of time.
+     * Although folly would support cloning with custom flags in some form, this
+     * API might change in the near future. So use the following assuming it is
+     * experimental. (Apr 11, 2017)
+     *
+     * This unlocks Subprocess to support clone flags, many of them need
+     * CAP_SYS_ADMIN permissions. It might also require you to go through the
+     * implementation to understand what happens before, between and after the
+     * fork-and-exec.
+     *
+     * `man 2 clone` would be a starting point for knowing about the available
+     * flags.
+     */
+    using clone_flags_t = uint64_t;
+    Options& useCloneWithFlags(clone_flags_t cloneFlags) noexcept {
+      cloneFlags_ = cloneFlags;
+      return *this;
+    }
+#endif
+
     /**
      * Helpful way to combine Options.
      */
@@ -414,6 +438,11 @@ class Subprocess {
     bool processGroupLeader_{false};
     DangerousPostForkPreExecCallback*
       dangerousPostForkPreExecCallback_{nullptr};
+#if __linux__
+    // none means `vfork()` instead of a custom `clone()`
+    // Optional<> is used because value of '0' means do clone without any flags.
+    Optional<clone_flags_t> cloneFlags_;
+#endif
   };
 
   static Options pipeStdin() { return Options().stdinFd(PIPE); }
index 07ede20..8d9f100 100644 (file)
 #include <glog/logging.h>
 
 #include <folly/Exception.h>
-#include <folly/Format.h>
 #include <folly/FileUtil.h>
+#include <folly/Format.h>
 #include <folly/String.h>
+#include <folly/experimental/TestUtil.h>
+#include <folly/experimental/io/FsUtil.h>
 #include <folly/gen/Base.h>
 #include <folly/gen/File.h>
 #include <folly/gen/String.h>
-#include <folly/experimental/TestUtil.h>
-#include <folly/experimental/io/FsUtil.h>
 #include <folly/portability/GTest.h>
 #include <folly/portability/Unistd.h>
 
@@ -47,6 +47,29 @@ TEST(SimpleSubprocessTest, ExitsSuccessfullyChecked) {
   proc.waitChecked();
 }
 
+TEST(SimpleSubprocessTest, CloneFlagsWithVfork) {
+  Subprocess proc(
+      std::vector<std::string>{"/bin/true"},
+      Subprocess::Options().useCloneWithFlags(SIGCHLD | CLONE_VFORK));
+  EXPECT_EQ(0, proc.wait().exitStatus());
+}
+
+TEST(SimpleSubprocessTest, CloneFlagsWithFork) {
+  Subprocess proc(
+      std::vector<std::string>{"/bin/true"},
+      Subprocess::Options().useCloneWithFlags(SIGCHLD));
+  EXPECT_EQ(0, proc.wait().exitStatus());
+}
+
+TEST(SimpleSubprocessTest, CloneFlagsSubprocessCtorExitsAfterExec) {
+  Subprocess proc(
+      std::vector<std::string>{"/bin/sleep", "3600"},
+      Subprocess::Options().useCloneWithFlags(SIGCHLD));
+  checkUnixError(::kill(proc.pid(), SIGKILL), "kill");
+  auto retCode = proc.wait();
+  EXPECT_TRUE(retCode.killed());
+}
+
 TEST(SimpleSubprocessTest, ExitsWithError) {
   Subprocess proc(std::vector<std::string>{ "/bin/false" });
   EXPECT_EQ(1, proc.wait().exitStatus());