You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by id...@apache.org on 2015/07/08 00:42:18 UTC

[1/3] mesos git commit: Support chrooting in MesosContainerizer launch helper.

Repository: mesos
Updated Branches:
  refs/heads/master a16be70ef -> 15bf9f67e


Support chrooting in MesosContainerizer launch helper.

Review: https://reviews.apache.org/r/31444/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/15bf9f67
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/15bf9f67
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/15bf9f67

Branch: refs/heads/master
Commit: 15bf9f67e3d9489e49b2176e1e4221a1a47fd0c9
Parents: 6950310
Author: Ian Downes <id...@twitter.com>
Authored: Thu Dec 18 15:46:15 2014 -0800
Committer: Ian Downes <id...@twitter.com>
Committed: Tue Jul 7 15:39:52 2015 -0700

----------------------------------------------------------------------
 src/Makefile.am                          |   1 +
 src/linux/fs.cpp                         |   5 +-
 src/slave/containerizer/mesos/launch.cpp |  60 ++++++-
 src/slave/containerizer/mesos/launch.hpp |   1 +
 src/tests/launch_tests.cpp               | 238 ++++++++++++++++++++++++++
 5 files changed, 296 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/15bf9f67/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 37aa878..e5b5d36 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1542,6 +1542,7 @@ if OS_LINUX
   mesos_tests_SOURCES += tests/cgroups_isolator_tests.cpp
   mesos_tests_SOURCES += tests/cgroups_tests.cpp
   mesos_tests_SOURCES += tests/fs_tests.cpp
+  mesos_tests_SOURCES += tests/launch_tests.cpp
   mesos_tests_SOURCES += tests/memory_pressure_tests.cpp
   mesos_tests_SOURCES += tests/ns_tests.cpp
   mesos_tests_SOURCES += tests/perf_tests.cpp

http://git-wip-us.apache.org/repos/asf/mesos/blob/15bf9f67/src/linux/fs.cpp
----------------------------------------------------------------------
diff --git a/src/linux/fs.cpp b/src/linux/fs.cpp
index 6d5af25..ea0891e 100644
--- a/src/linux/fs.cpp
+++ b/src/linux/fs.cpp
@@ -83,7 +83,7 @@ Try<MountInfoTable::Entry> MountInfoTable::Entry::parse(const string& s)
 
   // First group of fields (before the separator): 6 required fields
   // then zero or more optional fields
-  std::vector<string> tokens = strings::tokenize(s.substr(0, pos), " ");
+  vector<string> tokens = strings::tokenize(s.substr(0, pos), " ");
   if (tokens.size() < 6) {
     return Error("Failed to parse entry");
   }
@@ -101,7 +101,7 @@ Try<MountInfoTable::Entry> MountInfoTable::Entry::parse(const string& s)
   entry.parent = parent.get();
 
   // Parse out the major:minor device number.
-  std::vector<string> device = strings::split(tokens[2], ":");
+  vector<string> device = strings::split(tokens[2], ":");
   if (device.size() != 2) {
     return Error("Invalid major:minor device number");
   }
@@ -502,6 +502,7 @@ Try<Nothing> createStandardDevices(const string& root)
 
 } // namespace internal {
 
+
 // TODO(idownes): Add unit test.
 Try<Nothing> enter(const string& root)
 {

http://git-wip-us.apache.org/repos/asf/mesos/blob/15bf9f67/src/slave/containerizer/mesos/launch.cpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/launch.cpp b/src/slave/containerizer/mesos/launch.cpp
index 51bc068..96ca47d 100644
--- a/src/slave/containerizer/mesos/launch.cpp
+++ b/src/slave/containerizer/mesos/launch.cpp
@@ -26,11 +26,16 @@
 #include <stout/protobuf.hpp>
 #include <stout/unreachable.hpp>
 
+#ifdef __linux__
+#include "linux/fs.hpp"
+#endif
+
 #include "mesos/mesos.hpp"
 
 #include "slave/containerizer/mesos/launch.hpp"
 
 using std::cerr;
+using std::cout;
 using std::endl;
 using std::string;
 
@@ -49,7 +54,15 @@ MesosContainerizerLaunch::Flags::Flags()
 
   add(&directory,
       "directory",
-      "The directory to chdir to.");
+      "The directory to chdir to. If rootfs is specified this must\n"
+      "be relative to the new root.");
+
+  add(&rootfs,
+      "rootfs",
+      "Absolute path to the container root filesystem.\n"
+      "The command and directory flags are interpreted relative\n"
+      "to rootfs\n"
+      "Different platforms may implement 'chroot' differently.");
 
   add(&user,
       "user",
@@ -193,17 +206,41 @@ int MesosContainerizerLaunch::execute()
     }
   }
 
