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