// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/process/process.h"
#include <mach/mach.h>
#include <stddef.h>
#include <sys/resource.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <unistd.h>
#include <iterator>
#include <memory>
#include <optional>
#include <utility>
#include "base/apple/mach_logging.h"
#include "base/feature_list.h"
#include "base/memory/free_deleter.h"
namespace base {
namespace {
// Enables setting the task role of every child process to
// TASK_DEFAULT_APPLICATION.
BASE_FEATURE(kMacSetDefaultTaskRole,
"MacSetDefaultTaskRole",
FEATURE_ENABLED_BY_DEFAULT);
// Returns the `task_role_t` of the process whose task port is `task_port`.
std::optional<task_role_t> GetTaskCategoryPolicyRole(mach_port_t task_port) {
task_category_policy_data_t category_policy;
mach_msg_type_number_t task_info_count = TASK_CATEGORY_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&category_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_CATEGORY_POLICY";
return std::nullopt;
}
CHECK(!get_default);
return category_policy.role;
}
// Sets the task role for `task_port`.
bool SetTaskCategoryPolicy(mach_port_t task_port, task_role_t task_role) {
task_category_policy task_category_policy{.role = task_role};
kern_return_t result =
task_policy_set(task_port, TASK_CATEGORY_POLICY,
reinterpret_cast<task_policy_t>(&task_category_policy),
TASK_CATEGORY_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_CATEGORY_POLICY";
return false;
}
return true;
}
// Taken from task_policy_private.h.
struct task_suppression_policy {
integer_t active;
integer_t lowpri_cpu;
integer_t timer_throttle;
integer_t disk_throttle;
integer_t cpu_limit;
integer_t suspend;
integer_t throughput_qos;
integer_t suppressed_cpu;
integer_t background_sockets;
integer_t reserved[7];
};
// Taken from task_policy_private.h.
#define TASK_SUPPRESSION_POLICY_COUNT \
((mach_msg_type_number_t)(sizeof(struct task_suppression_policy) / \
sizeof(integer_t)))
// Activates or deactivates the suppression policy to match the effect of App
// Nap.
bool SetTaskSuppressionPolicy(mach_port_t task_port, bool activate) {
task_suppression_policy suppression_policy = {
.active = activate,
.lowpri_cpu = activate,
.timer_throttle =
activate ? LATENCY_QOS_TIER_5 : LATENCY_QOS_TIER_UNSPECIFIED,
.disk_throttle = activate,
.cpu_limit = 0, /* unused */
.suspend = false, /* unused */
.throughput_qos = THROUGHPUT_QOS_TIER_UNSPECIFIED, /* unused */
.suppressed_cpu = activate,
.background_sockets = activate,
};
kern_return_t result =
task_policy_set(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
TASK_SUPPRESSION_POLICY_COUNT);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_set TASK_SUPPRESSION_POLICY";
return false;
}
return true;
}
// Returns true if the task suppression policy is active for `task_port`.
bool IsTaskSuppressionPolicyActive(mach_port_t task_port) {
task_suppression_policy suppression_policy = {
.active = false,
};
mach_msg_type_number_t task_info_count = TASK_SUPPRESSION_POLICY_COUNT;
boolean_t get_default = FALSE;
kern_return_t result =
task_policy_get(task_port, TASK_SUPPRESSION_POLICY,
reinterpret_cast<task_policy_t>(&suppression_policy),
&task_info_count, &get_default);
if (result != KERN_SUCCESS) {
MACH_LOG(ERROR, result) << "task_policy_get TASK_SUPPRESSION_POLICY";
return false;
}
CHECK(!get_default);
// Only check the `active` property as it is sufficient to discern the state,
// even though other properties could be used.
return suppression_policy.active;
}
// Sets the task role and the suppression policy for `task_port`.
bool SetPriorityImpl(mach_port_t task_port,
task_role_t task_role,
bool activate_suppression_policy) {
// Do both operations, even if the first one fails.
bool succeeded = SetTaskCategoryPolicy(task_port, task_role);
succeeded &= SetTaskSuppressionPolicy(task_port, activate_suppression_policy);
return succeeded;
}
} // namespace
Time Process::CreationTime() const {
int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, Pid()};
size_t len = 0;
if (sysctl(mib, std::size(mib), NULL, &len, NULL, 0) < 0)
return Time();
std::unique_ptr<struct kinfo_proc, base::FreeDeleter> proc(
static_cast<struct kinfo_proc*>(malloc(len)));
if (sysctl(mib, std::size(mib), proc.get(), &len, NULL, 0) < 0)
return Time();
return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime);
}
bool Process::CanSetPriority() {
return true;
}
Process::Priority Process::GetPriority(PortProvider* port_provider) const {
CHECK(IsValid());
CHECK(port_provider);
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
std::optional<task_role_t> task_role = GetTaskCategoryPolicyRole(task_port);
if (!task_role) {
// Upon failure, return the default value.
return Priority::kUserBlocking;
}
bool is_suppression_policy_active = IsTaskSuppressionPolicyActive(task_port);
if (*task_role == TASK_BACKGROUND_APPLICATION &&
is_suppression_policy_active) {
return Priority::kBestEffort;
} else if (*task_role == TASK_BACKGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserVisible;
} else if (*task_role == TASK_FOREGROUND_APPLICATION &&
!is_suppression_policy_active) {
return Priority::kUserBlocking;
}
// It is possible to get a different state very early in the process lifetime,
// before SetCurrentTaskDefaultRole() has been invoked. Assume highest
// priority then.
return Priority::kUserBlocking;
}
bool Process::SetPriority(PortProvider* port_provider, Priority priority) {
CHECK(IsValid());
CHECK(port_provider);
if (!CanSetPriority()) {
return false;
}
mach_port_t task_port = port_provider->TaskForHandle(Handle());
if (task_port == TASK_NULL) {
return false;
}
switch (priority) {
case Priority::kBestEffort:
// Activate the suppression policy.
// Note:
// App Nap keeps the task role to TASK_FOREGROUND_APPLICATION when it
// activates the suppression policy. Here TASK_BACKGROUND_APPLICATION is
// used instead to keep the kBestEffort role consistent with the value for
// kUserVisible (so that its is not greater than kUserVisible). This
// difference is unlikely to matter.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, true);
case Priority::kUserVisible:
// Set a task role with a lower priority than kUserBlocking, but do not
// activate the suppression policy.
return SetPriorityImpl(task_port, TASK_BACKGROUND_APPLICATION, false);
case Priority::kUserBlocking:
default:
// Set the highest priority with the suppression policy inactive.
return SetPriorityImpl(task_port, TASK_FOREGROUND_APPLICATION, false);
}
}
// static
void Process::SetCurrentTaskDefaultRole() {
if (!base::FeatureList::IsEnabled(kMacSetDefaultTaskRole)) {
return;
}
SetTaskCategoryPolicy(mach_task_self(), TASK_FOREGROUND_APPLICATION);
// Set the QoS settings to tier 0, to match the default value given to App Nap
// enabled applications.
task_qos_policy task_qos_policy = {
.task_latency_qos_tier = LATENCY_QOS_TIER_0,
.task_throughput_qos_tier = THROUGHPUT_QOS_TIER_0,
};
task_policy_set(mach_task_self(), TASK_BASE_QOS_POLICY,
reinterpret_cast<task_policy_t>(&task_qos_policy),
TASK_QOS_POLICY_COUNT);
}
} // namespace base