Modify the Chrome Linux zygote to support a ZygoteForkHelper for flexible
fork implementations.

BUG=nativeclient:480
TEST=existing zygote tests

Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=92787

Review URL: http://codereview.chromium.org/7397003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@92858 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/child_process_launcher.cc b/content/browser/child_process_launcher.cc
index 7bb603f..7bada19 100644
--- a/content/browser/child_process_launcher.cc
+++ b/content/browser/child_process_launcher.cc
@@ -127,8 +127,9 @@
         mapping.push_back(std::pair<uint32_t, int>(kCrashDumpSignal,
                                                    crash_signal_fd));
       }
-      handle = ZygoteHost::GetInstance()->ForkRenderer(cmd_line->argv(),
-                                                       mapping);
+      handle = ZygoteHost::GetInstance()->ForkRequest(cmd_line->argv(),
+                                                      mapping,
+                                                      process_type);
     } else
     // Fall through to the normal posix case below when we're not zygoting.
 #endif
@@ -378,4 +379,3 @@
   if (context_)
     context_->set_terminate_child_on_shutdown(terminate_on_shutdown);
 }
-
diff --git a/content/browser/zygote_host_linux.cc b/content/browser/zygote_host_linux.cc
index 955628d..fc9a520 100644
--- a/content/browser/zygote_host_linux.cc
+++ b/content/browser/zygote_host_linux.cc
@@ -58,8 +58,7 @@
       init_(false),
       using_suid_sandbox_(false),
       have_read_sandbox_status_word_(false),
-      sandbox_status_(0) {
-}
+      sandbox_status_(0) {}
 
 ZygoteHost::~ZygoteHost() {
   if (init_)
@@ -110,6 +109,7 @@
     switches::kRegisterPepperPlugins,
     switches::kDisableSeccompSandbox,
     switches::kEnableSeccompSandbox,
+    switches::kNaClLinuxHelper,
   };
   cmd_line.CopySwitchesFrom(browser_command_line, kForwardSwitches,
                             arraysize(kForwardSwitches));
@@ -223,13 +223,15 @@
   return HANDLE_EINTR(read(control_fd_, buf, buf_len));
 }
 
