summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/proc.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/proc.cc')
-rw-r--r--test/syscalls/linux/proc.cc195
1 files changed, 144 insertions, 51 deletions
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index e4c030bbb..79a625ebc 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -37,6 +37,7 @@
#include <map>
#include <memory>
#include <ostream>
+#include <regex>
#include <string>
#include <unordered_set>
#include <utility>
@@ -51,6 +52,7 @@
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
+#include "absl/synchronization/notification.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/capability_util.h"
@@ -98,9 +100,39 @@ namespace {
#define SUID_DUMP_ROOT 2
#endif /* SUID_DUMP_ROOT */
-// O_LARGEFILE as defined by Linux. glibc tries to be clever by setting it to 0
-// because "it isn't needed", even though Linux can return it via F_GETFL.
-constexpr int kOLargeFile = 00100000;
+#if defined(__x86_64__) || defined(__i386__)
+// This list of "required" fields is taken from reading the file
+// arch/x86/kernel/cpu/proc.c and seeing which fields will be unconditionally
+// printed by the kernel.
+static const char* required_fields[] = {
+ "processor",
+ "vendor_id",
+ "cpu family",
+ "model\t\t:",
+ "model name",
+ "stepping",
+ "cpu MHz",
+ "fpu\t\t:",
+ "fpu_exception",
+ "cpuid level",
+ "wp",
+ "bogomips",
+ "clflush size",
+ "cache_alignment",
+ "address sizes",
+ "power management",
+};
+#elif __aarch64__
+// This list of "required" fields is taken from reading the file
+// arch/arm64/kernel/cpuinfo.c and seeing which fields will be unconditionally
+// printed by the kernel.
+static const char* required_fields[] = {
+ "processor", "BogoMIPS", "Features", "CPU implementer",
+ "CPU architecture", "CPU variant", "CPU part", "CPU revision",
+};
+#else
+#error "Unknown architecture"
+#endif
// Takes the subprocess command line and pid.
// If it returns !OK, WithSubprocess returns immediately.
@@ -183,7 +215,8 @@ PosixError WithSubprocess(SubprocessCallback const& running,
siginfo_t info;
// Wait until the child process has exited (WEXITED flag) but don't
// reap the child (WNOWAIT flag).
- waitid(P_PID, child_pid, &info, WNOWAIT | WEXITED);
+ EXPECT_THAT(waitid(P_PID, child_pid, &info, WNOWAIT | WEXITED),
+ SyscallSucceeds());
if (zombied) {
// Arg of "Z" refers to a Zombied Process.
@@ -714,28 +747,6 @@ TEST(ProcCpuinfo, RequiredFieldsArePresent) {
ASSERT_FALSE(proc_cpuinfo.empty());
std::vector<std::string> cpuinfo_fields = absl::StrSplit(proc_cpuinfo, '\n');
- // This list of "required" fields is taken from reading the file
- // arch/x86/kernel/cpu/proc.c and seeing which fields will be unconditionally
- // printed by the kernel.
- static const char* required_fields[] = {
- "processor",
- "vendor_id",
- "cpu family",
- "model\t\t:",
- "model name",
- "stepping",
- "cpu MHz",
- "fpu\t\t:",
- "fpu_exception",
- "cpuid level",
- "wp",
- "bogomips",
- "clflush size",
- "cache_alignment",
- "address sizes",
- "power management",
- };
-
// Check that the usual fields are there. We don't really care about the
// contents.
for (const std::string& field : required_fields) {
@@ -983,7 +994,7 @@ constexpr uint64_t kMappingSize = 100 << 20;
// Tolerance on RSS comparisons to account for background thread mappings,
// reclaimed pages, newly faulted pages, etc.
-constexpr uint64_t kRSSTolerance = 5 << 20;
+constexpr uint64_t kRSSTolerance = 10 << 20;
// Capture RSS before and after an anonymous mapping with passed prot.
void MapPopulateRSS(int prot, uint64_t* before, uint64_t* after) {
@@ -1315,8 +1326,6 @@ TEST(ProcPidSymlink, SubprocessRunning) {
SyscallSucceedsWithValue(sizeof(buf)));
}
-// FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
-// on proc files.
TEST(ProcPidSymlink, SubprocessZombied) {
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, false));
ASSERT_NO_ERRNO(SetCapability(CAP_DAC_READ_SEARCH, false));
@@ -1326,7 +1335,7 @@ TEST(ProcPidSymlink, SubprocessZombied) {
int want = EACCES;
if (!IsRunningOnGvisor()) {
auto version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion());
- if (version.major == 4 && version.minor > 3) {
+ if (version.major > 4 || (version.major == 4 && version.minor > 3)) {
want = ENOENT;
}
}
@@ -1339,24 +1348,25 @@ TEST(ProcPidSymlink, SubprocessZombied) {
SyscallFailsWithErrno(want));
}
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
- // on proc files.
- // 4.17 & gVisor: Syscall succeeds and returns 1
- // EXPECT_THAT(ReadlinkWhileZombied("ns/pid", buf, sizeof(buf)),
- // SyscallFailsWithErrno(EACCES));
+ // FIXME(gvisor.dev/issue/164): Inconsistent behavior between linux on proc
+ // files.
+ //
+ // ~4.3: Syscall fails with EACCES.
+ // 4.17: Syscall succeeds and returns 1.
+ //
+ if (!IsRunningOnGvisor()) {
+ return;
+ }
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
- // on proc files.
- // 4.17 & gVisor: Syscall succeeds and returns 1.
- // EXPECT_THAT(ReadlinkWhileZombied("ns/user", buf, sizeof(buf)),
- // SyscallFailsWithErrno(EACCES));
+ EXPECT_THAT(ReadlinkWhileZombied("ns/pid", buf, sizeof(buf)),
+ SyscallFailsWithErrno(want));
+
+ EXPECT_THAT(ReadlinkWhileZombied("ns/user", buf, sizeof(buf)),
+ SyscallFailsWithErrno(want));
}
// Test whether /proc/PID/ symlinks can be read for an exited process.
TEST(ProcPidSymlink, SubprocessExited) {
- // FIXME(gvisor.dev/issue/164): These all succeed on gVisor.
- SKIP_IF(IsRunningOnGvisor());
-
char buf[1];
EXPECT_THAT(ReadlinkWhileExited("exe", buf, sizeof(buf)),
@@ -1414,14 +1424,24 @@ TEST(ProcPidFile, SubprocessRunning) {
EXPECT_THAT(ReadWhileRunning("uid_map", buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
+
+ EXPECT_THAT(ReadWhileRunning("oom_score", buf, sizeof(buf)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ EXPECT_THAT(ReadWhileRunning("oom_score_adj", buf, sizeof(buf)),
+ SyscallSucceedsWithValue(sizeof(buf)));
}
// Test whether /proc/PID/ files can be read for a zombie process.
TEST(ProcPidFile, SubprocessZombie) {
char buf[1];
- // 4.17: Succeeds and returns 1
- // gVisor: Succeeds and returns 0
+ // FIXME(gvisor.dev/issue/164): Loosen requirement due to inconsistent
+ // behavior on different kernels.
+ //
+ // ~4.3: Succeds and returns 0.
+ // 4.17: Succeeds and returns 1.
+ // gVisor: Succeeds and returns 0.
EXPECT_THAT(ReadWhileZombied("auxv", buf, sizeof(buf)), SyscallSucceeds());
EXPECT_THAT(ReadWhileZombied("cmdline", buf, sizeof(buf)),
@@ -1445,9 +1465,18 @@ TEST(ProcPidFile, SubprocessZombie) {
EXPECT_THAT(ReadWhileZombied("uid_map", buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
+ EXPECT_THAT(ReadWhileZombied("oom_score", buf, sizeof(buf)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ EXPECT_THAT(ReadWhileZombied("oom_score_adj", buf, sizeof(buf)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
// FIXME(gvisor.dev/issue/164): Inconsistent behavior between gVisor and linux
// on proc files.
+ //
+ // ~4.3: Fails and returns EACCES.
// gVisor & 4.17: Succeeds and returns 1.
+ //
// EXPECT_THAT(ReadWhileZombied("io", buf, sizeof(buf)),
// SyscallFailsWithErrno(EACCES));
}
@@ -1456,9 +1485,12 @@ TEST(ProcPidFile, SubprocessZombie) {
TEST(ProcPidFile, SubprocessExited) {
char buf[1];
- // FIXME(gvisor.dev/issue/164): Inconsistent behavior between kernels
+ // FIXME(gvisor.dev/issue/164): Inconsistent behavior between kernels.
+ //
+ // ~4.3: Fails and returns ESRCH.
// gVisor: Fails with ESRCH.
// 4.17: Succeeds and returns 1.
+ //
// EXPECT_THAT(ReadWhileExited("auxv", buf, sizeof(buf)),
// SyscallFailsWithErrno(ESRCH));
@@ -1500,6 +1532,15 @@ TEST(ProcPidFile, SubprocessExited) {
EXPECT_THAT(ReadWhileExited("uid_map", buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(buf)));
+
+ if (!IsRunningOnGvisor()) {
+ // FIXME(gvisor.dev/issue/164): Succeeds on gVisor.
+ EXPECT_THAT(ReadWhileExited("oom_score", buf, sizeof(buf)),
+ SyscallFailsWithErrno(ESRCH));
+ }
+
+ EXPECT_THAT(ReadWhileExited("oom_score_adj", buf, sizeof(buf)),
+ SyscallFailsWithErrno(ESRCH));
}
PosixError DirContainsImpl(absl::string_view path,
@@ -1630,7 +1671,7 @@ TEST(ProcTask, KilledThreadsDisappear) {
EXPECT_NO_ERRNO(DirContainsExactly("/proc/self/task",
TaskFiles(initial, {child1.Tid()})));
- // Stat child1's task file.
+ // Stat child1's task file. Regression test for b/32097707.
struct stat statbuf;
const std::string child1_task_file =
absl::StrCat("/proc/self/task/", child1.Tid());
@@ -1658,7 +1699,7 @@ TEST(ProcTask, KilledThreadsDisappear) {
EXPECT_NO_ERRNO(EventuallyDirContainsExactly(
"/proc/self/task", TaskFiles(initial, {child3.Tid(), child5.Tid()})));
- // Stat child1's task file again. This time it should fail.
+ // Stat child1's task file again. This time it should fail. See b/32097707.
EXPECT_THAT(stat(child1_task_file.c_str(), &statbuf),
SyscallFailsWithErrno(ENOENT));
@@ -1813,7 +1854,7 @@ TEST(ProcSysVmOvercommitMemory, HasNumericValue) {
}
// Check that link for proc fd entries point the target node, not the
-// symlink itself.
+// symlink itself. Regression test for b/31155070.
TEST(ProcTaskFd, FstatatFollowsSymlink) {
const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
const FileDescriptor fd =
@@ -1872,6 +1913,20 @@ TEST(ProcMounts, IsSymlink) {
EXPECT_EQ(link, "self/mounts");
}
+TEST(ProcSelfMountinfo, RequiredFieldsArePresent) {
+ auto mountinfo =
+ ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/self/mountinfo"));
+ EXPECT_THAT(
+ mountinfo,
+ AllOf(
+ // Root mount.
+ ContainsRegex(
+ R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / / (rw|ro).*- \S+ \S+ (rw|ro)\S*)"),
+ // Proc mount - always rw.
+ ContainsRegex(
+ R"([0-9]+ [0-9]+ [0-9]+:[0-9]+ / /proc rw.*- \S+ \S+ rw\S*)")));
+}
+
// 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"));
@@ -1983,10 +2038,48 @@ TEST(Proc, GetdentsEnoent) {
},
nullptr, nullptr));
char buf[1024];
- ASSERT_THAT(syscall(SYS_getdents, fd.get(), buf, sizeof(buf)),
+ ASSERT_THAT(syscall(SYS_getdents64, fd.get(), buf, sizeof(buf)),
SyscallFailsWithErrno(ENOENT));
}
+void CheckSyscwFromIOFile(const std::string& path, const std::string& regex) {
+ std::string output;
+ ASSERT_NO_ERRNO(GetContents(path, &output));
+ ASSERT_THAT(output, ContainsRegex(absl::StrCat("syscw:\\s+", regex, "\n")));
+}
+
+// Checks that there is variable accounting of IO between threads/tasks.
+TEST(Proc, PidTidIOAccounting) {
+ absl::Notification notification;
+
+ // Run a thread with a bunch of writes. Check that io account records exactly
+ // the number of write calls. File open/close is there to prevent buffering.
+ ScopedThread writer([&notification] {
+ const int num_writes = 100;
+ for (int i = 0; i < num_writes; i++) {
+ auto path = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ ASSERT_NO_ERRNO(SetContents(path.path(), "a"));
+ }
+ notification.Notify();
+ const std::string& writer_dir =
+ absl::StrCat("/proc/", getpid(), "/task/", gettid(), "/io");
+
+ CheckSyscwFromIOFile(writer_dir, std::to_string(num_writes));
+ });
+
+ // Run a thread and do no writes. Check that no writes are recorded.
+ ScopedThread noop([&notification] {
+ notification.WaitForNotification();
+ const std::string& noop_dir =
+ absl::StrCat("/proc/", getpid(), "/task/", gettid(), "/io");
+
+ CheckSyscwFromIOFile(noop_dir, "0");
+ });
+
+ writer.Join();
+ noop.Join();
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
@@ -1997,5 +2090,5 @@ int main(int argc, char** argv) {
}
gvisor::testing::TestInit(&argc, &argv);
- return RUN_ALL_TESTS();
+ return gvisor::testing::RunAllTests();
}