diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 12f44de26..a03979a49 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -2139,6 +2139,7 @@ cc_binary( linkstatic = 1, deps = [ "//test/util:capability_util", + "//test/util:proc_util", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", diff --git a/test/syscalls/linux/rlimits.cc b/test/syscalls/linux/rlimits.cc index 13d15a529..323eb8ee4 100644 --- a/test/syscalls/linux/rlimits.cc +++ b/test/syscalls/linux/rlimits.cc @@ -21,8 +21,14 @@ #include #include +#include +#include +#include +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" #include "test/util/capability_util.h" +#include "test/util/proc_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" @@ -31,6 +37,21 @@ namespace testing { namespace { +PosixErrorOr GetProcLimitEntryByType(LimitType limit_type) { + ASSIGN_OR_RETURN_ERRNO(std::string proc_self_limits, + GetContents("/proc/self/limits")); + ASSIGN_OR_RETURN_ERRNO(auto entries, ParseProcLimits(proc_self_limits)); + auto it = + std::find_if(entries.begin(), entries.end(), + [limit_type](const ProcLimitsEntry& v) { + return v.limit_type == limit_type; + }); + if (it == entries.end()) { + return PosixError(ENOENT, "limit type not found"); + } + return *it; +} + TEST(RlimitTest, SetRlimitHigher) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_RESOURCE))); @@ -43,6 +64,12 @@ TEST(RlimitTest, SetRlimitHigher) { rl.rlim_max--; ASSERT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); + // Now verify we can read the changed values via /proc/self/limits + const ProcLimitsEntry limit_entry = + ASSERT_NO_ERRNO_AND_VALUE(GetProcLimitEntryByType(LimitType::NumberOfFiles)); + EXPECT_EQ(rl.rlim_cur, limit_entry.cur_limit); + EXPECT_EQ(rl.rlim_max, limit_entry.max_limit); + rl.rlim_max++; EXPECT_THAT(setrlimit(RLIMIT_NOFILE, &rl), SyscallSucceeds()); } diff --git a/test/util/proc_util.cc b/test/util/proc_util.cc index 132c53953..6304744ec 100644 --- a/test/util/proc_util.cc +++ b/test/util/proc_util.cc @@ -331,5 +331,158 @@ bool IsTHPDisabled() { return StackTHPDisabled(maps.ValueOrDie()); } +PosixErrorOr ParseProcLimitsLine(absl::string_view line) { + ProcLimitsEntry limits_entry = {}; + + std::vector parts = + absl::StrSplit(line.substr(25), ' ', absl::SkipWhitespace()); + + // should have 3 parts (soft, hard, units) + // the name is ignored since the whole line is space separated and + // the name has spaces in but not a consistent number of spaces per + // name (e.g. 'Max cpu time' vs 'Max processes') + // however, since units are optional, ignore them as well + if (parts.size() < 2 || parts.size() > 3) { + return PosixError(EINVAL, absl::StrCat("Invalid line: ", line)); + } + + // parse the limit type + auto limitType = line.substr(0,25); + if (absl::StrContains(limitType, "cpu time")) { + limits_entry.limit_type = LimitType::CPU; + } else if (absl::StrContains(limitType, "file size")) { + limits_entry.limit_type = LimitType::FileSize; + } else if (absl::StrContains(limitType, "data size")) { + limits_entry.limit_type = LimitType::Data; + } else if (absl::StrContains(limitType, "stack size")) { + limits_entry.limit_type = LimitType::Stack; + } else if (absl::StrContains(limitType, "core file size")) { + limits_entry.limit_type = LimitType::Core; + } else if (absl::StrContains(limitType, "resident set")) { + limits_entry.limit_type = LimitType::RSS; + } else if (absl::StrContains(limitType, "processes")) { + limits_entry.limit_type = LimitType::ProcessCount; + } else if (absl::StrContains(limitType, "open files")) { + limits_entry.limit_type = LimitType::NumberOfFiles; + } else if (absl::StrContains(limitType, "locked memory")) { + limits_entry.limit_type = LimitType::MemoryLocked; + } else if (absl::StrContains(limitType, "address space")) { + limits_entry.limit_type = LimitType::AS; + } else if (absl::StrContains(limitType, "file locks")) { + limits_entry.limit_type = LimitType::Locks; + } else if (absl::StrContains(limitType, "pending signals")) { + limits_entry.limit_type = LimitType::SignalsPending; + } else if (absl::StrContains(limitType, "msgqueue size")) { + limits_entry.limit_type = LimitType::MessageQueueBytes; + } else if (absl::StrContains(limitType, "nice priority")) { + limits_entry.limit_type = LimitType::Nice; + } else if (absl::StrContains(limitType, "realtime priority")) { + limits_entry.limit_type = LimitType::RealTimePriority; + } else if (absl::StrContains(limitType, "realtime timeout")) { + limits_entry.limit_type = LimitType::Rttime; + } else { + return PosixError(EINVAL, absl::StrCat("Invalid limit type: ", limitType)); + } + + // parse soft limit + if (parts[0] == "unlimited") { + limits_entry.cur_limit = ~0ULL; + } else { + ASSIGN_OR_RETURN_ERRNO(limits_entry.cur_limit, Atoi(parts[0])); + } + + // parse hard limit + if (parts[1] == "unlimited") { + limits_entry.max_limit = ~0ULL; + } else { + ASSIGN_OR_RETURN_ERRNO(limits_entry.max_limit, Atoi(parts[1])); + } + + // ignore units + + return limits_entry; +} + +PosixErrorOr> ParseProcLimits( + absl::string_view contents) { + std::vector entries; + std::vector lines = absl::StrSplit(contents, '\n', absl::SkipEmpty()); + // skip first line (headers) + for (size_t i = 1U; i < lines.size(); ++i) { + std::cout << "line: " << lines[i] << std::endl; + ASSIGN_OR_RETURN_ERRNO(auto entry, ParseProcLimitsLine(lines[i])); + entries.push_back(entry); + } + return entries; +} + +std::ostream& operator<<(std::ostream& os, const ProcLimitsEntry& entry) { + std::string str = "Max "; + + switch (entry.limit_type) { + case LimitType::CPU: absl::StrAppend(&str, absl::StrFormat("%-25s ", "cpu time")); break; + case LimitType::FileSize: absl::StrAppend(&str, absl::StrFormat("%-25s ", "file size")); break; + case LimitType::Data: absl::StrAppend(&str, absl::StrFormat("%-25s ", "data size")); break; + case LimitType::Stack: absl::StrAppend(&str, absl::StrFormat("%-25s ", "stack size")); break; + case LimitType::Core: absl::StrAppend(&str, absl::StrFormat("%-25s ", "core file size")); break; + case LimitType::RSS: absl::StrAppend(&str, absl::StrFormat("%-25s ", "resident set")); break; + case LimitType::ProcessCount: absl::StrAppend(&str, absl::StrFormat("%-25s ", "processes")); break; + case LimitType::NumberOfFiles: absl::StrAppend(&str, absl::StrFormat("%-25s ", "open files")); break; + case LimitType::MemoryLocked: absl::StrAppend(&str, absl::StrFormat("%-25s ", "locked memory")); break; + case LimitType::AS: absl::StrAppend(&str, absl::StrFormat("%-25s ", "address space")); break; + case LimitType::Locks: absl::StrAppend(&str, absl::StrFormat("%-25s ", "file locks")); break; + case LimitType::SignalsPending: absl::StrAppend(&str, absl::StrFormat("%-25s ", "pending signals")); break; + case LimitType::MessageQueueBytes: absl::StrAppend(&str, absl::StrFormat("%-25s ", "msgqueue size")); break; + case LimitType::Nice: absl::StrAppend(&str, absl::StrFormat("%-25s ", "nice priority")); break; + case LimitType::RealTimePriority: absl::StrAppend(&str, absl::StrFormat("%-25s ", "realtime priority")); break; + case LimitType::Rttime: absl::StrAppend(&str, absl::StrFormat("%-25s ", "realtime timeout")); break; + } + + if (entry.cur_limit == ~0ULL) { + absl::StrAppend(&str, absl::StrFormat("%-20s ", "unlimited")); + } else { + absl::StrAppend(&str, absl::StrFormat("%-20d ", entry.cur_limit)); + } + + if (entry.max_limit == ~0ULL) { + absl::StrAppend(&str, absl::StrFormat("%-20s ", "unlimited")); + } else { + absl::StrAppend(&str, absl::StrFormat("%-20d ", entry.max_limit)); + } + + switch (entry.limit_type) { + case LimitType::CPU: absl::StrAppend(&str, absl::StrFormat("%-10s", "seconds")); break; + case LimitType::FileSize: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::Data: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::Stack: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::Core: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::RSS: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::ProcessCount: absl::StrAppend(&str, absl::StrFormat("%-10s ", "processes")); break; + case LimitType::NumberOfFiles: absl::StrAppend(&str, absl::StrFormat("%-10s ", "files")); break; + case LimitType::MemoryLocked: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::AS: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::Locks: absl::StrAppend(&str, absl::StrFormat("%-10s ", "locks")); break; + case LimitType::SignalsPending: absl::StrAppend(&str, absl::StrFormat("%-10s ", "signals")); break; + case LimitType::MessageQueueBytes: absl::StrAppend(&str, absl::StrFormat("%-10s ", "bytes")); break; + case LimitType::Nice: absl::StrAppend(&str, absl::StrFormat("%-10s ", "")); break; + case LimitType::RealTimePriority: absl::StrAppend(&str, absl::StrFormat("%-10s ", "")); break; + case LimitType::Rttime: absl::StrAppend(&str, absl::StrFormat("%-10s ", "us")); break; + } + + os << str; + return os; +} + +std::ostream& operator<<(std::ostream& os, const std::vector& vec) { + os << "Limit Soft Limit Hard Limit Units \n"; + for (unsigned int i = 0; i < vec.size(); i++) { + os << vec[i]; + if (i != vec.size() - 1) { + os << "\n"; + } + } + return os; +} + } // namespace testing } // namespace gvisor diff --git a/test/util/proc_util.h b/test/util/proc_util.h index 2ec4e61be..106f44f7e 100644 --- a/test/util/proc_util.h +++ b/test/util/proc_util.h @@ -195,6 +195,49 @@ MATCHER_P(ContainsMappings, mappings, return all_present; } +// LimitType is an rlimit type +enum class LimitType { + CPU, + FileSize, + Data, + Stack, + Core, + RSS, + ProcessCount, + NumberOfFiles, + MemoryLocked, + AS, + Locks, + SignalsPending, + MessageQueueBytes, + Nice, + RealTimePriority, + Rttime, +}; + +// ProcLimitsEntry contains the data from a single line in /proc/xxx/limits. +struct ProcLimitsEntry { + LimitType limit_type; + uint64_t cur_limit; + uint64_t max_limit; +}; + +// Parses a single line from /proc/xxx/limits +PosixErrorOr ParseProcLimitsLine(absl::string_view line); + +// Parses an entire /proc/xxx/limits file into lines +PosixErrorOr> ParseProcLimits(absl::string_view contents); + +// Printer for ProcLimitsEntry. +std::ostream& operator<<(std::ostream& os, const ProcLimitsEntry& entry); + +// Printer for std::vector. +std::ostream& operator<<(std::ostream& os, const std::vector& vec); + +// GMock printer for std::vector. +inline void PrintTo(const std::vector& vec, std::ostream* os) { + *os << vec; +} } // namespace testing } // namespace gvisor