1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/process/process.h"
6
7 #include <errno.h>
8 #include <linux/magic.h>
9 #include <sys/resource.h>
10 #include <sys/vfs.h>
11
12 #include <cstring>
13
14 #include "base/check.h"
15 #include "base/files/file_util.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/notreached.h"
19 #include "base/posix/can_lower_nice_to.h"
20 #include "base/process/internal_linux.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "build/build_config.h"
26 #include "build/chromeos_buildflags.h"
27
28 #if BUILDFLAG(IS_CHROMEOS)
29 #include "base/feature_list.h"
30 #include "base/files/file_enumerator.h"
31 #include "base/files/file_path.h"
32 #include "base/functional/bind.h"
33 #include "base/process/process_handle.h"
34 #include "base/strings/strcat.h"
35 #include "base/strings/string_util.h"
36 #include "base/task/thread_pool.h"
37 #include "base/unguessable_token.h"
38 #endif // BUILDFLAG(IS_CHROMEOS)
39
40 namespace base {
41
42 #if BUILDFLAG(IS_CHROMEOS)
43 BASE_FEATURE(kOneGroupPerRenderer,
44 "OneGroupPerRenderer",
45 #if BUILDFLAG(IS_CHROMEOS_LACROS)
46 FEATURE_ENABLED_BY_DEFAULT);
47 #else
48 FEATURE_DISABLED_BY_DEFAULT);
49 #endif // BUILDFLAG(IS_CHROMEOS_LACROS)
50 #endif // BUILDFLAG(IS_CHROMEOS)
51
52 namespace {
53
54 const int kForegroundPriority = 0;
55
56 #if BUILDFLAG(IS_CHROMEOS)
57 // We are more aggressive in our lowering of background process priority
58 // for chromeos as we have much more control over other processes running
59 // on the machine.
60 //
61 // TODO(davemoore) Refactor this by adding support for higher levels to set
62 // the foregrounding / backgrounding process so we don't have to keep
63 // chrome / chromeos specific logic here.
64 const int kBackgroundPriority = 19;
65 const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
66 const char kFullRendererCgroupRoot[] = "/sys/fs/cgroup/cpu/chrome_renderers";
67 const char kForeground[] = "/chrome_renderers/foreground";
68 const char kBackground[] = "/chrome_renderers/background";
69 const char kProcPath[] = "/proc/%d/cgroup";
70 const char kUclampMinFile[] = "cpu.uclamp.min";
71 const char kUclampMaxFile[] = "cpu.uclamp.max";
72
73 constexpr int kCgroupDeleteRetries = 3;
74 constexpr TimeDelta kCgroupDeleteRetryTime(Seconds(1));
75
76 #if BUILDFLAG(IS_CHROMEOS_LACROS)
77 const char kCgroupPrefix[] = "l-";
78 #elif BUILDFLAG(IS_CHROMEOS_ASH)
79 const char kCgroupPrefix[] = "a-";
80 #endif
81
PathIsCGroupFileSystem(const FilePath & path)82 bool PathIsCGroupFileSystem(const FilePath& path) {
83 struct statfs statfs_buf;
84 if (statfs(path.value().c_str(), &statfs_buf) < 0)
85 return false;
86 return statfs_buf.f_type == CGROUP_SUPER_MAGIC;
87 }
88
89 struct CGroups {
90 // Check for cgroups files. ChromeOS supports these by default. It creates
91 // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups,
92 // one contains at most a single foreground renderer and the other contains
93 // all background renderers. This allows us to limit the impact of background
94 // renderers on foreground ones to a greater level than simple renicing.
95 bool enabled;
96 FilePath foreground_file;
97 FilePath background_file;
98
99 // A unique token for this instance of the browser.
100 std::string group_prefix_token;
101
102 // UCLAMP settings for the foreground cgroups.
103 std::string uclamp_min;
104 std::string uclamp_max;
105
CGroupsbase::__anon2a35c14a0111::CGroups106 CGroups() {
107 foreground_file = FilePath(StringPrintf(kControlPath, kForeground));
108 background_file = FilePath(StringPrintf(kControlPath, kBackground));
109 enabled = PathIsCGroupFileSystem(foreground_file) &&
110 PathIsCGroupFileSystem(background_file);
111
112 if (!enabled || !FeatureList::IsEnabled(kOneGroupPerRenderer)) {
113 return;
114 }
115
116 // Generate a unique token for the full browser process
117 group_prefix_token =
118 StrCat({kCgroupPrefix, UnguessableToken::Create().ToString(), "-"});
119
120 // Reads the ULCAMP settings from the foreground cgroup that will be used
121 // for each renderer's cgroup.
122 FilePath foreground_path = foreground_file.DirName();
123 ReadFileToString(foreground_path.Append(kUclampMinFile), &uclamp_min);
124 ReadFileToString(foreground_path.Append(kUclampMaxFile), &uclamp_max);
125 }
126
127 // Returns the full path to a the cgroup dir of a process using
128 // the supplied token.
GetForegroundCgroupDirbase::__anon2a35c14a0111::CGroups129 static FilePath GetForegroundCgroupDir(const std::string& token) {
130 // Get individualized cgroup if the feature is enabled
131 std::string cgroup_path_str;
132 StrAppend(&cgroup_path_str, {kFullRendererCgroupRoot, "/", token});
133 return FilePath(cgroup_path_str);
134 }
135
136 // Returns the path to the cgroup.procs file of the foreground cgroup.
GetForegroundCgroupFilebase::__anon2a35c14a0111::CGroups137 static FilePath GetForegroundCgroupFile(const std::string& token) {
138 // Processes with an empty token use the default foreground cgroup.
139 if (token.empty()) {
140 return CGroups::Get().foreground_file;
141 }
142
143 FilePath cgroup_path = GetForegroundCgroupDir(token);
144 return cgroup_path.Append("cgroup.procs");
145 }
146
Getbase::__anon2a35c14a0111::CGroups147 static CGroups& Get() {
148 static auto& groups = *new CGroups;
149 return groups;
150 }
151 };
152
153 // Returns true if the 'OneGroupPerRenderer' feature is enabled. The feature
154 // is enabled if the kOneGroupPerRenderer feature flag is enabled and the
155 // system supports the chrome cgroups. Will block if this is the first call
156 // that will read the cgroup configs.
OneGroupPerRendererEnabled()157 bool OneGroupPerRendererEnabled() {
158 return FeatureList::IsEnabled(kOneGroupPerRenderer) && CGroups::Get().enabled;
159 }
160 #else
161 const int kBackgroundPriority = 5;
162 #endif // BUILDFLAG(IS_CHROMEOS)
163
164 } // namespace
165
CreationTime() const166 Time Process::CreationTime() const {
167 int64_t start_ticks = is_current()
168 ? internal::ReadProcSelfStatsAndGetFieldAsInt64(
169 internal::VM_STARTTIME)
170 : internal::ReadProcStatsAndGetFieldAsInt64(
171 Pid(), internal::VM_STARTTIME);
172
173 if (!start_ticks)
174 return Time();
175
176 TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks);
177 Time boot_time = internal::GetBootTime();
178 if (boot_time.is_null())
179 return Time();
180 return Time(boot_time + start_offset);
181 }
182
183 // static
CanBackgroundProcesses()184 bool Process::CanBackgroundProcesses() {
185 #if BUILDFLAG(IS_CHROMEOS)
186 if (CGroups::Get().enabled)
187 return true;
188 #endif // BUILDFLAG(IS_CHROMEOS)
189
190 static const bool can_reraise_priority =
191 internal::CanLowerNiceTo(kForegroundPriority);
192 return can_reraise_priority;
193 }
194
IsProcessBackgrounded() const195 bool Process::IsProcessBackgrounded() const {
196 DCHECK(IsValid());
197
198 #if BUILDFLAG(IS_CHROMEOS)
199 if (CGroups::Get().enabled) {
200 // Used to allow reading the process priority from proc on thread launch.
201 ScopedAllowBlocking scoped_allow_blocking;
202 std::string proc;
203 if (ReadFileToString(FilePath(StringPrintf(kProcPath, process_)), &proc)) {
204 return IsProcessBackgroundedCGroup(proc);
205 }
206 return false;
207 }
208 #endif // BUILDFLAG(IS_CHROMEOS)
209
210 return GetPriority() == kBackgroundPriority;
211 }
212
SetProcessBackgrounded(bool background)213 bool Process::SetProcessBackgrounded(bool background) {
214 DCHECK(IsValid());
215
216 #if BUILDFLAG(IS_CHROMEOS)
217 if (CGroups::Get().enabled) {
218 std::string pid = NumberToString(process_);
219 const FilePath file =
220 background ? CGroups::Get().background_file
221 : CGroups::Get().GetForegroundCgroupFile(unique_token_);
222 return WriteFile(file, pid);
223 }
224 #endif // BUILDFLAG(IS_CHROMEOS)
225
226 if (!CanBackgroundProcesses())
227 return false;
228
229 int priority = background ? kBackgroundPriority : kForegroundPriority;
230 int result = setpriority(PRIO_PROCESS, static_cast<id_t>(process_), priority);
231 DPCHECK(result == 0);
232 return result == 0;
233 }
234
235 #if BUILDFLAG(IS_CHROMEOS)
IsProcessBackgroundedCGroup(const StringPiece & cgroup_contents)236 bool IsProcessBackgroundedCGroup(const StringPiece& cgroup_contents) {
237 // The process can be part of multiple control groups, and for each cgroup
238 // hierarchy there's an entry in the file. We look for a control group
239 // named "/chrome_renderers/background" to determine if the process is
240 // backgrounded. crbug.com/548818.
241 std::vector<StringPiece> lines = SplitStringPiece(
242 cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
243 for (const auto& line : lines) {
244 std::vector<StringPiece> fields =
245 SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
246 if (fields.size() != 3U) {
247 NOTREACHED();
248 continue;
249 }
250 if (fields[2] == kBackground)
251 return true;
252 }
253
254 return false;
255 }
256 #endif // BUILDFLAG(IS_CHROMEOS)
257
258 #if BUILDFLAG(IS_CHROMEOS_ASH)
259 // Reads /proc/<pid>/status and returns the PID in its PID namespace.
260 // If the process is not in a PID namespace or /proc/<pid>/status does not
261 // report NSpid, kNullProcessId is returned.
GetPidInNamespace() const262 ProcessId Process::GetPidInNamespace() const {
263 std::string status;
264 {
265 // Synchronously reading files in /proc does not hit the disk.
266 ScopedAllowBlocking scoped_allow_blocking;
267 FilePath status_file =
268 FilePath("/proc").Append(NumberToString(process_)).Append("status");
269 if (!ReadFileToString(status_file, &status)) {
270 return kNullProcessId;
271 }
272 }
273
274 StringPairs pairs;
275 SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
276 for (const auto& pair : pairs) {
277 const std::string& key = pair.first;
278 const std::string& value_str = pair.second;
279 if (key == "NSpid") {
280 std::vector<StringPiece> split_value_str = SplitStringPiece(
281 value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
282 if (split_value_str.size() <= 1) {
283 return kNullProcessId;
284 }
285 int value;
286 // The last value in the list is the PID in the namespace.
287 if (!StringToInt(split_value_str.back(), &value)) {
288 NOTREACHED();
289 return kNullProcessId;
290 }
291 return value;
292 }
293 }
294 return kNullProcessId;
295 }
296 #endif // BUILDFLAG(IS_CHROMEOS_ASH)
297
298 #if BUILDFLAG(IS_CHROMEOS)
299 // static
OneGroupPerRendererEnabledForTesting()300 bool Process::OneGroupPerRendererEnabledForTesting() {
301 return OneGroupPerRendererEnabled();
302 }
303
304 // On Chrome OS, each renderer runs in its own cgroup when running in the
305 // foreground. After process creation the cgroup is created using a
306 // unique token.
InitializePriority()307 void Process::InitializePriority() {
308 if (!OneGroupPerRendererEnabled() || !IsValid() || !unique_token_.empty()) {
309 return;
310 }
311
312 // The token has the following format:
313 // {cgroup_prefix}{UnguessableToken}
314 // The cgroup prefix is to distinguish ash from lacros tokens for stale
315 // cgroup cleanup.
316 unique_token_ = StrCat({CGroups::Get().group_prefix_token,
317 UnguessableToken::Create().ToString()});
318
319 FilePath cgroup_path = CGroups::Get().GetForegroundCgroupDir(unique_token_);
320 // Note that CreateDirectoryAndGetError() does not fail if the directory
321 // already exits.
322 if (!CreateDirectoryAndGetError(cgroup_path, nullptr)) {
323 // If creating the directory fails, fall back to use the foreground group.
324 int saved_errno = errno;
325 LOG(ERROR) << "Failed to create cgroup, falling back to foreground"
326 << ", cgroup=" << cgroup_path
327 << ", errno=" << strerror(saved_errno);
328
329 unique_token_.clear();
330 return;
331 }
332
333 if (!CGroups::Get().uclamp_min.empty() &&
334 !WriteFile(cgroup_path.Append(kUclampMinFile),
335 CGroups::Get().uclamp_min)) {
336 LOG(ERROR) << "Failed to write uclamp min file, cgroup_path="
337 << cgroup_path;
338 }
339 if (!CGroups::Get().uclamp_min.empty() &&
340 !WriteFile(cgroup_path.Append(kUclampMaxFile),
341 CGroups::Get().uclamp_max)) {
342 LOG(ERROR) << "Failed to write uclamp max file, cgroup_path="
343 << cgroup_path;
344 }
345 }
346
347 // static
CleanUpProcessScheduled(Process process,int remaining_retries)348 void Process::CleanUpProcessScheduled(Process process, int remaining_retries) {
349 process.CleanUpProcess(remaining_retries);
350 }
351
CleanUpProcessAsync() const352 void Process::CleanUpProcessAsync() const {
353 if (!FeatureList::IsEnabled(kOneGroupPerRenderer) || unique_token_.empty()) {
354 return;
355 }
356
357 ThreadPool::PostTask(FROM_HERE, {MayBlock(), TaskPriority::BEST_EFFORT},
358 BindOnce(&Process::CleanUpProcessScheduled, Duplicate(),
359 kCgroupDeleteRetries));
360 }
361
CleanUpProcess(int remaining_retries) const362 void Process::CleanUpProcess(int remaining_retries) const {
363 if (!OneGroupPerRendererEnabled() || unique_token_.empty()) {
364 return;
365 }
366
367 // Try to delete the cgroup
368 // TODO(1322562): We can use notify_on_release to automoatically delete the
369 // cgroup when the process has left the cgroup.
370 FilePath cgroup = CGroups::Get().GetForegroundCgroupDir(unique_token_);
371 if (!DeleteFile(cgroup)) {
372 auto saved_errno = errno;
373 LOG(ERROR) << "Failed to delete cgroup " << cgroup
374 << ", errno=" << strerror(saved_errno);
375 // If the delete failed, then the process is still potentially in the
376 // cgroup. Move the process to background and schedule a callback to try
377 // again.
378 if (remaining_retries > 0) {
379 std::string pidstr = NumberToString(process_);
380 if (!WriteFile(CGroups::Get().background_file, pidstr)) {
381 // Failed to move the process, LOG a warning but try again.
382 saved_errno = errno;
383 LOG(WARNING) << "Failed to move the process to background"
384 << ", pid=" << pidstr
385 << ", errno=" << strerror(saved_errno);
386 }
387 ThreadPool::PostDelayedTask(FROM_HERE,
388 {MayBlock(), TaskPriority::BEST_EFFORT},
389 BindOnce(&Process::CleanUpProcessScheduled,
390 Duplicate(), remaining_retries - 1),
391 kCgroupDeleteRetryTime);
392 }
393 }
394 }
395
396 // static
CleanUpStaleProcessStates()397 void Process::CleanUpStaleProcessStates() {
398 if (!OneGroupPerRendererEnabled()) {
399 return;
400 }
401
402 FileEnumerator traversal(FilePath(kFullRendererCgroupRoot), false,
403 FileEnumerator::DIRECTORIES);
404 for (FilePath path = traversal.Next(); !path.empty();
405 path = traversal.Next()) {
406 std::string dirname = path.BaseName().value();
407 if (dirname == FilePath(kForeground).BaseName().value() ||
408 dirname == FilePath(kBackground).BaseName().value()) {
409 continue;
410 }
411
412 if (!StartsWith(dirname, kCgroupPrefix) ||
413 StartsWith(dirname, CGroups::Get().group_prefix_token)) {
414 continue;
415 }
416
417 if (!DeleteFile(path)) {
418 auto saved_errno = errno;
419 LOG(ERROR) << "Failed to delete " << path
420 << ", errno=" << strerror(saved_errno);
421 }
422 }
423 }
424 #endif // BUILDFLAG(IS_CHROMEOS)
425
426 } // namespace base
427