gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/cgroup.cc (about) 1 // Copyright 2021 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // All tests in this file rely on being about to mount and unmount cgroupfs, 16 // which isn't expected to work, or be safe on a general linux system. 17 18 #include <limits.h> 19 #include <linux/magic.h> 20 #include <sys/mount.h> 21 #include <sys/statfs.h> 22 #include <unistd.h> 23 24 #include <cerrno> 25 #include <cstdint> 26 27 #include "gmock/gmock.h" 28 #include "gtest/gtest.h" 29 #include "absl/container/flat_hash_map.h" 30 #include "absl/container/flat_hash_set.h" 31 #include "absl/strings/str_split.h" 32 #include "absl/synchronization/notification.h" 33 #include "absl/time/time.h" 34 #include "test/util/cgroup_util.h" 35 #include "test/util/cleanup.h" 36 #include "test/util/linux_capability_util.h" 37 #include "test/util/mount_util.h" 38 #include "test/util/posix_error.h" 39 #include "test/util/temp_path.h" 40 #include "test/util/test_util.h" 41 #include "test/util/thread_util.h" 42 43 namespace gvisor { 44 namespace testing { 45 namespace { 46 47 using ::testing::_; 48 using ::testing::Contains; 49 using ::testing::Each; 50 using ::testing::Eq; 51 using ::testing::Ge; 52 using ::testing::Gt; 53 using ::testing::Key; 54 using ::testing::Not; 55 56 std::vector<std::string> known_controllers = { 57 "cpu", "cpuset", "cpuacct", "devices", "job", "memory", "pids", 58 }; 59 60 bool CgroupsAvailable() { 61 return IsRunningOnGvisor() && 62 TEST_CHECK_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)); 63 } 64 65 // NoopThreads spawns a set of threads that do nothing until they're asked to 66 // exit. Useful for testing functionality that requires a process with multiple 67 // threads. 68 class NoopThreads { 69 public: 70 NoopThreads(int count) { 71 auto noop = [this]() { exit_.WaitForNotification(); }; 72 73 for (int i = 0; i < count; ++i) { 74 threads_.emplace_back(noop); 75 } 76 } 77 78 ~NoopThreads() { Join(); } 79 80 void Join() { 81 if (joined_) { 82 return; 83 } 84 85 joined_ = true; 86 exit_.Notify(); 87 for (auto& thread : threads_) { 88 thread.Join(); 89 } 90 } 91 92 private: 93 std::list<ScopedThread> threads_; 94 absl::Notification exit_; 95 bool joined_ = false; 96 }; 97 98 TEST(Cgroup, MountsForAllControllers) { 99 SKIP_IF(!CgroupsAvailable()); 100 101 for (const auto& ctl : known_controllers) { 102 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/" + ctl); 103 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 104 } 105 } 106 107 // All supported controllers are mounted by default. 108 TEST(Cgroup, AllControllersImplicit) { 109 SKIP_IF(!CgroupsAvailable()); 110 111 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 112 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 113 for (const auto& ctl : known_controllers) { 114 EXPECT_TRUE(cgroups_entries.contains(ctl)) 115 << absl::StreamFormat("ctl=%s", ctl); 116 } 117 EXPECT_EQ(cgroups_entries.size(), known_controllers.size()); 118 } 119 120 TEST(Cgroup, ProcsAndTasks) { 121 SKIP_IF(!CgroupsAvailable()); 122 123 for (const auto& ctl : known_controllers) { 124 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/" + ctl); 125 absl::flat_hash_set<pid_t> pids = ASSERT_NO_ERRNO_AND_VALUE(c.Procs()); 126 absl::flat_hash_set<pid_t> tids = ASSERT_NO_ERRNO_AND_VALUE(c.Tasks()); 127 128 EXPECT_GE(tids.size(), pids.size()) << "Found more processes than threads"; 129 130 // Pids should be a strict subset of tids. 131 for (auto it = pids.begin(); it != pids.end(); ++it) { 132 EXPECT_TRUE(tids.contains(*it)) 133 << absl::StreamFormat("Have pid %d, but no such tid", *it); 134 } 135 } 136 } 137 138 TEST(Cgroup, Statfs) { 139 SKIP_IF(!CgroupsAvailable()); 140 141 for (const auto& ctl : known_controllers) { 142 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/" + ctl); 143 struct statfs st; 144 EXPECT_THAT(statfs(c.Relpath("cgroup.procs").c_str(), &st), 145 SyscallSucceeds()); 146 EXPECT_EQ(st.f_type, CGROUP_SUPER_MAGIC); 147 148 EXPECT_THAT(statfs(c.Relpath(".").c_str(), &st), SyscallSucceeds()); 149 EXPECT_EQ(st.f_type, CGROUP_SUPER_MAGIC); 150 } 151 } 152 153 TEST(Cgroup, StatfsCgroupDir) { 154 SKIP_IF(!CgroupsAvailable()); 155 156 struct statfs st; 157 EXPECT_THAT(statfs("/sys/fs/cgroup", &st), SyscallSucceeds()); 158 EXPECT_EQ(st.f_type, TMPFS_MAGIC); 159 } 160 161 TEST(Cgroup, CgroupsCannotMountTwice) { 162 SKIP_IF(!CgroupsAvailable()); 163 164 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 165 // Cgroups are already mounted. 166 EXPECT_THAT(m.MountCgroupfs(""), PosixErrorIs(EBUSY, _)) 167 << "Cgroups are already mounted"; 168 } 169 170 TEST(Cgroup, OnlyContainsControllerSpecificFiles) { 171 SKIP_IF(!CgroupsAvailable()); 172 Cgroup mem = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 173 EXPECT_THAT(Exists(mem.Relpath("memory.usage_in_bytes")), 174 IsPosixErrorOkAndHolds(true)); 175 // CPU files shouldn't exist in memory cgroups. 176 EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_period_us")), 177 IsPosixErrorOkAndHolds(false)); 178 EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_quota_us")), 179 IsPosixErrorOkAndHolds(false)); 180 EXPECT_THAT(Exists(mem.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(false)); 181 182 Cgroup cpu = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 183 EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_period_us")), 184 IsPosixErrorOkAndHolds(true)); 185 EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_quota_us")), 186 IsPosixErrorOkAndHolds(true)); 187 EXPECT_THAT(Exists(cpu.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(true)); 188 // Memory files shouldn't exist in cpu cgroups. 189 EXPECT_THAT(Exists(cpu.Relpath("memory.usage_in_bytes")), 190 IsPosixErrorOkAndHolds(false)); 191 } 192 193 TEST(Cgroup, InvalidController) { 194 SKIP_IF(!CgroupsAvailable()); 195 196 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 197 std::string mopts = "this-controller-is-invalid"; 198 EXPECT_THAT( 199 mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()), 200 SyscallFailsWithErrno(EINVAL)); 201 } 202 203 TEST(Cgroup, MoptAllMustBeExclusive) { 204 SKIP_IF(!CgroupsAvailable()); 205 206 TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); 207 std::string mopts = "all,cpu"; 208 EXPECT_THAT( 209 mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()), 210 SyscallFailsWithErrno(EINVAL)); 211 } 212 213 TEST(Cgroup, UnmountRepeated) { 214 SKIP_IF(!CgroupsAvailable()); 215 216 const DisableSave ds; // Too many syscalls. 217 218 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 219 220 // First unmount should succeed. 221 EXPECT_THAT(umount(c.Path().c_str()), SyscallSucceeds()); 222 EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL)); 223 } 224 225 TEST(Cgroup, Create) { 226 SKIP_IF(!CgroupsAvailable()); 227 228 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 229 ASSERT_NO_ERRNO(c.CreateChild("child1")); 230 EXPECT_TRUE(ASSERT_NO_ERRNO_AND_VALUE(Exists(c.Path()))); 231 } 232 233 TEST(Cgroup, SubcontainerInitiallyEmpty) { 234 SKIP_IF(!CgroupsAvailable()); 235 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 236 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); 237 auto procs = ASSERT_NO_ERRNO_AND_VALUE(child.Procs()); 238 EXPECT_TRUE(procs.empty()); 239 } 240 241 TEST(Cgroup, SubcontainersHaveIndependentState) { 242 SKIP_IF(!CgroupsAvailable()); 243 // Use the job cgroup as a simple cgroup with state we can modify. 244 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/job"); 245 246 // Initially job.id should be the default value of 0. 247 EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(0)); 248 249 // Set id so it is no longer the default. 250 ASSERT_NO_ERRNO(c.WriteIntegerControlFile("job.id", 1234)); 251 252 // Create a child. The child should inherit the value from the parent, and not 253 // the default value of 0. 254 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); 255 EXPECT_THAT(child.ReadIntegerControlFile("job.id"), 256 IsPosixErrorOkAndHolds(1234)); 257 258 // Setting the parent doesn't change the child. 259 ASSERT_NO_ERRNO(c.WriteIntegerControlFile("job.id", 5678)); 260 EXPECT_THAT(child.ReadIntegerControlFile("job.id"), 261 IsPosixErrorOkAndHolds(1234)); 262 263 // Likewise, setting the child doesn't change the parent. 264 ASSERT_NO_ERRNO(child.WriteIntegerControlFile("job.id", 9012)); 265 EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(5678)); 266 } 267 268 TEST(Cgroup, MigrateToSubcontainer) { 269 SKIP_IF(!CgroupsAvailable()); 270 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 271 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); 272 273 // Initially, test process should be in the root cgroup c. 274 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 275 276 pid_t pid = getpid(); 277 278 EXPECT_NO_ERRNO(child.Enter(pid)); 279 280 // After migration, child should contain the test process, and the c should 281 // not. 282 EXPECT_NO_ERRNO(child.ContainsCallingProcess()); 283 auto procs = ASSERT_NO_ERRNO_AND_VALUE(c.Procs()); 284 EXPECT_FALSE(procs.contains(pid)); 285 } 286 287 TEST(Cgroup, MigrateToSubcontainerThread) { 288 SKIP_IF(!CgroupsAvailable()); 289 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 290 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child1")); 291 292 // Ensure there are some threads for this process. 293 NoopThreads threads(10); 294 295 // Initially, test process should be in the root cgroup c. 296 EXPECT_NO_ERRNO(c.ContainsCallingThread()); 297 298 const pid_t tid = syscall(SYS_gettid); 299 300 EXPECT_NO_ERRNO(child.EnterThread(tid)); 301 302 // After migration, child should contain the test process, and the c should 303 // not. 304 EXPECT_NO_ERRNO(child.ContainsCallingThread()); 305 auto tasks = ASSERT_NO_ERRNO_AND_VALUE(c.Tasks()); 306 EXPECT_FALSE(tasks.contains(tid)); 307 } 308 309 TEST(Cgroup, MigrateInvalidPID) { 310 SKIP_IF(!CgroupsAvailable()); 311 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 312 313 EXPECT_THAT(c.WriteControlFile("cgroup.procs", "-1"), PosixErrorIs(EINVAL)); 314 EXPECT_THAT(c.WriteControlFile("cgroup.procs", "not-a-number"), 315 PosixErrorIs(EINVAL)); 316 317 EXPECT_THAT(c.WriteControlFile("tasks", "-1"), PosixErrorIs(EINVAL)); 318 EXPECT_THAT(c.WriteControlFile("tasks", "not-a-number"), 319 PosixErrorIs(EINVAL)); 320 } 321 322 // Regression test for b/222278194. 323 TEST(Cgroup, DuplicateUnlinkOnDirFD) { 324 SKIP_IF(!CgroupsAvailable()); 325 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuset"); 326 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 327 328 // Orphan child directory by opening FD to it then deleting it. 329 const FileDescriptor dirfd = 330 ASSERT_NO_ERRNO_AND_VALUE(Open(child.Path(), 0, 0)); 331 ASSERT_NO_ERRNO(child.Delete()); 332 333 // Replace orphan with new directory of same name, so path resolution 334 // succeeds. 335 Cgroup child_new = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 336 337 // Attempt to delete orphaned child again through dirfd. 338 EXPECT_THAT(UnlinkAt(dirfd, ".", AT_REMOVEDIR), PosixErrorIs(EINVAL)); 339 } 340 341 TEST(Cgroup, MkdirWithPermissions) { 342 SKIP_IF(!CgroupsAvailable()); 343 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuset"); 344 345 std::string child1_path = JoinPath(c.Path(), "child1"); 346 std::string child2_path = JoinPath(c.Path(), "child2"); 347 348 ASSERT_NO_ERRNO(Mkdir(child1_path, 0444)); 349 const struct stat s1 = ASSERT_NO_ERRNO_AND_VALUE(Stat(child1_path)); 350 EXPECT_THAT(s1.st_mode, PermissionIs(0444)); 351 EXPECT_TRUE(S_ISDIR(s1.st_mode)); 352 353 ASSERT_NO_ERRNO(Mkdir(child2_path, 0)); 354 const struct stat s2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(child2_path)); 355 EXPECT_THAT(s2.st_mode, PermissionIs(0000)); 356 EXPECT_TRUE(S_ISDIR(s2.st_mode)); 357 } 358 359 TEST(Cgroup, CantRenameControlFile) { 360 SKIP_IF(!CgroupsAvailable()); 361 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 362 363 const std::string control_file_path = c.Relpath("cgroup.procs"); 364 EXPECT_THAT( 365 rename(c.Relpath("cgroup.procs").c_str(), c.Relpath("foo").c_str()), 366 SyscallFailsWithErrno(ENOTDIR)); 367 } 368 369 TEST(Cgroup, CrossDirRenameNotAllowed) { 370 SKIP_IF(!CgroupsAvailable()); 371 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 372 373 Cgroup dir1 = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("dir1")); 374 Cgroup dir2 = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("dir2")); 375 376 Cgroup target = ASSERT_NO_ERRNO_AND_VALUE(dir1.CreateChild("target")); 377 // Move to sibling directory. 378 EXPECT_THAT(rename(target.Path().c_str(), dir2.Relpath("target").c_str()), 379 SyscallFailsWithErrno(EIO)); 380 // Move to parent directory. 381 EXPECT_THAT(rename(target.Path().c_str(), c.Relpath("target").c_str()), 382 SyscallFailsWithErrno(EIO)); 383 384 // Original directory unaffected. 385 EXPECT_THAT(Exists(target.Path()), IsPosixErrorOkAndHolds(true)); 386 } 387 388 TEST(Cgroup, RenameNameCollision) { 389 SKIP_IF(!CgroupsAvailable()); 390 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 391 392 Cgroup dir1 = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("dir1")); 393 Cgroup dir2 = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("dir2")); 394 395 // Collision with dir. 396 EXPECT_THAT(rename(dir1.Path().c_str(), dir2.Path().c_str()), 397 SyscallFailsWithErrno(EEXIST)); 398 // Collision with control file. 399 EXPECT_THAT(rename(dir1.Path().c_str(), c.Relpath("cgroup.procs").c_str()), 400 SyscallFailsWithErrno(EEXIST)); 401 } 402 403 TEST(Cgroup, Rename) { 404 SKIP_IF(!CgroupsAvailable()); 405 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 406 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 407 Cgroup target = ASSERT_NO_ERRNO_AND_VALUE(child.CreateChild("oldname")); 408 ASSERT_THAT(rename(target.Path().c_str(), child.Relpath("newname").c_str()), 409 SyscallSucceeds()); 410 EXPECT_THAT(Exists(child.Relpath("newname")), IsPosixErrorOkAndHolds(true)); 411 EXPECT_THAT(Exists(child.Relpath("oldname")), IsPosixErrorOkAndHolds(false)); 412 } 413 414 TEST(Cgroup, PIDZeroMovesSelf) { 415 SKIP_IF(!CgroupsAvailable()); 416 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 417 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 418 419 // Source contains this process. 420 EXPECT_NO_ERRNO(c.ContainsCallingProcess()); 421 422 // Move to child by writing PID 0. 423 ASSERT_NO_ERRNO(child.WriteIntegerControlFile("cgroup.procs", 0)); 424 425 // Destination now contains this process, and source does not. 426 EXPECT_NO_ERRNO(child.ContainsCallingProcess()); 427 auto procs = ASSERT_NO_ERRNO_AND_VALUE(c.Procs()); 428 EXPECT_FALSE(procs.contains(getpid())); 429 } 430 431 TEST(Cgroup, TIDZeroMovesSelf) { 432 SKIP_IF(!CgroupsAvailable()); 433 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 434 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 435 436 // Source contains this thread. 437 EXPECT_NO_ERRNO(c.ContainsCallingThread()); 438 439 // Move to child by writing TID 0. 440 ASSERT_NO_ERRNO(child.WriteIntegerControlFile("tasks", 0)); 441 442 // Destination now contains this thread, and source does not. 443 EXPECT_NO_ERRNO(child.ContainsCallingThread()); 444 auto tasks = ASSERT_NO_ERRNO_AND_VALUE(c.Tasks()); 445 EXPECT_FALSE(tasks.contains(syscall(SYS_gettid))); 446 } 447 448 TEST(Cgroup, NamedHierarchies) { 449 SKIP_IF(!CgroupsAvailable()); 450 451 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 452 Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("none,name=h1")); 453 Cgroup c2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("none,name=h2")); 454 455 // Check that /proc/<pid>/cgroup contains an entry for this task. 456 absl::flat_hash_map<std::string, PIDCgroupEntry> entries = 457 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 458 EXPECT_TRUE(entries.contains("name=h1")); 459 EXPECT_TRUE(entries.contains("name=h2")); 460 EXPECT_NO_ERRNO(c1.ContainsCallingProcess()); 461 EXPECT_NO_ERRNO(c2.ContainsCallingProcess()); 462 } 463 464 TEST(Cgroup, NoneExclusiveWithAnyController) { 465 SKIP_IF(!CgroupsAvailable()); 466 467 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 468 EXPECT_THAT(m.MountCgroupfs("none,cpu"), PosixErrorIs(EINVAL, _)); 469 } 470 471 TEST(Cgroup, EmptyHierarchyMustHaveName) { 472 SKIP_IF(!CgroupsAvailable()); 473 474 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 475 // This will fail since it is an empty hierarchy with no name. 476 EXPECT_THAT(m.MountCgroupfs("none"), PosixErrorIs(EINVAL, _)); 477 } 478 479 TEST(Cgroup, NameMatchButControllersDont) { 480 SKIP_IF(!CgroupsAvailable()); 481 482 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 483 Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("none,name=h1")); 484 EXPECT_THAT(m.MountCgroupfs("name=h2,memory"), PosixErrorIs(EBUSY, _)); 485 EXPECT_THAT(m.MountCgroupfs("name=h1,memory"), PosixErrorIs(EBUSY, _)); 486 EXPECT_THAT(m.MountCgroupfs("name=h2,cpu"), PosixErrorIs(EBUSY, _)); 487 } 488 489 TEST(MemoryCgroup, MemoryUsageInBytes) { 490 SKIP_IF(!CgroupsAvailable()); 491 492 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 493 const uint64_t usage = ASSERT_NO_ERRNO_AND_VALUE( 494 c.ReadIntegerControlFile("memory.usage_in_bytes")); 495 EXPECT_GE(usage, 0); 496 } 497 498 TEST(CPUCgroup, ControlFilesHaveDefaultValues) { 499 SKIP_IF(!CgroupsAvailable()); 500 501 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 502 EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_quota_us"), 503 IsPosixErrorOkAndHolds(-1)); 504 EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_period_us"), 505 IsPosixErrorOkAndHolds(100000)); 506 EXPECT_THAT(c.ReadIntegerControlFile("cpu.shares"), 507 IsPosixErrorOkAndHolds(1024)); 508 } 509 510 TEST(CPUAcctCgroup, CPUAcctUsage) { 511 SKIP_IF(!CgroupsAvailable()); 512 513 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 514 const int64_t usage = 515 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage")); 516 const int64_t usage_user = 517 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_user")); 518 const int64_t usage_sys = 519 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_sys")); 520 521 EXPECT_GE(usage, 0); 522 EXPECT_GE(usage_user, 0); 523 EXPECT_GE(usage_sys, 0); 524 525 EXPECT_GE(usage_user + usage_sys, usage); 526 } 527 528 TEST(CPUAcctCgroup, CPUAcctStat) { 529 SKIP_IF(!CgroupsAvailable()); 530 531 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 532 std::string stat = 533 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuacct.stat")); 534 535 // We're expecting the contents of "cpuacct.stat" to look similar to this: 536 // 537 // user 377986 538 // system 220662 539 540 std::vector<absl::string_view> lines = 541 absl::StrSplit(stat, '\n', absl::SkipEmpty()); 542 ASSERT_EQ(lines.size(), 2); 543 544 std::vector<absl::string_view> user_tokens = 545 StrSplit(lines[0], absl::ByChar(' ')); 546 EXPECT_EQ(user_tokens[0], "user"); 547 EXPECT_THAT(Atoi<int64_t>(user_tokens[1]), IsPosixErrorOkAndHolds(Ge(0))); 548 549 std::vector<absl::string_view> sys_tokens = 550 StrSplit(lines[1], absl::ByChar(' ')); 551 EXPECT_EQ(sys_tokens[0], "system"); 552 EXPECT_THAT(Atoi<int64_t>(sys_tokens[1]), IsPosixErrorOkAndHolds(Ge(0))); 553 } 554 555 TEST(CPUAcctCgroup, HierarchicalAccounting) { 556 SKIP_IF(!CgroupsAvailable()); 557 558 Cgroup root = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 559 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(root.CreateChild("child1")); 560 561 // The test starts in the root cgroup, so its CPU usage should be accounted 562 // there. Since the granularity of cpuacct.usage is unspecified and the test 563 // may not have run for very long yet, wait for it to be accounted. 564 ASSERT_NO_ERRNO( 565 root.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 566 EXPECT_THAT(root.ReadIntegerControlFile("cpuacct.usage"), 567 IsPosixErrorOkAndHolds(Gt(0))); 568 569 // Child should have zero usage since it is initially empty. 570 EXPECT_THAT(child.ReadIntegerControlFile("cpuacct.usage"), 571 IsPosixErrorOkAndHolds(Eq(0))); 572 573 // Move test into child and confirm child starts incurring usage. 574 const int64_t before_move = 575 ASSERT_NO_ERRNO_AND_VALUE(root.ReadIntegerControlFile("cpuacct.usage")); 576 ASSERT_NO_ERRNO(child.Enter(getpid())); 577 ASSERT_NO_ERRNO( 578 child.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 579 580 EXPECT_THAT(child.ReadIntegerControlFile("cpuacct.usage"), 581 IsPosixErrorOkAndHolds(Gt(0))); 582 583 // Root shouldn't lose usage due to the migration. 584 const int64_t after_move = 585 ASSERT_NO_ERRNO_AND_VALUE(root.ReadIntegerControlFile("cpuacct.usage")); 586 EXPECT_GE(after_move, before_move); 587 588 // Root should continue to gain usage after the move since child is a 589 // subcgroup. 590 ASSERT_NO_ERRNO( 591 child.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 592 EXPECT_THAT(root.ReadIntegerControlFile("cpuacct.usage"), 593 IsPosixErrorOkAndHolds(Ge(after_move))); 594 } 595 596 TEST(CPUAcctCgroup, IndirectCharge) { 597 SKIP_IF(!CgroupsAvailable()); 598 599 Cgroup root = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 600 Cgroup child1 = ASSERT_NO_ERRNO_AND_VALUE(root.CreateChild("child1")); 601 Cgroup child2 = ASSERT_NO_ERRNO_AND_VALUE(root.CreateChild("child2")); 602 Cgroup child2a = ASSERT_NO_ERRNO_AND_VALUE(child2.CreateChild("child2a")); 603 604 ASSERT_NO_ERRNO(child1.Enter(getpid())); 605 ASSERT_NO_ERRNO( 606 child1.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 607 608 // Only root and child1 should have usage. 609 for (auto const& cg : {root, child1}) { 610 EXPECT_THAT(cg.ReadIntegerControlFile("cpuacct.usage"), 611 IsPosixErrorOkAndHolds(Gt(0))); 612 } 613 for (auto const& cg : {child2, child2a}) { 614 EXPECT_THAT(cg.ReadIntegerControlFile("cpuacct.usage"), 615 IsPosixErrorOkAndHolds(Eq(0))); 616 } 617 618 ASSERT_NO_ERRNO(child2a.Enter(getpid())); 619 ASSERT_NO_ERRNO( 620 child2a.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 621 622 const int64_t snapshot_root = 623 ASSERT_NO_ERRNO_AND_VALUE(root.ReadIntegerControlFile("cpuacct.usage")); 624 const int64_t snapshot_child1 = 625 ASSERT_NO_ERRNO_AND_VALUE(child1.ReadIntegerControlFile("cpuacct.usage")); 626 const int64_t snapshot_child2 = 627 ASSERT_NO_ERRNO_AND_VALUE(child2.ReadIntegerControlFile("cpuacct.usage")); 628 const int64_t snapshot_child2a = ASSERT_NO_ERRNO_AND_VALUE( 629 child2a.ReadIntegerControlFile("cpuacct.usage")); 630 631 ASSERT_NO_ERRNO( 632 child2a.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 633 634 // Root, child2 and child2a should've accumulated new usage. Child1 should 635 // not. 636 const int64_t now_root = 637 ASSERT_NO_ERRNO_AND_VALUE(root.ReadIntegerControlFile("cpuacct.usage")); 638 const int64_t now_child1 = 639 ASSERT_NO_ERRNO_AND_VALUE(child1.ReadIntegerControlFile("cpuacct.usage")); 640 const int64_t now_child2 = 641 ASSERT_NO_ERRNO_AND_VALUE(child2.ReadIntegerControlFile("cpuacct.usage")); 642 const int64_t now_child2a = ASSERT_NO_ERRNO_AND_VALUE( 643 child2a.ReadIntegerControlFile("cpuacct.usage")); 644 645 EXPECT_GT(now_root, snapshot_root); 646 EXPECT_GT(now_child2, snapshot_child2); 647 EXPECT_GT(now_child2a, snapshot_child2a); 648 EXPECT_EQ(now_child1, snapshot_child1); 649 } 650 651 TEST(CPUAcctCgroup, NoDoubleAccounting) { 652 SKIP_IF(!CgroupsAvailable()); 653 654 Cgroup root = Cgroup::RootCgroup("/sys/fs/cgroup/cpuacct"); 655 Cgroup parent = ASSERT_NO_ERRNO_AND_VALUE(root.CreateChild("parent")); 656 Cgroup a = ASSERT_NO_ERRNO_AND_VALUE(parent.CreateChild("a")); 657 Cgroup b = ASSERT_NO_ERRNO_AND_VALUE(parent.CreateChild("b")); 658 659 ASSERT_NO_ERRNO(a.Enter(getpid())); 660 ASSERT_NO_ERRNO( 661 a.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 662 663 ASSERT_NO_ERRNO(b.Enter(getpid())); 664 ASSERT_NO_ERRNO( 665 b.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 666 667 ASSERT_NO_ERRNO(root.Enter(getpid())); 668 ASSERT_NO_ERRNO( 669 root.PollControlFileForChange("cpuacct.usage", absl::Seconds(5))); 670 671 // The usage for parent, a & b should now be frozen, since they no longer have 672 // any tasks. Root will continue to accumulate usage. 673 const int64_t usage_root = 674 ASSERT_NO_ERRNO_AND_VALUE(root.ReadIntegerControlFile("cpuacct.usage")); 675 const int64_t usage_parent = 676 ASSERT_NO_ERRNO_AND_VALUE(parent.ReadIntegerControlFile("cpuacct.usage")); 677 const int64_t usage_a = 678 ASSERT_NO_ERRNO_AND_VALUE(a.ReadIntegerControlFile("cpuacct.usage")); 679 const int64_t usage_b = 680 ASSERT_NO_ERRNO_AND_VALUE(b.ReadIntegerControlFile("cpuacct.usage")); 681 682 EXPECT_GT(usage_root, 0); 683 EXPECT_GT(usage_parent, 0); 684 EXPECT_GT(usage_a, 0); 685 EXPECT_GT(usage_b, 0); 686 EXPECT_EQ(usage_parent, usage_a + usage_b); 687 EXPECT_GE(usage_parent, usage_a); 688 EXPECT_GE(usage_parent, usage_b); 689 EXPECT_GE(usage_root, usage_parent); 690 } 691 692 // WriteAndVerifyControlValue attempts to write val to a cgroup file at path, 693 // and verify the value by reading it afterwards. 694 PosixError WriteAndVerifyControlValue(const Cgroup& c, absl::string_view path, 695 int64_t val) { 696 RETURN_IF_ERRNO(c.WriteIntegerControlFile(path, val)); 697 ASSIGN_OR_RETURN_ERRNO(int64_t newval, c.ReadIntegerControlFile(path)); 698 if (newval != val) { 699 return PosixError( 700 EINVAL, 701 absl::StrFormat( 702 "Unexpected value for control file '%s': expected %d, got %d", path, 703 val, newval)); 704 } 705 return NoError(); 706 } 707 708 PosixErrorOr<std::vector<bool>> ParseBitmap(std::string s) { 709 std::vector<bool> bitmap; 710 bitmap.reserve(64); 711 for (const absl::string_view& t : absl::StrSplit(s, ',')) { 712 std::vector<std::string> parts = absl::StrSplit(t, absl::MaxSplits('-', 2)); 713 if (parts.size() == 2) { 714 ASSIGN_OR_RETURN_ERRNO(uint64_t start, Atoi<uint64_t>(parts[0])); 715 ASSIGN_OR_RETURN_ERRNO(uint64_t end, Atoi<uint64_t>(parts[1])); 716 // Note: start and end are indices into bitmap. 717 if (end >= bitmap.size()) { 718 bitmap.resize(end + 1, false); 719 } 720 for (uint64_t i = start; i <= end; ++i) { 721 bitmap[i] = true; 722 } 723 } else { // parts.size() == 1, 0 not possible. 724 ASSIGN_OR_RETURN_ERRNO(uint64_t i, Atoi<uint64_t>(parts[0])); 725 if (i >= bitmap.size()) { 726 bitmap.resize(i + 1, false); 727 } 728 bitmap[i] = true; 729 } 730 } 731 return bitmap; 732 } 733 734 TEST(JobCgroup, ReadWriteRead) { 735 SKIP_IF(!CgroupsAvailable()); 736 737 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/job"); 738 739 EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(0)); 740 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", 1234)); 741 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", -1)); 742 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MIN)); 743 EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MAX)); 744 } 745 746 TEST(CpusetCgroup, Defaults) { 747 SKIP_IF(!CgroupsAvailable()); 748 749 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuset"); 750 std::string cpus = 751 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.cpus")); 752 std::vector<bool> cpus_bitmap = ASSERT_NO_ERRNO_AND_VALUE(ParseBitmap(cpus)); 753 EXPECT_GT(cpus_bitmap.size(), 0); 754 EXPECT_THAT(cpus_bitmap, Each(Eq(true))); 755 756 std::string mems = 757 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.mems")); 758 std::vector<bool> mems_bitmap = ASSERT_NO_ERRNO_AND_VALUE(ParseBitmap(mems)); 759 EXPECT_GT(mems_bitmap.size(), 0); 760 EXPECT_THAT(mems_bitmap, Each(Eq(true))); 761 } 762 763 TEST(CpusetCgroup, SetMask) { 764 SKIP_IF(!CgroupsAvailable()); 765 766 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuset"); 767 std::string cpus = 768 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.cpus")); 769 std::vector<bool> cpus_bitmap = ASSERT_NO_ERRNO_AND_VALUE(ParseBitmap(cpus)); 770 771 SKIP_IF(cpus_bitmap.size() <= 1); // "Not enough CPUs" 772 773 int max_cpu = cpus_bitmap.size() - 1; 774 ASSERT_NO_ERRNO( 775 c.WriteControlFile("cpuset.cpus", absl::StrCat("1-", max_cpu))); 776 cpus_bitmap[0] = false; 777 cpus = ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.cpus")); 778 std::vector<bool> cpus_bitmap_after = 779 ASSERT_NO_ERRNO_AND_VALUE(ParseBitmap(cpus)); 780 EXPECT_EQ(cpus_bitmap_after, cpus_bitmap); 781 } 782 783 TEST(CpusetCgroup, SetEmptyMask) { 784 SKIP_IF(!CgroupsAvailable()); 785 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/cpuset"); 786 ASSERT_NO_ERRNO(c.WriteControlFile("cpuset.cpus", "")); 787 std::string_view cpus = absl::StripAsciiWhitespace( 788 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.cpus"))); 789 EXPECT_EQ(cpus, ""); 790 ASSERT_NO_ERRNO(c.WriteControlFile("cpuset.mems", "")); 791 std::string_view mems = absl::StripAsciiWhitespace( 792 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuset.mems"))); 793 EXPECT_EQ(mems, ""); 794 } 795 796 TEST(ProcCgroups, Empty) { 797 SKIP_IF(!CgroupsAvailable()); 798 799 absl::flat_hash_map<std::string, CgroupsEntry> entries = 800 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 801 // Cgroups are mounted, we should have entries. 802 EXPECT_FALSE(entries.empty()); 803 } 804 805 TEST(ProcCgroups, ProcCgroupsEntries) { 806 SKIP_IF(!CgroupsAvailable()); 807 808 Cgroup mem = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 809 absl::flat_hash_map<std::string, CgroupsEntry> entries = 810 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 811 EXPECT_EQ(entries.size(), 7); 812 ASSERT_TRUE(entries.contains("memory")); 813 CgroupsEntry mem_e = entries["memory"]; 814 EXPECT_EQ(mem_e.subsys_name, "memory"); 815 EXPECT_GE(mem_e.hierarchy, 1); 816 // Expect a single root cgroup. 817 EXPECT_EQ(mem_e.num_cgroups, 2); 818 // Cgroups are currently always enabled when mounted. 819 EXPECT_TRUE(mem_e.enabled); 820 821 // Add a second cgroup, and check for new entry. 822 823 Cgroup cpu = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 824 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 825 EXPECT_EQ(entries.size(), 7); 826 EXPECT_TRUE(entries.contains("memory")); // Still have memory entry. 827 ASSERT_TRUE(entries.contains("cpu")); 828 CgroupsEntry cpu_e = entries["cpu"]; 829 EXPECT_EQ(cpu_e.subsys_name, "cpu"); 830 EXPECT_GE(cpu_e.hierarchy, 1); 831 EXPECT_EQ(cpu_e.num_cgroups, 2); 832 EXPECT_TRUE(cpu_e.enabled); 833 834 // Separate hierarchies, since controllers were mounted separately. 835 EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy); 836 } 837 838 TEST(ProcPIDCgroup, Entries) { 839 SKIP_IF(!CgroupsAvailable()); 840 841 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 842 absl::flat_hash_map<std::string, PIDCgroupEntry> entries = 843 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 844 // All controllers are mounted. 845 EXPECT_EQ(entries.size(), 7); 846 PIDCgroupEntry mem_e = entries["memory"]; 847 EXPECT_GE(mem_e.hierarchy, 1); 848 EXPECT_EQ(mem_e.controllers, "memory"); 849 // The path is /<container-id>. 850 EXPECT_NE(mem_e.path, "/"); 851 852 Cgroup c1 = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 853 entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 854 // All controllers are mounted. 855 EXPECT_EQ(entries.size(), 7); 856 EXPECT_TRUE(entries.contains("memory")); // Still have memory entry. 857 PIDCgroupEntry cpu_e = entries["cpu"]; 858 EXPECT_GE(cpu_e.hierarchy, 1); 859 EXPECT_EQ(cpu_e.controllers, "cpu"); 860 // The path is /<container-id>. 861 EXPECT_NE(cpu_e.path, "/"); 862 863 // Separate hierarchies, since controllers were mounted separately. 864 EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy); 865 } 866 867 TEST(ProcCgroup, PIDCgroupMatchesCgroups) { 868 SKIP_IF(!CgroupsAvailable()); 869 870 Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir())); 871 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/memory"); 872 Cgroup c1 = Cgroup::RootCgroup("/sys/fs/cgroup/cpu"); 873 874 absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries = 875 ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries()); 876 absl::flat_hash_map<std::string, PIDCgroupEntry> pid_entries = 877 ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid())); 878 879 CgroupsEntry cgroup_mem = cgroups_entries["memory"]; 880 PIDCgroupEntry pid_mem = pid_entries["memory"]; 881 882 EXPECT_EQ(cgroup_mem.hierarchy, pid_mem.hierarchy); 883 884 CgroupsEntry cgroup_cpu = cgroups_entries["cpu"]; 885 PIDCgroupEntry pid_cpu = pid_entries["cpu"]; 886 887 EXPECT_EQ(cgroup_cpu.hierarchy, pid_cpu.hierarchy); 888 EXPECT_NE(cgroup_mem.hierarchy, cgroup_cpu.hierarchy); 889 EXPECT_NE(pid_mem.hierarchy, pid_cpu.hierarchy); 890 } 891 892 TEST(PIDsCgroup, ControlFilesExist) { 893 SKIP_IF(!CgroupsAvailable()); 894 895 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/pids"); 896 897 const std::string root_limit = 898 ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("pids.max")); 899 EXPECT_EQ(root_limit, "max\n"); 900 901 // There should be at least one PID in use in the root controller, since the 902 // test process is running in the root controller. 903 const int64_t current = 904 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("pids.current")); 905 EXPECT_GE(current, 1); 906 907 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 908 909 // The limit file should exist for any child cgroups, and should be unlimited 910 // by default. 911 const std::string child_limit = 912 ASSERT_NO_ERRNO_AND_VALUE(child.ReadControlFile("pids.max")); 913 EXPECT_EQ(child_limit, "max\n"); 914 915 // The child cgroup should have no tasks, and thus no pids usage. 916 const int64_t current_child = 917 ASSERT_NO_ERRNO_AND_VALUE(child.ReadIntegerControlFile("pids.current")); 918 EXPECT_EQ(current_child, 0); 919 } 920 921 TEST(PIDsCgroup, ChargeMigration) { 922 SKIP_IF(!CgroupsAvailable()); 923 924 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/pids"); 925 const int64_t root_start = 926 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("pids.current")); 927 // Root should have at least one task. 928 ASSERT_GE(root_start, 1); 929 930 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 931 932 // Child initially has no charge. 933 EXPECT_THAT(child.ReadIntegerControlFile("pids.current"), 934 IsPosixErrorOkAndHolds(0)); 935 936 // Move the test process. The root cgroup should lose charges equal to the 937 // number of tasks moved to the child. 938 ASSERT_NO_ERRNO(child.Enter(getpid())); 939 940 const int64_t child_after = 941 ASSERT_NO_ERRNO_AND_VALUE(child.ReadIntegerControlFile("pids.current")); 942 EXPECT_GE(child_after, 1); 943 944 const int64_t root_after = 945 ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("pids.current")); 946 EXPECT_EQ(root_start - root_after, child_after); 947 } 948 949 TEST(PIDsCgroup, MigrationCanExceedLimit) { 950 SKIP_IF(!CgroupsAvailable()); 951 952 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/pids"); 953 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 954 955 // Set child limit to 0, and try move tasks into it. This should be allowed, 956 // as the limit isn't enforced on migration. 957 ASSERT_NO_ERRNO(child.WriteIntegerControlFile("pids.max", 0)); 958 ASSERT_NO_ERRNO(child.Enter(getpid())); 959 EXPECT_THAT(child.ReadIntegerControlFile("pids.current"), 960 IsPosixErrorOkAndHolds(Gt(0))); 961 } 962 963 TEST(PIDsCgroup, SetInvalidLimit) { 964 SKIP_IF(!CgroupsAvailable()); 965 966 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/pids"); 967 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 968 969 // Set a valid limit, so we can verify it doesn't change after invalid writes. 970 ASSERT_NO_ERRNO(child.WriteIntegerControlFile("pids.max", 1234)); 971 972 EXPECT_THAT(child.WriteControlFile("pids.max", "m a x"), 973 PosixErrorIs(EINVAL, _)); 974 EXPECT_THAT(child.WriteControlFile("pids.max", "some-invalid-string"), 975 PosixErrorIs(EINVAL, _)); 976 EXPECT_THAT(child.WriteControlFile("pids.max", "-1"), 977 PosixErrorIs(EINVAL, _)); 978 EXPECT_THAT(child.WriteControlFile("pids.max", "-3894732"), 979 PosixErrorIs(EINVAL, _)); 980 // This value is much larger than the maximum allowed value of ~ 1<<22. 981 EXPECT_THAT(child.WriteIntegerControlFile("pids.max", LLONG_MAX - 1), 982 PosixErrorIs(EINVAL, _)); 983 984 // The initial valid limit should remain unchanged. 985 EXPECT_THAT(child.ReadIntegerControlFile("pids.max"), 986 IsPosixErrorOkAndHolds(1234)); 987 } 988 989 TEST(PIDsCgroup, CanLowerLimitBelowCurrentCharge) { 990 SKIP_IF(!CgroupsAvailable()); 991 992 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/pids"); 993 Cgroup child = ASSERT_NO_ERRNO_AND_VALUE(c.CreateChild("child")); 994 ASSERT_NO_ERRNO(child.Enter(getpid())); 995 // Confirm current charge is non-zero. 996 ASSERT_THAT(child.ReadIntegerControlFile("pids.current"), 997 IsPosixErrorOkAndHolds(Gt(0))); 998 // Try set limit to zero. 999 EXPECT_NO_ERRNO(child.WriteIntegerControlFile("pids.max", 0)); 1000 } 1001 1002 TEST(DevicesCgroup, ControlFilesExist) { 1003 SKIP_IF(!CgroupsAvailable()); 1004 1005 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/devices"); 1006 1007 // The root group starts with allowing rwm to all. 1008 EXPECT_THAT(c.ReadControlFile("devices.allow"), IsPosixErrorOkAndHolds("")); 1009 EXPECT_THAT(c.ReadControlFile("devices.deny"), IsPosixErrorOkAndHolds("")); 1010 EXPECT_THAT(c.ReadControlFile("devices.list"), 1011 IsPosixErrorOkAndHolds("a *:* rwm")); 1012 } 1013 1014 TEST(DevicesCgroup, DenyAll) { 1015 SKIP_IF(!CgroupsAvailable()); 1016 1017 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/devices"); 1018 1019 ASSERT_NO_ERRNO(c.WriteControlFile("devices.allow", "b *:* rw\n")); 1020 EXPECT_THAT(c.ReadControlFile("devices.list"), 1021 IsPosixErrorOkAndHolds("b *:* rw\n")); 1022 1023 ASSERT_NO_ERRNO(c.WriteControlFile("devices.deny", "a")); 1024 EXPECT_THAT(c.ReadControlFile("devices.list"), IsPosixErrorOkAndHolds("")); 1025 } 1026 1027 TEST(DevicesCgroup, AddDeviceRule) { 1028 SKIP_IF(!CgroupsAvailable()); 1029 1030 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/devices"); 1031 1032 ASSERT_THAT(c.ReadControlFile("devices.list"), 1033 IsPosixErrorOkAndHolds("a *:* rwm")); 1034 // Gives character devices with major device number 7 read and write 1035 // permission. 1036 ASSERT_NO_ERRNO(c.WriteControlFile("devices.allow", "c 7:* rw\n")); 1037 EXPECT_THAT(c.ReadControlFile("devices.list"), 1038 IsPosixErrorOkAndHolds("c 7:* rw\n")); 1039 1040 // Diasllows all devices. 1041 ASSERT_NO_ERRNO(c.WriteControlFile("devices.deny", "a")); 1042 EXPECT_THAT(c.ReadControlFile("devices.list"), IsPosixErrorOkAndHolds("")); 1043 1044 // Adds one more rule. 1045 ASSERT_NO_ERRNO(c.WriteControlFile("devices.allow", "b *:* rw\n")); 1046 EXPECT_THAT(c.ReadControlFile("devices.list"), 1047 IsPosixErrorOkAndHolds("b *:* rw\n")); 1048 } 1049 1050 TEST(DevicesCgroup, RemoveDeviceRule) { 1051 SKIP_IF(!CgroupsAvailable()); 1052 1053 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/devices"); 1054 // The root group starts with allowing rwm to all. 1055 ASSERT_THAT(c.ReadControlFile("devices.list"), 1056 IsPosixErrorOkAndHolds("a *:* rwm")); 1057 // Gives character devices with the major device number 7 read and write 1058 // permission. 1059 ASSERT_NO_ERRNO(c.WriteControlFile("devices.allow", "c 7:* rw")); 1060 EXPECT_THAT(c.ReadControlFile("devices.list"), 1061 IsPosixErrorOkAndHolds("c 7:* rw\n")); 1062 1063 // Removes the write permission from the character devices with the major 1064 // device number 7. 1065 ASSERT_NO_ERRNO(c.WriteControlFile("devices.deny", "c 7:* w")); 1066 EXPECT_THAT(c.ReadControlFile("devices.list"), 1067 IsPosixErrorOkAndHolds("c 7:* r\n")); 1068 } 1069 1070 TEST(DevicesCgroup, IgnorePartialMatchRule) { 1071 SKIP_IF(!CgroupsAvailable()); 1072 1073 Cgroup c = Cgroup::RootCgroup("/sys/fs/cgroup/devices"); 1074 1075 // Gives character devices with the major device number 7 read and write 1076 // permission. 1077 ASSERT_NO_ERRNO(c.WriteControlFile("devices.allow", "c 7:* rw")); 1078 EXPECT_THAT(c.ReadControlFile("devices.list"), 1079 IsPosixErrorOkAndHolds("c 7:* rw\n")); 1080 1081 // Expect no change to the allow list since minor device matches partially a 1082 // exsting rule for character devices 7:*. 1083 ASSERT_NO_ERRNO(c.WriteControlFile("devices.deny", "c 7:0 w")); 1084 EXPECT_THAT(c.ReadControlFile("devices.list"), 1085 IsPosixErrorOkAndHolds("c 7:* rw\n")); 1086 } 1087 1088 } // namespace 1089 } // namespace testing 1090 } // namespace gvisor