mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #7816 from jseba:proc_self_limits
PiperOrigin-RevId: 507076925
This commit is contained in:
@@ -64,6 +64,7 @@ func (fs *filesystem) newTaskInode(ctx context.Context, task *kernel.Task, pidns
|
||||
"fdinfo": fs.newFDInfoDirInode(ctx, task),
|
||||
"gid_map": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0644, &idMapData{task: task, gids: true}),
|
||||
"io": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0400, newIO(task, isThreadGroup)),
|
||||
"limits": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &limitsData{task: task}),
|
||||
"maps": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mapsData{task: task}),
|
||||
"mem": fs.newMemInode(ctx, task, fs.NextIno(), 0400),
|
||||
"mountinfo": fs.newTaskOwnedInode(ctx, task, fs.NextIno(), 0444, &mountInfoData{fs: fs, task: task}),
|
||||
|
||||
@@ -529,6 +529,44 @@ func (fd *memFD) SetStat(context.Context, vfs.SetStatOptions) error {
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *memFD) Release(context.Context) {}
|
||||
|
||||
// limitsData implements vfs.DynamicBytesSource for /proc/[pid]/limits.
|
||||
//
|
||||
// +stateify savable
|
||||
type limitsData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
task *kernel.Task
|
||||
}
|
||||
|
||||
func (d *limitsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
taskLimits := d.task.Limits()
|
||||
// formatting matches the kernel output from linux/fs/proc/base.c:proc_pid_limits()
|
||||
fmt.Fprintf(buf, "Limit Soft Limit Hard Limit Units \n")
|
||||
for _, lt := range limits.AllLimitTypes {
|
||||
fmt.Fprintf(buf, "%-25s ", lt.Name())
|
||||
|
||||
l := taskLimits.Get(lt)
|
||||
if l.Cur == limits.Infinity {
|
||||
fmt.Fprintf(buf, "%-20s ", "unlimited")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%-20d ", l.Cur)
|
||||
}
|
||||
|
||||
if l.Max == limits.Infinity {
|
||||
fmt.Fprintf(buf, "%-20s ", "unlimited")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%-20d ", l.Max)
|
||||
}
|
||||
|
||||
if u := lt.Unit(); u != "" {
|
||||
fmt.Fprintf(buf, "%-10s", u)
|
||||
}
|
||||
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
|
||||
//
|
||||
// +stateify savable
|
||||
|
||||
@@ -78,6 +78,7 @@ var (
|
||||
"fdinfo": linux.DT_DIR,
|
||||
"gid_map": linux.DT_REG,
|
||||
"io": linux.DT_REG,
|
||||
"limits": linux.DT_REG,
|
||||
"maps": linux.DT_REG,
|
||||
"mem": linux.DT_REG,
|
||||
"mountinfo": linux.DT_REG,
|
||||
|
||||
@@ -43,6 +43,105 @@ const (
|
||||
Rttime
|
||||
)
|
||||
|
||||
// AllLimitTypes contains all types in the order how they are presented in
|
||||
// /proc/pid/limits.
|
||||
var AllLimitTypes = []LimitType{
|
||||
CPU,
|
||||
FileSize,
|
||||
Data,
|
||||
Stack,
|
||||
Core,
|
||||
Rss,
|
||||
ProcessCount,
|
||||
NumberOfFiles,
|
||||
MemoryLocked,
|
||||
AS,
|
||||
Locks,
|
||||
SignalsPending,
|
||||
MessageQueueBytes,
|
||||
Nice,
|
||||
RealTimePriority,
|
||||
Rttime,
|
||||
}
|
||||
|
||||
// Name returns the kernel name of the limit
|
||||
func (lt LimitType) Name() string {
|
||||
switch lt {
|
||||
case CPU:
|
||||
return "Max cpu time"
|
||||
case FileSize:
|
||||
return "Max file size"
|
||||
case Data:
|
||||
return "Max data size"
|
||||
case Stack:
|
||||
return "Max stack size"
|
||||
case Core:
|
||||
return "Max core file size"
|
||||
case Rss:
|
||||
return "Max resident set"
|
||||
case ProcessCount:
|
||||
return "Max processes"
|
||||
case NumberOfFiles:
|
||||
return "Max open files"
|
||||
case MemoryLocked:
|
||||
return "Max locked memory"
|
||||
case AS:
|
||||
return "Max address space"
|
||||
case Locks:
|
||||
return "Max file locks"
|
||||
case SignalsPending:
|
||||
return "Max pending signals"
|
||||
case MessageQueueBytes:
|
||||
return "Max msgqueue size"
|
||||
case Nice:
|
||||
return "Max nice priority"
|
||||
case RealTimePriority:
|
||||
return "Max realtime priority"
|
||||
case Rttime:
|
||||
return "Max realtime timeout"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// Unit returns the unit string for a limit
|
||||
func (lt LimitType) Unit() string {
|
||||
switch lt {
|
||||
case CPU:
|
||||
return "seconds"
|
||||
case FileSize:
|
||||
return "bytes"
|
||||
case Data:
|
||||
return "bytes"
|
||||
case Stack:
|
||||
return "bytes"
|
||||
case Core:
|
||||
return "bytes"
|
||||
case Rss:
|
||||
return "bytes"
|
||||
case ProcessCount:
|
||||
return "processes"
|
||||
case NumberOfFiles:
|
||||
return "files"
|
||||
case MemoryLocked:
|
||||
return "bytes"
|
||||
case AS:
|
||||
return "bytes"
|
||||
case Locks:
|
||||
return "locks"
|
||||
case SignalsPending:
|
||||
return "signals"
|
||||
case MessageQueueBytes:
|
||||
return "bytes"
|
||||
case Nice:
|
||||
return ""
|
||||
case RealTimePriority:
|
||||
return ""
|
||||
case Rttime:
|
||||
return "us"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Infinity is a constant representing a resource with no limit.
|
||||
const Infinity = ^uint64(0)
|
||||
|
||||
|
||||
@@ -2136,9 +2136,11 @@ 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",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -20,9 +20,15 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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,20 @@ namespace testing {
|
||||
|
||||
namespace {
|
||||
|
||||
PosixErrorOr<ProcLimitsEntry> 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 +63,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());
|
||||
}
|
||||
|
||||
@@ -331,5 +331,225 @@ bool IsTHPDisabled() {
|
||||
return StackTHPDisabled(maps.ValueOrDie());
|
||||
}
|
||||
|
||||
PosixErrorOr<ProcLimitsEntry> ParseProcLimitsLine(absl::string_view line) {
|
||||
ProcLimitsEntry limits_entry = {};
|
||||
|
||||
std::vector<std::string> 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<uint64_t>(parts[0]));
|
||||
}
|
||||
|
||||
// parse hard limit
|
||||
if (parts[1] == "unlimited") {
|
||||
limits_entry.max_limit = ~0ULL;
|
||||
} else {
|
||||
ASSIGN_OR_RETURN_ERRNO(limits_entry.max_limit, Atoi<uint64_t>(parts[1]));
|
||||
}
|
||||
|
||||
// ignore units
|
||||
|
||||
return limits_entry;
|
||||
}
|
||||
|
||||
PosixErrorOr<std::vector<ProcLimitsEntry>> ParseProcLimits(
|
||||
absl::string_view contents) {
|
||||
std::vector<ProcLimitsEntry> entries;
|
||||
std::vector<std::string> 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<ProcLimitsEntry>& 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
|
||||
|
||||
@@ -195,6 +195,51 @@ 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<ProcLimitsEntry> ParseProcLimitsLine(absl::string_view line);
|
||||
|
||||
// Parses an entire /proc/xxx/limits file into lines
|
||||
PosixErrorOr<std::vector<ProcLimitsEntry>> ParseProcLimits(
|
||||
absl::string_view contents);
|
||||
|
||||
// Printer for ProcLimitsEntry.
|
||||
std::ostream& operator<<(std::ostream& os, const ProcLimitsEntry& entry);
|
||||
|
||||
// Printer for std::vector<ProcLimitsEntry>.
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const std::vector<ProcLimitsEntry>& vec);
|
||||
|
||||
// GMock printer for std::vector<ProcLimitsEntry>.
|
||||
inline void PrintTo(const std::vector<ProcLimitsEntry>& vec, std::ostream* os) {
|
||||
*os << vec;
|
||||
}
|
||||
} // namespace testing
|
||||
} // namespace gvisor
|
||||
|
||||
|
||||
Reference in New Issue
Block a user