github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 <sys/mount.h>
    20  #include <unistd.h>
    21  
    22  #include "gtest/gtest.h"
    23  #include "absl/container/flat_hash_map.h"
    24  #include "absl/container/flat_hash_set.h"
    25  #include "absl/strings/str_split.h"
    26  #include "test/util/capability_util.h"
    27  #include "test/util/cgroup_util.h"
    28  #include "test/util/cleanup.h"
    29  #include "test/util/mount_util.h"
    30  #include "test/util/temp_path.h"
    31  #include "test/util/test_util.h"
    32  #include "test/util/thread_util.h"
    33  
    34  namespace gvisor {
    35  namespace testing {
    36  namespace {
    37  
    38  using ::testing::_;
    39  using ::testing::Contains;
    40  using ::testing::Ge;
    41  using ::testing::Gt;
    42  using ::testing::Key;
    43  using ::testing::Not;
    44  
    45  std::vector<std::string> known_controllers = {
    46      "cpu", "cpuset", "cpuacct", "job", "memory",
    47  };
    48  
    49  bool CgroupsAvailable() {
    50    return IsRunningOnGvisor() && !IsRunningWithVFS1() &&
    51           TEST_CHECK_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN));
    52  }
    53  
    54  TEST(Cgroup, MountSucceeds) {
    55    SKIP_IF(!CgroupsAvailable());
    56  
    57    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
    58    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
    59    EXPECT_NO_ERRNO(c.ContainsCallingProcess());
    60  }
    61  
    62  TEST(Cgroup, SeparateMounts) {
    63    SKIP_IF(!CgroupsAvailable());
    64  
    65    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
    66  
    67    for (const auto& ctl : known_controllers) {
    68      Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(ctl));
    69      EXPECT_NO_ERRNO(c.ContainsCallingProcess());
    70    }
    71  }
    72  
    73  TEST(Cgroup, AllControllersImplicit) {
    74    SKIP_IF(!CgroupsAvailable());
    75  
    76    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
    77    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
    78  
    79    absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries =
    80        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
    81    for (const auto& ctl : known_controllers) {
    82      EXPECT_TRUE(cgroups_entries.contains(ctl))
    83          << absl::StreamFormat("ctl=%s", ctl);
    84    }
    85    EXPECT_EQ(cgroups_entries.size(), known_controllers.size());
    86  }
    87  
    88  TEST(Cgroup, AllControllersExplicit) {
    89    SKIP_IF(!CgroupsAvailable());
    90  
    91    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
    92    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("all"));
    93  
    94    absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries =
    95        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
    96    for (const auto& ctl : known_controllers) {
    97      EXPECT_TRUE(cgroups_entries.contains(ctl))
    98          << absl::StreamFormat("ctl=%s", ctl);
    99    }
   100    EXPECT_EQ(cgroups_entries.size(), known_controllers.size());
   101  }
   102  
   103  TEST(Cgroup, ProcsAndTasks) {
   104    SKIP_IF(!CgroupsAvailable());
   105  
   106    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   107    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
   108    absl::flat_hash_set<pid_t> pids = ASSERT_NO_ERRNO_AND_VALUE(c.Procs());
   109    absl::flat_hash_set<pid_t> tids = ASSERT_NO_ERRNO_AND_VALUE(c.Tasks());
   110  
   111    EXPECT_GE(tids.size(), pids.size()) << "Found more processes than threads";
   112  
   113    // Pids should be a strict subset of tids.
   114    for (auto it = pids.begin(); it != pids.end(); ++it) {
   115      EXPECT_TRUE(tids.contains(*it))
   116          << absl::StreamFormat("Have pid %d, but no such tid", *it);
   117    }
   118  }
   119  
   120  TEST(Cgroup, ControllersMustBeInUniqueHierarchy) {
   121    SKIP_IF(!CgroupsAvailable());
   122  
   123    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   124    // Hierarchy #1: all controllers.
   125    Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
   126    // Hierarchy #2: memory.
   127    //
   128    // This should conflict since memory is already in hierarchy #1, and the two
   129    // hierarchies have different sets of controllers, so this mount can't be a
   130    // view into hierarchy #1.
   131    EXPECT_THAT(m.MountCgroupfs("memory"), PosixErrorIs(EBUSY, _))
   132        << "Memory controller mounted on two hierarchies";
   133    EXPECT_THAT(m.MountCgroupfs("cpu"), PosixErrorIs(EBUSY, _))
   134        << "CPU controller mounted on two hierarchies";
   135  }
   136  
   137  TEST(Cgroup, UnmountFreesControllers) {
   138    SKIP_IF(!CgroupsAvailable());
   139    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   140    Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
   141    // All controllers are now attached to all's hierarchy. Attempting new mount
   142    // with any individual controller should fail.
   143    EXPECT_THAT(m.MountCgroupfs("memory"), PosixErrorIs(EBUSY, _))
   144        << "Memory controller mounted on two hierarchies";
   145  
   146    // Unmount the "all" hierarchy. This should enable any controller to be
   147    // mounted on a new hierarchy again.
   148    ASSERT_NO_ERRNO(m.Unmount(all));
   149    EXPECT_NO_ERRNO(m.MountCgroupfs("memory"));
   150    EXPECT_NO_ERRNO(m.MountCgroupfs("cpu"));
   151  }
   152  
   153  TEST(Cgroup, OnlyContainsControllerSpecificFiles) {
   154    SKIP_IF(!CgroupsAvailable());
   155    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   156    Cgroup mem = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
   157    EXPECT_THAT(Exists(mem.Relpath("memory.usage_in_bytes")),
   158                IsPosixErrorOkAndHolds(true));
   159    // CPU files shouldn't exist in memory cgroups.
   160    EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_period_us")),
   161                IsPosixErrorOkAndHolds(false));
   162    EXPECT_THAT(Exists(mem.Relpath("cpu.cfs_quota_us")),
   163                IsPosixErrorOkAndHolds(false));
   164    EXPECT_THAT(Exists(mem.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(false));
   165  
   166    Cgroup cpu = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
   167    EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_period_us")),
   168                IsPosixErrorOkAndHolds(true));
   169    EXPECT_THAT(Exists(cpu.Relpath("cpu.cfs_quota_us")),
   170                IsPosixErrorOkAndHolds(true));
   171    EXPECT_THAT(Exists(cpu.Relpath("cpu.shares")), IsPosixErrorOkAndHolds(true));
   172    // Memory files shouldn't exist in cpu cgroups.
   173    EXPECT_THAT(Exists(cpu.Relpath("memory.usage_in_bytes")),
   174                IsPosixErrorOkAndHolds(false));
   175  }
   176  
   177  TEST(Cgroup, InvalidController) {
   178    SKIP_IF(!CgroupsAvailable());
   179  
   180    TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   181    std::string mopts = "this-controller-is-invalid";
   182    EXPECT_THAT(
   183        mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()),
   184        SyscallFailsWithErrno(EINVAL));
   185  }
   186  
   187  TEST(Cgroup, MoptAllMustBeExclusive) {
   188    SKIP_IF(!CgroupsAvailable());
   189  
   190    TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   191    std::string mopts = "all,cpu";
   192    EXPECT_THAT(
   193        mount("none", mountpoint.path().c_str(), "cgroup", 0, mopts.c_str()),
   194        SyscallFailsWithErrno(EINVAL));
   195  }
   196  
   197  TEST(Cgroup, MountRace) {
   198    SKIP_IF(!CgroupsAvailable());
   199  
   200    TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   201  
   202    const DisableSave ds;  // Too many syscalls.
   203  
   204    auto mount_thread = [&mountpoint]() {
   205      for (int i = 0; i < 100; ++i) {
   206        mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
   207      }
   208    };
   209    std::list<ScopedThread> threads;
   210    for (int i = 0; i < 10; ++i) {
   211      threads.emplace_back(mount_thread);
   212    }
   213    for (auto& t : threads) {
   214      t.Join();
   215    }
   216  
   217    auto cleanup = Cleanup([&mountpoint] {
   218      // We need 1 umount call per successful mount. If some of the mount calls
   219      // were unsuccessful, their corresponding umount will silently fail.
   220      for (int i = 0; i < (10 * 100) + 1; ++i) {
   221        umount(mountpoint.path().c_str());
   222      }
   223    });
   224  
   225    Cgroup c = Cgroup(mountpoint.path());
   226    // c should be a valid cgroup.
   227    EXPECT_NO_ERRNO(c.ContainsCallingProcess());
   228  }
   229  
   230  TEST(Cgroup, MountUnmountRace) {
   231    SKIP_IF(!CgroupsAvailable());
   232  
   233    TempPath mountpoint = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   234  
   235    const DisableSave ds;  // Too many syscalls.
   236  
   237    auto mount_thread = [&mountpoint]() {
   238      for (int i = 0; i < 100; ++i) {
   239        mount("none", mountpoint.path().c_str(), "cgroup", 0, 0);
   240      }
   241    };
   242    auto unmount_thread = [&mountpoint]() {
   243      for (int i = 0; i < 100; ++i) {
   244        umount(mountpoint.path().c_str());
   245      }
   246    };
   247    std::list<ScopedThread> threads;
   248    for (int i = 0; i < 10; ++i) {
   249      threads.emplace_back(mount_thread);
   250    }
   251    for (int i = 0; i < 10; ++i) {
   252      threads.emplace_back(unmount_thread);
   253    }
   254    for (auto& t : threads) {
   255      t.Join();
   256    }
   257  
   258    // We don't know how many mount refs are remaining, since the count depends on
   259    // the ordering of mount and umount calls. Keep calling unmount until it
   260    // returns an error.
   261    while (umount(mountpoint.path().c_str()) == 0) {
   262    }
   263  }
   264  
   265  TEST(Cgroup, UnmountRepeated) {
   266    SKIP_IF(!CgroupsAvailable());
   267  
   268    const DisableSave ds;  // Too many syscalls.
   269  
   270    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   271    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
   272  
   273    // First unmount should succeed.
   274    EXPECT_THAT(umount(c.Path().c_str()), SyscallSucceeds());
   275  
   276    // We just manually unmounted, so release managed resources.
   277    m.release(c);
   278  
   279    EXPECT_THAT(umount(c.Path().c_str()), SyscallFailsWithErrno(EINVAL));
   280  }
   281  
   282  TEST(MemoryCgroup, MemoryUsageInBytes) {
   283    SKIP_IF(!CgroupsAvailable());
   284  
   285    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   286    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
   287    EXPECT_THAT(c.ReadIntegerControlFile("memory.usage_in_bytes"),
   288                IsPosixErrorOkAndHolds(Gt(0)));
   289  }
   290  
   291  TEST(CPUCgroup, ControlFilesHaveDefaultValues) {
   292    SKIP_IF(!CgroupsAvailable());
   293  
   294    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   295    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
   296    EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_quota_us"),
   297                IsPosixErrorOkAndHolds(-1));
   298    EXPECT_THAT(c.ReadIntegerControlFile("cpu.cfs_period_us"),
   299                IsPosixErrorOkAndHolds(100000));
   300    EXPECT_THAT(c.ReadIntegerControlFile("cpu.shares"),
   301                IsPosixErrorOkAndHolds(1024));
   302  }
   303  
   304  TEST(CPUAcctCgroup, CPUAcctUsage) {
   305    SKIP_IF(!CgroupsAvailable());
   306  
   307    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   308    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct"));
   309  
   310    const int64_t usage =
   311        ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage"));
   312    const int64_t usage_user =
   313        ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_user"));
   314    const int64_t usage_sys =
   315        ASSERT_NO_ERRNO_AND_VALUE(c.ReadIntegerControlFile("cpuacct.usage_sys"));
   316  
   317    EXPECT_GE(usage, 0);
   318    EXPECT_GE(usage_user, 0);
   319    EXPECT_GE(usage_sys, 0);
   320  
   321    EXPECT_GE(usage_user + usage_sys, usage);
   322  }
   323  
   324  TEST(CPUAcctCgroup, CPUAcctStat) {
   325    SKIP_IF(!CgroupsAvailable());
   326  
   327    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   328    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct"));
   329  
   330    std::string stat =
   331        ASSERT_NO_ERRNO_AND_VALUE(c.ReadControlFile("cpuacct.stat"));
   332  
   333    // We're expecting the contents of "cpuacct.stat" to look similar to this:
   334    //
   335    // user 377986
   336    // system 220662
   337  
   338    std::vector<absl::string_view> lines =
   339        absl::StrSplit(stat, '\n', absl::SkipEmpty());
   340    ASSERT_EQ(lines.size(), 2);
   341  
   342    std::vector<absl::string_view> user_tokens =
   343        StrSplit(lines[0], absl::ByChar(' '));
   344    EXPECT_EQ(user_tokens[0], "user");
   345    EXPECT_THAT(Atoi<int64_t>(user_tokens[1]), IsPosixErrorOkAndHolds(Ge(0)));
   346  
   347    std::vector<absl::string_view> sys_tokens =
   348        StrSplit(lines[1], absl::ByChar(' '));
   349    EXPECT_EQ(sys_tokens[0], "system");
   350    EXPECT_THAT(Atoi<int64_t>(sys_tokens[1]), IsPosixErrorOkAndHolds(Ge(0)));
   351  }
   352  
   353  // WriteAndVerifyControlValue attempts to write val to a cgroup file at path,
   354  // and verify the value by reading it afterwards.
   355  PosixError WriteAndVerifyControlValue(const Cgroup& c, std::string_view path,
   356                                        int64_t val) {
   357    RETURN_IF_ERRNO(c.WriteIntegerControlFile(path, val));
   358    ASSIGN_OR_RETURN_ERRNO(int64_t newval, c.ReadIntegerControlFile(path));
   359    if (newval != val) {
   360      return PosixError(
   361          EINVAL,
   362          absl::StrFormat(
   363              "Unexpected value for control file '%s': expected %d, got %d", path,
   364              val, newval));
   365    }
   366    return NoError();
   367  }
   368  
   369  TEST(JobCgroup, ReadWriteRead) {
   370    SKIP_IF(!CgroupsAvailable());
   371  
   372    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   373    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("job"));
   374  
   375    EXPECT_THAT(c.ReadIntegerControlFile("job.id"), IsPosixErrorOkAndHolds(0));
   376    EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", 1234));
   377    EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", -1));
   378    EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MIN));
   379    EXPECT_NO_ERRNO(WriteAndVerifyControlValue(c, "job.id", LLONG_MAX));
   380  }
   381  
   382  TEST(ProcCgroups, Empty) {
   383    SKIP_IF(!CgroupsAvailable());
   384  
   385    absl::flat_hash_map<std::string, CgroupsEntry> entries =
   386        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   387    // No cgroups mounted yet, we should have no entries.
   388    EXPECT_TRUE(entries.empty());
   389  }
   390  
   391  TEST(ProcCgroups, ProcCgroupsEntries) {
   392    SKIP_IF(!CgroupsAvailable());
   393  
   394    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   395  
   396    Cgroup mem = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
   397    absl::flat_hash_map<std::string, CgroupsEntry> entries =
   398        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   399    EXPECT_EQ(entries.size(), 1);
   400    ASSERT_TRUE(entries.contains("memory"));
   401    CgroupsEntry mem_e = entries["memory"];
   402    EXPECT_EQ(mem_e.subsys_name, "memory");
   403    EXPECT_GE(mem_e.hierarchy, 1);
   404    // Expect a single root cgroup.
   405    EXPECT_EQ(mem_e.num_cgroups, 1);
   406    // Cgroups are currently always enabled when mounted.
   407    EXPECT_TRUE(mem_e.enabled);
   408  
   409    // Add a second cgroup, and check for new entry.
   410  
   411    Cgroup cpu = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
   412    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   413    EXPECT_EQ(entries.size(), 2);
   414    EXPECT_TRUE(entries.contains("memory"));  // Still have memory entry.
   415    ASSERT_TRUE(entries.contains("cpu"));
   416    CgroupsEntry cpu_e = entries["cpu"];
   417    EXPECT_EQ(cpu_e.subsys_name, "cpu");
   418    EXPECT_GE(cpu_e.hierarchy, 1);
   419    EXPECT_EQ(cpu_e.num_cgroups, 1);
   420    EXPECT_TRUE(cpu_e.enabled);
   421  
   422    // Separate hierarchies, since controllers were mounted separately.
   423    EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy);
   424  }
   425  
   426  TEST(ProcCgroups, UnmountRemovesEntries) {
   427    SKIP_IF(!CgroupsAvailable());
   428  
   429    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   430    Cgroup cg = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu,memory"));
   431    absl::flat_hash_map<std::string, CgroupsEntry> entries =
   432        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   433    EXPECT_EQ(entries.size(), 2);
   434  
   435    ASSERT_NO_ERRNO(m.Unmount(cg));
   436  
   437    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   438    EXPECT_TRUE(entries.empty());
   439  }
   440  
   441  TEST(ProcPIDCgroup, Empty) {
   442    SKIP_IF(!CgroupsAvailable());
   443  
   444    absl::flat_hash_map<std::string, PIDCgroupEntry> entries =
   445        ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   446    EXPECT_TRUE(entries.empty());
   447  }
   448  
   449  TEST(ProcPIDCgroup, Entries) {
   450    SKIP_IF(!CgroupsAvailable());
   451  
   452    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   453    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
   454  
   455    absl::flat_hash_map<std::string, PIDCgroupEntry> entries =
   456        ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   457    EXPECT_EQ(entries.size(), 1);
   458    PIDCgroupEntry mem_e = entries["memory"];
   459    EXPECT_GE(mem_e.hierarchy, 1);
   460    EXPECT_EQ(mem_e.controllers, "memory");
   461    EXPECT_EQ(mem_e.path, "/");
   462  
   463    Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
   464    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   465    EXPECT_EQ(entries.size(), 2);
   466    EXPECT_TRUE(entries.contains("memory"));  // Still have memory entry.
   467    PIDCgroupEntry cpu_e = entries["cpu"];
   468    EXPECT_GE(cpu_e.hierarchy, 1);
   469    EXPECT_EQ(cpu_e.controllers, "cpu");
   470    EXPECT_EQ(cpu_e.path, "/");
   471  
   472    // Separate hierarchies, since controllers were mounted separately.
   473    EXPECT_NE(mem_e.hierarchy, cpu_e.hierarchy);
   474  }
   475  
   476  TEST(ProcPIDCgroup, UnmountRemovesEntries) {
   477    SKIP_IF(!CgroupsAvailable());
   478  
   479    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   480    Cgroup all = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs(""));
   481  
   482    absl::flat_hash_map<std::string, PIDCgroupEntry> entries =
   483        ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   484    EXPECT_GT(entries.size(), 0);
   485  
   486    ASSERT_NO_ERRNO(m.Unmount(all));
   487  
   488    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   489    EXPECT_TRUE(entries.empty());
   490  }
   491  
   492  TEST(ProcCgroup, PIDCgroupMatchesCgroups) {
   493    SKIP_IF(!CgroupsAvailable());
   494  
   495    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   496    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory"));
   497    Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpu"));
   498  
   499    absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries =
   500        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   501    absl::flat_hash_map<std::string, PIDCgroupEntry> pid_entries =
   502        ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   503  
   504    CgroupsEntry cgroup_mem = cgroups_entries["memory"];
   505    PIDCgroupEntry pid_mem = pid_entries["memory"];
   506  
   507    EXPECT_EQ(cgroup_mem.hierarchy, pid_mem.hierarchy);
   508  
   509    CgroupsEntry cgroup_cpu = cgroups_entries["cpu"];
   510    PIDCgroupEntry pid_cpu = pid_entries["cpu"];
   511  
   512    EXPECT_EQ(cgroup_cpu.hierarchy, pid_cpu.hierarchy);
   513    EXPECT_NE(cgroup_mem.hierarchy, cgroup_cpu.hierarchy);
   514    EXPECT_NE(pid_mem.hierarchy, pid_cpu.hierarchy);
   515  }
   516  
   517  TEST(ProcCgroup, MultiControllerHierarchy) {
   518    SKIP_IF(!CgroupsAvailable());
   519  
   520    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   521    Cgroup c = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu"));
   522  
   523    absl::flat_hash_map<std::string, CgroupsEntry> cgroups_entries =
   524        ASSERT_NO_ERRNO_AND_VALUE(ProcCgroupsEntries());
   525  
   526    CgroupsEntry mem_e = cgroups_entries["memory"];
   527    CgroupsEntry cpu_e = cgroups_entries["cpu"];
   528  
   529    // Both controllers should have the same hierarchy ID.
   530    EXPECT_EQ(mem_e.hierarchy, cpu_e.hierarchy);
   531  
   532    absl::flat_hash_map<std::string, PIDCgroupEntry> pid_entries =
   533        ASSERT_NO_ERRNO_AND_VALUE(ProcPIDCgroupEntries(getpid()));
   534  
   535    // Expecting an entry listing both controllers, that matches the previous
   536    // hierarchy ID. Note that the controllers are listed in alphabetical order.
   537    PIDCgroupEntry pid_e = pid_entries["cpu,memory"];
   538    EXPECT_EQ(pid_e.hierarchy, mem_e.hierarchy);
   539  }
   540  
   541  TEST(ProcCgroup, ProcfsReportsCgroupfsMountOptions) {
   542    SKIP_IF(!CgroupsAvailable());
   543  
   544    Mounter m(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()));
   545    // Hierarchy with multiple controllers.
   546    Cgroup c1 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("memory,cpu"));
   547    // Hierarchy with a single controller.
   548    Cgroup c2 = ASSERT_NO_ERRNO_AND_VALUE(m.MountCgroupfs("cpuacct"));
   549  
   550    const std::vector<ProcMountsEntry> mounts =
   551        ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountsEntries());
   552  
   553    for (auto const& e : mounts) {
   554      if (e.mount_point == c1.Path()) {
   555        auto mopts = ParseMountOptions(e.mount_opts);
   556        EXPECT_THAT(mopts, Contains(Key("memory")));
   557        EXPECT_THAT(mopts, Contains(Key("cpu")));
   558        EXPECT_THAT(mopts, Not(Contains(Key("cpuacct"))));
   559      }
   560  
   561      if (e.mount_point == c2.Path()) {
   562        auto mopts = ParseMountOptions(e.mount_opts);
   563        EXPECT_THAT(mopts, Contains(Key("cpuacct")));
   564        EXPECT_THAT(mopts, Not(Contains(Key("cpu"))));
   565        EXPECT_THAT(mopts, Not(Contains(Key("memory"))));
   566      }
   567    }
   568  
   569    const std::vector<ProcMountInfoEntry> mountinfo =
   570        ASSERT_NO_ERRNO_AND_VALUE(ProcSelfMountInfoEntries());
   571  
   572    for (auto const& e : mountinfo) {
   573      if (e.mount_point == c1.Path()) {
   574        auto mopts = ParseMountOptions(e.super_opts);
   575        EXPECT_THAT(mopts, Contains(Key("memory")));
   576        EXPECT_THAT(mopts, Contains(Key("cpu")));
   577        EXPECT_THAT(mopts, Not(Contains(Key("cpuacct"))));
   578      }
   579  
   580      if (e.mount_point == c2.Path()) {
   581        auto mopts = ParseMountOptions(e.super_opts);
   582        EXPECT_THAT(mopts, Contains(Key("cpuacct")));
   583        EXPECT_THAT(mopts, Not(Contains(Key("cpu"))));
   584        EXPECT_THAT(mopts, Not(Contains(Key("memory"))));
   585      }
   586    }
   587  }
   588  
   589  }  // namespace
   590  }  // namespace testing
   591  }  // namespace gvisor