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