1 /*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "libprocessgroup"
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <grp.h>
23 #include <pwd.h>
24 #include <sys/mman.h>
25 #include <sys/mount.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29 #include <unistd.h>
30
31 #include <regex>
32
33 #include <android-base/file.h>
34 #include <android-base/logging.h>
35 #include <android-base/properties.h>
36 #include <android-base/stringprintf.h>
37 #include <android-base/strings.h>
38 #include <android-base/unique_fd.h>
39 #include <cgroup_map.h>
40 #include <json/reader.h>
41 #include <json/value.h>
42 #include <processgroup/processgroup.h>
43
44 using android::base::GetBoolProperty;
45 using android::base::StartsWith;
46 using android::base::StringPrintf;
47 using android::base::unique_fd;
48 using android::base::WriteStringToFile;
49
50 static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
51 static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
52 static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.threads";
53
version() const54 uint32_t CgroupController::version() const {
55 CHECK(HasValue());
56 return ACgroupController_getVersion(controller_);
57 }
58
name() const59 const char* CgroupController::name() const {
60 CHECK(HasValue());
61 return ACgroupController_getName(controller_);
62 }
63
path() const64 const char* CgroupController::path() const {
65 CHECK(HasValue());
66 return ACgroupController_getPath(controller_);
67 }
68
HasValue() const69 bool CgroupController::HasValue() const {
70 return controller_ != nullptr;
71 }
72
IsUsable()73 bool CgroupController::IsUsable() {
74 if (!HasValue()) return false;
75
76 if (state_ == UNKNOWN) {
77 if (__builtin_available(android 30, *)) {
78 uint32_t flags = ACgroupController_getFlags(controller_);
79 state_ = (flags & CGROUPRC_CONTROLLER_FLAG_MOUNTED) != 0 ? USABLE : MISSING;
80 } else {
81 state_ = access(GetProcsFilePath("", 0, 0).c_str(), F_OK) == 0 ? USABLE : MISSING;
82 }
83 }
84
85 return state_ == USABLE;
86 }
87
GetTasksFilePath(const std::string & rel_path) const88 std::string CgroupController::GetTasksFilePath(const std::string& rel_path) const {
89 std::string tasks_path = path();
90
91 if (!rel_path.empty()) {
92 tasks_path += "/" + rel_path;
93 }
94 return (version() == 1) ? tasks_path + CGROUP_TASKS_FILE : tasks_path + CGROUP_TASKS_FILE_V2;
95 }
96
GetProcsFilePath(const std::string & rel_path,uid_t uid,pid_t pid) const97 std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
98 pid_t pid) const {
99 std::string proc_path(path());
100 proc_path.append("/").append(rel_path);
101 proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
102 proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
103
104 return proc_path.append(CGROUP_PROCS_FILE);
105 }
106
GetTaskGroup(int tid,std::string * group) const107 bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
108 std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
109 std::string content;
110 if (!android::base::ReadFileToString(file_name, &content)) {
111 PLOG(ERROR) << "Failed to read " << file_name;
112 return false;
113 }
114
115 // if group is null and tid exists return early because
116 // user is not interested in cgroup membership
117 if (group == nullptr) {
118 return true;
119 }
120
121 std::string cg_tag;
122
123 if (version() == 2) {
124 cg_tag = "0::";
125 } else {
126 cg_tag = StringPrintf(":%s:", name());
127 }
128 size_t start_pos = content.find(cg_tag);
129 if (start_pos == std::string::npos) {
130 return false;
131 }
132
133 start_pos += cg_tag.length() + 1; // skip '/'
134 size_t end_pos = content.find('\n', start_pos);
135 if (end_pos == std::string::npos) {
136 *group = content.substr(start_pos, std::string::npos);
137 } else {
138 *group = content.substr(start_pos, end_pos - start_pos);
139 }
140
141 return true;
142 }
143
CgroupMap()144 CgroupMap::CgroupMap() {
145 if (!LoadRcFile()) {
146 LOG(ERROR) << "CgroupMap::LoadRcFile called for [" << getpid() << "] failed";
147 }
148 }
149
GetInstance()150 CgroupMap& CgroupMap::GetInstance() {
151 // Deliberately leak this object to avoid a race between destruction on
152 // process exit and concurrent access from another thread.
153 static auto* instance = new CgroupMap;
154 return *instance;
155 }
156
LoadRcFile()157 bool CgroupMap::LoadRcFile() {
158 if (!loaded_) {
159 loaded_ = (ACgroupFile_getVersion() != 0);
160 }
161 return loaded_;
162 }
163
Print() const164 void CgroupMap::Print() const {
165 if (!loaded_) {
166 LOG(ERROR) << "CgroupMap::Print called for [" << getpid()
167 << "] failed, RC file was not initialized properly";
168 return;
169 }
170 LOG(INFO) << "File version = " << ACgroupFile_getVersion();
171 LOG(INFO) << "File controller count = " << ACgroupFile_getControllerCount();
172
173 LOG(INFO) << "Mounted cgroups:";
174
175 auto controller_count = ACgroupFile_getControllerCount();
176 for (uint32_t i = 0; i < controller_count; ++i) {
177 const ACgroupController* controller = ACgroupFile_getController(i);
178 if (__builtin_available(android 30, *)) {
179 LOG(INFO) << "\t" << ACgroupController_getName(controller) << " ver "
180 << ACgroupController_getVersion(controller) << " path "
181 << ACgroupController_getPath(controller) << " flags "
182 << ACgroupController_getFlags(controller);
183 } else {
184 LOG(INFO) << "\t" << ACgroupController_getName(controller) << " ver "
185 << ACgroupController_getVersion(controller) << " path "
186 << ACgroupController_getPath(controller);
187 }
188 }
189 }
190
FindController(const std::string & name) const191 CgroupController CgroupMap::FindController(const std::string& name) const {
192 if (!loaded_) {
193 LOG(ERROR) << "CgroupMap::FindController called for [" << getpid()
194 << "] failed, RC file was not initialized properly";
195 return CgroupController(nullptr);
196 }
197
198 auto controller_count = ACgroupFile_getControllerCount();
199 for (uint32_t i = 0; i < controller_count; ++i) {
200 const ACgroupController* controller = ACgroupFile_getController(i);
201 if (name == ACgroupController_getName(controller)) {
202 return CgroupController(controller);
203 }
204 }
205
206 return CgroupController(nullptr);
207 }
208
FindControllerByPath(const std::string & path) const209 CgroupController CgroupMap::FindControllerByPath(const std::string& path) const {
210 if (!loaded_) {
211 LOG(ERROR) << "CgroupMap::FindControllerByPath called for [" << getpid()
212 << "] failed, RC file was not initialized properly";
213 return CgroupController(nullptr);
214 }
215
216 auto controller_count = ACgroupFile_getControllerCount();
217 for (uint32_t i = 0; i < controller_count; ++i) {
218 const ACgroupController* controller = ACgroupFile_getController(i);
219 if (StartsWith(path, ACgroupController_getPath(controller))) {
220 return CgroupController(controller);
221 }
222 }
223
224 return CgroupController(nullptr);
225 }
226
ActivateControllers(const std::string & path) const227 int CgroupMap::ActivateControllers(const std::string& path) const {
228 if (__builtin_available(android 30, *)) {
229 auto controller_count = ACgroupFile_getControllerCount();
230 for (uint32_t i = 0; i < controller_count; ++i) {
231 const ACgroupController* controller = ACgroupFile_getController(i);
232 const uint32_t flags = ACgroupController_getFlags(controller);
233 if (flags & CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
234 std::string str("+");
235 str.append(ACgroupController_getName(controller));
236 if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
237 if (flags & CGROUPRC_CONTROLLER_FLAG_OPTIONAL) {
238 PLOG(WARNING) << "Activation of cgroup controller " << str
239 << " failed in path " << path;
240 } else {
241 return -errno;
242 }
243 }
244 }
245 }
246 return 0;
247 }
248 return -ENOSYS;
249 }
250