-  // Enter working directory.
-  Try<Nothing> chdir = os::chdir(flags.directory.get());
-  if (chdir.isError()) {
-    cerr << "Failed to chdir into work directory '"
-         << flags.directory.get() << "': " << chdir.error() << endl;
-    return 1;
+  // Change root to a new root, if provided.
+  if (flags.rootfs.isSome()) {
+    cout << "Changing root to " << flags.rootfs.get() << endl;
+
+    // Verify that rootfs is an absolute path.
+    Result<string> realpath = os::realpath(flags.rootfs.get());
+    if (realpath.isError()) {
+      cerr << "Failed to determine if rootfs is an absolute path: "
+           << realpath.error() << endl;
+      return 1;
+    } else if (realpath.isNone()) {
+      cerr << "Rootfs path does not exist" << endl;
+      return 1;
+    } else if (realpath.get() != flags.rootfs.get()) {
+      cerr << "Rootfs path is not an absolute path" << endl;
+      return 1;
+    }
+
+#ifdef __linux__
+    Try<Nothing> chroot = fs::chroot::enter(flags.rootfs.get());
+#else // For any other platform we'll just use POSIX chroot.
+    Try<Nothing> chroot = os::chroot(flags.rootfs.get());
+#endif // __linux__
+    if (chroot.isError()) {
+      cerr << "Failed to enter chroot '" << flags.rootfs.get()
+           << "': " << chroot.error();
+      return 1;
+    }
   }
 
   // Change user if provided. Note that we do that after executing the
   // preparation commands so that those commands will be run with the
   // same privilege as the mesos-slave.
+  // NOTE: The requisite user/group information must be present if
+  // a container root filesystem is used.
   if (flags.user.isSome()) {
     Try<Nothing> su = os::su(flags.user.get());
     if (su.isError()) {
@@ -213,6 +250,15 @@ int MesosContainerizerLaunch::execute()
     }
   }
 
+  // Enter working directory, relative to the new root.
+  Try<Nothing> chdir = os::chdir(flags.directory.get());
+  if (chdir.isError()) {
+    cerr << "Failed to chdir into work directory '"
+         << flags.directory.get() << "': " << chdir.error() << endl;
+    return 1;
+  }
+
+  // Relay the environment variables.
   // TODO(jieyu): Consider using a clean environment.
 
   if (command.get().shell()) {

http://git-wip-us.apache.org/repos/asf/mesos/blob/15bf9f67/src/slave/containerizer/mesos/launch.hpp
----------------------------------------------------------------------
diff --git a/src/slave/containerizer/mesos/launch.hpp b/src/slave/containerizer/mesos/launch.hpp
index 7c8b535..7a7f3d7 100644
--- a/src/slave/containerizer/mesos/launch.hpp
+++ b/src/slave/containerizer/mesos/launch.hpp
@@ -38,6 +38,7 @@ public:
 
     Option<JSON::Object> command;
     Option<std::string> directory;
+    Option<std::string> rootfs;
     Option<std::string> user;
     Option<int> pipe_read;
     Option<int> pipe_write;

http://git-wip-us.apache.org/repos/asf/mesos/blob/15bf9f67/src/tests/launch_tests.cpp
----------------------------------------------------------------------
diff --git a/src/tests/launch_tests.cpp b/src/tests/launch_tests.cpp
new file mode 100644
index 0000000..73c8c5f
--- /dev/null
+++ b/src/tests/launch_tests.cpp
@@ -0,0 +1,238 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <gmock/gmock.h>
+
+#include <stout/foreach.hpp>
+#include <stout/gtest.hpp>
+#include <stout/os.hpp>
+#include <stout/try.hpp>
+
+#include <process/gtest.hpp>
+#include <process/io.hpp>
+#include <process/reap.hpp>
+#include <process/subprocess.hpp>
+
+#include "mesos/resources.hpp"
+
+#include "slave/containerizer/mesos/launch.hpp"
+
+#include "linux/fs.hpp"
+
+#include "tests/flags.hpp"
+#include "tests/utils.hpp"
+
+using namespace process;
+
+using std::string;
+using std::vector;
+
+namespace mesos {
+namespace internal {
+namespace tests {
+
+class Chroot
+{
+public:
+  Chroot(const string& _rootfs)
+    : rootfs(_rootfs) {}
+
+  virtual ~Chroot() {}
+
+  virtual Try<Subprocess> run(const string& command) = 0;
+
+  const string rootfs;
+};
+
+
+class BasicLinuxChroot : public Chroot
+{
+public:
+  static Try<Owned<Chroot>> create(const string& rootfs)
+  {
+    if (!os::exists(rootfs)) {
+      return Error("rootfs does not exist");
+    }
+
+    if (os::system("cp -r /bin " + rootfs + "/") != 0) {
+      return ErrnoError("Failed to copy /bin to chroot");
+    }
+
+    if (os::system("cp -r /lib " + rootfs + "/") != 0) {
+      return ErrnoError("Failed to copy /lib to chroot");
+    }
+
+    if (os::system("cp -r /lib64 " + rootfs + "/") != 0) {
+      return ErrnoError("Failed to copy /lib64 to chroot");
+    }
+
+    vector<string> directories = {"proc", "sys", "dev", "tmp"};
+    foreach (const string& directory, directories) {
+      Try<Nothing> mkdir = os::mkdir(path::join(rootfs, directory));
+      if (mkdir.isError()) {
+        return Error("Failed to create /" + directory + " in chroot: " +
+                     mkdir.error());
+      }
+    }
+
+    // We need to bind mount the rootfs so we can pivot on it.
+    Try<Nothing> mount =
+      fs::mount(rootfs, rootfs, None(), MS_BIND | MS_SLAVE, NULL);
+
+    if (mount.isError()) {
+      return Error("Failed to bind mount chroot rootfs: " + mount.error());
+    }
+
+    return Owned<Chroot>(new BasicLinuxChroot(rootfs));
+  }
+
+  virtual Try<Subprocess> run(const string& _command)
+  {
+    slave::MesosContainerizerLaunch::Flags launchFlags;
+
+    CommandInfo command;
+    command.set_value(_command);
+
+    launchFlags.command = JSON::Protobuf(command);
+    launchFlags.directory = "/tmp";
+    launchFlags.pipe_read = open("/dev/zero", O_RDONLY);
+    launchFlags.pipe_write = open("/dev/null", O_WRONLY);
+    launchFlags.rootfs = rootfs;
+
+    vector<string> argv(2);
+    argv[0] = "mesos-containerizer";
+    argv[1] = slave::MesosContainerizerLaunch::NAME;
+
+    Try<Subprocess> s = subprocess(
+        path::join(tests::flags.build_dir, "src", "mesos-containerizer"),
+        argv,
+        Subprocess::PATH("/dev/null"),
+        Subprocess::PIPE(),
+        Subprocess::FD(STDERR_FILENO),
+        launchFlags,
+        None(),
+        None(),
+        lambda::bind(&clone, lambda::_1));
+
+    if (s.isError()) {
+      close(launchFlags.pipe_read.get());
+      close(launchFlags.pipe_write.get());
+    } else {
+      s.get().status().onAny([=]() {
+        // Close when the subprocess terminates.
+        close(launchFlags.pipe_read.get());
+        close(launchFlags.pipe_write.get());
+      });
+    }
+
+    return s;
+  }
+
+private:
+  static pid_t clone(const lambda::function<int()>& f)
+  {
+    static unsigned long long stack[(8*1024*1024)/sizeof(unsigned long long)];
+
+    return ::clone(
+        _clone,
+        &stack[sizeof(stack)/sizeof(stack[0]) - 1],  // Stack grows down.
+        CLONE_NEWNS | SIGCHLD,   // Specify SIGCHLD as child termination signal.
+        (void*) &f);
+  }
+
+  static int _clone(void* f)
+  {
+    const lambda::function<int()>* _f =
+      static_cast<const lambda::function<int()>*> (f);
+
+    return (*_f)();
+  }
+
+  BasicLinuxChroot(const string& rootfs) : Chroot(rootfs) {}
+
+  ~BasicLinuxChroot()
+  {
+    // Because the test process has the rootfs as its cwd the umount
+    // won't actually happen until the
+    // TemporaryDirectoryTest::TearDown() changes back to the original
+    // directory.
+    fs::unmount(rootfs, MNT_DETACH);
+  }
+};
+
+
+template <typename T>
+class LaunchChrootTest : public TemporaryDirectoryTest {};
+
+
+// TODO(idownes): Add tests for OSX chroots.
+typedef ::testing::Types<BasicLinuxChroot> ChrootTypes;
+
+
+TYPED_TEST_CASE(LaunchChrootTest, ChrootTypes);
+
+
+TYPED_TEST(LaunchChrootTest, ROOT_DifferentRoot)
+{
+  Try<Owned<Chroot>> chroot = TypeParam::create(os::getcwd());
+  ASSERT_SOME(chroot);
+
+  // Add /usr/bin/stat into the chroot.
+  const string usrbin = path::join(chroot.get()->rootfs, "usr", "bin");
+  ASSERT_SOME(os::mkdir(usrbin));
+  ASSERT_EQ(0, os::system("cp /usr/bin/stat " + path::join(usrbin, "stat")));
+
+  Clock::pause();
+
+  Try<Subprocess> s = chroot.get()->run(
+      "/usr/bin/stat -c %i / >" + path::join("/", "stat.output"));
+
+  CHECK_SOME(s);
+
+  // Advance time until the internal reaper reaps the subprocess.
+  while (s.get().status().isPending()) {
+    Clock::advance(Seconds(1));
+    Clock::settle();
+  }
+
+  AWAIT_ASSERT_READY(s.get().status());
+  ASSERT_SOME(s.get().status().get());
+
+  int status = s.get().status().get().get();
+  ASSERT_TRUE(WIFEXITED(status));
+  ASSERT_EQ(0, WEXITSTATUS(status));
+
+  // Check the chroot has a different root by comparing the inodes.
+  Try<ino_t> self = os::stat::inode("/");
+  ASSERT_SOME(self);
+
+  Try<string> read = os::read(path::join(chroot.get()->rootfs, "stat.output"));
+  CHECK_SOME(read);
+
+  Try<ino_t> other = numify<ino_t>(strings::trim(read.get()));
+  ASSERT_SOME(other);
+
+  EXPECT_NE(self.get(), other.get());
+}
+
+} // namespace tests {
+} // namespace internal {
+} // namespace mesos {


[3/3] mesos git commit: Define all MS_* mount flags.

Posted by id...@apache.org.
Define all MS_* mount flags.

Review: https://reviews.apache.org/r/35033/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/828806c6
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/828806c6
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/828806c6

Branch: refs/heads/master
Commit: 828806c6dac43e3dfa325df1d2fcf7e6945a28f1
Parents: a16be70
Author: Ian Downes <id...@twitter.com>
Authored: Wed Jun 3 15:29:25 2015 -0700
Committer: Ian Downes <id...@twitter.com>
Committed: Tue Jul 7 15:39:52 2015 -0700

----------------------------------------------------------------------
 src/linux/fs.hpp | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/828806c6/src/linux/fs.hpp
----------------------------------------------------------------------
diff --git a/src/linux/fs.hpp b/src/linux/fs.hpp
index d7832a4..bcb0a1b 100644
--- a/src/linux/fs.hpp
+++ b/src/linux/fs.hpp
@@ -32,6 +32,123 @@
 #include <stout/option.hpp>
 #include <stout/try.hpp>
 
+// Define relevant MS_* flags for old includes.
+// This is taken from the enum in sys/mount.h.
+#ifndef MS_RDONLY
+#define MS_RDONLY 1
+#endif
+
+#ifndef MS_NOSUID
+#define MS_NOSUID 2
+#endif
+
+#ifndef MS_NODEV
+#define MS_NODEV 4
+#endif
+
+#ifndef MS_NOEXEC
+#define MS_NOEXEC 8
+#endif
+
+#ifndef MS_SYNCHRONOUS
+#define MS_SYNCHRONOUS 16
+#endif
+
+#ifndef MS_REMOUNT
+#define MS_REMOUNT 32
+#endif
+
+#ifndef MS_MANDLOCK
+#define MS_MANDLOCK 64
+#endif
+
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC 128
+#endif
+
+#ifndef MS_NOATIME
+#define MS_NOATIME 1024
+#endif
+
+#ifndef MS_NODIRATIME
+#define MS_NODIRATIME 2048
+#endif
+
+#ifndef MS_BIND
+#define MS_BIND 4096
+#endif
+
+#ifndef MS_MOVE
+#define MS_MOVE 8192
+#endif
+
+#ifndef MS_REC
+#define MS_REC 16384
+#endif
+
+#ifndef MS_SILENT
+#define MS_SILENT 32768
+#endif
+
+#ifndef MS_POSIXACL
+#define MS_POSIXACL (1 << 16)
+#endif
+
+#ifndef MS_UNBINDABLE
+#define MS_UNBINDABLE (1 << 17)
+#endif
+
+#ifndef MS_PRIVATE
+#define MS_PRIVATE (1 << 18)
+#endif
+
+#ifndef MS_SLAVE
+#define MS_SLAVE (1 << 19)
+#endif
+
+#ifndef MS_SHARED
+#define MS_SHARED (1 << 20)
+#endif
+
+#ifndef MS_RELATIME
+#define MS_RELATIME (1 << 21)
+#endif
+
+#ifndef MS_KERNMOUNT
+#define MS_KERNMOUNT (1 << 22)
+#endif
+
+#ifndef MS_I_VERSION
+#define MS_I_VERSION (1 << 23)
+#endif
+
+#ifndef MS_STRICTATIME
+#define MS_STRICTATIME (1 << 24)
+#endif
+
+#ifndef MS_ACTIVE
+#define MS_ACTIVE (1 << 30)
+#endif
+
+#ifndef MS_NOUSER
+#define MS_NOUSER (1 << 31)
+#endif
+
+#ifndef MNT_FORCE
+#define MNT_FORCE 1
+#endif
+
+#ifndef MNT_DETACH
+#define MNT_DETACH 2
+#endif
+
+#ifndef MNT_EXPIRE
+#define MNT_EXPIRE 4
+#endif
+
+#ifndef UMOUNT_NOFOLLOW
+#define UMOUNT_NOFOLLOW 8
+#endif
 
 namespace mesos {
 namespace internal {


[2/3] mesos git commit: Support for entering and configuring a Linux chroot.

Posted by id...@apache.org.
Support for entering and configuring a Linux chroot.

Review: https://reviews.apache.org/r/32891/


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/6950310f
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/6950310f
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/6950310f

Branch: refs/heads/master
Commit: 6950310f75ab1a5235e1ae03578797f0a01b2a5e
Parents: 828806c
Author: Ian Downes <id...@twitter.com>
Authored: Mon Apr 6 10:16:38 2015 -0700
Committer: Ian Downes <id...@twitter.com>
Committed: Tue Jul 7 15:39:52 2015 -0700

----------------------------------------------------------------------
 src/linux/fs.cpp | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/linux/fs.hpp |  18 ++++
 2 files changed, 288 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/6950310f/src/linux/fs.cpp
----------------------------------------------------------------------
diff --git a/src/linux/fs.cpp b/src/linux/fs.cpp
index 568565f..6d5af25 100644
--- a/src/linux/fs.cpp
+++ b/src/linux/fs.cpp
@@ -28,12 +28,16 @@
 #include <stout/strings.hpp>
 #include <stout/synchronized.hpp>
 
+#include <stout/fs.hpp>
+#include <stout/os.hpp>
+
 #include <stout/os/read.hpp>
 #include <stout/os/stat.hpp>
 
 #include "linux/fs.hpp"
 
 using std::string;
+using std::vector;
 
 namespace mesos {
 namespace internal {
@@ -278,6 +282,21 @@ Try<Nothing> mount(const Option<string>& source,
 }
 
 
+Try<Nothing> mount(const Option<string>& source,
+                   const string& target,
+                   const Option<string>& type,
+                   unsigned long flags,
+                   const Option<string>& options)
+{
+  return mount(
+      source,
+      target,
+      type,
+      flags,
+      options.isSome() ? options.get().c_str() : NULL);
+}
+
+
 Try<Nothing> unmount(const string& target, int flags)
 {
   // The prototype of function 'umount2' on Linux is as follows:
@@ -334,6 +353,257 @@ Try<Nothing> pivot_root(
 }
 
 
+namespace chroot {
+
+namespace internal {
+
+Try<Nothing> copyDeviceNode(const string& source, const string& target)
+{
+  // We are likely to be operating in a multi-threaded environment so
+  // it's not safe to change the umask. Instead, we'll explicitly set
+  // permissions after we create the device node.
+  Try<mode_t> mode = os::stat::mode(source);
+  if (mode.isError()) {
+    return Error("Failed to source mode: " + mode.error());
+  }
+
+  Try<dev_t> dev = os::stat::rdev(source);
+  if (dev.isError()) {
+    return Error("Failed to get source dev: " + dev.error());
+  }
+
+  Try<Nothing> mknod = os::mknod(target, mode.get(), dev.get());
+  if (mknod.isError()) {
+    return Error("Failed to create device:" +  mknod.error());
+  }
+
+  Try<Nothing> chmod = os::chmod(target, mode.get());
+  if (chmod.isError()) {
+    return Error("Failed to chmod device: " + chmod.error());
+  }
+
+  return Nothing();
+}
+
+
+// Some helpful types.
+struct Mount
+{
+  Option<string> source;
+  string target;
+  Option<string> type;
+  Option<string> options;
+  unsigned long flags;
+};
+
+struct SymLink
+{
+  string original;
+  string link;
+};
+
+
+Try<Nothing> mountSpecialFilesystems(const string& root)
+{
+  // List of special filesystems useful for a chroot environment.
+  // NOTE: This list is ordered, e.g., mount /proc before bind
+  // mounting /proc/sys and then making it read-only.
+  vector<Mount> mounts = {
+    {"proc",      "/proc",     "proc",   None(),      MS_NOSUID | MS_NOEXEC | MS_NODEV},             // NOLINT(whitespace/line_length)
+    {"/proc/sys", "/proc/sys", None(),   None(),      MS_BIND},
+    {None(),      "/proc/sys", None(),   None(),      MS_BIND | MS_RDONLY | MS_REMOUNT},             // NOLINT(whitespace/line_length)
+    {"sysfs",     "/sys",      "sysfs",  None(),      MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV}, // NOLINT(whitespace/line_length)
+    {"tmpfs",     "/dev",      "tmpfs",  "mode=755",  MS_NOSUID | MS_STRICTATIME},                   // NOLINT(whitespace/line_length)
+    {"devpts",    "/dev/pts",  "devpts", "newinstance,ptmxmode=0666", MS_NOSUID | MS_NOEXEC},        // NOLINT(whitespace/line_length)
+    {"tmpfs",     "/dev/shm",  "tmpfs",  "mode=1777", MS_NOSUID | MS_NODEV | MS_STRICTATIME},        // NOLINT(whitespace/line_length)
+  };
+
+  foreach (const Mount& mount, mounts) {
+    // Target is always under the new root.
+    const string target = path::join(root, mount.target);
+
+    // Try to create the mount point, if it doesn't already exist.
+    if (!os::exists(target)) {
+      Try<Nothing> mkdir = os::mkdir(target);
+
+      if (mkdir.isError()) {
+        return Error("Failed to create mount point '" + target +
+                     "': " + mkdir.error());
+      }
+    }
+
+    // If source is a path, e.g,. for a bind mount, then it needs to
+    // be prefixed by the new root.
+    Option<string> source;
+    if (mount.source.isSome() && strings::startsWith(mount.source.get(), "/")) {
+      source = path::join(root, mount.source.get());
+    } else {
+      source = mount.source;
+    }
+
+    Try<Nothing> mnt = fs::mount(
+        source,
+        target,
+        mount.type,
+        mount.flags,
+        mount.options);
+
+    if (mnt.isError()) {
+      return Error("Failed to mount '" + target + "': " + mnt.error());
+    }
+  }
+
+  return Nothing();
+}
+
+
+Try<Nothing> createStandardDevices(const string& root)
+{
+  // List of standard devices useful for a chroot environment.
+  // TODO(idownes): Make this list configurable.
+  vector<string> devices = {
+    "full",
+    "null",
+    "random",
+    "tty",
+    "urandom",
+    "zero"
+  };
+
+  foreach (const string& device, devices) {
+    // Copy the mode and device from the corresponding host device.
+    Try<Nothing> copy = copyDeviceNode(
+        path::join("/",  "dev", device),
+        path::join(root, "dev", device));
+
+    if (copy.isError()) {
+      return Error("Failed to copy device '" + device + "': " + copy.error());
+    }
+  }
+
+  vector<SymLink> symlinks = {
+    {"/proc/self/fd0", path::join(root, "dev", "stdin")},
+    {"/proc/self/fd1", path::join(root, "dev", "stdout")},
+    {"/proc/self/fd2", path::join(root, "dev", "stderr")},
+    {"pts/ptmx",       path::join(root, "dev", "ptmx")}
+  };
+
+  foreach (const SymLink& symlink, symlinks) {
+    Try<Nothing> link = ::fs::symlink(symlink.original, symlink.link);
+    if (link.isError()) {
+      return Error("Failed to symlink '" + symlink.original +
+                   "' to '" + symlink.link + "': " + link.error());
+    }
+  }
+
+  // TODO(idownes): Set up console device.
+  return Nothing();
+}
+
+} // namespace internal {
+
+// TODO(idownes): Add unit test.
+Try<Nothing> enter(const string& root)
+{
+  // Recursively mark current mounts as slaves to prevent propagation.
+  Try<Nothing> mount = fs::mount(None(), "/", None(), MS_REC | MS_SLAVE, NULL);
+  if (mount.isError()) {
+    return Error("Failed to make slave mounts: " + mount.error());
+  }
+
+  // Mount special filesystems.
+  mount = internal::mountSpecialFilesystems(root);
+  if (mount.isError()) {
+    return Error("Failed to mount: " + mount.error());
+  }
+
+  // Create basic device nodes.
+  Try<Nothing> create = internal::createStandardDevices(root);
+  if (create.isError()) {
+    return Error("Failed to create devices: " + create.error());
+  }
+
+  // Create a /tmp directory if it doesn't exist.
+  // TODO(idownes): Consider mounting a tmpfs to /tmp.
+  if (!os::exists(path::join(root, "tmp"))) {
+    Try<Nothing> mkdir = os::mkdir(path::join(root, "tmp"));
+     if (mkdir.isError()) {
+       return Error("Failed to create /tmp in chroot: " + mkdir.error());
+     }
+
+     Try<Nothing> chmod = os::chmod(
+         path::join(root, "tmp"),
+         S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
+
+     if (chmod.isError()) {
+       return Error("Failed to set mode on /tmp: " + chmod.error());
+     }
+  }
+
+  // Create a mount point for the old root.
+  Try<string> old = os::mkdtemp(path::join(root, "tmp", "._old_root_.XXXXXX"));
+  if (old.isError()) {
+    return Error("Failed to create mount point for old root: " + old.error());
+  }
+
+  // Chroot to the new root. This is done by a particular sequence of
+  // operations, each of which is necessary: chdir, pivot_root,
+  // chroot, chdir. After these operations, the process will be
+  // chrooted to the new root.
+
+  // Chdir to the new root.
+  Try<Nothing> chdir = os::chdir(root);
+  if (chdir.isError()) {
+    return Error("Failed to chdir to new root: " + chdir.error());
+  }
+
+  // Pivot the root to the cwd.
+  Try<Nothing> pivot = fs::pivot_root(root, old.get());
+  if (pivot.isError()) {
+    return Error("Failed to pivot to new root: " + pivot.error());
+  }
+
+  // Chroot to the new "/". This is necessary to correctly set the
+  // base for all paths.
+  Try<Nothing> chroot = os::chroot(".");
+  if (chroot.isError()) {
+    return Error("Failed to chroot to new root: " + chroot.error());
+  }
+
+  // Ensure all references are within the new root.
+  chdir = os::chdir("/");
+  if (chdir.isError()) {
+    return Error("Failed to chdir to new root: " + chdir.error());
+  }
+
+  // Unmount filesystems on the old root. Note, any filesystems that
+  // were mounted to the chroot directory will be correctly pivoted.
+  Try<fs::MountTable> mountTable = fs::MountTable::read("/proc/mounts");
+  if (mountTable.isError()) {
+    return Error("Failed to read mount table: " + mountTable.error());
+  }
+
+  // The old root is now relative to chroot so remove the chroot path.
+  const string relativeOld = strings::remove(old.get(), root, strings::PREFIX);
+
+  foreach (const fs::MountTable::Entry& entry, mountTable.get().entries) {
+    // TODO(idownes): sort the entries and remove depth first so we
+    // don't rely on the lazy umount and can check the status.
+    if (strings::startsWith(entry.dir, relativeOld)) {
+      fs::unmount(entry.dir, MNT_DETACH);
+    }
+  }
+
+  // TODO(idownes): If any of the lazy umounts above is still pending
+  // this will fail, leaving behind an empty directory which we'll
+  // ignore.
+  // Check status when we stop using lazy umounts.
+  os::rmdir(relativeOld);
+
+  return Nothing();
+}
+
+} // namespace chroot {
 } // namespace fs {
 } // namespace internal {
 } // namespace mesos {

http://git-wip-us.apache.org/repos/asf/mesos/blob/6950310f/src/linux/fs.hpp
----------------------------------------------------------------------
diff --git a/src/linux/fs.hpp b/src/linux/fs.hpp
index bcb0a1b..f3aa0c2 100644
--- a/src/linux/fs.hpp
+++ b/src/linux/fs.hpp
@@ -311,6 +311,15 @@ Try<Nothing> mount(const Option<std::string>& source,
                    const void* data);
 
 
+// Alternate version of mount which passes an option string as
+// additional data for the filesystem mount.
+Try<Nothing> mount(const Option<std::string>& source,
+                   const std::string& target,
+                   const Option<std::string>& type,
+                   unsigned long flags,
+                   const Option<std::string>& options);
+
+
 // Unmount a file system.
 // @param   target    The (topmost) directory where the file system attaches.
 // @param   flags     Unmount flags.
@@ -321,6 +330,15 @@ Try<Nothing> unmount(const std::string& target, int flags = 0);
 // Change the root filesystem.
 Try<Nothing> pivot_root(const std::string& newRoot, const std::string& putOld);
 
+namespace chroot {
+
+// Enter a 'chroot' enviroment. The caller should be in a new mount
+// namespace. Basic configuration of special filesystems and device
+// nodes is performed. Any mounts to the current root will be
+// unmounted.
+Try<Nothing> enter(const std::string& root);
+
+} // namespace chroot {
 
 } // namespace fs {
 } // namespace internal {