libprocessgroup: Add SetSchedulerPolicy Action

Add a new profile action that allows users to set a scheduler policy and
priority for a thread when a profile with the action is applied. This
can be used with the POSIX policies specified in the sched_setscheduler
man page. A priority value is required for real-time policies. A nice
value may optionally be provided for normal policies.

If a normal policy is used and a nice value is provided, it will be
applied with setpriority() after the scheduler policy is applied. The
range of nice values that can be used in the task profiles JSON in this
case is [-20, 19].

If a real-time policy is used, the range of priorities that can be used
in the task profiles JSON is [1, 99] which will be mapped onto
[sched_get_priority_min(), sched_get_priority_max()] at runtime for the
the provided policy.

Here are some examples:

Use lowest-priority with FIFO policy:
"Actions": [
  {
    "Name": "SetSchedulerPolicy",
    "Params":
    {
      "Policy": "SCHED_FIFO",
      "Priority": 1
    }
  }
]

Use highest-priority (lowest nice) with standard NORMAL scheduler:
"Actions": [
  {
    "Name": "SetSchedulerPolicy",
    "Params":
    {
      "Policy": "SCHED_OTHER",
      "Nice": -20
    }
  }
]

Bug: 368072932
BYPASS_INCLUSIVE_LANGUAGE_REASON=false positive
Change-Id: I502ceb3913016e9f4e6b6322cc0eee905c9a5c9a
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 7f33d4a..110ef52 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -17,11 +17,16 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "libprocessgroup"
 
+#include <task_profiles.h>
+
+#include <map>
+#include <string>
+
 #include <dirent.h>
 #include <fcntl.h>
+#include <sched.h>
+#include <sys/resource.h>
 #include <unistd.h>
-#include <task_profiles.h>
-#include <string>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -30,13 +35,13 @@
 #include <android-base/strings.h>
 #include <android-base/threads.h>
 
+#include <build_flags.h>
+
 #include <cutils/android_filesystem_config.h>
 
 #include <json/reader.h>
 #include <json/value.h>
 
-#include <build_flags.h>
-
 using android::base::GetThreadId;
 using android::base::GetUintProperty;
 using android::base::StringPrintf;
@@ -649,6 +654,57 @@
     return access(task_path_.c_str(), W_OK) == 0;
 }
 
+bool SetSchedulerPolicyAction::isNormalPolicy(int policy) {
+    return policy == SCHED_OTHER || policy == SCHED_BATCH || policy == SCHED_IDLE;
+}
+
+bool SetSchedulerPolicyAction::toPriority(int policy, int virtual_priority, int& priority_out) {
+    constexpr int VIRTUAL_PRIORITY_MIN = 1;
+    constexpr int VIRTUAL_PRIORITY_MAX = 99;
+
+    if (virtual_priority < VIRTUAL_PRIORITY_MIN || virtual_priority > VIRTUAL_PRIORITY_MAX) {
+        LOG(WARNING) << "SetSchedulerPolicy: invalid priority (" << virtual_priority
+                     << ") for policy (" << policy << ")";
+        return false;
+    }
+
+    const int min = sched_get_priority_min(policy);
+    if (min == -1) {
+        PLOG(ERROR) << "SetSchedulerPolicy: Cannot get min sched priority for policy " << policy;
+        return false;
+    }
+
+    const int max = sched_get_priority_max(policy);
+    if (max == -1) {
+        PLOG(ERROR) << "SetSchedulerPolicy: Cannot get max sched priority for policy " << policy;
+        return false;
+    }
+
+    priority_out = min + (virtual_priority - VIRTUAL_PRIORITY_MIN) * (max - min) /
+        (VIRTUAL_PRIORITY_MAX - VIRTUAL_PRIORITY_MIN);
+
+    return true;
+}
+
+bool SetSchedulerPolicyAction::ExecuteForTask(pid_t tid) const {
+    struct sched_param param = {};
+    param.sched_priority = isNormalPolicy(policy_) ? 0 : *priority_or_nice_;
+    if (sched_setscheduler(tid, policy_, &param) == -1) {
+        PLOG(WARNING) << "SetSchedulerPolicy: Failed to apply scheduler policy (" << policy_
+                      << ") with priority (" << *priority_or_nice_ << ") to tid " << tid;
+        return false;
+    }
+
+    if (isNormalPolicy(policy_) && priority_or_nice_ &&
+        setpriority(PRIO_PROCESS, tid, *priority_or_nice_) == -1) {
+        PLOG(WARNING) << "SetSchedulerPolicy: Failed to apply nice (" << *priority_or_nice_
+                      << ") to tid " << tid;
+        return false;
+    }
+
+    return true;
+}
+
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& profile : profiles_) {
         profile->ExecuteForProcess(uid, pid);
@@ -936,6 +992,62 @@
                     LOG(WARNING) << "WriteFile: invalid parameter: "
                                  << "empty value";
                 }
+            } else if (action_name == "SetSchedulerPolicy") {
+                const std::map<std::string, int> POLICY_MAP = {
+                    {"SCHED_OTHER", SCHED_OTHER},
+                    {"SCHED_BATCH", SCHED_BATCH},
+                    {"SCHED_IDLE", SCHED_IDLE},
+                    {"SCHED_FIFO", SCHED_FIFO},
+                    {"SCHED_RR", SCHED_RR},
+                };
+                const std::string policy_str = params_val["Policy"].asString();
+
+                const auto it = POLICY_MAP.find(policy_str);
+                if (it == POLICY_MAP.end()) {
+                    LOG(WARNING) << "SetSchedulerPolicy: invalid policy " << policy_str;
+                    continue;
+                }
+
+                const int policy = it->second;
+
+                if (SetSchedulerPolicyAction::isNormalPolicy(policy)) {
+                    if (params_val.isMember("Priority")) {
+                        LOG(WARNING) << "SetSchedulerPolicy: Normal policies (" << policy_str
+                                     << ") use Nice values, not Priority values";
+                    }
+
+                    if (params_val.isMember("Nice")) {
+                        // If present, this optional value will be passed in an additional syscall
+                        // to setpriority(), since the sched_priority value must be 0 for calls to
+                        // sched_setscheduler() with "normal" policies.
+                        const int nice = params_val["Nice"].asInt();
+
+                        const int LINUX_MIN_NICE = -20;
+                        const int LINUX_MAX_NICE = 19;
+                        if (nice < LINUX_MIN_NICE || nice > LINUX_MAX_NICE) {
+                            LOG(WARNING) << "SetSchedulerPolicy: Provided nice (" << nice
+                                         << ") appears out of range.";
+                        }
+                        profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy, nice));
+                    } else {
+                        profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy));
+                    }
+                } else {
+                    if (params_val.isMember("Nice")) {
+                        LOG(WARNING) << "SetSchedulerPolicy: Real-time policies (" << policy_str
+                                     << ") use Priority values, not Nice values";
+                    }
+
+                    // This is a "virtual priority" as described by `man 2 sched_get_priority_min`
+                    // that will be mapped onto the following range for the provided policy:
+                    // [sched_get_priority_min(), sched_get_priority_max()]
+                    const int virtual_priority = params_val["Priority"].asInt();
+
+                    int priority;
+                    if (SetSchedulerPolicyAction::toPriority(policy, virtual_priority, priority)) {
+                        profile->Add(std::make_unique<SetSchedulerPolicyAction>(policy, priority));
+                    }
+                }
             } else {
                 LOG(WARNING) << "Unknown profile action: " << action_name;
             }