github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/util/cgroup_util.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  #include "test/util/cgroup_util.h"
    16  
    17  #include <sys/syscall.h>
    18  #include <unistd.h>
    19  
    20  #include "absl/strings/str_cat.h"
    21  #include "absl/strings/str_split.h"
    22  #include "test/util/fs_util.h"
    23  #include "test/util/mount_util.h"
    24  
    25  namespace gvisor {
    26  namespace testing {
    27  
    28  Cgroup::Cgroup(std::string_view path) : cgroup_path_(path) {
    29    id_ = ++Cgroup::next_id_;
    30    std::cerr << absl::StreamFormat("[cg#%d] <= %s", id_, cgroup_path_)
    31              << std::endl;
    32  }
    33  
    34  PosixErrorOr<std::string> Cgroup::ReadControlFile(
    35      absl::string_view name) const {
    36    std::string buf;
    37    RETURN_IF_ERRNO(GetContents(Relpath(name), &buf));
    38  
    39    const std::string alias_path = absl::StrFormat("[cg#%d]/%s", id_, name);
    40    std::cerr << absl::StreamFormat("<contents of %s>", alias_path) << std::endl;
    41    std::cerr << buf;
    42    std::cerr << absl::StreamFormat("<end of %s>", alias_path) << std::endl;
    43  
    44    return buf;
    45  }
    46  
    47  PosixErrorOr<int64_t> Cgroup::ReadIntegerControlFile(
    48      absl::string_view name) const {
    49    ASSIGN_OR_RETURN_ERRNO(const std::string buf, ReadControlFile(name));
    50    ASSIGN_OR_RETURN_ERRNO(const int64_t val, Atoi<int64_t>(buf));
    51    return val;
    52  }
    53  
    54  PosixError Cgroup::WriteControlFile(absl::string_view name,
    55                                      const std::string& value) const {
    56    ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, Open(Relpath(name), O_WRONLY));
    57    RETURN_ERROR_IF_SYSCALL_FAIL(WriteFd(fd.get(), value.c_str(), value.size()));
    58    return NoError();
    59  }
    60  
    61  PosixError Cgroup::WriteIntegerControlFile(absl::string_view name,
    62                                             int64_t value) const {
    63    return WriteControlFile(name, absl::StrCat(value));
    64  }
    65  
    66  PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Procs() const {
    67    ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("cgroup.procs"));
    68    return ParsePIDList(buf);
    69  }
    70  
    71  PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::Tasks() const {
    72    ASSIGN_OR_RETURN_ERRNO(std::string buf, ReadControlFile("tasks"));
    73    return ParsePIDList(buf);
    74  }
    75  
    76  PosixError Cgroup::ContainsCallingProcess() const {
    77    ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> procs, Procs());
    78    ASSIGN_OR_RETURN_ERRNO(const absl::flat_hash_set<pid_t> tasks, Tasks());
    79    const pid_t pid = getpid();
    80    const pid_t tid = syscall(SYS_gettid);
    81    if (!procs.contains(pid)) {
    82      return PosixError(
    83          ENOENT, absl::StrFormat("Cgroup doesn't contain process %d", pid));
    84    }
    85    if (!tasks.contains(tid)) {
    86      return PosixError(ENOENT,
    87                        absl::StrFormat("Cgroup doesn't contain task %d", tid));
    88    }
    89    return NoError();
    90  }
    91  
    92  PosixErrorOr<absl::flat_hash_set<pid_t>> Cgroup::ParsePIDList(
    93      absl::string_view data) const {
    94    absl::flat_hash_set<pid_t> res;
    95    std::vector<absl::string_view> lines = absl::StrSplit(data, '\n');
    96    for (const std::string_view& line : lines) {
    97      if (line.empty()) {
    98        continue;
    99      }
   100      ASSIGN_OR_RETURN_ERRNO(const int32_t pid, Atoi<int32_t>(line));
   101      res.insert(static_cast<pid_t>(pid));
   102    }
   103    return res;
   104  }
   105  
   106  int64_t Cgroup::next_id_ = 0;
   107  
   108  PosixErrorOr<Cgroup> Mounter::MountCgroupfs(std::string mopts) {
   109    ASSIGN_OR_RETURN_ERRNO(TempPath mountpoint,
   110                           TempPath::CreateDirIn(root_.path()));
   111    ASSIGN_OR_RETURN_ERRNO(
   112        Cleanup mount, Mount("none", mountpoint.path(), "cgroup", 0, mopts, 0));
   113    const std::string mountpath = mountpoint.path();
   114    std::cerr << absl::StreamFormat(
   115                     "Mount(\"none\", \"%s\", \"cgroup\", 0, \"%s\", 0) => OK",
   116                     mountpath, mopts)
   117              << std::endl;
   118    Cgroup cg = Cgroup(mountpath);
   119    mountpoints_[cg.id()] = std::move(mountpoint);
   120    mounts_[cg.id()] = std::move(mount);
   121    return cg;
   122  }
   123  
   124  PosixError Mounter::Unmount(const Cgroup& c) {
   125    auto mount = mounts_.find(c.id());
   126    auto mountpoint = mountpoints_.find(c.id());
   127  
   128    if (mount == mounts_.end() || mountpoint == mountpoints_.end()) {
   129      return PosixError(
   130          ESRCH, absl::StrFormat("No mount found for cgroupfs containing cg#%d",
   131                                 c.id()));
   132    }
   133  
   134    std::cerr << absl::StreamFormat("Unmount([cg#%d])", c.id()) << std::endl;
   135  
   136    // Simply delete the entries, their destructors will unmount and delete the
   137    // mountpoint. Note the order is important to avoid errors: mount then
   138    // mountpoint.
   139    mounts_.erase(mount);
   140    mountpoints_.erase(mountpoint);
   141  
   142    return NoError();
   143  }
   144  
   145  void Mounter::release(const Cgroup& c) {
   146    auto mp = mountpoints_.find(c.id());
   147    if (mp != mountpoints_.end()) {
   148      mp->second.release();
   149      mountpoints_.erase(mp);
   150    }
   151  
   152    auto m = mounts_.find(c.id());
   153    if (m != mounts_.end()) {
   154      m->second.Release();
   155      mounts_.erase(m);
   156    }
   157  }
   158  
   159  constexpr char kProcCgroupsHeader[] =
   160      "#subsys_name\thierarchy\tnum_cgroups\tenabled";
   161  
   162  PosixErrorOr<absl::flat_hash_map<std::string, CgroupsEntry>>
   163  ProcCgroupsEntries() {
   164    std::string content;
   165    RETURN_IF_ERRNO(GetContents("/proc/cgroups", &content));
   166  
   167    bool found_header = false;
   168    absl::flat_hash_map<std::string, CgroupsEntry> entries;
   169    std::vector<std::string> lines = absl::StrSplit(content, '\n');
   170    std::cerr << "<contents of /proc/cgroups>" << std::endl;
   171    for (const std::string& line : lines) {
   172      std::cerr << line << std::endl;
   173  
   174      if (!found_header) {
   175        EXPECT_EQ(line, kProcCgroupsHeader);
   176        found_header = true;
   177        continue;
   178      }
   179      if (line.empty()) {
   180        continue;
   181      }
   182  
   183      // Parse a single entry from /proc/cgroups.
   184      //
   185      // Example entries, fields are tab separated in the real file:
   186      //
   187      // #subsys_name    hierarchy       num_cgroups     enabled
   188      // cpuset  12      35      1
   189      // cpu     3       222     1
   190      //   ^     ^       ^       ^
   191      //   0     1       2       3
   192  
   193      CgroupsEntry entry;
   194      std::vector<std::string> fields =
   195          StrSplit(line, absl::ByAnyChar(": \t"), absl::SkipEmpty());
   196  
   197      entry.subsys_name = fields[0];
   198      ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[1]));
   199      ASSIGN_OR_RETURN_ERRNO(entry.num_cgroups, Atoi<uint64_t>(fields[2]));
   200      ASSIGN_OR_RETURN_ERRNO(const int enabled, Atoi<int>(fields[3]));
   201      entry.enabled = enabled != 0;
   202  
   203      entries[entry.subsys_name] = entry;
   204    }
   205    std::cerr << "<end of /proc/cgroups>" << std::endl;
   206  
   207    return entries;
   208  }
   209  
   210  PosixErrorOr<absl::flat_hash_map<std::string, PIDCgroupEntry>>
   211  ProcPIDCgroupEntries(pid_t pid) {
   212    const std::string path = absl::StrFormat("/proc/%d/cgroup", pid);
   213    std::string content;
   214    RETURN_IF_ERRNO(GetContents(path, &content));
   215  
   216    absl::flat_hash_map<std::string, PIDCgroupEntry> entries;
   217    std::vector<std::string> lines = absl::StrSplit(content, '\n');
   218  
   219    std::cerr << absl::StreamFormat("<contents of %s>", path) << std::endl;
   220    for (const std::string& line : lines) {
   221      std::cerr << line << std::endl;
   222  
   223      if (line.empty()) {
   224        continue;
   225      }
   226  
   227      // Parse a single entry from /proc/<pid>/cgroup.
   228      //
   229      // Example entries:
   230      //
   231      // 2:cpu:/path/to/cgroup
   232      // 1:memory:/
   233  
   234      PIDCgroupEntry entry;
   235      std::vector<std::string> fields =
   236          absl::StrSplit(line, absl::ByChar(':'), absl::SkipEmpty());
   237  
   238      ASSIGN_OR_RETURN_ERRNO(entry.hierarchy, Atoi<uint32_t>(fields[0]));
   239      entry.controllers = fields[1];
   240      entry.path = fields[2];
   241  
   242      entries[entry.controllers] = entry;
   243    }
   244    std::cerr << absl::StreamFormat("<end of %s>", path) << std::endl;
   245  
   246    return entries;
   247  }
   248  
   249  }  // namespace testing
   250  }  // namespace gvisor