login_manager: Move startup from shell scripts to C++.

The XServerRunner class replaces xstart.sh and cros-xauth.c.
ChromiumCommandBuilder and the PerformChromeSetup() function
replace session_manager_setup.sh and most of ui.conf.

ChromiumCommandBuilder is used to configure the system (e.g.
creating directories) and to generate environment variables
and command-line flags that will likely be needed by all
Chromium-derived binaries.

PerformChromeSetup() is part of session_manager and performs
additional Chrome-specific configuration.

Developers wishing to make changes to Chrome environment and
command line without needing to recompile session_manager
may edit /etc/chrome_dev.conf.

XServerRunner, ChromiumCommandBuilder, and the util.{h,cc}
files will be moved to a different repository in a following
change.

BUG=chromium:377301
TEST=wrote a bunch of unit tests; also did manual
     verification that chrome's command line hasn't changed
CQ-DEPEND=I5e17d3c2266ccaaea2847a3050a746b9b102dd88
CQ-DEPEND=I514f1e9feacb2e808a2fe1e58329abf41892d08c

Change-Id: Iab76d2f4e25463688b3ec7f85513ff27e1da36ff
Reviewed-on: https://chromium-review.googlesource.com/202352
Reviewed-by: Daniel Erat <derat@chromium.org>
Commit-Queue: Daniel Erat <derat@chromium.org>
Tested-by: Daniel Erat <derat@chromium.org>
diff --git a/Makefile b/Makefile
index a01755a..d7f5b48 100644
--- a/Makefile
+++ b/Makefile
@@ -62,10 +62,5 @@
 clean: CLEAN($(TEST_BIN)))
 tests: TEST(CXX_BINARY($(TEST_BIN)))
 
-CC_BINARY(cros-xauth): cros-xauth.o
-CC_BINARY(cros-xauth): LDLIBS =
-clean: CLEAN(cros-xauth)
-
-all: login_manager CC_BINARY(cros-xauth)
-login_manager: \
-  CXX_BINARY($(KEYGEN_BIN)) CXX_BINARY($(SESSION_BIN))
+all: login_manager
+login_manager: CXX_BINARY($(KEYGEN_BIN)) CXX_BINARY($(SESSION_BIN))
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
new file mode 100644
index 0000000..33acae5
--- /dev/null
+++ b/PRESUBMIT.cfg
@@ -0,0 +1,2 @@
+[Hook Scripts]
+hook0 = if grep -q '^[^#]' chrome_dev.conf; then echo "Please don't check in changes to chrome_dev.conf." 1>&2; exit 1; fi
diff --git a/browser_job.cc b/browser_job.cc
index 79abf82..1eb633e 100644
--- a/browser_job.cc
+++ b/browser_job.cc
@@ -45,12 +45,15 @@
 const time_t BrowserJob::kRestartWindowSeconds = 60;
 
 BrowserJob::BrowserJob(const std::vector<std::string>& arguments,
+                       const std::map<std::string, std::string>&
+                           environment_variables,
                        bool support_multi_profile,
                        uid_t desired_uid,
                        FileChecker* checker,
                        LoginMetrics* metrics,
                        SystemUtils* utils)
-      : arguments_(arguments),
+      : environment_variables_(environment_variables),
+        arguments_(arguments),
         file_checker_(checker),
         login_metrics_(metrics),
 
@@ -106,7 +109,7 @@
 
   LOG(INFO) << "Running child " << GetName() << "...";
   RecordTime();
-  return subprocess_.ForkAndExec(ExportArgv());
+  return subprocess_.ForkAndExec(ExportArgv(), environment_variables_);
 }
 
 void BrowserJob::KillEverything(int signal, const std::string& message) {
diff --git a/browser_job.h b/browser_job.h
index 4aa9ca9..f3ee481 100644
--- a/browser_job.h
+++ b/browser_job.h
@@ -11,8 +11,9 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <string>
+#include <map>
 #include <queue>
+#include <string>
 #include <vector>
 
 #include <base/basictypes.h>
@@ -75,6 +76,7 @@
 class BrowserJob : public BrowserJobInterface {
  public:
   BrowserJob(const std::vector<std::string>& arguments,
+             const std::map<std::string, std::string>& environment_varables,
              bool support_multi_profile,
              uid_t desired_uid,
              FileChecker* checker,
@@ -115,6 +117,9 @@
   static const time_t kRestartWindowSeconds;
 
  private:
+  // Environment variables exported for Chrome.
+  std::map<std::string, std::string> environment_variables_;
+
   // Arguments to pass to exec.
   std::vector<std::string> arguments_;
 
diff --git a/browser_job_unittest.cc b/browser_job_unittest.cc
index f38aff8..e31773f 100644
--- a/browser_job_unittest.cc
+++ b/browser_job_unittest.cc
@@ -7,6 +7,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <map>
 #include <set>
 #include <string>
 #include <vector>
@@ -71,6 +72,7 @@
     }
   }
 
+  std::map<std::string, std::string> env_;
   std::vector<std::string> argv_;
   MockFileChecker checker_;
   MockMetrics metrics_;
@@ -96,7 +98,8 @@
 void BrowserJobTest::SetUp() {
   argv_ = std::vector<std::string>(kArgv,
                                    kArgv + arraysize(BrowserJobTest::kArgv));
-  job_.reset(new BrowserJob(argv_, false, 1, &checker_, &metrics_, &utils_));
+  job_.reset(
+      new BrowserJob(argv_, env_, false, 1, &checker_, &metrics_, &utils_));
 }
 
 TEST_F(BrowserJobTest, InitializationTest) {
@@ -175,7 +178,7 @@
 }
 
 TEST_F(BrowserJobTest, NullFileCheckerTest) {
-  BrowserJob job(argv_, true, 1, NULL, &metrics_, &utils_);
+  BrowserJob job(argv_, env_, true, 1, NULL, &metrics_, &utils_);
   EXPECT_TRUE(job.ShouldRunBrowser());
 }
 
@@ -238,7 +241,7 @@
 }
 
 TEST_F(BrowserJobTest, StartStopMultiSessionTest) {
-  BrowserJob job(argv_, true, 1, &checker_, &metrics_, &utils_);
+  BrowserJob job(argv_, env_, true, 1, &checker_, &metrics_, &utils_);
   job.StartSession(kUser, kHash);
 
   std::vector<std::string> job_args = job.ExportArgv();
@@ -272,7 +275,7 @@
   };
   std::vector<std::string> argv(
       kArgvWithLoginFlag, kArgvWithLoginFlag + arraysize(kArgvWithLoginFlag));
-  BrowserJob job(argv, false, 1, &checker_, &metrics_, &utils_);
+  BrowserJob job(argv, env_, false, 1, &checker_, &metrics_, &utils_);
 
   job.StartSession(kUser, kHash);
 
