libprocessgroup: Add support for task profiles

Abstract usage of cgroups into task profiles that allows for changes
in cgroup hierarchy and version without affecting framework codebase.
Rework current processgroup and sched_policy API function implementations
to use task profiles instead of hardcoded paths and attributes.
Mount cgroups using information from cgroups.json rather than from init.rc

Exempt-From-Owner-Approval: already approved in internal master

Bug: 111307099
Test: builds, boots

Change-Id: If5532d6dc570add825cebd5b5148e00c7d688e32
Merged-In: If5532d6dc570add825cebd5b5148e00c7d688e32
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
new file mode 100644
index 0000000..eb50f85
--- /dev/null
+++ b/libprocessgroup/task_profiles.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "libprocessgroup"
+
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <task_profiles.h>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/threads.h>
+
+#include <cutils/android_filesystem_config.h>
+
+#include <json/reader.h>
+#include <json/value.h>
+
+using android::base::GetThreadId;
+using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::base::WriteStringToFile;
+
+#define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
+
+bool ProfileAttribute::GetPathForTask(int tid, std::string* path) const {
+    std::string subgroup;
+    if (!controller_->GetTaskGroup(tid, &subgroup)) {
+        return false;
+    }
+
+    if (path == nullptr) {
+        return true;
+    }
+
+    if (subgroup.empty()) {
+        *path = StringPrintf("%s/%s", controller_->path(), file_name_.c_str());
+    } else {
+        *path = StringPrintf("%s/%s/%s", controller_->path(), subgroup.c_str(), file_name_.c_str());
+    }
+    return true;
+}
+
+bool SetClampsAction::ExecuteForProcess(uid_t, pid_t) const {
+    // TODO: add support when kernel supports util_clamp
+    LOG(WARNING) << "SetClampsAction::ExecuteForProcess is not supported";
+    return false;
+}
+
+bool SetClampsAction::ExecuteForTask(int) const {
+    // TODO: add support when kernel supports util_clamp
+    LOG(WARNING) << "SetClampsAction::ExecuteForTask is not supported";
+    return false;
+}
+
+bool SetTimerSlackAction::IsTimerSlackSupported(int tid) {
+    auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
+
+    return (access(file.c_str(), W_OK) == 0);
+}
+
+bool SetTimerSlackAction::ExecuteForTask(int tid) const {
+    static bool sys_supports_timerslack = IsTimerSlackSupported(tid);
+
+    // v4.6+ kernels support the /proc/<tid>/timerslack_ns interface.
+    // TODO: once we've backported this, log if the open(2) fails.
+    if (sys_supports_timerslack) {
+        auto file = StringPrintf("/proc/%d/timerslack_ns", tid);
+        if (!WriteStringToFile(std::to_string(slack_), file)) {
+            PLOG(ERROR) << "set_timerslack_ns write failed";
+        }
+    }
+
+    // TODO: Remove when /proc/<tid>/timerslack_ns interface is backported.
+    if (tid == 0 || tid == GetThreadId()) {
+        if (prctl(PR_SET_TIMERSLACK, slack_) == -1) {
+            PLOG(ERROR) << "set_timerslack_ns prctl failed";
+        }
+    }
+
+    return true;
+}
+
+bool SetAttributeAction::ExecuteForProcess(uid_t, pid_t pid) const {
+    return ExecuteForTask(pid);
+}
+
+bool SetAttributeAction::ExecuteForTask(int tid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForTask(tid, &path)) {
+        PLOG(ERROR) << "Failed to find cgroup for tid " << tid;
+        return false;
+    }
+
+    if (!WriteStringToFile(value_, path)) {
+        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
+        return false;
+    }
+
+    return true;
+}
+
+bool SetCgroupAction::IsAppDependentPath(const std::string& path) {
+    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
+SetCgroupAction::SetCgroupAction(const CgroupController* c, const std::string& p)
+    : controller_(c), path_(p) {
+    // cache file descriptor only if path is app independent
+    if (IsAppDependentPath(path_)) {
+        // file descriptor is not cached
+        fd_.reset(-2);
+        return;
+    }
+
+    std::string tasks_path = c->GetTasksFilePath(p.c_str());
+
+    if (access(tasks_path.c_str(), W_OK) != 0) {
+        // file is not accessible
+        fd_.reset(-1);
+        return;
+    }
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        PLOG(ERROR) << "Failed to cache fd '" << tasks_path << "'";
+        fd_.reset(-1);
+        return;
+    }
+
+    fd_ = std::move(fd);
+}
+
+bool SetCgroupAction::AddTidToCgroup(int tid, int fd) {
+    if (tid <= 0) {
+        return true;
+    }
+
+    std::string value = std::to_string(tid);
+
+    if (TEMP_FAILURE_RETRY(write(fd, value.c_str(), value.length())) < 0) {
+        // If the thread is in the process of exiting, don't flag an error
+        if (errno != ESRCH) {
+            PLOG(ERROR) << "JoinGroup failed to write '" << value << "'; fd=" << fd;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    if (fd_ >= 0) {
+        // fd is cached, reuse it
+        if (!AddTidToCgroup(pid, fd_)) {
+            PLOG(ERROR) << "Failed to add task into cgroup";
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == -1) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    // this is app-dependent path, file descriptor is not cached
+    std::string procs_path = controller_->GetProcsFilePath(path_.c_str(), uid, pid);
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (tmp_fd < 0) {
+        PLOG(WARNING) << "Failed to open " << procs_path << ": " << strerror(errno);
+        return false;
+    }
+    if (!AddTidToCgroup(pid, tmp_fd)) {
+        PLOG(ERROR) << "Failed to add task into cgroup";
+        return false;
+    }
+
+    return true;
+}
+
+bool SetCgroupAction::ExecuteForTask(int tid) const {
+    if (fd_ >= 0) {
+        // fd is cached, reuse it
+        if (!AddTidToCgroup(tid, fd_)) {
+            PLOG(ERROR) << "Failed to add task into cgroup";
+            return false;
+        }
+        return true;
+    }
+
+    if (fd_ == -1) {
+        // no permissions to access the file, ignore
+        return true;
+    }
+
+    // application-dependent path can't be used with tid
+    PLOG(ERROR) << "Application profile can't be applied to a thread";
+    return false;
+}
+
+bool TaskProfile::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForProcess(uid, pid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool TaskProfile::ExecuteForTask(int tid) const {
+    if (tid == 0) {
+        tid = GetThreadId();
+    }
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForTask(tid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+TaskProfiles& TaskProfiles::GetInstance() {
+    static TaskProfiles instance;
+    return instance;
+}
+
+TaskProfiles::TaskProfiles() {
+    if (!Load(CgroupMap::GetInstance())) {
+        LOG(ERROR) << "TaskProfiles::Load for [" << getpid() << "] failed";
+    }
+}
+
+bool TaskProfiles::Load(const CgroupMap& cg_map) {
+    std::string json_doc;
+
+    if (!android::base::ReadFileToString(TASK_PROFILE_DB_FILE, &json_doc)) {
+        LOG(ERROR) << "Failed to read task profiles from " << TASK_PROFILE_DB_FILE;
+        return false;
+    }
+
+    Json::Reader reader;
+    Json::Value root;
+    if (!reader.parse(json_doc, root)) {
+        LOG(ERROR) << "Failed to parse task profiles: " << reader.getFormattedErrorMessages();
+        return false;
+    }
+
+    Json::Value attr = root["Attributes"];
+    for (Json::Value::ArrayIndex i = 0; i < attr.size(); ++i) {
+        std::string name = attr[i]["Name"].asString();
+        std::string ctrlName = attr[i]["Controller"].asString();
+        std::string file_name = attr[i]["File"].asString();
+
+        if (attributes_.find(name) == attributes_.end()) {
+            const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
+            if (controller) {
+                attributes_[name] = std::make_unique<ProfileAttribute>(controller, file_name);
+            } else {
+                LOG(WARNING) << "Controller " << ctrlName << " is not found";
+            }
+        } else {
+            LOG(WARNING) << "Attribute " << name << " is already defined";
+        }
+    }
+
+    std::map<std::string, std::string> params;
+
+    Json::Value profilesVal = root["Profiles"];
+    for (Json::Value::ArrayIndex i = 0; i < profilesVal.size(); ++i) {
+        Json::Value profileVal = profilesVal[i];
+
+        std::string profileName = profileVal["Name"].asString();
+        Json::Value actions = profileVal["Actions"];
+        auto profile = std::make_unique<TaskProfile>();
+
+        for (Json::Value::ArrayIndex actIdx = 0; actIdx < actions.size(); ++actIdx) {
+            Json::Value actionVal = actions[actIdx];
+            std::string actionName = actionVal["Name"].asString();
+            Json::Value paramsVal = actionVal["Params"];
+            if (actionName == "JoinCgroup") {
+                std::string ctrlName = paramsVal["Controller"].asString();
+                std::string path = paramsVal["Path"].asString();
+
+                const CgroupController* controller = cg_map.FindController(ctrlName.c_str());
+                if (controller) {
+                    profile->Add(std::make_unique<SetCgroupAction>(controller, path));
+                } else {
+                    LOG(WARNING) << "JoinCgroup: controller " << ctrlName << " is not found";
+                }
+            } else if (actionName == "SetTimerSlack") {
+                std::string slackValue = paramsVal["Slack"].asString();
+                char* end;
+                unsigned long slack;
+
+                slack = strtoul(slackValue.c_str(), &end, 10);
+                if (end > slackValue.c_str()) {
+                    profile->Add(std::make_unique<SetTimerSlackAction>(slack));
+                } else {
+                    LOG(WARNING) << "SetTimerSlack: invalid parameter: " << slackValue;
+                }
+            } else if (actionName == "SetAttribute") {
+                std::string attrName = paramsVal["Name"].asString();
+                std::string attrValue = paramsVal["Value"].asString();
+
+                auto iter = attributes_.find(attrName);
+                if (iter != attributes_.end()) {
+                    profile->Add(
+                            std::make_unique<SetAttributeAction>(iter->second.get(), attrValue));
+                } else {
+                    LOG(WARNING) << "SetAttribute: unknown attribute: " << attrName;
+                }
+            } else if (actionName == "SetClamps") {
+                std::string boostValue = paramsVal["Boost"].asString();
+                std::string clampValue = paramsVal["Clamp"].asString();
+                char* end;
+                unsigned long boost;
+
+                boost = strtoul(boostValue.c_str(), &end, 10);
+                if (end > boostValue.c_str()) {
+                    unsigned long clamp = strtoul(clampValue.c_str(), &end, 10);
+                    if (end > clampValue.c_str()) {
+                        profile->Add(std::make_unique<SetClampsAction>(boost, clamp));
+                    } else {
+                        LOG(WARNING) << "SetClamps: invalid parameter " << clampValue;
+                    }
+                } else {
+                    LOG(WARNING) << "SetClamps: invalid parameter: " << boostValue;
+                }
+            } else {
+                LOG(WARNING) << "Unknown profile action: " << actionName;
+            }
+        }
+        profiles_[profileName] = std::move(profile);
+    }
+
+    return true;
+}
+
+const TaskProfile* TaskProfiles::GetProfile(const std::string& name) const {
+    auto iter = profiles_.find(name);
+
+    if (iter != profiles_.end()) {
+        return iter->second.get();
+    }
+    return nullptr;
+}
+
+const ProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
+    auto iter = attributes_.find(name);
+
+    if (iter != attributes_.end()) {
+        return iter->second.get();
+    }
+    return nullptr;
+}