Repository: mesos Updated Branches: refs/heads/master da1b7161e -> 501756ed2
Add a MountInfoTable for per-process mount information. Entries in this table include information on mount propagation. Review: https://reviews.apache.org/r/31897 Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/501756ed Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/501756ed Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/501756ed Branch: refs/heads/master Commit: 501756ed27d727487ad2fe562f09133607032573 Parents: aa06b33 Author: Ian Downes <[email protected]> Authored: Fri Mar 6 14:48:29 2015 -0800 Committer: Ian Downes <[email protected]> Committed: Tue Mar 10 17:18:25 2015 -0700 ---------------------------------------------------------------------- src/linux/fs.cpp | 122 +++++++++++++++++++++++++++++++++++++++++--- src/linux/fs.hpp | 52 +++++++++++++++++-- src/tests/fs_tests.cpp | 71 ++++++++++++++++++++++++++ 3 files changed, 234 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/501756ed/src/linux/fs.cpp ---------------------------------------------------------------------- diff --git a/src/linux/fs.cpp b/src/linux/fs.cpp index 54afcc0..1c9cf3f 100644 --- a/src/linux/fs.cpp +++ b/src/linux/fs.cpp @@ -23,21 +23,127 @@ #include <linux/limits.h> #include <stout/error.hpp> +#include <stout/numify.hpp> +#include <stout/path.hpp> #include <stout/strings.hpp> +#include <stout/os/read.hpp> #include <stout/os/stat.hpp> #include "common/lock.hpp" #include "linux/fs.hpp" +using std::string; namespace mesos { namespace internal { namespace fs { -bool MountTable::Entry::hasOption(const std::string& option) const +Try<MountInfoTable> MountInfoTable::read(const Option<pid_t>& pid) +{ + MountInfoTable table; + + const string path = path::join( + "/proc", + (pid.isSome() ? stringify(pid.get()) : "self"), + "mountinfo"); + + Try<string> lines = os::read(path); + if (lines.isError()) { + return Error("Failed to read mountinfo file: " + lines.error()); + } + + foreach (const string& line, strings::tokenize(lines.get(), "\n")) { + Try<Entry> parse = MountInfoTable::Entry::parse(line); + if (parse.isError()) { + return Error("Failed to parse entry '" + line + "': " + parse.error()); + } + + table.entries.push_back(parse.get()); + } + + return table; +} + + +Try<MountInfoTable::Entry> MountInfoTable::Entry::parse(const string& s) +{ + MountInfoTable::Entry entry; + + const string separator = " - "; + size_t pos = s.find(separator); + if (pos == string::npos) { + return Error("Could not find separator ' - '"); + } + + // 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), " "); + if (tokens.size() < 6) { + return Error("Failed to parse entry"); + } + + Try<int> id = numify<int>(tokens[0]); + if (id.isError()) { + return Error("Mount ID is not a number"); + } + entry.id = id.get(); + + Try<int> parent = numify<int>(tokens[1]); + if (parent.isError()) { + return Error("Parent ID is not a number"); + } + entry.parent = parent.get(); + + // Parse out the major:minor device number. + std::vector<string> device = strings::split(tokens[2], ":"); + if (device.size() != 2) { + return Error("Invalid major:minor device number"); + } + + Try<int> major = numify<int>(device[0]); + if (major.isError()) { + return Error("Device major is not a number"); + } + + Try<int> minor = numify<int>(device[1]); + if (minor.isError()) { + return Error("Device minor is not a number"); + } + + entry.devno = makedev(major.get(), minor.get()); + + entry.root = tokens[3]; + entry.target = tokens[4]; + + entry.vfsOptions = tokens[5]; + + // The "proc" manpage states there can be zero or more optional + // fields. The kernel source (fs/proc_namespace.c) has the optional + // fields ("tagged fields") separated by " " when printing the table + // (see show_mountinfo()). + if (tokens.size() > 6) { + tokens.erase(tokens.begin(), tokens.begin() + 6); + entry.optionalFields = strings::join(" ", tokens); + } + + // Second set of fields: 3 required fields. + tokens = strings::tokenize(s.substr(pos + separator.size() - 1), " "); + if (tokens.size() != 3) { + return Error("Failed to parse type, source or options"); + } + + entry.type = tokens[0]; + entry.source = tokens[1]; + entry.fsOptions = tokens[2]; + + return entry; +} + + +bool MountTable::Entry::hasOption(const string& option) const { struct mntent mntent; mntent.mnt_fsname = const_cast<char*>(fsname.c_str()); @@ -50,7 +156,7 @@ bool MountTable::Entry::hasOption(const std::string& option) const } -Try<MountTable> MountTable::read(const std::string& path) +Try<MountTable> MountTable::read(const string& path) { MountTable table; @@ -151,9 +257,9 @@ Try<FileSystemTable> FileSystemTable::read() } -Try<Nothing> mount(const Option<std::string>& source, - const std::string& target, - const Option<std::string>& type, +Try<Nothing> mount(const Option<string>& source, + const string& target, + const Option<string>& type, unsigned long flags, const void* data) { @@ -176,7 +282,7 @@ Try<Nothing> mount(const Option<std::string>& source, } -Try<Nothing> unmount(const std::string& target, int flags) +Try<Nothing> unmount(const string& target, int flags) { // The prototype of function 'umount2' on Linux is as follows: // int umount2(const char *target, int flags); @@ -189,8 +295,8 @@ Try<Nothing> unmount(const std::string& target, int flags) Try<Nothing> pivot_root( - const std::string& newRoot, - const std::string& putOld) + const string& newRoot, + const string& putOld) { // These checks are done in the syscall but we'll do them here to // provide less cryptic error messages. See 'man 2 pivot_root'. http://git-wip-us.apache.org/repos/asf/mesos/blob/501756ed/src/linux/fs.hpp ---------------------------------------------------------------------- diff --git a/src/linux/fs.hpp b/src/linux/fs.hpp index a9ce7ed..d7832a4 100644 --- a/src/linux/fs.hpp +++ b/src/linux/fs.hpp @@ -23,6 +23,7 @@ #include <mntent.h> #include <sys/mount.h> +#include <sys/types.h> #include <string> #include <vector> @@ -36,6 +37,53 @@ namespace mesos { namespace internal { namespace fs { +// TODO(idownes): These three variations on mount information should +// be consolidated and moved to stout, along with mount and umount. + +// Structure describing the per-process mounts as found in +// /proc/[pid]/mountinfo. In particular, entries in this table specify +// the propagation properties of mounts, information not present in +// the MountTable or FileSystemTable. Entry order is preserved when +// parsing /proc/[pid]/mountinfo. +struct MountInfoTable { + // Structure describing an individual /proc/[pid]/mountinfo entry. + // See the /proc/[pid]/mountinfo section in 'man proc' for further + // details on each field. + struct Entry { + Entry() {} + + int id; // mountinfo[1]: mount ID. + int parent; // mountinfo[2]: parent ID. + dev_t devno; // mountinfo[3]: st_dev. + + std::string root; // mountinfo[4]: root of the mount. + std::string target; // mountinfo[5]: mount point. + + // Filesystem independent (VFS) options, e.g., "rw,noatime". + std::string vfsOptions; // mountinfo[6]: per-mount options. + // Filesystem dependent options, e.g., "rw,memory" for a memory + // cgroup filesystem. + std::string fsOptions; // mountinfo[11]: per-block options. + + // Current possible optional fields include shared:X, master:X, + // propagate_from:X, unbindable. + std::string optionalFields; // mountinfo[7]: optional fields. + + // mountinfo[8] is a separator. + + std::string type; // mountinfo[9]: filesystem type. + std::string source; // mountinfo[10]: source dev, other. + + static Try<Entry> parse(const std::string& s); + }; + + // If pid is None() the "self" is used, i.e., the mountinfo table + // for the calling process. + static Try<MountInfoTable> read(const Option<pid_t>& pid = None()); + + std::vector<Entry> entries; +}; + // Structure describing a mount table (e.g. /etc/mtab or /proc/mounts). struct MountTable { @@ -154,9 +202,7 @@ 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); +Try<Nothing> pivot_root(const std::string& newRoot, const std::string& putOld); } // namespace fs { http://git-wip-us.apache.org/repos/asf/mesos/blob/501756ed/src/tests/fs_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/fs_tests.cpp b/src/tests/fs_tests.cpp index 39d212f..83f375a 100644 --- a/src/tests/fs_tests.cpp +++ b/src/tests/fs_tests.cpp @@ -34,6 +34,7 @@ namespace tests { using fs::MountTable; using fs::FileSystemTable; +using fs::MountInfoTable; TEST(FsTest, MountTableRead) @@ -94,6 +95,76 @@ TEST(FsTest, FileSystemTableRead) EXPECT_SOME(root); } + +TEST(FsTest, MountInfoTableParse) +{ + // Parse a private mount (no optional fields). + const std::string privateMount = + "19 1 8:1 / / rw,relatime - ext4 /dev/sda1 rw,seclabel,data=ordered"; + Try<MountInfoTable::Entry> entry = MountInfoTable::Entry::parse(privateMount); + + ASSERT_SOME(entry); + EXPECT_EQ(19, entry.get().id); + EXPECT_EQ(1, entry.get().parent); + EXPECT_EQ(makedev(8, 1), entry.get().devno); + EXPECT_EQ("/", entry.get().root); + EXPECT_EQ("/", entry.get().target); + EXPECT_EQ("rw,relatime", entry.get().vfsOptions); + EXPECT_EQ("rw,seclabel,data=ordered", entry.get().fsOptions); + EXPECT_EQ("", entry.get().optionalFields); + EXPECT_EQ("ext4", entry.get().type); + EXPECT_EQ("/dev/sda1", entry.get().source); + + // Parse a shared mount (includes one optional field). + const std::string sharedMount = + "19 1 8:1 / / rw,relatime shared:2 - ext4 /dev/sda1 rw,seclabel"; + entry = MountInfoTable::Entry::parse(sharedMount); + + ASSERT_SOME(entry); + EXPECT_EQ(19, entry.get().id); + EXPECT_EQ(1, entry.get().parent); + EXPECT_EQ(makedev(8, 1), entry.get().devno); + EXPECT_EQ("/", entry.get().root); + EXPECT_EQ("/", entry.get().target); + EXPECT_EQ("rw,relatime", entry.get().vfsOptions); + EXPECT_EQ("rw,seclabel", entry.get().fsOptions); + EXPECT_EQ("shared:2", entry.get().optionalFields); + EXPECT_EQ("ext4", entry.get().type); + EXPECT_EQ("/dev/sda1", entry.get().source); +} + + +TEST(FsTest, MountInfoTableRead) +{ + // Examine the calling process's mountinfo table. + Try<fs::MountInfoTable> table = fs::MountInfoTable::read(); + ASSERT_SOME(table); + + // Every system should have at least a rootfs mounted. + Option<MountInfoTable::Entry> root = None(); + foreach (const MountInfoTable::Entry& entry, table.get().entries) { + if (entry.target == "/") { + root = entry; + } + } + + EXPECT_SOME(root); + + // Repeat for pid 1. + table = fs::MountInfoTable::read(1); + ASSERT_SOME(table); + + // Every system should have at least a rootfs mounted. + root = None(); + foreach (const MountInfoTable::Entry& entry, table.get().entries) { + if (entry.target == "/") { + root = entry; + } + } + + EXPECT_SOME(root); +} + } // namespace tests { } // namespace internal { } // namespace mesos {
