diff options
Diffstat (limited to 'test/util')
-rw-r--r-- | test/util/BUILD | 33 | ||||
-rw-r--r-- | test/util/capability_util.h | 13 | ||||
-rw-r--r-- | test/util/cgroup_util.cc | 236 | ||||
-rw-r--r-- | test/util/cgroup_util.h | 121 | ||||
-rw-r--r-- | test/util/fs_util.cc | 44 | ||||
-rw-r--r-- | test/util/fs_util.h | 12 | ||||
-rw-r--r-- | test/util/mount_util.cc | 176 | ||||
-rw-r--r-- | test/util/mount_util.h | 48 | ||||
-rw-r--r-- | test/util/mount_util_test.cc | 47 | ||||
-rw-r--r-- | test/util/posix_error.h | 7 | ||||
-rw-r--r-- | test/util/save_util.cc | 26 | ||||
-rw-r--r-- | test/util/test_util.h | 9 |
12 files changed, 749 insertions, 23 deletions
diff --git a/test/util/BUILD b/test/util/BUILD index e561f3daa..8985b54af 100644 --- a/test/util/BUILD +++ b/test/util/BUILD @@ -94,6 +94,7 @@ cc_library( ":file_descriptor", ":posix_error", "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", gtest, ], ) @@ -136,11 +137,26 @@ 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, + ], +) + +cc_test( + name = "mount_util_test", + size = "small", + srcs = ["mount_util_test.cc"], + deps = [ + ":mount_util", + ":test_main", + ":test_util", gtest, ], ) @@ -368,3 +384,20 @@ cc_library( testonly = 1, hdrs = ["temp_umask.h"], ) + +cc_library( + name = "cgroup_util", + testonly = 1, + srcs = ["cgroup_util.cc"], + hdrs = ["cgroup_util.h"], + deps = [ + ":cleanup", + ":fs_util", + ":mount_util", + ":posix_error", + ":temp_path", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + ], +) diff --git a/test/util/capability_util.h b/test/util/capability_util.h index a03bc7e05..f2c370125 100644 --- a/test/util/capability_util.h +++ b/test/util/capability_util.h @@ -99,14 +99,23 @@ PosixErrorOr<bool> CanCreateUserNamespace(); class AutoCapability { public: AutoCapability(int cap, bool set) : cap_(cap), set_(set) { - EXPECT_NO_ERRNO(SetCapability(cap_, set_)); + const bool has = EXPECT_NO_ERRNO_AND_VALUE(HaveCapability(cap)); + if (set != has) { + EXPECT_NO_ERRNO(SetCapability(cap_, set_)); + applied_ = true; + } } - ~AutoCapability() { EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); } + ~AutoCapability() { + if (applied_) { + EXPECT_NO_ERRNO(SetCapability(cap_, !set_)); + } + } private: int cap_; bool set_; + bool applied_ = false; }; } // namespace testing diff --git a/test/util/cgroup_util.cc b/test/util/cgroup_util.cc new file mode 100644 index 000000000..04d4f8de0 --- /dev/null +++ b/test/util/cgroup_util.cc @@ -0,0 +1,236 @@ +// 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/cgroup_util.h" + +#include <sys/syscall.h> +#include <unistd.h> + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "test/util/fs_util.h" +#include "test/util/mount_util.h" + +namespace gvisor { +namespace testing { + +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; +} + +PosixErrorOr<std::string> Cgroup::ReadControlFile( + absl::string_view name) const { + std::string buf; + RETURN_IF_ERRNO(GetContents(Relpath(name), &buf)); + + const std::string alias_path = absl::StrFormat("[cg#%d]/%s", id_, name); + std::cerr << absl::StreamFormat("<contents of %s>", alias_path) << std::endl; + std::cerr << buf; + std::cerr << absl::StreamFormat("<end of %s>", alias_path) << std::endl; + + return buf; +} + +PosixErrorOr<int64_t> Cgroup::ReadIntegerControlFile( + absl::string_view name) const { + ASSIGN_OR_RETURN_ERRNO(const std::string buf, ReadControlFile(name)); + ASSIGN_OR_RETURN_ERRNO(const int64_t val, Atoi<int64_t>(buf)); + return val; +} + +PosixError Cgroup::WriteControlFile(absl::string_view name, + const std::string& value) const { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(Relpath(name), O_WRONLY)); + RETURN_ERROR_IF_SYSCALL_FAIL(WriteFd(fd.get(), value.c_str(), value.size())); + return NoError(); +} + +PosixError Cgroup::WriteIntegerControlFile(absl::string_view name, + int64_t value) const { + return WriteControlFile(name, absl::StrCat(value)); +} + +PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Procs() const { + ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("cgroup.procs")); + return ParsePIDList(buf); +} + +PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Tasks() const { + ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("tasks")); + return ParsePIDList(buf); +} + +PosixError Cgroup::ContainsCallingProcess() const { + ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> procs, Procs()); + ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> tasks, Tasks()); + const pid_t pid = getpid(); + const pid_t tid = syscall(SYS_gettid); + if (!procs.contains(pid)) { + return PosixError( + ENOENT, absl::StrFormat("Cgroup doesn't contain process %d", pid)); + } + if (!tasks.contains(tid)) { + return PosixError(ENOENT, + absl::StrFormat("Cgroup doesn't contain task %d", tid)); + } + return NoError(); +} + +PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::ParsePIDList( + absl::string_view data) const { + absl::flat_hash_set<pid_t> res; + std::vector<absl::string_view> lines = absl::StrSplit(data, '\n'); + for (const std::string_view& line : lines) { + if (line.empty()) { + continue; + } + ASSIGN_OR_RETURN_ERRNO(const int32_t pid, Atoi<int32_t>(line)); + res.insert(static_cast<pid_t>(pid)); + } + return res; +} + +int64_t Cgroup::next_id_ = 0; + +PosixErrorOr<Cgroup> Mounter::MountCgroupfs(std::string mopts) { + ASSIGN_OR_RETURN_ERRNO(TempPath mountpoint, + TempPath::CreateDirIn(root_.path())); + ASSIGN_OR_RETURN_ERRNO( + Cleanup mount, Mount("none", mountpoint.path(), "cgroup", 0, mopts, 0)); + const std::string mountpath = mountpoint.path(); + std::cerr << absl::StreamFormat( + "Mount(\"none\", \"%s\", \"cgroup\", 0, \"%s\", 0) => OK", + mountpath, mopts) + << std::endl; + Cgroup cg = Cgroup(mountpath); + mountpoints_[cg.id()] = std::move(mountpoint); + mounts_[cg.id()] = std::move(mount); + return cg; +} + +PosixError Mounter::Unmount(const Cgroup& c) { + auto mount = mounts_.find(c.id()); + auto mountpoint = mountpoints_.find(c.id()); + + if (mount == mounts_.end() || mountpoint == mountpoints_.end()) { + return PosixError( + ESRCH, absl::StrFormat("No mount found for cgroupfs containing cg#%d", + c.id())); + } + + std::cerr << absl::StreamFormat("Unmount([cg#%d])", c.id()) << std::endl; + + // Simply delete the entries, their destructors will unmount and delete the + // mountpoint. Note the order is important to avoid errors: mount then + // mountpoint. + mounts_.erase(mount); + mountpoints_.erase(mountpoint); + + return NoError(); +} + +constexpr char kProcCgroupsHeader[] = + "#subsys_name\thierarchy\tnum_cgroups\tenabled"; + +PosixErrorOr<absl::flat_hash_map<std::string, CgroupsEntry>> +ProcCgroupsEntries() { + std::string content; + RETURN_IF_ERRNO(GetContents("/proc/cgroups", &content)); + + bool found_header = false; + absl::flat_hash_map<std::string, CgroupsEntry> entries; + std::vector<std::string> lines = absl::StrSplit(content, '\n'); + std::cerr << "<contents of /proc/cgroups>" << std::endl; + for (const std::string& line : lines) { + std::cerr << line << std::endl; + + if (!found_header) { + EXPECT_EQ(line, kProcCgroupsHeader); + found_header = true; + continue; + } + if (line.empty()) { + continue; + } + + // Parse a single entry from /proc/cgroups. + // + // Example entries, fields are tab separated in the real file: + // + // #subsys_name hierarchy num_cgroups enabled + // cpuset 12 35 1 + // cpu 3 222 1 + // ^ ^ ^ ^ + // 0 1 2 3 + + CgroupsEntry entry; + std::vector<std::string> fields = + StrSplit(line, absl::ByAnyChar(": \t"), absl::SkipEmpty()); + + entry.subsys_name = fields[0]; + ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[1])); + ASSIGN_OR_RETURN_ERRNO(entry.num_cgroups, Atoi<uint64_t>(fields[2])); + ASSIGN_OR_RETURN_ERRNO(const int enabled, Atoi<int>(fields[3])); + entry.enabled = enabled != 0; + + entries[entry.subsys_name] = entry; + } + std::cerr << "<end of /proc/cgroups>" << std::endl; + + return entries; +} + +PosixErrorOr<absl::flat_hash_map<std::string, PIDCgroupEntry>> +ProcPIDCgroupEntries(pid_t pid) { + const std::string path = absl::StrFormat("/proc/%d/cgroup", pid); + std::string content; + RETURN_IF_ERRNO(GetContents(path, &content)); + + absl::flat_hash_map<std::string, PIDCgroupEntry> entries; + std::vector<std::string> lines = absl::StrSplit(content, '\n'); + + std::cerr << absl::StreamFormat("<contents of %s>", path) << std::endl; + for (const std::string& line : lines) { + std::cerr << line << std::endl; + + if (line.empty()) { + continue; + } + + // Parse a single entry from /proc/<pid>/cgroup. + // + // Example entries: + // + // 2:cpu:/path/to/cgroup + // 1:memory:/ + + PIDCgroupEntry entry; + std::vector<std::string> fields = + absl::StrSplit(line, absl::ByChar(':'), absl::SkipEmpty()); + + ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[0])); + entry.controllers = fields[1]; + entry.path = fields[2]; + + entries[entry.controllers] = entry; + } + std::cerr << absl::StreamFormat("<end of %s>", path) << std::endl; + + return entries; +} + +} // namespace testing +} // namespace gvisor diff --git a/test/util/cgroup_util.h b/test/util/cgroup_util.h new file mode 100644 index 000000000..b797a8b24 --- /dev/null +++ b/test/util/cgroup_util.h @@ -0,0 +1,121 @@ +// 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. + +#ifndef GVISOR_TEST_UTIL_CGROUP_UTIL_H_ +#define GVISOR_TEST_UTIL_CGROUP_UTIL_H_ + +#include <unistd.h> + +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/string_view.h" +#include "test/util/cleanup.h" +#include "test/util/fs_util.h" +#include "test/util/temp_path.h" + +namespace gvisor { +namespace testing { + +// Cgroup represents a cgroup directory on a mounted cgroupfs. +class Cgroup { + public: + 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); + } + + // Returns the contents of a cgroup control file with the given name. + PosixErrorOr<std::string> ReadControlFile(absl::string_view name) const; + + // Reads the contents of a cgroup control with the given name, and attempts + // to parse it as an integer. + PosixErrorOr<int64_t> ReadIntegerControlFile(absl::string_view name) const; + + // Writes a string to a cgroup control file. + PosixError WriteControlFile(absl::string_view name, + const std::string& value) const; + + // Writes an integer value to a cgroup control file. + PosixError WriteIntegerControlFile(absl::string_view name, + int64_t value) const; + + // Returns the thread ids of the leaders of thread groups managed by this + // cgroup. + PosixErrorOr<absl::flat_hash_set<pid_t>> Procs() const; + + PosixErrorOr<absl::flat_hash_set<pid_t>> Tasks() const; + + // ContainsCallingProcess checks whether the calling process is part of the + PosixError ContainsCallingProcess() const; + + private: + PosixErrorOr<absl::flat_hash_set<pid_t>> ParsePIDList( + absl::string_view data) const; + + static int64_t next_id_; + int64_t id_; + const std::string cgroup_path_; +}; + +// Mounter is a utility for creating cgroupfs mounts. It automatically manages +// the lifetime of created mounts. +class Mounter { + public: + Mounter(TempPath root) : root_(std::move(root)) {} + + PosixErrorOr<Cgroup> MountCgroupfs(std::string mopts); + + PosixError Unmount(const Cgroup& c); + + private: + // The destruction order of these members avoids errors during cleanup. We + // first unmount (by executing the mounts_ cleanups), then delete the + // mountpoint subdirs, then delete the root. + TempPath root_; + absl::flat_hash_map<int64_t, TempPath> mountpoints_; + absl::flat_hash_map<int64_t, Cleanup> mounts_; +}; + +// Represents a line from /proc/cgroups. +struct CgroupsEntry { + std::string subsys_name; + uint32_t hierarchy; + uint64_t num_cgroups; + bool enabled; +}; + +// Returns a parsed representation of /proc/cgroups. +PosixErrorOr<absl::flat_hash_map<std::string, CgroupsEntry>> +ProcCgroupsEntries(); + +// Represents a line from /proc/<pid>/cgroup. +struct PIDCgroupEntry { + uint32_t hierarchy; + std::string controllers; + std::string path; +}; + +// Returns a parsed representation of /proc/<pid>/cgroup. +PosixErrorOr<absl::flat_hash_map<std::string, PIDCgroupEntry>> +ProcPIDCgroupEntries(pid_t pid); + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_UTIL_CGROUP_UTIL_H_ diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc index 5f1ce0d8a..483ae848d 100644 --- a/test/util/fs_util.cc +++ b/test/util/fs_util.cc @@ -28,6 +28,8 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/posix_error.h" @@ -366,6 +368,48 @@ PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, return files; } +PosixError DirContains(absl::string_view path, + const std::vector<std::string>& expect, + const std::vector<std::string>& exclude) { + ASSIGN_OR_RETURN_ERRNO(auto listing, ListDir(path, false)); + + for (auto& expected_entry : expect) { + auto cursor = std::find(listing.begin(), listing.end(), expected_entry); + if (cursor == listing.end()) { + return PosixError(ENOENT, absl::StrFormat("Failed to find '%s' in '%s'", + expected_entry, path)); + } + } + for (auto& excluded_entry : exclude) { + auto cursor = std::find(listing.begin(), listing.end(), excluded_entry); + if (cursor != listing.end()) { + return PosixError(ENOENT, absl::StrCat("File '", excluded_entry, + "' found in path '", path, "'")); + } + } + return NoError(); +} + +PosixError EventuallyDirContains(absl::string_view path, + const std::vector<std::string>& expect, + const std::vector<std::string>& exclude) { + constexpr int kRetryCount = 100; + const absl::Duration kRetryDelay = absl::Milliseconds(100); + + for (int i = 0; i < kRetryCount; ++i) { + auto res = DirContains(path, expect, exclude); + if (res.ok()) { + return res; + } + if (i < kRetryCount - 1) { + // Sleep if this isn't the final iteration. + absl::SleepFor(kRetryDelay); + } + } + return PosixError(ETIMEDOUT, + "Timed out while waiting for directory to contain files "); +} + PosixError RecursivelyDelete(absl::string_view path, int* undeleted_dirs, int* undeleted_files) { ASSIGN_OR_RETURN_ERRNO(bool exists, Exists(path)); diff --git a/test/util/fs_util.h b/test/util/fs_util.h index 2190c3bca..bb2d1d3c8 100644 --- a/test/util/fs_util.h +++ b/test/util/fs_util.h @@ -129,6 +129,18 @@ PosixError WalkTree( PosixErrorOr<std::vector<std::string>> ListDir(absl::string_view abspath, bool skipdots); +// Check that a directory contains children nodes named in expect, and does not +// contain any children nodes named in exclude. +PosixError DirContains(absl::string_view path, + const std::vector<std::string>& expect, + const std::vector<std::string>& exclude); + +// Same as DirContains, but adds a retry. Suitable for checking a directory +// being modified asynchronously. +PosixError EventuallyDirContains(absl::string_view path, + const std::vector<std::string>& expect, + const std::vector<std::string>& exclude); + // Attempt to recursively delete a directory or file. Returns an error and // the number of undeleted directories and files. If either // undeleted_dirs or undeleted_files is nullptr then it will not be used. diff --git a/test/util/mount_util.cc b/test/util/mount_util.cc new file mode 100644 index 000000000..48640d6a1 --- /dev/null +++ b/test/util/mount_util.cc @@ -0,0 +1,176 @@ +// 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)); + return ProcSelfMountsEntriesFrom(content); +} + +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom( + const std::string& content) { + std::vector<ProcMountsEntry> entries; + std::vector<std::string> lines = + absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty()); + 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::AllowEmpty()); + if (fields.size() != 6) { + return PosixError( + EINVAL, absl::StrFormat("Not enough tokens, got %d, content: <<%s>>", + fields.size(), content)); + } + + 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)); + return ProcSelfMountInfoEntriesFrom(content); +} + +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom( + const std::string& content) { + std::vector<ProcMountInfoEntry> entries; + std::vector<std::string> lines = + absl::StrSplit(content, absl::ByChar('\n'), absl::AllowEmpty()); + 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::AllowEmpty()); + if (fields.size() < 10 || fields.size() > 11) { + return PosixError( + EINVAL, absl::StrFormat( + "Unexpected number of tokens, got %d, content: <<%s>>", + fields.size(), content)); + } + + 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::AllowEmpty()); + 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 diff --git a/test/util/mount_util.h b/test/util/mount_util.h index 09e2281eb..3f8a1c0f1 100644 --- a/test/util/mount_util.h +++ b/test/util/mount_util.h @@ -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,53 @@ 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(); + +// ProcSelfMountsEntries returns a parsed representation of mounts from the +// provided content. +PosixErrorOr<std::vector<ProcMountsEntry>> ProcSelfMountsEntriesFrom( + const std::string& content); + +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(); + +// ProcSelfMountInfoEntriesFrom returns a parsed representation of +// mountinfo from the provided content. +PosixErrorOr<std::vector<ProcMountInfoEntry>> ProcSelfMountInfoEntriesFrom( + const std::string&); + +// 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 diff --git a/test/util/mount_util_test.cc b/test/util/mount_util_test.cc new file mode 100644 index 000000000..2bcb6cc43 --- /dev/null +++ b/test/util/mount_util_test.cc @@ -0,0 +1,47 @@ +// Copyright 2018 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 "gmock/gmock.h" +#include "gtest/gtest.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +TEST(ParseMounts, Mounts) { + auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntriesFrom( + R"proc(sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 +proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 + /mnt tmpfs rw,noexec 0 0 +)proc")); + EXPECT_EQ(entries.size(), 3); +} + +TEST(ParseMounts, MountInfo) { + auto entries = ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntriesFrom( + R"proc(22 28 0:20 / /sys rw,relatime shared:7 - sysfs sysfs rw +23 28 0:21 / /proc rw,relatime shared:14 - proc proc rw +2007 8844 0:278 / /mnt rw,noexec - tmpfs rw,mode=123,uid=268601820,gid=5000 +)proc")); + EXPECT_EQ(entries.size(), 3); +} + +} // namespace + +} // namespace testing +} // namespace gvisor diff --git a/test/util/posix_error.h b/test/util/posix_error.h index 27557ad44..9ca09b77c 100644 --- a/test/util/posix_error.h +++ b/test/util/posix_error.h @@ -438,6 +438,13 @@ IsPosixErrorOkAndHolds(InnerMatcher&& inner_matcher) { std::move(_expr_result).ValueOrDie(); \ }) +#define EXPECT_NO_ERRNO_AND_VALUE(expr) \ + ({ \ + auto _expr_result = (expr); \ + EXPECT_NO_ERRNO(_expr_result); \ + std::move(_expr_result).ValueOrDie(); \ + }) + } // namespace testing } // namespace gvisor diff --git a/test/util/save_util.cc b/test/util/save_util.cc index 59d47e06e..3e724d99b 100644 --- a/test/util/save_util.cc +++ b/test/util/save_util.cc @@ -27,23 +27,13 @@ namespace gvisor { namespace testing { namespace { -std::atomic<absl::optional<bool>> cooperative_save_present; -std::atomic<absl::optional<bool>> random_save_present; +std::atomic<absl::optional<bool>> save_present; -bool CooperativeSavePresent() { - auto present = cooperative_save_present.load(); +bool SavePresent() { + auto present = save_present.load(); if (!present.has_value()) { - present = getenv("GVISOR_COOPERATIVE_SAVE_TEST") != nullptr; - cooperative_save_present.store(present); - } - return present.value(); -} - -bool RandomSavePresent() { - auto present = random_save_present.load(); - if (!present.has_value()) { - present = getenv("GVISOR_RANDOM_SAVE_TEST") != nullptr; - random_save_present.store(present); + present = getenv("GVISOR_SAVE_TEST") != nullptr; + save_present.store(present); } return present.value(); } @@ -52,12 +42,10 @@ std::atomic<int> save_disable; } // namespace -bool IsRunningWithSaveRestore() { - return CooperativeSavePresent() || RandomSavePresent(); -} +bool IsRunningWithSaveRestore() { return SavePresent(); } void MaybeSave() { - if (CooperativeSavePresent() && save_disable.load() == 0) { + if (SavePresent() && save_disable.load() == 0) { internal::DoCooperativeSave(); } } diff --git a/test/util/test_util.h b/test/util/test_util.h index 876ff58db..bcbb388ed 100644 --- a/test/util/test_util.h +++ b/test/util/test_util.h @@ -272,10 +272,15 @@ PosixErrorOr<std::vector<OpenFd>> GetOpenFDs(); // Returns the number of hard links to a path. PosixErrorOr<uint64_t> Links(const std::string& path); +inline uint64_t ns_elapsed(const struct timespec& begin, + const struct timespec& end) { + return (end.tv_sec - begin.tv_sec) * 1000000000 + + (end.tv_nsec - begin.tv_nsec); +} + inline uint64_t ms_elapsed(const struct timespec& begin, const struct timespec& end) { - return (end.tv_sec - begin.tv_sec) * 1000 + - (end.tv_nsec - begin.tv_nsec) / 1000000; + return ns_elapsed(begin, end) / 1000000; } namespace internal { |