#include "base/process/process.h"
#include <errno.h>
#include <linux/magic.h>
#include <sys/resource.h>
#include <sys/vfs.h>
#include <cstring>
#include <string_view>
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/posix/can_lower_nice_to.h"
#include "base/process/internal_linux.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/process/process_handle.h"
#include "base/process/process_priority_delegate.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "base/unguessable_token.h"
#endif
namespace base {
#if BUILDFLAG(IS_CHROMEOS)
BASE_FEATURE(kOneGroupPerRenderer,
"OneGroupPerRenderer",
#if BUILDFLAG(IS_CHROMEOS_LACROS)
FEATURE_ENABLED_BY_DEFAULT);
#else
FEATURE_DISABLED_BY_DEFAULT);
#endif
#endif
namespace {
const int kForegroundPriority = …;
#if BUILDFLAG(IS_CHROMEOS)
ProcessPriorityDelegate* g_process_priority_delegate = nullptr;
const int kBackgroundPriority = 19;
const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
const char kFullRendererCgroupRoot[] = "/sys/fs/cgroup/cpu/chrome_renderers";
const char kForeground[] = "/chrome_renderers/foreground";
const char kBackground[] = "/chrome_renderers/background";
const char kProcPath[] = "/proc/%d/cgroup";
const char kUclampMinFile[] = "cpu.uclamp.min";
const char kUclampMaxFile[] = "cpu.uclamp.max";
constexpr int kCgroupDeleteRetries = 3;
constexpr TimeDelta kCgroupDeleteRetryTime(Seconds(1));
#if BUILDFLAG(IS_CHROMEOS_LACROS)
const char kCgroupPrefix[] = "l-";
#elif BUILDFLAG(IS_CHROMEOS_ASH)
const char kCgroupPrefix[] = "a-";
#endif
bool PathIsCGroupFileSystem(const FilePath& path) {
struct statfs statfs_buf;
if (statfs(path.value().c_str(), &statfs_buf) < 0)
return false;
return statfs_buf.f_type == CGROUP_SUPER_MAGIC;
}
struct CGroups {
bool enabled;
FilePath foreground_file;
FilePath background_file;
std::string group_prefix_token;
std::string uclamp_min;
std::string uclamp_max;
CGroups() {
foreground_file = FilePath(StringPrintf(kControlPath, kForeground));
background_file = FilePath(StringPrintf(kControlPath, kBackground));
enabled = PathIsCGroupFileSystem(foreground_file) &&
PathIsCGroupFileSystem(background_file);
if (!enabled || !FeatureList::IsEnabled(kOneGroupPerRenderer)) {
return;
}
group_prefix_token =
StrCat({kCgroupPrefix, UnguessableToken::Create().ToString(), "-"});
FilePath foreground_path = foreground_file.DirName();
ReadFileToString(foreground_path.Append(kUclampMinFile), &uclamp_min);
ReadFileToString(foreground_path.Append(kUclampMaxFile), &uclamp_max);
}
static FilePath GetForegroundCgroupDir(const std::string& token) {
std::string cgroup_path_str;
StrAppend(&cgroup_path_str, {kFullRendererCgroupRoot, "/", token});
return FilePath(cgroup_path_str);
}
static FilePath GetForegroundCgroupFile(const std::string& token) {
if (token.empty()) {
return CGroups::Get().foreground_file;
}
FilePath cgroup_path = GetForegroundCgroupDir(token);
return cgroup_path.Append("cgroup.procs");
}
static CGroups& Get() {
static auto& groups = *new CGroups;
return groups;
}
};
bool OneGroupPerRendererEnabled() {
return FeatureList::IsEnabled(kOneGroupPerRenderer) && CGroups::Get().enabled;
}
#else
const int kBackgroundPriority = …;
#endif
}
Time Process::CreationTime() const { … }
bool Process::CanSetPriority() { … }
Process::Priority Process::GetPriority() const { … }
bool Process::SetPriority(Priority priority) { … }
#if BUILDFLAG(IS_CHROMEOS)
Process::Priority GetProcessPriorityCGroup(std::string_view cgroup_contents) {
std::vector<std::string_view> lines = SplitStringPiece(
cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
for (const auto& line : lines) {
std::vector<std::string_view> fields =
SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
if (fields.size() != 3U) {
NOTREACHED();
}
if (fields[2] == kBackground)
return Process::Priority::kBestEffort;
}
return Process::Priority::kUserBlocking;
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
ProcessId Process::GetPidInNamespace() const {
StringPairs pairs;
if (!internal::ReadProcFileToTrimmedStringPairs(process_, "status", &pairs)) {
return kNullProcessId;
}
for (const auto& pair : pairs) {
const std::string& key = pair.first;
const std::string& value_str = pair.second;
if (key == "NSpid") {
std::vector<std::string_view> split_value_str = SplitStringPiece(
value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
if (split_value_str.size() <= 1) {
return kNullProcessId;
}
int value;
if (!StringToInt(split_value_str.back(), &value)) {
NOTREACHED();
}
return value;
}
}
return kNullProcessId;
}
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
bool Process::IsSeccompSandboxed() { … }
#endif
#if BUILDFLAG(IS_CHROMEOS)
void Process::SetProcessPriorityDelegate(ProcessPriorityDelegate* delegate) {
DCHECK_NE(!!g_process_priority_delegate, !!delegate);
g_process_priority_delegate = delegate;
}
bool Process::OneGroupPerRendererEnabledForTesting() {
return OneGroupPerRendererEnabled();
}
void Process::InitializePriority() {
if (g_process_priority_delegate) {
g_process_priority_delegate->InitializeProcessPriority(process_);
return;
}
if (!OneGroupPerRendererEnabled() || !IsValid() || !unique_token_.empty()) {
return;
}
unique_token_ = StrCat({CGroups::Get().group_prefix_token,
UnguessableToken::Create().ToString()});
FilePath cgroup_path = CGroups::Get().GetForegroundCgroupDir(unique_token_);
if (!CreateDirectoryAndGetError(cgroup_path, nullptr)) {
int saved_errno = errno;
LOG(ERROR) << "Failed to create cgroup, falling back to foreground"
<< ", cgroup=" << cgroup_path
<< ", errno=" << strerror(saved_errno);
unique_token_.clear();
return;
}
if (!CGroups::Get().uclamp_min.empty() &&
!WriteFile(cgroup_path.Append(kUclampMinFile),
CGroups::Get().uclamp_min)) {
LOG(ERROR) << "Failed to write uclamp min file, cgroup_path="
<< cgroup_path;
}
if (!CGroups::Get().uclamp_min.empty() &&
!WriteFile(cgroup_path.Append(kUclampMaxFile),
CGroups::Get().uclamp_max)) {
LOG(ERROR) << "Failed to write uclamp max file, cgroup_path="
<< cgroup_path;
}
}
void Process::ForgetPriority() {
if (g_process_priority_delegate) {
g_process_priority_delegate->ForgetProcessPriority(process_);
return;
}
}
void Process::CleanUpProcessScheduled(Process process, int remaining_retries) {
process.CleanUpProcess(remaining_retries);
}
void Process::CleanUpProcessAsync() const {
if (!FeatureList::IsEnabled(kOneGroupPerRenderer) || unique_token_.empty()) {
return;
}
ThreadPool::PostTask(FROM_HERE, {MayBlock(), TaskPriority::BEST_EFFORT},
BindOnce(&Process::CleanUpProcessScheduled, Duplicate(),
kCgroupDeleteRetries));
}
void Process::CleanUpProcess(int remaining_retries) const {
if (!OneGroupPerRendererEnabled() || unique_token_.empty()) {
return;
}
FilePath cgroup = CGroups::Get().GetForegroundCgroupDir(unique_token_);
if (!DeleteFile(cgroup)) {
auto saved_errno = errno;
LOG(ERROR) << "Failed to delete cgroup " << cgroup
<< ", errno=" << strerror(saved_errno);
if (remaining_retries > 0) {
std::string pidstr = NumberToString(process_);
if (!WriteFile(CGroups::Get().background_file, pidstr)) {
saved_errno = errno;
LOG(WARNING) << "Failed to move the process to background"
<< ", pid=" << pidstr
<< ", errno=" << strerror(saved_errno);
}
ThreadPool::PostDelayedTask(FROM_HERE,
{MayBlock(), TaskPriority::BEST_EFFORT},
BindOnce(&Process::CleanUpProcessScheduled,
Duplicate(), remaining_retries - 1),
kCgroupDeleteRetryTime);
}
}
}
void Process::CleanUpStaleProcessStates() {
if (!OneGroupPerRendererEnabled()) {
return;
}
FileEnumerator traversal(FilePath(kFullRendererCgroupRoot), false,
FileEnumerator::DIRECTORIES);
for (FilePath path = traversal.Next(); !path.empty();
path = traversal.Next()) {
std::string dirname = path.BaseName().value();
if (dirname == FilePath(kForeground).BaseName().value() ||
dirname == FilePath(kBackground).BaseName().value()) {
continue;
}
if (!StartsWith(dirname, kCgroupPrefix) ||
StartsWith(dirname, CGroups::Get().group_prefix_token)) {
continue;
}
if (!DeleteFile(path)) {
auto saved_errno = errno;
LOG(ERROR) << "Failed to delete " << path
<< ", errno=" << strerror(saved_errno);
}
}
}
#endif
}