-pid_t ZygoteHost::ForkRenderer(
+pid_t ZygoteHost::ForkRequest(
     const std::vector<std::string>& argv,
-    const base::GlobalDescriptors::Mapping& mapping) {
+    const base::GlobalDescriptors::Mapping& mapping,
+    const std::string& process_type) {
   DCHECK(init_);
   Pickle pickle;
 
   pickle.WriteInt(kCmdFork);
+  pickle.WriteString(process_type);
   pickle.WriteInt(argv.size());
   for (std::vector<std::string>::const_iterator
        i = argv.begin(); i != argv.end(); ++i)
diff --git a/content/browser/zygote_host_linux.h b/content/browser/zygote_host_linux.h
index 5ead5f59..9098e6d 100644
--- a/content/browser/zygote_host_linux.h
+++ b/content/browser/zygote_host_linux.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -32,10 +32,12 @@
 
   void Init(const std::string& sandbox_cmd);
 
-  // Tries to start a renderer process.  Returns its pid on success, otherwise
+  // Tries to start a process of type indicated by process_type.
+  // Returns its pid on success, otherwise
   // base::kNullProcessHandle;
-  pid_t ForkRenderer(const std::vector<std::string>& command_line,
-                     const base::GlobalDescriptors::Mapping& mapping);
+  pid_t ForkRequest(const std::vector<std::string>& command_line,
+                    const base::GlobalDescriptors::Mapping& mapping,
+                    const std::string& process_type);
   void EnsureProcessTerminated(pid_t process);
 
   // Get the termination status (and, optionally, the exit code) of
diff --git a/content/browser/zygote_main_linux.cc b/content/browser/zygote_main_linux.cc
index 99a7735..95b3975b 100644
--- a/content/browser/zygote_main_linux.cc
+++ b/content/browser/zygote_main_linux.cc
@@ -37,9 +37,11 @@
 #include "content/common/sandbox_methods_linux.h"
 #include "content/common/set_process_title.h"
 #include "content/common/unix_domain_socket_posix.h"
+#include "content/common/zygote_fork_delegate_linux.h"
 #include "seccompsandbox/sandbox.h"
 #include "skia/ext/SkFontHost_fontconfig_control.h"
 #include "unicode/timezone.h"
+#include "ipc/ipc_switches.h"
 
 #if defined(OS_LINUX)
 #include <sys/epoll.h>
@@ -97,8 +99,9 @@
 // runs it.
 class Zygote {
  public:
-  explicit Zygote(int sandbox_flags)
-      : sandbox_flags_(sandbox_flags) {
+  explicit Zygote(int sandbox_flags, ZygoteForkDelegate* helper)
+    : sandbox_flags_(sandbox_flags),
+      helper_(helper) {
   }
 
   bool ProcessRequests() {
@@ -165,6 +168,7 @@
         case ZygoteHost::kCmdFork:
           // This function call can return multiple times, once per fork().
           return HandleForkRequest(fd, pickle, iter, fds);
+
         case ZygoteHost::kCmdReap:
           if (!fds.empty())
             break;
@@ -247,9 +251,12 @@
   // sandbox, it returns the real PID of the child process as it
   // appears outside the sandbox, rather than returning the PID inside
   // the sandbox.
-  int ForkWithRealPid() {
-    if (!g_suid_sandbox_active)
+  int ForkWithRealPid(const std::string& process_type, std::vector<int>& fds,
+                      const std::string& channel_switch) {
+    const bool use_helper = (helper_ && helper_->CanHelp(process_type));
+    if (!(use_helper || g_suid_sandbox_active)) {
       return fork();
+    }
 
     int dummy_fd;
     ino_t dummy_inode;
@@ -270,7 +277,13 @@
       goto error;
     }
 
-    pid = fork();
+    if (use_helper) {
+      fds.push_back(dummy_fd);
+      fds.push_back(pipe_fds[0]);
+      pid = helper_->Fork(fds);
+    } else {
+      pid = fork();
+    }
     if (pid < 0) {
       goto error;
     } else if (pid == 0) {
@@ -294,33 +307,43 @@
       dummy_fd = -1;
       close(pipe_fds[0]);
       pipe_fds[0] = -1;
-      uint8_t reply_buf[512];
-      Pickle request;
-      request.WriteInt(LinuxSandbox::METHOD_GET_CHILD_WITH_INODE);
-      request.WriteUInt64(dummy_inode);
-
-      const ssize_t r = UnixDomainSocket::SendRecvMsg(
-          kMagicSandboxIPCDescriptor, reply_buf, sizeof(reply_buf), NULL,
-          request);
-      if (r == -1) {
-        LOG(ERROR) << "Failed to get child process's real PID";
-        goto error;
-      }
-
       base::ProcessId real_pid;
-      Pickle reply(reinterpret_cast<char*>(reply_buf), r);
-      void* iter2 = NULL;
-      if (!reply.ReadInt(&iter2, &real_pid))
-        goto error;
-      if (real_pid <= 0) {
-        // METHOD_GET_CHILD_WITH_INODE failed. Did the child die already?
-        LOG(ERROR) << "METHOD_GET_CHILD_WITH_INODE failed";
-        goto error;
+      if (g_suid_sandbox_active) {
+        uint8_t reply_buf[512];
+        Pickle request;
+        request.WriteInt(LinuxSandbox::METHOD_GET_CHILD_WITH_INODE);
+        request.WriteUInt64(dummy_inode);
+
+        const ssize_t r = UnixDomainSocket::SendRecvMsg(
+            kMagicSandboxIPCDescriptor, reply_buf, sizeof(reply_buf), NULL,
+            request);
+        if (r == -1) {
+          LOG(ERROR) << "Failed to get child process's real PID";
+          goto error;
+        }
+
+        Pickle reply(reinterpret_cast<char*>(reply_buf), r);
+        void* iter = NULL;
+        if (!reply.ReadInt(&iter, &real_pid))
+          goto error;
+        if (real_pid <= 0) {
+          // METHOD_GET_CHILD_WITH_INODE failed. Did the child die already?
+          LOG(ERROR) << "METHOD_GET_CHILD_WITH_INODE failed";
+          goto error;
+        }
+        real_pids_to_sandbox_pids[real_pid] = pid;
       }
-      real_pids_to_sandbox_pids[real_pid] = pid;
-      if (HANDLE_EINTR(write(pipe_fds[1], "x", 1)) != 1) {
-        LOG(ERROR) << "Failed to synchronise with child process";
-        goto error;
+      if (use_helper) {
+        real_pid = pid;
+        if (!helper_->AckChild(pipe_fds[1], channel_switch)) {
+          LOG(ERROR) << "Failed to synchronise with zygote fork helper";
+          goto error;
+        }
+      } else {
+        if (HANDLE_EINTR(write(pipe_fds[1], "x", 1)) != 1) {
+          LOG(ERROR) << "Failed to synchronise with child process";
+          goto error;
+        }
       }
       close(pipe_fds[1]);
       return real_pid;
@@ -342,12 +365,19 @@
 
   // Handle a 'fork' request from the browser: this means that the browser
   // wishes to start a new renderer.
-  bool HandleForkRequest(int fd, const Pickle& pickle, void* iter,
-                         std::vector<int>& fds) {
+  bool HandleForkRequest(int fd, const Pickle& pickle,
+                         void* iter, std::vector<int>& fds) {
     std::vector<std::string> args;
     int argc, numfds;
     base::GlobalDescriptors::Mapping mapping;
     base::ProcessId child;
+    std::string process_type;
+    std::string channel_id;
+    const std::string channel_id_prefix = std::string("--")
+        + switches::kProcessChannelID + std::string("=");
+
+    if (!pickle.ReadString(&iter, &process_type))
+      goto error;
 
     if (!pickle.ReadInt(&iter, &argc))
       goto error;
@@ -357,6 +387,8 @@
       if (!pickle.ReadString(&iter, &arg))
         goto error;
       args.push_back(arg);
+      if (arg.compare(0, channel_id_prefix.length(), channel_id_prefix) == 0)
+        channel_id = arg;
     }
 
     if (!pickle.ReadInt(&iter, &numfds))
@@ -374,7 +406,7 @@
     mapping.push_back(std::make_pair(
         static_cast<uint32_t>(kSandboxIPCChannel), kMagicSandboxIPCDescriptor));
 
-    child = ForkWithRealPid();
+    child = ForkWithRealPid(process_type, fds, channel_id);
 
     if (!child) {
 #if defined(SECCOMP_SANDBOX)
@@ -447,6 +479,7 @@
   ProcessMap real_pids_to_sandbox_pids;
 
   const int sandbox_flags_;
+  ZygoteForkDelegate* helper_;
 };
 
 // With SELinux we can carve out a precise sandbox, so we don't have to play
@@ -705,7 +738,8 @@
 
 #endif  // CHROMIUM_SELINUX
 
-bool ZygoteMain(const MainFunctionParams& params) {
+bool ZygoteMain(const MainFunctionParams& params,
+                ZygoteForkDelegate* forkdelegate) {
 #if !defined(CHROMIUM_SELINUX)
   g_am_zygote_or_renderer = true;
 #endif
@@ -724,6 +758,15 @@
   }
 #endif  // SECCOMP_SANDBOX
 
+  if (forkdelegate != NULL) {
+    VLOG(1) << "ZygoteMain: initializing fork delegate";
+    forkdelegate->Init(getenv("SBX_D") != NULL, // g_suid_sandbox_active,
+                       kBrowserDescriptor,
+                       kMagicSandboxIPCDescriptor);
+  } else {
+    VLOG(1) << "ZygoteMain: fork delegate is NULL";
+  }
+
   // Turn on the SELinux or SUID sandbox
   if (!EnterSandbox()) {
     LOG(FATAL) << "Failed to enter sandbox. Fail safe abort. (errno: "
@@ -760,7 +803,7 @@
   }
 #endif  // SECCOMP_SANDBOX
 
-  Zygote zygote(sandbox_flags);
+  Zygote zygote(sandbox_flags, forkdelegate);
   // This function call can return multiple times, once per fork().
   return zygote.ProcessRequests();
 }
diff --git a/content/common/content_switches.cc b/content/common/content_switches.cc
index ba2448f..e18ccbf 100644
--- a/content/common/content_switches.cc
+++ b/content/common/content_switches.cc
@@ -275,6 +275,10 @@
 // (used for launching NaCl loader processes on 64-bit Windows).
 const char kNaClBrokerProcess[]             = "nacl-broker";
 
+// Enables experimental lightweight Native Client launcher for Linux
+// Value is the path to the helper binary.
+const char kNaClLinuxHelper[]               = "nacl-linux-helper";
+
 // Causes the process to run as a NativeClient loader.
 const char kNaClLoaderProcess[]             = "nacl-loader";
 
diff --git a/content/common/content_switches.h b/content/common/content_switches.h
index 868ad84..c94af06 100644
--- a/content/common/content_switches.h
+++ b/content/common/content_switches.h
@@ -92,6 +92,10 @@
 // TODO(jam): this doesn't belong in content.
 extern const char kNaClBrokerProcess[];
 extern const char kNaClLoaderProcess[];
+// TODO(bradchen): remove kNaClLinuxHelper switch.
+// This switch enables the experimental lightweight nacl_helper for Linux.
+// It will be going away soon, when the helper is enabled permanently.
+extern const char kNaClLinuxHelper[];
 extern const char kNoDisplayingInsecureContent[];
 extern const char kNoJsRandomness[];
 extern const char kNoReferrers[];
diff --git a/content/common/sandbox_methods_linux.h b/content/common/sandbox_methods_linux.h
index 02287f6d..f048ad82 100644
--- a/content/common/sandbox_methods_linux.h
+++ b/content/common/sandbox_methods_linux.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
@@ -7,7 +7,7 @@
 #pragma once
 
 // This is a list of sandbox IPC methods which the renderer may send to the
-// sandbox host. See http://code.google.com/p/chromium/LinuxSandboxIPC
+// sandbox host. See http://code.google.com/p/chromium/wiki/LinuxSandboxIPC
 // This isn't the full list, values < 32 are reserved for methods called from
 // Skia.
 class LinuxSandbox {
diff --git a/content/common/zygote_fork_delegate_linux.h b/content/common/zygote_fork_delegate_linux.h
new file mode 100644
index 0000000..26dfbc9
--- /dev/null
+++ b/content/common/zygote_fork_delegate_linux.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_COMMON_ZYGOTE_FORK_DELEGATE_LINUX_H_
+#define CONTENT_COMMON_ZYGOTE_FORK_DELEGATE_LINUX_H_
+#pragma once
+
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+
+// The ZygoteForkDelegate allows the Chrome Linux zygote to delegate
+// fork operations to another class that knows how to do some
+// specialized version of fork.
+class ZygoteForkDelegate {
+ public:
+  // A ZygoteForkDelegate is created during Chrome linux zygote
+  // initialization, and provides "fork()" functionality with
+  // as an alternative to forking the zygote. A new delegate is
+  // passed in as an argument to ZygoteMain().
+  ZygoteForkDelegate() {}
+  virtual ~ZygoteForkDelegate() {}
+
+  // Initialization happens in the zygote after it has been
+  // started by ZygoteMain.
+  virtual void Init(bool sandboxed,
+                    int browserdesc,
+                    int sandboxdesc) = 0;
+
+  // Returns 'true' if the delegate would like to handle a given
+  // fork request. Otherwise returns false.
+  virtual bool CanHelp(const std::string& process_type) = 0;
+
+  // Delegate forks, returning a -1 on failure. Outside the
+  // suid sandbox, Fork() returns the Linux process ID. Inside
+  // the sandbox, returns a positive integer, with PID discovery
+  // handled by the sandbox.
+  virtual pid_t Fork(const std::vector<int>& fds) = 0;
+
+  // After a successful for, signal the child to indicate that
+  // the child's PID has been received. Also communicate the
+  // channel switch as a part of acknowledgement message.
+  virtual bool AckChild(int fd, const std::string& channel_switch) = 0;
+};
+#endif  // CONTENT_COMMON_ZYGOTE_FORK_DELEGATE_LINUX_H_