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 {

Reply via email to