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 {