@@ -323,7 +326,7 @@
 
 TEST_F(BrowserJobTest, ExportArgv) {
   std::vector<std::string> argv(kArgv, kArgv + arraysize(kArgv));
-  BrowserJob job(argv, false, -1, &checker_, &metrics_, &utils_);
+  BrowserJob job(argv, env_, false, -1, &checker_, &metrics_, &utils_);
 
   const char* kExtraArgs[] = { "--ichi", "--ni", "--san" };
   std::vector<std::string> extra_args(kExtraArgs,
diff --git a/child_exit_handler.cc b/child_exit_handler.cc
index 7e8167e..d069022 100644
--- a/child_exit_handler.cc
+++ b/child_exit_handler.cc
@@ -102,7 +102,7 @@
 }
 
 void ChildExitHandler::Dispatch(const siginfo_t& info) {
-  // Find the manager who's child has exited.
+  // Find the manager whose child has exited.
   std::vector<JobManagerInterface*>::iterator job_manager = managers_.begin();
   while (job_manager != managers_.end()) {
     if ((*job_manager)->IsManagedJob(info.si_pid))
@@ -119,6 +119,7 @@
     LOG_IF(ERROR, info.si_status != 0) << "  Exited with exit code "
                                        << info.si_status;
     CHECK(info.si_status != ChildJobInterface::kCantSetUid);
+    CHECK(info.si_status != ChildJobInterface::kCantSetEnv);
     CHECK(info.si_status != ChildJobInterface::kCantExec);
   } else {
     LOG(ERROR) << "  Exited with signal " << info.si_status;
diff --git a/child_job.cc b/child_job.cc
index c58f489..c9c2efe 100644
--- a/child_job.cc
+++ b/child_job.cc
@@ -22,6 +22,7 @@
 const int ChildJobInterface::kCantSetUid = 127;
 const int ChildJobInterface::kCantSetGid = 128;
 const int ChildJobInterface::kCantSetGroups = 129;
+const int ChildJobInterface::kCantSetEnv = 130;
 const int ChildJobInterface::kCantExec = 255;
 
 ChildJobInterface::Subprocess::Subprocess(uid_t desired_uid,
@@ -34,7 +35,8 @@
 ChildJobInterface::Subprocess::~Subprocess() {}
 
 bool ChildJobInterface::Subprocess::ForkAndExec(
-    const std::vector<std::string>& args) {
+    const std::vector<std::string>& args,
+    const std::map<std::string, std::string>& environment_variables) {
   pid_ = system_->fork();
   if (pid_ == 0) {
     SessionManagerService::RevertHandlers();
@@ -46,6 +48,19 @@
 
     logging::CloseLogFile();  // So browser does not inherit logging FD.
 
+    if (clearenv() != 0) {
+      PLOG(ERROR) << "Error clearing environment";
+      exit(kCantSetEnv);
+    }
+    for (std::map<std::string, std::string>::const_iterator it =
+             environment_variables.begin();
+         it != environment_variables.end(); ++it) {
+      if (setenv(it->first.c_str(), it->second.c_str(), 1) != 0) {
+        PLOG(ERROR) << "Error exporting " << it->first << "=" << it->second;
+        exit(kCantSetEnv);
+      }
+    }
+
     scoped_ptr<char const*[]> argv(new char const*[args.size() + 1]);
     for (size_t i = 0; i < args.size(); ++i)
       argv[i] = args[i].c_str();
diff --git a/child_job.h b/child_job.h
index 16ace43..d644ec4 100644
--- a/child_job.h
+++ b/child_job.h
@@ -7,6 +7,7 @@
 
 #include <unistd.h>
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -28,10 +29,15 @@
     Subprocess(uid_t desired_uid, SystemUtils* system);
     virtual ~Subprocess();
 
-    // fork() and exec(argv). Returns false if fork() fails, true otherwise.
-    bool ForkAndExec(const std::vector<std::string>& args);
+    // fork(), export |environment_variables|, and exec(argv).
+    // Returns false if fork() fails, true otherwise.
+    bool ForkAndExec(
+        const std::vector<std::string>& args,
+        const std::map<std::string, std::string>& environment_variables);
+
     // Sends signal to pid_. No-op if there is no subprocess running.
     void Kill(int signal);
+
     // Sends signal to pid_'s entire process group.
     // No-op if there is no subprocess running.
     void KillEverything(int signal);
@@ -89,6 +95,7 @@
   static const int kCantSetUid;
   static const int kCantSetGid;
   static const int kCantSetGroups;
+  static const int kCantSetEnv;
   static const int kCantExec;
 };
 
diff --git a/chrome_dev.conf b/chrome_dev.conf
new file mode 100644
index 0000000..7ffcb53
--- /dev/null
+++ b/chrome_dev.conf
@@ -0,0 +1,22 @@
+# This file may be modified to make local changes to the environment and command
+# line that session_manager uses to run Chrome. It contains one directive per
+# line, with the following forms available:
+#
+# --some-argument=/tmp/some file
+#   Adds "--some-argument=/tmp/some file" to Chrome's command line.
+#
+# !--a-prefix
+#   Removes all arguments beginning with "--a-prefix" from the command line.
+#
+# vmodule=foo=1
+#   Adds a "foo=1" pattern to the --vmodule argument.
+#
+# NAME=some value
+#   Sets the environment variable "NAME" to the value "some value".
+#
+# Directives are applied in the order they appear (i.e. to change a flag, first
+# delete it and then re-add it with the desired value).
+#
+# Note: Permanent changes to Chrome's configuration should be made in
+# session_manager (see chrome_setup.h).
+
diff --git a/chrome_setup.cc b/chrome_setup.cc
new file mode 100644
index 0000000..ea8d8d8
--- /dev/null
+++ b/chrome_setup.cc
@@ -0,0 +1,370 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/chrome_setup.h"
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/strings/stringprintf.h>
+
+#include "login_manager/chromium_command_builder.h"
+#include "login_manager/util.h"
+#include "login_manager/x_server_runner.h"
+
+namespace login_manager {
+
+namespace {
+
+// User, VT, and authority file used for running the X server.
+const char kXorgUser[] = "xorg";
+const int kXorgVt = 1;
+const char kXauthFile[] = "/var/run/chromelogin.auth";
+
+// Path to the Chrome binary.
+const char kChromeExecutable[] = "/opt/google/chrome/chrome";
+
+// Path to file containing developer-supplied modifications to Chrome's
+// environment and command line. Passed to
+// ChromiumCommandBuilder::ApplyUserConfig().
+const char kChromeDevConfigPath[] = "/etc/chrome_dev.conf";
+
+// Returns a base::FilePath corresponding to the DATA_DIR environment variable.
+base::FilePath GetDataDir(ChromiumCommandBuilder* builder) {
+  return base::FilePath(builder->ReadEnvVar("DATA_DIR"));
+}
+
+// Returns a base::FilePath corresponding to the subdirectory of DATA_DIR where
+// user data is stored.
+base::FilePath GetUserDir(ChromiumCommandBuilder* builder) {
+  return base::FilePath(GetDataDir(builder).Append("user"));
+}
+
+// Called by AddUiFlags() to take a wallpaper flag type ("default" or "guest"
+// and file type (e.g. "default", "oem", "guest") and add the corresponding
+// flags to |builder| if the files exist. Returns false if the files don't
+// exist.
+bool AddWallpaperFlags(ChromiumCommandBuilder* builder,
+                       const std::string& flag_type,
+                       const std::string& file_type) {
+  const base::FilePath large_path(base::StringPrintf(
+      "/usr/share/chromeos-assets/wallpaper/%s_large.jpg", file_type.c_str()));
+  const base::FilePath small_path(base::StringPrintf(
+      "/usr/share/chromeos-assets/wallpaper/%s_small.jpg", file_type.c_str()));
+  if (!base::PathExists(large_path) || !base::PathExists(small_path))
+    return false;
+
+  builder->AddArg(base::StringPrintf("--ash-%s-wallpaper-large=%s",
+      flag_type.c_str(), large_path.value().c_str()));
+  builder->AddArg(base::StringPrintf("--ash-%s-wallpaper-small=%s",
+      flag_type.c_str(), small_path.value().c_str()));
+  return true;
+}
+
+// Ensures that necessary directory exist with the correct permissions and sets
+// related arguments and environment variables.
+void CreateDirectories(ChromiumCommandBuilder* builder) {
+  const uid_t uid = builder->uid();
+  const gid_t gid = builder->gid();
+  const uid_t kRootUid = 0;
+  const gid_t kRootGid = 0;
+
+  const base::FilePath data_dir = GetDataDir(builder);
+  builder->AddArg("--user-data-dir=" + data_dir.value());
+
+  const base::FilePath user_dir = GetUserDir(builder);
+  CHECK(util::EnsureDirectoryExists(user_dir, uid, gid, 0755));
+  // TODO(keescook): Remove Chrome's use of $HOME.
+  builder->AddEnvVar("HOME", user_dir.value());
+
+  // Old builds will have a profile dir that's owned by root; newer ones won't
+  // have this directory at all.
+  CHECK(util::EnsureDirectoryExists(
+      data_dir.Append("Default"), uid, gid, 0755));
+
+  // TODO(cmasone,derat): Stop using this directory and delete this code.
+  const base::FilePath state_dir("/var/run/state");
+  CHECK(base::DeleteFile(state_dir, true));
+  CHECK(util::EnsureDirectoryExists(state_dir, kRootUid, kRootGid, 0710));
+
+  // Create a directory where the session manager can store a copy of the user
+  // policy key, that will be readable by the chrome process as chronos.
+  const base::FilePath policy_dir("/var/run/user_policy");
+  CHECK(base::DeleteFile(policy_dir, true));
+  CHECK(util::EnsureDirectoryExists(policy_dir, kRootUid, gid, 0710));
+
+  // Create a directory where the chrome process can store a reboot request so
+  // that it persists across browser crashes but is always removed on reboot.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/run/chrome"), uid, gid, 0700));
+
+  // Ensure the existence of the directory in which the whitelist and other
+  // ownership-related state will live. Yes, it should be owned by root. The
+  // permissions are set such that the chronos user can see the content of known
+  // files inside whitelist, but not anything else.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/lib/whitelist"), kRootUid, gid, 0710));
+
+  // Create the directory where external data referenced by policies is cached
+  // for device-local accounts. This data is read and written by chronos.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/cache/device_local_account_external_policy_data"),
+      uid, gid, 0700));
+
+  // Create the directory where the AppPack extensions are cached.
+  // These extensions are read and written by chronos.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/cache/app_pack"), uid, gid, 0700));
+
+  // Create the directory where extensions for device-local accounts are cached.
+  // These extensions are read and written by chronos.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/cache/device_local_account_extensions"),
+      uid, gid, 0700));
+
+  // Create the directory for shared installed extensions.
+  // Shared extensions are validated at runtime by the browser.
+  // These extensions are read and written by chronos.
+  CHECK(util::EnsureDirectoryExists(
+      base::FilePath("/var/cache/shared_extensions"), uid, gid, 0700));
+
+  // Enable us to keep track of the user's chosen time zone.
+  // Default to Pacific if we don't have one set.
+  const base::FilePath timezone_file("/var/lib/timezone/localtime");
+  if (!base::PathExists(timezone_file)) {
+    CHECK(util::EnsureDirectoryExists(timezone_file.DirName(), uid, gid, 0700));
+    CHECK(base::CreateSymbolicLink(
+        base::FilePath("/usr/share/zoneinfo/US/Pacific"), timezone_file));
+    CHECK(util::SetPermissions(timezone_file, uid, gid, 0644));
+  }
+
+  // Tell Chrome where to write logging messages before the user logs in.
+  base::FilePath system_log_dir("/var/log/chrome");
+  CHECK(util::EnsureDirectoryExists(system_log_dir, uid, gid, 0755));
+  builder->AddEnvVar("CHROME_LOG_FILE",
+      system_log_dir.Append("chrome").value());
+
+  // Log directory for the user session. Note that the user dir won't be mounted
+  // until later (when the cryptohome is mounted), so we don't create
+  // CHROMEOS_SESSION_LOG_DIR here.
+  builder->AddEnvVar("CHROMEOS_SESSION_LOG_DIR",
+      user_dir.Append("log").value());
+}
+
+// Creates crash-handling-related directories and adds related arguments.
+void InitCrashHandling(ChromiumCommandBuilder* builder) {
+  const base::FilePath user_dir = GetUserDir(builder);
+  const uid_t uid = builder->uid();
+  const gid_t gid = builder->gid();
+
+  // Force Chrome minidumps that are sent to the crash server to also be written
+  // locally. Chrome creates these files in
+  // ~/.config/google-chrome/Crash Reports/.
+  const base::FilePath stateful_etc("/mnt/stateful_partition/etc");
+  if (base::PathExists(stateful_etc.Append("enable_chromium_minidumps"))) {
+    builder->AddEnvVar("CHROME_HEADLESS", "1");
+    const base::FilePath reports_dir(
+        user_dir.Append(".config/google-chrome/Crash Reports"));
+    if (!base::PathExists(reports_dir)) {
+      base::FilePath minidump_dir("/var/minidumps");
+      util::EnsureDirectoryExists(minidump_dir, uid, gid, 0700);
+      util::EnsureDirectoryExists(reports_dir.DirName(), uid, gid, 0700);
+      base::CreateSymbolicLink(minidump_dir, reports_dir);
+    }
+  }
+
+  // Enable gathering of core dumps via a file in the stateful partition so it
+  // can be enabled post-build.
+  if (base::PathExists(stateful_etc.Append("enable_chromium_coredumps"))) {
+    util::EnsureDirectoryExists(
+        base::FilePath("/var/coredumps"), uid, gid, 0700);
+    struct rlimit limit;
+    limit.rlim_cur = limit.rlim_max = RLIM_INFINITY;
+    if (setrlimit(RLIMIT_CORE, &limit) != 0)
+      PLOG(ERROR) << "Setting unlimited coredumps with setrlimit() failed";
+    const std::string kPattern("/var/coredumps/core.%e.%p");
+    base::WriteFile(base::FilePath("/proc/sys/kernel/core_pattern"),
+                    kPattern.c_str(), kPattern.size());
+  }
+}
+
+// Adds system-related flags to the command line.
+void AddSystemFlags(ChromiumCommandBuilder* builder) {
+  const base::FilePath data_dir = GetDataDir(builder);
+
+  // We need to delete these files as Chrome may have left them around from its
+  // prior run (if it crashed).
+  base::DeleteFile(data_dir.Append("SingletonLock"), false);
+  base::DeleteFile(data_dir.Append("SingletonSocket"), false);
+
+  builder->AddArg("--max-unused-resource-memory-usage-percentage=5");
+
+  // Exporting this environment variable turns on a useful diagnostic feature in
+  // Chrome/NSS, which can allow users to decrypt their own SSL traffic later
+  // with e.g. Wireshark. We key this off of both a USE flag stored on rootfs
+  // (which, essentially, locks this feature off for normal systems), and, the
+  // logfile itself (which makes this feature easy to toggle on/off, on systems
+  // like mod-for-test images, where the USE flag has been customized to permit
+  // its use).
+  const base::FilePath ssl_key_log_file("/var/log/sslkeys.log");
+  if (builder->UseFlagIsSet("dangerous_sslkeylogfile") &&
+      base::PathExists(ssl_key_log_file))
+    builder->AddEnvVar("SSLKEYLOGFILE", ssl_key_log_file.value());
+
+  // On developer systems, set a flag to let the browser know.
+  if (builder->is_developer_end_user())
+    builder->AddArg("--system-developer-mode");
+}
+
+// Adds UI-related flags to the command line.
+void AddUiFlags(ChromiumCommandBuilder* builder, bool using_x11) {
+  const base::FilePath data_dir = GetDataDir(builder);
+
+  // Force OOBE on test images that have requested it.
+  if (base::PathExists(base::FilePath("/root/.test_repeat_oobe"))) {
+    base::DeleteFile(data_dir.Append(".oobe_completed"), false);
+    base::DeleteFile(data_dir.Append("Local State"), false);
+  }
+
+  if (using_x11) {
+    const base::FilePath user_xauth_file(data_dir.Append(".Xauthority"));
+    PCHECK(base::CopyFile(base::FilePath(kXauthFile), user_xauth_file))
+        << "Unable to copy " << kXauthFile << " to " << user_xauth_file.value();
+    CHECK(util::SetPermissions(
+        user_xauth_file, builder->uid(), builder->gid(), 0600));
+    builder->AddEnvVar("XAUTHORITY", user_xauth_file.value());
+    builder->AddEnvVar("DISPLAY", ":0.0");
+  }
+
+  builder->AddArg("--login-manager");
+  builder->AddArg("--login-profile=user");
+
+  if (builder->UseFlagIsSet("natural_scroll_default"))
+    builder->AddArg("--enable-natural-scroll-default");
+  if (!builder->UseFlagIsSet("legacy_keyboard"))
+    builder->AddArg("--has-chromeos-keyboard");
+  if (builder->UseFlagIsSet("has_diamond_key"))
+    builder->AddArg("--has-chromeos-diamond-key");
+
+  if (builder->UseFlagIsSet("legacy_power_button"))
+    builder->AddArg("--aura-legacy-power-button");
+
+  if (builder->UseFlagIsSet("disable_login_animations")) {
+    builder->AddArg("--disable-login-animations");
+    builder->AddArg("--disable-boot-animation");
+    builder->AddArg("--ash-copy-host-background-at-boot");
+  } else if (builder->UseFlagIsSet("fade_boot_splash_screen")) {
+    builder->AddArg("--ash-animate-from-boot-splash-screen");
+  }
+
+  if (AddWallpaperFlags(builder, "default", "oem"))
+    builder->AddArg("--ash-default-wallpaper-is-oem");
+  else
+    AddWallpaperFlags(builder, "default", "default");
+  AddWallpaperFlags(builder, "guest", "guest");
+
+  // TODO(yongjaek): Remove the following flag when the kiosk mode app is ready
+  // at crbug.com/309806.
+  if (builder->UseFlagIsSet("moblab"))
+    builder->AddArg("--disable-demo-mode");
+}
+
+// Adds enterprise-related flags to the command line.
+void AddEnterpriseFlags(ChromiumCommandBuilder* builder) {
+  // Device Manager Server used to fetch the enterprise policy, if applicable.
+  builder->AddArg(
+      "--device-management-url=https://m.google.com/devicemanagement/data/api");
+
+  builder->AddArg("--enterprise-enable-forced-re-enrollment");
+  builder->AddArg("--enterprise-enrollment-initial-modulus=9");
+  builder->AddArg("--enterprise-enrollment-modulus-limit=13");
+}
+
+// Adds patterns to the --vmodule flag.
+void AddVmodulePatterns(ChromiumCommandBuilder* builder) {
+  // There has been a steady supply of bug reports about screen locking. These
+  // messages are useful for determining what happened within feedback reports.
+  builder->AddVmodulePattern("screen_locker=1");
+  builder->AddVmodulePattern("webui_screen_locker=1");
+
+  // TODO(ygorshenin): Remove this once we will have logs from places where
+  // shill was tested (crosbug.com/36622).
+  builder->AddVmodulePattern("network_portal_detector_impl=1");
+
+  // Turn on logging about external displays being connected and disconnected.
+  // Different behavior is seen from different displays and these messages are
+  // used to determine what happened within feedback reports.
+  builder->AddVmodulePattern("*ui/display/chromeos*=1");
+  builder->AddVmodulePattern("*ash/display*=1");
+
+  // Turn on plugin loading failure logging for crbug.com/314301.
+  builder->AddVmodulePattern("*zygote*=1");
+  builder->AddVmodulePattern("*plugin*=2");
+}
+
+}  // namespace
+
+void PerformChromeSetup(std::map<std::string, std::string>* env_vars_out,
+                        std::vector<std::string>* chrome_command_out,
+                        uid_t* chrome_uid_out) {
+  DCHECK(env_vars_out);
+  DCHECK(chrome_command_out);
+  DCHECK(chrome_uid_out);
+
+  ChromiumCommandBuilder builder;
+  CHECK(builder.Init());
+
+  // Start X in the background before doing more-expensive setup.
+  scoped_ptr<XServerRunner> x_runner;
+  const bool using_x11 = builder.UseFlagIsSet("X");
+  if (using_x11) {
+    x_runner.reset(new XServerRunner);
+    CHECK(x_runner->StartServer(
+        kXorgUser, kXorgVt, builder.is_developer_end_user(),
+        base::FilePath(kXauthFile)));
+  }
+
+  builder.SetUpChromium();
+
+  // Please add new code to the most-appropriate helper function instead of
+  // putting it here. Things that to all Chromium-derived binaries (e.g.
+  // app_shell, content_shell, etc.) rather than just to Chrome belong in the
+  // ChromiumCommandBuilder class instead.
+  CreateDirectories(&builder);
+  InitCrashHandling(&builder);
+  AddSystemFlags(&builder);
+  AddUiFlags(&builder, using_x11);
+  AddEnterpriseFlags(&builder);
+  AddVmodulePatterns(&builder);
+
+  // Apply any modifications requested by the developer.
+  if (builder.is_developer_end_user())
+    builder.ApplyUserConfig(base::FilePath(kChromeDevConfigPath));
+
+  chrome_command_out->clear();
+  chrome_command_out->reserve(builder.arguments().size() + 1);
+  chrome_command_out->push_back(kChromeExecutable);
+  chrome_command_out->insert(chrome_command_out->end(),
+                             builder.arguments().begin(),
+                             builder.arguments().end());
+  *env_vars_out = builder.environment_variables();
+  *chrome_uid_out = builder.uid();
+
+  if (using_x11) {
+    CHECK(x_runner->WaitForServer());
+    // TODO: Emit this D-Bus signal directly.
+    ChromiumCommandBuilder::Run(true, "initctl", "emit", "x-started", NULL);
+    // TODO: Link against libbootstat to log this.
+    ChromiumCommandBuilder::Run(true, "bootstat", "x-started", NULL);
+  }
+  // Do not add code here. Potentially-expensive work should be done between
+  // StartServer() and WaitForServer().
+}
+
+}  // namespace login_manager
diff --git a/chrome_setup.h b/chrome_setup.h
new file mode 100644
index 0000000..c9f409a
--- /dev/null
+++ b/chrome_setup.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium OS 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 LOGIN_MANAGER_CHROME_SETUP_H_
+#define LOGIN_MANAGER_CHROME_SETUP_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+class ChromiumCommandBuilder;
+class XServerRunner;
+
+namespace login_manager {
+
+// Initializes a ChromiumCommandBuilder and performs additional Chrome-specific
+// setup. If X is being used, it also starts the X server. Returns environment
+// variables that the caller should export for Chrome and arguments that it
+// should pass to the Chrome binary, along with the UID that should be used to
+// run Chrome.
+//
+// Initialization that is common across all Chromium-derived binaries (e.g.
+// content_shell, app_shell, etc.) rather than just applying to the Chrome
+// browser should be added to the ChromiumCommandBuilder class instead.
+void PerformChromeSetup(std::map<std::string, std::string>* env_vars_out,
+                        std::vector<std::string>* chrome_command_out,
+                        uid_t* chrome_uid_out);
+
+}  // namespace login_manager
+
+#endif  // LOGIN_MANAGER_CHROME_SETUP_H_
diff --git a/chromium_command_builder.cc b/chromium_command_builder.cc
new file mode 100644
index 0000000..9a16f4e
--- /dev/null
+++ b/chromium_command_builder.cc
@@ -0,0 +1,477 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/chromium_command_builder.h"
+
+#include <sys/resource.h>
+
+#include <cstdarg>
+#include <ctime>
+
+#include <base/command_line.h>
+#include <base/file_util.h>
+#include <base/files/file_enumerator.h>
+#include <base/logging.h>
+#include <base/process/launch.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+
+#include "login_manager/util.h"
+
+namespace {
+
+// User that will run the executable.
+const char kUser[] = "chronos";
+
+// Prefix for the USE flag containing the name of the board.
+const char kBoardUseFlagPrefix[] = "board_use_";
+
+// Location where GPU debug information is bind-mounted.
+const char kDebugfsGpuPath[] = "/var/run/debugfs_gpu";
+
+// Returns the value associated with |key| in |pairs| or an empty string if the
+// key isn't present. If the value is encapsulated in single or double quotes,
+// they are removed.
+std::string LookUpInStringPairs(const base::StringPairs& pairs,
+                                const std::string& key) {
+  for (size_t i = 0; i < pairs.size(); ++i) {
+    if (key != pairs[i].first)
+      continue;
+
+    // Strip quotes.
+    // TODO(derat): Remove quotes from Pepper .info files after
+    // session_manager_setup.sh is no longer interpreting them as shell scripts.
+    std::string value = pairs[i].second;
+    if (value.size() >= 2U &&
+        ((value[0] == '"' && value[value.size()-1] == '"') ||
+         (value[0] == '\'' && value[value.size()-1] == '\'')))
+      value = value.substr(1, value.size() - 2);
+
+    return value;
+  }
+  return std::string();
+}
+
+// Returns true if |name| matches /^[A-Z][_A-Z0-9]+$/.
+bool IsEnvironmentVariableName(const std::string& name) {
+  if (name.empty() || !(name[0] >= 'A' && name[0] <= 'Z'))
+    return false;
+  for (size_t i = 1; i < name.size(); ++i) {
+    char ch = name[i];
+    if (ch != '_' && !(ch >= '0' && ch <= '9') && !(ch >= 'A' && ch <= 'Z'))
+      return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+// TODO(derat): Rename this file to not be session-manager-specific.
+const char ChromiumCommandBuilder::kUseFlagsPath[] =
+    "/etc/session_manager_use_flags.txt";
+const char ChromiumCommandBuilder::kLsbReleasePath[] = "/etc/lsb-release";
+const char ChromiumCommandBuilder::kPepperPluginsPath[] =
+    "/opt/google/chrome/pepper";
+const char ChromiumCommandBuilder::kDeepMemoryProfilerPrefixPath[] =
+    "/var/tmp/deep_memory_profiler_prefix.txt";
+const char ChromiumCommandBuilder::kDeepMemoryProfilerTimeIntervalPath[] =
+    "/var/tmp/deep_memory_profiler_time_interval.txt";
+
+// static
+bool ChromiumCommandBuilder::Run(bool log_failure,
+                                 const char* command,
+                                 const char* arg, ...) {
+  // Extra parentheses because yay C++ most vexing parse.
+  base::CommandLine cl((base::FilePath(command)));
+  va_list list;
+  va_start(list, arg);
+  while (arg) {
+    cl.AppendArg(const_cast<char*>(arg));
+    arg = va_arg(list, char*);
+  }
+  va_end(list);
+
+  std::string output;
+  int exit_code = 0;
+  if (!base::GetAppOutputWithExitCode(cl, &output, &exit_code)) {
+    if (log_failure) {
+      LOG(WARNING) << "\"" << cl.GetCommandLineString() << "\" failed with "
+                   << exit_code << ": " << output;
+    }
+    return false;
+  }
+
+  return true;
+}
+
+ChromiumCommandBuilder::ChromiumCommandBuilder()
+    : uid_(0),
+      gid_(0),
+      is_chrome_os_hardware_(false),
+      is_developer_end_user_(false),
+      vmodule_argument_index_(-1) {
+}
+
+ChromiumCommandBuilder::~ChromiumCommandBuilder() {}
+
+bool ChromiumCommandBuilder::Init() {
+  if (!util::GetUserInfo(kUser, &uid_, &gid_))
+    return false;
+
+  // Read the list of USE flags that were set at build time.
+  std::string data;
+  if (!base::ReadFileToString(GetPath(kUseFlagsPath), &data)) {
+    PLOG(ERROR) << "Unable to read " << kUseFlagsPath;
+    return false;
+  }
+  StringVector flags;
+  base::SplitStringAlongWhitespace(data, &flags);
+  for (size_t i = 0; i < flags.size(); ++i)
+    use_flags_.insert(flags[i]);
+
+  base::CommandLine cl(base::FilePath("crossystem"));
+  cl.AppendArg("mainfw_type");
+  std::string output;
+  if (base::GetAppOutput(cl, &output)) {
+    base::TrimWhitespace(output, base::TRIM_TRAILING, &output);
+    is_chrome_os_hardware_ = (output != "nonchrome");
+  }
+
+  is_developer_end_user_ = base::GetAppOutput(
+      base::CommandLine(base::FilePath("is_developer_end_user")), &output);
+
+  return true;
+}
+
+bool ChromiumCommandBuilder::SetUpChromium() {
+  AddEnvVar("USER", kUser);
+  AddEnvVar("LOGNAME", kUser);
+  AddEnvVar("SHELL", "/bin/sh");
+  AddEnvVar("PATH", "/bin:/usr/bin");
+  AddEnvVar("LC_ALL", "en_US.utf8");
+
+  const base::FilePath data_dir(GetPath("/home").Append(kUser));
+  AddEnvVar("DATA_DIR", data_dir.value());
+  if (!util::EnsureDirectoryExists(data_dir, uid_, gid_, 0755))
+    return false;
+
+  // Provide /etc/lsb-release contents and timestamp so that they are available
+  // to Chrome immediately without requiring a blocking file read.
+  const base::FilePath lsb_path(GetPath(kLsbReleasePath));
+  std::string lsb_data;
+  base::File::Info info;
+  if (!base::ReadFileToString(lsb_path, &lsb_data) ||
+      !base::GetFileInfo(lsb_path, &info)) {
+    LOG(ERROR) << "Unable to read or stat " << kLsbReleasePath;
+    return false;
+  }
+  AddEnvVar("LSB_RELEASE", lsb_data);
+  AddEnvVar("LSB_RELEASE_TIME",
+            base::IntToString(info.creation_time.ToTimeT()));
+
+  // By default, libdbus treats all warnings as fatal errors. That's too strict.
+  AddEnvVar("DBUS_FATAL_WARNINGS", "0");
+
+  // Prevent Flash asserts from crashing the plugin process.
+  AddEnvVar("DONT_CRASH_ON_ASSERT", "1");
+
+  // Increase maximum file descriptors to 2048 (default is otherwise 1024).
+  // Some offline websites using IndexedDB are particularly hungry for
+  // descriptors, so the default is insufficient. See crbug.com/251385.
+  struct rlimit limit;
+  limit.rlim_cur = limit.rlim_max = 2048;
+  if (setrlimit(RLIMIT_NOFILE, &limit) < 0)
+    PLOG(ERROR) << "Setting max FDs with setrlimit() failed";
+
+  // Disable sandboxing as it causes crashes in ASAN: crbug.com/127536
+  bool disable_sandbox = false;
+  disable_sandbox |= SetUpASAN();
+  disable_sandbox |= SetUpDeepMemoryProfiler();
+  if (disable_sandbox)
+    AddArg("--no-sandbox");
+
+  SetUpPepperPlugins();
+  AddUiFlags();
+
+  AddArg("--enable-logging");
+  AddArg("--log-level=1");
+  AddArg("--use-cras");
+
+  return true;
+}
+
+bool ChromiumCommandBuilder::ApplyUserConfig(const base::FilePath& path) {
+  std::string data;
+  if (!base::ReadFileToString(path, &data)) {
+    PLOG(WARNING) << "Unable to read " << path.value();
+    return false;
+  }
+
+  std::vector<std::string> lines;
+  base::SplitString(data, '\n', &lines);
+
+  for (size_t i = 0; i < lines.size(); ++i) {
+    std::string line;
+    base::TrimWhitespace(lines[i], base::TRIM_ALL, &line);
+    if (line.empty() || line[0] == '#')
+      continue;
+
+    if (line[0] == '!' && line.size() > 1) {
+      const std::string pattern = line.substr(1, line.size() - 1);
+      size_t num_copied = 0;
+      for (size_t src_index = 0; src_index < arguments_.size(); ++src_index) {
+        if (arguments_[src_index].find(pattern) == 0) {
+          // Drop the argument and shift |vmodule_argument_index_| forward if
+          // the argument precedes it (or reset the index if the --vmodule flag
+          // itself is being deleted).
+          if (vmodule_argument_index_ > static_cast<int>(src_index))
+            vmodule_argument_index_--;
+          else if (vmodule_argument_index_ == static_cast<int>(src_index))
+            vmodule_argument_index_ = -1;
+        } else {
+          arguments_[num_copied] = arguments_[src_index];
+          num_copied++;
+        }
+      }
+      arguments_.resize(num_copied);
+    } else {
+      base::StringPairs pairs;
+      base::SplitStringIntoKeyValuePairs(line, '=', '\n', &pairs);
+      if (pairs.size() == 1U && pairs[0].first == "vmodule")
+        AddVmodulePattern(pairs[0].second);
+      else if (pairs.size() == 1U && IsEnvironmentVariableName(pairs[0].first))
+        AddEnvVar(pairs[0].first, pairs[0].second);
+      else
+        AddArg(line);
+    }
+  }
+
+  return true;
+}
+
+bool ChromiumCommandBuilder::UseFlagIsSet(const std::string& flag) const {
+  return use_flags_.count(flag) > 0;
+}
+
+bool ChromiumCommandBuilder::IsBoard(const std::string& board) const {
+  return UseFlagIsSet(kBoardUseFlagPrefix + board);
+}
+
+void ChromiumCommandBuilder::AddEnvVar(const std::string& name,
+                                       const std::string& value) {
+  environment_variables_[name] = value;
+}
+
+std::string ChromiumCommandBuilder::ReadEnvVar(const std::string& name) const {
+  StringMap::const_iterator it = environment_variables_.find(name);
+  CHECK(it != environment_variables_.end()) << name << " hasn't been set";
+  return it->second;
+}
+
+void ChromiumCommandBuilder::AddArg(const std::string& arg) {
+  arguments_.push_back(arg);
+}
+
+void ChromiumCommandBuilder::AddVmodulePattern(const std::string& pattern) {
+  if (pattern.empty())
+    return;
+
+  if (vmodule_argument_index_ < 0) {
+    AddArg("--vmodule=" + pattern);
+    vmodule_argument_index_ = arguments_.size() - 1;
+  } else {
+    arguments_[vmodule_argument_index_] += "," + pattern;
+  }
+}
+
+base::FilePath ChromiumCommandBuilder::GetPath(const std::string& path) const {
+  return util::GetReparentedPath(path, base_path_for_testing_);
+}
+
+bool ChromiumCommandBuilder::SetUpASAN() {
+  if (!UseFlagIsSet("asan"))
+    return false;
+
+  // Make glib use system malloc.
+  AddEnvVar("G_SLICE", "always-malloc");
+
+  // Make nss use system malloc.
+  AddEnvVar("NSS_DISABLE_ARENA_FREE_LIST", "1");
+
+  // Make nss skip dlclosing dynamically loaded modules, which would result in
+  // "obj:*" in backtraces.
+  AddEnvVar("NSS_DISABLE_UNLOAD", "1");
+
+  // Make ASAN output to the file because Chrome stderr is /dev/null now
+  // (crbug.com/156308).
+  // TODO(derat): It's weird that this lives in a Chrome directory that's
+  // created by ChromeInitializer; move it somewhere else, maybe.
+  AddEnvVar("ASAN_OPTIONS", "log_path=/var/log/chrome/asan_log");
+
+  return true;
+}
+
+bool ChromiumCommandBuilder::SetUpDeepMemoryProfiler() {
+  if (!UseFlagIsSet("deep_memory_profiler"))
+    return false;
+
+  // Dump heap profiles to /tmp/dmprof.*.
+  std::string prefix;
+  if (!base::ReadFileToString(
+          GetPath(kDeepMemoryProfilerPrefixPath), &prefix)) {
+    return false;
+  }
+  base::TrimWhitespaceASCII(prefix, base::TRIM_TRAILING, &prefix);
+  AddEnvVar("HEAPPROFILE", prefix);
+
+  // Dump every |interval| seconds.
+  std::string interval;
+  base::ReadFileToString(
+      GetPath(kDeepMemoryProfilerTimeIntervalPath), &interval);
+  base::TrimWhitespaceASCII(interval, base::TRIM_TRAILING, &interval);
+  AddEnvVar("HEAP_PROFILE_TIME_INTERVAL", interval);
+
+  // Turn on profiling mmap.
+  AddEnvVar("HEAP_PROFILE_MMAP", "1");
+
+  // Turn on Deep Memory Profiler.
+  AddEnvVar("DEEP_HEAP_PROFILE", "1");
+
+  return true;
+}
+
+void ChromiumCommandBuilder::SetUpPepperPlugins() {
+  std::vector<std::string> register_plugins;
+
+  base::FileEnumerator enumerator(GetPath(kPepperPluginsPath),
+      false /* recursive */, base::FileEnumerator::FILES);
+  while (true) {
+    const base::FilePath path = enumerator.Next();
+    if (path.empty())
+      break;
+
+    if (path.Extension() != ".info")
+      continue;
+
+    std::string data;
+    if (!base::ReadFileToString(path, &data)) {
+      PLOG(ERROR) << "Unable to read " << path.value();
+      continue;
+    }
+
+    // .info files are full of shell junk like #-prefixed comments, so don't
+    // check that SplitStringIntoKeyValuePairs() successfully parses every line.
+    base::StringPairs pairs;
+    base::SplitStringIntoKeyValuePairs(data, '=', '\n', &pairs);
+
+    const std::string file_name = LookUpInStringPairs(pairs, "FILE_NAME");
+    const std::string plugin_name = LookUpInStringPairs(pairs, "PLUGIN_NAME");
+    const std::string version = LookUpInStringPairs(pairs, "VERSION");
+
+    if (file_name.empty()) {
+      LOG(ERROR) << "Missing FILE_NAME in " << path.value();
+      continue;
+    }
+
+    if (plugin_name == "Shockwave Flash") {
+      AddArg("--ppapi-flash-path=" + file_name);
+      AddArg("--ppapi-flash-version=" + version);
+
+      // TODO(ihf): Remove once crbug.com/237380 and crbug.com/276738 are fixed.
+      const bool is_atom = IsBoard("x86-alex") || IsBoard("x86-alex_he") ||
+          IsBoard("x86-mario") || IsBoard("x86-zgb") || IsBoard("x86-zgb_he");
+      AddArg("--ppapi-flash-args=enable_hw_video_decode=" +
+             std::string(is_atom ? "0" : "1"));
+    } else {
+      const std::string description = LookUpInStringPairs(pairs, "DESCRIPTION");
+      const std::string mime_types = LookUpInStringPairs(pairs, "MIME_TYPES");
+
+      std::string plugin_string = file_name;
+      if (!plugin_name.empty()) {
+        plugin_string += "#" + plugin_name;
+        if (!description.empty()) {
+          plugin_string += "#" + description;
+          if (!version.empty()) {
+            plugin_string += "#" + version;
+          }
+        }
+      }
+      plugin_string += ";" + mime_types;
+      register_plugins.push_back(plugin_string);
+    }
+  }
+
+  if (!register_plugins.empty()) {
+    std::sort(register_plugins.begin(), register_plugins.end());
+    AddArg("--register-pepper-plugins=" + JoinString(register_plugins, ","));
+  }
+}
+
+void ChromiumCommandBuilder::AddUiFlags() {
+  AddArg("--enable-fixed-position-compositing");
+  AddArg("--enable-impl-side-painting");
+  AddArg("--max-tiles-for-interest-area=512");
+  AddArg("--ui-enable-per-tile-painting");
+  AddArg("--ui-prioritize-in-gpu-process");
+
+  if (UseFlagIsSet("egl"))
+    AddArg("--use-gl=egl");
+
+  // On boards with ARM NEON support, force libvpx to use the NEON-optimized
+  // code paths. Remove once http://crbug.com/161834 is fixed.
+  // This is needed because libvpx cannot check cpuinfo within the sandbox.
+  if (UseFlagIsSet("exynos"))
+    AddEnvVar("VPX_SIMD_CAPS", "0xf");
+
+  if (UseFlagIsSet("highdpi")) {
+    AddArg("--enable-webkit-text-subpixel-positioning");
+    AddArg("--enable-accelerated-overflow-scroll");
+    AddArg("--default-tile-width=512");
+    AddArg("--default-tile-height=512");
+  }
+
+  if (IsBoard("link"))
+    AddArg("--touch-calibration=0,0,0,50");
+
+  AddArg(std::string("--gpu-sandbox-failures-fatal=") +
+      (is_chrome_os_hardware() ? "yes" : "no"));
+
+  if (UseFlagIsSet("gpu_sandbox_allow_sysv_shm"))
+    AddArg("--gpu-sandbox-allow-sysv-shm");
+
+  if (UseFlagIsSet("gpu_sandbox_start_after_initialization"))
+    AddArg("--gpu-sandbox-start-after-initialization");
+
+  if (IsBoard("peach_pit") || IsBoard("peach_pi") || IsBoard("nyan") ||
+      IsBoard("nyan_big") || IsBoard("nyan_blaze"))
+    AddArg("--enable-webrtc-hw-vp8-encoding");
+
+  if (IsBoard("peach_pi"))
+    AddArg("--ignore-resolution-limits-for-accelerated-video-decode");
+
+  // Ozone platform configuration.
+  if (UseFlagIsSet("ozone_platform_dri")) {
+    // TODO(spang): Use freon/chromeos platform, not DRI example platform.
+    AddArg("--ozone-platform=dri");
+
+    // TODO(spang): Fix hardware acceleration.
+    AddArg("--disable-gpu");
+    AddArg("--ui-disable-threaded-compositing");
+  }
+
+  // Allow Chrome to access GPU memory information despite /sys/kernel/debug
+  // being owned by debugd. This limits the security attack surface versus
+  // leaving the whole debug directory world-readable: http://crbug.com/175828
+  // (Only do this if we're running as root, i.e. not in a test.)
+  const base::FilePath debugfs_gpu_path(GetPath(kDebugfsGpuPath));
+  if (getuid() == 0 && !base::DirectoryExists(debugfs_gpu_path)) {
+    if (base::CreateDirectory(debugfs_gpu_path)) {
+      Run(true, "mount", "-o", "bind", "/sys/kernel/debug/dri/0",
+          kDebugfsGpuPath, NULL);
+    } else {
+      PLOG(ERROR) << "Unable to create " << kDebugfsGpuPath;
+    }
+  }
+}
diff --git a/chromium_command_builder.h b/chromium_command_builder.h
new file mode 100644
index 0000000..164a1bc
--- /dev/null
+++ b/chromium_command_builder.h
@@ -0,0 +1,159 @@
+// Copyright 2014 The Chromium OS 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 LOGIN_MANAGER_CHROMIUM_COMMAND_BUILDER_H_
+#define LOGIN_MANAGER_CHROMIUM_COMMAND_BUILDER_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+// ChromiumCommandBuilder facilitates building a command line for running a
+// Chromium-derived binary and performing related setup.
+class ChromiumCommandBuilder {
+ public:
+  typedef std::map<std::string, std::string> StringMap;
+  typedef std::vector<std::string> StringVector;
+
+  // Location of the file containing newline-separated USE flags that were set
+  // when the system was built.
+  static const char kUseFlagsPath[];
+
+  // Location of the file containing .info files describing Pepper plugins.
+  static const char kPepperPluginsPath[];
+
+  // Location of the lsb-release file describing the system image.
+  static const char kLsbReleasePath[];
+
+  // Deep-memory-profiler-related files.
+  static const char kDeepMemoryProfilerPrefixPath[];
+  static const char kDeepMemoryProfilerTimeIntervalPath[];
+
+  // Runs the passed-in command and arguments synchronously, returning true on
+  // success. On failure, the command's output is logged if |log_failure| is
+  // true. The path will be searched for |command|.
+  static bool Run(bool log_failure, const char* command, const char* arg, ...);
+
+  ChromiumCommandBuilder();
+  ~ChromiumCommandBuilder();
+
+  uid_t uid() const { return uid_; }
+  gid_t gid() const { return gid_; }
+  bool is_chrome_os_hardware() const { return is_chrome_os_hardware_; }
+  bool is_developer_end_user() const { return is_developer_end_user_; }
+  const StringMap& environment_variables() const {
+    return environment_variables_;
+  }
+  const StringVector& arguments() const { return arguments_; }
+
+  void set_base_path_for_testing(const base::FilePath& path) {
+    base_path_for_testing_ = path;
+  }
+
+  // Performs just the basic initialization needed before UseFlagIsSet() and
+  // IsBoard() can be used. Returns true on success.
+  bool Init();
+
+  // Determines the environment variables and arguments that should be set for
+  // all Chromium-derived binaries and updates |environment_variables_| and
+  // |arguments_| accordingly. Also creates necessary directories, sets resource
+  // limits, etc. Returns true on success.
+  bool SetUpChromium();
+
+  // Reads a user-supplied file requesting modifications to the current set of
+  // arguments. The following directives are supported:
+  //
+  //   # This is a comment.
+  //     Lines beginning with '#' are skipped.
+  //
+  //   --some-flag=some-value
+  //     Calls AddArg("--some-flag=some-value").
+  //
+  //   !--flag-prefix
+  //     Remove all arguments beginning with "--flag-prefix".
+  //
+  //   NAME=VALUE
+  //     Calls AddEnvVar("NAME", "VALUE").
+  //
+  // Returns true on success.
+  bool ApplyUserConfig(const base::FilePath& path);
+
+  // Returns true if a USE flag named |flag| was set when the system image was
+  // built.
+  bool UseFlagIsSet(const std::string& flag) const;
+
+  // Returns true if the system image was compiled for |board|.
+  bool IsBoard(const std::string& board) const;
+
+  // Adds an environment variable to |environment_variables_|. Note that this
+  // method does not call setenv(); it is the caller's responsibility to
+  // actually export the variables.
+  void AddEnvVar(const std::string& name, const std::string& value);
+
+  // Returns the value of an environment variable previously added via
+  // AddEnvVar(). Crashes if the variable isn't set. Note that this method does
+  // not call getenv().
+  std::string ReadEnvVar(const std::string& name) const;
+
+  // Adds a command-line argument.
+  void AddArg(const std::string& arg);
+
+  // Adds |pattern| to the --vmodule flag in |arguments_|.
+  void AddVmodulePattern(const std::string& pattern);
+
+ private:
+  // Converts absolute path |path| into a base::FilePath, rooting it under
+  // |base_path_for_testing_| if it's non-empty.
+  base::FilePath GetPath(const std::string& path) const;
+
+  // Checks if an ASAN or deep-memory-profiler build was requested, doing
+  // appropriate initialization and returning true if so. Called by
+  // InitSharedConfig().
+  bool SetUpASAN();
+  bool SetUpDeepMemoryProfiler();
+
+  // Reads .info files in |pepper_plugins_path_| and adds the appropriate
+  // arguments to |arguments_|. Called by InitSharedConfig().
+  void SetUpPepperPlugins();
+
+  // Add UI- and compositing-related flags to |arguments_|.
+  void AddUiFlags();
+
+  // Path under which files are created when running in a test.
+  base::FilePath base_path_for_testing_;
+
+  // UID and GID of the user used to run the binary.
+  uid_t uid_;
+  gid_t gid_;
+
+  // USE flags that were set when the system was built.
+  std::set<std::string> use_flags_;
+
+  // True if official Chrome OS hardware is being used.
+  bool is_chrome_os_hardware_;
+
+  // True if this is a developer system, per the is_developer_end_user command.
+  bool is_developer_end_user_;
+
+  // Environment variables that the caller should export before starting the
+  // executable.
+  StringMap environment_variables_;
+
+  // Command-line arguments that the caller should pass to the executable.
+  StringVector arguments_;
+
+  // Index in |arguments_| of the --vmodule flag. -1 if the flag hasn't been
+  // set.
+  int vmodule_argument_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromiumCommandBuilder);
+};
+
+#endif  // LOGIN_MANAGER_CHROMIUM_COMMAND_BUILDER_H_
diff --git a/chromium_command_builder_unittest.cc b/chromium_command_builder_unittest.cc
new file mode 100644
index 0000000..fd6ff70
--- /dev/null
+++ b/chromium_command_builder_unittest.cc
@@ -0,0 +1,280 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/chromium_command_builder.h"
+
+#include <base/files/file_path.h>
+#include <base/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/macros.h>
+#include <gtest/gtest.h>
+
+#include "login_manager/util.h"
+
+class ChromiumCommandBuilderTest : public testing::Test {
+ public:
+  ChromiumCommandBuilderTest()
+      : write_use_flags_file_(true),
+        write_lsb_release_file_(true) {
+    CHECK(temp_dir_.CreateUniqueTempDir());
+    base_path_ = temp_dir_.path();
+    builder_.set_base_path_for_testing(base_path_);
+
+    pepper_dir_ = util::GetReparentedPath(
+        ChromiumCommandBuilder::kPepperPluginsPath, base_path_);
+    PCHECK(base::CreateDirectory(pepper_dir_));
+  }
+  virtual ~ChromiumCommandBuilderTest() {}
+
+
+  // Does testing-related initialization and returns the result of |builder_|'s
+  // Init() method.
+  bool Init() {
+    if (write_use_flags_file_) {
+      WriteFileUnderBasePath(ChromiumCommandBuilder::kUseFlagsPath,
+                             use_flags_data_);
+    }
+    if (write_lsb_release_file_) {
+      WriteFileUnderBasePath(ChromiumCommandBuilder::kLsbReleasePath,
+                             lsb_release_data_);
+    }
+    return builder_.Init();
+  }
+
+  // Writes |data| to |path| underneath |base_path_|.
+  void WriteFileUnderBasePath(const std::string& path,
+                              const std::string& data) {
+    base::FilePath reparented_path(util::GetReparentedPath(path, base_path_));
+    if (!base::DirectoryExists(reparented_path.DirName()))
+      PCHECK(base::CreateDirectory(reparented_path.DirName()));
+    PCHECK(base::WriteFile(reparented_path, data.data(), data.size()) ==
+           static_cast<int>(data.size()));
+  }
+
+  // Looks up |name| in |builder_|'s list of environment variables, returning
+  // its value if present or an empty string otherwise.
+  std::string ReadEnvVar(const std::string& name) {
+    const ChromiumCommandBuilder::StringMap& vars =
+        builder_.environment_variables();
+    ChromiumCommandBuilder::StringMap::const_iterator it = vars.find(name);
+    return it != vars.end() ? it->second : std::string();
+  }
+
+  // Returns the first argument in |builder_| that starts with |prefix|, or an
+  // empty string if no matching argument is found.
+  std::string GetFirstArgWithPrefix(const std::string& prefix) {
+    const ChromiumCommandBuilder::StringVector& args = builder_.arguments();
+    for (size_t i = 0; i < args.size(); ++i) {
+      if (args[i].find(prefix) == 0)
+        return args[i];
+    }
+    return std::string();
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  base::FilePath base_path_;
+
+  bool write_use_flags_file_;
+  std::string use_flags_data_;
+
+  bool write_lsb_release_file_;
+  std::string lsb_release_data_;
+
+  base::FilePath pepper_dir_;
+
+  ChromiumCommandBuilder builder_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChromiumCommandBuilderTest);
+};
+
+TEST_F(ChromiumCommandBuilderTest, MissingUseFlagsFile) {
+  write_use_flags_file_ = false;
+  EXPECT_FALSE(Init());
+}
+
+TEST_F(ChromiumCommandBuilderTest, UseFlags) {
+  use_flags_data_ = "foo\nbar\nboard_use_blah\n";
+  EXPECT_TRUE(Init());
+
+  EXPECT_TRUE(builder_.UseFlagIsSet("foo"));
+  EXPECT_TRUE(builder_.UseFlagIsSet("bar"));
+  EXPECT_FALSE(builder_.UseFlagIsSet("food"));
+
+  EXPECT_TRUE(builder_.IsBoard("blah"));
+  EXPECT_FALSE(builder_.IsBoard("foo"));
+  EXPECT_FALSE(builder_.IsBoard("blah1"));
+}
+
+TEST_F(ChromiumCommandBuilderTest, MissingLsbReleaseFile) {
+  write_lsb_release_file_ = false;
+  EXPECT_TRUE(Init());
+  EXPECT_FALSE(builder_.SetUpChromium());
+}
+
+TEST_F(ChromiumCommandBuilderTest, LsbRelease) {
+  lsb_release_data_ = "abc\ndef";
+  EXPECT_TRUE(Init());
+  EXPECT_TRUE(builder_.SetUpChromium());
+
+  EXPECT_EQ(lsb_release_data_, ReadEnvVar("LSB_RELEASE"));
+  EXPECT_FALSE(ReadEnvVar("LSB_RELEASE_TIME").empty());
+}
+
+TEST_F(ChromiumCommandBuilderTest, BasicEnvironment) {
+  EXPECT_TRUE(Init());
+  EXPECT_TRUE(builder_.SetUpChromium());
+
+  EXPECT_EQ("chronos", ReadEnvVar("USER"));
+  EXPECT_EQ("chronos", ReadEnvVar("LOGNAME"));
+  EXPECT_EQ("/bin/sh", ReadEnvVar("SHELL"));
+  EXPECT_FALSE(ReadEnvVar("PATH").empty());
+  EXPECT_EQ("en_US.utf8", ReadEnvVar("LC_ALL"));
+  base::FilePath data_dir(util::GetReparentedPath("/home/chronos", base_path_));
+  EXPECT_EQ(data_dir.value(), ReadEnvVar("DATA_DIR"));
+  EXPECT_TRUE(base::DirectoryExists(data_dir));
+}
+
+TEST_F(ChromiumCommandBuilderTest, VmoduleFlag) {
+  EXPECT_TRUE(Init());
+  EXPECT_TRUE(builder_.SetUpChromium());
+
+  const char kVmodulePrefix[] = "--vmodule=";
+  ASSERT_EQ("", GetFirstArgWithPrefix(kVmodulePrefix));
+  builder_.AddVmodulePattern("foo=1");
+  ASSERT_EQ("--vmodule=foo=1", GetFirstArgWithPrefix(kVmodulePrefix));
+  builder_.AddVmodulePattern("bar=2");
+  ASSERT_EQ("--vmodule=foo=1,bar=2", GetFirstArgWithPrefix(kVmodulePrefix));
+
+  // Add another argument and check that --vmodule still gets updated.
+  builder_.AddArg("--blah");
+  builder_.AddVmodulePattern("baz=1");
+  ASSERT_EQ("--vmodule=foo=1,bar=2,baz=1",
+            GetFirstArgWithPrefix(kVmodulePrefix));
+}
+
+TEST_F(ChromiumCommandBuilderTest, UserConfig) {
+  EXPECT_TRUE(Init());
+  builder_.AddArg("--baz=4");
+  builder_.AddArg("--blah-a");
+  builder_.AddArg("--blah-b");
+
+  const char kConfig[] =
+      "# Here's a comment followed by a blank line and some whitespace.\n"
+      "\n"
+      "     \n"
+      "--foo=1\n"
+      "--bar=2\n"
+      "FOO=3\n"
+      "BAR=4\n"
+      "!--bar\n"
+      "!--baz\n"
+      "--bar=3\n"
+      "!--blah\n";
+  base::FilePath path(util::GetReparentedPath("/config.txt", base_path_));
+  PCHECK(base::WriteFile(path, kConfig, strlen(kConfig)) == strlen(kConfig));
+
+  ASSERT_TRUE(builder_.ApplyUserConfig(path));
+  ASSERT_EQ(2U, builder_.arguments().size());
+  EXPECT_EQ("--foo=1", builder_.arguments()[0]);
+  EXPECT_EQ("--bar=3", builder_.arguments()[1]);
+  EXPECT_EQ("3", ReadEnvVar("FOO"));
+  EXPECT_EQ("4", ReadEnvVar("BAR"));
+}
+
+TEST_F(ChromiumCommandBuilderTest, UserConfigVmodule) {
+  EXPECT_TRUE(Init());
+  builder_.AddArg("--foo");
+  builder_.AddVmodulePattern("a=2");
+  builder_.AddArg("--bar");
+
+  // Check that we don't get confused when deleting flags surrounding the
+  // vmodule flag.
+  const char kConfig[] = "!--foo\n!--bar";
+  base::FilePath path(util::GetReparentedPath("/config.txt", base_path_));
+  PCHECK(base::WriteFile(path, kConfig, strlen(kConfig)) == strlen(kConfig));
+  ASSERT_TRUE(builder_.ApplyUserConfig(path));
+  builder_.AddVmodulePattern("b=1");
+  ASSERT_EQ("--vmodule=a=2,b=1", GetFirstArgWithPrefix("--vmodule="));
+
+  // Delete the --vmodule flag.
+  const char kConfig2[] = "!--vmodule=";
+  PCHECK(base::WriteFile(path, kConfig2, strlen(kConfig2)) == strlen(kConfig2));
+  ASSERT_TRUE(builder_.ApplyUserConfig(path));
+  EXPECT_TRUE(builder_.arguments().empty());
+
+  // Now add another vmodule pattern and check that the flag is re-added.
+  builder_.AddVmodulePattern("c=1");
+  ASSERT_EQ("--vmodule=c=1", GetFirstArgWithPrefix("--vmodule="));
+
+  // Check that vmodule directives in config files are handled.
+  const char kConfig3[] = "vmodule=a=1\nvmodule=b=2";
+  PCHECK(base::WriteFile(path, kConfig3, strlen(kConfig3)) == strlen(kConfig3));
+  ASSERT_TRUE(builder_.ApplyUserConfig(path));
+  ASSERT_EQ("--vmodule=c=1,a=1,b=2", GetFirstArgWithPrefix("--vmodule="));
+}
+
+TEST_F(ChromiumCommandBuilderTest, PepperPlugins) {
+  const char kFlash[] =
+      "# Here's a comment.\n"
+      "FILE_NAME=/opt/google/chrome/pepper/flash.so\n"
+      "PLUGIN_NAME=\"Shockwave Flash\"\n"
+      "VERSION=1.2.3.4\n";
+  PCHECK(base::WriteFile(pepper_dir_.Append("flash.info"), kFlash,
+                         strlen(kFlash)) == strlen(kFlash));
+
+  const char kNetflix[] =
+      "FILE_NAME=/opt/google/chrome/pepper/netflix.so\n"
+      "PLUGIN_NAME=\"Netflix\"\n"
+      "VERSION=2.0.0\n"
+      "DESCRIPTION=Helper for the Netflix application\n"
+      "MIME_TYPES=\"application/netflix\"\n";
+  PCHECK(base::WriteFile(pepper_dir_.Append("netflix.info"), kNetflix,
+                         strlen(kNetflix)) == strlen(kNetflix));
+
+  const char kOther[] =
+      "PLUGIN_NAME=Some other plugin\n"
+      "FILE_NAME=/opt/google/chrome/pepper/other.so\n";
+  PCHECK(base::WriteFile(pepper_dir_.Append("other.info"), kOther,
+                         strlen(kOther)) == strlen(kOther));
+
+  const char kMissingFileName[] =
+      "PLUGIN_NAME=Foo\n"
+      "VERSION=2.3\n";
+  PCHECK(base::WriteFile(pepper_dir_.Append("broken.info"), kMissingFileName,
+                         strlen(kMissingFileName)) == strlen(kMissingFileName));
+
+  EXPECT_TRUE(Init());
+  EXPECT_TRUE(builder_.SetUpChromium());
+
+  EXPECT_EQ("--ppapi-flash-path=/opt/google/chrome/pepper/flash.so",
+            GetFirstArgWithPrefix("--ppapi-flash-path"));
+  EXPECT_EQ("--ppapi-flash-version=1.2.3.4",
+            GetFirstArgWithPrefix("--ppapi-flash-version"));
+
+  // Plugins are ordered alphabetically by registration info.
+  const char kExpected[] =
+      "--register-pepper-plugins="
+      "/opt/google/chrome/pepper/netflix.so#Netflix#"
+      "Helper for the Netflix application#2.0.0;application/netflix,"
+      "/opt/google/chrome/pepper/other.so#Some other plugin;";
+  EXPECT_EQ(kExpected, GetFirstArgWithPrefix("--register-pepper-plugins"));
+}
+
+TEST_F(ChromiumCommandBuilderTest, DeepMemoryProfiler) {
+  use_flags_data_ = "deep_memory_profiler";
+  WriteFileUnderBasePath(
+      ChromiumCommandBuilder::kDeepMemoryProfilerPrefixPath, "/foo\n");
+  WriteFileUnderBasePath(
+      ChromiumCommandBuilder::kDeepMemoryProfilerTimeIntervalPath, "5\n");
+  EXPECT_TRUE(Init());
+  EXPECT_TRUE(builder_.SetUpChromium());
+
+  EXPECT_EQ("/foo", ReadEnvVar("HEAPPROFILE"));
+  EXPECT_EQ("5", ReadEnvVar("HEAP_PROFILE_TIME_INTERVAL"));
+  EXPECT_EQ("1", ReadEnvVar("HEAP_PROFILE_MMAP"));
+  EXPECT_EQ("1", ReadEnvVar("DEEP_HEAP_PROFILE"));
+  EXPECT_EQ("--no-sandbox", GetFirstArgWithPrefix("--no-sandbox"));
+}
diff --git a/cros-xauth.c b/cros-xauth.c
deleted file mode 100644
index 39e55b8..0000000
--- a/cros-xauth.c
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/* For background on this, see:
- * https://chromium-review.googlesource.com/43885
- * https://crosbug.com/39422
- *
- * Short answer: `xauth` pulls in a lot of legacy libs we don't care about.
- */
-
-#include <arpa/inet.h>
-#include <assert.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-
-#if __GLIBC_MINOR__ >= 16
-/* New to glibc-2.16 */
-#include <sys/auxv.h>
-#else
-/* Roll our own as long as we have glibc-2.15 */
-#include <elf.h>
-#include <link.h>
-static unsigned long getauxval(unsigned long type)
-{
-  unsigned long val = 0;
-  ElfW(auxv_t) auxv;
-  FILE *fp = fopen("/proc/self/auxv", "r");
-  while (fread(&auxv, sizeof(auxv), 1, fp))
-    if (auxv.a_type == type) {
-      val = auxv.a_un.a_val;
-      break;
-    }
-  fclose(fp);
-  return val;
-}
-#endif
-
-/* New to glibc-2.16 / gcc-4.7 */
-#ifndef static_assert
-# define static_assert(cond, msg) \
-  do { \
-    (void)(sizeof(char[1 - 2 * !(cond)])); \
-  } while (0)
-#endif
-
-static bool writeu16(FILE *fp, uint16_t val)
-{
-  uint16_t tmp = htons(val);
-  return fwrite(&tmp, sizeof(tmp), 1, fp) == 1 ? true : false;
-}
-
-static bool writelv(FILE *fp, uint16_t len, const char *val)
-{
-  if (!writeu16(fp, len) ||
-      fwrite(val, len, 1, fp) != 1)
-    return false;
-  return true;
-}
-
-static bool writestrlv(FILE *fp, const char *str)
-{
-  return writelv(fp, (uint16_t)strlen(str), str);
-}
-
-/*
- * cros-xauth - ChromeOS MIT-MAGIC-COOKIE-1 generator
- *
- * Usage: cros-xauth <Xauthority file>
- *
- * Outputs an xauth cookie equivalent to:
- *   $ xauth -q -f .Xauthority add :0 . $(mcookie)
- */
-int main(int argc, char *argv[])
-{
-  static uint16_t family = 0x100;
-  static const char address[] = "localhost";
-  static const char number[] = "0";
-  static const char name[] = "MIT-MAGIC-COOKIE-1";
-  char cookie[16];
-
-  /* Sanity in an insane world! */
-  umask(0022);
-
-  /* The kernel gives us a pointer to 16 bytes of random data via
-   * AT_RANDOM.  Suck it up as our cookie if possible.
-   */
-  void *at_random = (void *)(uintptr_t)getauxval(AT_RANDOM);
-  if (!at_random) {
-    /* Rely on ASLR to give us at least 32 bits of randomness,
-     * and we'll let garbage on the stack do the rest.
-     */
-    uintptr_t addr = (uintptr_t)&main;
-    memcpy(cookie, &addr, sizeof(addr));
-    static_assert(sizeof(addr) <= sizeof(cookie), "cookie/pointer mismatch");
-  } else {
-    memcpy(cookie, at_random, 16);
-    static_assert(16 <= sizeof(cookie), "cookie too small for AT_RANDOM");
-  }
-
-  FILE *fp = fopen(argv[1], "wb");
-  if (fp == NULL ||
-      !writeu16(fp, family) ||
-      !writestrlv(fp, address) ||
-      !writestrlv(fp, number) ||
-      !writestrlv(fp, name) ||
-      !writelv(fp, sizeof(cookie), cookie)) {
-    perror(argv[0]);
-    return EXIT_FAILURE;
-  }
-
-  return EXIT_SUCCESS;
-}
diff --git a/generator_job.cc b/generator_job.cc
index b33726a..6b257dd 100644
--- a/generator_job.cc
+++ b/generator_job.cc
@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -59,7 +60,7 @@
   argv.push_back(filename_);
   argv.push_back(user_path_);
 
-  return subprocess_.ForkAndExec(argv);
+  return subprocess_.ForkAndExec(argv, std::map<std::string, std::string>());
 }
 
 void GeneratorJob::KillEverything(int signal, const std::string& message) {
diff --git a/init/ui.conf b/init/ui.conf
index b2dae97..4de3165 100644
--- a/init/ui.conf
+++ b/init/ui.conf
@@ -25,90 +25,18 @@
 limit rtprio 10 10
 
 # Extend the grace period given to the session_manager before upstart decides
-# it's gone unresponsive on job termination and must be killed.
-# Chrome gets 12s (configured in session_manager_setup.sh) to shut down on
-# devices with HDDs, so give a cushion.
+# it's gone unresponsive on job termination and must be killed. Chrome gets 12s
+# (configured below) to shut down on devices with HDDs, so give a cushion.
 kill timeout 20  # In seconds.
 
 # Uncomment line below to output to VT02
 #console output
 
-# Directory where Chrome logs are written and prefix of files there.
-env CHROME_LOG_DIR=/var/log/chrome
-env CHROME_LOG_PREFIX=chrome
-
 # Directory where session manager logs are written and prefix of files there.
 env UI_LOG_DIR=/var/log/ui
 env UI_LOG_FILE=ui.LATEST
 
 pre-start script
-  X_SOCKET_DIR=/tmp/.X11-unix
-  X_ICE_DIR=/tmp/.ICE-unix
-  mkdir -p $X_SOCKET_DIR $X_ICE_DIR
-  chown root:root $X_SOCKET_DIR $X_ICE_DIR
-  chmod 1777 $X_SOCKET_DIR $X_ICE_DIR
-
-  # XKB writes keymaps here; otherwise things like Ctrl-Alt-Fx VT switching
-  # don't work.
-  mkdir -p /var/lib/xkb
-  chown xorg:xorg /var/lib/xkb
-
-  # Make sure we we can easily track UI state.
-  rm -rf /var/run/state
-  mkdir -p /var/run/state
-
-  # Create a directory where the session manager can store a copy of the user
-  # policy key, that will be readable by the chrome process as chronos.
-  rm -rf /var/run/user_policy
-  mkdir -m 0710 -p /var/run/user_policy
-  chown root:chronos /var/run/user_policy
-
-  # Create a directory where the chrome process can store a reboot request so
-  # that it persists across browser crashes but is always removed on reboot.
-  mkdir -m 0700 -p /var/run/chrome
-  chown chronos:chronos /var/run/chrome
-
-  # Ensure the existence of the directory in which the whitelist and other
-  # Ownership-related state will live.  Yes, it should be owned by root.
-  # The permissions are set such that the chronos user can see the content
-  # of known files inside whitelist, but not anything else.
-  mkdir -m 0710 -p /var/lib/whitelist
-  chown root:chronos /var/lib/whitelist
-
-  # Create the directory where external data referenced by policies is cached
-  # for device-local accounts. This data is read and written by chronos.
-  mkdir -m 0700 -p /var/cache/device_local_account_external_policy_data
-  chown chronos:chronos /var/cache/device_local_account_external_policy_data
-
-  # Create the directory where the AppPack extensions are cached.
-  # These extensions are read and written by chronos.
-  mkdir -m 0700 -p /var/cache/app_pack
-  chown chronos:chronos /var/cache/app_pack
-
-  # Create the directory where extensions for device-local accounts are cached.
-  # These extensions are read and written by chronos.
-  mkdir -m 0700 -p /var/cache/device_local_account_extensions
-  chown chronos:chronos /var/cache/device_local_account_extensions
-
-  # Create the directory for shared installed extensions.
-  # Shared extensions are validated at runtime by the browser.
-  # These extensions are read and written by chronos.
-  mkdir -m 0700 -p /var/cache/shared_extensions
-  chown chronos:chronos /var/cache/shared_extensions
-
-  # Enable us to keep track of the user's chosen TZ.
-  # Default to Pacific timezone if we don't have one set
-  TIMEZONE_DIR=/var/lib/timezone
-  TIMEZONE_FILE=$TIMEZONE_DIR/localtime
-  if [ ! -f $TIMEZONE_FILE ]; then
-    mkdir -p $TIMEZONE_DIR
-    ln -sf /usr/share/zoneinfo/US/Pacific ${TIMEZONE_FILE}
-    chown -R chronos:chronos ${TIMEZONE_DIR}
-  fi
-
-  mkdir -p $CHROME_LOG_DIR
-  chown chronos:chronos $CHROME_LOG_DIR
-
   mkdir -p $UI_LOG_DIR
   ln -sf ui.$(date +%Y%m%d-%H%M%S) $UI_LOG_DIR/$UI_LOG_FILE
 
@@ -119,15 +47,42 @@
   # lower. The default value is 1024.
   CHROME_CGROUP_DIR=/sys/fs/cgroup/cpu/chrome_renderers
   if [ ! -d $CHROME_CGROUP_DIR ]; then
-     mkdir -p ${CHROME_CGROUP_DIR}
-     mkdir -p ${CHROME_CGROUP_DIR}/foreground
-     mkdir -p ${CHROME_CGROUP_DIR}/background
-     echo "10" > ${CHROME_CGROUP_DIR}/background/cpu.shares
-     chown -R chronos ${CHROME_CGROUP_DIR}
+    mkdir -p ${CHROME_CGROUP_DIR}/foreground
+    mkdir -p ${CHROME_CGROUP_DIR}/background
+    echo "10" > ${CHROME_CGROUP_DIR}/background/cpu.shares
+    chown -R chronos ${CHROME_CGROUP_DIR}
   fi
 end script  # pre-start
 
-exec session_manager_setup.sh >$UI_LOG_DIR/$UI_LOG_FILE 2>&1
+script
+  # The session_manager supports pinging the browser periodically to check that
+  # it is still alive.  On developer systems, this would be a problem, as
+  # debugging the browser would cause it to be aborted. Override via a flag-file
+  # is allowed to enable integration testing.
+  HANG_DETECTION_FLAG_FILE=/var/run/session_manager/enable_hang_detection
+  HANG_DETECTION_FLAG=
+  if ! is_developer_end_user; then
+    HANG_DETECTION_FLAG="--enable-hang-detection"
+  elif [ -f ${HANG_DETECTION_FLAG_FILE} ]; then
+    HANG_DETECTION_FLAG="--enable-hang-detection=5"  # And do it FASTER!
+  fi
+
+  # On platforms with rotational disks, Chrome takes longer to shut down. As
+  # such, we need to change our baseline assumption about what "taking too long
+  # to shutdown" means and wait for longer before killing Chrome and triggering
+  # a report.
+  # TODO(derat): Move this into the session_manager binary, maybe.
+  ROOTDEV="$(rootdev -s -d | sed -e 's/^\/dev\///')"
+  KILL_TIMEOUT_FLAG=
+  if [ "$(cat /sys/block/${ROOTDEV}/queue/rotational)" = 1 ]; then
+    KILL_TIMEOUT_FLAG="--kill-timeout=12"
+  fi
+
+  exec session_manager \
+    $HANG_DETECTION_FLAG \
+    $KILL_TIMEOUT_FLAG \
+    >$UI_LOG_DIR/$UI_LOG_FILE 2>&1
+end script
 
 post-stop script
   bootstat ui-post-stop
diff --git a/session_manager_main.cc b/session_manager_main.cc
index bfe3d02..668deb3 100644
--- a/session_manager_main.cc
+++ b/session_manager_main.cc
@@ -7,6 +7,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <map>
 #include <string>
 #include <vector>
 
@@ -27,12 +28,14 @@
 #include <chromeos/syslog_logging.h>
 
 #include "login_manager/browser_job.h"
+#include "login_manager/chrome_setup.h"
 #include "login_manager/file_checker.h"
 #include "login_manager/login_metrics.h"
 #include "login_manager/regen_mitigator.h"
 #include "login_manager/session_manager_service.h"
 #include "login_manager/system_utils_impl.h"
 
+using std::map;
 using std::string;
 using std::vector;
 using std::wstring;
@@ -41,8 +44,6 @@
 // window manager binary as well. Actually supports watching several
 // processes specified as command line arguments separated with --.
 // Also listens over DBus for the commands specified in dbus_glib_shim.h.
-// Usage:
-//   session_manager --uid=1000 -- /path/to/command1 [arg1 [arg2 [ . . . ] ] ]
 
 namespace switches {
 
@@ -53,10 +54,6 @@
 static const char kDisableChromeRestartFileDefault[] =
     "/var/run/disable_chrome_restart";
 
-// Name of the flag specifying UID to be set for each managed job before
-// starting it.
-static const char kUid[] = "uid";
-
 // Name of flag specifying the time (in s) to wait for children to exit
 // gracefully before killing them with a SIGABRT.
 static const char kKillTimeout[] = "kill-timeout";
@@ -78,8 +75,6 @@
 "  --disable-chrome-restart-file=</path/to/file>\n"
 "    Magic file that causes this program to stop restarting the\n"
 "    chrome binary and exit. (default: /var/run/disable_chrome_restart)\n"
-"  --uid=[number]\n"
-"    Numeric uid to transition to prior to execution.\n"
 "  --kill-timeout=[number in seconds]\n"
 "    Number of seconds to wait for children to exit gracefully before\n"
 "    killing them with a SIGABRT.\n"
@@ -96,6 +91,7 @@
 using login_manager::BrowserJobInterface;
 using login_manager::FileChecker;
 using login_manager::LoginMetrics;
+using login_manager::PerformChromeSetup;
 using login_manager::SessionManagerService;
 using login_manager::SystemUtilsImpl;
 
@@ -113,18 +109,6 @@
     return 0;
   }
 
-  // Parse UID if it's present, -1 means no UID should be set.
-  uid_t uid = getuid();
-  if (cl->HasSwitch(switches::kUid)) {
-    string uid_flag = cl->GetSwitchValueASCII(switches::kUid);
-    int uid_value = 0;
-    if (base::StringToInt(uid_flag, &uid_value) && uid_value >= 0) {
-      uid = static_cast<uid_t>(uid_value);
-    } else {
-      DLOG(WARNING) << "Failed to parse uid, defaulting to none.";
-    }
-  }
-
   // Parse kill timeout if it's present.
   int kill_timeout = switches::kKillTimeoutDefault;
   if (cl->HasSwitch(switches::kKillTimeout)) {
@@ -155,8 +139,11 @@
   // session_manager side, Chrome will use --multi-profiles flag to enable it.
   bool support_multi_profile = !cl->HasSwitch(switches::kLegacyLoginProfile);
 
-  // We only support a single job with args, so grab all loose args
-  vector<string> arg_list = SessionManagerService::GetArgList(cl->GetArgs());
+  // Start the X server and set things up for running Chrome.
+  map<string, string> env_vars;
+  vector<string> arg_list;
+  uid_t uid = 0;
+  PerformChromeSetup(&env_vars, &arg_list, &uid);
 
   // Shim that wraps system calls, file system ops, etc.
   SystemUtilsImpl system;
@@ -181,6 +168,7 @@
   // UID that the caller would like to run it as.
   scoped_ptr<BrowserJobInterface> browser_job(
       new BrowserJob(arg_list,
+                     env_vars,
                      support_multi_profile,
                      uid,
                      &checker,
diff --git a/session_manager_service.cc b/session_manager_service.cc
index ce1ce17..a39db06 100644
--- a/session_manager_service.cc
+++ b/session_manager_service.cc
@@ -449,12 +449,4 @@
   key_gen_.EnsureJobExit(timeout);
 }
 
-std::vector<std::string> SessionManagerService::GetArgList(
-    const std::vector<std::string>& args) {
-  std::vector<std::string>::const_iterator start_arg = args.begin();
-  if (!args.empty() && *start_arg == "--")
-    ++start_arg;
-  return std::vector<std::string>(start_arg, args.end());
-}
-
 }  // namespace login_manager
diff --git a/session_manager_service.h b/session_manager_service.h
index 45a1613..5be576d 100644
--- a/session_manager_service.h
+++ b/session_manager_service.h
@@ -163,14 +163,6 @@
   // Ensure that browser_ is gone.
   virtual void EnsureJobExit(base::TimeDelta timeout) OVERRIDE;
 
-  // Ensures |args| is in the correct format, stripping "--" if needed.
-  // No initial "--" is needed, but is allowed.
-  // ("a", "b", "c") => ("a", "b", "c")
-  // ("--", "a", "b", "c") => ("a", "b", "c").
-  // Converts args from wide to plain strings.
-  static std::vector<std::string> GetArgList(
-      const std::vector<std::string>& args);
-
   // Set all changed signal handlers back to the default behavior.
   static void RevertHandlers();
 
diff --git a/session_manager_setup.sh b/session_manager_setup.sh
old mode 100755
new mode 100644
index 4012fd0..8cf26c3
--- a/session_manager_setup.sh
+++ b/session_manager_setup.sh
@@ -1,519 +1,14 @@
 #!/bin/sh
 
-# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-# Set up to start the X server ASAP, then let the startup run in the
-# background while we set up other stuff.
-XUSER=xorg
-XTTY=1
-XAUTH_FILE="/var/run/chromelogin.auth"
-xstart.sh ${XUSER} ${XTTY} ${XAUTH_FILE} &
+cat <<EOF 1>&2
+This file is no longer used; Chrome's command line is now generated directly by
+session_manager. Either make your changes there and rebuild the chromeos-login
+package or modify /etc/chrome_dev.conf to make local changes to Chrome's
+environment and command line.
+EOF
 
-USE_FLAGS="$(cat /etc/session_manager_use_flags.txt)"
-
-# Returns success if the USE flag passed as its sole parameter was defined.
-# New flags must be first be added to the ebuild file.
-use_flag_is_set() {
-  local flag i
-  flag="$1"
-  for i in $USE_FLAGS; do
-    if [ $i = "${flag}" ]; then
-      return 0
-    fi
-  done
-  return 1
-}
-
-# Returns success if we were built for the board passed as the sole parameter.
-is_board() {
-  use_flag_is_set "board_use_$1"
-}
-
-# --vmodule=PATTERN1=LEVEL1,PATTERN2=LEVEL2 flag passed to Chrome to selectively
-# enable verbose logging for particular files.
-VMODULE_FLAG=
-
-# Appends a pattern to VMODULE_FLAG.
-add_vmodule_pattern() {
-  if [ -z "$VMODULE_FLAG" ]; then
-    VMODULE_FLAG="--vmodule=$1"
-  else
-    VMODULE_FLAG="$VMODULE_FLAG,$1"
-  fi
-}
-
-# Takes a wallpaper name and size and returns the corresponding filename.
-get_wallpaper_filename() {
-  local NAME=$1
-  local SIZE=$2
-  echo "/usr/share/chromeos-assets/wallpaper/${NAME}_${SIZE}.jpg"
-}
-
-# Takes a wallpaper name ("default" or "guest"), size ("large" or "small"),
-# and filename and adds the corresponding flag to ASH_FLAGS.
-add_wallpaper_flag() {
-  local NAME=$1
-  local SIZE=$2
-  local FILE=$3
-  ASH_FLAGS="$ASH_FLAGS --ash-${NAME}-wallpaper-${SIZE}=${FILE}"
-}
-
-# Returns success if we're running on Chrome OS hardware.
-is_chromeos_hardware() {
-  [ "$(crossystem mainfw_type)" != "nonchrome" ]
-}
-
-export USER=chronos
-export DATA_DIR=/home/${USER}
-export LOGIN_PROFILE_DIR=${DATA_DIR}/Default
-export LOGNAME=${USER}
-export SHELL=/bin/sh
-# TODO(keescook): remove Chrome's use of $HOME.
-export HOME=${DATA_DIR}/user
-export DISPLAY=:0.0
-export XAUTHORITY=${DATA_DIR}/.Xauthority
-
-# Provide /etc/lsb-release contents and timestamp so that they are available
-# to Chrome immediately without requiring a blocking file read.
-export LSB_RELEASE="$(cat /etc/lsb-release)"
-export LSB_RELEASE_TIME="$(stat -c '%Z' /etc/lsb-release)"
-
-# If used with Address Sanitizer, set the following flags to alter memory
-# allocations by glibc. Hopefully later, when ASAN matures, we will not need
-# any changes for it to run.
-ASAN_FLAGS=
-if use_flag_is_set asan; then
-  # Make glib use system malloc.
-  export G_SLICE=always-malloc
-
-  # Make nss skip dlclosing dynamically loaded modules,
-  # which would result in "obj:*" in backtraces.
-  export NSS_DISABLE_ARENA_FREE_LIST=1
-
-  # Make nss use system malloc.
-  export NSS_DISABLE_UNLOAD=1
-
-  # Make ASAN output to the file because
-  # Chrome stderr is /dev/null now (crbug.com/156308).
-  export ASAN_OPTIONS="log_path=/var/log/chrome/asan_log"
-
-  # Disable sandboxing as it causes crashes in ASAN. crosbug.com/127536.
-  ASAN_FLAGS="--no-sandbox"
-fi
-
-# If used with Deep Memory Profiler, turn on the heap profiler.
-DMPROF_FLAGS=
-if use_flag_is_set deep_memory_profiler; then
-  if [ -f /var/tmp/deep_memory_profiler_time_interval.txt ] ; then
-    read dmprof_time_interval < /var/tmp/deep_memory_profiler_time_interval.txt
-  fi
-  if [ -f /var/tmp/deep_memory_profiler_prefix.txt ] ; then
-    read dmprof_prefix < /var/tmp/deep_memory_profiler_prefix.txt
-
-    # Dump heap profiles to /tmp/dmprof.*.
-    export HEAPPROFILE=${dmprof_prefix}
-
-    # Turn on profiling mmap.
-    export HEAP_PROFILE_MMAP=1
-
-    # Turn on Deep Memory Profiler.
-    export DEEP_HEAP_PROFILE=1
-
-    # Dump every ${dmprof_time_interval} seconds.
-    export HEAP_PROFILE_TIME_INTERVAL=${dmprof_time_interval}
-
-    DMPROF_FLAGS="--no-sandbox"
-  fi
-fi
-
-# By default, libdbus treats all warnings as fatal errors. That's too strict.
-export DBUS_FATAL_WARNINGS=0
-
-# Tell Chrome where to write logging messages.
-# $CHROME_LOG_DIR and $CHROME_LOG_PREFIX are defined in ui.conf,
-# and the directory is created there as well.
-export CHROME_LOG_FILE="${CHROME_LOG_DIR}/${CHROME_LOG_PREFIX}"
-
-# Log directory for this session.  Note that ${DATA_DIR}/user might not be
-# mounted until later (when the cryptohome is mounted), so we don't
-# mkdir CHROMEOS_SESSION_LOG_DIR immediately.
-export CHROMEOS_SESSION_LOG_DIR="${DATA_DIR}/user/log"
-
-# Forces Chrome mini dumps that are sent to the crash server to also be written
-# locally.  Chrome by default will create these mini dump files in
-# ~/.config/google-chrome/Crash Reports/
-if [ -f /mnt/stateful_partition/etc/enable_chromium_minidumps ] ; then
-  export CHROME_HEADLESS=1
-  # If possible we would like to have the crash reports located somewhere else
-  if [ ! -f ~/.config/google-chrome/Crash\ Reports ] ; then
-    mkdir -p /var/minidumps/
-    chown chronos /var/minidumps/
-    ln -s /var/minidumps/ \
-      ~/.config/google-chrome/Crash\ Reports
-  fi
-fi
-
-mkdir -p ${DATA_DIR} && chown ${USER}:${USER} ${DATA_DIR}
-mkdir -p ${DATA_DIR}/user && chown ${USER}:${USER} ${DATA_DIR}/user
-
-# Old builds will have a ${LOGIN_PROFILE_DIR} that's owned by root; newer ones
-# won't have this directory at all.
-mkdir -p ${LOGIN_PROFILE_DIR}
-chown ${USER}:${USER} ${LOGIN_PROFILE_DIR}
-
-CHROME="/opt/google/chrome/chrome"
-CONSENT_FILE="$DATA_DIR/Consent To Send Stats"
-
-# xdg-open is used to open downloaded files.
-# It runs sensible-browser, which uses $BROWSER.
-export BROWSER=${CHROME}
-
-USER_ID=$(id -u ${USER})
-
-# To always force OOBE. This works ok with test images so that they
-# always start with OOBE.
-if [ -f /root/.test_repeat_oobe ] ; then
-  rm -f "${DATA_DIR}/.oobe_completed"
-  rm -f "${DATA_DIR}/Local State"
-fi
-
-SSLKEYLOGFILE=/var/log/sslkeys.log
-if use_flag_is_set dangerous_sslkeylogfile &&
-   [ -f "$SSLKEYLOGFILE" ]; then
-  # Exporting this environment variable turns on a useful diagnostic
-  # feature in Chrome/NSS, which can allow users to decrypt their own
-  # SSL traffic later with e.g. Wireshark. We key this off of both a
-  # USE flag stored on rootfs (which, essentially, locks this feature
-  # off for normal systems), and, the logfile itself (which makes this
-  # feature easy to toggle on/off, on systems like mod-for-test
-  # images, where the USE flag has been customized to permit its use).
-  export SSLKEYLOGFILE
-fi
-
-# Enables gathering of chrome dumps.  In stateful partition so testers
-# can enable getting core dumps after build time.
-if [ -f /mnt/stateful_partition/etc/enable_chromium_coredumps ] ; then
-  mkdir -p /var/coredumps/
-  # Chrome runs and chronos so we need to change the permissions of this folder
-  # so it can write there when it crashes
-  chown chronos /var/coredumps/
-  ulimit -c unlimited
-  echo "/var/coredumps/core.%e.%p" > \
-    /proc/sys/kernel/core_pattern
-fi
-
-# Increase maximum file descriptors to 2048 (default is otherwise 1024).
-# Some offline websites using IndexedDB are particularly hungry for
-# descriptors, so the default is insufficient. See crbug.com/251385.
-ulimit -n 2048
-
-# Remove consent file if it had at one point been created by this script.
-if [ -f "$CONSENT_FILE" ]; then
-  CONSENT_USER_GROUP=$(stat -c %U:%G "$CONSENT_FILE")
-  # normally, the consent file would be owned by "chronos:chronos".
-  if [ "$CONSENT_USER_GROUP" = "root:root" ]; then
-    TAG="$(basename $0)[$$]"
-    logger -t "${TAG}" "Removing consent file owned by root"
-    rm -f "$CONSENT_FILE"
-  fi
-fi
-
-# Allow Chrome to access GPU memory information despite /sys/kernel/debug
-# being owned by debugd. This limits the security attack surface versus
-# leaving the whole debug directory world-readable. http://crbug.com/175828
-DEBUGFS_GPU=/var/run/debugfs_gpu
-if [ ! -d $DEBUGFS_GPU ]; then
-  mkdir -p $DEBUGFS_GPU
-  mount -o bind /sys/kernel/debug/dri/0 $DEBUGFS_GPU
-fi
-
-# We need to delete these files as Chrome may have left them around from
-# its prior run (if it crashed).
-rm -f ${DATA_DIR}/SingletonLock
-rm -f ${DATA_DIR}/SingletonSocket
-
-# Set an environment variable to prevent Flash asserts from crashing the plugin
-# process.
-export DONT_CRASH_ON_ASSERT=1
-
-# Look for pepper plugins and register them
-PEPPER_PATH=/opt/google/chrome/pepper
-REGISTER_PLUGINS=
-COMMA=
-FLASH_FLAGS=
-PPAPI_FLASH_FLAGS=
-for file in $(find $PEPPER_PATH -name '*.info'); do
-  FILE_NAME=
-  PLUGIN_NAME=
-  DESCRIPTION=
-  VERSION=
-  MIME_TYPES=
-  . $file
-  [ -z "$FILE_NAME" ] && continue
-  PLUGIN_STRING="${FILE_NAME}"
-  if [ -n "$PLUGIN_NAME" ]; then
-    PLUGIN_STRING="${PLUGIN_STRING}#${PLUGIN_NAME}"
-    if [ -n "$DESCRIPTION" ]; then
-      PLUGIN_STRING="${PLUGIN_STRING}#${DESCRIPTION}"
-      [ -n "$VERSION" ] && PLUGIN_STRING="${PLUGIN_STRING}#${VERSION}"
-    fi
-  fi
-  if [ "$PLUGIN_NAME" = "Shockwave Flash" ]; then
-    # Flash is treated specially.
-    FLASH_FLAGS="--ppapi-flash-path=${FILE_NAME}"
-    FLASH_FLAGS="${FLASH_FLAGS} --ppapi-flash-version=${VERSION}"
-    # TODO(ihf): Remove once crbug.com/237380 and crbug.com/276738 are fixed.
-    if is_board x86-alex || is_board x86-alex_he || is_board x86-mario ||
-        is_board x86-zgb || is_board x86-zgb_he ; then
-      PPAPI_FLASH_FLAGS="--ppapi-flash-args=enable_hw_video_decode=0"
-    else
-      PPAPI_FLASH_FLAGS="--ppapi-flash-args=enable_hw_video_decode=1"
-    fi
-  else
-    PLUGIN_STRING="${PLUGIN_STRING};${MIME_TYPES}"
-    REGISTER_PLUGINS="${REGISTER_PLUGINS}${COMMA}${PLUGIN_STRING}"
-    COMMA=","
-  fi
-done
-if [ -n "$REGISTER_PLUGINS" ]; then
-  REGISTER_PLUGINS="--register-pepper-plugins=$REGISTER_PLUGINS"
-fi
-
-# Enable natural scroll by default.
-TOUCHPAD_FLAGS=
-if use_flag_is_set natural_scroll_default; then
-  TOUCHPAD_FLAGS="--enable-natural-scroll-default"
-fi
-
-KEYBOARD_FLAGS=
-if ! use_flag_is_set legacy_keyboard; then
-  KEYBOARD_FLAGS="--has-chromeos-keyboard"
-fi
-
-if use_flag_is_set has_diamond_key; then
-  KEYBOARD_FLAGS="$KEYBOARD_FLAGS --has-chromeos-diamond-key"
-fi
-
-ASH_FLAGS=
-if use_flag_is_set legacy_power_button; then
-  ASH_FLAGS="$ASH_FLAGS --aura-legacy-power-button"
-fi
-if use_flag_is_set disable_login_animations; then
-  ASH_FLAGS="$ASH_FLAGS --disable-login-animations"
-  ASH_FLAGS="$ASH_FLAGS --disable-boot-animation"
-  ASH_FLAGS="$ASH_FLAGS --ash-copy-host-background-at-boot"
-elif use_flag_is_set fade_boot_splash_screen; then
-  ASH_FLAGS="$ASH_FLAGS --ash-animate-from-boot-splash-screen"
-fi
-
-if [ -e $(get_wallpaper_filename oem large) ] &&
-   [ -e $(get_wallpaper_filename oem small) ]; then
-  add_wallpaper_flag default large $(get_wallpaper_filename oem large)
-  add_wallpaper_flag default small $(get_wallpaper_filename oem small)
-  ASH_FLAGS="$ASH_FLAGS --ash-default-wallpaper-is-oem"
-elif [ -e $(get_wallpaper_filename default large) ] &&
-     [ -e $(get_wallpaper_filename default small) ]; then
-  add_wallpaper_flag default large $(get_wallpaper_filename default large)
-  add_wallpaper_flag default small $(get_wallpaper_filename default small)
-fi
-
-if [ -e $(get_wallpaper_filename guest large) ] &&
-   [ -e $(get_wallpaper_filename guest small) ]; then
-  add_wallpaper_flag guest large $(get_wallpaper_filename guest large)
-  add_wallpaper_flag guest small $(get_wallpaper_filename guest small)
-fi
-
-# Setup GPU & acceleration flags which differ between SoCs that
-# use EGL/GLX rendering
-if use_flag_is_set egl; then
-  ACCELERATED_FLAGS="--use-gl=egl"
-fi
-
-if use_flag_is_set exynos; then
-  # On boards with ARM NEON support, force libvpx to use the NEON-optimized
-  # code paths. Remove once http://crbug.com/161834 is fixed.
-  # This is needed because libvpx cannot check cpuinfo within the sandbox.
-  export VPX_SIMD_CAPS=0xf
-fi
-
-HIGHDPI_FLAGS=
-if use_flag_is_set highdpi; then
-  HIGHDPI_FLAGS="$HIGHDPI_FLAGS --enable-webkit-text-subpixel-positioning"
-  HIGHDPI_FLAGS="$HIGHDPI_FLAGS --enable-accelerated-overflow-scroll"
-  HIGHDPI_FLAGS="$HIGHDPI_FLAGS --default-tile-width=512"
-  HIGHDPI_FLAGS="$HIGHDPI_FLAGS --default-tile-height=512"
-fi
-
-TOUCHUI_FLAGS=
-if is_board link; then
-  TOUCHUI_FLAGS="--touch-calibration=0,0,0,50"
-fi
-
-# Device Manager Server used to fetch the enterprise policy, if applicable.
-DMSERVER="https://m.google.com/devicemanagement/data/api"
-
-# For i18n keyboard support (crbug.com/116999)
-export LC_ALL=en_US.utf8
-
-# On platforms with rotational disks, Chrome takes longer to shut down.
-# As such, we need to change our baseline assumption about what "taking too long
-# to shutdown" means and wait for longer before killing Chrome and triggering
-# a report.
-KILL_TIMEOUT_FLAG=
-if use_flag_is_set has_hdd; then
-  KILL_TIMEOUT_FLAG="--kill-timeout=12"
-fi
-
-# The session_manager supports pinging the browser periodically to
-# check that it is still alive.  On developer systems, this would be a
-# problem, as debugging the browser would cause it to be aborted.
-# Override via a flag-file is allowed to enable integration testing.
-HANG_DETECTION_FLAG_FILE=/var/run/session_manager/enable_hang_detection
-HANG_DETECTION_FLAG=
-if ! is_developer_end_user; then
-  HANG_DETECTION_FLAG="--enable-hang-detection"
-elif [ -f ${HANG_DETECTION_FLAG_FILE} ]; then
-  HANG_DETECTION_FLAG="--enable-hang-detection=5"  # And do it FASTER!
-fi
-
-GPU_SANDBOX_FAILURES_FATAL=
-if is_chromeos_hardware; then
-  GPU_SANDBOX_FAILURES_FATAL="yes"
-else
-  GPU_SANDBOX_FAILURES_FATAL="no"
-fi
-
-GPU_FLAGS="--gpu-sandbox-failures-fatal=$GPU_SANDBOX_FAILURES_FATAL"
-
-if use_flag_is_set gpu_sandbox_allow_sysv_shm; then
-  GPU_FLAGS="$GPU_FLAGS --gpu-sandbox-allow-sysv-shm"
-fi
-
-if use_flag_is_set gpu_sandbox_start_after_initialization; then
-  GPU_FLAGS="$GPU_FLAGS --gpu-sandbox-start-after-initialization"
-fi
-
-# TODO(posciak,owenlin): Autodetection for VIDEO_FLAGS features instead
-# (crbug.com/350197)
-VIDEO_FLAGS=
-if is_board peach_pit || is_board peach_pi ||
-   is_board nyan || is_board nyan_big || is_board nyan_blaze; then
-  VIDEO_FLAGS="--enable-webrtc-hw-vp8-encoding"
-fi
-
-if is_board peach_pi; then
-  VIDEO_FLAGS="$VIDEO_FLAGS --ignore-resolution-limits-for-accelerated-video-decode"
-fi
-
-# Ozone platform configuration
-OZONE_FLAGS=
-if use_flag_is_set ozone_platform_dri; then
-  # TODO(spang): Use freon/chromeos platform, not DRI example platform.
-  OZONE_FLAGS="$OZONE_FLAGS --ozone-platform=dri"
-
-  # TODO(spang): Fix hardware acceleration.
-  OZONE_FLAGS="$OZONE_FLAGS --disable-gpu"
-  OZONE_FLAGS="$OZONE_FLAGS --ui-disable-threaded-compositing"
-fi
-
-# On developer systems, set a flag to let the browser know that it is on
-# one.
-DEVELOPER_MODE_FLAG=
-if is_developer_end_user; then
-  DEVELOPER_MODE_FLAG="--system-developer-mode"
-fi
-
-# TODO(yongjaek): Remove the following flag when the kiosk mode app is ready
-# at crbug.com/309806.
-DISABLE_DEMO_MODE_FLAG=
-if use_flag_is_set moblab; then
-  DISABLE_DEMO_MODE_FLAG="--disable-demo-mode"
-fi
-
-# There has been a steady supply of bug reports about screen locking. These
-# messages are useful for determining what happened within feedback reports.
-add_vmodule_pattern "screen_locker=1"
-add_vmodule_pattern "webui_screen_locker=1"
-
-# TODO(ygorshenin): Remove this once we will have logs from places
-# where shill was tested (crosbug.com/36622).
-add_vmodule_pattern "network_portal_detector_impl=1"
-
-# Turn on logging about external displays being connected and disconnected.
-# Different behavior is seen from different displays and these messages are used
-# to determine what happened within feedback reports.
-add_vmodule_pattern "*ui/display/chromeos*=1"
-add_vmodule_pattern "*ash/display*=1"
-
-# Turn on plugin loading failure logging for crbug.com/314301.
-add_vmodule_pattern "*zygote*=1"
-add_vmodule_pattern "*plugin*=2"
-
-# The subshell that started the X server will terminate once X is
-# ready.  Wait here for that event before continuing.
-#
-# RED ALERT!  The code from the 'wait' to the end of the script is
-# part of the boot time critical path.  Every millisecond spent after
-# the wait is a millisecond longer till the login screen.
-#
-# KEEP THIS CODE PATH CLEAN!  The code must be obviously fast by
-# inspection; nothing should go after the wait that isn't required
-# for correctness.
-
-wait
-
-# Create the XAUTHORITY file so ${USER} can access the X server.
-# This must happen after xstart.sh has finished (and created ${XAUTH_FILE}),
-# hence after the wait.
-cp -f ${XAUTH_FILE} ${XAUTHORITY} && chown ${USER}:${USER} ${XAUTHORITY}
-
-initctl emit x-started
-bootstat x-started
-
-# This is a bad place to add your code.  See "RED ALERT", above.
-# Regrettably, this comment is not redundant.  :-(
-
-#
-# Reset PATH to exclude directories unneeded by session_manager.
-# Save that until here, because many of the commands above depend
-# on the default PATH handed to us by init.
-#
-export PATH=/bin:/usr/bin:/usr/bin/X11
-
-exec /sbin/session_manager --uid=${USER_ID} ${KILL_TIMEOUT_FLAG} \
-    ${HANG_DETECTION_FLAG} -- \
-    $CHROME --device-management-url="$DMSERVER" \
-            --enable-fixed-position-compositing \
-            --enable-logging \
-            --enable-impl-side-painting \
-            --max-tiles-for-interest-area=512 \
-            --enterprise-enable-forced-re-enrollment \
-            --enterprise-enrollment-initial-modulus=9 \
-            --enterprise-enrollment-modulus-limit=13 \
-            --log-level=1 \
-            --login-manager \
-            --login-profile=user \
-            --max-unused-resource-memory-usage-percentage=5 \
-            --ui-enable-per-tile-painting \
-            --ui-prioritize-in-gpu-process \
-            --use-cras \
-            --user-data-dir="$DATA_DIR" \
-            "$REGISTER_PLUGINS" \
-            ${ACCELERATED_FLAGS} \
-            ${ASH_FLAGS} \
-            ${FLASH_FLAGS} \
-            ${HIGHDPI_FLAGS} \
-            ${TOUCHPAD_FLAGS} \
-            ${KEYBOARD_FLAGS} \
-            ${TOUCHUI_FLAGS} \
-            ${ASAN_FLAGS} \
-            ${DMPROF_FLAGS} \
-            ${PPAPI_FLASH_FLAGS} \
-            ${VMODULE_FLAG} \
-            ${GPU_FLAGS} \
-            ${VIDEO_FLAGS} \
-            ${OZONE_FLAGS} \
-            ${DEVELOPER_MODE_FLAG} \
-            ${DISABLE_DEMO_MODE_FLAG}
+exit 1
diff --git a/session_manager_static_unittest.cc b/session_manager_static_unittest.cc
deleted file mode 100644
index 606ef5a..0000000
--- a/session_manager_static_unittest.cc
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "login_manager/session_manager_service.h"
-
-#include <gtest/gtest.h>
-
-namespace login_manager {
-
-TEST(SessionManagerTestStatic, GetArgLists0) {
-  std::vector<std::string> args;
-  std::vector<std::string> arg_list = SessionManagerService::GetArgList(args);
-  EXPECT_EQ(0, arg_list.size());
-}
-
-namespace {
-
-static std::vector<std::string> GetArgs(const char** c_args) {
-  std::vector<std::string> args;
-  while (*c_args) {
-    args.push_back(*c_args);
-    c_args++;
-  }
-  return SessionManagerService::GetArgList(args);
-}
-
-}  // anonymous namespace
-
-TEST(SessionManagerTestStatic, GetArgLists1) {
-  const char* c_args[] = {"a", "b", "c", NULL};
-  std::vector<std::string> arg_list = GetArgs(c_args);
-  ASSERT_EQ(3, arg_list.size());
-  for (uint i = 0; i < arg_list.size(); ++i)
-    EXPECT_EQ(c_args[i], arg_list[i]);
-}
-
-TEST(SessionManagerTestStatic, GetArgLists_InitialDashes) {
-  const char* c_args[] = {"--", "a", "b", "c", NULL};
-  std::vector<std::string> arg_list = GetArgs(c_args);
-  ASSERT_EQ(3, arg_list.size());
-  for (uint i = 0; i < arg_list.size(); ++i)
-    EXPECT_EQ(c_args[i+1], arg_list[i]);
-}
-
-}  // namespace login_manager
diff --git a/util.cc b/util.cc
new file mode 100644
index 0000000..61f7f1d
--- /dev/null
+++ b/util.cc
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/util.h"
+
+#include <pwd.h>
+#include <unistd.h>
+
+#include <base/file_util.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+
+namespace util {
+
+base::FilePath GetReparentedPath(const std::string& path,
+                                 const base::FilePath& parent) {
+  if (parent.empty())
+    return base::FilePath(path);
+
+  CHECK(!path.empty() && path[0] == '/');
+  base::FilePath relative_path(path.substr(1));
+  CHECK(!relative_path.IsAbsolute());
+  return parent.Append(relative_path);
+}
+
+bool SetPermissions(const base::FilePath& path,
+                    uid_t uid,
+                    gid_t gid,
+                    mode_t mode) {
+  if (getuid() == 0) {
+    if (chown(path.value().c_str(), uid, gid) != 0) {
+      PLOG(ERROR) << "Couldn't chown " << path.value() << " to "
+                  << uid << ":" << gid;
+      return false;
+    }
+  }
+  if (chmod(path.value().c_str(), mode) != 0) {
+    PLOG(ERROR) << "Unable to chmod " << path.value() << " to "
+                << std::oct << mode;
+    return false;
+  }
+  return true;
+}
+
+bool EnsureDirectoryExists(const base::FilePath& path,
+                           uid_t uid,
+                           gid_t gid,
+                           mode_t mode) {
+  if (!base::CreateDirectory(path)) {
+    PLOG(ERROR) << "Unable to create " << path.value();
+    return false;
+  }
+  return SetPermissions(path, uid, gid, mode);
+}
+
+bool GetUserInfo(const std::string& user, uid_t* uid, gid_t* gid) {
+  const struct passwd* user_info = getpwnam(user.c_str());
+  endpwent();
+  if (!user_info) {
+    PLOG(ERROR) << "Unable to find user " << user;
+    return false;
+  }
+  if (uid)
+    *uid = user_info->pw_uid;
+  if (gid)
+    *gid = user_info->pw_gid;
+  return true;
+}
+
+}  // namespace util
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..bcbafaf
--- /dev/null
+++ b/util.h
@@ -0,0 +1,42 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <sys/types.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+#ifndef LOGIN_MANAGER_UTIL_H_
+#define LOGIN_MANAGER_UTIL_H_
+
+namespace util {
+
+  // Converts an absolute path |path| into a base::FilePath. If |parent| is
+  // non-empty, |path| is rooted within it. For example,
+  // GetPath("/usr/bin/bar", base::FilePath("/tmp/foo")) returns
+  // base::FilePath("/tmp/foo/usr/bin/bar")).
+base::FilePath GetReparentedPath(const std::string& path,
+                                 const base::FilePath& parent);
+
+// Changes the ownership of |path| to |uid|:|gid| and sets its mode to |mode|.
+// Skips updating ownership when not running as root (for use in tests).
+bool SetPermissions(const base::FilePath& path,
+                    uid_t uid,
+                    gid_t gid,
+                    mode_t mode);
+
+// Ensures that |path| exists with the requested ownership and permissions,
+// creating and/or updating it if needed. Returns true on success.
+bool EnsureDirectoryExists(const base::FilePath& path,
+                           uid_t uid,
+                           gid_t gid,
+                           mode_t mode);
+
+// Looks up the UID and GID corresponding to |user|. Returns true on success.
+bool GetUserInfo(const std::string& user, uid_t* uid, gid_t* gid);
+
+}  // namespace util
+
+#endif  // LOGIN_MANAGER_UTIL_H_
diff --git a/x_server_runner.cc b/x_server_runner.cc
new file mode 100644
index 0000000..ca51520
--- /dev/null
+++ b/x_server_runner.cc
@@ -0,0 +1,285 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/x_server_runner.h"
+
+#include <arpa/inet.h>
+#include <grp.h>
+#include <signal.h>
+#include <sys/resource.h>
+#include <sys/signalfd.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/file_util.h>
+#include <base/files/file.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/process/launch.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+#include "login_manager/util.h"
+
+namespace {
+
+// Path to the X server binary.
+const char kXServerCommand[] = "/usr/bin/X";
+
+// Writes |data| to |file|, returning true on success.
+bool WriteString(base::File* file, const std::string& data) {
+  return file->WriteAtCurrentPos(data.data(), data.size()) ==
+      static_cast<int>(data.size());
+}
+
+// Writes |value| to |file| in big-endian order, returning true on success.
+bool WriteUint16(base::File* file, uint16 value) {
+  value = htons(value);
+  return file->WriteAtCurrentPos(
+      reinterpret_cast<const char*>(&value), sizeof(value)) ==
+      static_cast<int>(sizeof(value));
+}
+
+// Creates a new X authority file at |path|, returning true on success.
+bool CreateXauthFile(const base::FilePath& path, uid_t uid, gid_t gid) {
+  base::File file(path,
+      base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+  if (!file.IsValid()) {
+    PLOG(ERROR) << "Couldn't open " << path.value();
+    return false;
+  }
+  if (!util::SetPermissions(path, uid, gid, 0600))
+    return false;
+
+  const int kCookieSize = 16;
+  // TODO(derat): base/rand_util.h says not to use RandBytesAsString() for
+  // security-related purposes, but crypto::RandBytes() (which we don't package)
+  // just wraps RandBytes(). The base implementation uses /dev/urandom, which is
+  // fine for our purposes (see e.g. http://www.2uo.de/myths-about-urandom/),
+  // but to make this code self-documenting, this call should be changed to
+  // crypto::RandBytes() if/when that gets packaged for Chrome OS.
+  const std::string kCookie(base::RandBytesAsString(kCookieSize));
+  const uint16 kFamily = 0x100;
+  const std::string kAddress = "localhost";
+  const std::string kNumber = "0";
+  const std::string kName = "MIT-MAGIC-COOKIE-1";
+
+  if (!WriteUint16(&file, kFamily) ||
+      !WriteUint16(&file, kAddress.size()) ||
+      !WriteString(&file, kAddress) ||
+      !WriteUint16(&file, kNumber.size()) ||
+      !WriteString(&file, kNumber) ||
+      !WriteUint16(&file, kName.size()) ||
+      !WriteString(&file, kName) ||
+      !WriteUint16(&file, kCookie.size()) ||
+      !WriteString(&file, kCookie)) {
+    PLOG(ERROR) << "Couldn't write to " << path.value();
+    return false;
+  }
+
+  return true;
+}
+
+// Runs the X server, replacing the current process.
+void ExecServer(int vt,
+                int max_vt,
+                const base::FilePath& xauth_file,
+                const base::FilePath& log_file) {
+  std::vector<std::string> args;
+  args.push_back(kXServerCommand);
+  args.push_back("-nohwaccess");
+  args.push_back("-noreset");
+  args.push_back("-maxvt");
+  args.push_back(base::IntToString(max_vt));
+  args.push_back("-nolisten");
+  args.push_back("tcp");
+  args.push_back(base::StringPrintf("vt%d", vt));
+  args.push_back("-auth");
+  args.push_back(xauth_file.value());
+  args.push_back("-logfile");
+  args.push_back(log_file.value());
+
+  const size_t kMaxArgs = 32;
+  char* argv[kMaxArgs + 1];
+  CHECK_LE(args.size(), kMaxArgs);
+  for (size_t i = 0; i < args.size(); ++i)
+    argv[i] = const_cast<char*>(args[i].c_str());
+  argv[args.size()] = NULL;
+
+  // This call doesn't return on success.
+  PCHECK(execv(argv[0], argv) == 0) << "execv() failed";
+}
+
+// Drops privileges, forks-and-execs the X server, waits for it to emit SIGUSR1
+// to indicate that it's ready for connections, and returns true on success.
+bool ExecAndWaitForServer(const std::string& user,
+                          uid_t uid,
+                          gid_t gid,
+                          const base::Closure& closure) {
+  // Avoid some syscalls when not running as root in tests.
+  if (getuid() == 0) {
+    if (setpriority(PRIO_PROCESS, 0, -20) != 0)
+      PLOG(WARNING) << "setpriority() failed";
+
+    PCHECK(initgroups(user.c_str(), gid) == 0);
+    PCHECK(setgid(gid) == 0);
+    PCHECK(setuid(uid) == 0);
+  }
+
+  sigset_t mask;
+  PCHECK(sigemptyset(&mask) == 0);
+  PCHECK(sigaddset(&mask, SIGUSR1) == 0);
+  PCHECK(sigaddset(&mask, SIGCHLD) == 0);
+  const int fd = signalfd(-1, &mask, 0);
+  PCHECK(fd != -1) << "signalfd() failed";
+  PCHECK(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
+
+  switch (pid_t pid = fork()) {
+    case -1:
+      PLOG(ERROR) << "fork() failed";
+      return false;
+    case 0:
+      // Forked process: exec the X server.
+      base::CloseSuperfluousFds(base::InjectiveMultimap());
+
+      // Set SIGUSR1's disposition to SIG_IGN before exec-ing so that X will
+      // emit SIGUSR1 once it's ready to accept connections.
+      PCHECK(signal(SIGUSR1, SIG_IGN) != SIG_ERR);
+      PCHECK(signal(SIGCHLD, SIG_DFL) != SIG_ERR);
+
+      closure.Run();
+      break;
+    default: {
+      // Original process: wait for the forked process to become ready or exit.
+      LOG(INFO) << "X server started with PID " << pid;
+      struct signalfd_siginfo siginfo;
+      while (true) {
+        int bytes_read = HANDLE_EINTR(read(fd, &siginfo, sizeof(siginfo)));
+        PCHECK(bytes_read >= 0);
+        if (bytes_read != sizeof(siginfo)) {
+          LOG(ERROR) << "Read " << bytes_read << " byte(s); expected "
+                     << sizeof(siginfo);
+          close(fd);
+          return false;
+        }
+
+        switch (siginfo.ssi_signo) {
+          case SIGUSR1:
+            LOG(INFO) << "X server is ready for connections";
+            close(fd);
+            return true;
+          case SIGCHLD: {
+            int status = 0;
+            int result = waitpid(pid, &status, WNOHANG);
+            if (result != 0) {
+              PCHECK(result == pid) << "waitpid() returned " << result;
+              if (WIFEXITED(status)) {
+                LOG(ERROR) << "X server exited with " << WEXITSTATUS(status)
+                           << " before sending SIGUSR1";
+                close(fd);
+                return false;
+              } else if (WIFSIGNALED(status)) {
+                LOG(ERROR) << "X server was terminated with signal "
+                           << WTERMSIG(status) << " before sending SIGUSR1";
+                close(fd);
+                return false;
+              }
+            }
+            // Ignore non-exit SIGCHLDs.
+            break;
+          }
+          default:
+            CHECK(false) << "Unexpected signal " << siginfo.ssi_signo;
+        }
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+const char XServerRunner::kSocketDir[] = "/tmp/.X11-unix";
+const char XServerRunner::kIceDir[] = "/tmp/.ICE-unix";
+const char XServerRunner::kLogFile[] = "/var/log/xorg/Xorg.0.log";
+const char XServerRunner::kXkbDir[] = "/var/lib/xkb";
+
+XServerRunner::XServerRunner() : child_pid_(0) {}
+
+XServerRunner::~XServerRunner() {}
+
+bool XServerRunner::StartServer(const std::string& user,
+                                int vt,
+                                bool allow_vt_switching,
+                                const base::FilePath& xauth_file) {
+  uid_t uid = 0;
+  gid_t gid = 0;
+  if (!util::GetUserInfo(user, &uid, &gid))
+    return false;
+
+  if (!CreateXauthFile(xauth_file, uid, gid))
+    return false;
+
+  if (!util::EnsureDirectoryExists(GetPath(kSocketDir), 0, 0, 01777) ||
+      !util::EnsureDirectoryExists(GetPath(kIceDir), 0, 0, 01777))
+    return false;
+
+  const base::FilePath log_file(GetPath(kLogFile));
+  if (!util::EnsureDirectoryExists(log_file.DirName(), uid, gid, 0755) ||
+      !util::EnsureDirectoryExists(GetPath(kXkbDir), uid, gid, 0755))
+    return false;
+
+  // Create a relative symlink from one directory above |log_file| to the file
+  // itself (e.g. /var/log/Xorg.0.log -> xorg/Xorg.0.log).
+  base::CreateSymbolicLink(
+      log_file.DirName().BaseName().Append(log_file.BaseName()),
+      log_file.DirName().DirName().Append(log_file.BaseName()));
+
+  // Disable all the Ctrl-Alt-Fn shortcuts for switching between virtual
+  // terminals if requested. Otherwise, disable only Fn (n>=3) keys.
+  int max_vt = allow_vt_switching ? 2 : 0;
+
+  switch (child_pid_ = fork()) {
+    case -1:
+      PLOG(ERROR) << "fork() failed";
+      return false;
+    case 0: {
+      base::Closure closure = !callback_for_testing_.is_null() ?
+          callback_for_testing_ :
+          base::Bind(&ExecServer, vt, max_vt, xauth_file, log_file);
+      // The child process waits for the server to start and exits with 0.
+      exit(ExecAndWaitForServer(user, uid, gid, closure) ? 0 : 1);
+    }
+    default:
+      LOG(INFO) << "Child process " << child_pid_
+                << " starting X server in background";
+  }
+  return true;
+}
+
+bool XServerRunner::WaitForServer() {
+  CHECK_GT(child_pid_, 0);
+  int status = 0;
+  if (waitpid(child_pid_, &status, 0) != child_pid_) {
+    PLOG(ERROR) << "waitpid() on " << child_pid_ << " failed";
+    return false;
+  }
+  if (!WIFEXITED(status)) {
+    LOG(ERROR) << "Child process " << child_pid_ << " didn't exit normally";
+    return false;
+  }
+  if (WEXITSTATUS(status) != 0) {
+    LOG(ERROR) << "Child process " << child_pid_ << " exited with "
+               << WEXITSTATUS(status);
+    return false;
+  }
+  return true;
+}
+
+base::FilePath XServerRunner::GetPath(const std::string& path) const {
+  return util::GetReparentedPath(path, base_path_for_testing_);
+}
diff --git a/x_server_runner.h b/x_server_runner.h
new file mode 100644
index 0000000..de0d61a
--- /dev/null
+++ b/x_server_runner.h
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium OS 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 LOGIN_MANAGER_X_SERVER_RUNNER_H_
+#define LOGIN_MANAGER_X_SERVER_RUNNER_H_
+
+#include <sys/types.h>
+
+#include <string>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+// XServerRunner can be used to start the X server asynchronously and later
+// block until the server is ready to accept connections from clients.
+class XServerRunner {
+ public:
+  // Various hard-coded paths exposed here for tests.
+  static const char kSocketDir[];
+  static const char kIceDir[];
+  static const char kLogFile[];
+  static const char kXkbDir[];
+
+  XServerRunner();
+  ~XServerRunner();
+
+  void set_base_path_for_testing(const base::FilePath& path) {
+    base_path_for_testing_ = path;
+  }
+  void set_callback_for_testing(const base::Closure& callback) {
+    callback_for_testing_ = callback;
+  }
+
+  // Creates necessary directories and starts the X server in the background
+  // running as |user| on |vt|. |xauth_file| will be created to permit
+  // connections to the server. Returns true if the setup was successful and the
+  // child process that starts the server was forked sucessfully.
+  bool StartServer(const std::string& user,
+                   int vt,
+                   bool allow_vt_switching,
+                   const base::FilePath& xauth_file);
+
+  // Blocks until the previously-started X server is ready to accept
+  // connections.
+  bool WaitForServer();
+
+ private:
+  // Converts absolute path |path| into a base::FilePath, rooting it under
+  // |base_path_for_testing_| if it's non-empty.
+  base::FilePath GetPath(const std::string& path) const;
+
+  // Path under which files are created when running in a test.
+  base::FilePath base_path_for_testing_;
+
+  // If non-null, run instead of actually starting the X server.
+  base::Closure callback_for_testing_;
+
+  // PID of the child process that will exit once the X server is ready to
+  // accept connections.
+  pid_t child_pid_;
+
+  DISALLOW_COPY_AND_ASSIGN(XServerRunner);
+};
+
+#endif  // LOGIN_MANAGER_X_SERVER_RUNNER_H_
diff --git a/x_server_runner_unittest.cc b/x_server_runner_unittest.cc
new file mode 100644
index 0000000..5d6c82b
--- /dev/null
+++ b/x_server_runner_unittest.cc
@@ -0,0 +1,193 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "login_manager/x_server_runner.h"
+
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/macros.h>
+#include <base/files/file_path.h>
+#include <base/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/threading/platform_thread.h>
+#include <gtest/gtest.h>
+
+#include "login_manager/util.h"
+
+namespace {
+
+// Passed to XServerRunner as a callback that should be run instead of actually
+// starting the X server. Writes the current process's PID to |pipe_path|. If
+// |exit_delay| is non-zero, sleeps and exits without sending SIGUSR1.
+// Otherwise, sleeps for |signal_delay|, sends SIGUSR1 to its parent process,
+// and then sleeps for a long time.
+void ExecServer(const base::FilePath& pipe_path,
+                const base::TimeDelta& signal_delay,
+                const base::TimeDelta& exit_delay) {
+  // Write our PID so the test (our grandparent process) can clean us up.
+  pid_t pid = getpid();
+  PCHECK(base::WriteFile(pipe_path, reinterpret_cast<const char*>(&pid),
+                         sizeof(pid)) == sizeof(pid));
+
+  if (exit_delay > base::TimeDelta()) {
+    base::PlatformThread::Sleep(exit_delay);
+    exit(1);
+  }
+
+  if (signal_delay > base::TimeDelta())
+    base::PlatformThread::Sleep(signal_delay);
+  PCHECK(kill(getppid(), SIGUSR1) == 0);
+
+  base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(60));
+}
+
+}  // namespace
+
+class XServerRunnerTest : public testing::Test {
+ public:
+  XServerRunnerTest() : server_pid_(0) {
+    CHECK(temp_dir_.CreateUniqueTempDir());
+    base_path_ = temp_dir_.path();
+    runner_.set_base_path_for_testing(base_path_);
+    xauth_path_ = base_path_.Append("xauth");
+  }
+  virtual ~XServerRunnerTest() {}
+
+  // Calls StartServer(). See ExecServer() for descriptions of the arguments.
+  void StartServer(const base::TimeDelta& signal_delay,
+                   const base::TimeDelta& exit_delay) {
+    // Named pipe used by ExecServer() to pass its PID back to the test process.
+    base::FilePath pipe_path = base_path_.Append("pipe");
+    PCHECK(mkfifo(pipe_path.value().c_str(), 0600) == 0);
+
+    runner_.set_callback_for_testing(
+        base::Bind(&ExecServer, pipe_path, signal_delay, exit_delay));
+    ASSERT_TRUE(runner_.StartServer(
+        getpwuid(getuid())->pw_name, 1, false, xauth_path_));
+
+    // Open the pipe and read ExecServer()'s PID.
+    int pipe_fd = open(pipe_path.value().c_str(), O_RDONLY);
+    PCHECK(pipe_fd >= 0) << "Failed to open " << pipe_path.value();
+    PCHECK(HANDLE_EINTR(read(pipe_fd, &server_pid_, sizeof(server_pid_))) ==
+           sizeof(server_pid_));
+    close(pipe_fd);
+  }
+
+  // Calls WaitForServer() and returns its result. If it returns true (i.e. the
+  // X server process sent SIGUSR1), additionally kills the process before
+  // returning.
+  bool WaitForServer() {
+    // No need to kill the process if it already exited on its own.
+    if (!runner_.WaitForServer())
+      return false;
+
+    LOG(INFO) << "Killing server process " << server_pid_;
+    kill(server_pid_, SIGTERM);
+    return true;
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+  base::FilePath base_path_;
+  base::FilePath xauth_path_;
+
+  XServerRunner runner_;
+
+  // PID of the process running ExecServer().
+  pid_t server_pid_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(XServerRunnerTest);
+};
+
+TEST_F(XServerRunnerTest, FastSuccess) {
+  StartServer(base::TimeDelta(), base::TimeDelta());
+  EXPECT_TRUE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, SlowSuccess) {
+  StartServer(base::TimeDelta::FromSeconds(1), base::TimeDelta());
+  EXPECT_TRUE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, FastCrash) {
+  StartServer(base::TimeDelta(), base::TimeDelta::FromMicroseconds(1));
+  EXPECT_FALSE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, SlowCrash) {
+  StartServer(base::TimeDelta(), base::TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, TermServer) {
+  StartServer(base::TimeDelta::FromSeconds(60), base::TimeDelta());
+  PCHECK(kill(server_pid_, SIGTERM) == 0);
+  EXPECT_FALSE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, StopAndContinueServer) {
+  // Test that SIGCHLD signals that are sent in response to the process being
+  // stopped or continued are ignored.
+  StartServer(base::TimeDelta::FromSeconds(1), base::TimeDelta());
+  PCHECK(kill(server_pid_, SIGSTOP) == 0);
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
+  PCHECK(kill(server_pid_, SIGCONT) == 0);
+  EXPECT_TRUE(WaitForServer());
+}
+
+TEST_F(XServerRunnerTest, XauthFile) {
+  StartServer(base::TimeDelta(), base::TimeDelta());
+  EXPECT_TRUE(WaitForServer());
+
+  std::string data;
+  ASSERT_TRUE(base::ReadFileToString(xauth_path_, &data));
+
+  const char kExpected[] =
+      "\x01" "\x00"
+      "\x00" "\x09" "localhost"
+      "\x00" "\x01" "0"
+      "\x00" "\x12" "MIT-MAGIC-COOKIE-1"
+      "\x00" "\x10" /* random 16-byte cookie data goes here */;
+  const size_t kExpectedSize = arraysize(kExpected) - 1;
+  const size_t kCookieSize = 16;
+
+  ASSERT_EQ(kExpectedSize + kCookieSize, data.size());
+  EXPECT_EQ(base::HexEncode(kExpected, kExpectedSize),
+            base::HexEncode(data.data(), kExpectedSize));
+}
+
+TEST_F(XServerRunnerTest, CreateDirectories) {
+  StartServer(base::TimeDelta(), base::TimeDelta());
+  EXPECT_TRUE(WaitForServer());
+
+  EXPECT_TRUE(base::DirectoryExists(util::GetReparentedPath(
+      XServerRunner::kSocketDir, base_path_)));
+  EXPECT_TRUE(base::DirectoryExists(util::GetReparentedPath(
+      XServerRunner::kIceDir, base_path_)));
+  EXPECT_TRUE(base::DirectoryExists(util::GetReparentedPath(
+      XServerRunner::kXkbDir, base_path_)));
+
+  base::FilePath log_file(util::GetReparentedPath(
+      XServerRunner::kLogFile, base_path_));
+  base::FilePath log_dir(log_file.DirName());
+  EXPECT_TRUE(base::DirectoryExists(log_dir));
+
+  // Check that a relative symlink is created in the directory above the one
+  // where the log file is written.
+  base::FilePath link;
+  EXPECT_TRUE(base::ReadSymbolicLink(
+      log_dir.DirName().Append(log_file.BaseName()), &link));
+  EXPECT_EQ(log_dir.BaseName().Append(log_file.BaseName()).value(),
+            link.value());
+}
diff --git a/xstart.sh b/xstart.sh
deleted file mode 100644
index 2c00591..0000000
--- a/xstart.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-
-# Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-XUSER="${1}"
-VT="${2}"
-XAUTH_FILE="${3}"
-
-cros-xauth ${XAUTH_FILE} && chown ${XUSER}:${XUSER} ${XAUTH_FILE}
-
-# Set permissions for logfile
-mkdir -p /var/log/xorg && chown ${XUSER}:${XUSER} /var/log/xorg
-ln -sf xorg/Xorg.0.log /var/log/Xorg.0.log
-
-# Disables all the Ctrl-Alt-Fn shortcuts for switching between virtual terminals
-# if we're in verified boot mode. Otherwise, disables only Fn (n>=3) keys.
-MAXVT=0
-if is_developer_end_user; then
-  # developer end-user
-  MAXVT=2
-fi
-
-# The X server sends SIGUSR1 to its parent once it's ready to accept
-# connections -- but only if its own process has inherited SIG_IGN for the
-# SIGUSR1 handler.  So we:
-# 1. Start a shell through sudo that waits for SIGUSR1.
-# 2. This shell starts another subshell which clears its own handler for (among
-#    other things) SIGUSR1, then calls exec on the actual X binary.
-# 3. When X is ready to accept connections, it sends SIGUSR1, which is caught
-#    by the outer shell.  Note that both X and the parent shell are running
-#    with the same UID through the sudo session -- this is important to allow
-#    SIGUSR1 to propagate.
-# 4. The script then terminates, leaving X running.
-
-# The '-noreset' works around an apparent bug in the X server that
-# makes us fail to boot sometimes; see http://crosbug.com/7578.
-nice -n -20 sudo -u ${XUSER} sh -c "
-  trap 'exit 0' USR1
-  ( trap '' USR1 TTOU TTIN
-    exec /usr/bin/X -nohwaccess -noreset -maxvt ${MAXVT} -nolisten tcp vt${VT} \
-      -auth ${XAUTH_FILE} -logfile /var/log/xorg/Xorg.0.log 2> /dev/null ) &
-  wait"