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/unique_fd.h>
38 #include <cgroup_map.h>
39 #include <json/reader.h>
40 #include <json/value.h>
41 #include <processgroup/processgroup.h>
42 
43 using android::base::GetBoolProperty;
44 using android::base::StringPrintf;
45 using android::base::unique_fd;
46 
47 static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
48 static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
49 static constexpr const char* CGROUP_TASKS_FILE_V2 = "/cgroup.tasks";
50 
version() const51 uint32_t CgroupController::version() const {
52     CHECK(HasValue());
53     return ACgroupController_getVersion(controller_);
54 }
55 
name() const56 const char* CgroupController::name() const {
57     CHECK(HasValue());
58     return ACgroupController_getName(controller_);
59 }
60 
path() const61 const char* CgroupController::path() const {
62     CHECK(HasValue());
63     return ACgroupController_getPath(controller_);
64 }
65 
HasValue() const66 bool CgroupController::HasValue() const {
67     return controller_ != nullptr;
68 }
69 
IsUsable()70 bool CgroupController::IsUsable() {
71     if (!HasValue()) return false;
72 
73     if (state_ == UNKNOWN) {
74         if (ACgroupController_getFlags != nullptr) {
75             uint32_t flags = ACgroupController_getFlags(controller_);
76             state_ = (flags & CGROUPRC_CONTROLLER_FLAG_MOUNTED) != 0 ? USABLE : MISSING;
77         } else {
78             state_ = access(GetProcsFilePath("", 0, 0).c_str(), F_OK) == 0 ? USABLE : MISSING;
79         }
80     }
81 
82     return state_ == USABLE;
83 }
84 
GetTasksFilePath(const std::string & rel_path) const85 std::string CgroupController::GetTasksFilePath(const std::string& rel_path) const {
86     std::string tasks_path = path();
87 
88     if (!rel_path.empty()) {
89         tasks_path += "/" + rel_path;
90     }
91     return (version() == 1) ? tasks_path + CGROUP_TASKS_FILE : tasks_path + CGROUP_TASKS_FILE_V2;
92 }
93 
GetProcsFilePath(const std::string & rel_path,uid_t uid,pid_t pid) const94 std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
95                                                pid_t pid) const {
96     std::string proc_path(path());
97     proc_path.append("/").append(rel_path);
98     proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
99     proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
100 
101     return proc_path.append(CGROUP_PROCS_FILE);
102 }
103 
GetTaskGroup(int tid,std::string * group) const104 bool CgroupController::GetTaskGroup(int tid, std::string* group) const {
105     std::string file_name = StringPrintf("/proc/%d/cgroup", tid);
106     std::string content;
107     if (!android::base::ReadFileToString(file_name, &content)) {
108         PLOG(ERROR) << "Failed to read " << file_name;
109         return false;
110     }
111 
112     // if group is null and tid exists return early because
113     // user is not interested in cgroup membership
114     if (group == nullptr) {
115         return true;
116     }
117 
118     std::string cg_tag;
119 
120     if (version() == 2) {
121         cg_tag = "0::";
122     } else {
123         cg_tag = StringPrintf(":%s:", name());
124     }
125     size_t start_pos = content.find(cg_tag);
126     if (start_pos == std::string::npos) {
127         return false;
128     }
129 
130     start_pos += cg_tag.length() + 1;  // skip '/'
131     size_t end_pos = content.find('\n', start_pos);
132     if (end_pos == std::string::npos) {
133         *group = content.substr(start_pos, std::string::npos);
134     } else {
135         *group = content.substr(start_pos, end_pos - start_pos);
136     }
137 
138     return true;
139 }
140 
CgroupMap()141 CgroupMap::CgroupMap() {
142     if (!LoadRcFile()) {
143         LOG(ERROR) << "CgroupMap::LoadRcFile called for [" << getpid() << "] failed";
144     }
145 }
146 
GetInstance()147 CgroupMap& CgroupMap::GetInstance() {
148     // Deliberately leak this object to avoid a race between destruction on
149     // process exit and concurrent access from another thread.
150     static auto* instance = new CgroupMap;
151     return *instance;
152 }
153 
LoadRcFile()154 bool CgroupMap::LoadRcFile() {
155     if (!loaded_) {
156         loaded_ = (ACgroupFile_getVersion() != 0);
157     }
158     return loaded_;
159 }
160 
Print() const161 void CgroupMap::Print() const {
162     if (!loaded_) {
163         LOG(ERROR) << "CgroupMap::Print called for [" << getpid()
164                    << "] failed, RC file was not initialized properly";
165         return;
166     }
167     LOG(INFO) << "File version = " << ACgroupFile_getVersion();
168     LOG(INFO) << "File controller count = " << ACgroupFile_getControllerCount();
169 
170     LOG(INFO) << "Mounted cgroups:";
171 
172     auto controller_count = ACgroupFile_getControllerCount();
173     for (uint32_t i = 0; i < controller_count; ++i) {
174         const ACgroupController* controller = ACgroupFile_getController(i);
175         if (ACgroupController_getFlags != nullptr) {
176             LOG(INFO) << "\t" << ACgroupController_getName(controller) << " ver "
177                       << ACgroupController_getVersion(controller) << " path "
178                       << ACgroupController_getPath(controller) << " flags "
179                       << ACgroupController_getFlags(controller);
180         } else {
181             LOG(INFO) << "\t" << ACgroupController_getName(controller) << " ver "
182                       << ACgroupController_getVersion(controller) << " path "
183                       << ACgroupController_getPath(controller);
184         }
185     }
186 }
187 
FindController(const std::string & name) const188 CgroupController CgroupMap::FindController(const std::string& name) const {
189     if (!loaded_) {
190         LOG(ERROR) << "CgroupMap::FindController called for [" << getpid()
191                    << "] failed, RC file was not initialized properly";
192         return CgroupController(nullptr);
193     }
194 
195     auto controller_count = ACgroupFile_getControllerCount();
196     for (uint32_t i = 0; i < controller_count; ++i) {
197         const ACgroupController* controller = ACgroupFile_getController(i);
198         if (name == ACgroupController_getName(controller)) {
199             return CgroupController(controller);
200         }
201     }
202 
203     return CgroupController(nullptr);
204 }
205