mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Also report mount options through /proc/<pid>/mounts.
PiperOrigin-RevId: 369967629
This commit is contained in:
committed by
gVisor bot
parent
dbfdb31e8a
commit
d93907110e
@@ -826,6 +826,9 @@ func (vfs *VirtualFilesystem) GenerateProcMounts(ctx context.Context, taskRootDi
|
||||
if mnt.Flags.NoExec {
|
||||
opts += ",noexec"
|
||||
}
|
||||
if mopts := mnt.fs.Impl().MountOptions(); mopts != "" {
|
||||
opts += "," + mopts
|
||||
}
|
||||
|
||||
// Format:
|
||||
// <special device or remote filesystem> <mount point> <filesystem type> <mount options> <needs dump> <fsck order>
|
||||
|
||||
@@ -1726,6 +1726,7 @@ cc_binary(
|
||||
"//test/util:cleanup",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:mount_util",
|
||||
"@com_google_absl//absl/container:node_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
@@ -4243,6 +4244,7 @@ cc_binary(
|
||||
"//test/util:cgroup_util",
|
||||
"//test/util:file_descriptor",
|
||||
"//test/util:fs_util",
|
||||
"//test/util:mount_util",
|
||||
"@com_google_absl//absl/strings",
|
||||
gtest,
|
||||
"//test/util:posix_error",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "test/util/capability_util.h"
|
||||
#include "test/util/cgroup_util.h"
|
||||
#include "test/util/mount_util.h"
|
||||
#include "test/util/temp_path.h"
|
||||
#include "test/util/test_util.h"
|
||||
|
||||
@@ -33,8 +34,11 @@ namespace testing {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Contains;
|
||||
using ::testing::Ge;
|
||||
using ::testing::Gt;
|
||||
using ::testing::Key;
|
||||
using ::testing::Not;
|
||||
|
||||
std::vector<std::string> known_controllers = {
|
||||
"cpu", "cpuset", "cpuacct", "job", "memory",
|
||||
@@ -447,6 +451,54 @@ TEST(ProcCgroup, MultiControllerHierarchy) {
|
||||
EXPECT_EQ(pid_e.hierarchy, mem_e.hierarchy);
|
||||
}
|
||||
|
||||
TEST(ProcCgroup, ProcfsReportsCgroupfsMountOptions) {
|
||||
SKIP_IF(!CgroupsAvailable());
|
||||
|
||||
Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
|
||||
// Hierarchy with multiple controllers.
|
||||
Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu"));
|
||||
// Hierarchy with a single controller.
|
||||
Cgroup c2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct"));
|
||||
|
||||
const std::vector<ProcMountsEntry> mounts =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
|
||||
|
||||
for (auto const& e : mounts) {
|
||||
if (e.mount_point == c1.Path()) {
|
||||
auto mopts = ParseMountOptions(e.mount_opts);
|
||||
EXPECT_THAT(mopts, Contains(Key("memory")));
|
||||
EXPECT_THAT(mopts, Contains(Key("cpu")));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("cpuacct"))));
|
||||
}
|
||||
|
||||
if (e.mount_point == c2.Path()) {
|
||||
auto mopts = ParseMountOptions(e.mount_opts);
|
||||
EXPECT_THAT(mopts, Contains(Key("cpuacct")));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("cpu"))));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("memory"))));
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ProcMountInfoEntry> mountinfo =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
|
||||
|
||||
for (auto const& e : mountinfo) {
|
||||
if (e.mount_point == c1.Path()) {
|
||||
auto mopts = ParseMountOptions(e.super_opts);
|
||||
EXPECT_THAT(mopts, Contains(Key("memory")));
|
||||
EXPECT_THAT(mopts, Contains(Key("cpu")));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("cpuacct"))));
|
||||
}
|
||||
|
||||
if (e.mount_point == c2.Path()) {
|
||||
auto mopts = ParseMountOptions(e.super_opts);
|
||||
EXPECT_THAT(mopts, Contains(Key("cpuacct")));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("cpu"))));
|
||||
EXPECT_THAT(mopts, Not(Contains(Key("memory"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "test/util/capability_util.h"
|
||||
@@ -44,6 +45,9 @@ namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::testing::Contains;
|
||||
using ::testing::Pair;
|
||||
|
||||
TEST(MountTest, MountBadFilesystem) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
@@ -345,6 +349,35 @@ TEST(MountTest, RenameRemoveMountPoint) {
|
||||
ASSERT_THAT(rmdir(dir.path().c_str()), SyscallFailsWithErrno(EBUSY));
|
||||
}
|
||||
|
||||
TEST(MountTest, MountInfo) {
|
||||
SKIP_IF(IsRunningWithVFS1());
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
|
||||
|
||||
auto const dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
|
||||
auto const mount = ASSERT_NO_ERRNO_AND_VALUE(
|
||||
Mount("", dir.path(), "tmpfs", MS_NOEXEC, "mode=0123", 0));
|
||||
const std::vector<ProcMountsEntry> mounts =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
|
||||
for (const auto& e : mounts) {
|
||||
if (e.mount_point == dir.path()) {
|
||||
EXPECT_EQ(e.fstype, "tmpfs");
|
||||
auto mopts = ParseMountOptions(e.mount_opts);
|
||||
EXPECT_THAT(mopts, Contains(Pair("mode", "0123")));
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ProcMountInfoEntry> mountinfo =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
|
||||
|
||||
for (auto const& e : mountinfo) {
|
||||
if (e.mount_point == dir.path()) {
|
||||
EXPECT_EQ(e.fstype, "tmpfs");
|
||||
auto mopts = ParseMountOptions(e.super_opts);
|
||||
EXPECT_THAT(mopts, Contains(Pair("mode", "0123")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace testing
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "test/util/file_descriptor.h"
|
||||
#include "test/util/fs_util.h"
|
||||
#include "test/util/memory_util.h"
|
||||
#include "test/util/mount_util.h"
|
||||
#include "test/util/multiprocess_util.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/proc_util.h"
|
||||
@@ -2468,6 +2469,19 @@ TEST(ProcSelfMountinfo, RequiredFieldsArePresent) {
|
||||
R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)")));
|
||||
}
|
||||
|
||||
TEST(ProcSelfMountinfo, ContainsProcfsEntry) {
|
||||
const std::vector<ProcMountInfoEntry> entries =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
|
||||
bool found = false;
|
||||
for (const auto& e : entries) {
|
||||
if (e.fstype == "proc") {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
|
||||
// Check that /proc/self/mounts looks something like a real mounts file.
|
||||
TEST(ProcSelfMounts, RequiredFieldsArePresent) {
|
||||
auto mounts = ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mounts"));
|
||||
@@ -2479,6 +2493,19 @@ TEST(ProcSelfMounts, RequiredFieldsArePresent) {
|
||||
ContainsRegex(R"(\S+ /proc \S+ rw\S* [0-9]+ [0-9]+\s)")));
|
||||
}
|
||||
|
||||
TEST(ProcSelfMounts, ContainsProcfsEntry) {
|
||||
const std::vector<ProcMountsEntry> entries =
|
||||
ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
|
||||
bool found = false;
|
||||
for (const auto& e : entries) {
|
||||
if (e.fstype == "proc") {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
|
||||
void CheckDuplicatesRecursively(std::string path) {
|
||||
std::vector<std::string> child_dirs;
|
||||
|
||||
|
||||
@@ -137,11 +137,14 @@ cc_library(
|
||||
cc_library(
|
||||
name = "mount_util",
|
||||
testonly = 1,
|
||||
srcs = ["mount_util.cc"],
|
||||
hdrs = ["mount_util.h"],
|
||||
deps = [
|
||||
":cleanup",
|
||||
":posix_error",
|
||||
":test_util",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/strings",
|
||||
gtest,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
Cgroup::Cgroup(std::string path) : cgroup_path_(path) {
|
||||
Cgroup::Cgroup(std::string_view path) : cgroup_path_(path) {
|
||||
id_ = ++Cgroup::next_id_;
|
||||
std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_)
|
||||
<< std::endl;
|
||||
|
||||
@@ -30,10 +30,12 @@ namespace testing {
|
||||
// Cgroup represents a cgroup directory on a mounted cgroupfs.
|
||||
class Cgroup {
|
||||
public:
|
||||
Cgroup(std::string path);
|
||||
Cgroup(std::string_view path);
|
||||
|
||||
uint64_t id() const { return id_; }
|
||||
|
||||
const std::string& Path() const { return cgroup_path_; }
|
||||
|
||||
std::string Relpath(absl::string_view leaf) const {
|
||||
return JoinPath(cgroup_path_, leaf);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2021 The gVisor Authors.
|
||||
//
|
||||
// Licensed 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 "test/util/mount_util.h"
|
||||
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
|
||||
namespace gvisor {
|
||||
namespace testing {
|
||||
|
||||
PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries() {
|
||||
std::string content;
|
||||
RETURN_IF_ERRNO(GetContents("/proc/self/mounts", &content));
|
||||
|
||||
std::vector<ProcMountsEntry> entries;
|
||||
std::vector<std::string> lines = absl::StrSplit(content, '\n');
|
||||
std::cerr << "<contents of /proc/self/mounts>" << std::endl;
|
||||
for (const std::string& line : lines) {
|
||||
std::cerr << line << std::endl;
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse a single entry from /proc/self/mounts.
|
||||
//
|
||||
// Example entries:
|
||||
//
|
||||
// sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
|
||||
// proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
|
||||
// ^ ^ ^ ^ ^ ^
|
||||
// 0 1 2 3 4 5
|
||||
|
||||
ProcMountsEntry entry;
|
||||
std::vector<std::string> fields =
|
||||
absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty());
|
||||
if (fields.size() != 6) {
|
||||
return PosixError(EINVAL,
|
||||
absl::StrFormat("Not enough tokens, got %d, line: %s",
|
||||
fields.size(), line));
|
||||
}
|
||||
|
||||
entry.spec = fields[0];
|
||||
entry.mount_point = fields[1];
|
||||
entry.fstype = fields[2];
|
||||
entry.mount_opts = fields[3];
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.dump, Atoi<uint32_t>(fields[4]));
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.fsck, Atoi<uint32_t>(fields[5]));
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
std::cerr << "<end of /proc/self/mounts>" << std::endl;
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries() {
|
||||
std::string content;
|
||||
RETURN_IF_ERRNO(GetContents("/proc/self/mountinfo", &content));
|
||||
|
||||
std::vector<ProcMountInfoEntry> entries;
|
||||
std::vector<std::string> lines = absl::StrSplit(content, '\n');
|
||||
std::cerr << "<contents of /proc/self/mountinfo>" << std::endl;
|
||||
for (const std::string& line : lines) {
|
||||
std::cerr << line << std::endl;
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse a single entry from /proc/self/mountinfo.
|
||||
//
|
||||
// Example entries:
|
||||
//
|
||||
// 22 28 0:20 / /sys rw,relatime shared:7 - sysfs sysfs rw
|
||||
// 23 28 0:21 / /proc rw,relatime shared:14 - proc proc rw
|
||||
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
|
||||
// 0 1 2 3 4 5 6 7 8 9 10
|
||||
|
||||
ProcMountInfoEntry entry;
|
||||
std::vector<std::string> fields =
|
||||
absl::StrSplit(line, absl::ByChar(' '), absl::SkipEmpty());
|
||||
if (fields.size() < 10 || fields.size() > 11) {
|
||||
return PosixError(
|
||||
EINVAL,
|
||||
absl::StrFormat("Unexpected number of tokens, got %d, line: %s",
|
||||
fields.size(), line));
|
||||
}
|
||||
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.id, Atoi<uint64_t>(fields[0]));
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.parent_id, Atoi<uint64_t>(fields[1]));
|
||||
|
||||
std::vector<std::string> devs =
|
||||
absl::StrSplit(fields[2], absl::ByChar(':'));
|
||||
if (devs.size() != 2) {
|
||||
return PosixError(
|
||||
EINVAL,
|
||||
absl::StrFormat(
|
||||
"Failed to parse dev number field %s: too many tokens, got %d",
|
||||
fields[2], devs.size()));
|
||||
}
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.major, Atoi<dev_t>(devs[0]));
|
||||
ASSIGN_OR_RETURN_ERRNO(entry.minor, Atoi<dev_t>(devs[1]));
|
||||
|
||||
entry.root = fields[3];
|
||||
entry.mount_point = fields[4];
|
||||
entry.mount_opts = fields[5];
|
||||
|
||||
// The optional field (fields[6]) may or may not be present. We know based
|
||||
// on the total number of tokens.
|
||||
int off = -1;
|
||||
if (fields.size() == 11) {
|
||||
entry.optional = fields[6];
|
||||
off = 0;
|
||||
}
|
||||
// Field 7 is the optional field terminator char '-'.
|
||||
entry.fstype = fields[8 + off];
|
||||
entry.mount_source = fields[9 + off];
|
||||
entry.super_opts = fields[10 + off];
|
||||
|
||||
entries.push_back(entry);
|
||||
}
|
||||
std::cerr << "<end of /proc/self/mountinfo>" << std::endl;
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
absl::flat_hash_map<std::string, std::string> ParseMountOptions(
|
||||
std::string mopts) {
|
||||
absl::flat_hash_map<std::string, std::string> entries;
|
||||
const std::vector<std::string> tokens =
|
||||
absl::StrSplit(mopts, absl::ByChar(','), absl::SkipEmpty());
|
||||
for (const auto& token : tokens) {
|
||||
std::vector<std::string> kv =
|
||||
absl::StrSplit(token, absl::MaxSplits('=', 1));
|
||||
if (kv.size() == 2) {
|
||||
entries[kv[0]] = kv[1];
|
||||
} else if (kv.size() == 1) {
|
||||
entries[kv[0]] = "";
|
||||
} else {
|
||||
TEST_CHECK_MSG(
|
||||
false,
|
||||
absl::StrFormat(
|
||||
"Invalid mount option token '%s', was split into %d subtokens",
|
||||
token, kv.size())
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "test/util/cleanup.h"
|
||||
#include "test/util/posix_error.h"
|
||||
#include "test/util/test_util.h"
|
||||
@@ -45,6 +46,43 @@ inline PosixErrorOr<Cleanup> Mount(const std::string& source,
|
||||
});
|
||||
}
|
||||
|
||||
struct ProcMountsEntry {
|
||||
std::string spec;
|
||||
std::string mount_point;
|
||||
std::string fstype;
|
||||
std::string mount_opts;
|
||||
uint32_t dump;
|
||||
uint32_t fsck;
|
||||
};
|
||||
|
||||
// ProcSelfMountsEntries returns a parsed representation of /proc/self/mounts.
|
||||
PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntries();
|
||||
|
||||
struct ProcMountInfoEntry {
|
||||
uint64_t id;
|
||||
uint64_t parent_id;
|
||||
dev_t major;
|
||||
dev_t minor;
|
||||
std::string root;
|
||||
std::string mount_point;
|
||||
std::string mount_opts;
|
||||
std::string optional;
|
||||
std::string fstype;
|
||||
std::string mount_source;
|
||||
std::string super_opts;
|
||||
};
|
||||
|
||||
// ProcSelfMountInfoEntries returns a parsed representation of
|
||||
// /proc/self/mountinfo.
|
||||
PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntries();
|
||||
|
||||
// Interprets the input string mopts as a comma separated list of mount
|
||||
// options. A mount option can either be just a value, or a key=value pair. For
|
||||
// example, the string "rw,relatime,fd=7" will be parsed into a map like { "rw":
|
||||
// "", "relatime": "", "fd": "7" }.
|
||||
absl::flat_hash_map<std::string, std::string> ParseMountOptions(
|
||||
std::string mopts);
|
||||
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
|
||||
Reference in New Issue
Block a user