Import Cobalt 19.master.0.205881
diff --git a/src/base/process/internal_aix.cc b/src/base/process/internal_aix.cc
new file mode 100644
index 0000000..98f18ab
--- /dev/null
+++ b/src/base/process/internal_aix.cc
@@ -0,0 +1,156 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/internal_aix.h"
+
+#include <sys/procfs.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "starboard/types.h"
+
+// Not defined on AIX by default.
+#define NAME_MAX 255
+
+namespace base {
+namespace internalAIX {
+
+const char kProcDir[] = "/proc";
+
+const char kStatFile[] = "psinfo"; // AIX specific
+
+FilePath GetProcPidDir(pid_t pid) {
+ return FilePath(kProcDir).Append(IntToString(pid));
+}
+
+pid_t ProcDirSlotToPid(const char* d_name) {
+ int i;
+ for (i = 0; i < NAME_MAX && d_name[i]; ++i) {
+ if (!IsAsciiDigit(d_name[i])) {
+ return 0;
+ }
+ }
+ if (i == NAME_MAX)
+ return 0;
+
+ // Read the process's command line.
+ pid_t pid;
+ std::string pid_string(d_name);
+ if (!StringToInt(pid_string, &pid)) {
+ NOTREACHED();
+ return 0;
+ }
+ return pid;
+}
+
+bool ReadProcFile(const FilePath& file, struct psinfo* info) {
+ // Synchronously reading files in /proc is safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ int fileId;
+ if ((fileId = open(file.value().c_str(), O_RDONLY)) < 0) {
+ DLOG(WARNING) << "Failed to open " << file.MaybeAsASCII()
+ << " errno = " << errno;
+ return false;
+ }
+
+ if (read(fileId, info, sizeof(*info)) < 0) {
+ DLOG(WARNING) << "Failed to read " << file.MaybeAsASCII()
+ << " errno = " << errno;
+ return false;
+ }
+
+ return true;
+}
+
+bool ReadProcStats(pid_t pid, struct psinfo* info) {
+ FilePath stat_file = internalAIX::GetProcPidDir(pid).Append(kStatFile);
+ return ReadProcFile(stat_file, info);
+}
+
+bool ParseProcStats(struct psinfo& stats_data,
+ std::vector<std::string>* proc_stats) {
+ // The stat file is formatted as:
+ // struct psinfo
+ // see -
+ // https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.files/proc.htm
+ proc_stats->clear();
+ // PID.
+ proc_stats->push_back(IntToString(stats_data.pr_pid));
+ // Process name without parentheses. // 1
+ proc_stats->push_back(stats_data.pr_fname);
+ // Process State (Not available) // 2
+ proc_stats->push_back("0");
+ // Process id of parent // 3
+ proc_stats->push_back(IntToString(stats_data.pr_ppid));
+
+ // Process group id // 4
+ proc_stats->push_back(IntToString(stats_data.pr_pgid));
+
+ return true;
+}
+
+typedef std::map<std::string, std::string> ProcStatMap;
+void ParseProcStat(const std::string& contents, ProcStatMap* output) {
+ StringPairs key_value_pairs;
+ SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs);
+ for (size_t i = 0; i < key_value_pairs.size(); ++i) {
+ output->insert(key_value_pairs[i]);
+ }
+}
+
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ int64_t value;
+ return StringToInt64(proc_stats[field_num], &value) ? value : 0;
+}
+
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ size_t value;
+ return StringToSizeT(proc_stats[field_num], &value) ? value : 0;
+}
+
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) {
+ struct psinfo stats_data;
+ if (!ReadProcStats(pid, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+
+ return GetProcStatsFieldAsInt64(proc_stats, field_num);
+}
+
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num) {
+ struct psinfo stats_data;
+ if (!ReadProcStats(pid, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+ return GetProcStatsFieldAsSizeT(proc_stats, field_num);
+}
+
+} // namespace internalAIX
+} // namespace base
diff --git a/src/base/process/internal_aix.h b/src/base/process/internal_aix.h
new file mode 100644
index 0000000..5ce03d9
--- /dev/null
+++ b/src/base/process/internal_aix.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains internal routines that are called by other files in
+// base/process/.
+
+#ifndef BASE_PROCESS_INTERNAL_AIX_H_
+#define BASE_PROCESS_INTERNAL_AIX_H_
+
+#include <unistd.h>
+
+#include "base/files/file_path.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace internalAIX {
+
+// "/proc"
+extern const char kProcDir[];
+
+// "psinfo"
+extern const char kStatFile[];
+
+// Returns a FilePath to "/proc/pid".
+base::FilePath GetProcPidDir(pid_t pid);
+
+// Take a /proc directory entry named |d_name|, and if it is the directory for
+// a process, convert it to a pid_t.
+// Returns 0 on failure.
+// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234.
+pid_t ProcDirSlotToPid(const char* d_name);
+
+// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read
+// and is non-empty.
+bool ReadProcStats(pid_t pid, std::string* buffer);
+
+// Takes |stats_data| and populates |proc_stats| with the values split by
+// spaces. Taking into account the 2nd field may, in itself, contain spaces.
+// Returns true if successful.
+bool ParseProcStats(const std::string& stats_data,
+ std::vector<std::string>* proc_stats);
+
+// Fields from /proc/<pid>/psinfo.
+// If the ordering ever changes, carefully review functions that use these
+// values.
+// For AIX this is the bare minimum that we need. Most of the commented out
+// fields can still be extracted but currently none of these are required.
+enum ProcStatsFields {
+ VM_COMM = 1, // Filename of executable, without parentheses.
+ // VM_STATE = 2, // Letter indicating the state of the process.
+ VM_PPID = 3, // PID of the parent.
+ VM_PGRP = 4, // Process group id.
+ // VM_UTIME = 13, // Time scheduled in user mode in clock ticks.
+ // VM_STIME = 14, // Time scheduled in kernel mode in clock ticks.
+ // VM_NUMTHREADS = 19, // Number of threads.
+ // VM_STARTTIME = 21, // The time the process started in clock ticks.
+ // VM_VSIZE = 22, // Virtual memory size in bytes.
+ // VM_RSS = 23, // Resident Set Size in pages.
+};
+
+// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure.
+// This version does not handle the first 3 values, since the first value is
+// simply |pid|, and the next two values are strings.
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Same as GetProcStatsFieldAsInt64(), but for size_t values.
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Convenience wrapper around GetProcStatsFieldAsInt64(), ParseProcStats() and
+// ReadProcStats(). See GetProcStatsFieldAsInt64() for details.
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num);
+
+// Same as ReadProcStatsAndGetFieldAsInt64() but for size_t values.
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num);
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_PROCESS_INTERNAL_AIX_H_
diff --git a/src/base/process/internal_linux.cc b/src/base/process/internal_linux.cc
new file mode 100644
index 0000000..49f28fc
--- /dev/null
+++ b/src/base/process/internal_linux.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/internal_linux.h"
+
+#include <limits.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "starboard/types.h"
+
+// Not defined on AIX by default.
+#if defined(OS_AIX)
+#define NAME_MAX 255
+#endif
+
+namespace base {
+namespace internal {
+
+const char kProcDir[] = "/proc";
+
+const char kStatFile[] = "stat";
+
+FilePath GetProcPidDir(pid_t pid) {
+ return FilePath(kProcDir).Append(IntToString(pid));
+}
+
+pid_t ProcDirSlotToPid(const char* d_name) {
+ int i;
+ for (i = 0; i < NAME_MAX && d_name[i]; ++i) {
+ if (!IsAsciiDigit(d_name[i])) {
+ return 0;
+ }
+ }
+ if (i == NAME_MAX)
+ return 0;
+
+ // Read the process's command line.
+ pid_t pid;
+ std::string pid_string(d_name);
+ if (!StringToInt(pid_string, &pid)) {
+ NOTREACHED();
+ return 0;
+ }
+ return pid;
+}
+
+bool ReadProcFile(const FilePath& file, std::string* buffer) {
+ buffer->clear();
+ // Synchronously reading files in /proc is safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ if (!ReadFileToString(file, buffer)) {
+ DLOG(WARNING) << "Failed to read " << file.MaybeAsASCII();
+ return false;
+ }
+ return !buffer->empty();
+}
+
+bool ReadProcStats(pid_t pid, std::string* buffer) {
+ FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
+ return ReadProcFile(stat_file, buffer);
+}
+
+bool ParseProcStats(const std::string& stats_data,
+ std::vector<std::string>* proc_stats) {
+ // |stats_data| may be empty if the process disappeared somehow.
+ // e.g. http://crbug.com/145811
+ if (stats_data.empty())
+ return false;
+
+ // The stat file is formatted as:
+ // pid (process name) data1 data2 .... dataN
+ // Look for the closing paren by scanning backwards, to avoid being fooled by
+ // processes with ')' in the name.
+ size_t open_parens_idx = stats_data.find(" (");
+ size_t close_parens_idx = stats_data.rfind(") ");
+ if (open_parens_idx == std::string::npos ||
+ close_parens_idx == std::string::npos ||
+ open_parens_idx > close_parens_idx) {
+ DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'";
+ NOTREACHED();
+ return false;
+ }
+ open_parens_idx++;
+
+ proc_stats->clear();
+ // PID.
+ proc_stats->push_back(stats_data.substr(0, open_parens_idx));
+ // Process name without parentheses.
+ proc_stats->push_back(
+ stats_data.substr(open_parens_idx + 1,
+ close_parens_idx - (open_parens_idx + 1)));
+
+ // Split the rest.
+ std::vector<std::string> other_stats = SplitString(
+ stats_data.substr(close_parens_idx + 2), " ",
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+ for (size_t i = 0; i < other_stats.size(); ++i)
+ proc_stats->push_back(other_stats[i]);
+ return true;
+}
+
+typedef std::map<std::string, std::string> ProcStatMap;
+void ParseProcStat(const std::string& contents, ProcStatMap* output) {
+ StringPairs key_value_pairs;
+ SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs);
+ for (size_t i = 0; i < key_value_pairs.size(); ++i) {
+ output->insert(key_value_pairs[i]);
+ }
+}
+
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ int64_t value;
+ return StringToInt64(proc_stats[field_num], &value) ? value : 0;
+}
+
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num) {
+ DCHECK_GE(field_num, VM_PPID);
+ CHECK_LT(static_cast<size_t>(field_num), proc_stats.size());
+
+ size_t value;
+ return StringToSizeT(proc_stats[field_num], &value) ? value : 0;
+}
+
+int64_t ReadStatFileAndGetFieldAsInt64(const FilePath& stat_file,
+ ProcStatsFields field_num) {
+ std::string stats_data;
+ if (!ReadProcFile(stat_file, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+ return GetProcStatsFieldAsInt64(proc_stats, field_num);
+}
+
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) {
+ FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile);
+ return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
+}
+
+int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num) {
+ FilePath stat_file = FilePath(kProcDir).Append("self").Append(kStatFile);
+ return ReadStatFileAndGetFieldAsInt64(stat_file, field_num);
+}
+
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid,
+ ProcStatsFields field_num) {
+ std::string stats_data;
+ if (!ReadProcStats(pid, &stats_data))
+ return 0;
+ std::vector<std::string> proc_stats;
+ if (!ParseProcStats(stats_data, &proc_stats))
+ return 0;
+ return GetProcStatsFieldAsSizeT(proc_stats, field_num);
+}
+
+Time GetBootTime() {
+ FilePath path("/proc/stat");
+ std::string contents;
+ if (!ReadProcFile(path, &contents))
+ return Time();
+ ProcStatMap proc_stat;
+ ParseProcStat(contents, &proc_stat);
+ ProcStatMap::const_iterator btime_it = proc_stat.find("btime");
+ if (btime_it == proc_stat.end())
+ return Time();
+ int btime;
+ if (!StringToInt(btime_it->second, &btime))
+ return Time();
+ return Time::FromTimeT(btime);
+}
+
+TimeDelta GetUserCpuTimeSinceBoot() {
+ FilePath path("/proc/stat");
+ std::string contents;
+ if (!ReadProcFile(path, &contents))
+ return TimeDelta();
+
+ ProcStatMap proc_stat;
+ ParseProcStat(contents, &proc_stat);
+ ProcStatMap::const_iterator cpu_it = proc_stat.find("cpu");
+ if (cpu_it == proc_stat.end())
+ return TimeDelta();
+
+ std::vector<std::string> cpu = SplitString(
+ cpu_it->second, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ if (cpu.size() < 2 || cpu[0] != "cpu")
+ return TimeDelta();
+
+ uint64_t user;
+ uint64_t nice;
+ if (!StringToUint64(cpu[0], &user) || !StringToUint64(cpu[1], &nice))
+ return TimeDelta();
+
+ return ClockTicksToTimeDelta(user + nice);
+}
+
+TimeDelta ClockTicksToTimeDelta(int clock_ticks) {
+ // This queries the /proc-specific scaling factor which is
+ // conceptually the system hertz. To dump this value on another
+ // system, try
+ // od -t dL /proc/self/auxv
+ // and look for the number after 17 in the output; mine is
+ // 0000040 17 100 3 134512692
+ // which means the answer is 100.
+ // It may be the case that this value is always 100.
+ static const int kHertz = sysconf(_SC_CLK_TCK);
+
+ return TimeDelta::FromMicroseconds(
+ Time::kMicrosecondsPerSecond * clock_ticks / kHertz);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/src/base/process/internal_linux.h b/src/base/process/internal_linux.h
new file mode 100644
index 0000000..8139c18
--- /dev/null
+++ b/src/base/process/internal_linux.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains internal routines that are called by other files in
+// base/process/.
+
+#ifndef BASE_PROCESS_INTERNAL_LINUX_H_
+#define BASE_PROCESS_INTERNAL_LINUX_H_
+
+#include <unistd.h>
+
+#include "base/files/file_path.h"
+#include "starboard/types.h"
+
+namespace base {
+
+class Time;
+class TimeDelta;
+
+namespace internal {
+
+// "/proc"
+extern const char kProcDir[];
+
+// "stat"
+extern const char kStatFile[];
+
+// Returns a FilePath to "/proc/pid".
+base::FilePath GetProcPidDir(pid_t pid);
+
+// Take a /proc directory entry named |d_name|, and if it is the directory for
+// a process, convert it to a pid_t.
+// Returns 0 on failure.
+// e.g. /proc/self/ will return 0, whereas /proc/1234 will return 1234.
+pid_t ProcDirSlotToPid(const char* d_name);
+
+// Reads /proc/<pid>/stat into |buffer|. Returns true if the file can be read
+// and is non-empty.
+bool ReadProcStats(pid_t pid, std::string* buffer);
+
+// Takes |stats_data| and populates |proc_stats| with the values split by
+// spaces. Taking into account the 2nd field may, in itself, contain spaces.
+// Returns true if successful.
+bool ParseProcStats(const std::string& stats_data,
+ std::vector<std::string>* proc_stats);
+
+// Fields from /proc/<pid>/stat, 0-based. See man 5 proc.
+// If the ordering ever changes, carefully review functions that use these
+// values.
+enum ProcStatsFields {
+ VM_COMM = 1, // Filename of executable, without parentheses.
+ VM_STATE = 2, // Letter indicating the state of the process.
+ VM_PPID = 3, // PID of the parent.
+ VM_PGRP = 4, // Process group id.
+ VM_MINFLT = 9, // Minor page fault count excluding children.
+ VM_MAJFLT = 11, // Major page fault count excluding children.
+ VM_UTIME = 13, // Time scheduled in user mode in clock ticks.
+ VM_STIME = 14, // Time scheduled in kernel mode in clock ticks.
+ VM_NUMTHREADS = 19, // Number of threads.
+ VM_STARTTIME = 21, // The time the process started in clock ticks.
+ VM_VSIZE = 22, // Virtual memory size in bytes.
+ VM_RSS = 23, // Resident Set Size in pages.
+};
+
+// Reads the |field_num|th field from |proc_stats|. Returns 0 on failure.
+// This version does not handle the first 3 values, since the first value is
+// simply |pid|, and the next two values are strings.
+int64_t GetProcStatsFieldAsInt64(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Same as GetProcStatsFieldAsInt64(), but for size_t values.
+size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats,
+ ProcStatsFields field_num);
+
+// Convenience wrappers around GetProcStatsFieldAsInt64(), ParseProcStats() and
+// ReadProcStats(). See GetProcStatsFieldAsInt64() for details.
+int64_t ReadStatsFilendGetFieldAsInt64(const FilePath& stat_file,
+ ProcStatsFields field_num);
+int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num);
+int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num);
+
+// Same as ReadProcStatsAndGetFieldAsInt64() but for size_t values.
+size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid,
+ ProcStatsFields field_num);
+
+// Returns the time that the OS started. Clock ticks are relative to this.
+Time GetBootTime();
+
+// Returns the amount of time spent in user space since boot across all CPUs.
+TimeDelta GetUserCpuTimeSinceBoot();
+
+// Converts Linux clock ticks to a wall time delta.
+TimeDelta ClockTicksToTimeDelta(int clock_ticks);
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_PROCESS_INTERNAL_LINUX_H_
diff --git a/src/base/process/kill.cc b/src/base/process/kill.cc
new file mode 100644
index 0000000..f641d3f
--- /dev/null
+++ b/src/base/process/kill.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/kill.h"
+
+#include "base/bind.h"
+#include "base/process/process_iterator.h"
+#include "base/task/post_task.h"
+#include "base/time/time.h"
+
+namespace base {
+
+bool KillProcesses(const FilePath::StringType& executable_name,
+ int exit_code,
+ const ProcessFilter* filter) {
+ bool result = true;
+ NamedProcessIterator iter(executable_name, filter);
+ while (const ProcessEntry* entry = iter.NextProcessEntry()) {
+ Process process = Process::Open(entry->pid());
+ // Sometimes process open fails. This would cause a DCHECK in
+ // process.Terminate(). Maybe the process has killed itself between the
+ // time the process list was enumerated and the time we try to open the
+ // process?
+ if (!process.IsValid()) {
+ result = false;
+ continue;
+ }
+ result &= process.Terminate(exit_code, true);
+ }
+ return result;
+}
+
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+// Common implementation for platforms under which |process| is a handle to
+// the process, rather than an identifier that must be "reaped".
+void EnsureProcessTerminated(Process process) {
+ DCHECK(!process.is_current());
+
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+
+ PostDelayedTaskWithTraits(
+ FROM_HERE,
+ {TaskPriority::BEST_EFFORT, TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+ BindOnce(
+ [](Process process) {
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+#if defined(OS_WIN)
+ process.Terminate(win::kProcessKilledExitCode, false);
+#else
+ process.Terminate(-1, false);
+#endif
+ },
+ std::move(process)),
+ TimeDelta::FromSeconds(2));
+}
+#endif // defined(OS_WIN) || defined(OS_FUCHSIA)
+
+} // namespace base
diff --git a/src/base/process/kill.h b/src/base/process/kill.h
new file mode 100644
index 0000000..9acfb8a
--- /dev/null
+++ b/src/base/process/kill.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains routines to kill processes and get the exit code and
+// termination status.
+
+#ifndef BASE_PROCESS_KILL_H_
+#define BASE_PROCESS_KILL_H_
+
+#include "base/files/file_path.h"
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class ProcessFilter;
+
+#if defined(OS_WIN)
+namespace win {
+
+// See definition in sandbox/win/src/sandbox_types.h
+const DWORD kSandboxFatalMemoryExceeded = 7012;
+
+// Exit codes with special meanings on Windows.
+const DWORD kNormalTerminationExitCode = 0;
+const DWORD kDebuggerInactiveExitCode = 0xC0000354;
+const DWORD kKeyboardInterruptExitCode = 0xC000013A;
+const DWORD kDebuggerTerminatedExitCode = 0x40010004;
+
+// This exit code is used by the Windows task manager when it kills a
+// process. It's value is obviously not that unique, and it's
+// surprising to me that the task manager uses this value, but it
+// seems to be common practice on Windows to test for it as an
+// indication that the task manager has killed something if the
+// process goes away.
+const DWORD kProcessKilledExitCode = 1;
+
+} // namespace win
+
+#endif // OS_WIN
+
+// Return status values from GetTerminationStatus. Don't use these as
+// exit code arguments to KillProcess*(), use platform/application
+// specific values instead.
+enum TerminationStatus {
+ TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status
+ TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status
+ TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill
+ TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault
+ TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet
+#if defined(OS_CHROMEOS)
+ // Used for the case when oom-killer kills a process on ChromeOS.
+ TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM,
+#endif
+#if defined(OS_ANDROID)
+ // On Android processes are spawned from the system Zygote and we do not get
+ // the termination status. We can't know if the termination was a crash or an
+ // oom kill for sure, but we can use status of the strong process bindings as
+ // a hint.
+ TERMINATION_STATUS_OOM_PROTECTED, // child was protected from oom kill
+#endif
+ TERMINATION_STATUS_LAUNCH_FAILED, // child process never launched
+ TERMINATION_STATUS_OOM, // Process died due to oom
+ TERMINATION_STATUS_MAX_ENUM
+};
+
+// Attempts to kill all the processes on the current machine that were launched
+// from the given executable name, ending them with the given exit code. If
+// filter is non-null, then only processes selected by the filter are killed.
+// Returns true if all processes were able to be killed off, false if at least
+// one couldn't be killed.
+BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name,
+ int exit_code,
+ const ProcessFilter* filter);
+
+#if defined(OS_POSIX)
+// Attempts to kill the process group identified by |process_group_id|. Returns
+// true on success.
+BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id);
+#endif // defined(OS_POSIX)
+
+// Get the termination status of the process by interpreting the
+// circumstances of the child process' death. |exit_code| is set to
+// the status returned by waitpid() on POSIX, and from GetExitCodeProcess() on
+// Windows, and may not be null. Note that on Linux, this function
+// will only return a useful result the first time it is called after
+// the child exits (because it will reap the child and the information
+// will no longer be available).
+BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle,
+ int* exit_code);
+
+#if defined(OS_POSIX)
+// Send a kill signal to the process and then wait for the process to exit
+// and get the termination status.
+//
+// This is used in situations where it is believed that the process is dead
+// or dying (because communication with the child process has been cut).
+// In order to avoid erroneously returning that the process is still running
+// because the kernel is still cleaning it up, this will wait for the process
+// to terminate. In order to avoid the risk of hanging while waiting for the
+// process to terminate, send a SIGKILL to the process before waiting for the
+// termination status.
+//
+// Note that it is not an option to call WaitForExitCode and then
+// GetTerminationStatus as the child will be reaped when WaitForExitCode
+// returns, and this information will be lost.
+//
+BASE_EXPORT TerminationStatus GetKnownDeadTerminationStatus(
+ ProcessHandle handle, int* exit_code);
+
+#if defined(OS_LINUX)
+// Spawns a thread to wait asynchronously for the child |process| to exit
+// and then reaps it.
+BASE_EXPORT void EnsureProcessGetsReaped(Process process);
+#endif // defined(OS_LINUX)
+#endif // defined(OS_POSIX)
+
+// Registers |process| to be asynchronously monitored for termination, forcibly
+// terminated if necessary, and reaped on exit. The caller should have signalled
+// |process| to exit before calling this API. The API will allow a couple of
+// seconds grace period before forcibly terminating |process|.
+// TODO(https://crbug.com/806451): The Mac implementation currently blocks the
+// calling thread for up to two seconds.
+BASE_EXPORT void EnsureProcessTerminated(Process process);
+
+// These are only sparingly used, and not needed on Fuchsia. They could be
+// implemented if necessary.
+#if !defined(OS_FUCHSIA)
+// Wait for all the processes based on the named executable to exit. If filter
+// is non-null, then only processes selected by the filter are waited on.
+// Returns after all processes have exited or wait_milliseconds have expired.
+// Returns true if all the processes exited, false otherwise.
+BASE_EXPORT bool WaitForProcessesToExit(
+ const FilePath::StringType& executable_name,
+ base::TimeDelta wait,
+ const ProcessFilter* filter);
+
+// Waits a certain amount of time (can be 0) for all the processes with a given
+// executable name to exit, then kills off any of them that are still around.
+// If filter is non-null, then only processes selected by the filter are waited
+// on. Killed processes are ended with the given exit code. Returns false if
+// any processes needed to be killed, true if they all exited cleanly within
+// the wait_milliseconds delay.
+BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name,
+ base::TimeDelta wait,
+ int exit_code,
+ const ProcessFilter* filter);
+#endif // !defined(OS_FUCHSIA)
+
+} // namespace base
+
+#endif // BASE_PROCESS_KILL_H_
diff --git a/src/base/process/kill_fuchsia.cc b/src/base/process/kill_fuchsia.cc
new file mode 100644
index 0000000..8faf64d
--- /dev/null
+++ b/src/base/process/kill_fuchsia.cc
@@ -0,0 +1,54 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/kill.h"
+
+#include <zircon/syscalls.h>
+
+#include "base/process/process_iterator.h"
+#include "base/task/post_task.h"
+#include "base/threading/platform_thread.h"
+#include "starboard/types.h"
+
+namespace base {
+
+bool KillProcessGroup(ProcessHandle process_group_id) {
+ // |process_group_id| is really a job on Fuchsia.
+ zx_status_t status = zx_task_kill(process_group_id);
+ DLOG_IF(ERROR, status != ZX_OK)
+ << "unable to terminate job " << process_group_id;
+ return status == ZX_OK;
+}
+
+TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) {
+ DCHECK(exit_code);
+
+ zx_info_process_t process_info;
+ zx_status_t status =
+ zx_object_get_info(handle, ZX_INFO_PROCESS, &process_info,
+ sizeof(process_info), nullptr, nullptr);
+ if (status != ZX_OK) {
+ DLOG(ERROR) << "unable to get termination status for " << handle;
+ *exit_code = 0;
+ return TERMINATION_STATUS_NORMAL_TERMINATION;
+ }
+ if (!process_info.started) {
+ *exit_code = 0;
+ return TERMINATION_STATUS_LAUNCH_FAILED;
+ }
+ if (!process_info.exited) {
+ *exit_code = 0;
+ return TERMINATION_STATUS_STILL_RUNNING;
+ }
+
+ // TODO(fuchsia): Is there more information about types of crashes, OOM, etc.
+ // available? https://crbug.com/706592.
+
+ *exit_code = process_info.return_code;
+ return process_info.return_code == 0
+ ? TERMINATION_STATUS_NORMAL_TERMINATION
+ : TERMINATION_STATUS_ABNORMAL_TERMINATION;
+}
+
+} // namespace base
diff --git a/src/base/process/kill_mac.cc b/src/base/process/kill_mac.cc
new file mode 100644
index 0000000..e2604da
--- /dev/null
+++ b/src/base/process/kill_mac.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/kill.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/event.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+const int kWaitBeforeKillSeconds = 2;
+
+// Reap |child| process. This call blocks until completion.
+void BlockingReap(pid_t child) {
+ const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0));
+ if (result == -1) {
+ DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)";
+ }
+}
+
+// Waits for |timeout| seconds for the given |child| to exit and reap it. If
+// the child doesn't exit within the time specified, kills it.
+//
+// This function takes two approaches: first, it tries to use kqueue to
+// observe when the process exits. kevent can monitor a kqueue with a
+// timeout, so this method is preferred to wait for a specified period of
+// time. Once the kqueue indicates the process has exited, waitpid will reap
+// the exited child. If the kqueue doesn't provide an exit event notification,
+// before the timeout expires, or if the kqueue fails or misbehaves, the
+// process will be mercilessly killed and reaped.
+//
+// A child process passed to this function may be in one of several states:
+// running, terminated and not yet reaped, and (apparently, and unfortunately)
+// terminated and already reaped. Normally, a process will at least have been
+// asked to exit before this function is called, but this is not required.
+// If a process is terminating and unreaped, there may be a window between the
+// time that kqueue will no longer recognize it and when it becomes an actual
+// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is
+// detected when kqueue indicates that the process is not running and a
+// non-blocking waitpid fails to reap the process but indicates that it is
+// still running. In this event, a blocking attempt to reap the process
+// collects the known-dying child, preventing zombies from congregating.
+//
+// In the event that the kqueue misbehaves entirely, as it might under a
+// EMFILE condition ("too many open files", or out of file descriptors), this
+// function will forcibly kill and reap the child without delay. This
+// eliminates another potential zombie vector. (If you're out of file
+// descriptors, you're probably deep into something else, but that doesn't
+// mean that zombies be allowed to kick you while you're down.)
+//
+// The fact that this function seemingly can be called to wait on a child
+// that's not only already terminated but already reaped is a bit of a
+// problem: a reaped child's pid can be reclaimed and may refer to a distinct
+// process in that case. The fact that this function can seemingly be called
+// to wait on a process that's not even a child is also a problem: kqueue will
+// work in that case, but waitpid won't, and killing a non-child might not be
+// the best approach.
+void WaitForChildToDie(pid_t child, int timeout) {
+ DCHECK_GT(child, 0);
+ DCHECK_GT(timeout, 0);
+
+ // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that
+ // |child| has been reaped. Specifically, even if a kqueue, kevent, or other
+ // call fails, this function should fall back to the last resort of trying
+ // to kill and reap the process. Not observing this rule will resurrect
+ // zombies.
+
+ int result;
+
+ ScopedFD kq(HANDLE_EINTR(kqueue()));
+ if (!kq.is_valid()) {
+ DPLOG(ERROR) << "kqueue()";
+ } else {
+ struct kevent change = {0};
+ EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
+ result = HANDLE_EINTR(kevent(kq.get(), &change, 1, NULL, 0, NULL));
+
+ if (result == -1) {
+ if (errno != ESRCH) {
+ DPLOG(ERROR) << "kevent (setup " << child << ")";
+ } else {
+ // At this point, one of the following has occurred:
+ // 1. The process has died but has not yet been reaped.
+ // 2. The process has died and has already been reaped.
+ // 3. The process is in the process of dying. It's no longer
+ // kqueueable, but it may not be waitable yet either. Mark calls
+ // this case the "zombie death race".
+
+ result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG));
+
+ if (result != 0) {
+ // A positive result indicates case 1. waitpid succeeded and reaped
+ // the child. A result of -1 indicates case 2. The child has already
+ // been reaped. In both of these cases, no further action is
+ // necessary.
+ return;
+ }
+
+ // |result| is 0, indicating case 3. The process will be waitable in
+ // short order. Fall back out of the kqueue code to kill it (for good
+ // measure) and reap it.
+ }
+ } else {
+ // Keep track of the elapsed time to be able to restart kevent if it's
+ // interrupted.
+ TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout);
+ TimeTicks deadline = TimeTicks::Now() + remaining_delta;
+ result = -1;
+ struct kevent event = {0};
+ while (remaining_delta.InMilliseconds() > 0) {
+ const struct timespec remaining_timespec = remaining_delta.ToTimeSpec();
+ result = kevent(kq.get(), NULL, 0, &event, 1, &remaining_timespec);
+ if (result == -1 && errno == EINTR) {
+ remaining_delta = deadline - TimeTicks::Now();
+ result = 0;
+ } else {
+ break;
+ }
+ }
+
+ if (result == -1) {
+ DPLOG(ERROR) << "kevent (wait " << child << ")";
+ } else if (result > 1) {
+ DLOG(ERROR) << "kevent (wait " << child << "): unexpected result "
+ << result;
+ } else if (result == 1) {
+ if ((event.fflags & NOTE_EXIT) &&
+ (event.ident == static_cast<uintptr_t>(child))) {
+ // The process is dead or dying. This won't block for long, if at
+ // all.
+ BlockingReap(child);
+ return;
+ } else {
+ DLOG(ERROR) << "kevent (wait " << child
+ << "): unexpected event: fflags=" << event.fflags
+ << ", ident=" << event.ident;
+ }
+ }
+ }
+ }
+
+ // The child is still alive, or is very freshly dead. Be sure by sending it
+ // a signal. This is safe even if it's freshly dead, because it will be a
+ // zombie (or on the way to zombiedom) and kill will return 0 even if the
+ // signal is not delivered to a live process.
+ result = kill(child, SIGKILL);
+ if (result == -1) {
+ DPLOG(ERROR) << "kill(" << child << ", SIGKILL)";
+ } else {
+ // The child is definitely on the way out now. BlockingReap won't need to
+ // wait for long, if at all.
+ BlockingReap(child);
+ }
+}
+
+} // namespace
+
+void EnsureProcessTerminated(Process process) {
+ WaitForChildToDie(process.Pid(), kWaitBeforeKillSeconds);
+}
+
+} // namespace base
diff --git a/src/base/process/kill_posix.cc b/src/base/process/kill_posix.cc
new file mode 100644
index 0000000..a84dd7b
--- /dev/null
+++ b/src/base/process/kill_posix.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/kill.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/debug/activity_tracker.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/process_iterator.h"
+#include "base/task/post_task.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+TerminationStatus GetTerminationStatusImpl(ProcessHandle handle,
+ bool can_block,
+ int* exit_code) {
+ DCHECK(exit_code);
+
+ int status = 0;
+ const pid_t result = HANDLE_EINTR(waitpid(handle, &status,
+ can_block ? 0 : WNOHANG));
+ if (result == -1) {
+ DPLOG(ERROR) << "waitpid(" << handle << ")";
+ *exit_code = 0;
+ return TERMINATION_STATUS_NORMAL_TERMINATION;
+ }
+ if (result == 0) {
+ // the child hasn't exited yet.
+ *exit_code = 0;
+ return TERMINATION_STATUS_STILL_RUNNING;
+ }
+
+ *exit_code = status;
+
+ if (WIFSIGNALED(status)) {
+ switch (WTERMSIG(status)) {
+ case SIGABRT:
+ case SIGBUS:
+ case SIGFPE:
+ case SIGILL:
+ case SIGSEGV:
+ case SIGTRAP:
+ case SIGSYS:
+ return TERMINATION_STATUS_PROCESS_CRASHED;
+ case SIGKILL:
+#if defined(OS_CHROMEOS)
+ // On ChromeOS, only way a process gets kill by SIGKILL
+ // is by oom-killer.
+ return TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM;
+#endif
+ case SIGINT:
+ case SIGTERM:
+ return TERMINATION_STATUS_PROCESS_WAS_KILLED;
+ default:
+ break;
+ }
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+ return TERMINATION_STATUS_ABNORMAL_TERMINATION;
+
+ return TERMINATION_STATUS_NORMAL_TERMINATION;
+}
+
+} // namespace
+
+#if !defined(OS_NACL_NONSFI)
+bool KillProcessGroup(ProcessHandle process_group_id) {
+ bool result = kill(-1 * process_group_id, SIGKILL) == 0;
+ if (!result)
+ DPLOG(ERROR) << "Unable to terminate process group " << process_group_id;
+ return result;
+}
+#endif // !defined(OS_NACL_NONSFI)
+
+TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) {
+ return GetTerminationStatusImpl(handle, false /* can_block */, exit_code);
+}
+
+TerminationStatus GetKnownDeadTerminationStatus(ProcessHandle handle,
+ int* exit_code) {
+ bool result = kill(handle, SIGKILL) == 0;
+
+ if (!result)
+ DPLOG(ERROR) << "Unable to terminate process " << handle;
+
+ return GetTerminationStatusImpl(handle, true /* can_block */, exit_code);
+}
+
+#if !defined(OS_NACL_NONSFI)
+bool WaitForProcessesToExit(const FilePath::StringType& executable_name,
+ TimeDelta wait,
+ const ProcessFilter* filter) {
+ bool result = false;
+
+ // TODO(port): This is inefficient, but works if there are multiple procs.
+ // TODO(port): use waitpid to avoid leaving zombies around
+
+ TimeTicks end_time = TimeTicks::Now() + wait;
+ do {
+ NamedProcessIterator iter(executable_name, filter);
+ if (!iter.NextProcessEntry()) {
+ result = true;
+ break;
+ }
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
+ } while ((end_time - TimeTicks::Now()) > TimeDelta());
+
+ return result;
+}
+
+bool CleanupProcesses(const FilePath::StringType& executable_name,
+ TimeDelta wait,
+ int exit_code,
+ const ProcessFilter* filter) {
+ bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter);
+ if (!exited_cleanly)
+ KillProcesses(executable_name, exit_code, filter);
+ return exited_cleanly;
+}
+
+#if !defined(OS_MACOSX)
+
+namespace {
+
+class BackgroundReaper : public PlatformThread::Delegate {
+ public:
+ BackgroundReaper(base::Process child_process, const TimeDelta& wait_time)
+ : child_process_(std::move(child_process)), wait_time_(wait_time) {}
+
+ void ThreadMain() override {
+ if (!wait_time_.is_zero()) {
+ child_process_.WaitForExitWithTimeout(wait_time_, nullptr);
+ kill(child_process_.Handle(), SIGKILL);
+ }
+ child_process_.WaitForExit(nullptr);
+ delete this;
+ }
+
+ private:
+ Process child_process_;
+ const TimeDelta wait_time_;
+ DISALLOW_COPY_AND_ASSIGN(BackgroundReaper);
+};
+
+} // namespace
+
+void EnsureProcessTerminated(Process process) {
+ DCHECK(!process.is_current());
+
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+
+ PlatformThread::CreateNonJoinable(
+ 0, new BackgroundReaper(std::move(process), TimeDelta::FromSeconds(2)));
+}
+
+#if defined(OS_LINUX)
+void EnsureProcessGetsReaped(Process process) {
+ DCHECK(!process.is_current());
+
+ // If the child is already dead, then there's nothing to do.
+ if (process.WaitForExitWithTimeout(TimeDelta(), nullptr))
+ return;
+
+ PlatformThread::CreateNonJoinable(
+ 0, new BackgroundReaper(std::move(process), TimeDelta()));
+}
+#endif // defined(OS_LINUX)
+
+#endif // !defined(OS_MACOSX)
+#endif // !defined(OS_NACL_NONSFI)
+
+} // namespace base
diff --git a/src/base/process/kill_win.cc b/src/base/process/kill_win.cc
new file mode 100644
index 0000000..b430400
--- /dev/null
+++ b/src/base/process/kill_win.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/kill.h"
+
+#include <algorithm>
+
+#include <windows.h>
+#include <io.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/process/memory.h"
+#include "base/process/process_iterator.h"
+#include "starboard/types.h"
+
+namespace base {
+
+TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) {
+ DCHECK(exit_code);
+
+ DWORD tmp_exit_code = 0;
+
+ if (!::GetExitCodeProcess(handle, &tmp_exit_code)) {
+ DPLOG(FATAL) << "GetExitCodeProcess() failed";
+
+ // This really is a random number. We haven't received any
+ // information about the exit code, presumably because this
+ // process doesn't have permission to get the exit code, or
+ // because of some other cause for GetExitCodeProcess to fail
+ // (MSDN docs don't give the possible failure error codes for
+ // this function, so it could be anything). But we don't want
+ // to leave exit_code uninitialized, since that could cause
+ // random interpretations of the exit code. So we assume it
+ // terminated "normally" in this case.
+ *exit_code = win::kNormalTerminationExitCode;
+
+ // Assume the child has exited normally if we can't get the exit
+ // code.
+ return TERMINATION_STATUS_NORMAL_TERMINATION;
+ }
+ if (tmp_exit_code == STILL_ACTIVE) {
+ DWORD wait_result = WaitForSingleObject(handle, 0);
+ if (wait_result == WAIT_TIMEOUT) {
+ *exit_code = wait_result;
+ return TERMINATION_STATUS_STILL_RUNNING;
+ }
+
+ if (wait_result == WAIT_FAILED) {
+ DPLOG(ERROR) << "WaitForSingleObject() failed";
+ } else {
+ DCHECK_EQ(WAIT_OBJECT_0, wait_result);
+
+ // Strange, the process used 0x103 (STILL_ACTIVE) as exit code.
+ NOTREACHED();
+ }
+
+ return TERMINATION_STATUS_ABNORMAL_TERMINATION;
+ }
+
+ *exit_code = tmp_exit_code;
+
+ switch (tmp_exit_code) {
+ case win::kNormalTerminationExitCode:
+ return TERMINATION_STATUS_NORMAL_TERMINATION;
+ case win::kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE.
+ case win::kKeyboardInterruptExitCode: // Control-C/end session.
+ case win::kDebuggerTerminatedExitCode: // Debugger terminated process.
+ case win::kProcessKilledExitCode: // Task manager kill.
+ return TERMINATION_STATUS_PROCESS_WAS_KILLED;
+ case win::kSandboxFatalMemoryExceeded: // Terminated process due to
+ // exceeding the sandbox job
+ // object memory limits.
+ case win::kOomExceptionCode: // Ran out of memory.
+ return TERMINATION_STATUS_OOM;
+ default:
+ // All other exit codes indicate crashes.
+ return TERMINATION_STATUS_PROCESS_CRASHED;
+ }
+}
+
+bool WaitForProcessesToExit(const FilePath::StringType& executable_name,
+ TimeDelta wait,
+ const ProcessFilter* filter) {
+ bool result = true;
+ DWORD start_time = GetTickCount();
+
+ NamedProcessIterator iter(executable_name, filter);
+ for (const ProcessEntry* entry = iter.NextProcessEntry(); entry;
+ entry = iter.NextProcessEntry()) {
+ DWORD remaining_wait = static_cast<DWORD>(
+ std::max(static_cast<int64_t>(0),
+ wait.InMilliseconds() - (GetTickCount() - start_time)));
+ HANDLE process = OpenProcess(SYNCHRONIZE,
+ FALSE,
+ entry->th32ProcessID);
+ DWORD wait_result = WaitForSingleObject(process, remaining_wait);
+ CloseHandle(process);
+ result &= (wait_result == WAIT_OBJECT_0);
+ }
+
+ return result;
+}
+
+bool CleanupProcesses(const FilePath::StringType& executable_name,
+ TimeDelta wait,
+ int exit_code,
+ const ProcessFilter* filter) {
+ if (WaitForProcessesToExit(executable_name, wait, filter))
+ return true;
+ KillProcesses(executable_name, exit_code, filter);
+ return false;
+}
+
+} // namespace base
diff --git a/src/base/process/launch.cc b/src/base/process/launch.cc
new file mode 100644
index 0000000..c03e1a7
--- /dev/null
+++ b/src/base/process/launch.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+#include "build/build_config.h"
+
+namespace base {
+
+LaunchOptions::LaunchOptions() = default;
+
+LaunchOptions::LaunchOptions(const LaunchOptions& other) = default;
+
+LaunchOptions::~LaunchOptions() = default;
+
+LaunchOptions LaunchOptionsForTest() {
+ LaunchOptions options;
+#if defined(OS_LINUX)
+ // To prevent accidental privilege sharing to an untrusted child, processes
+ // are started with PR_SET_NO_NEW_PRIVS. Do not set that here, since this
+ // new child will be used for testing only.
+ options.allow_new_privs = true;
+#endif
+ return options;
+}
+
+} // namespace base
diff --git a/src/base/process/launch.h b/src/base/process/launch.h
new file mode 100644
index 0000000..83696f5
--- /dev/null
+++ b/src/base/process/launch.h
@@ -0,0 +1,398 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains functions for launching subprocesses.
+
+#ifndef BASE_PROCESS_LAUNCH_H_
+#define BASE_PROCESS_LAUNCH_H_
+
+#if !defined(STARBOARD)
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/environment.h"
+#include "base/macros.h"
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
+#include "base/strings/string_piece.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(OS_FUCHSIA)
+#include <lib/fdio/spawn.h>
+#include <zircon/types.h>
+#endif
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include "base/posix/file_descriptor_shuffle.h"
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+class CommandLine;
+
+#if defined(OS_WIN)
+typedef std::vector<HANDLE> HandlesToInheritVector;
+#elif defined(OS_FUCHSIA)
+struct PathToTransfer {
+ base::FilePath path;
+ zx_handle_t handle;
+};
+struct HandleToTransfer {
+ uint32_t id;
+ zx_handle_t handle;
+};
+typedef std::vector<HandleToTransfer> HandlesToTransferVector;
+typedef std::vector<std::pair<int, int>> FileHandleMappingVector;
+#elif defined(OS_POSIX)
+typedef std::vector<std::pair<int, int>> FileHandleMappingVector;
+#endif // defined(OS_WIN)
+
+// Options for launching a subprocess that are passed to LaunchProcess().
+// The default constructor constructs the object with default options.
+struct BASE_EXPORT LaunchOptions {
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Delegate to be run in between fork and exec in the subprocess (see
+ // pre_exec_delegate below)
+ class BASE_EXPORT PreExecDelegate {
+ public:
+ PreExecDelegate() = default;
+ virtual ~PreExecDelegate() = default;
+
+ // Since this is to be run between fork and exec, and fork may have happened
+ // while multiple threads were running, this function needs to be async
+ // safe.
+ virtual void RunAsyncSafe() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PreExecDelegate);
+ };
+#endif // defined(OS_POSIX)
+
+ LaunchOptions();
+ LaunchOptions(const LaunchOptions&);
+ ~LaunchOptions();
+
+ // If true, wait for the process to complete.
+ bool wait = false;
+
+ // If not empty, change to this directory before executing the new process.
+ base::FilePath current_directory;
+
+#if defined(OS_WIN)
+ bool start_hidden = false;
+
+ // Windows can inherit handles when it launches child processes.
+ // See https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873
+ // for a good overview of Windows handle inheritance.
+ //
+ // Implementation note: it might be nice to implement in terms of
+ // base::Optional<>, but then the natural default state (vector not present)
+ // would be "all inheritable handles" while we want "no inheritance."
+ enum class Inherit {
+ // Only those handles in |handles_to_inherit| vector are inherited. If the
+ // vector is empty, no handles are inherited. The handles in the vector must
+ // all be inheritable.
+ kSpecific,
+
+ // All handles in the current process which are inheritable are inherited.
+ // In production code this flag should be used only when running
+ // short-lived, trusted binaries, because open handles from other libraries
+ // and subsystems will leak to the child process, causing errors such as
+ // open socket hangs. There are also race conditions that can cause handle
+ // over-sharing.
+ //
+ // |handles_to_inherit| must be null.
+ //
+ // DEPRECATED. THIS SHOULD NOT BE USED. Explicitly map all handles that
+ // need to be shared in new code.
+ // TODO(brettw) bug 748258: remove this.
+ kAll
+ };
+ Inherit inherit_mode = Inherit::kSpecific;
+ HandlesToInheritVector handles_to_inherit;
+
+ // If non-null, runs as if the user represented by the token had launched it.
+ // Whether the application is visible on the interactive desktop depends on
+ // the token belonging to an interactive logon session.
+ //
+ // To avoid hard to diagnose problems, when specified this loads the
+ // environment variables associated with the user and if this operation fails
+ // the entire call fails as well.
+ UserTokenHandle as_user = nullptr;
+
+ // If true, use an empty string for the desktop name.
+ bool empty_desktop_name = false;
+
+ // If non-null, launches the application in that job object. The process will
+ // be terminated immediately and LaunchProcess() will fail if assignment to
+ // the job object fails.
+ HANDLE job_handle = nullptr;
+
+ // Handles for the redirection of stdin, stdout and stderr. The caller should
+ // either set all three of them or none (i.e. there is no way to redirect
+ // stderr without redirecting stdin).
+ //
+ // The handles must be inheritable. Pseudo handles are used when stdout and
+ // stderr redirect to the console. In that case, GetFileType() will return
+ // FILE_TYPE_CHAR and they're automatically inherited by child processes. See
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx
+ // Otherwise, the caller must ensure that the |inherit_mode| and/or
+ // |handles_to_inherit| set so that the handles are inherited.
+ HANDLE stdin_handle = nullptr;
+ HANDLE stdout_handle = nullptr;
+ HANDLE stderr_handle = nullptr;
+
+ // If set to true, ensures that the child process is launched with the
+ // CREATE_BREAKAWAY_FROM_JOB flag which allows it to breakout of the parent
+ // job if any.
+ bool force_breakaway_from_job_ = false;
+
+ // If set to true, permission to bring windows to the foreground is passed to
+ // the launched process if the current process has such permission.
+ bool grant_foreground_privilege = false;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Set/unset environment variables. These are applied on top of the parent
+ // process environment. Empty (the default) means to inherit the same
+ // environment. See AlterEnvironment().
+ EnvironmentMap environ;
+
+ // Clear the environment for the new process before processing changes from
+ // |environ|.
+ bool clear_environ = false;
+
+ // Remap file descriptors according to the mapping of src_fd->dest_fd to
+ // propagate FDs into the child process.
+ FileHandleMappingVector fds_to_remap;
+#endif // defined(OS_WIN)
+
+#if defined(OS_LINUX)
+ // If non-zero, start the process using clone(), using flags as provided.
+ // Unlike in clone, clone_flags may not contain a custom termination signal
+ // that is sent to the parent when the child dies. The termination signal will
+ // always be set to SIGCHLD.
+ int clone_flags = 0;
+
+ // By default, child processes will have the PR_SET_NO_NEW_PRIVS bit set. If
+ // true, then this bit will not be set in the new child process.
+ bool allow_new_privs = false;
+
+ // Sets parent process death signal to SIGKILL.
+ bool kill_on_parent_death = false;
+#endif // defined(OS_LINUX)
+
+#if defined(OS_FUCHSIA)
+ // If valid, launches the application in that job object.
+ zx_handle_t job_handle = ZX_HANDLE_INVALID;
+
+ // Specifies additional handles to transfer (not duplicate) to the child
+ // process. Each entry is an <id,handle> pair, with an |id| created using the
+ // PA_HND() macro. The child retrieves the handle
+ // |zx_take_startup_handle(id)|. The supplied handles are consumed by
+ // LaunchProcess() even on failure.
+ HandlesToTransferVector handles_to_transfer;
+
+ // Specifies which basic capabilities to grant to the child process.
+ // By default the child process will receive the caller's complete namespace,
+ // access to the current base::fuchsia::DefaultJob(), handles for stdio and
+ // access to the dynamic library loader.
+ // Note that the child is always provided access to the loader service.
+ uint32_t spawn_flags = FDIO_SPAWN_CLONE_NAMESPACE | FDIO_SPAWN_CLONE_STDIO |
+ FDIO_SPAWN_CLONE_JOB;
+
+ // Specifies paths to clone from the calling process' namespace into that of
+ // the child process. If |paths_to_clone| is empty then the process will
+ // receive either a full copy of the parent's namespace, or an empty one,
+ // depending on whether FDIO_SPAWN_CLONE_NAMESPACE is set.
+ std::vector<FilePath> paths_to_clone;
+
+ // Specifies handles which will be installed as files or directories in the
+ // child process' namespace. Paths installed by |paths_to_clone| will be
+ // overridden by these entries.
+ std::vector<PathToTransfer> paths_to_transfer;
+#endif // defined(OS_FUCHSIA)
+
+#if defined(OS_POSIX)
+ // If not empty, launch the specified executable instead of
+ // cmdline.GetProgram(). This is useful when it is necessary to pass a custom
+ // argv[0].
+ base::FilePath real_path;
+
+ // If non-null, a delegate to be run immediately prior to executing the new
+ // program in the child process.
+ //
+ // WARNING: If LaunchProcess is called in the presence of multiple threads,
+ // code running in this delegate essentially needs to be async-signal safe
+ // (see man 7 signal for a list of allowed functions).
+ PreExecDelegate* pre_exec_delegate = nullptr;
+
+ // Each element is an RLIMIT_* constant that should be raised to its
+ // rlim_max. This pointer is owned by the caller and must live through
+ // the call to LaunchProcess().
+ const std::vector<int>* maximize_rlimits = nullptr;
+
+ // If true, start the process in a new process group, instead of
+ // inheriting the parent's process group. The pgid of the child process
+ // will be the same as its pid.
+ bool new_process_group = false;
+#endif // defined(OS_POSIX)
+
+#if defined(OS_CHROMEOS)
+ // If non-negative, the specified file descriptor will be set as the launched
+ // process' controlling terminal.
+ int ctrl_terminal_fd = -1;
+#endif // defined(OS_CHROMEOS)
+};
+
+// Launch a process via the command line |cmdline|.
+// See the documentation of LaunchOptions for details on |options|.
+//
+// Returns a valid Process upon success.
+//
+// Unix-specific notes:
+// - All file descriptors open in the parent process will be closed in the
+// child process except for any preserved by options::fds_to_remap, and
+// stdin, stdout, and stderr. If not remapped by options::fds_to_remap,
+// stdin is reopened as /dev/null, and the child is allowed to inherit its
+// parent's stdout and stderr.
+// - If the first argument on the command line does not contain a slash,
+// PATH will be searched. (See man execvp.)
+BASE_EXPORT Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options);
+
+#if defined(OS_WIN)
+// Windows-specific LaunchProcess that takes the command line as a
+// string. Useful for situations where you need to control the
+// command line arguments directly, but prefer the CommandLine version
+// if launching Chrome itself.
+//
+// The first command line argument should be the path to the process,
+// and don't forget to quote it.
+//
+// Example (including literal quotes)
+// cmdline = "c:\windows\explorer.exe" -foo "c:\bar\"
+BASE_EXPORT Process LaunchProcess(const string16& cmdline,
+ const LaunchOptions& options);
+
+// Launches a process with elevated privileges. This does not behave exactly
+// like LaunchProcess as it uses ShellExecuteEx instead of CreateProcess to
+// create the process. This means the process will have elevated privileges
+// and thus some common operations like OpenProcess will fail. Currently the
+// only supported LaunchOptions are |start_hidden| and |wait|.
+BASE_EXPORT Process LaunchElevatedProcess(const CommandLine& cmdline,
+ const LaunchOptions& options);
+
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+// A POSIX-specific version of LaunchProcess that takes an argv array
+// instead of a CommandLine. Useful for situations where you need to
+// control the command line arguments directly, but prefer the
+// CommandLine version if launching Chrome itself.
+BASE_EXPORT Process LaunchProcess(const std::vector<std::string>& argv,
+ const LaunchOptions& options);
+
+// Close all file descriptors, except those which are a destination in the
+// given multimap. Only call this function in a child process where you know
+// that there aren't any other threads.
+BASE_EXPORT void CloseSuperfluousFds(const InjectiveMultimap& saved_map);
+#endif // defined(OS_WIN)
+
+#if defined(OS_WIN)
+// Set |job_object|'s JOBOBJECT_EXTENDED_LIMIT_INFORMATION
+// BasicLimitInformation.LimitFlags to |limit_flags|.
+BASE_EXPORT bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags);
+
+// Output multi-process printf, cout, cerr, etc to the cmd.exe console that ran
+// chrome. This is not thread-safe: only call from main thread.
+BASE_EXPORT void RouteStdioToConsole(bool create_console_if_not_found);
+#endif // defined(OS_WIN)
+
+// Executes the application specified by |cl| and wait for it to exit. Stores
+// the output (stdout) in |output|. Redirects stderr to /dev/null. Returns true
+// on success (application launched and exited cleanly, with exit code
+// indicating success).
+BASE_EXPORT bool GetAppOutput(const CommandLine& cl, std::string* output);
+
+// Like GetAppOutput, but also includes stderr.
+BASE_EXPORT bool GetAppOutputAndError(const CommandLine& cl,
+ std::string* output);
+
+// A version of |GetAppOutput()| which also returns the exit code of the
+// executed command. Returns true if the application runs and exits cleanly. If
+// this is the case the exit code of the application is available in
+// |*exit_code|.
+BASE_EXPORT bool GetAppOutputWithExitCode(const CommandLine& cl,
+ std::string* output, int* exit_code);
+
+#if defined(OS_WIN)
+// A Windows-specific version of GetAppOutput that takes a command line string
+// instead of a CommandLine object. Useful for situations where you need to
+// control the command line arguments directly.
+BASE_EXPORT bool GetAppOutput(const StringPiece16& cl, std::string* output);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+// A POSIX-specific version of GetAppOutput that takes an argv array
+// instead of a CommandLine. Useful for situations where you need to
+// control the command line arguments directly.
+BASE_EXPORT bool GetAppOutput(const std::vector<std::string>& argv,
+ std::string* output);
+
+// Like the above POSIX-specific version of GetAppOutput, but also includes
+// stderr.
+BASE_EXPORT bool GetAppOutputAndError(const std::vector<std::string>& argv,
+ std::string* output);
+#endif // defined(OS_WIN)
+
+// If supported on the platform, and the user has sufficent rights, increase
+// the current process's scheduling priority to a high priority.
+BASE_EXPORT void RaiseProcessToHighPriority();
+
+#if defined(OS_MACOSX)
+// An implementation of LaunchProcess() that uses posix_spawn() instead of
+// fork()+exec(). This does not support the |pre_exec_delegate| and
+// |current_directory| options.
+Process LaunchProcessPosixSpawn(const std::vector<std::string>& argv,
+ const LaunchOptions& options);
+
+// Restore the default exception handler, setting it to Apple Crash Reporter
+// (ReportCrash). When forking and execing a new process, the child will
+// inherit the parent's exception ports, which may be set to the Breakpad
+// instance running inside the parent. The parent's Breakpad instance should
+// not handle the child's exceptions. Calling RestoreDefaultExceptionHandler
+// in the child after forking will restore the standard exception handler.
+// See http://crbug.com/20371/ for more details.
+void RestoreDefaultExceptionHandler();
+#endif // defined(OS_MACOSX)
+
+// Creates a LaunchOptions object suitable for launching processes in a test
+// binary. This should not be called in production/released code.
+BASE_EXPORT LaunchOptions LaunchOptionsForTest();
+
+#if defined(OS_LINUX) || defined(OS_NACL_NONSFI)
+// A wrapper for clone with fork-like behavior, meaning that it returns the
+// child's pid in the parent and 0 in the child. |flags|, |ptid|, and |ctid| are
+// as in the clone system call (the CLONE_VM flag is not supported).
+//
+// This function uses the libc clone wrapper (which updates libc's pid cache)
+// internally, so callers may expect things like getpid() to work correctly
+// after in both the child and parent.
+//
+// As with fork(), callers should be extremely careful when calling this while
+// multiple threads are running, since at the time the fork happened, the
+// threads could have been in any state (potentially holding locks, etc.).
+// Callers should most likely call execve() in the child soon after calling
+// this.
+//
+// It is unsafe to use any pthread APIs after ForkWithFlags().
+// However, performing an exec() will lift this restriction.
+BASE_EXPORT pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid);
+#endif
+
+} // namespace base
+
+#endif // !defined(STARBOARD)
+#endif // BASE_PROCESS_LAUNCH_H_
diff --git a/src/base/process/launch_fuchsia.cc b/src/base/process/launch_fuchsia.cc
new file mode 100644
index 0000000..294cb61
--- /dev/null
+++ b/src/base/process/launch_fuchsia.cc
@@ -0,0 +1,261 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+#include <lib/fdio/limits.h>
+#include <lib/fdio/namespace.h>
+#include <lib/fdio/spawn.h>
+#include <lib/fdio/util.h>
+#include <lib/zx/job.h>
+#include <unistd.h>
+#include <zircon/processargs.h>
+
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/fuchsia/default_job.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/scoped_generic.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+bool GetAppOutputInternal(const CommandLine& cmd_line,
+ bool include_stderr,
+ std::string* output,
+ int* exit_code) {
+ DCHECK(exit_code);
+
+ LaunchOptions options;
+
+ // LaunchProcess will automatically clone any stdio fd we do not explicitly
+ // map.
+ int pipe_fd[2];
+ if (pipe(pipe_fd) < 0)
+ return false;
+ options.fds_to_remap.emplace_back(pipe_fd[1], STDOUT_FILENO);
+ if (include_stderr)
+ options.fds_to_remap.emplace_back(pipe_fd[1], STDERR_FILENO);
+
+ Process process = LaunchProcess(cmd_line, options);
+ close(pipe_fd[1]);
+ if (!process.IsValid()) {
+ close(pipe_fd[0]);
+ return false;
+ }
+
+ output->clear();
+ for (;;) {
+ char buffer[256];
+ ssize_t bytes_read = read(pipe_fd[0], buffer, sizeof(buffer));
+ if (bytes_read <= 0)
+ break;
+ output->append(buffer, bytes_read);
+ }
+ close(pipe_fd[0]);
+
+ return process.WaitForExit(exit_code);
+}
+
+fdio_spawn_action_t FdioSpawnAction(uint32_t action) {
+ fdio_spawn_action_t new_action = {};
+ new_action.action = action;
+ return new_action;
+}
+
+fdio_spawn_action_t FdioSpawnActionCloneFd(int local_fd, int target_fd) {
+ fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_CLONE_FD);
+ action.fd.local_fd = local_fd;
+ action.fd.target_fd = target_fd;
+ return action;
+}
+
+fdio_spawn_action_t FdioSpawnActionAddNamespaceEntry(const char* prefix,
+ zx_handle_t handle) {
+ fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_ADD_NS_ENTRY);
+ action.ns.prefix = prefix;
+ action.ns.handle = handle;
+ return action;
+}
+
+fdio_spawn_action_t FdioSpawnActionAddHandle(uint32_t id, zx_handle_t handle) {
+ fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_ADD_HANDLE);
+ action.h.id = id;
+ action.h.handle = handle;
+ return action;
+}
+
+} // namespace
+
+Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ return LaunchProcess(cmdline.argv(), options);
+}
+
+// TODO(768416): Investigate whether we can make LaunchProcess() create
+// unprivileged processes by default (no implicit capabilities are granted).
+Process LaunchProcess(const std::vector<std::string>& argv,
+ const LaunchOptions& options) {
+ // fdio_spawn_etc() accepts an array of |fdio_spawn_action_t|, describing
+ // namespace entries, descriptors and handles to launch the child process
+ // with.
+ std::vector<fdio_spawn_action_t> spawn_actions;
+
+ // Handles to be transferred to the child are owned by this vector, so that
+ // they they are closed on early-exit, and can be release()d otherwise.
+ std::vector<zx::handle> transferred_handles;
+
+ // Add caller-supplied handles for transfer. We must do this first to ensure
+ // that the handles are consumed even if some later step fails.
+ for (const auto& id_and_handle : options.handles_to_transfer) {
+ spawn_actions.push_back(
+ FdioSpawnActionAddHandle(id_and_handle.id, id_and_handle.handle));
+ transferred_handles.emplace_back(id_and_handle.handle);
+ }
+
+ // Determine the job under which to launch the new process.
+ zx::unowned_job job = options.job_handle != ZX_HANDLE_INVALID
+ ? zx::unowned_job(options.job_handle)
+ : GetDefaultJob();
+ DCHECK(job->is_valid());
+
+ // Construct an |argv| array of C-strings from the supplied std::strings.
+ std::vector<const char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ for (const auto& arg : argv)
+ argv_cstr.push_back(arg.c_str());
+ argv_cstr.push_back(nullptr);
+
+ // Determine the environment to pass to the new process.
+ // If |clear_environ|, |environ| or |current_directory| are set then we
+ // construct a new (possibly empty) environment, otherwise we let fdio_spawn()
+ // clone the caller's environment into the new process.
+ uint32_t spawn_flags = FDIO_SPAWN_CLONE_LDSVC | options.spawn_flags;
+
+ EnvironmentMap environ_modifications = options.environ;
+ if (!options.current_directory.empty()) {
+ environ_modifications["PWD"] = options.current_directory.value();
+ } else {
+ FilePath cwd;
+ GetCurrentDirectory(&cwd);
+ environ_modifications["PWD"] = cwd.value();
+ }
+
+ std::unique_ptr<char* []> new_environ;
+ if (!environ_modifications.empty()) {
+ char* const empty_environ = nullptr;
+ char* const* old_environ = options.clear_environ ? &empty_environ : environ;
+ new_environ = AlterEnvironment(old_environ, environ_modifications);
+ } else if (!options.clear_environ) {
+ spawn_flags |= FDIO_SPAWN_CLONE_ENVIRON;
+ }
+
+ // Add actions to clone handles for any specified paths into the new process'
+ // namespace.
+ if (!options.paths_to_clone.empty() || !options.paths_to_transfer.empty()) {
+ DCHECK((options.spawn_flags & FDIO_SPAWN_CLONE_NAMESPACE) == 0);
+ transferred_handles.reserve(transferred_handles.size() +
+ options.paths_to_clone.size() +
+ options.paths_to_transfer.size());
+
+ for (const auto& path_to_transfer : options.paths_to_transfer) {
+ zx::handle handle(path_to_transfer.handle);
+ spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry(
+ path_to_transfer.path.value().c_str(), handle.get()));
+ transferred_handles.push_back(std::move(handle));
+ }
+
+ for (const auto& path_to_clone : options.paths_to_clone) {
+ zx::handle handle = fuchsia::GetHandleFromFile(
+ base::File(base::FilePath(path_to_clone),
+ base::File::FLAG_OPEN | base::File::FLAG_READ));
+ if (!handle) {
+ LOG(WARNING) << "Could not open handle for path: " << path_to_clone;
+ return base::Process();
+ }
+
+ spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry(
+ path_to_clone.value().c_str(), handle.get()));
+ transferred_handles.push_back(std::move(handle));
+ }
+ }
+
+ // Add any file-descriptors to be cloned into the new process.
+ // Note that if FDIO_SPAWN_CLONE_STDIO is set, then any stdio entries in
+ // |fds_to_remap| will be used in place of the parent process' descriptors.
+ for (const auto& src_target : options.fds_to_remap) {
+ spawn_actions.push_back(
+ FdioSpawnActionCloneFd(src_target.first, src_target.second));
+ }
+
+ zx::process process_handle;
+ // fdio_spawn_etc() will write a null-terminated scring to |error_message| in
+ // case of failure, so we avoid unnecessarily initializing it here.
+ char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
+ zx_status_t status = fdio_spawn_etc(
+ job->get(), spawn_flags, argv_cstr[0], argv_cstr.data(),
+ new_environ.get(), spawn_actions.size(), spawn_actions.data(),
+ process_handle.reset_and_get_address(), error_message);
+
+ // fdio_spawn_etc() will close all handles specified in add-handle actions,
+ // regardless of whether it succeeds or fails, so release our copies.
+ for (auto& transferred_handle : transferred_handles)
+ ignore_result(transferred_handle.release());
+
+ if (status != ZX_OK) {
+ ZX_LOG(ERROR, status) << "fdio_spawn: " << error_message;
+ return Process();
+ }
+
+ // Wrap the handle into a Process, and wait for it to terminate, if requested.
+ Process process(process_handle.release());
+ if (options.wait) {
+ status = zx_object_wait_one(process.Handle(), ZX_TASK_TERMINATED,
+ ZX_TIME_INFINITE, nullptr);
+ ZX_DCHECK(status == ZX_OK, status) << "zx_object_wait_one";
+ }
+
+ return process;
+}
+
+bool GetAppOutput(const CommandLine& cl, std::string* output) {
+ int exit_code;
+ bool result = GetAppOutputInternal(cl, false, output, &exit_code);
+ return result && exit_code == EXIT_SUCCESS;
+}
+
+bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) {
+ return GetAppOutput(CommandLine(argv), output);
+}
+
+bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
+ int exit_code;
+ bool result = GetAppOutputInternal(cl, true, output, &exit_code);
+ return result && exit_code == EXIT_SUCCESS;
+}
+
+bool GetAppOutputAndError(const std::vector<std::string>& argv,
+ std::string* output) {
+ return GetAppOutputAndError(CommandLine(argv), output);
+}
+
+bool GetAppOutputWithExitCode(const CommandLine& cl,
+ std::string* output,
+ int* exit_code) {
+ // Contrary to GetAppOutput(), |true| return here means that the process was
+ // launched and the exit code was waited upon successfully, but not
+ // necessarily that the exit code was EXIT_SUCCESS.
+ return GetAppOutputInternal(cl, false, output, exit_code);
+}
+
+void RaiseProcessToHighPriority() {
+ // Fuchsia doesn't provide an API to change process priority.
+}
+
+} // namespace base
diff --git a/src/base/process/launch_ios.cc b/src/base/process/launch_ios.cc
new file mode 100644
index 0000000..3c700f8
--- /dev/null
+++ b/src/base/process/launch_ios.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+namespace base {
+
+void RaiseProcessToHighPriority() {
+ // Impossible on iOS. Do nothing.
+}
+
+} // namespace base
diff --git a/src/base/process/launch_mac.cc b/src/base/process/launch_mac.cc
new file mode 100644
index 0000000..dfaa7ac
--- /dev/null
+++ b/src/base/process/launch_mac.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+#include <crt_externs.h>
+#include <mach/mach.h>
+#include <spawn.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+// DPSXCHECK is a Debug Posix Spawn Check macro. The posix_spawn* family of
+// functions return an errno value, as opposed to setting errno directly. This
+// macro emulates a DPCHECK().
+#define DPSXCHECK(expr) \
+ do { \
+ int rv = (expr); \
+ DCHECK_EQ(rv, 0) << #expr << ": -" << rv << " " << strerror(rv); \
+ } while (0)
+
+class PosixSpawnAttr {
+ public:
+ PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_init(&attr_)); }
+
+ ~PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_destroy(&attr_)); }
+
+ posix_spawnattr_t* get() { return &attr_; }
+
+ private:
+ posix_spawnattr_t attr_;
+};
+
+class PosixSpawnFileActions {
+ public:
+ PosixSpawnFileActions() {
+ DPSXCHECK(posix_spawn_file_actions_init(&file_actions_));
+ }
+
+ ~PosixSpawnFileActions() {
+ DPSXCHECK(posix_spawn_file_actions_destroy(&file_actions_));
+ }
+
+ void Open(int filedes, const char* path, int mode) {
+ DPSXCHECK(posix_spawn_file_actions_addopen(&file_actions_, filedes, path,
+ mode, 0));
+ }
+
+ void Dup2(int filedes, int newfiledes) {
+ DPSXCHECK(
+ posix_spawn_file_actions_adddup2(&file_actions_, filedes, newfiledes));
+ }
+
+ void Inherit(int filedes) {
+ DPSXCHECK(posix_spawn_file_actions_addinherit_np(&file_actions_, filedes));
+ }
+
+ const posix_spawn_file_actions_t* get() const { return &file_actions_; }
+
+ private:
+ posix_spawn_file_actions_t file_actions_;
+
+ DISALLOW_COPY_AND_ASSIGN(PosixSpawnFileActions);
+};
+
+} // namespace
+
+void RestoreDefaultExceptionHandler() {
+ // This function is tailored to remove the Breakpad exception handler.
+ // exception_mask matches s_exception_mask in
+ // third_party/breakpad/breakpad/src/client/mac/handler/exception_handler.cc
+ const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS |
+ EXC_MASK_BAD_INSTRUCTION |
+ EXC_MASK_ARITHMETIC |
+ EXC_MASK_BREAKPOINT;
+
+ // Setting the exception port to MACH_PORT_NULL may not be entirely
+ // kosher to restore the default exception handler, but in practice,
+ // it results in the exception port being set to Apple Crash Reporter,
+ // the desired behavior.
+ task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL,
+ EXCEPTION_DEFAULT, THREAD_STATE_NONE);
+}
+
+Process LaunchProcessPosixSpawn(const std::vector<std::string>& argv,
+ const LaunchOptions& options) {
+ DCHECK(!options.pre_exec_delegate)
+ << "LaunchProcessPosixSpawn does not support PreExecDelegate";
+ DCHECK(options.current_directory.empty())
+ << "LaunchProcessPosixSpawn does not support current_directory";
+
+ PosixSpawnAttr attr;
+
+ short flags = POSIX_SPAWN_CLOEXEC_DEFAULT;
+ if (options.new_process_group) {
+ flags |= POSIX_SPAWN_SETPGROUP;
+ DPSXCHECK(posix_spawnattr_setpgroup(attr.get(), 0));
+ }
+ DPSXCHECK(posix_spawnattr_setflags(attr.get(), flags));
+
+ PosixSpawnFileActions file_actions;
+
+ // Process file descriptors for the child. By default, LaunchProcess will
+ // open stdin to /dev/null and inherit stdout and stderr.
+ bool inherit_stdout = true, inherit_stderr = true;
+ bool null_stdin = true;
+ for (const auto& dup2_pair : options.fds_to_remap) {
+ if (dup2_pair.second == STDIN_FILENO) {
+ null_stdin = false;
+ } else if (dup2_pair.second == STDOUT_FILENO) {
+ inherit_stdout = false;
+ } else if (dup2_pair.second == STDERR_FILENO) {
+ inherit_stderr = false;
+ }
+
+ if (dup2_pair.first == dup2_pair.second) {
+ file_actions.Inherit(dup2_pair.second);
+ } else {
+ file_actions.Dup2(dup2_pair.first, dup2_pair.second);
+ }
+ }
+
+ if (null_stdin) {
+ file_actions.Open(STDIN_FILENO, "/dev/null", O_RDONLY);
+ }
+ if (inherit_stdout) {
+ file_actions.Inherit(STDOUT_FILENO);
+ }
+ if (inherit_stderr) {
+ file_actions.Inherit(STDERR_FILENO);
+ }
+
+ std::vector<char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ for (const auto& arg : argv)
+ argv_cstr.push_back(const_cast<char*>(arg.c_str()));
+ argv_cstr.push_back(nullptr);
+
+ std::unique_ptr<char* []> owned_environ;
+ char** new_environ = options.clear_environ ? nullptr : *_NSGetEnviron();
+ if (!options.environ.empty()) {
+ owned_environ = AlterEnvironment(new_environ, options.environ);
+ new_environ = owned_environ.get();
+ }
+
+ const char* executable_path = !options.real_path.empty()
+ ? options.real_path.value().c_str()
+ : argv_cstr[0];
+
+ // Use posix_spawnp as some callers expect to have PATH consulted.
+ pid_t pid;
+ int rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(),
+ &argv_cstr[0], new_environ);
+
+ if (rv != 0) {
+ DLOG(ERROR) << "posix_spawnp(" << executable_path << "): -" << rv << " "
+ << strerror(rv);
+ return Process();
+ }
+
+ if (options.wait) {
+ // While this isn't strictly disk IO, waiting for another process to
+ // finish is the sort of thing ThreadRestrictions is trying to prevent.
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+ pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
+ DPCHECK(ret > 0);
+ }
+
+ return Process(pid);
+}
+
+} // namespace base
diff --git a/src/base/process/launch_posix.cc b/src/base/process/launch_posix.cc
new file mode 100644
index 0000000..b4aeebc
--- /dev/null
+++ b/src/base/process/launch_posix.cc
@@ -0,0 +1,766 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <set>
+
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/debug/debugger.h"
+#include "base/debug/stack_trace.h"
+#include "base/files/dir_reader_posix.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/process.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+#include <sys/prctl.h>
+#endif
+
+#if defined(OS_CHROMEOS)
+#include <sys/ioctl.h>
+#endif
+
+#if defined(OS_FREEBSD)
+#include <sys/event.h>
+#include <sys/ucontext.h>
+#endif
+
+#if defined(OS_MACOSX)
+#include <crt_externs.h>
+#include <sys/event.h>
+
+#include "base/feature_list.h"
+#include "starboard/memory.h"
+#include "starboard/types.h"
+#else
+extern char** environ;
+#endif
+
+namespace base {
+
+// Friend and derived class of ScopedAllowBaseSyncPrimitives which allows
+// GetAppOutputInternal() to join a process. GetAppOutputInternal() can't itself
+// be a friend of ScopedAllowBaseSyncPrimitives because it is in the anonymous
+// namespace.
+class GetAppOutputScopedAllowBaseSyncPrimitives
+ : public base::ScopedAllowBaseSyncPrimitives {};
+
+#if !defined(OS_NACL_NONSFI)
+
+namespace {
+
+#if defined(OS_MACOSX)
+const Feature kMacLaunchProcessPosixSpawn{"MacLaunchProcessPosixSpawn",
+ FEATURE_ENABLED_BY_DEFAULT};
+#endif
+
+// Get the process's "environment" (i.e. the thing that setenv/getenv
+// work with).
+char** GetEnvironment() {
+#if defined(OS_MACOSX)
+ return *_NSGetEnviron();
+#else
+ return environ;
+#endif
+}
+
+// Set the process's "environment" (i.e. the thing that setenv/getenv
+// work with).
+void SetEnvironment(char** env) {
+#if defined(OS_MACOSX)
+ *_NSGetEnviron() = env;
+#else
+ environ = env;
+#endif
+}
+
+// Set the calling thread's signal mask to new_sigmask and return
+// the previous signal mask.
+sigset_t SetSignalMask(const sigset_t& new_sigmask) {
+ sigset_t old_sigmask;
+#if defined(OS_ANDROID)
+ // POSIX says pthread_sigmask() must be used in multi-threaded processes,
+ // but Android's pthread_sigmask() was broken until 4.1:
+ // https://code.google.com/p/android/issues/detail?id=15337
+ // http://stackoverflow.com/questions/13777109/pthread-sigmask-on-android-not-working
+ RAW_CHECK(sigprocmask(SIG_SETMASK, &new_sigmask, &old_sigmask) == 0);
+#else
+ RAW_CHECK(pthread_sigmask(SIG_SETMASK, &new_sigmask, &old_sigmask) == 0);
+#endif
+ return old_sigmask;
+}
+
+#if (!defined(OS_LINUX) && !defined(OS_AIX)) || \
+ (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__))
+void ResetChildSignalHandlersToDefaults() {
+ // The previous signal handlers are likely to be meaningless in the child's
+ // context so we reset them to the defaults for now. http://crbug.com/44953
+ // These signal handlers are set up at least in browser_main_posix.cc:
+ // BrowserMainPartsPosix::PreEarlyInitialization and stack_trace_posix.cc:
+ // EnableInProcessStackDumping.
+ signal(SIGHUP, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGILL, SIG_DFL);
+ signal(SIGABRT, SIG_DFL);
+ signal(SIGFPE, SIG_DFL);
+ signal(SIGBUS, SIG_DFL);
+ signal(SIGSEGV, SIG_DFL);
+ signal(SIGSYS, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+}
+
+#else
+
+// TODO(jln): remove the Linux special case once kernels are fixed.
+
+// Internally the kernel makes sigset_t an array of long large enough to have
+// one bit per signal.
+typedef uint64_t kernel_sigset_t;
+
+// This is what struct sigaction looks like to the kernel at least on X86 and
+// ARM. MIPS, for instance, is very different.
+struct kernel_sigaction {
+ void* k_sa_handler; // For this usage it only needs to be a generic pointer.
+ unsigned long k_sa_flags;
+ void* k_sa_restorer; // For this usage it only needs to be a generic pointer.
+ kernel_sigset_t k_sa_mask;
+};
+
+// glibc's sigaction() will prevent access to sa_restorer, so we need to roll
+// our own.
+int sys_rt_sigaction(int sig, const struct kernel_sigaction* act,
+ struct kernel_sigaction* oact) {
+ return syscall(SYS_rt_sigaction, sig, act, oact, sizeof(kernel_sigset_t));
+}
+
+// This function is intended to be used in between fork() and execve() and will
+// reset all signal handlers to the default.
+// The motivation for going through all of them is that sa_restorer can leak
+// from parents and help defeat ASLR on buggy kernels. We reset it to null.
+// See crbug.com/177956.
+void ResetChildSignalHandlersToDefaults(void) {
+ for (int signum = 1; ; ++signum) {
+ struct kernel_sigaction act = {nullptr};
+ int sigaction_get_ret = sys_rt_sigaction(signum, nullptr, &act);
+ if (sigaction_get_ret && errno == EINVAL) {
+#if !defined(NDEBUG)
+ // Linux supports 32 real-time signals from 33 to 64.
+ // If the number of signals in the Linux kernel changes, someone should
+ // look at this code.
+ const int kNumberOfSignals = 64;
+ RAW_CHECK(signum == kNumberOfSignals + 1);
+#endif // !defined(NDEBUG)
+ break;
+ }
+ // All other failures are fatal.
+ if (sigaction_get_ret) {
+ RAW_LOG(FATAL, "sigaction (get) failed.");
+ }
+
+ // The kernel won't allow to re-set SIGKILL or SIGSTOP.
+ if (signum != SIGSTOP && signum != SIGKILL) {
+ act.k_sa_handler = reinterpret_cast<void*>(SIG_DFL);
+ act.k_sa_restorer = nullptr;
+ if (sys_rt_sigaction(signum, &act, nullptr)) {
+ RAW_LOG(FATAL, "sigaction (set) failed.");
+ }
+ }
+#if !defined(NDEBUG)
+ // Now ask the kernel again and check that no restorer will leak.
+ if (sys_rt_sigaction(signum, nullptr, &act) || act.k_sa_restorer) {
+ RAW_LOG(FATAL, "Cound not fix sa_restorer.");
+ }
+#endif // !defined(NDEBUG)
+ }
+}
+#endif // !defined(OS_LINUX) ||
+ // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__))
+} // anonymous namespace
+
+// Functor for |ScopedDIR| (below).
+struct ScopedDIRClose {
+ inline void operator()(DIR* x) const {
+ if (x)
+ closedir(x);
+ }
+};
+
+// Automatically closes |DIR*|s.
+typedef std::unique_ptr<DIR, ScopedDIRClose> ScopedDIR;
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+static const char kFDDir[] = "/proc/self/fd";
+#elif defined(OS_MACOSX)
+static const char kFDDir[] = "/dev/fd";
+#elif defined(OS_SOLARIS)
+static const char kFDDir[] = "/dev/fd";
+#elif defined(OS_FREEBSD)
+static const char kFDDir[] = "/dev/fd";
+#elif defined(OS_OPENBSD)
+static const char kFDDir[] = "/dev/fd";
+#elif defined(OS_ANDROID)
+static const char kFDDir[] = "/proc/self/fd";
+#endif
+
+void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) {
+ // DANGER: no calls to malloc or locks are allowed from now on:
+ // http://crbug.com/36678
+
+ // Get the maximum number of FDs possible.
+ size_t max_fds = GetMaxFds();
+
+ DirReaderPosix fd_dir(kFDDir);
+ if (!fd_dir.IsValid()) {
+ // Fallback case: Try every possible fd.
+ for (size_t i = 0; i < max_fds; ++i) {
+ const int fd = static_cast<int>(i);
+ if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
+ continue;
+ // Cannot use STL iterators here, since debug iterators use locks.
+ size_t j;
+ for (j = 0; j < saved_mapping.size(); j++) {
+ if (fd == saved_mapping[j].dest)
+ break;
+ }
+ if (j < saved_mapping.size())
+ continue;
+
+ // Since we're just trying to close anything we can find,
+ // ignore any error return values of close().
+ close(fd);
+ }
+ return;
+ }
+
+ const int dir_fd = fd_dir.fd();
+
+ for ( ; fd_dir.Next(); ) {
+ // Skip . and .. entries.
+ if (fd_dir.name()[0] == '.')
+ continue;
+
+ char *endptr;
+ errno = 0;
+ const long int fd = strtol(fd_dir.name(), &endptr, 10);
+ if (fd_dir.name()[0] == 0 || *endptr || fd < 0 || errno)
+ continue;
+ if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO)
+ continue;
+ // Cannot use STL iterators here, since debug iterators use locks.
+ size_t i;
+ for (i = 0; i < saved_mapping.size(); i++) {
+ if (fd == saved_mapping[i].dest)
+ break;
+ }
+ if (i < saved_mapping.size())
+ continue;
+ if (fd == dir_fd)
+ continue;
+
+ int ret = IGNORE_EINTR(close(fd));
+ DPCHECK(ret == 0);
+ }
+}
+
+Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ return LaunchProcess(cmdline.argv(), options);
+}
+
+Process LaunchProcess(const std::vector<std::string>& argv,
+ const LaunchOptions& options) {
+ TRACE_EVENT0("base", "LaunchProcess");
+#if defined(OS_MACOSX)
+ if (FeatureList::IsEnabled(kMacLaunchProcessPosixSpawn)) {
+ // TODO(rsesek): Do this unconditionally. There is one user for each of
+ // these two options. https://crbug.com/179923.
+ if (!options.pre_exec_delegate && options.current_directory.empty())
+ return LaunchProcessPosixSpawn(argv, options);
+ }
+#endif
+
+ InjectiveMultimap fd_shuffle1;
+ InjectiveMultimap fd_shuffle2;
+ fd_shuffle1.reserve(options.fds_to_remap.size());
+ fd_shuffle2.reserve(options.fds_to_remap.size());
+
+ std::vector<char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ for (const auto& arg : argv)
+ argv_cstr.push_back(const_cast<char*>(arg.c_str()));
+ argv_cstr.push_back(nullptr);
+
+ std::unique_ptr<char* []> new_environ;
+ char* const empty_environ = nullptr;
+ char* const* old_environ = GetEnvironment();
+ if (options.clear_environ)
+ old_environ = &empty_environ;
+ if (!options.environ.empty())
+ new_environ = AlterEnvironment(old_environ, options.environ);
+
+ sigset_t full_sigset;
+ sigfillset(&full_sigset);
+ const sigset_t orig_sigmask = SetSignalMask(full_sigset);
+
+ const char* current_directory = nullptr;
+ if (!options.current_directory.empty()) {
+ current_directory = options.current_directory.value().c_str();
+ }
+
+ pid_t pid;
+ base::TimeTicks before_fork = TimeTicks::Now();
+#if defined(OS_LINUX) || defined(OS_AIX)
+ if (options.clone_flags) {
+ // Signal handling in this function assumes the creation of a new
+ // process, so we check that a thread is not being created by mistake
+ // and that signal handling follows the process-creation rules.
+ RAW_CHECK(
+ !(options.clone_flags & (CLONE_SIGHAND | CLONE_THREAD | CLONE_VM)));
+
+ // We specify a null ptid and ctid.
+ RAW_CHECK(
+ !(options.clone_flags &
+ (CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | CLONE_PARENT_SETTID)));
+
+ // Since we use waitpid, we do not support custom termination signals in the
+ // clone flags.
+ RAW_CHECK((options.clone_flags & 0xff) == 0);
+
+ pid = ForkWithFlags(options.clone_flags | SIGCHLD, nullptr, nullptr);
+ } else
+#endif
+ {
+ pid = fork();
+ }
+
+ // Always restore the original signal mask in the parent.
+ if (pid != 0) {
+ base::TimeTicks after_fork = TimeTicks::Now();
+ SetSignalMask(orig_sigmask);
+
+ base::TimeDelta fork_time = after_fork - before_fork;
+ UMA_HISTOGRAM_TIMES("MPArch.ForkTime", fork_time);
+ }
+
+ if (pid < 0) {
+ DPLOG(ERROR) << "fork";
+ return Process();
+ }
+ if (pid == 0) {
+ // Child process
+
+ // DANGER: no calls to malloc or locks are allowed from now on:
+ // http://crbug.com/36678
+
+ // DANGER: fork() rule: in the child, if you don't end up doing exec*(),
+ // you call _exit() instead of exit(). This is because _exit() does not
+ // call any previously-registered (in the parent) exit handlers, which
+ // might do things like block waiting for threads that don't even exist
+ // in the child.
+
+ // If a child process uses the readline library, the process block forever.
+ // In BSD like OSes including OS X it is safe to assign /dev/null as stdin.
+ // See http://crbug.com/56596.
+ base::ScopedFD null_fd(HANDLE_EINTR(open("/dev/null", O_RDONLY)));
+ if (!null_fd.is_valid()) {
+ RAW_LOG(ERROR, "Failed to open /dev/null");
+ _exit(127);
+ }
+
+ int new_fd = HANDLE_EINTR(dup2(null_fd.get(), STDIN_FILENO));
+ if (new_fd != STDIN_FILENO) {
+ RAW_LOG(ERROR, "Failed to dup /dev/null for stdin");
+ _exit(127);
+ }
+
+ if (options.new_process_group) {
+ // Instead of inheriting the process group ID of the parent, the child
+ // starts off a new process group with pgid equal to its process ID.
+ if (setpgid(0, 0) < 0) {
+ RAW_LOG(ERROR, "setpgid failed");
+ _exit(127);
+ }
+ }
+
+ if (options.maximize_rlimits) {
+ // Some resource limits need to be maximal in this child.
+ for (size_t i = 0; i < options.maximize_rlimits->size(); ++i) {
+ const int resource = (*options.maximize_rlimits)[i];
+ struct rlimit limit;
+ if (getrlimit(resource, &limit) < 0) {
+ RAW_LOG(WARNING, "getrlimit failed");
+ } else if (limit.rlim_cur < limit.rlim_max) {
+ limit.rlim_cur = limit.rlim_max;
+ if (setrlimit(resource, &limit) < 0) {
+ RAW_LOG(WARNING, "setrlimit failed");
+ }
+ }
+ }
+ }
+
+#if defined(OS_MACOSX)
+ RestoreDefaultExceptionHandler();
+#endif // defined(OS_MACOSX)
+
+ ResetChildSignalHandlersToDefaults();
+ SetSignalMask(orig_sigmask);
+
+#if 0
+ // When debugging it can be helpful to check that we really aren't making
+ // any hidden calls to malloc.
+ void *malloc_thunk =
+ reinterpret_cast<void*>(reinterpret_cast<intptr_t>(malloc) & ~4095);
+ mprotect(malloc_thunk, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
+ SbMemorySet(reinterpret_cast<void*>(malloc), 0xff, 8);
+#endif // 0
+
+#if defined(OS_CHROMEOS)
+ if (options.ctrl_terminal_fd >= 0) {
+ // Set process' controlling terminal.
+ if (HANDLE_EINTR(setsid()) != -1) {
+ if (HANDLE_EINTR(
+ ioctl(options.ctrl_terminal_fd, TIOCSCTTY, nullptr)) == -1) {
+ RAW_LOG(WARNING, "ioctl(TIOCSCTTY), ctrl terminal not set");
+ }
+ } else {
+ RAW_LOG(WARNING, "setsid failed, ctrl terminal not set");
+ }
+ }
+#endif // defined(OS_CHROMEOS)
+
+ // Cannot use STL iterators here, since debug iterators use locks.
+ for (size_t i = 0; i < options.fds_to_remap.size(); ++i) {
+ const FileHandleMappingVector::value_type& value =
+ options.fds_to_remap[i];
+ fd_shuffle1.push_back(InjectionArc(value.first, value.second, false));
+ fd_shuffle2.push_back(InjectionArc(value.first, value.second, false));
+ }
+
+ if (!options.environ.empty() || options.clear_environ)
+ SetEnvironment(new_environ.get());
+
+ // fd_shuffle1 is mutated by this call because it cannot malloc.
+ if (!ShuffleFileDescriptors(&fd_shuffle1))
+ _exit(127);
+
+ CloseSuperfluousFds(fd_shuffle2);
+
+ // Set NO_NEW_PRIVS by default. Since NO_NEW_PRIVS only exists in kernel
+ // 3.5+, do not check the return value of prctl here.
+#if defined(OS_LINUX) || defined(OS_AIX)
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#endif
+ if (!options.allow_new_privs) {
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) && errno != EINVAL) {
+ // Only log if the error is not EINVAL (i.e. not supported).
+ RAW_LOG(FATAL, "prctl(PR_SET_NO_NEW_PRIVS) failed");
+ }
+ }
+
+ if (options.kill_on_parent_death) {
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0) {
+ RAW_LOG(ERROR, "prctl(PR_SET_PDEATHSIG) failed");
+ _exit(127);
+ }
+ }
+#endif
+
+ if (current_directory != nullptr) {
+ RAW_CHECK(chdir(current_directory) == 0);
+ }
+
+ if (options.pre_exec_delegate != nullptr) {
+ options.pre_exec_delegate->RunAsyncSafe();
+ }
+
+ const char* executable_path = !options.real_path.empty() ?
+ options.real_path.value().c_str() : argv_cstr[0];
+
+ execvp(executable_path, argv_cstr.data());
+
+ RAW_LOG(ERROR, "LaunchProcess: failed to execvp:");
+ RAW_LOG(ERROR, argv_cstr[0]);
+ _exit(127);
+ } else {
+ // Parent process
+ if (options.wait) {
+ // While this isn't strictly disk IO, waiting for another process to
+ // finish is the sort of thing ThreadRestrictions is trying to prevent.
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+ pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
+ DPCHECK(ret > 0);
+ }
+ }
+
+ return Process(pid);
+}
+
+void RaiseProcessToHighPriority() {
+ // On POSIX, we don't actually do anything here. We could try to nice() or
+ // setpriority() or sched_getscheduler, but these all require extra rights.
+}
+
+// Executes the application specified by |argv| and wait for it to exit. Stores
+// the output (stdout) in |output|. If |do_search_path| is set, it searches the
+// path for the application; in that case, |envp| must be null, and it will use
+// the current environment. If |do_search_path| is false, |argv[0]| should fully
+// specify the path of the application, and |envp| will be used as the
+// environment. If |include_stderr| is true, includes stderr otherwise redirects
+// it to /dev/null.
+// The return value of the function indicates success or failure. In the case of
+// success, the application exit code will be returned in |*exit_code|, which
+// should be checked to determine if the application ran successfully.
+static bool GetAppOutputInternal(
+ const std::vector<std::string>& argv,
+ char* const envp[],
+ bool include_stderr,
+ std::string* output,
+ bool do_search_path,
+ int* exit_code) {
+ ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+ // exit_code must be supplied so calling function can determine success.
+ DCHECK(exit_code);
+ *exit_code = EXIT_FAILURE;
+
+ // Declare and call reserve() here before calling fork() because the child
+ // process cannot allocate memory.
+ std::vector<char*> argv_cstr;
+ argv_cstr.reserve(argv.size() + 1);
+ InjectiveMultimap fd_shuffle1;
+ InjectiveMultimap fd_shuffle2;
+ fd_shuffle1.reserve(3);
+ fd_shuffle2.reserve(3);
+
+ // Either |do_search_path| should be false or |envp| should be null, but not
+ // both.
+ DCHECK(!do_search_path ^ !envp);
+
+ int pipe_fd[2];
+ if (pipe(pipe_fd) < 0)
+ return false;
+
+ pid_t pid = fork();
+ switch (pid) {
+ case -1: {
+ // error
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return false;
+ }
+ case 0: {
+ // child
+ //
+ // DANGER: no calls to malloc or locks are allowed from now on:
+ // http://crbug.com/36678
+
+#if defined(OS_MACOSX)
+ RestoreDefaultExceptionHandler();
+#endif
+
+ // Obscure fork() rule: in the child, if you don't end up doing exec*(),
+ // you call _exit() instead of exit(). This is because _exit() does not
+ // call any previously-registered (in the parent) exit handlers, which
+ // might do things like block waiting for threads that don't even exist
+ // in the child.
+ int dev_null = open("/dev/null", O_WRONLY);
+ if (dev_null < 0)
+ _exit(127);
+
+ fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true));
+ fd_shuffle1.push_back(InjectionArc(include_stderr ? pipe_fd[1] : dev_null,
+ STDERR_FILENO, true));
+ fd_shuffle1.push_back(InjectionArc(dev_null, STDIN_FILENO, true));
+ // Adding another element here? Remeber to increase the argument to
+ // reserve(), above.
+
+ for (size_t i = 0; i < fd_shuffle1.size(); ++i)
+ fd_shuffle2.push_back(fd_shuffle1[i]);
+
+ if (!ShuffleFileDescriptors(&fd_shuffle1))
+ _exit(127);
+
+ CloseSuperfluousFds(fd_shuffle2);
+
+ for (const auto& arg : argv)
+ argv_cstr.push_back(const_cast<char*>(arg.c_str()));
+ argv_cstr.push_back(nullptr);
+
+ if (do_search_path)
+ execvp(argv_cstr[0], argv_cstr.data());
+ else
+ execve(argv_cstr[0], argv_cstr.data(), envp);
+ _exit(127);
+ }
+ default: {
+ // parent
+ //
+ // Close our writing end of pipe now. Otherwise later read would not
+ // be able to detect end of child's output (in theory we could still
+ // write to the pipe).
+ close(pipe_fd[1]);
+
+ output->clear();
+
+ while (true) {
+ char buffer[256];
+ ssize_t bytes_read =
+ HANDLE_EINTR(read(pipe_fd[0], buffer, sizeof(buffer)));
+ if (bytes_read <= 0)
+ break;
+ output->append(buffer, bytes_read);
+ }
+ close(pipe_fd[0]);
+
+ // Always wait for exit code (even if we know we'll declare
+ // GOT_MAX_OUTPUT).
+ Process process(pid);
+ // A process launched with GetAppOutput*() usually doesn't wait on the
+ // process that launched it and thus chances of deadlock are low.
+ GetAppOutputScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
+ return process.WaitForExit(exit_code);
+ }
+ }
+}
+
+bool GetAppOutput(const CommandLine& cl, std::string* output) {
+ return GetAppOutput(cl.argv(), output);
+}
+
+bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) {
+ // Run |execve()| with the current environment.
+ int exit_code;
+ bool result =
+ GetAppOutputInternal(argv, nullptr, false, output, true, &exit_code);
+ return result && exit_code == EXIT_SUCCESS;
+}
+
+bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
+ // Run |execve()| with the current environment.
+ int exit_code;
+ bool result =
+ GetAppOutputInternal(cl.argv(), nullptr, true, output, true, &exit_code);
+ return result && exit_code == EXIT_SUCCESS;
+}
+
+bool GetAppOutputAndError(const std::vector<std::string>& argv,
+ std::string* output) {
+ int exit_code;
+ bool result =
+ GetAppOutputInternal(argv, nullptr, true, output, true, &exit_code);
+ return result && exit_code == EXIT_SUCCESS;
+}
+
+bool GetAppOutputWithExitCode(const CommandLine& cl,
+ std::string* output,
+ int* exit_code) {
+ // Run |execve()| with the current environment.
+ return GetAppOutputInternal(cl.argv(), nullptr, false, output, true,
+ exit_code);
+}
+
+#endif // !defined(OS_NACL_NONSFI)
+
+#if defined(OS_LINUX) || defined(OS_NACL_NONSFI) || defined(OS_AIX)
+namespace {
+
+// This function runs on the stack specified on the clone call. It uses longjmp
+// to switch back to the original stack so the child can return from sys_clone.
+int CloneHelper(void* arg) {
+ jmp_buf* env_ptr = reinterpret_cast<jmp_buf*>(arg);
+ longjmp(*env_ptr, 1);
+
+ // Should not be reached.
+ RAW_CHECK(false);
+ return 1;
+}
+
+// This function is noinline to ensure that stack_buf is below the stack pointer
+// that is saved when setjmp is called below. This is needed because when
+// compiled with FORTIFY_SOURCE, glibc's longjmp checks that the stack is moved
+// upwards. See crbug.com/442912 for more details.
+#if defined(ADDRESS_SANITIZER)
+// Disable AddressSanitizer instrumentation for this function to make sure
+// |stack_buf| is allocated on thread stack instead of ASan's fake stack.
+// Under ASan longjmp() will attempt to clean up the area between the old and
+// new stack pointers and print a warning that may confuse the user.
+__attribute__((no_sanitize_address))
+#endif
+NOINLINE pid_t CloneAndLongjmpInChild(unsigned long flags,
+ pid_t* ptid,
+ pid_t* ctid,
+ jmp_buf* env) {
+ // We use the libc clone wrapper instead of making the syscall
+ // directly because making the syscall may fail to update the libc's
+ // internal pid cache. The libc interface unfortunately requires
+ // specifying a new stack, so we use setjmp/longjmp to emulate
+ // fork-like behavior.
+ alignas(16) char stack_buf[PTHREAD_STACK_MIN];
+#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \
+ defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_S390_FAMILY) || \
+ defined(ARCH_CPU_PPC64_FAMILY)
+ // The stack grows downward.
+ void* stack = stack_buf + sizeof(stack_buf);
+#else
+#error "Unsupported architecture"
+#endif
+ return clone(&CloneHelper, stack, flags, env, ptid, nullptr, ctid);
+}
+
+} // anonymous namespace
+
+pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid) {
+ const bool clone_tls_used = flags & CLONE_SETTLS;
+ const bool invalid_ctid =
+ (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid;
+ const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid;
+
+ // We do not support CLONE_VM.
+ const bool clone_vm_used = flags & CLONE_VM;
+
+ if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) {
+ RAW_LOG(FATAL, "Invalid usage of ForkWithFlags");
+ }
+
+ jmp_buf env;
+ if (setjmp(env) == 0) {
+ return CloneAndLongjmpInChild(flags, ptid, ctid, &env);
+ }
+
+ return 0;
+}
+#endif // defined(OS_LINUX) || defined(OS_NACL_NONSFI)
+
+} // namespace base
diff --git a/src/base/process/launch_unittest_win.cc b/src/base/process/launch_unittest_win.cc
new file mode 100644
index 0000000..1b060c5
--- /dev/null
+++ b/src/base/process/launch_unittest_win.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+TEST(LaunchWinTest, GetAppOutputWithExitCodeShouldReturnExitCode) {
+ CommandLine cl(FilePath(FILE_PATH_LITERAL("cmd")));
+ cl.AppendArg("/c");
+ cl.AppendArg("this-is-not-an-application");
+ std::string output;
+ int exit_code;
+ ASSERT_TRUE(GetAppOutputWithExitCode(cl, &output, &exit_code));
+ ASSERT_TRUE(output.empty());
+ ASSERT_EQ(1, exit_code);
+}
+
+} // namespace
diff --git a/src/base/process/launch_win.cc b/src/base/process/launch_win.cc
new file mode 100644
index 0000000..13686aa
--- /dev/null
+++ b/src/base/process/launch_win.cc
@@ -0,0 +1,399 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/launch.h"
+
+#include <fcntl.h>
+#include <io.h>
+#include <shellapi.h>
+#include <windows.h>
+#include <userenv.h>
+#include <psapi.h>
+
+#include <ios>
+#include <limits>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/debug/activity_tracker.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/process/kill.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/sys_info.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_process_information.h"
+#include "base/win/startup_information.h"
+#include "base/win/windows_version.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+bool GetAppOutputInternal(const StringPiece16& cl,
+ bool include_stderr,
+ std::string* output,
+ int* exit_code) {
+ HANDLE out_read = nullptr;
+ HANDLE out_write = nullptr;
+
+ SECURITY_ATTRIBUTES sa_attr;
+ // Set the bInheritHandle flag so pipe handles are inherited.
+ sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa_attr.bInheritHandle = TRUE;
+ sa_attr.lpSecurityDescriptor = nullptr;
+
+ // Create the pipe for the child process's STDOUT.
+ if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) {
+ NOTREACHED() << "Failed to create pipe";
+ return false;
+ }
+
+ // Ensure we don't leak the handles.
+ win::ScopedHandle scoped_out_read(out_read);
+ win::ScopedHandle scoped_out_write(out_write);
+
+ // Ensure the read handles to the pipes are not inherited.
+ if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) {
+ NOTREACHED() << "Failed to disabled pipe inheritance";
+ return false;
+ }
+
+ FilePath::StringType writable_command_line_string;
+ writable_command_line_string.assign(cl.data(), cl.size());
+
+ STARTUPINFO start_info = {};
+
+ start_info.cb = sizeof(STARTUPINFO);
+ start_info.hStdOutput = out_write;
+ // Keep the normal stdin.
+ start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ if (include_stderr) {
+ start_info.hStdError = out_write;
+ } else {
+ start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
+ }
+ start_info.dwFlags |= STARTF_USESTDHANDLES;
+
+ // Create the child process.
+ PROCESS_INFORMATION temp_process_info = {};
+ if (!CreateProcess(nullptr, &writable_command_line_string[0], nullptr,
+ nullptr,
+ TRUE, // Handles are inherited.
+ 0, nullptr, nullptr, &start_info, &temp_process_info)) {
+ NOTREACHED() << "Failed to start process";
+ return false;
+ }
+
+ base::win::ScopedProcessInformation proc_info(temp_process_info);
+ base::debug::GlobalActivityTracker* tracker =
+ base::debug::GlobalActivityTracker::Get();
+ if (tracker)
+ tracker->RecordProcessLaunch(proc_info.process_id(), cl.as_string());
+
+ // Close our writing end of pipe now. Otherwise later read would not be able
+ // to detect end of child's output.
+ scoped_out_write.Close();
+
+ // Read output from the child process's pipe for STDOUT
+ const int kBufferSize = 1024;
+ char buffer[kBufferSize];
+
+ for (;;) {
+ DWORD bytes_read = 0;
+ BOOL success =
+ ::ReadFile(out_read, buffer, kBufferSize, &bytes_read, nullptr);
+ if (!success || bytes_read == 0)
+ break;
+ output->append(buffer, bytes_read);
+ }
+
+ // Let's wait for the process to finish.
+ WaitForSingleObject(proc_info.process_handle(), INFINITE);
+
+ base::TerminationStatus status = GetTerminationStatus(
+ proc_info.process_handle(), exit_code);
+ base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(
+ proc_info.process_id(), *exit_code);
+ return status != base::TERMINATION_STATUS_PROCESS_CRASHED &&
+ status != base::TERMINATION_STATUS_ABNORMAL_TERMINATION;
+}
+
+} // namespace
+
+void RouteStdioToConsole(bool create_console_if_not_found) {
+ // Don't change anything if stdout or stderr already point to a
+ // valid stream.
+ //
+ // If we are running under Buildbot or under Cygwin's default
+ // terminal (mintty), stderr and stderr will be pipe handles. In
+ // that case, we don't want to open CONOUT$, because its output
+ // likely does not go anywhere.
+ //
+ // We don't use GetStdHandle() to check stdout/stderr here because
+ // it can return dangling IDs of handles that were never inherited
+ // by this process. These IDs could have been reused by the time
+ // this function is called. The CRT checks the validity of
+ // stdout/stderr on startup (before the handle IDs can be reused).
+ // _fileno(stdout) will return -2 (_NO_CONSOLE_FILENO) if stdout was
+ // invalid.
+ if (_fileno(stdout) >= 0 || _fileno(stderr) >= 0) {
+ // _fileno was broken for SUBSYSTEM:WINDOWS from VS2010 to VS2012/2013.
+ // http://crbug.com/358267. Confirm that the underlying HANDLE is valid
+ // before aborting.
+
+ intptr_t stdout_handle = _get_osfhandle(_fileno(stdout));
+ intptr_t stderr_handle = _get_osfhandle(_fileno(stderr));
+ if (stdout_handle >= 0 || stderr_handle >= 0)
+ return;
+ }
+
+ if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
+ unsigned int result = GetLastError();
+ // Was probably already attached.
+ if (result == ERROR_ACCESS_DENIED)
+ return;
+ // Don't bother creating a new console for each child process if the
+ // parent process is invalid (eg: crashed).
+ if (result == ERROR_GEN_FAILURE)
+ return;
+ if (create_console_if_not_found) {
+ // Make a new console if attaching to parent fails with any other error.
+ // It should be ERROR_INVALID_HANDLE at this point, which means the
+ // browser was likely not started from a console.
+ AllocConsole();
+ } else {
+ return;
+ }
+ }
+
+ // Arbitrary byte count to use when buffering output lines. More
+ // means potential waste, less means more risk of interleaved
+ // log-lines in output.
+ enum { kOutputBufferSize = 64 * 1024 };
+
+ if (freopen("CONOUT$", "w", stdout)) {
+ setvbuf(stdout, nullptr, _IOLBF, kOutputBufferSize);
+ // Overwrite FD 1 for the benefit of any code that uses this FD
+ // directly. This is safe because the CRT allocates FDs 0, 1 and
+ // 2 at startup even if they don't have valid underlying Windows
+ // handles. This means we won't be overwriting an FD created by
+ // _open() after startup.
+ _dup2(_fileno(stdout), 1);
+ }
+ if (freopen("CONOUT$", "w", stderr)) {
+ setvbuf(stderr, nullptr, _IOLBF, kOutputBufferSize);
+ _dup2(_fileno(stderr), 2);
+ }
+
+ // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog.
+ std::ios::sync_with_stdio();
+}
+
+Process LaunchProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ return LaunchProcess(cmdline.GetCommandLineString(), options);
+}
+
+Process LaunchProcess(const string16& cmdline,
+ const LaunchOptions& options) {
+ win::StartupInformation startup_info_wrapper;
+ STARTUPINFO* startup_info = startup_info_wrapper.startup_info();
+
+ bool inherit_handles = options.inherit_mode == LaunchOptions::Inherit::kAll;
+ DWORD flags = 0;
+ if (!options.handles_to_inherit.empty()) {
+ DCHECK_EQ(options.inherit_mode, LaunchOptions::Inherit::kSpecific);
+
+ if (options.handles_to_inherit.size() >
+ std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) {
+ DLOG(ERROR) << "Too many handles to inherit.";
+ return Process();
+ }
+
+ // Ensure the handles can be inherited.
+ for (HANDLE handle : options.handles_to_inherit) {
+ BOOL result = SetHandleInformation(handle, HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT);
+ PCHECK(result);
+ }
+
+ if (!startup_info_wrapper.InitializeProcThreadAttributeList(1)) {
+ DPLOG(ERROR);
+ return Process();
+ }
+
+ if (!startup_info_wrapper.UpdateProcThreadAttribute(
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ const_cast<HANDLE*>(&options.handles_to_inherit[0]),
+ static_cast<DWORD>(options.handles_to_inherit.size() *
+ sizeof(HANDLE)))) {
+ DPLOG(ERROR);
+ return Process();
+ }
+
+ inherit_handles = true;
+ flags |= EXTENDED_STARTUPINFO_PRESENT;
+ }
+
+ if (options.empty_desktop_name)
+ startup_info->lpDesktop = const_cast<wchar_t*>(L"");
+ startup_info->dwFlags = STARTF_USESHOWWINDOW;
+ startup_info->wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOWNORMAL;
+
+ if (options.stdin_handle || options.stdout_handle || options.stderr_handle) {
+ DCHECK(inherit_handles);
+ DCHECK(options.stdin_handle);
+ DCHECK(options.stdout_handle);
+ DCHECK(options.stderr_handle);
+ startup_info->dwFlags |= STARTF_USESTDHANDLES;
+ startup_info->hStdInput = options.stdin_handle;
+ startup_info->hStdOutput = options.stdout_handle;
+ startup_info->hStdError = options.stderr_handle;
+ }
+
+ if (options.job_handle) {
+ // If this code is run under a debugger, the launched process is
+ // automatically associated with a job object created by the debugger.
+ // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this on Windows
+ // releases that do not support nested jobs.
+ if (win::GetVersion() < win::VERSION_WIN8)
+ flags |= CREATE_BREAKAWAY_FROM_JOB;
+ }
+
+ if (options.force_breakaway_from_job_)
+ flags |= CREATE_BREAKAWAY_FROM_JOB;
+
+ PROCESS_INFORMATION temp_process_info = {};
+
+ LPCTSTR current_directory = options.current_directory.empty()
+ ? nullptr
+ : options.current_directory.value().c_str();
+
+ string16 writable_cmdline(cmdline);
+ DCHECK(!(flags & CREATE_SUSPENDED))
+ << "Creating a suspended process can lead to hung processes if the "
+ << "launching process is killed before it assigns the process to the"
+ << "job. https://crbug.com/820996";
+ if (options.as_user) {
+ flags |= CREATE_UNICODE_ENVIRONMENT;
+ void* enviroment_block = nullptr;
+
+ if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) {
+ DPLOG(ERROR);
+ return Process();
+ }
+
+ BOOL launched = CreateProcessAsUser(
+ options.as_user, nullptr, &writable_cmdline[0], nullptr, nullptr,
+ inherit_handles, flags, enviroment_block, current_directory,
+ startup_info, &temp_process_info);
+ DestroyEnvironmentBlock(enviroment_block);
+ if (!launched) {
+ DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline)
+ << std::endl;
+ return Process();
+ }
+ } else {
+ if (!CreateProcess(nullptr, &writable_cmdline[0], nullptr, nullptr,
+ inherit_handles, flags, nullptr, current_directory,
+ startup_info, &temp_process_info)) {
+ DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline)
+ << std::endl;
+ return Process();
+ }
+ }
+ base::win::ScopedProcessInformation process_info(temp_process_info);
+
+ if (options.job_handle &&
+ !AssignProcessToJobObject(options.job_handle,
+ process_info.process_handle())) {
+ DPLOG(ERROR) << "Could not AssignProcessToObject";
+ Process scoped_process(process_info.TakeProcessHandle());
+ scoped_process.Terminate(win::kProcessKilledExitCode, true);
+ return Process();
+ }
+
+ if (options.grant_foreground_privilege &&
+ !AllowSetForegroundWindow(GetProcId(process_info.process_handle()))) {
+ DPLOG(ERROR) << "Failed to grant foreground privilege to launched process";
+ }
+
+ if (options.wait)
+ WaitForSingleObject(process_info.process_handle(), INFINITE);
+
+ base::debug::GlobalActivityTracker::RecordProcessLaunchIfEnabled(
+ process_info.process_id(), cmdline);
+ return Process(process_info.TakeProcessHandle());
+}
+
+Process LaunchElevatedProcess(const CommandLine& cmdline,
+ const LaunchOptions& options) {
+ const string16 file = cmdline.GetProgram().value();
+ const string16 arguments = cmdline.GetArgumentsString();
+
+ SHELLEXECUTEINFO shex_info = {};
+ shex_info.cbSize = sizeof(shex_info);
+ shex_info.fMask = SEE_MASK_NOCLOSEPROCESS;
+ shex_info.hwnd = GetActiveWindow();
+ shex_info.lpVerb = L"runas";
+ shex_info.lpFile = file.c_str();
+ shex_info.lpParameters = arguments.c_str();
+ shex_info.lpDirectory = nullptr;
+ shex_info.nShow = options.start_hidden ? SW_HIDE : SW_SHOWNORMAL;
+ shex_info.hInstApp = nullptr;
+
+ if (!ShellExecuteEx(&shex_info)) {
+ DPLOG(ERROR);
+ return Process();
+ }
+
+ if (options.wait)
+ WaitForSingleObject(shex_info.hProcess, INFINITE);
+
+ base::debug::GlobalActivityTracker::RecordProcessLaunchIfEnabled(
+ GetProcessId(shex_info.hProcess), file, arguments);
+ return Process(shex_info.hProcess);
+}
+
+bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {};
+ limit_info.BasicLimitInformation.LimitFlags = limit_flags;
+ return 0 != SetInformationJobObject(
+ job_object,
+ JobObjectExtendedLimitInformation,
+ &limit_info,
+ sizeof(limit_info));
+}
+
+bool GetAppOutput(const CommandLine& cl, std::string* output) {
+ return GetAppOutput(cl.GetCommandLineString(), output);
+}
+
+bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
+ int exit_code;
+ return GetAppOutputInternal(
+ cl.GetCommandLineString(), true, output, &exit_code);
+}
+
+bool GetAppOutputWithExitCode(const CommandLine& cl,
+ std::string* output,
+ int* exit_code) {
+ return GetAppOutputInternal(
+ cl.GetCommandLineString(), false, output, exit_code);
+}
+
+bool GetAppOutput(const StringPiece16& cl, std::string* output) {
+ int exit_code;
+ return GetAppOutputInternal(cl, false, output, &exit_code);
+}
+
+void RaiseProcessToHighPriority() {
+ SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
+}
+
+} // namespace base
diff --git a/src/base/process/memory.cc b/src/base/process/memory.cc
new file mode 100644
index 0000000..9e40143
--- /dev/null
+++ b/src/base/process/memory.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/debug/alias.h"
+#include "base/logging.h"
+#include "base/process/memory.h"
+#include "build/build_config.h"
+
+#if defined(STARBOARD)
+#include "starboard/memory.h"
+#endif
+
+namespace base {
+
+// Defined in memory_win.cc for Windows.
+#if !defined(OS_WIN)
+
+namespace {
+
+// Breakpad server classifies base::`anonymous namespace'::OnNoMemory as
+// out-of-memory crash.
+NOINLINE void OnNoMemory(size_t size) {
+ size_t tmp_size = size;
+ base::debug::Alias(&tmp_size);
+ LOG(FATAL) << "Out of memory. size=" << tmp_size;
+}
+
+} // namespace
+
+void TerminateBecauseOutOfMemory(size_t size) {
+ OnNoMemory(size);
+}
+
+#endif
+
+// Defined in memory_mac.mm for Mac.
+#if !defined(OS_MACOSX)
+
+bool UncheckedCalloc(size_t num_items, size_t size, void** result) {
+ const size_t alloc_size = num_items * size;
+
+ // Overflow check
+ if (size && ((alloc_size / size) != num_items)) {
+ *result = nullptr;
+ return false;
+ }
+
+ if (!UncheckedMalloc(alloc_size, result))
+ return false;
+
+#if defined(STARBOARD)
+ SbMemorySet(*result, 0, alloc_size);
+#else
+ SbMemorySet(*result, 0, alloc_size);
+#endif
+ return true;
+}
+
+#endif
+
+} // namespace base
diff --git a/src/base/process/memory.h b/src/base/process/memory.h
new file mode 100644
index 0000000..c9bd0ba
--- /dev/null
+++ b/src/base/process/memory.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROCESS_MEMORY_H_
+#define BASE_PROCESS_MEMORY_H_
+
+#include "base/base_export.h"
+#include "base/process/process_handle.h"
+#include "build/build_config.h"
+#include "starboard/types.h"
+
+#ifdef PVALLOC_AVAILABLE
+// Build config explicitly tells us whether or not pvalloc is available.
+#elif defined(LIBC_GLIBC) && !defined(USE_TCMALLOC)
+#define PVALLOC_AVAILABLE 1
+#else
+#define PVALLOC_AVAILABLE 0
+#endif
+
+namespace base {
+
+// Enables 'terminate on heap corruption' flag. Helps protect against heap
+// overflow. Has no effect if the OS doesn't provide the necessary facility.
+BASE_EXPORT void EnableTerminationOnHeapCorruption();
+
+// Turns on process termination if memory runs out.
+BASE_EXPORT void EnableTerminationOnOutOfMemory();
+
+// Terminates process. Should be called only for out of memory errors.
+// Crash reporting classifies such crashes as OOM.
+BASE_EXPORT void TerminateBecauseOutOfMemory(size_t size);
+
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
+BASE_EXPORT extern size_t g_oom_size;
+
+// The maximum allowed value for the OOM score.
+const int kMaxOomScore = 1000;
+
+// This adjusts /proc/<pid>/oom_score_adj so the Linux OOM killer will
+// prefer to kill certain process types over others. The range for the
+// adjustment is [-1000, 1000], with [0, 1000] being user accessible.
+// If the Linux system doesn't support the newer oom_score_adj range
+// of [0, 1000], then we revert to using the older oom_adj, and
+// translate the given value into [0, 15]. Some aliasing of values
+// may occur in that case, of course.
+BASE_EXPORT bool AdjustOOMScore(ProcessId process, int score);
+#endif
+
+#if defined(OS_WIN)
+namespace win {
+
+// Custom Windows exception code chosen to indicate an out of memory error.
+// See https://msdn.microsoft.com/en-us/library/het71c37.aspx.
+// "To make sure that you do not define a code that conflicts with an existing
+// exception code" ... "The resulting error code should therefore have the
+// highest four bits set to hexadecimal E."
+// 0xe0000008 was chosen arbitrarily, as 0x00000008 is ERROR_NOT_ENOUGH_MEMORY.
+const DWORD kOomExceptionCode = 0xe0000008;
+
+} // namespace win
+#endif
+
+// Special allocator functions for callers that want to check for OOM.
+// These will not abort if the allocation fails even if
+// EnableTerminationOnOutOfMemory has been called.
+// This can be useful for huge and/or unpredictable size memory allocations.
+// Please only use this if you really handle the case when the allocation
+// fails. Doing otherwise would risk security.
+// These functions may still crash on OOM when running under memory tools,
+// specifically ASan and other sanitizers.
+// Return value tells whether the allocation succeeded. If it fails |result| is
+// set to NULL, otherwise it holds the memory address.
+BASE_EXPORT WARN_UNUSED_RESULT bool UncheckedMalloc(size_t size,
+ void** result);
+BASE_EXPORT WARN_UNUSED_RESULT bool UncheckedCalloc(size_t num_items,
+ size_t size,
+ void** result);
+
+} // namespace base
+
+#endif // BASE_PROCESS_MEMORY_H_
diff --git a/src/base/process/memory_fuchsia.cc b/src/base/process/memory_fuchsia.cc
new file mode 100644
index 0000000..0a13e55
--- /dev/null
+++ b/src/base/process/memory_fuchsia.cc
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory.h"
+
+#include <stdlib.h>
+
+#include "starboard/memory.h"
+#include "starboard/types.h"
+
+namespace base {
+
+void EnableTerminationOnOutOfMemory() {
+ // Nothing to be done here.
+}
+
+void EnableTerminationOnHeapCorruption() {
+ // Nothing to be done here.
+}
+
+bool UncheckedMalloc(size_t size, void** result) {
+ *result = SbMemoryAllocate(size);
+ return *result != nullptr;
+}
+
+} // namespace base
diff --git a/src/base/process/memory_linux.cc b/src/base/process/memory_linux.cc
new file mode 100644
index 0000000..6e68602
--- /dev/null
+++ b/src/base/process/memory_linux.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory.h"
+
+#include <new>
+
+#include "base/allocator/allocator_shim.h"
+#include "base/allocator/buildflags.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/process/internal_linux.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+
+#if defined(USE_TCMALLOC)
+#if BUILDFLAG(USE_NEW_TCMALLOC)
+#include "third_party/tcmalloc/chromium/src/config.h"
+#include "third_party/tcmalloc/chromium/src/gperftools/tcmalloc.h"
+#else
+#include "starboard/memory.h"
+#include "starboard/types.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/config.h"
+#include "third_party/tcmalloc/gperftools-2.0/chromium/src/gperftools/tcmalloc.h"
+#endif
+#endif
+
+namespace base {
+
+size_t g_oom_size = 0U;
+
+namespace {
+
+void OnNoMemorySize(size_t size) {
+ g_oom_size = size;
+
+ if (size != 0)
+ LOG(FATAL) << "Out of memory, size = " << size;
+ LOG(FATAL) << "Out of memory.";
+}
+
+void OnNoMemory() {
+ OnNoMemorySize(0);
+}
+
+} // namespace
+
+void EnableTerminationOnHeapCorruption() {
+ // On Linux, there nothing to do AFAIK.
+}
+
+void EnableTerminationOnOutOfMemory() {
+ // Set the new-out of memory handler.
+ std::set_new_handler(&OnNoMemory);
+ // If we're using glibc's allocator, the above functions will override
+ // malloc and friends and make them die on out of memory.
+
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ allocator::SetCallNewHandlerOnMallocFailure(true);
+#elif defined(USE_TCMALLOC)
+ // For tcmalloc, we need to tell it to behave like new.
+ tc_set_new_mode(1);
+#endif
+}
+
+// ScopedAllowBlocking() has private constructor and it can only be used in
+// friend classes/functions. Declaring a class is easier in this situation to
+// avoid adding more dependency to thread_restrictions.h because of the
+// parameter used in AdjustOOMScore(). Specifically, ProcessId is a typedef
+// and we'll need to include another header file in thread_restrictions.h
+// without the class.
+class AdjustOOMScoreHelper {
+ public:
+ static bool AdjustOOMScore(ProcessId process, int score);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(AdjustOOMScoreHelper);
+};
+
+// static.
+bool AdjustOOMScoreHelper::AdjustOOMScore(ProcessId process, int score) {
+ if (score < 0 || score > kMaxOomScore)
+ return false;
+
+ FilePath oom_path(internal::GetProcPidDir(process));
+
+ // Temporarily allowing blocking since oom paths are pseudo-filesystem paths.
+ base::ScopedAllowBlocking allow_blocking;
+
+ // Attempt to write the newer oom_score_adj file first.
+ FilePath oom_file = oom_path.AppendASCII("oom_score_adj");
+ if (PathExists(oom_file)) {
+ std::string score_str = IntToString(score);
+ DVLOG(1) << "Adjusting oom_score_adj of " << process << " to "
+ << score_str;
+ int score_len = static_cast<int>(score_str.length());
+ return (score_len == WriteFile(oom_file, score_str.c_str(), score_len));
+ }
+
+ // If the oom_score_adj file doesn't exist, then we write the old
+ // style file and translate the oom_adj score to the range 0-15.
+ oom_file = oom_path.AppendASCII("oom_adj");
+ if (PathExists(oom_file)) {
+ // Max score for the old oom_adj range. Used for conversion of new
+ // values to old values.
+ const int kMaxOldOomScore = 15;
+
+ int converted_score = score * kMaxOldOomScore / kMaxOomScore;
+ std::string score_str = IntToString(converted_score);
+ DVLOG(1) << "Adjusting oom_adj of " << process << " to " << score_str;
+ int score_len = static_cast<int>(score_str.length());
+ return (score_len == WriteFile(oom_file, score_str.c_str(), score_len));
+ }
+
+ return false;
+}
+
+// NOTE: This is not the only version of this function in the source:
+// the setuid sandbox (in process_util_linux.c, in the sandbox source)
+// also has its own C version.
+bool AdjustOOMScore(ProcessId process, int score) {
+ return AdjustOOMScoreHelper::AdjustOOMScore(process, score);
+}
+
+bool UncheckedMalloc(size_t size, void** result) {
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ *result = allocator::UncheckedAlloc(size);
+#elif defined(MEMORY_TOOL_REPLACES_ALLOCATOR) || \
+ (!defined(LIBC_GLIBC) && !defined(USE_TCMALLOC))
+ *result = SbMemoryAllocate(size);
+#elif defined(LIBC_GLIBC) && !defined(USE_TCMALLOC)
+ *result = __libc_malloc(size);
+#elif defined(USE_TCMALLOC)
+ *result = tc_malloc_skip_new_handler(size);
+#endif
+ return *result != nullptr;
+}
+
+} // namespace base
diff --git a/src/base/process/memory_mac.mm b/src/base/process/memory_mac.mm
new file mode 100644
index 0000000..5b8cd13
--- /dev/null
+++ b/src/base/process/memory_mac.mm
@@ -0,0 +1,49 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory.h"
+
+#include "base/allocator/allocator_interception_mac.h"
+#include "base/allocator/allocator_shim.h"
+#include "base/allocator/buildflags.h"
+#include "build/build_config.h"
+
+namespace base {
+
+namespace {
+void oom_killer_new() {
+ TerminateBecauseOutOfMemory(0);
+}
+} // namespace
+
+void EnableTerminationOnHeapCorruption() {
+#if !ARCH_CPU_64_BITS
+ DLOG(WARNING) << "EnableTerminationOnHeapCorruption only works on 64-bit";
+#endif
+}
+
+bool UncheckedMalloc(size_t size, void** result) {
+ return allocator::UncheckedMallocMac(size, result);
+}
+
+bool UncheckedCalloc(size_t num_items, size_t size, void** result) {
+ return allocator::UncheckedCallocMac(num_items, size, result);
+}
+
+void EnableTerminationOnOutOfMemory() {
+ // Step 1: Enable OOM killer on C++ failures.
+ std::set_new_handler(oom_killer_new);
+
+// Step 2: Enable OOM killer on C-malloc failures for the default zone (if we
+// have a shim).
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ allocator::SetCallNewHandlerOnMallocFailure(true);
+#endif
+
+ // Step 3: Enable OOM killer on all other malloc zones (or just "all" without
+ // "other" if shim is disabled).
+ allocator::InterceptAllocationsMac();
+}
+
+} // namespace base
diff --git a/src/base/process/memory_starboard.cc b/src/base/process/memory_starboard.cc
new file mode 100644
index 0000000..809267c
--- /dev/null
+++ b/src/base/process/memory_starboard.cc
@@ -0,0 +1,34 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include "base/process/memory.h"
+
+#include "starboard/memory.h"
+
+namespace base {
+
+void EnableTerminationOnOutOfMemory() {
+ // Nothing to be done here.
+}
+
+void EnableTerminationOnHeapCorruption() {
+ // Nothing to be done here.
+}
+
+bool UncheckedMalloc(size_t size, void** result) {
+ *result = SbMemoryAllocate(size);
+ return *result != nullptr;
+}
+
+} // namespace base
diff --git a/src/base/process/memory_stubs.cc b/src/base/process/memory_stubs.cc
new file mode 100644
index 0000000..56291ee
--- /dev/null
+++ b/src/base/process/memory_stubs.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory.h"
+
+#include <stdlib.h>
+
+#include "starboard/memory.h"
+#include "starboard/types.h"
+
+namespace base {
+
+void EnableTerminationOnOutOfMemory() {
+}
+
+void EnableTerminationOnHeapCorruption() {
+}
+
+bool AdjustOOMScore(ProcessId process, int score) {
+ return false;
+}
+
+void TerminateBecauseOutOfMemory(size_t size) {
+ abort();
+}
+
+// UncheckedMalloc and Calloc exist so that platforms making use of
+// EnableTerminationOnOutOfMemory have a way to allocate memory without
+// crashing. This _stubs.cc file is for platforms that do not support
+// EnableTerminationOnOutOfMemory (note the empty implementation above). As
+// such, these two Unchecked.alloc functions need only trivially pass-through to
+// their respective stdlib function since those functions will return null on a
+// failure to allocate.
+
+bool UncheckedMalloc(size_t size, void** result) {
+ *result = SbMemoryAllocate(size);
+ return *result != nullptr;
+}
+
+bool UncheckedCalloc(size_t num_items, size_t size, void** result) {
+ *result = calloc(num_items, size);
+ return *result != nullptr;
+}
+
+} // namespace base
diff --git a/src/base/process/memory_unittest.cc b/src/base/process/memory_unittest.cc
new file mode 100644
index 0000000..7c34d73
--- /dev/null
+++ b/src/base/process/memory_unittest.cc
@@ -0,0 +1,536 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include "base/process/memory.h"
+
+#include <limits>
+
+#include "base/allocator/allocator_check.h"
+#include "base/allocator/buildflags.h"
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/memory/aligned_memory.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#if defined(OS_POSIX)
+#include <errno.h>
+#endif
+#if defined(OS_MACOSX)
+#include <malloc/malloc.h>
+#include "base/allocator/allocator_interception_mac.h"
+#include "base/allocator/allocator_shim.h"
+#include "base/process/memory_unittest_mac.h"
+#endif
+#if defined(OS_LINUX)
+#include <malloc.h>
+#include "base/test/malloc_wrapper.h"
+#include "starboard/memory.h"
+#include "starboard/types.h"
+#endif
+
+#if defined(OS_WIN)
+
+#if defined(COMPILER_MSVC)
+// ssize_t needed for OutOfMemoryTest.
+#if defined(_WIN64)
+typedef __int64 ssize_t;
+#else
+typedef long ssize_t;
+#endif
+#endif
+
+// HeapQueryInformation function pointer.
+typedef BOOL (WINAPI* HeapQueryFn) \
+ (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T);
+
+#endif // defined(OS_WIN)
+
+#if defined(OS_MACOSX)
+
+// For the following Mac tests:
+// Note that base::EnableTerminationOnHeapCorruption() is called as part of
+// test suite setup and does not need to be done again, else mach_override
+// will fail.
+
+TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) {
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+ // Assert that freeing an unallocated pointer will crash the process.
+ char buf[9];
+ asm("" : "=r" (buf)); // Prevent clang from being too smart.
+#if ARCH_CPU_64_BITS
+ // On 64 bit Macs, the malloc system automatically abort()s on heap corruption
+ // but does not output anything.
+ ASSERT_DEATH(SbMemoryDeallocate(buf), "");
+#elif defined(ADDRESS_SANITIZER)
+ // AddressSanitizer replaces malloc() and prints a different error message on
+ // heap corruption.
+ ASSERT_DEATH(SbMemoryDeallocate(buf),
+ "attempting SbMemoryDeallocate on address which "
+ "was not malloc\\(\\)-ed");
+#else
+ ADD_FAILURE() << "This test is not supported in this build configuration.";
+#endif
+
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+}
+
+#endif // defined(OS_MACOSX)
+
+TEST(MemoryTest, AllocatorShimWorking) {
+#if defined(OS_MACOSX)
+#if BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+ base::allocator::InterceptAllocationsMac();
+#endif
+ ASSERT_TRUE(base::allocator::IsAllocatorInitialized());
+
+#if defined(OS_MACOSX)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+}
+
+// OpenBSD does not support these tests. Don't test these on ASan/TSan/MSan
+// configurations: only test the real allocator.
+// Windows only supports these tests with the allocator shim in place.
+#if !defined(OS_OPENBSD) && BUILDFLAG(USE_ALLOCATOR_SHIM) && \
+ !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
+
+namespace {
+#if defined(OS_WIN)
+// Windows raises an exception rather than using LOG(FATAL) in order to make the
+// exit code unique to OOM.
+const char* kOomRegex = "";
+const int kExitCode = base::win::kOomExceptionCode;
+#else
+const char* kOomRegex = "Out of memory";
+const int kExitCode = 1;
+#endif
+} // namespace
+
+class OutOfMemoryTest : public testing::Test {
+ public:
+ OutOfMemoryTest()
+ : value_(nullptr),
+ // Make test size as large as possible minus a few pages so
+ // that alignment or other rounding doesn't make it wrap.
+ test_size_(std::numeric_limits<std::size_t>::max() - 12 * 1024),
+ // A test size that is > 2Gb and will cause the allocators to reject
+ // the allocation due to security restrictions. See crbug.com/169327.
+ insecure_test_size_(std::numeric_limits<int>::max()),
+ signed_test_size_(std::numeric_limits<ssize_t>::max()) {}
+
+ protected:
+ void* value_;
+ size_t test_size_;
+ size_t insecure_test_size_;
+ ssize_t signed_test_size_;
+};
+
+class OutOfMemoryDeathTest : public OutOfMemoryTest {
+ public:
+ void SetUpInDeathAssert() {
+#if defined(OS_MACOSX) && BUILDFLAG(USE_ALLOCATOR_SHIM)
+ base::allocator::InitializeAllocatorShim();
+#endif
+
+ // Must call EnableTerminationOnOutOfMemory() because that is called from
+ // chrome's main function and therefore hasn't been called yet.
+ // Since this call may result in another thread being created and death
+ // tests shouldn't be started in a multithread environment, this call
+ // should be done inside of the ASSERT_DEATH.
+ base::EnableTerminationOnOutOfMemory();
+ }
+
+#if defined(OS_MACOSX)
+ void TearDown() override {
+ base::allocator::UninterceptMallocZonesForTesting();
+ }
+#endif
+};
+
+#if !defined(STARBOARD)
+TEST_F(OutOfMemoryDeathTest, New) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = operator new(test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, NewArray) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = new char[test_size_];
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Malloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = SbMemoryAllocate(test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Realloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = SbMemoryReallocate(nullptr, test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, Calloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = calloc(1024, test_size_ / 1024L);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, AlignedAlloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = base::AlignedAlloc(test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+#endif // !defined(STARBOARD)
+
+// POSIX does not define an aligned realloc function.
+#if defined(OS_WIN)
+TEST_F(OutOfMemoryDeathTest, AlignedRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = _aligned_realloc(NULL, test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+namespace {
+
+constexpr uint32_t kUnhandledExceptionExitCode = 0xBADA55;
+
+// This unhandled exception filter exits the process with an exit code distinct
+// from the exception code. This is to verify that the out of memory new handler
+// causes an unhandled exception.
+LONG WINAPI ExitingUnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
+ _exit(kUnhandledExceptionExitCode);
+}
+
+} // namespace
+
+TEST_F(OutOfMemoryDeathTest, NewHandlerGeneratesUnhandledException) {
+ ASSERT_EXIT(
+ {
+ SetUpInDeathAssert();
+ SetUnhandledExceptionFilter(&ExitingUnhandledExceptionFilter);
+ value_ = new char[test_size_];
+ },
+ testing::ExitedWithCode(kUnhandledExceptionExitCode), kOomRegex);
+}
+#endif // defined(OS_WIN)
+
+// OS X and Android have no 2Gb allocation limit.
+// See https://crbug.com/169327.
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(STARBOARD)
+TEST_F(OutOfMemoryDeathTest, SecurityNew) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = operator new(insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityNewArray) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = new char[insecure_test_size_];
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityMalloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = SbMemoryAllocate(insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = SbMemoryReallocate(nullptr, insecure_test_size_);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityCalloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = calloc(1024, insecure_test_size_ / 1024L);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityAlignedAlloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = base::AlignedAlloc(insecure_test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+
+// POSIX does not define an aligned realloc function.
+#if defined(OS_WIN)
+TEST_F(OutOfMemoryDeathTest, SecurityAlignedRealloc) {
+ ASSERT_EXIT({
+ SetUpInDeathAssert();
+ value_ = _aligned_realloc(NULL, insecure_test_size_, 8);
+ }, testing::ExitedWithCode(kExitCode), kOomRegex);
+}
+#endif // defined(OS_WIN)
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID) && !defined(STARBOARD)
+
+#if defined(OS_LINUX)
+
+TEST_F(OutOfMemoryDeathTest, Valloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = valloc(test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityValloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = valloc(insecure_test_size_);
+ }, kOomRegex);
+}
+
+#if PVALLOC_AVAILABLE == 1
+TEST_F(OutOfMemoryDeathTest, Pvalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = pvalloc(test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, SecurityPvalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = pvalloc(insecure_test_size_);
+ }, kOomRegex);
+}
+#endif // PVALLOC_AVAILABLE == 1
+
+TEST_F(OutOfMemoryDeathTest, Memalign) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = memalign(4, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) {
+ // This tests that the run-time symbol resolution is overriding malloc for
+ // shared libraries as well as for our code.
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = MallocWrapper(test_size_);
+ }, kOomRegex);
+}
+#endif // OS_LINUX
+
+// Android doesn't implement posix_memalign().
+#if defined(OS_POSIX) && !defined(OS_ANDROID)
+TEST_F(OutOfMemoryDeathTest, Posix_memalign) {
+ // Grab the return value of posix_memalign to silence a compiler warning
+ // about unused return values. We don't actually care about the return
+ // value, since we're asserting death.
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_));
+ }, kOomRegex);
+}
+#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
+
+#if defined(OS_MACOSX)
+
+// Purgeable zone tests
+
+TEST_F(OutOfMemoryDeathTest, MallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_malloc(zone, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_realloc(zone, NULL, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, VallocPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_valloc(zone, test_size_);
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) {
+ malloc_zone_t* zone = malloc_default_purgeable_zone();
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ value_ = malloc_zone_memalign(zone, 8, test_size_);
+ }, kOomRegex);
+}
+
+// Since these allocation functions take a signed size, it's possible that
+// calling them just once won't be enough to exhaust memory. In the 32-bit
+// environment, it's likely that these allocation attempts will fail because
+// not enough contiguous address space is available. In the 64-bit environment,
+// it's likely that they'll fail because they would require a preposterous
+// amount of (virtual) memory.
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ =
+ base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {}
+ }, kOomRegex);
+}
+
+#if !defined(ARCH_CPU_64_BITS)
+
+// See process_util_unittest_mac.mm for an explanation of why this test isn't
+// run in the 64-bit environment.
+
+TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) {
+ ASSERT_DEATH({
+ SetUpInDeathAssert();
+ while ((value_ = base::AllocatePsychoticallyBigObjCObject())) {}
+ }, kOomRegex);
+}
+
+#endif // !ARCH_CPU_64_BITS
+#endif // OS_MACOSX
+
+class OutOfMemoryHandledTest : public OutOfMemoryTest {
+ public:
+ static const size_t kSafeMallocSize = 512;
+ static const size_t kSafeCallocSize = 128;
+ static const size_t kSafeCallocItems = 4;
+
+ void SetUp() override {
+ OutOfMemoryTest::SetUp();
+
+ // We enable termination on OOM - just as Chrome does at early
+ // initialization - and test that UncheckedMalloc and UncheckedCalloc
+ // properly by-pass this in order to allow the caller to handle OOM.
+ base::EnableTerminationOnOutOfMemory();
+ }
+
+ void TearDown() override {
+#if defined(OS_MACOSX)
+ base::allocator::UninterceptMallocZonesForTesting();
+#endif
+ }
+};
+
+#if defined(OS_WIN)
+
+namespace {
+
+DWORD HandleOutOfMemoryException(EXCEPTION_POINTERS* exception_ptrs,
+ size_t expected_size) {
+ EXPECT_EQ(base::win::kOomExceptionCode,
+ exception_ptrs->ExceptionRecord->ExceptionCode);
+ EXPECT_LE(1U, exception_ptrs->ExceptionRecord->NumberParameters);
+ EXPECT_EQ(expected_size,
+ exception_ptrs->ExceptionRecord->ExceptionInformation[0]);
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+} // namespace
+
+TEST_F(OutOfMemoryTest, TerminateBecauseOutOfMemoryReportsAllocSize) {
+// On Windows, TerminateBecauseOutOfMemory reports the attempted allocation
+// size in the exception raised.
+#if defined(ARCH_CPU_64_BITS)
+ // Test with a size larger than 32 bits on 64 bit machines.
+ const size_t kAttemptedAllocationSize = 0xBADA55F00DULL;
+#else
+ const size_t kAttemptedAllocationSize = 0xBADA55;
+#endif
+
+ __try {
+ base::TerminateBecauseOutOfMemory(kAttemptedAllocationSize);
+ } __except (HandleOutOfMemoryException(GetExceptionInformation(),
+ kAttemptedAllocationSize)) {
+ }
+}
+#endif // OS_WIN
+
+// TODO(b.kelemen): make UncheckedMalloc and UncheckedCalloc work
+// on Windows as well.
+TEST_F(OutOfMemoryHandledTest, UncheckedMalloc) {
+ EXPECT_TRUE(base::UncheckedMalloc(kSafeMallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ SbMemoryDeallocate(value_);
+
+ EXPECT_FALSE(base::UncheckedMalloc(test_size_, &value_));
+ EXPECT_TRUE(value_ == nullptr);
+}
+
+TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) {
+ EXPECT_TRUE(base::UncheckedCalloc(1, kSafeMallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ const char* bytes = static_cast<const char*>(value_);
+ for (size_t i = 0; i < kSafeMallocSize; ++i)
+ EXPECT_EQ(0, bytes[i]);
+ SbMemoryDeallocate(value_);
+
+ EXPECT_TRUE(
+ base::UncheckedCalloc(kSafeCallocItems, kSafeCallocSize, &value_));
+ EXPECT_TRUE(value_ != nullptr);
+ bytes = static_cast<const char*>(value_);
+ for (size_t i = 0; i < (kSafeCallocItems * kSafeCallocSize); ++i)
+ EXPECT_EQ(0, bytes[i]);
+ SbMemoryDeallocate(value_);
+
+ EXPECT_FALSE(base::UncheckedCalloc(1, test_size_, &value_));
+ EXPECT_TRUE(value_ == nullptr);
+}
+#endif // !defined(OS_OPENBSD) && BUILDFLAG(ENABLE_WIN_ALLOCATOR_SHIM_TESTS) &&
+ // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
diff --git a/src/base/process/memory_unittest_mac.h b/src/base/process/memory_unittest_mac.h
new file mode 100644
index 0000000..0377e8c
--- /dev/null
+++ b/src/base/process/memory_unittest_mac.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains helpers for the process_util_unittest to allow it to fully
+// test the Mac code.
+
+#ifndef BASE_PROCESS_MEMORY_UNITTEST_MAC_H_
+#define BASE_PROCESS_MEMORY_UNITTEST_MAC_H_
+
+#include <sys/types.h>
+
+#include "build/build_config.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// Allocates memory via system allocators. Alas, they take a _signed_ size for
+// allocation.
+void* AllocateViaCFAllocatorSystemDefault(ssize_t size);
+void* AllocateViaCFAllocatorMalloc(ssize_t size);
+void* AllocateViaCFAllocatorMallocZone(ssize_t size);
+
+#if !defined(ARCH_CPU_64_BITS)
+// See process_util_unittest_mac.mm for an explanation of why this function
+// isn't implemented for the 64-bit environment.
+
+// Allocates a huge Objective C object.
+void* AllocatePsychoticallyBigObjCObject();
+
+#endif // !ARCH_CPU_64_BITS
+
+} // namespace base
+
+#endif // BASE_PROCESS_MEMORY_UNITTEST_MAC_H_
diff --git a/src/base/process/memory_unittest_mac.mm b/src/base/process/memory_unittest_mac.mm
new file mode 100644
index 0000000..26fe1af
--- /dev/null
+++ b/src/base/process/memory_unittest_mac.mm
@@ -0,0 +1,60 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory_unittest_mac.h"
+#include "build/build_config.h"
+
+#import <Foundation/Foundation.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#if !defined(ARCH_CPU_64_BITS)
+
+// In the 64-bit environment, the Objective-C 2.0 Runtime Reference states
+// that sizeof(anInstance) is constrained to 32 bits. That's not necessarily
+// "psychotically big" and in fact a 64-bit program is expected to be able to
+// successfully allocate an object that large, likely reserving a good deal of
+// swap space. The only way to test the behavior of memory exhaustion for
+// Objective-C allocation in this environment would be to loop over allocation
+// of these large objects, but that would slowly consume all available memory
+// and cause swap file proliferation. That's bad, so this behavior isn't
+// tested in the 64-bit environment.
+
+@interface PsychoticallyBigObjCObject : NSObject
+{
+ // In the 32-bit environment, the compiler limits Objective-C objects to
+ // < 2GB in size.
+ int justUnder2Gigs_[(2U * 1024 * 1024 * 1024 - 1) / sizeof(int)];
+}
+
+@end
+
+@implementation PsychoticallyBigObjCObject
+
+@end
+
+namespace base {
+
+void* AllocatePsychoticallyBigObjCObject() {
+ return [[PsychoticallyBigObjCObject alloc] init];
+}
+
+} // namespace base
+
+#endif // ARCH_CPU_64_BITS
+
+namespace base {
+
+void* AllocateViaCFAllocatorSystemDefault(ssize_t size) {
+ return CFAllocatorAllocate(kCFAllocatorSystemDefault, size, 0);
+}
+
+void* AllocateViaCFAllocatorMalloc(ssize_t size) {
+ return CFAllocatorAllocate(kCFAllocatorMalloc, size, 0);
+}
+
+void* AllocateViaCFAllocatorMallocZone(ssize_t size) {
+ return CFAllocatorAllocate(kCFAllocatorMallocZone, size, 0);
+}
+
+} // namespace base
diff --git a/src/base/process/memory_win.cc b/src/base/process/memory_win.cc
new file mode 100644
index 0000000..49f3866
--- /dev/null
+++ b/src/base/process/memory_win.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/memory.h"
+
+#include <windows.h> // Must be in front of other Windows header files.
+
+#include <new.h>
+#include <psapi.h>
+
+#include "starboard/types.h"
+
+#if defined(__clang__)
+// This global constructor is trivial and non-racy (per being const).
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wglobal-constructors"
+#endif
+
+// malloc_unchecked is required to implement UncheckedMalloc properly.
+// It's provided by allocator_shim_win.cc but since that's not always present,
+// we provide a default that falls back to regular malloc.
+typedef void* (*MallocFn)(size_t);
+extern "C" void* (*const malloc_unchecked)(size_t);
+extern "C" void* (*const malloc_default)(size_t) = &malloc;
+
+#if defined(__clang__)
+#pragma clang diagnostic pop // -Wglobal-constructors
+#endif
+
+#if defined(_M_IX86)
+#pragma comment(linker, "/alternatename:_malloc_unchecked=_malloc_default")
+#elif defined(_M_X64) || defined(_M_ARM)
+#pragma comment(linker, "/alternatename:malloc_unchecked=malloc_default")
+#else
+#error Unsupported platform
+#endif
+
+namespace base {
+
+namespace {
+
+#pragma warning(push)
+#pragma warning(disable: 4702) // Unreachable code after the _exit.
+
+NOINLINE int OnNoMemory(size_t size) {
+ // Kill the process. This is important for security since most of code
+ // does not check the result of memory allocation.
+ // https://msdn.microsoft.com/en-us/library/het71c37.aspx
+ // Pass the size of the failed request in an exception argument.
+ ULONG_PTR exception_args[] = {size};
+ ::RaiseException(win::kOomExceptionCode, EXCEPTION_NONCONTINUABLE,
+ arraysize(exception_args), exception_args);
+
+ // Safety check, make sure process exits here.
+ _exit(win::kOomExceptionCode);
+ return 0;
+}
+
+#pragma warning(pop)
+
+} // namespace
+
+void TerminateBecauseOutOfMemory(size_t size) {
+ OnNoMemory(size);
+}
+
+void EnableTerminationOnHeapCorruption() {
+ // Ignore the result code. Supported on XP SP3 and Vista.
+ HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
+}
+
+void EnableTerminationOnOutOfMemory() {
+ _set_new_handler(&OnNoMemory);
+ _set_new_mode(1);
+}
+
+// Implemented using a weak symbol.
+bool UncheckedMalloc(size_t size, void** result) {
+ *result = malloc_unchecked(size);
+ return *result != NULL;
+}
+
+} // namespace base
diff --git a/src/base/process/port_provider_mac.cc b/src/base/process/port_provider_mac.cc
new file mode 100644
index 0000000..23d214c
--- /dev/null
+++ b/src/base/process/port_provider_mac.cc
@@ -0,0 +1,28 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/port_provider_mac.h"
+
+namespace base {
+
+PortProvider::PortProvider() : lock_(), observer_list_() {}
+PortProvider::~PortProvider() {}
+
+void PortProvider::AddObserver(Observer* observer) {
+ base::AutoLock l(lock_);
+ observer_list_.AddObserver(observer);
+}
+
+void PortProvider::RemoveObserver(Observer* observer) {
+ base::AutoLock l(lock_);
+ observer_list_.RemoveObserver(observer);
+}
+
+void PortProvider::NotifyObservers(ProcessHandle process) {
+ base::AutoLock l(lock_);
+ for (auto& observer : observer_list_)
+ observer.OnReceivedTaskPort(process);
+}
+
+} // namespace base
diff --git a/src/base/process/port_provider_mac.h b/src/base/process/port_provider_mac.h
new file mode 100644
index 0000000..9352249
--- /dev/null
+++ b/src/base/process/port_provider_mac.h
@@ -0,0 +1,62 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROCESS_PORT_PROVIDER_MAC_H_
+#define BASE_PROCESS_PORT_PROVIDER_MAC_H_
+
+#include <mach/mach.h>
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/process/process_handle.h"
+#include "base/synchronization/lock.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// Abstract base class that provides a mapping from ProcessHandle (pid_t) to the
+// Mach task port. This replicates task_for_pid(), which requires root
+// privileges.
+class BASE_EXPORT PortProvider {
+ public:
+ PortProvider();
+ virtual ~PortProvider();
+
+ class Observer {
+ public:
+ virtual ~Observer() {};
+ // Called by the PortProvider to notify observers that the task port was
+ // received for a given process.
+ // No guarantees are made about the thread on which this notification will
+ // be sent.
+ // Observers must not call AddObserver() or RemoveObserver() in this
+ // callback, as doing so will deadlock.
+ virtual void OnReceivedTaskPort(ProcessHandle process) = 0;
+ };
+
+ // Returns the mach task port for |process| if possible, or else
+ // |MACH_PORT_NULL|.
+ virtual mach_port_t TaskForPid(ProcessHandle process) const = 0;
+
+ // Observer interface.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ protected:
+ // Called by subclasses to send a notification to observers.
+ void NotifyObservers(ProcessHandle process);
+
+ private:
+ // ObserverList is not thread-safe, so |lock_| ensures consistency of
+ // |observer_list_|.
+ base::Lock lock_;
+ base::ObserverList<Observer>::Unchecked observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(PortProvider);
+};
+
+} // namespace base
+
+#endif // BASE_PROCESS_PORT_PROVIDER_MAC_H_
diff --git a/src/base/process/process.h b/src/base/process/process.h
new file mode 100644
index 0000000..5872805
--- /dev/null
+++ b/src/base/process/process.h
@@ -0,0 +1,216 @@
+// Copyright 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROCESS_PROCESS_H_
+#define BASE_PROCESS_PROCESS_H_
+
+#include "base/base_export.h"
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <lib/zx/process.h>
+#endif
+
+#if defined(OS_MACOSX)
+#include "base/feature_list.h"
+#include "base/process/port_provider_mac.h"
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+#if defined(OS_MACOSX)
+extern const Feature kMacAllowBackgroundingProcesses;
+#endif
+
+// Provides a move-only encapsulation of a process.
+//
+// This object is not tied to the lifetime of the underlying process: the
+// process may be killed and this object may still around, and it will still
+// claim to be valid. The actual behavior in that case is OS dependent like so:
+//
+// Windows: The underlying ProcessHandle will be valid after the process dies
+// and can be used to gather some information about that process, but most
+// methods will obviously fail.
+//
+// POSIX: The underlying ProcessHandle is not guaranteed to remain valid after
+// the process dies, and it may be reused by the system, which means that it may
+// end up pointing to the wrong process.
+class BASE_EXPORT Process {
+ public:
+ // On Windows, this takes ownership of |handle|. On POSIX, this does not take
+ // ownership of |handle|.
+ explicit Process(ProcessHandle handle = kNullProcessHandle);
+
+ Process(Process&& other);
+
+ // The destructor does not terminate the process.
+ ~Process();
+
+ Process& operator=(Process&& other);
+
+ // Returns an object for the current process.
+ static Process Current();
+
+ // Returns a Process for the given |pid|.
+ static Process Open(ProcessId pid);
+
+ // Returns a Process for the given |pid|. On Windows the handle is opened
+ // with more access rights and must only be used by trusted code (can read the
+ // address space and duplicate handles).
+ static Process OpenWithExtraPrivileges(ProcessId pid);
+
+#if defined(OS_WIN)
+ // Returns a Process for the given |pid|, using some |desired_access|.
+ // See ::OpenProcess documentation for valid |desired_access|.
+ static Process OpenWithAccess(ProcessId pid, DWORD desired_access);
+#endif
+
+ // Creates an object from a |handle| owned by someone else.
+ // Don't use this for new code. It is only intended to ease the migration to
+ // a strict ownership model.
+ // TODO(rvargas) crbug.com/417532: Remove this code.
+ static Process DeprecatedGetProcessFromHandle(ProcessHandle handle);
+
+ // Returns true if processes can be backgrounded.
+ static bool CanBackgroundProcesses();
+
+#ifndef STARBOARD
+ // Terminates the current process immediately with |exit_code|.
+ [[noreturn]] static void TerminateCurrentProcessImmediately(int exit_code);
+#endif
+
+ // Returns true if this objects represents a valid process.
+ bool IsValid() const;
+
+ // Returns a handle for this process. There is no guarantee about when that
+ // handle becomes invalid because this object retains ownership.
+ ProcessHandle Handle() const;
+
+ // Returns a second object that represents this process.
+ Process Duplicate() const;
+
+ // Get the PID for this process.
+ ProcessId Pid() const;
+
+ // Returns true if this process is the current process.
+ bool is_current() const;
+
+ // Close the process handle. This will not terminate the process.
+ void Close();
+
+ // Returns true if this process is still running. This is only safe on Windows
+ // (and maybe Fuchsia?), because the ProcessHandle will keep the zombie
+ // process information available until itself has been released. But on Posix,
+ // the OS may reuse the ProcessId.
+#if defined(OS_WIN)
+ bool IsRunning() const {
+ return !WaitForExitWithTimeout(base::TimeDelta(), nullptr);
+ }
+#endif
+
+ // Terminates the process with extreme prejudice. The given |exit_code| will
+ // be the exit code of the process. If |wait| is true, this method will wait
+ // for up to one minute for the process to actually terminate.
+ // Returns true if the process terminates within the allowed time.
+ // NOTE: On POSIX |exit_code| is ignored.
+ bool Terminate(int exit_code, bool wait) const;
+
+ // Waits for the process to exit. Returns true on success.
+ // On POSIX, if the process has been signaled then |exit_code| is set to -1.
+ // On Linux this must be a child process, however on Mac and Windows it can be
+ // any process.
+ // NOTE: |exit_code| is optional, nullptr can be passed if the exit code is
+ // not required.
+ bool WaitForExit(int* exit_code) const;
+
+ // Same as WaitForExit() but only waits for up to |timeout|.
+ // NOTE: |exit_code| is optional, nullptr can be passed if the exit code
+ // is not required.
+ bool WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const;
+
+ // Indicates that the process has exited with the specified |exit_code|.
+ // This should be called if process exit is observed outside of this class.
+ // (i.e. Not because Terminate or WaitForExit, above, was called.)
+ // Note that nothing prevents this being called multiple times for a dead
+ // process though that should be avoided.
+ void Exited(int exit_code) const;
+
+#if defined(OS_MACOSX)
+ // The Mac needs a Mach port in order to manipulate a process's priority,
+ // and there's no good way to get that from base given the pid. These Mac
+ // variants of the IsProcessBackgrounded and SetProcessBackgrounded API take
+ // a port provider for this reason. See crbug.com/460102
+ //
+ // A process is backgrounded when its task priority is
+ // |TASK_BACKGROUND_APPLICATION|.
+ //
+ // Returns true if the port_provider can locate a task port for the process
+ // and it is backgrounded. If port_provider is null, returns false.
+ bool IsProcessBackgrounded(PortProvider* port_provider) const;
+
+ // Set the process as backgrounded. If value is
+ // true, the priority of the associated task will be set to
+ // TASK_BACKGROUND_APPLICATION. If value is false, the
+ // priority of the process will be set to TASK_FOREGROUND_APPLICATION.
+ //
+ // Returns true if the priority was changed, false otherwise. If
+ // |port_provider| is null, this is a no-op and it returns false.
+ bool SetProcessBackgrounded(PortProvider* port_provider, bool value);
+#else
+ // A process is backgrounded when it's priority is lower than normal.
+ // Return true if this process is backgrounded, false otherwise.
+ bool IsProcessBackgrounded() const;
+
+ // Set a process as backgrounded. If value is true, the priority of the
+ // process will be lowered. If value is false, the priority of the process
+ // will be made "normal" - equivalent to default process priority.
+ // Returns true if the priority was changed, false otherwise.
+ bool SetProcessBackgrounded(bool value);
+#endif // defined(OS_MACOSX)
+ // Returns an integer representing the priority of a process. The meaning
+ // of this value is OS dependent.
+ int GetPriority() const;
+
+#if defined(OS_CHROMEOS)
+ // Get the PID in its PID namespace.
+ // If the process is not in a PID namespace or /proc/<pid>/status does not
+ // report NSpid, kNullProcessId is returned.
+ ProcessId GetPidInNamespace() const;
+#endif
+
+ private:
+#if defined(OS_WIN)
+ win::ScopedHandle process_;
+#elif defined(OS_FUCHSIA)
+ zx::process process_;
+#else
+ ProcessHandle process_;
+#endif
+
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+ bool is_current_process_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(Process);
+};
+
+#if defined(OS_CHROMEOS)
+// Exposed for testing.
+// Given the contents of the /proc/<pid>/cgroup file, determine whether the
+// process is backgrounded or not.
+BASE_EXPORT bool IsProcessBackgroundedCGroup(
+ const StringPiece& cgroup_contents);
+#endif // defined(OS_CHROMEOS)
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_H_
diff --git a/src/base/process/process_fuchsia.cc b/src/base/process/process_fuchsia.cc
new file mode 100644
index 0000000..11ab124
--- /dev/null
+++ b/src/base/process/process_fuchsia.cc
@@ -0,0 +1,215 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// 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 <lib/zx/process.h>
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+
+#include "base/debug/activity_tracker.h"
+#include "base/fuchsia/default_job.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/clang_coverage.h"
+#include "starboard/types.h"
+
+namespace base {
+
+Process::Process(ProcessHandle handle)
+ : process_(handle), is_current_process_(false) {
+ CHECK_NE(handle, zx_process_self());
+}
+
+Process::~Process() {
+ Close();
+}
+
+Process::Process(Process&& other)
+ : process_(std::move(other.process_)),
+ is_current_process_(other.is_current_process_) {
+ other.is_current_process_ = false;
+}
+
+Process& Process::operator=(Process&& other) {
+ process_ = std::move(other.process_);
+ is_current_process_ = other.is_current_process_;
+ other.is_current_process_ = false;
+ return *this;
+}
+
+// static
+Process Process::Current() {
+ Process process;
+ process.is_current_process_ = true;
+ return process;
+}
+
+// static
+Process Process::Open(ProcessId pid) {
+ if (pid == GetCurrentProcId())
+ return Current();
+
+ // While a process with object id |pid| might exist, the job returned by
+ // zx::job::default_job() might not contain it, so this call can fail.
+ zx::process process;
+ zx_status_t status =
+ GetDefaultJob()->get_child(pid, ZX_RIGHT_SAME_RIGHTS, &process);
+ if (status != ZX_OK) {
+ ZX_DLOG(ERROR, status) << "zx_object_get_child";
+ return Process();
+ }
+ return Process(process.release());
+}
+
+// static
+Process Process::OpenWithExtraPrivileges(ProcessId pid) {
+ // No privileges to set.
+ return Open(pid);
+}
+
+// static
+Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
+ DCHECK_NE(handle, GetCurrentProcessHandle());
+ zx::process out;
+ zx_status_t result =
+ zx::unowned_process(handle)->duplicate(ZX_RIGHT_SAME_RIGHTS, &out);
+ if (result != ZX_OK) {
+ ZX_DLOG(ERROR, result) << "zx_handle_duplicate(from_handle)";
+ return Process();
+ }
+
+ return Process(out.release());
+}
+
+// static
+bool Process::CanBackgroundProcesses() {
+ return false;
+}
+
+// static
+void Process::TerminateCurrentProcessImmediately(int exit_code) {
+#if defined(CLANG_COVERAGE)
+ WriteClangCoverageProfile();
+#endif
+ _exit(exit_code);
+}
+
+bool Process::IsValid() const {
+ return process_.is_valid() || is_current();
+}
+
+ProcessHandle Process::Handle() const {
+ return is_current_process_ ? zx_process_self() : process_.get();
+}
+
+Process Process::Duplicate() const {
+ if (is_current())
+ return Current();
+
+ if (!IsValid())
+ return Process();
+
+ zx::process out;
+ zx_status_t result = process_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out);
+ if (result != ZX_OK) {
+ ZX_DLOG(ERROR, result) << "zx_handle_duplicate";
+ return Process();
+ }
+
+ return Process(out.release());
+}
+
+ProcessId Process::Pid() const {
+ DCHECK(IsValid());
+ return GetProcId(Handle());
+}
+
+bool Process::is_current() const {
+ return is_current_process_;
+}
+
+void Process::Close() {
+ is_current_process_ = false;
+ process_.reset();
+}
+
+bool Process::Terminate(int exit_code, bool wait) const {
+ // exit_code isn't supportable. https://crbug.com/753490.
+ zx_status_t status = zx_task_kill(Handle());
+ if (status == ZX_OK && wait) {
+ zx_signals_t signals;
+ status = zx_object_wait_one(Handle(), ZX_TASK_TERMINATED,
+ zx_deadline_after(ZX_SEC(60)), &signals);
+ if (status != ZX_OK) {
+ ZX_DLOG(ERROR, status) << "zx_object_wait_one(terminate)";
+ } else {
+ CHECK(signals & ZX_TASK_TERMINATED);
+ }
+ } else if (status != ZX_OK) {
+ ZX_DLOG(ERROR, status) << "zx_task_kill";
+ }
+
+ return status >= 0;
+}
+
+bool Process::WaitForExit(int* exit_code) const {
+ return WaitForExitWithTimeout(TimeDelta::Max(), exit_code);
+}
+
+bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
+ if (is_current_process_)
+ return false;
+
+ // Record the event that this thread is blocking upon (for hang diagnosis).
+ base::debug::ScopedProcessWaitActivity process_activity(this);
+
+ zx_time_t deadline = timeout == TimeDelta::Max()
+ ? ZX_TIME_INFINITE
+ : (TimeTicks::Now() + timeout).ToZxTime();
+ zx_signals_t signals_observed = 0;
+ zx_status_t status = zx_object_wait_one(process_.get(), ZX_TASK_TERMINATED,
+ deadline, &signals_observed);
+ if (status != ZX_OK) {
+ ZX_DLOG(ERROR, status) << "zx_object_wait_one";
+ return false;
+ }
+
+ zx_info_process_t proc_info;
+ status = zx_object_get_info(process_.get(), ZX_INFO_PROCESS, &proc_info,
+ sizeof(proc_info), nullptr, nullptr);
+ if (status != ZX_OK) {
+ ZX_DLOG(ERROR, status) << "zx_object_get_info";
+ if (exit_code)
+ *exit_code = -1;
+ return false;
+ }
+
+ if (exit_code)
+ *exit_code = proc_info.return_code;
+
+ return true;
+}
+
+void Process::Exited(int exit_code) const {}
+
+bool Process::IsProcessBackgrounded() const {
+ // See SetProcessBackgrounded().
+ DCHECK(IsValid());
+ return false;
+}
+
+bool Process::SetProcessBackgrounded(bool value) {
+ // No process priorities on Fuchsia. TODO(fuchsia): See MG-783, and update
+ // this later if priorities are implemented.
+ return false;
+}
+
+int Process::GetPriority() const {
+ DCHECK(IsValid());
+ // No process priorities on Fuchsia.
+ return 0;
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle.cc b/src/base/process/process_handle.cc
new file mode 100644
index 0000000..661b94b
--- /dev/null
+++ b/src/base/process/process_handle.cc
@@ -0,0 +1,55 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+bool g_have_unique_id = false;
+uint32_t g_unique_id;
+
+// The process which set |g_unique_id|.
+ProcessId g_procid;
+
+// Mangle IDs so that they are not accidentally used as PIDs, e.g. as an
+// argument to kill or waitpid.
+uint32_t MangleProcessId(ProcessId process_id) {
+ // Add a large power of 10 so that the pid is still the pid is still readable
+ // inside the mangled id.
+ return static_cast<uint32_t>(process_id) + 1000000000U;
+}
+
+} // namespace
+
+uint32_t GetUniqueIdForProcess() {
+#if defined(STARBOARD)
+ return 0;
+#else
+ if (!g_have_unique_id) {
+ return MangleProcessId(GetCurrentProcId());
+ }
+
+ // Make sure we are the same process that set |g_procid|. This check may have
+ // false negatives (if a process ID was reused) but should have no false
+ // positives.
+ DCHECK_EQ(GetCurrentProcId(), g_procid);
+ return g_unique_id;
+#endif // !defined(STARBOARD)
+}
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+
+void InitUniqueIdForProcessInPidNamespace(ProcessId pid_outside_of_namespace) {
+ g_unique_id = MangleProcessId(pid_outside_of_namespace);
+ g_procid = GetCurrentProcId();
+ g_have_unique_id = true;
+}
+
+#endif
+
+} // namespace base
diff --git a/src/base/process/process_handle.h b/src/base/process/process_handle.h
new file mode 100644
index 0000000..7befc31
--- /dev/null
+++ b/src/base/process/process_handle.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROCESS_PROCESS_HANDLE_H_
+#define BASE_PROCESS_PROCESS_HANDLE_H_
+
+#include <sys/types.h>
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_types.h"
+#endif
+
+#if defined(OS_FUCHSIA)
+#include <zircon/types.h>
+
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+// ProcessHandle is a platform specific type which represents the underlying OS
+// handle to a process.
+// ProcessId is a number which identifies the process in the OS.
+#if defined(STARBOARD)
+typedef uint32_t ProcessHandle;
+typedef uint32_t ProcessId;
+const ProcessHandle kNullProcessHandle = 0;
+const ProcessId kNullProcessId = 0;
+#define CrPRIdPid "d"
+#else
+#if defined(OS_WIN)
+typedef HANDLE ProcessHandle;
+typedef DWORD ProcessId;
+typedef HANDLE UserTokenHandle;
+const ProcessHandle kNullProcessHandle = NULL;
+const ProcessId kNullProcessId = 0;
+#elif defined(OS_FUCHSIA)
+typedef zx_handle_t ProcessHandle;
+typedef zx_koid_t ProcessId;
+const ProcessHandle kNullProcessHandle = ZX_HANDLE_INVALID;
+const ProcessId kNullProcessId = ZX_KOID_INVALID;
+#elif defined(OS_POSIX)
+// On POSIX, our ProcessHandle will just be the PID.
+typedef pid_t ProcessHandle;
+typedef pid_t ProcessId;
+const ProcessHandle kNullProcessHandle = 0;
+const ProcessId kNullProcessId = 0;
+#endif // defined(OS_WIN)
+
+// To print ProcessIds portably use CrPRIdPid (based on PRIuS and friends from
+// C99 and format_macros.h) like this:
+// base::StringPrintf("PID is %" CrPRIdPid ".\n", pid);
+#if defined(OS_WIN) || defined(OS_FUCHSIA)
+#define CrPRIdPid "ld"
+#else
+#define CrPRIdPid "d"
+#endif
+#endif // defined(STARBOARD)
+
+// Returns the id of the current process.
+// Note that on some platforms, this is not guaranteed to be unique across
+// processes (use GetUniqueIdForProcess if uniqueness is required).
+BASE_EXPORT ProcessId GetCurrentProcId();
+
+// Returns a unique ID for the current process. The ID will be unique across all
+// currently running processes within the chrome session, but IDs of terminated
+// processes may be reused. This returns an opaque value that is different from
+// a process's PID.
+BASE_EXPORT uint32_t GetUniqueIdForProcess();
+
+#if defined(OS_LINUX)
+// When a process is started in a different PID namespace from the browser
+// process, this function must be called with the process's PID in the browser's
+// PID namespace in order to initialize its unique ID. Not thread safe.
+// WARNING: To avoid inconsistent results from GetUniqueIdForProcess, this
+// should only be called very early after process startup - ideally as soon
+// after process creation as possible.
+BASE_EXPORT void InitUniqueIdForProcessInPidNamespace(
+ ProcessId pid_outside_of_namespace);
+#endif
+
+// Returns the ProcessHandle of the current process.
+BASE_EXPORT ProcessHandle GetCurrentProcessHandle();
+
+// Returns the process ID for the specified process. This is functionally the
+// same as Windows' GetProcessId(), but works on versions of Windows before Win
+// XP SP1 as well.
+// DEPRECATED. New code should be using Process::Pid() instead.
+// Note that on some platforms, this is not guaranteed to be unique across
+// processes.
+BASE_EXPORT ProcessId GetProcId(ProcessHandle process);
+
+#if !defined(OS_FUCHSIA)
+// Returns the ID for the parent of the given process. Not available on Fuchsia.
+// Returning a negative value indicates an error, such as if the |process| does
+// not exist. Returns 0 when |process| has no parent process.
+BASE_EXPORT ProcessId GetParentProcessId(ProcessHandle process);
+#endif // !defined(OS_FUCHSIA)
+
+#if defined(OS_POSIX)
+// Returns the path to the executable of the given process.
+BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process);
+#endif
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_HANDLE_H_
diff --git a/src/base/process/process_handle_freebsd.cc b/src/base/process/process_handle_freebsd.cc
new file mode 100644
index 0000000..cce2e01
--- /dev/null
+++ b/src/base/process/process_handle_freebsd.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+
+#include <limits.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <sys/user.h>
+#include <unistd.h>
+
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetParentProcessId(ProcessHandle process) {
+ struct kinfo_proc info;
+ size_t length;
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process };
+
+ if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0)
+ return -1;
+
+ return info.ki_ppid;
+}
+
+FilePath GetProcessExecutablePath(ProcessHandle process) {
+ char pathname[PATH_MAX];
+ size_t length;
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, process };
+
+ length = sizeof(pathname);
+
+ if (sysctl(mib, arraysize(mib), pathname, &length, NULL, 0) < 0 ||
+ length == 0) {
+ return FilePath();
+ }
+
+ return FilePath(std::string(pathname));
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_fuchsia.cc b/src/base/process/process_handle_fuchsia.cc
new file mode 100644
index 0000000..e6d4775
--- /dev/null
+++ b/src/base/process/process_handle_fuchsia.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+
+#include <zircon/process.h>
+#include <zircon/status.h>
+#include <zircon/syscalls.h>
+
+#include "base/logging.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetCurrentProcId() {
+ return GetProcId(GetCurrentProcessHandle());
+}
+
+ProcessHandle GetCurrentProcessHandle() {
+ // Note that zx_process_self() returns a real handle, and ownership is not
+ // transferred to the caller (i.e. this should never be closed).
+ return zx_process_self();
+}
+
+ProcessId GetProcId(ProcessHandle process) {
+ zx_info_handle_basic_t basic;
+ zx_status_t status = zx_object_get_info(process, ZX_INFO_HANDLE_BASIC, &basic,
+ sizeof(basic), nullptr, nullptr);
+ if (status != ZX_OK) {
+ DLOG(ERROR) << "zx_object_get_info failed: "
+ << zx_status_get_string(status);
+ return ZX_KOID_INVALID;
+ }
+ return basic.koid;
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_linux.cc b/src/base/process/process_handle_linux.cc
new file mode 100644
index 0000000..f921b42
--- /dev/null
+++ b/src/base/process/process_handle_linux.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+
+#include "base/files/file_util.h"
+#include "base/process/internal_linux.h"
+#if defined(OS_AIX)
+#include "base/process/internal_aix.h"
+#endif
+
+namespace base {
+
+ProcessId GetParentProcessId(ProcessHandle process) {
+ ProcessId pid =
+#if defined(OS_AIX)
+ internalAIX::ReadProcStatsAndGetFieldAsInt64(process,
+ internalAIX::VM_PPID);
+#else
+ internal::ReadProcStatsAndGetFieldAsInt64(process, internal::VM_PPID);
+#endif
+ // TODO(zijiehe): Returns 0 if |process| does not have a parent process.
+ if (pid)
+ return pid;
+ return -1;
+}
+
+FilePath GetProcessExecutablePath(ProcessHandle process) {
+ FilePath stat_file = internal::GetProcPidDir(process).Append("exe");
+ FilePath exe_name;
+ if (!ReadSymbolicLink(stat_file, &exe_name)) {
+ // No such process. Happens frequently in e.g. TerminateAllChromeProcesses
+ return FilePath();
+ }
+ return exe_name;
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_mac.cc b/src/base/process/process_handle_mac.cc
new file mode 100644
index 0000000..29efc62
--- /dev/null
+++ b/src/base/process/process_handle_mac.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+
+#include <libproc.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+#include "base/logging.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetParentProcessId(ProcessHandle process) {
+ struct kinfo_proc info;
+ size_t length = sizeof(struct kinfo_proc);
+ int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process };
+ if (sysctl(mib, 4, &info, &length, NULL, 0) < 0) {
+ DPLOG(ERROR) << "sysctl";
+ return -1;
+ }
+ if (length == 0)
+ return -1;
+ return info.kp_eproc.e_ppid;
+}
+
+FilePath GetProcessExecutablePath(ProcessHandle process) {
+ char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
+ if (!proc_pidpath(process, pathbuf, sizeof(pathbuf)))
+ return FilePath();
+
+ return FilePath(pathbuf);
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_openbsd.cc b/src/base/process/process_handle_openbsd.cc
new file mode 100644
index 0000000..46048e2
--- /dev/null
+++ b/src/base/process/process_handle_openbsd.cc
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetParentProcessId(ProcessHandle process) {
+ struct kinfo_proc info;
+ size_t length;
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process,
+ sizeof(struct kinfo_proc), 0 };
+
+ if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0)
+ return -1;
+
+ mib[5] = (length / sizeof(struct kinfo_proc));
+
+ if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0)
+ return -1;
+
+ return info.p_ppid;
+}
+
+FilePath GetProcessExecutablePath(ProcessHandle process) {
+ struct kinfo_proc kp;
+ size_t len;
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process,
+ sizeof(struct kinfo_proc), 0 };
+
+ if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) == -1)
+ return FilePath();
+ mib[5] = (len / sizeof(struct kinfo_proc));
+ if (sysctl(mib, arraysize(mib), &kp, &len, NULL, 0) < 0)
+ return FilePath();
+ if ((kp.p_flag & P_SYSTEM) != 0)
+ return FilePath();
+ if (strcmp(kp.p_comm, "chrome") == 0)
+ return FilePath(kp.p_comm);
+
+ return FilePath();
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_posix.cc b/src/base/process/process_handle_posix.cc
new file mode 100644
index 0000000..2c7e908
--- /dev/null
+++ b/src/base/process/process_handle_posix.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+
+#include <unistd.h>
+
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetCurrentProcId() {
+ return getpid();
+}
+
+ProcessHandle GetCurrentProcessHandle() {
+ return GetCurrentProcId();
+}
+
+ProcessId GetProcId(ProcessHandle process) {
+ return process;
+}
+
+} // namespace base
diff --git a/src/base/process/process_handle_win.cc b/src/base/process/process_handle_win.cc
new file mode 100644
index 0000000..f050c0e
--- /dev/null
+++ b/src/base/process/process_handle_win.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_handle.h"
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_version.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessId GetCurrentProcId() {
+ return ::GetCurrentProcessId();
+}
+
+ProcessHandle GetCurrentProcessHandle() {
+ return ::GetCurrentProcess();
+}
+
+ProcessId GetProcId(ProcessHandle process) {
+ // This returns 0 if we have insufficient rights to query the process handle.
+ return GetProcessId(process);
+}
+
+ProcessId GetParentProcessId(ProcessHandle process) {
+ ProcessId child_pid = GetProcId(process);
+ PROCESSENTRY32 process_entry;
+ process_entry.dwSize = sizeof(PROCESSENTRY32);
+
+ win::ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));
+ if (snapshot.IsValid() && Process32First(snapshot.Get(), &process_entry)) {
+ do {
+ if (process_entry.th32ProcessID == child_pid)
+ return process_entry.th32ParentProcessID;
+ } while (Process32Next(snapshot.Get(), &process_entry));
+ }
+
+ // TODO(zijiehe): To match other platforms, -1 (UINT32_MAX) should be returned
+ // if |child_id| cannot be found in the |snapshot|.
+ return 0u;
+}
+
+} // namespace base
diff --git a/src/base/process/process_info.h b/src/base/process/process_info.h
new file mode 100644
index 0000000..f06370e
--- /dev/null
+++ b/src/base/process/process_info.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROCESS_PROCESS_INFO_H_
+#define BASE_PROCESS_PROCESS_INFO_H_
+
+#include "base/base_export.h"
+#include "build/build_config.h"
+
+namespace base {
+
+class Time;
+
+// Vends information about the current process.
+class BASE_EXPORT CurrentProcessInfo {
+ public:
+ // Returns the time at which the process was launched. May be empty if an
+ // error occurred retrieving the information.
+ static const Time CreationTime();
+};
+
+#if defined(OS_WIN)
+enum IntegrityLevel {
+ INTEGRITY_UNKNOWN,
+ UNTRUSTED_INTEGRITY,
+ LOW_INTEGRITY,
+ MEDIUM_INTEGRITY,
+ HIGH_INTEGRITY,
+};
+
+// Returns the integrity level of the process. Returns INTEGRITY_UNKNOWN in the
+// case of an underlying system failure.
+BASE_EXPORT IntegrityLevel GetCurrentProcessIntegrityLevel();
+
+// Determines whether the current process is elevated.
+BASE_EXPORT bool IsCurrentProcessElevated();
+
+#endif // defined(OS_WIN)
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_INFO_H_
diff --git a/src/base/process/process_info_fuchsia.cc b/src/base/process/process_info_fuchsia.cc
new file mode 100644
index 0000000..ced3348
--- /dev/null
+++ b/src/base/process/process_info_fuchsia.cc
@@ -0,0 +1,17 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_info.h"
+
+#include "base/time/time.h"
+
+namespace base {
+
+// static
+const Time CurrentProcessInfo::CreationTime() {
+ // TODO(https://crbug.com/726484): There is no syscall providing this data.
+ return Time();
+}
+
+} // namespace base
diff --git a/src/base/process/process_info_linux.cc b/src/base/process/process_info_linux.cc
new file mode 100644
index 0000000..502fb9c
--- /dev/null
+++ b/src/base/process/process_info_linux.cc
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_info.h"
+
+#include "base/logging.h"
+#include "base/process/internal_linux.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// static
+const Time CurrentProcessInfo::CreationTime() {
+ int64_t start_ticks =
+ internal::ReadProcSelfStatsAndGetFieldAsInt64(internal::VM_STARTTIME);
+ if (!start_ticks)
+ return Time();
+ TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks);
+ Time boot_time = internal::GetBootTime();
+ if (boot_time.is_null())
+ return Time();
+ return Time(boot_time + start_offset);
+}
+
+} // namespace base
diff --git a/src/base/process/process_info_mac.cc b/src/base/process/process_info_mac.cc
new file mode 100644
index 0000000..a1cfa00
--- /dev/null
+++ b/src/base/process/process_info_mac.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_info.h"
+
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/free_deleter.h"
+#include "base/time/time.h"
+#include "starboard/memory.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// static
+const Time CurrentProcessInfo::CreationTime() {
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() };
+ size_t len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0)
+ return Time();
+
+ std::unique_ptr<struct kinfo_proc, base::FreeDeleter> proc(
+ static_cast<struct kinfo_proc*>(SbMemoryAllocate(len)));
+ if (sysctl(mib, arraysize(mib), proc.get(), &len, NULL, 0) < 0)
+ return Time();
+ return Time::FromTimeVal(proc->kp_proc.p_un.__p_starttime);
+}
+
+} // namespace base
diff --git a/src/base/process/process_info_unittest.cc b/src/base/process/process_info_unittest.cc
new file mode 100644
index 0000000..f54d957
--- /dev/null
+++ b/src/base/process/process_info_unittest.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_info.h"
+
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// See https://crbug.com/726484 for Fuchsia.
+// Cannot read boot time on Android O, crbug.com/788870.
+#if !defined(OS_IOS) && !defined(OS_FUCHSIA) && !defined(OS_ANDROID)
+TEST(ProcessInfoTest, CreationTime) {
+ Time creation_time = CurrentProcessInfo::CreationTime();
+ ASSERT_FALSE(creation_time.is_null());
+}
+#endif // !defined(OS_IOS) && !defined(OS_FUCHSIA) && !defined(OS_ANDROID)
+
+} // namespace base
diff --git a/src/base/process/process_info_win.cc b/src/base/process/process_info_win.cc
new file mode 100644
index 0000000..3ac57e9
--- /dev/null
+++ b/src/base/process/process_info_win.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_info.h"
+
+#include <windows.h>
+#include <memory>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/time/time.h"
+#include "base/win/scoped_handle.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+HANDLE GetCurrentProcessToken() {
+ HANDLE process_token;
+ OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token);
+ DCHECK(process_token != NULL && process_token != INVALID_HANDLE_VALUE);
+ return process_token;
+}
+
+} // namespace
+
+// static
+const Time CurrentProcessInfo::CreationTime() {
+ FILETIME creation_time = {};
+ FILETIME ignore1 = {};
+ FILETIME ignore2 = {};
+ FILETIME ignore3 = {};
+ if (!::GetProcessTimes(::GetCurrentProcess(), &creation_time, &ignore1,
+ &ignore2, &ignore3)) {
+ return Time();
+ }
+ return Time::FromFileTime(creation_time);
+}
+
+IntegrityLevel GetCurrentProcessIntegrityLevel() {
+ HANDLE process_token(GetCurrentProcessToken());
+
+ DWORD token_info_length = 0;
+ if (::GetTokenInformation(process_token, TokenIntegrityLevel, nullptr, 0,
+ &token_info_length) ||
+ ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ NOTREACHED();
+ return INTEGRITY_UNKNOWN;
+ }
+
+ auto token_label_bytes = std::make_unique<char[]>(token_info_length);
+ TOKEN_MANDATORY_LABEL* token_label =
+ reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_label_bytes.get());
+ if (!::GetTokenInformation(process_token, TokenIntegrityLevel, token_label,
+ token_info_length, &token_info_length)) {
+ NOTREACHED();
+ return INTEGRITY_UNKNOWN;
+ }
+
+ DWORD integrity_level = *::GetSidSubAuthority(
+ token_label->Label.Sid,
+ static_cast<DWORD>(*::GetSidSubAuthorityCount(token_label->Label.Sid) -
+ 1));
+
+ if (integrity_level < SECURITY_MANDATORY_LOW_RID)
+ return UNTRUSTED_INTEGRITY;
+
+ if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID)
+ return LOW_INTEGRITY;
+
+ if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID &&
+ integrity_level < SECURITY_MANDATORY_HIGH_RID) {
+ return MEDIUM_INTEGRITY;
+ }
+
+ if (integrity_level >= SECURITY_MANDATORY_HIGH_RID)
+ return HIGH_INTEGRITY;
+
+ NOTREACHED();
+ return INTEGRITY_UNKNOWN;
+}
+
+bool IsCurrentProcessElevated() {
+ HANDLE process_token(GetCurrentProcessToken());
+
+ // Unlike TOKEN_ELEVATION_TYPE which returns TokenElevationTypeDefault when
+ // UAC is turned off, TOKEN_ELEVATION returns whether the process is elevated.
+ DWORD size;
+ TOKEN_ELEVATION elevation;
+ if (!GetTokenInformation(process_token, TokenElevation, &elevation,
+ sizeof(elevation), &size)) {
+ PLOG(ERROR) << "GetTokenInformation() failed";
+ return false;
+ }
+ return !!elevation.TokenIsElevated;
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator.cc b/src/base/process/process_iterator.cc
new file mode 100644
index 0000000..8b530a0
--- /dev/null
+++ b/src/base/process/process_iterator.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+#include "build/build_config.h"
+
+namespace base {
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ProcessEntry::ProcessEntry() : pid_(0), ppid_(0), gid_(0) {}
+ProcessEntry::ProcessEntry(const ProcessEntry& other) = default;
+ProcessEntry::~ProcessEntry() = default;
+#endif
+
+const ProcessEntry* ProcessIterator::NextProcessEntry() {
+ bool result = false;
+ do {
+ result = CheckForNextProcess();
+ } while (result && !IncludeEntry());
+ if (result)
+ return &entry_;
+ return nullptr;
+}
+
+ProcessIterator::ProcessEntries ProcessIterator::Snapshot() {
+ ProcessEntries found;
+ while (const ProcessEntry* process_entry = NextProcessEntry()) {
+ found.push_back(*process_entry);
+ }
+ return found;
+}
+
+bool ProcessIterator::IncludeEntry() {
+ return !filter_ || filter_->Includes(entry_);
+}
+
+NamedProcessIterator::NamedProcessIterator(
+ const FilePath::StringType& executable_name,
+ const ProcessFilter* filter) : ProcessIterator(filter),
+ executable_name_(executable_name) {
+#if defined(OS_ANDROID)
+ // On Android, the process name contains only the last 15 characters, which
+ // is in file /proc/<pid>/stat, the string between open parenthesis and close
+ // parenthesis. Please See ProcessIterator::CheckForNextProcess for details.
+ // Now if the length of input process name is greater than 15, only save the
+ // last 15 characters.
+ if (executable_name_.size() > 15) {
+ executable_name_ = FilePath::StringType(executable_name_,
+ executable_name_.size() - 15, 15);
+ }
+#endif
+}
+
+NamedProcessIterator::~NamedProcessIterator() = default;
+
+int GetProcessCount(const FilePath::StringType& executable_name,
+ const ProcessFilter* filter) {
+ int count = 0;
+ NamedProcessIterator iter(executable_name, filter);
+ while (iter.NextProcessEntry())
+ ++count;
+ return count;
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator.h b/src/base/process/process_iterator.h
new file mode 100644
index 0000000..b674b33
--- /dev/null
+++ b/src/base/process/process_iterator.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains methods to iterate over processes on the system.
+
+#ifndef BASE_PROCESS_PROCESS_ITERATOR_H_
+#define BASE_PROCESS_PROCESS_ITERATOR_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "starboard/types.h"
+
+#include "base/base_export.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/process/process.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <tlhelp32.h>
+#elif defined(OS_MACOSX) || defined(OS_OPENBSD)
+#include <sys/sysctl.h>
+#elif defined(OS_FREEBSD)
+#include <sys/user.h>
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <dirent.h>
+#endif
+
+namespace base {
+
+#if defined(OS_WIN)
+struct ProcessEntry : public PROCESSENTRY32 {
+ ProcessId pid() const { return th32ProcessID; }
+ ProcessId parent_pid() const { return th32ParentProcessID; }
+ const wchar_t* exe_file() const { return szExeFile; }
+};
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+struct BASE_EXPORT ProcessEntry {
+ ProcessEntry();
+ ProcessEntry(const ProcessEntry& other);
+ ~ProcessEntry();
+
+ ProcessId pid() const { return pid_; }
+ ProcessId parent_pid() const { return ppid_; }
+ ProcessId gid() const { return gid_; }
+ const char* exe_file() const { return exe_file_.c_str(); }
+ const std::vector<std::string>& cmd_line_args() const {
+ return cmd_line_args_;
+ }
+
+ ProcessId pid_;
+ ProcessId ppid_;
+ ProcessId gid_;
+ std::string exe_file_;
+ std::vector<std::string> cmd_line_args_;
+};
+#endif // defined(OS_WIN)
+
+// Used to filter processes by process ID.
+class ProcessFilter {
+ public:
+ // Returns true to indicate set-inclusion and false otherwise. This method
+ // should not have side-effects and should be idempotent.
+ virtual bool Includes(const ProcessEntry& entry) const = 0;
+
+ protected:
+ virtual ~ProcessFilter() = default;
+};
+
+// This class provides a way to iterate through a list of processes on the
+// current machine with a specified filter.
+// To use, create an instance and then call NextProcessEntry() until it returns
+// false.
+class BASE_EXPORT ProcessIterator {
+ public:
+ typedef std::list<ProcessEntry> ProcessEntries;
+
+ explicit ProcessIterator(const ProcessFilter* filter);
+ virtual ~ProcessIterator();
+
+ // If there's another process that matches the given executable name,
+ // returns a const pointer to the corresponding PROCESSENTRY32.
+ // If there are no more matching processes, returns NULL.
+ // The returned pointer will remain valid until NextProcessEntry()
+ // is called again or this NamedProcessIterator goes out of scope.
+ const ProcessEntry* NextProcessEntry();
+
+ // Takes a snapshot of all the ProcessEntry found.
+ ProcessEntries Snapshot();
+
+ protected:
+ virtual bool IncludeEntry();
+ const ProcessEntry& entry() { return entry_; }
+
+ private:
+ // Determines whether there's another process (regardless of executable)
+ // left in the list of all processes. Returns true and sets entry_ to
+ // that process's info if there is one, false otherwise.
+ bool CheckForNextProcess();
+
+ // Initializes a PROCESSENTRY32 data structure so that it's ready for
+ // use with Process32First/Process32Next.
+ void InitProcessEntry(ProcessEntry* entry);
+
+#if defined(OS_WIN)
+ HANDLE snapshot_;
+ bool started_iteration_;
+#elif defined(OS_MACOSX) || defined(OS_BSD)
+ std::vector<kinfo_proc> kinfo_procs_;
+ size_t index_of_kinfo_proc_;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ DIR* procfs_dir_;
+#endif
+ ProcessEntry entry_;
+ const ProcessFilter* filter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessIterator);
+};
+
+// This class provides a way to iterate through the list of processes
+// on the current machine that were started from the given executable
+// name. To use, create an instance and then call NextProcessEntry()
+// until it returns false.
+class BASE_EXPORT NamedProcessIterator : public ProcessIterator {
+ public:
+ NamedProcessIterator(const FilePath::StringType& executable_name,
+ const ProcessFilter* filter);
+ ~NamedProcessIterator() override;
+
+ protected:
+ bool IncludeEntry() override;
+
+ private:
+ FilePath::StringType executable_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(NamedProcessIterator);
+};
+
+// Returns the number of processes on the machine that are running from the
+// given executable name. If filter is non-null, then only processes selected
+// by the filter will be counted.
+BASE_EXPORT int GetProcessCount(const FilePath::StringType& executable_name,
+ const ProcessFilter* filter);
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_ITERATOR_H_
diff --git a/src/base/process/process_iterator_freebsd.cc b/src/base/process/process_iterator_freebsd.cc
new file mode 100644
index 0000000..5b327c6
--- /dev/null
+++ b/src/base/process/process_iterator_freebsd.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter)
+ : index_of_kinfo_proc_(),
+ filter_(filter) {
+
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid() };
+
+ bool done = false;
+ int try_num = 1;
+ const int max_tries = 10;
+
+ do {
+ size_t len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) {
+ LOG(ERROR) << "failed to get the size needed for the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ } else {
+ size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
+ // Leave some spare room for process table growth (more could show up
+ // between when we check and now)
+ num_of_kinfo_proc += 16;
+ kinfo_procs_.resize(num_of_kinfo_proc);
+ len = num_of_kinfo_proc * sizeof(struct kinfo_proc);
+ if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) <0) {
+ // If we get a mem error, it just means we need a bigger buffer, so
+ // loop around again. Anything else is a real error and give up.
+ if (errno != ENOMEM) {
+ LOG(ERROR) << "failed to get the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ }
+ } else {
+ // Got the list, just make sure we're sized exactly right
+ size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
+ kinfo_procs_.resize(num_of_kinfo_proc);
+ done = true;
+ }
+ }
+ } while (!done && (try_num++ < max_tries));
+
+ if (!done) {
+ LOG(ERROR) << "failed to collect the process list in a few tries";
+ kinfo_procs_.resize(0);
+ }
+}
+
+ProcessIterator::~ProcessIterator() {
+}
+
+bool ProcessIterator::CheckForNextProcess() {
+ std::string data;
+
+ for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
+ size_t length;
+ struct kinfo_proc kinfo = kinfo_procs_[index_of_kinfo_proc_];
+ int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.ki_pid };
+
+ if ((kinfo.ki_pid > 0) && (kinfo.ki_stat == SZOMB))
+ continue;
+
+ length = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0) {
+ LOG(ERROR) << "failed to figure out the buffer size for a command line";
+ continue;
+ }
+
+ data.resize(length);
+
+ if (sysctl(mib, arraysize(mib), &data[0], &length, NULL, 0) < 0) {
+ LOG(ERROR) << "failed to fetch a commandline";
+ continue;
+ }
+
+ std::string delimiters;
+ delimiters.push_back('\0');
+ entry_.cmd_line_args_ = SplitString(data, delimiters,
+ KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ size_t exec_name_end = data.find('\0');
+ if (exec_name_end == std::string::npos) {
+ LOG(ERROR) << "command line data didn't match expected format";
+ continue;
+ }
+
+ entry_.pid_ = kinfo.ki_pid;
+ entry_.ppid_ = kinfo.ki_ppid;
+ entry_.gid_ = kinfo.ki_pgid;
+
+ size_t last_slash = data.rfind('/', exec_name_end);
+ if (last_slash == std::string::npos) {
+ entry_.exe_file_.assign(data, 0, exec_name_end);
+ } else {
+ entry_.exe_file_.assign(data, last_slash + 1,
+ exec_name_end - last_slash - 1);
+ }
+
+ // Start w/ the next entry next time through
+ ++index_of_kinfo_proc_;
+
+ return true;
+ }
+ return false;
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ if (executable_name_ != entry().exe_file())
+ return false;
+
+ return ProcessIterator::IncludeEntry();
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator_fuchsia.cc b/src/base/process/process_iterator_fuchsia.cc
new file mode 100644
index 0000000..6d411ba
--- /dev/null
+++ b/src/base/process/process_iterator_fuchsia.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+
+namespace base {
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter) {
+ // TODO(fuchsia): There's no Fuchsia API to iterate processes currently.
+ NOTREACHED();
+}
+
+ProcessIterator::~ProcessIterator() {}
+
+bool ProcessIterator::CheckForNextProcess() {
+ // TODO(fuchsia): There's no Fuchsia API to iterate processes currently.
+ return false;
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ // TODO(fuchsia): There's no Fuchsia API to iterate processes currently.
+ return false;
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator_linux.cc b/src/base/process/process_iterator_linux.cc
new file mode 100644
index 0000000..1b129c9
--- /dev/null
+++ b/src/base/process/process_iterator_linux.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/process/internal_linux.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+// Reads the |field_num|th field from |proc_stats|.
+// Returns an empty string on failure.
+// This version only handles VM_COMM and VM_STATE, which are the only fields
+// that are strings.
+std::string GetProcStatsFieldAsString(
+ const std::vector<std::string>& proc_stats,
+ internal::ProcStatsFields field_num) {
+ if (field_num < internal::VM_COMM || field_num > internal::VM_STATE) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ if (proc_stats.size() > static_cast<size_t>(field_num))
+ return proc_stats[field_num];
+
+ NOTREACHED();
+ return nullptr;
+}
+
+// Reads /proc/<pid>/cmdline and populates |proc_cmd_line_args| with the command
+// line arguments. Returns true if successful.
+// Note: /proc/<pid>/cmdline contains command line arguments separated by single
+// null characters. We tokenize it into a vector of strings using '\0' as a
+// delimiter.
+bool GetProcCmdline(pid_t pid, std::vector<std::string>* proc_cmd_line_args) {
+ // Synchronously reading files in /proc is safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ FilePath cmd_line_file = internal::GetProcPidDir(pid).Append("cmdline");
+ std::string cmd_line;
+ if (!ReadFileToString(cmd_line_file, &cmd_line))
+ return false;
+ std::string delimiters;
+ delimiters.push_back('\0');
+ *proc_cmd_line_args = SplitString(cmd_line, delimiters, KEEP_WHITESPACE,
+ SPLIT_WANT_NONEMPTY);
+ return true;
+}
+
+} // namespace
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter)
+ : filter_(filter) {
+ procfs_dir_ = opendir(internal::kProcDir);
+ if (!procfs_dir_) {
+ // On Android, SELinux may prevent reading /proc. See
+ // https://crbug.com/581517 for details.
+ PLOG(ERROR) << "opendir " << internal::kProcDir;
+ }
+}
+
+ProcessIterator::~ProcessIterator() {
+ if (procfs_dir_) {
+ closedir(procfs_dir_);
+ procfs_dir_ = nullptr;
+ }
+}
+
+bool ProcessIterator::CheckForNextProcess() {
+ // TODO(port): skip processes owned by different UID
+
+ if (!procfs_dir_) {
+ DLOG(ERROR) << "Skipping CheckForNextProcess(), no procfs_dir_";
+ return false;
+ }
+
+ pid_t pid = kNullProcessId;
+ std::vector<std::string> cmd_line_args;
+ std::string stats_data;
+ std::vector<std::string> proc_stats;
+
+ // Arbitrarily guess that there will never be more than 200 non-process
+ // files in /proc. Hardy has 53 and Lucid has 61.
+ int skipped = 0;
+ const int kSkipLimit = 200;
+ while (skipped < kSkipLimit) {
+ dirent* slot = readdir(procfs_dir_);
+ // all done looking through /proc?
+ if (!slot)
+ return false;
+
+ // If not a process, keep looking for one.
+ pid = internal::ProcDirSlotToPid(slot->d_name);
+ if (!pid) {
+ skipped++;
+ continue;
+ }
+
+ if (!GetProcCmdline(pid, &cmd_line_args))
+ continue;
+
+ if (!internal::ReadProcStats(pid, &stats_data))
+ continue;
+ if (!internal::ParseProcStats(stats_data, &proc_stats))
+ continue;
+
+ std::string runstate =
+ GetProcStatsFieldAsString(proc_stats, internal::VM_STATE);
+ if (runstate.size() != 1) {
+ NOTREACHED();
+ continue;
+ }
+
+ // Is the process in 'Zombie' state, i.e. dead but waiting to be reaped?
+ // Allowed values: D R S T Z
+ if (runstate[0] != 'Z')
+ break;
+
+ // Nope, it's a zombie; somebody isn't cleaning up after their children.
+ // (e.g. WaitForProcessesToExit doesn't clean up after dead children yet.)
+ // There could be a lot of zombies, can't really decrement i here.
+ }
+ if (skipped >= kSkipLimit) {
+ NOTREACHED();
+ return false;
+ }
+
+ entry_.pid_ = pid;
+ entry_.ppid_ = GetProcStatsFieldAsInt64(proc_stats, internal::VM_PPID);
+ entry_.gid_ = GetProcStatsFieldAsInt64(proc_stats, internal::VM_PGRP);
+ entry_.cmd_line_args_.assign(cmd_line_args.begin(), cmd_line_args.end());
+ entry_.exe_file_ = GetProcessExecutablePath(pid).BaseName().value();
+ return true;
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ if (executable_name_ != entry().exe_file())
+ return false;
+ return ProcessIterator::IncludeEntry();
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator_mac.cc b/src/base/process/process_iterator_mac.cc
new file mode 100644
index 0000000..22be519
--- /dev/null
+++ b/src/base/process/process_iterator_mac.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+
+#include <errno.h>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter)
+ : index_of_kinfo_proc_(0),
+ filter_(filter) {
+ // Get a snapshot of all of my processes (yes, as we loop it can go stale, but
+ // but trying to find where we were in a constantly changing list is basically
+ // impossible.
+
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID,
+ static_cast<int>(geteuid()) };
+
+ // Since more processes could start between when we get the size and when
+ // we get the list, we do a loop to keep trying until we get it.
+ bool done = false;
+ int try_num = 1;
+ const int max_tries = 10;
+ do {
+ // Get the size of the buffer
+ size_t len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) {
+ DLOG(ERROR) << "failed to get the size needed for the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ } else {
+ size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
+ // Leave some spare room for process table growth (more could show up
+ // between when we check and now)
+ num_of_kinfo_proc += 16;
+ kinfo_procs_.resize(num_of_kinfo_proc);
+ len = num_of_kinfo_proc * sizeof(struct kinfo_proc);
+ // Load the list of processes
+ if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) {
+ // If we get a mem error, it just means we need a bigger buffer, so
+ // loop around again. Anything else is a real error and give up.
+ if (errno != ENOMEM) {
+ DLOG(ERROR) << "failed to get the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ }
+ } else {
+ // Got the list, just make sure we're sized exactly right
+ kinfo_procs_.resize(len / sizeof(struct kinfo_proc));
+ done = true;
+ }
+ }
+ } while (!done && (try_num++ < max_tries));
+
+ if (!done) {
+ DLOG(ERROR) << "failed to collect the process list in a few tries";
+ kinfo_procs_.resize(0);
+ }
+}
+
+ProcessIterator::~ProcessIterator() {
+}
+
+bool ProcessIterator::CheckForNextProcess() {
+ std::string data;
+ for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
+ kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_];
+
+ // Skip processes just awaiting collection
+ if ((kinfo.kp_proc.p_pid > 0) && (kinfo.kp_proc.p_stat == SZOMB))
+ continue;
+
+ int mib[] = { CTL_KERN, KERN_PROCARGS, kinfo.kp_proc.p_pid };
+
+ // Find out what size buffer we need.
+ size_t data_len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) {
+ DVPLOG(1) << "failed to figure out the buffer size for a commandline";
+ continue;
+ }
+
+ data.resize(data_len);
+ if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) {
+ DVPLOG(1) << "failed to fetch a commandline";
+ continue;
+ }
+
+ // |data| contains all the command line parameters of the process, separated
+ // by blocks of one or more null characters. We tokenize |data| into a
+ // vector of strings using '\0' as a delimiter and populate
+ // |entry_.cmd_line_args_|.
+ std::string delimiters;
+ delimiters.push_back('\0');
+ entry_.cmd_line_args_ = SplitString(data, delimiters,
+ KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ // |data| starts with the full executable path followed by a null character.
+ // We search for the first instance of '\0' and extract everything before it
+ // to populate |entry_.exe_file_|.
+ size_t exec_name_end = data.find('\0');
+ if (exec_name_end == std::string::npos) {
+ DLOG(ERROR) << "command line data didn't match expected format";
+ continue;
+ }
+
+ entry_.pid_ = kinfo.kp_proc.p_pid;
+ entry_.ppid_ = kinfo.kp_eproc.e_ppid;
+ entry_.gid_ = kinfo.kp_eproc.e_pgid;
+ size_t last_slash = data.rfind('/', exec_name_end);
+ if (last_slash == std::string::npos)
+ entry_.exe_file_.assign(data, 0, exec_name_end);
+ else
+ entry_.exe_file_.assign(data, last_slash + 1,
+ exec_name_end - last_slash - 1);
+ // Start w/ the next entry next time through
+ ++index_of_kinfo_proc_;
+ // Done
+ return true;
+ }
+ return false;
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ return (executable_name_ == entry().exe_file() &&
+ ProcessIterator::IncludeEntry());
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator_openbsd.cc b/src/base/process/process_iterator_openbsd.cc
new file mode 100644
index 0000000..cad712c
--- /dev/null
+++ b/src/base/process/process_iterator_openbsd.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+
+#include <errno.h>
+#include <sys/sysctl.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter)
+ : index_of_kinfo_proc_(),
+ filter_(filter) {
+
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_UID, getuid(),
+ sizeof(struct kinfo_proc), 0 };
+
+ bool done = false;
+ int try_num = 1;
+ const int max_tries = 10;
+
+ do {
+ size_t len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &len, NULL, 0) < 0) {
+ DLOG(ERROR) << "failed to get the size needed for the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ } else {
+ size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
+ // Leave some spare room for process table growth (more could show up
+ // between when we check and now)
+ num_of_kinfo_proc += 16;
+ kinfo_procs_.resize(num_of_kinfo_proc);
+ len = num_of_kinfo_proc * sizeof(struct kinfo_proc);
+ if (sysctl(mib, arraysize(mib), &kinfo_procs_[0], &len, NULL, 0) < 0) {
+ // If we get a mem error, it just means we need a bigger buffer, so
+ // loop around again. Anything else is a real error and give up.
+ if (errno != ENOMEM) {
+ DLOG(ERROR) << "failed to get the process list";
+ kinfo_procs_.resize(0);
+ done = true;
+ }
+ } else {
+ // Got the list, just make sure we're sized exactly right
+ size_t num_of_kinfo_proc = len / sizeof(struct kinfo_proc);
+ kinfo_procs_.resize(num_of_kinfo_proc);
+ done = true;
+ }
+ }
+ } while (!done && (try_num++ < max_tries));
+
+ if (!done) {
+ DLOG(ERROR) << "failed to collect the process list in a few tries";
+ kinfo_procs_.resize(0);
+ }
+}
+
+ProcessIterator::~ProcessIterator() {
+}
+
+bool ProcessIterator::CheckForNextProcess() {
+ std::string data;
+ for (; index_of_kinfo_proc_ < kinfo_procs_.size(); ++index_of_kinfo_proc_) {
+ kinfo_proc& kinfo = kinfo_procs_[index_of_kinfo_proc_];
+
+ // Skip processes just awaiting collection
+ if ((kinfo.p_pid > 0) && (kinfo.p_stat == SZOMB))
+ continue;
+
+ int mib[] = { CTL_KERN, KERN_PROC_ARGS, kinfo.p_pid };
+
+ // Find out what size buffer we need.
+ size_t data_len = 0;
+ if (sysctl(mib, arraysize(mib), NULL, &data_len, NULL, 0) < 0) {
+ DVPLOG(1) << "failed to figure out the buffer size for a commandline";
+ continue;
+ }
+
+ data.resize(data_len);
+ if (sysctl(mib, arraysize(mib), &data[0], &data_len, NULL, 0) < 0) {
+ DVPLOG(1) << "failed to fetch a commandline";
+ continue;
+ }
+
+ // |data| contains all the command line parameters of the process, separated
+ // by blocks of one or more null characters. We tokenize |data| into a
+ // vector of strings using '\0' as a delimiter and populate
+ // |entry_.cmd_line_args_|.
+ std::string delimiters;
+ delimiters.push_back('\0');
+ entry_.cmd_line_args_ = SplitString(data, delimiters, KEEP_WHITESPACE,
+ SPLIT_WANT_NONEMPTY);
+
+ // |data| starts with the full executable path followed by a null character.
+ // We search for the first instance of '\0' and extract everything before it
+ // to populate |entry_.exe_file_|.
+ size_t exec_name_end = data.find('\0');
+ if (exec_name_end == std::string::npos) {
+ DLOG(ERROR) << "command line data didn't match expected format";
+ continue;
+ }
+
+ entry_.pid_ = kinfo.p_pid;
+ entry_.ppid_ = kinfo.p_ppid;
+ entry_.gid_ = kinfo.p__pgid;
+ size_t last_slash = data.rfind('/', exec_name_end);
+ if (last_slash == std::string::npos)
+ entry_.exe_file_.assign(data, 0, exec_name_end);
+ else
+ entry_.exe_file_.assign(data, last_slash + 1,
+ exec_name_end - last_slash - 1);
+ // Start w/ the next entry next time through
+ ++index_of_kinfo_proc_;
+ // Done
+ return true;
+ }
+ return false;
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ return (executable_name_ == entry().exe_file() &&
+ ProcessIterator::IncludeEntry());
+}
+
+} // namespace base
diff --git a/src/base/process/process_iterator_win.cc b/src/base/process/process_iterator_win.cc
new file mode 100644
index 0000000..1937c5e
--- /dev/null
+++ b/src/base/process/process_iterator_win.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_iterator.h"
+#include "starboard/memory.h"
+
+namespace base {
+
+ProcessIterator::ProcessIterator(const ProcessFilter* filter)
+ : started_iteration_(false),
+ filter_(filter) {
+ snapshot_ = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+}
+
+ProcessIterator::~ProcessIterator() {
+ CloseHandle(snapshot_);
+}
+
+bool ProcessIterator::CheckForNextProcess() {
+ InitProcessEntry(&entry_);
+
+ if (!started_iteration_) {
+ started_iteration_ = true;
+ return !!Process32First(snapshot_, &entry_);
+ }
+
+ return !!Process32Next(snapshot_, &entry_);
+}
+
+void ProcessIterator::InitProcessEntry(ProcessEntry* entry) {
+ SbMemorySet(entry, 0, sizeof(*entry));
+ entry->dwSize = sizeof(*entry);
+}
+
+bool NamedProcessIterator::IncludeEntry() {
+ // Case insensitive.
+ return _wcsicmp(executable_name_.c_str(), entry().exe_file()) == 0 &&
+ ProcessIterator::IncludeEntry();
+}
+
+} // namespace base
diff --git a/src/base/process/process_linux.cc b/src/base/process/process_linux.cc
new file mode 100644
index 0000000..c56f690
--- /dev/null
+++ b/src/base/process/process_linux.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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 <errno.h>
+#include <sys/resource.h>
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/posix/can_lower_nice_to.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+const int kForegroundPriority = 0;
+
+#if defined(OS_CHROMEOS)
+// We are more aggressive in our lowering of background process priority
+// for chromeos as we have much more control over other processes running
+// on the machine.
+//
+// TODO(davemoore) Refactor this by adding support for higher levels to set
+// the foregrounding / backgrounding process so we don't have to keep
+// chrome / chromeos specific logic here.
+const int kBackgroundPriority = 19;
+const char kControlPath[] = "/sys/fs/cgroup/cpu%s/cgroup.procs";
+const char kForeground[] = "/chrome_renderers/foreground";
+const char kBackground[] = "/chrome_renderers/background";
+const char kProcPath[] = "/proc/%d/cgroup";
+
+struct CGroups {
+ // Check for cgroups files. ChromeOS supports these by default. It creates
+ // a cgroup mount in /sys/fs/cgroup and then configures two cpu task groups,
+ // one contains at most a single foreground renderer and the other contains
+ // all background renderers. This allows us to limit the impact of background
+ // renderers on foreground ones to a greater level than simple renicing.
+ bool enabled;
+ base::FilePath foreground_file;
+ base::FilePath background_file;
+
+ CGroups() {
+ foreground_file =
+ base::FilePath(base::StringPrintf(kControlPath, kForeground));
+ background_file =
+ base::FilePath(base::StringPrintf(kControlPath, kBackground));
+ base::FileSystemType foreground_type;
+ base::FileSystemType background_type;
+ enabled =
+ base::GetFileSystemType(foreground_file, &foreground_type) &&
+ base::GetFileSystemType(background_file, &background_type) &&
+ foreground_type == FILE_SYSTEM_CGROUP &&
+ background_type == FILE_SYSTEM_CGROUP;
+ }
+
+ static CGroups& Get() {
+ static auto& groups = *new CGroups;
+ return groups;
+ }
+};
+#else
+const int kBackgroundPriority = 5;
+#endif // defined(OS_CHROMEOS)
+
+} // namespace
+
+// static
+bool Process::CanBackgroundProcesses() {
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled)
+ return true;
+#endif // defined(OS_CHROMEOS)
+
+ static const bool can_reraise_priority =
+ internal::CanLowerNiceTo(kForegroundPriority);
+ return can_reraise_priority;
+}
+
+bool Process::IsProcessBackgrounded() const {
+ DCHECK(IsValid());
+
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled) {
+ // Used to allow reading the process priority from proc on thread launch.
+ base::ThreadRestrictions::ScopedAllowIO allow_io;
+ std::string proc;
+ if (base::ReadFileToString(
+ base::FilePath(StringPrintf(kProcPath, process_)), &proc)) {
+ return IsProcessBackgroundedCGroup(proc);
+ }
+ return false;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ return GetPriority() == kBackgroundPriority;
+}
+
+bool Process::SetProcessBackgrounded(bool background) {
+ DCHECK(IsValid());
+
+#if defined(OS_CHROMEOS)
+ if (CGroups::Get().enabled) {
+ std::string pid = IntToString(process_);
+ const base::FilePath file = background ? CGroups::Get().background_file
+ : CGroups::Get().foreground_file;
+ return base::WriteFile(file, pid.c_str(), pid.size()) > 0;
+ }
+#endif // defined(OS_CHROMEOS)
+
+ if (!CanBackgroundProcesses())
+ return false;
+
+ int priority = background ? kBackgroundPriority : kForegroundPriority;
+ int result = setpriority(PRIO_PROCESS, process_, priority);
+ DPCHECK(result == 0);
+ return result == 0;
+}
+
+#if defined(OS_CHROMEOS)
+bool IsProcessBackgroundedCGroup(const StringPiece& cgroup_contents) {
+ // The process can be part of multiple control groups, and for each cgroup
+ // hierarchy there's an entry in the file. We look for a control group
+ // named "/chrome_renderers/background" to determine if the process is
+ // backgrounded. crbug.com/548818.
+ std::vector<StringPiece> lines = SplitStringPiece(
+ cgroup_contents, "\n", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ for (const auto& line : lines) {
+ std::vector<StringPiece> fields =
+ SplitStringPiece(line, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ if (fields.size() != 3U) {
+ NOTREACHED();
+ continue;
+ }
+ if (fields[2] == kBackground)
+ return true;
+ }
+
+ return false;
+}
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+// Reads /proc/<pid>/status and returns the PID in its PID namespace.
+// If the process is not in a PID namespace or /proc/<pid>/status does not
+// report NSpid, kNullProcessId is returned.
+ProcessId Process::GetPidInNamespace() const {
+ std::string status;
+ {
+ // Synchronously reading files in /proc does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ FilePath status_file =
+ FilePath("/proc").Append(IntToString(process_)).Append("status");
+ if (!ReadFileToString(status_file, &status)) {
+ return kNullProcessId;
+ }
+ }
+
+ StringPairs pairs;
+ SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key == "NSpid") {
+ std::vector<StringPiece> split_value_str = SplitStringPiece(
+ value_str, "\t", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (split_value_str.size() <= 1) {
+ return kNullProcessId;
+ }
+ int value;
+ // The last value in the list is the PID in the namespace.
+ if (!StringToInt(split_value_str.back(), &value)) {
+ NOTREACHED();
+ return kNullProcessId;
+ }
+ return value;
+ }
+ }
+ return kNullProcessId;
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace base
diff --git a/src/base/process/process_mac.cc b/src/base/process/process_mac.cc
new file mode 100644
index 0000000..7396b20
--- /dev/null
+++ b/src/base/process/process_mac.cc
@@ -0,0 +1,78 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// 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 "base/feature_list.h"
+#include "base/mac/mach_logging.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// Enables backgrounding hidden renderers on Mac.
+const Feature kMacAllowBackgroundingProcesses{"MacAllowBackgroundingProcesses",
+ FEATURE_DISABLED_BY_DEFAULT};
+
+bool Process::CanBackgroundProcesses() {
+ return FeatureList::IsEnabled(kMacAllowBackgroundingProcesses);
+}
+
+bool Process::IsProcessBackgrounded(PortProvider* port_provider) const {
+ DCHECK(IsValid());
+ if (port_provider == nullptr || !CanBackgroundProcesses())
+ return false;
+
+ mach_port_t task_port = port_provider->TaskForPid(Pid());
+ if (task_port == TASK_NULL)
+ return false;
+
+ 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);
+ MACH_LOG_IF(ERROR, result != KERN_SUCCESS, result)
+ << "task_policy_get TASK_CATEGORY_POLICY";
+
+ if (result == KERN_SUCCESS && get_default == FALSE) {
+ return category_policy.role == TASK_BACKGROUND_APPLICATION;
+ }
+ return false;
+}
+
+bool Process::SetProcessBackgrounded(PortProvider* port_provider,
+ bool background) {
+ DCHECK(IsValid());
+ if (port_provider == nullptr || !CanBackgroundProcesses())
+ return false;
+
+ mach_port_t task_port = port_provider->TaskForPid(Pid());
+ if (task_port == TASK_NULL)
+ return false;
+
+ if (IsProcessBackgrounded(port_provider) == background)
+ return true;
+
+ task_category_policy category_policy;
+ category_policy.role =
+ background ? TASK_BACKGROUND_APPLICATION : TASK_FOREGROUND_APPLICATION;
+ kern_return_t result =
+ task_policy_set(task_port, TASK_CATEGORY_POLICY,
+ reinterpret_cast<task_policy_t>(&category_policy),
+ TASK_CATEGORY_POLICY_COUNT);
+
+ if (result != KERN_SUCCESS) {
+ MACH_LOG(ERROR, result) << "task_policy_set TASK_CATEGORY_POLICY";
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics.cc b/src/base/process/process_metrics.cc
new file mode 100644
index 0000000..bc843a4
--- /dev/null
+++ b/src/base/process/process_metrics.cc
@@ -0,0 +1,167 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+namespace {
+int CalculateEventsPerSecond(uint64_t event_count,
+ uint64_t* last_event_count,
+ base::TimeTicks* last_calculated) {
+ base::TimeTicks time = base::TimeTicks::Now();
+
+ if (*last_event_count == 0) {
+ // First call, just set the last values.
+ *last_calculated = time;
+ *last_event_count = event_count;
+ return 0;
+ }
+
+ int64_t events_delta = event_count - *last_event_count;
+ int64_t time_delta = (time - *last_calculated).InMicroseconds();
+ if (time_delta == 0) {
+ NOTREACHED();
+ return 0;
+ }
+
+ *last_calculated = time;
+ *last_event_count = event_count;
+
+ int64_t events_delta_for_ms =
+ events_delta * base::Time::kMicrosecondsPerSecond;
+ // Round the result up by adding 1/2 (the second term resolves to 1/2 without
+ // dropping down into floating point).
+ return (events_delta_for_ms + time_delta / 2) / time_delta;
+}
+
+} // namespace
+
+namespace base {
+
+SystemMemoryInfoKB::SystemMemoryInfoKB() = default;
+
+SystemMemoryInfoKB::SystemMemoryInfoKB(const SystemMemoryInfoKB& other) =
+ default;
+
+SystemMetrics::SystemMetrics() {
+ committed_memory_ = 0;
+}
+
+SystemMetrics SystemMetrics::Sample() {
+ SystemMetrics system_metrics;
+
+ system_metrics.committed_memory_ = GetSystemCommitCharge();
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ GetSystemMemoryInfo(&system_metrics.memory_info_);
+ GetVmStatInfo(&system_metrics.vmstat_info_);
+ GetSystemDiskInfo(&system_metrics.disk_info_);
+#endif
+#if defined(OS_CHROMEOS)
+ GetSwapInfo(&system_metrics.swap_info_);
+#endif
+#if defined(OS_WIN)
+ GetSystemPerformanceInfo(&system_metrics.performance_);
+#endif
+ return system_metrics;
+}
+
+std::unique_ptr<Value> SystemMetrics::ToValue() const {
+ std::unique_ptr<DictionaryValue> res(new DictionaryValue());
+
+ res->SetInteger("committed_memory", static_cast<int>(committed_memory_));
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ std::unique_ptr<DictionaryValue> meminfo = memory_info_.ToValue();
+ std::unique_ptr<DictionaryValue> vmstat = vmstat_info_.ToValue();
+ meminfo->MergeDictionary(vmstat.get());
+ res->Set("meminfo", std::move(meminfo));
+ res->Set("diskinfo", disk_info_.ToValue());
+#endif
+#if defined(OS_CHROMEOS)
+ res->Set("swapinfo", swap_info_.ToValue());
+#endif
+#if defined(OS_WIN)
+ res->Set("perfinfo", performance_.ToValue());
+#endif
+
+ return std::move(res);
+}
+
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateCurrentProcessMetrics() {
+#if !defined(OS_MACOSX) || defined(OS_IOS)
+ return CreateProcessMetrics(base::GetCurrentProcessHandle());
+#else
+ return CreateProcessMetrics(base::GetCurrentProcessHandle(), nullptr);
+#endif // !defined(OS_MACOSX) || defined(OS_IOS)
+}
+
+#if !defined(OS_FREEBSD) || !defined(OS_POSIX)
+double ProcessMetrics::GetPlatformIndependentCPUUsage() {
+ TimeDelta cumulative_cpu = GetCumulativeCPUUsage();
+ TimeTicks time = TimeTicks::Now();
+
+ if (last_cumulative_cpu_.is_zero()) {
+ // First call, just set the last values.
+ last_cumulative_cpu_ = cumulative_cpu;
+ last_cpu_time_ = time;
+ return 0;
+ }
+
+ TimeDelta system_time_delta = cumulative_cpu - last_cumulative_cpu_;
+ TimeDelta time_delta = time - last_cpu_time_;
+ DCHECK(!time_delta.is_zero());
+ if (time_delta.is_zero())
+ return 0;
+
+ last_cumulative_cpu_ = cumulative_cpu;
+ last_cpu_time_ = time;
+
+ return 100.0 * system_time_delta.InMicrosecondsF() /
+ time_delta.InMicrosecondsF();
+}
+#endif
+
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+int ProcessMetrics::CalculateIdleWakeupsPerSecond(
+ uint64_t absolute_idle_wakeups) {
+ return CalculateEventsPerSecond(absolute_idle_wakeups,
+ &last_absolute_idle_wakeups_,
+ &last_idle_wakeups_time_);
+}
+#else
+int ProcessMetrics::GetIdleWakeupsPerSecond() {
+ NOTIMPLEMENTED(); // http://crbug.com/120488
+ return 0;
+}
+#endif // defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+
+#if defined(OS_MACOSX)
+int ProcessMetrics::CalculatePackageIdleWakeupsPerSecond(
+ uint64_t absolute_package_idle_wakeups) {
+ return CalculateEventsPerSecond(absolute_package_idle_wakeups,
+ &last_absolute_package_idle_wakeups_,
+ &last_package_idle_wakeups_time_);
+}
+
+#endif // defined(OS_MACOSX)
+
+#if !defined(OS_WIN)
+uint64_t ProcessMetrics::GetCumulativeDiskUsageInBytes() {
+ // Not implemented.
+ return 0;
+}
+#endif
+
+uint64_t ProcessMetrics::GetDiskUsageBytesPerSecond() {
+ uint64_t cumulative_disk_usage = GetCumulativeDiskUsageInBytes();
+ return CalculateEventsPerSecond(cumulative_disk_usage,
+ &last_cumulative_disk_usage_,
+ &last_disk_usage_time_);
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics.h b/src/base/process/process_metrics.h
new file mode 100644
index 0000000..553b90d
--- /dev/null
+++ b/src/base/process/process_metrics.h
@@ -0,0 +1,602 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file contains routines for gathering resource statistics for processes
+// running on the system.
+
+#ifndef BASE_PROCESS_PROCESS_METRICS_H_
+#define BASE_PROCESS_PROCESS_METRICS_H_
+
+#include <memory>
+#include <string>
+
+#include "base/base_export.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/process/process_handle.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <mach/mach.h>
+#include "base/process/port_provider_mac.h"
+
+#if !defined(OS_IOS)
+#include <mach/mach_vm.h>
+#endif
+#endif
+
+#if defined(OS_WIN)
+#include "base/win/scoped_handle.h"
+#include "base/win/windows_types.h"
+#include "starboard/types.h"
+#endif
+
+#if !defined(STARBOARD)
+
+namespace base {
+
+// Full declaration is in process_metrics_iocounters.h.
+struct IoCounters;
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+// Minor and major page fault counts since the process creation.
+// Both counts are process-wide, and exclude child processes.
+//
+// minor: Number of page faults that didn't require disk IO.
+// major: Number of page faults that required disk IO.
+struct PageFaultCounts {
+ int64_t minor;
+ int64_t major;
+};
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+// Convert a POSIX timeval to microseconds.
+BASE_EXPORT int64_t TimeValToMicroseconds(const struct timeval& tv);
+
+// Provides performance metrics for a specified process (CPU usage and IO
+// counters). Use CreateCurrentProcessMetrics() to get an instance for the
+// current process, or CreateProcessMetrics() to get an instance for an
+// arbitrary process. Then, access the information with the different get
+// methods.
+//
+// This class exposes a few platform-specific APIs for parsing memory usage, but
+// these are not intended to generalize to other platforms, since the memory
+// models differ substantially.
+//
+// To obtain consistent memory metrics, use the memory_instrumentation service.
+//
+// For further documentation on memory, see
+// https://chromium.googlesource.com/chromium/src/+/HEAD/docs/README.md
+class BASE_EXPORT ProcessMetrics {
+ public:
+ ~ProcessMetrics();
+
+ // Creates a ProcessMetrics for the specified process.
+#if !defined(OS_MACOSX) || defined(OS_IOS)
+ static std::unique_ptr<ProcessMetrics> CreateProcessMetrics(
+ ProcessHandle process);
+#else
+
+ // The port provider needs to outlive the ProcessMetrics object returned by
+ // this function. If NULL is passed as provider, the returned object
+ // only returns valid metrics if |process| is the current process.
+ static std::unique_ptr<ProcessMetrics> CreateProcessMetrics(
+ ProcessHandle process,
+ PortProvider* port_provider);
+#endif // !defined(OS_MACOSX) || defined(OS_IOS)
+
+ // Creates a ProcessMetrics for the current process. This a cross-platform
+ // convenience wrapper for CreateProcessMetrics().
+ static std::unique_ptr<ProcessMetrics> CreateCurrentProcessMetrics();
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // Resident Set Size is a Linux/Android specific memory concept. Do not
+ // attempt to extend this to other platforms.
+ BASE_EXPORT size_t GetResidentSetSize() const;
+#endif
+
+#if defined(OS_CHROMEOS)
+ // /proc/<pid>/totmaps is a syscall that returns memory summary statistics for
+ // the process.
+ // totmaps is a Linux specific concept, currently only being used on ChromeOS.
+ // Do not attempt to extend this to other platforms.
+ //
+ struct TotalsSummary {
+ size_t private_clean_kb;
+ size_t private_dirty_kb;
+ size_t swap_kb;
+ };
+ BASE_EXPORT TotalsSummary GetTotalsSummary() const;
+#endif
+
+#if defined(OS_MACOSX)
+ struct TaskVMInfo {
+ // Only available on macOS 10.12+.
+ // Anonymous, non-discardable memory, including non-volatile IOKit.
+ // Measured in bytes.
+ uint64_t phys_footprint = 0;
+
+ // Anonymous, non-discardable, non-compressed memory, excluding IOKit.
+ // Measured in bytes.
+ uint64_t internal = 0;
+
+ // Compressed memory measured in bytes.
+ uint64_t compressed = 0;
+ };
+ TaskVMInfo GetTaskVMInfo() const;
+#endif
+
+ // Returns the percentage of time spent executing, across all threads of the
+ // process, in the interval since the last time the method was called. Since
+ // this considers the total execution time across all threads in a process,
+ // the result can easily exceed 100% in multi-thread processes running on
+ // multi-core systems. In general the result is therefore a value in the
+ // range 0% to SysInfo::NumberOfProcessors() * 100%.
+ //
+ // To obtain the percentage of total available CPU resources consumed by this
+ // process over the interval, the caller must divide by NumberOfProcessors().
+ //
+ // Since this API measures usage over an interval, it will return zero on the
+ // first call, and an actual value only on the second and subsequent calls.
+ double GetPlatformIndependentCPUUsage();
+
+ // Returns the cumulative CPU usage across all threads of the process since
+ // process start. In case of multi-core processors, a process can consume CPU
+ // at a rate higher than wall-clock time, e.g. two cores at full utilization
+ // will result in a time delta of 2 seconds/per 1 wall-clock second.
+ TimeDelta GetCumulativeCPUUsage();
+
+ // Returns the number of average idle cpu wakeups per second since the last
+ // call.
+ int GetIdleWakeupsPerSecond();
+
+#if defined(OS_MACOSX)
+ // Returns the number of average "package idle exits" per second, which have
+ // a higher energy impact than a regular wakeup, since the last call.
+ //
+ // From the powermetrics man page:
+ // "With the exception of some Mac Pro systems, Mac and
+ // iOS systems are typically single package systems, wherein all CPUs are
+ // part of a single processor complex (typically a single IC die) with shared
+ // logic that can include (depending on system specifics) shared last level
+ // caches, an integrated memory controller etc. When all CPUs in the package
+ // are idle, the hardware can power-gate significant portions of the shared
+ // logic in addition to each individual processor's logic, as well as take
+ // measures such as placing DRAM in to self-refresh (also referred to as
+ // auto-refresh), place interconnects into lower-power states etc"
+ int GetPackageIdleWakeupsPerSecond();
+#endif
+
+ // Retrieves accounting information for all I/O operations performed by the
+ // process.
+ // If IO information is retrieved successfully, the function returns true
+ // and fills in the IO_COUNTERS passed in. The function returns false
+ // otherwise.
+ bool GetIOCounters(IoCounters* io_counters) const;
+
+ // Returns the number of bytes transferred to/from disk per second, across all
+ // threads of the process, in the interval since the last time the method was
+ // called.
+ //
+ // Since this API measures usage over an interval, it will return zero on the
+ // first call, and an actual value only on the second and subsequent calls.
+ uint64_t GetDiskUsageBytesPerSecond();
+
+ // Returns the cumulative disk usage in bytes across all threads of the
+ // process since process start.
+ uint64_t GetCumulativeDiskUsageInBytes();
+
+#if defined(OS_LINUX) || defined(OS_AIX) || defined(OS_ANDROID)
+ // Returns the number of file descriptors currently open by the process, or
+ // -1 on error.
+ int GetOpenFdCount() const;
+
+ // Returns the soft limit of file descriptors that can be opened by the
+ // process, or -1 on error.
+ int GetOpenFdSoftLimit() const;
+#endif // defined(OS_LINUX) || defined(OS_AIX) || defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ // Bytes of swap as reported by /proc/[pid]/status.
+ uint64_t GetVmSwapBytes() const;
+
+ // Minor and major page fault count as reported by /proc/[pid]/stat.
+ // Returns true for success.
+ bool GetPageFaultCounts(PageFaultCounts* counts) const;
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // Returns total memory usage of malloc.
+ size_t GetMallocUsage();
+
+ private:
+#if !defined(OS_MACOSX) || defined(OS_IOS)
+ explicit ProcessMetrics(ProcessHandle process);
+#else
+ ProcessMetrics(ProcessHandle process, PortProvider* port_provider);
+#endif // !defined(OS_MACOSX) || defined(OS_IOS)
+
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+ int CalculateIdleWakeupsPerSecond(uint64_t absolute_idle_wakeups);
+#endif
+#if defined(OS_MACOSX)
+ // The subset of wakeups that cause a "package exit" can be tracked on macOS.
+ // See |GetPackageIdleWakeupsForSecond| comment for more info.
+ int CalculatePackageIdleWakeupsPerSecond(
+ uint64_t absolute_package_idle_wakeups);
+#endif
+
+#if defined(OS_WIN)
+ win::ScopedHandle process_;
+#else
+ ProcessHandle process_;
+#endif
+
+ // Used to store the previous times and CPU usage counts so we can
+ // compute the CPU usage between calls.
+ TimeTicks last_cpu_time_;
+#if !defined(OS_FREEBSD) || !defined(OS_POSIX)
+ TimeDelta last_cumulative_cpu_;
+#endif
+
+ // Used to store the previous times and disk usage counts so we can
+ // compute the disk usage between calls.
+ TimeTicks last_disk_usage_time_;
+ // Number of bytes transferred to/from disk in bytes.
+ uint64_t last_cumulative_disk_usage_ = 0;
+
+#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_AIX)
+ // Same thing for idle wakeups.
+ TimeTicks last_idle_wakeups_time_;
+ uint64_t last_absolute_idle_wakeups_;
+#endif
+
+#if defined(OS_MACOSX)
+ // And same thing for package idle exit wakeups.
+ TimeTicks last_package_idle_wakeups_time_;
+ uint64_t last_absolute_package_idle_wakeups_;
+#endif
+
+#if !defined(OS_IOS)
+#if defined(OS_MACOSX)
+ // Queries the port provider if it's set.
+ mach_port_t TaskForPid(ProcessHandle process) const;
+
+ PortProvider* port_provider_;
+#endif // defined(OS_MACOSX)
+#endif // !defined(OS_IOS)
+
+ DISALLOW_COPY_AND_ASSIGN(ProcessMetrics);
+};
+
+// Returns the memory committed by the system in KBytes.
+// Returns 0 if it can't compute the commit charge.
+BASE_EXPORT size_t GetSystemCommitCharge();
+
+// Returns the number of bytes in a memory page. Do not use this to compute
+// the number of pages in a block of memory for calling mincore(). On some
+// platforms, e.g. iOS, mincore() uses a different page size from what is
+// returned by GetPageSize().
+BASE_EXPORT size_t GetPageSize();
+
+// Returns the maximum number of file descriptors that can be open by a process
+// at once. If the number is unavailable, a conservative best guess is returned.
+BASE_EXPORT size_t GetMaxFds();
+
+#if defined(OS_POSIX)
+// Increases the file descriptor soft limit to |max_descriptors| or the OS hard
+// limit, whichever is lower. If the limit is already higher than
+// |max_descriptors|, then nothing happens.
+BASE_EXPORT void IncreaseFdLimitTo(unsigned int max_descriptors);
+#endif // defined(OS_POSIX)
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
+ defined(OS_ANDROID) || defined(OS_AIX) || defined(OS_FUCHSIA)
+// Data about system-wide memory consumption. Values are in KB. Available on
+// Windows, Mac, Linux, Android and Chrome OS.
+//
+// Total memory are available on all platforms that implement
+// GetSystemMemoryInfo(). Total/free swap memory are available on all platforms
+// except on Mac. Buffers/cached/active_anon/inactive_anon/active_file/
+// inactive_file/dirty/reclaimable/pswpin/pswpout/pgmajfault are available on
+// Linux/Android/Chrome OS. Shmem/slab/gem_objects/gem_size are Chrome OS only.
+// Speculative/file_backed/purgeable are Mac and iOS only.
+// Free is absent on Windows (see "avail_phys" below).
+struct BASE_EXPORT SystemMemoryInfoKB {
+ SystemMemoryInfoKB();
+ SystemMemoryInfoKB(const SystemMemoryInfoKB& other);
+
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<DictionaryValue> ToValue() const;
+
+ int total = 0;
+
+#if !defined(OS_WIN)
+ int free = 0;
+#endif
+
+#if defined(OS_WIN)
+ // "This is the amount of physical memory that can be immediately reused
+ // without having to write its contents to disk first. It is the sum of the
+ // size of the standby, free, and zero lists." (MSDN).
+ // Standby: not modified pages of physical ram (file-backed memory) that are
+ // not actively being used.
+ int avail_phys = 0;
+#endif
+
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
+ // This provides an estimate of available memory as described here:
+ // https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+ // NOTE: this is ONLY valid in kernels 3.14 and up. Its value will always
+ // be 0 in earlier kernel versions.
+ // Note: it includes _all_ file-backed memory (active + inactive).
+ int available = 0;
+#endif
+
+#if !defined(OS_MACOSX)
+ int swap_total = 0;
+ int swap_free = 0;
+#endif
+
+#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_AIX) || \
+ defined(OS_FUCHSIA)
+ int buffers = 0;
+ int cached = 0;
+ int active_anon = 0;
+ int inactive_anon = 0;
+ int active_file = 0;
+ int inactive_file = 0;
+ int dirty = 0;
+ int reclaimable = 0;
+#endif // defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_AIX) ||
+ // defined(OS_FUCHSIA)
+
+#if defined(OS_CHROMEOS)
+ int shmem = 0;
+ int slab = 0;
+ // Gem data will be -1 if not supported.
+ int gem_objects = -1;
+ long long gem_size = -1;
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_MACOSX)
+ int speculative = 0;
+ int file_backed = 0;
+ int purgeable = 0;
+#endif // defined(OS_MACOSX)
+};
+
+// On Linux/Android/Chrome OS, system-wide memory consumption data is parsed
+// from /proc/meminfo and /proc/vmstat. On Windows/Mac, it is obtained using
+// system API calls.
+//
+// Fills in the provided |meminfo| structure. Returns true on success.
+// Exposed for memory debugging widget.
+BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo);
+
+#endif // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) ||
+ // defined(OS_ANDROID) || defined(OS_AIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
+// Parse the data found in /proc/<pid>/stat and return the sum of the
+// CPU-related ticks. Returns -1 on parse error.
+// Exposed for testing.
+BASE_EXPORT int ParseProcStatCPU(StringPiece input);
+
+// Get the number of threads of |process| as available in /proc/<pid>/stat.
+// This should be used with care as no synchronization with running threads is
+// done. This is mostly useful to guarantee being single-threaded.
+// Returns 0 on failure.
+BASE_EXPORT int GetNumberOfThreads(ProcessHandle process);
+
+// /proc/self/exe refers to the current executable.
+BASE_EXPORT extern const char kProcSelfExe[];
+
+// Parses a string containing the contents of /proc/meminfo
+// returns true on success or false for a parsing error
+// Exposed for testing.
+BASE_EXPORT bool ParseProcMeminfo(StringPiece input,
+ SystemMemoryInfoKB* meminfo);
+
+// Data from /proc/vmstat.
+struct BASE_EXPORT VmStatInfo {
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<DictionaryValue> ToValue() const;
+
+ unsigned long pswpin = 0;
+ unsigned long pswpout = 0;
+ unsigned long pgmajfault = 0;
+};
+
+// Retrieves data from /proc/vmstat about system-wide vm operations.
+// Fills in the provided |vmstat| structure. Returns true on success.
+BASE_EXPORT bool GetVmStatInfo(VmStatInfo* vmstat);
+
+// Parses a string containing the contents of /proc/vmstat
+// returns true on success or false for a parsing error
+// Exposed for testing.
+BASE_EXPORT bool ParseProcVmstat(StringPiece input, VmStatInfo* vmstat);
+
+// Data from /proc/diskstats about system-wide disk I/O.
+struct BASE_EXPORT SystemDiskInfo {
+ SystemDiskInfo();
+ SystemDiskInfo(const SystemDiskInfo& other);
+
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<Value> ToValue() const;
+
+ uint64_t reads = 0;
+ uint64_t reads_merged = 0;
+ uint64_t sectors_read = 0;
+ uint64_t read_time = 0;
+ uint64_t writes = 0;
+ uint64_t writes_merged = 0;
+ uint64_t sectors_written = 0;
+ uint64_t write_time = 0;
+ uint64_t io = 0;
+ uint64_t io_time = 0;
+ uint64_t weighted_io_time = 0;
+};
+
+// Checks whether the candidate string is a valid disk name, [hsv]d[a-z]+
+// for a generic disk or mmcblk[0-9]+ for the MMC case.
+// Names of disk partitions (e.g. sda1) are not valid.
+BASE_EXPORT bool IsValidDiskName(StringPiece candidate);
+
+// Retrieves data from /proc/diskstats about system-wide disk I/O.
+// Fills in the provided |diskinfo| structure. Returns true on success.
+BASE_EXPORT bool GetSystemDiskInfo(SystemDiskInfo* diskinfo);
+
+// Returns the amount of time spent in user space since boot across all CPUs.
+BASE_EXPORT TimeDelta GetUserCpuTimeSinceBoot();
+
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+#if defined(OS_CHROMEOS)
+// Data from files in directory /sys/block/zram0 about ZRAM usage.
+struct BASE_EXPORT SwapInfo {
+ SwapInfo()
+ : num_reads(0),
+ num_writes(0),
+ compr_data_size(0),
+ orig_data_size(0),
+ mem_used_total(0) {
+ }
+
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<Value> ToValue() const;
+
+ uint64_t num_reads = 0;
+ uint64_t num_writes = 0;
+ uint64_t compr_data_size = 0;
+ uint64_t orig_data_size = 0;
+ uint64_t mem_used_total = 0;
+};
+
+// Parses a string containing the contents of /sys/block/zram0/mm_stat.
+// This should be used for the new ZRAM sysfs interfaces.
+// Returns true on success or false for a parsing error.
+// Exposed for testing.
+BASE_EXPORT bool ParseZramMmStat(StringPiece mm_stat_data, SwapInfo* swap_info);
+
+// Parses a string containing the contents of /sys/block/zram0/stat
+// This should be used for the new ZRAM sysfs interfaces.
+// Returns true on success or false for a parsing error.
+// Exposed for testing.
+BASE_EXPORT bool ParseZramStat(StringPiece stat_data, SwapInfo* swap_info);
+
+// In ChromeOS, reads files from /sys/block/zram0 that contain ZRAM usage data.
+// Fills in the provided |swap_data| structure.
+// Returns true on success or false for a parsing error.
+BASE_EXPORT bool GetSwapInfo(SwapInfo* swap_info);
+#endif // defined(OS_CHROMEOS)
+
+struct BASE_EXPORT SystemPerformanceInfo {
+ SystemPerformanceInfo();
+ SystemPerformanceInfo(const SystemPerformanceInfo& other);
+
+ // Serializes the platform specific fields to value.
+ std::unique_ptr<Value> ToValue() const;
+
+ // Total idle time of all processes in the system (units of 100 ns).
+ uint64_t idle_time = 0;
+ // Number of bytes read.
+ uint64_t read_transfer_count = 0;
+ // Number of bytes written.
+ uint64_t write_transfer_count = 0;
+ // Number of bytes transferred (e.g. DeviceIoControlFile)
+ uint64_t other_transfer_count = 0;
+ // The amount of read operations.
+ uint64_t read_operation_count = 0;
+ // The amount of write operations.
+ uint64_t write_operation_count = 0;
+ // The amount of other operations.
+ uint64_t other_operation_count = 0;
+ // The number of pages written to the system's pagefiles.
+ uint64_t pagefile_pages_written = 0;
+ // The number of write operations performed on the system's pagefiles.
+ uint64_t pagefile_pages_write_ios = 0;
+ // The number of pages of physical memory available to processes running on
+ // the system.
+ uint64_t available_pages = 0;
+ // The number of pages read from disk to resolve page faults.
+ uint64_t pages_read = 0;
+ // The number of read operations initiated to resolve page faults.
+ uint64_t page_read_ios = 0;
+};
+
+// Retrieves performance counters from the operating system.
+// Fills in the provided |info| structure. Returns true on success.
+BASE_EXPORT bool GetSystemPerformanceInfo(SystemPerformanceInfo* info);
+
+// Collects and holds performance metrics for system memory and disk.
+// Provides functionality to retrieve the data on various platforms and
+// to serialize the stored data.
+class SystemMetrics {
+ public:
+ SystemMetrics();
+
+ static SystemMetrics Sample();
+
+ // Serializes the system metrics to value.
+ std::unique_ptr<Value> ToValue() const;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(SystemMetricsTest, SystemMetrics);
+
+ size_t committed_memory_;
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ SystemMemoryInfoKB memory_info_;
+ VmStatInfo vmstat_info_;
+ SystemDiskInfo disk_info_;
+#endif
+#if defined(OS_CHROMEOS)
+ SwapInfo swap_info_;
+#endif
+#if defined(OS_WIN)
+ SystemPerformanceInfo performance_;
+#endif
+};
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+enum class MachVMRegionResult {
+ // There were no more memory regions between |address| and the end of the
+ // virtual address space.
+ Finished,
+
+ // All output parameters are invalid.
+ Error,
+
+ // All output parameters are filled in.
+ Success
+};
+
+// Returns info on the first memory region at or after |address|, including
+// resident memory and share mode. On Success, |size| reflects the size of the
+// memory region.
+// |size| and |info| are output parameters, only valid on Success.
+// |address| is an in-out parameter, than represents both the address to start
+// looking, and the start address of the memory region.
+BASE_EXPORT MachVMRegionResult GetTopInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_top_info_data_t* info);
+
+// Returns info on the first memory region at or after |address|, including
+// protection values. On Success, |size| reflects the size of the
+// memory region.
+// Returns info on the first memory region at or after |address|, including
+// resident memory and share mode.
+// |size| and |info| are output parameters, only valid on Success.
+BASE_EXPORT MachVMRegionResult GetBasicInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_basic_info_64* info);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+} // namespace base
+
+#endif // !defined(STARBOARD)
+#endif // BASE_PROCESS_PROCESS_METRICS_H_
diff --git a/src/base/process/process_metrics_freebsd.cc b/src/base/process/process_metrics_freebsd.cc
new file mode 100644
index 0000000..a5cf58d
--- /dev/null
+++ b/src/base/process/process_metrics_freebsd.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <unistd.h>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/process_metrics_iocounters.h"
+#include "base/stl_util.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessMetrics::ProcessMetrics(ProcessHandle process)
+ : process_(process),
+ last_cpu_(0) {}
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ return WrapUnique(new ProcessMetrics(process));
+}
+
+double ProcessMetrics::GetPlatformIndependentCPUUsage() {
+ struct kinfo_proc info;
+ int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, process_};
+ size_t length = sizeof(info);
+
+ if (sysctl(mib, base::size(mib), &info, &length, NULL, 0) < 0)
+ return 0;
+
+ return (info.ki_pctcpu / FSCALE) * 100.0;
+}
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ NOTREACHED();
+ return TimeDelta();
+}
+
+bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
+ return false;
+}
+
+size_t GetSystemCommitCharge() {
+ int mib[2], pagesize;
+ unsigned long mem_total, mem_free, mem_inactive;
+ size_t length = sizeof(mem_total);
+
+ if (sysctl(mib, base::size(mib), &mem_total, &length, NULL, 0) < 0)
+ return 0;
+
+ length = sizeof(mem_free);
+ if (sysctlbyname("vm.stats.vm.v_free_count", &mem_free, &length, NULL, 0) < 0)
+ return 0;
+
+ length = sizeof(mem_inactive);
+ if (sysctlbyname("vm.stats.vm.v_inactive_count", &mem_inactive, &length,
+ NULL, 0) < 0) {
+ return 0;
+ }
+
+ pagesize = getpagesize();
+
+ return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize);
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_fuchsia.cc b/src/base/process/process_metrics_fuchsia.cc
new file mode 100644
index 0000000..0ac7969
--- /dev/null
+++ b/src/base/process/process_metrics_fuchsia.cc
@@ -0,0 +1,40 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <lib/fdio/limits.h>
+
+#include "starboard/types.h"
+
+namespace base {
+
+size_t GetMaxFds() {
+ return FDIO_MAX_FD;
+}
+
+size_t GetSystemCommitCharge() {
+ // Not available, doesn't seem likely that it will be (for the whole system).
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ NOTIMPLEMENTED(); // TODO(fuchsia): https://crbug.com/706592.
+ return nullptr;
+}
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ NOTIMPLEMENTED(); // TODO(fuchsia): https://crbug.com/706592.
+ return TimeDelta();
+}
+
+bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
+ NOTIMPLEMENTED(); // TODO(fuchsia): https://crbug.com/706592.
+ return false;
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_iocounters.h b/src/base/process/process_metrics_iocounters.h
new file mode 100644
index 0000000..fd8d5c2
--- /dev/null
+++ b/src/base/process/process_metrics_iocounters.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This is a separate file so that users of process metrics don't need to
+// include windows.h unless they need IoCounters.
+
+#ifndef BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
+#define BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
+
+#include "base/process/process_metrics.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+#if defined(OS_WIN)
+struct IoCounters : public IO_COUNTERS {};
+#elif defined(OS_POSIX)
+struct IoCounters {
+ uint64_t ReadOperationCount;
+ uint64_t WriteOperationCount;
+ uint64_t OtherOperationCount;
+ uint64_t ReadTransferCount;
+ uint64_t WriteTransferCount;
+ uint64_t OtherTransferCount;
+};
+#endif
+
+} // namespace base
+
+#endif // BASE_PROCESS_PROCESS_METRICS_IOCOUNTERS_H_
diff --git a/src/base/process/process_metrics_ios.cc b/src/base/process/process_metrics_ios.cc
new file mode 100644
index 0000000..32a3d16
--- /dev/null
+++ b/src/base/process/process_metrics_ios.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <limits.h>
+#include <mach/task.h>
+
+#include "base/logging.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "starboard/types.h"
+
+namespace base {
+
+ProcessMetrics::ProcessMetrics(ProcessHandle process) {}
+
+ProcessMetrics::~ProcessMetrics() {}
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ return WrapUnique(new ProcessMetrics(process));
+}
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ NOTIMPLEMENTED();
+ return TimeDelta();
+}
+
+size_t GetMaxFds() {
+ static const rlim_t kSystemDefaultMaxFds = 256;
+ rlim_t max_fds;
+ struct rlimit nofile;
+ if (getrlimit(RLIMIT_NOFILE, &nofile)) {
+ // Error case: Take a best guess.
+ max_fds = kSystemDefaultMaxFds;
+ } else {
+ max_fds = nofile.rlim_cur;
+ }
+
+ if (max_fds > INT_MAX)
+ max_fds = INT_MAX;
+
+ return static_cast<size_t>(max_fds);
+}
+
+void IncreaseFdLimitTo(unsigned int max_descriptors) {
+ // Unimplemented.
+}
+
+size_t GetPageSize() {
+ return getpagesize();
+}
+
+// Bytes committed by the system.
+size_t GetSystemCommitCharge() {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
+ struct host_basic_info hostinfo;
+ mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
+ base::mac::ScopedMachSendRight host(mach_host_self());
+ int result = host_info(host.get(), HOST_BASIC_INFO,
+ reinterpret_cast<host_info_t>(&hostinfo), &count);
+ if (result != KERN_SUCCESS)
+ return false;
+
+ DCHECK_EQ(HOST_BASIC_INFO_COUNT, count);
+ meminfo->total = static_cast<int>(hostinfo.max_mem / 1024);
+
+ vm_statistics64_data_t vm_info;
+ count = HOST_VM_INFO64_COUNT;
+
+ if (host_statistics64(host.get(), HOST_VM_INFO64,
+ reinterpret_cast<host_info64_t>(&vm_info),
+ &count) != KERN_SUCCESS) {
+ return false;
+ }
+ DCHECK_EQ(HOST_VM_INFO64_COUNT, count);
+
+ // Check that PAGE_SIZE is divisible by 1024 (2^10).
+ CHECK_EQ(PAGE_SIZE, (PAGE_SIZE >> 10) << 10);
+ meminfo->free = saturated_cast<int>(
+ PAGE_SIZE / 1024 * (vm_info.free_count - vm_info.speculative_count));
+ meminfo->speculative =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.speculative_count);
+ meminfo->file_backed =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.external_page_count);
+ meminfo->purgeable =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.purgeable_count);
+
+ return true;
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_linux.cc b/src/base/process/process_metrics_linux.cc
new file mode 100644
index 0000000..3df08cc
--- /dev/null
+++ b/src/base/process/process_metrics_linux.cc
@@ -0,0 +1,976 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utility>
+
+#include "base/files/dir_reader_posix.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/optional.h"
+#include "base/process/internal_linux.h"
+#include "base/process/process_metrics_iocounters.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_tokenizer.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "starboard/character.h"
+#include "starboard/string.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+void TrimKeyValuePairs(StringPairs* pairs) {
+ for (auto& pair : *pairs) {
+ TrimWhitespaceASCII(pair.first, TRIM_ALL, &pair.first);
+ TrimWhitespaceASCII(pair.second, TRIM_ALL, &pair.second);
+ }
+}
+
+#if defined(OS_CHROMEOS)
+// Read a file with a single number string and return the number as a uint64_t.
+uint64_t ReadFileToUint64(const FilePath& file) {
+ std::string file_contents;
+ if (!ReadFileToString(file, &file_contents))
+ return 0;
+ TrimWhitespaceASCII(file_contents, TRIM_ALL, &file_contents);
+ uint64_t file_contents_uint64 = 0;
+ if (!StringToUint64(file_contents, &file_contents_uint64))
+ return 0;
+ return file_contents_uint64;
+}
+#endif
+
+// Read |filename| in /proc/<pid>/, split the entries into key/value pairs, and
+// trim the key and value. On success, return true and write the trimmed
+// key/value pairs into |key_value_pairs|.
+bool ReadProcFileToTrimmedStringPairs(pid_t pid,
+ StringPiece filename,
+ StringPairs* key_value_pairs) {
+ std::string status_data;
+ {
+ // Synchronously reading files in /proc does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ FilePath status_file = internal::GetProcPidDir(pid).Append(filename);
+ if (!ReadFileToString(status_file, &status_data))
+ return false;
+ }
+ SplitStringIntoKeyValuePairs(status_data, ':', '\n', key_value_pairs);
+ TrimKeyValuePairs(key_value_pairs);
+ return true;
+}
+
+// Read /proc/<pid>/status and return the value for |field|, or 0 on failure.
+// Only works for fields in the form of "Field: value kB".
+size_t ReadProcStatusAndGetFieldAsSizeT(pid_t pid, StringPiece field) {
+ StringPairs pairs;
+ if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
+ return 0;
+
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key != field)
+ continue;
+
+ std::vector<StringPiece> split_value_str =
+ SplitStringPiece(value_str, " ", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+ if (split_value_str.size() != 2 || split_value_str[1] != "kB") {
+ NOTREACHED();
+ return 0;
+ }
+ size_t value;
+ if (!StringToSizeT(split_value_str[0], &value)) {
+ NOTREACHED();
+ return 0;
+ }
+ return value;
+ }
+ // This can be reached if the process dies when proc is read -- in that case,
+ // the kernel can return missing fields.
+ return 0;
+}
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+// Read /proc/<pid>/status and look for |field|. On success, return true and
+// write the value for |field| into |result|.
+// Only works for fields in the form of "field : uint_value"
+bool ReadProcStatusAndGetFieldAsUint64(pid_t pid,
+ StringPiece field,
+ uint64_t* result) {
+ StringPairs pairs;
+ if (!ReadProcFileToTrimmedStringPairs(pid, "status", &pairs))
+ return false;
+
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ if (key != field)
+ continue;
+
+ uint64_t value;
+ if (!StringToUint64(value_str, &value))
+ return false;
+ *result = value;
+ return true;
+ }
+ return false;
+}
+#endif // defined(OS_LINUX) || defined(OS_AIX)
+
+// Get the total CPU of a single process. Return value is number of jiffies
+// on success or -1 on error.
+int64_t GetProcessCPU(pid_t pid) {
+ std::string buffer;
+ std::vector<std::string> proc_stats;
+ if (!internal::ReadProcStats(pid, &buffer) ||
+ !internal::ParseProcStats(buffer, &proc_stats)) {
+ return -1;
+ }
+
+ int64_t total_cpu =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME) +
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME);
+
+ return total_cpu;
+}
+
+#if defined(OS_CHROMEOS)
+// Report on Chrome OS GEM object graphics memory. /run/debugfs_gpu is a
+// bind mount into /sys/kernel/debug and synchronously reading the in-memory
+// files in /sys is fast.
+void ReadChromeOSGraphicsMemory(SystemMemoryInfoKB* meminfo) {
+#if defined(ARCH_CPU_ARM_FAMILY)
+ FilePath geminfo_file("/run/debugfs_gpu/exynos_gem_objects");
+#else
+ FilePath geminfo_file("/run/debugfs_gpu/i915_gem_objects");
+#endif
+ std::string geminfo_data;
+ meminfo->gem_objects = -1;
+ meminfo->gem_size = -1;
+ if (ReadFileToString(geminfo_file, &geminfo_data)) {
+ int gem_objects = -1;
+ long long gem_size = -1;
+ int num_res = sscanf(geminfo_data.c_str(), "%d objects, %lld bytes",
+ &gem_objects, &gem_size);
+ if (num_res == 2) {
+ meminfo->gem_objects = gem_objects;
+ meminfo->gem_size = gem_size;
+ }
+ }
+
+#if defined(ARCH_CPU_ARM_FAMILY)
+ // Incorporate Mali graphics memory if present.
+ FilePath mali_memory_file("/sys/class/misc/mali0/device/memory");
+ std::string mali_memory_data;
+ if (ReadFileToString(mali_memory_file, &mali_memory_data)) {
+ long long mali_size = -1;
+ int num_res = sscanf(mali_memory_data.c_str(), "%lld bytes", &mali_size);
+ if (num_res == 1)
+ meminfo->gem_size += mali_size;
+ }
+#endif // defined(ARCH_CPU_ARM_FAMILY)
+}
+#endif // defined(OS_CHROMEOS)
+
+} // namespace
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ return WrapUnique(new ProcessMetrics(process));
+}
+
+size_t ProcessMetrics::GetResidentSetSize() const {
+ return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) *
+ getpagesize();
+}
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ return internal::ClockTicksToTimeDelta(GetProcessCPU(process_));
+}
+
+// For the /proc/self/io file to exist, the Linux kernel must have
+// CONFIG_TASK_IO_ACCOUNTING enabled.
+bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
+ StringPairs pairs;
+ if (!ReadProcFileToTrimmedStringPairs(process_, "io", &pairs))
+ return false;
+
+ io_counters->OtherOperationCount = 0;
+ io_counters->OtherTransferCount = 0;
+
+ for (const auto& pair : pairs) {
+ const std::string& key = pair.first;
+ const std::string& value_str = pair.second;
+ uint64_t* target_counter = nullptr;
+ if (key == "syscr")
+ target_counter = &io_counters->ReadOperationCount;
+ else if (key == "syscw")
+ target_counter = &io_counters->WriteOperationCount;
+ else if (key == "rchar")
+ target_counter = &io_counters->ReadTransferCount;
+ else if (key == "wchar")
+ target_counter = &io_counters->WriteTransferCount;
+ if (!target_counter)
+ continue;
+ bool converted = StringToUint64(value_str, target_counter);
+ DCHECK(converted);
+ }
+ return true;
+}
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+uint64_t ProcessMetrics::GetVmSwapBytes() const {
+ return ReadProcStatusAndGetFieldAsSizeT(process_, "VmSwap") * 1024;
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+bool ProcessMetrics::GetPageFaultCounts(PageFaultCounts* counts) const {
+ // We are not using internal::ReadStatsFileAndGetFieldAsInt64(), since it
+ // would read the file twice, and return inconsistent numbers.
+ std::string stats_data;
+ if (!internal::ReadProcStats(process_, &stats_data))
+ return false;
+ std::vector<std::string> proc_stats;
+ if (!internal::ParseProcStats(stats_data, &proc_stats))
+ return false;
+
+ counts->minor =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MINFLT);
+ counts->major =
+ internal::GetProcStatsFieldAsInt64(proc_stats, internal::VM_MAJFLT);
+ return true;
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+int ProcessMetrics::GetOpenFdCount() const {
+ // Use /proc/<pid>/fd to count the number of entries there.
+ FilePath fd_path = internal::GetProcPidDir(process_).Append("fd");
+
+ DirReaderPosix dir_reader(fd_path.value().c_str());
+ if (!dir_reader.IsValid())
+ return -1;
+
+ int total_count = 0;
+ for (; dir_reader.Next(); ) {
+ const char* name = dir_reader.name();
+ if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
+ ++total_count;
+ }
+
+ return total_count;
+}
+
+int ProcessMetrics::GetOpenFdSoftLimit() const {
+ // Use /proc/<pid>/limits to read the open fd limit.
+ FilePath fd_path = internal::GetProcPidDir(process_).Append("limits");
+
+ std::string limits_contents;
+ if (!ReadFileToString(fd_path, &limits_contents))
+ return -1;
+
+ for (const auto& line : SplitStringPiece(
+ limits_contents, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
+ if (!line.starts_with("Max open files"))
+ continue;
+
+ auto tokens =
+ SplitStringPiece(line, " ", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() > 3) {
+ int limit = -1;
+ if (!StringToInt(tokens[3], &limit))
+ return -1;
+ return limit;
+ }
+ }
+ return -1;
+}
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+ProcessMetrics::ProcessMetrics(ProcessHandle process)
+ : process_(process), last_absolute_idle_wakeups_(0) {}
+#else
+ProcessMetrics::ProcessMetrics(ProcessHandle process) : process_(process) {}
+#endif
+
+#if defined(OS_CHROMEOS)
+// Private, Shared and Proportional working set sizes are obtained from
+// /proc/<pid>/totmaps
+ProcessMetrics::TotalsSummary ProcessMetrics::GetTotalsSummary() const {
+ // The format of /proc/<pid>/totmaps is:
+ //
+ // Rss: 6120 kB
+ // Pss: 3335 kB
+ // Shared_Clean: 1008 kB
+ // Shared_Dirty: 4012 kB
+ // Private_Clean: 4 kB
+ // Private_Dirty: 1096 kB
+ // Referenced: XXX kB
+ // Anonymous: XXX kB
+ // AnonHugePages: XXX kB
+ // Swap: XXX kB
+ // Locked: XXX kB
+ ProcessMetrics::TotalsSummary summary = {};
+
+ const size_t kPrivate_CleanIndex = (4 * 3) + 1;
+ const size_t kPrivate_DirtyIndex = (5 * 3) + 1;
+ const size_t kSwapIndex = (9 * 3) + 1;
+
+ std::string totmaps_data;
+ {
+ FilePath totmaps_file = internal::GetProcPidDir(process_).Append("totmaps");
+ ThreadRestrictions::ScopedAllowIO allow_io;
+ bool ret = ReadFileToString(totmaps_file, &totmaps_data);
+ if (!ret || totmaps_data.length() == 0)
+ return summary;
+ }
+
+ std::vector<std::string> totmaps_fields = SplitString(
+ totmaps_data, kWhitespaceASCII, KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ DCHECK_EQ("Private_Clean:", totmaps_fields[kPrivate_CleanIndex - 1]);
+ DCHECK_EQ("Private_Dirty:", totmaps_fields[kPrivate_DirtyIndex - 1]);
+ DCHECK_EQ("Swap:", totmaps_fields[kSwapIndex-1]);
+
+ int private_clean_kb = 0;
+ int private_dirty_kb = 0;
+ int swap_kb = 0;
+ bool success = true;
+ success &=
+ StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean_kb);
+ success &=
+ StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty_kb);
+ success &= StringToInt(totmaps_fields[kSwapIndex], &swap_kb);
+
+ if (!success)
+ return summary;
+
+ summary.private_clean_kb = private_clean_kb;
+ summary.private_dirty_kb = private_dirty_kb;
+ summary.swap_kb = swap_kb;
+
+ return summary;
+}
+#endif
+
+size_t GetSystemCommitCharge() {
+ SystemMemoryInfoKB meminfo;
+ if (!GetSystemMemoryInfo(&meminfo))
+ return 0;
+ return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached;
+}
+
+int ParseProcStatCPU(StringPiece input) {
+ // |input| may be empty if the process disappeared somehow.
+ // e.g. http://crbug.com/145811.
+ if (input.empty())
+ return -1;
+
+ size_t start = input.find_last_of(')');
+ if (start == input.npos)
+ return -1;
+
+ // Number of spaces remaining until reaching utime's index starting after the
+ // last ')'.
+ int num_spaces_remaining = internal::VM_UTIME - 1;
+
+ size_t i = start;
+ while ((i = input.find(' ', i + 1)) != input.npos) {
+ // Validate the assumption that there aren't any contiguous spaces
+ // in |input| before utime.
+ DCHECK_NE(input[i - 1], ' ');
+ if (--num_spaces_remaining == 0) {
+ int utime = 0;
+ int stime = 0;
+ if (sscanf(&input.data()[i], "%d %d", &utime, &stime) != 2)
+ return -1;
+
+ return utime + stime;
+ }
+ }
+
+ return -1;
+}
+
+int GetNumberOfThreads(ProcessHandle process) {
+ return internal::ReadProcStatsAndGetFieldAsInt64(process,
+ internal::VM_NUMTHREADS);
+}
+
+const char kProcSelfExe[] = "/proc/self/exe";
+
+namespace {
+
+// The format of /proc/diskstats is:
+// Device major number
+// Device minor number
+// Device name
+// Field 1 -- # of reads completed
+// This is the total number of reads completed successfully.
+// Field 2 -- # of reads merged, field 6 -- # of writes merged
+// Reads and writes which are adjacent to each other may be merged for
+// efficiency. Thus two 4K reads may become one 8K read before it is
+// ultimately handed to the disk, and so it will be counted (and queued)
+// as only one I/O. This field lets you know how often this was done.
+// Field 3 -- # of sectors read
+// This is the total number of sectors read successfully.
+// Field 4 -- # of milliseconds spent reading
+// This is the total number of milliseconds spent by all reads (as
+// measured from __make_request() to end_that_request_last()).
+// Field 5 -- # of writes completed
+// This is the total number of writes completed successfully.
+// Field 6 -- # of writes merged
+// See the description of field 2.
+// Field 7 -- # of sectors written
+// This is the total number of sectors written successfully.
+// Field 8 -- # of milliseconds spent writing
+// This is the total number of milliseconds spent by all writes (as
+// measured from __make_request() to end_that_request_last()).
+// Field 9 -- # of I/Os currently in progress
+// The only field that should go to zero. Incremented as requests are
+// given to appropriate struct request_queue and decremented as they
+// finish.
+// Field 10 -- # of milliseconds spent doing I/Os
+// This field increases so long as field 9 is nonzero.
+// Field 11 -- weighted # of milliseconds spent doing I/Os
+// This field is incremented at each I/O start, I/O completion, I/O
+// merge, or read of these stats by the number of I/Os in progress
+// (field 9) times the number of milliseconds spent doing I/O since the
+// last update of this field. This can provide an easy measure of both
+// I/O completion time and the backlog that may be accumulating.
+
+const size_t kDiskDriveName = 2;
+const size_t kDiskReads = 3;
+const size_t kDiskReadsMerged = 4;
+const size_t kDiskSectorsRead = 5;
+const size_t kDiskReadTime = 6;
+const size_t kDiskWrites = 7;
+const size_t kDiskWritesMerged = 8;
+const size_t kDiskSectorsWritten = 9;
+const size_t kDiskWriteTime = 10;
+const size_t kDiskIO = 11;
+const size_t kDiskIOTime = 12;
+const size_t kDiskWeightedIOTime = 13;
+
+} // namespace
+
+std::unique_ptr<DictionaryValue> SystemMemoryInfoKB::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
+ res->SetInteger("total", total);
+ res->SetInteger("free", free);
+ res->SetInteger("available", available);
+ res->SetInteger("buffers", buffers);
+ res->SetInteger("cached", cached);
+ res->SetInteger("active_anon", active_anon);
+ res->SetInteger("inactive_anon", inactive_anon);
+ res->SetInteger("active_file", active_file);
+ res->SetInteger("inactive_file", inactive_file);
+ res->SetInteger("swap_total", swap_total);
+ res->SetInteger("swap_free", swap_free);
+ res->SetInteger("swap_used", swap_total - swap_free);
+ res->SetInteger("dirty", dirty);
+ res->SetInteger("reclaimable", reclaimable);
+#ifdef OS_CHROMEOS
+ res->SetInteger("shmem", shmem);
+ res->SetInteger("slab", slab);
+ res->SetInteger("gem_objects", gem_objects);
+ res->SetInteger("gem_size", gem_size);
+#endif
+
+ return res;
+}
+
+bool ParseProcMeminfo(StringPiece meminfo_data, SystemMemoryInfoKB* meminfo) {
+ // The format of /proc/meminfo is:
+ //
+ // MemTotal: 8235324 kB
+ // MemFree: 1628304 kB
+ // Buffers: 429596 kB
+ // Cached: 4728232 kB
+ // ...
+ // There is no guarantee on the ordering or position
+ // though it doesn't appear to change very often
+
+ // As a basic sanity check at the end, make sure the MemTotal value will be at
+ // least non-zero. So start off with a zero total.
+ meminfo->total = 0;
+
+ for (const StringPiece& line : SplitStringPiece(
+ meminfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ // HugePages_* only has a number and no suffix so there may not be exactly 3
+ // tokens.
+ if (tokens.size() <= 1) {
+ DLOG(WARNING) << "meminfo: tokens: " << tokens.size()
+ << " malformed line: " << line.as_string();
+ continue;
+ }
+
+ int* target = nullptr;
+ if (tokens[0] == "MemTotal:")
+ target = &meminfo->total;
+ else if (tokens[0] == "MemFree:")
+ target = &meminfo->free;
+ else if (tokens[0] == "MemAvailable:")
+ target = &meminfo->available;
+ else if (tokens[0] == "Buffers:")
+ target = &meminfo->buffers;
+ else if (tokens[0] == "Cached:")
+ target = &meminfo->cached;
+ else if (tokens[0] == "Active(anon):")
+ target = &meminfo->active_anon;
+ else if (tokens[0] == "Inactive(anon):")
+ target = &meminfo->inactive_anon;
+ else if (tokens[0] == "Active(file):")
+ target = &meminfo->active_file;
+ else if (tokens[0] == "Inactive(file):")
+ target = &meminfo->inactive_file;
+ else if (tokens[0] == "SwapTotal:")
+ target = &meminfo->swap_total;
+ else if (tokens[0] == "SwapFree:")
+ target = &meminfo->swap_free;
+ else if (tokens[0] == "Dirty:")
+ target = &meminfo->dirty;
+ else if (tokens[0] == "SReclaimable:")
+ target = &meminfo->reclaimable;
+#if defined(OS_CHROMEOS)
+ // Chrome OS has a tweaked kernel that allows querying Shmem, which is
+ // usually video memory otherwise invisible to the OS.
+ else if (tokens[0] == "Shmem:")
+ target = &meminfo->shmem;
+ else if (tokens[0] == "Slab:")
+ target = &meminfo->slab;
+#endif
+ if (target)
+ StringToInt(tokens[1], target);
+ }
+
+ // Make sure the MemTotal is valid.
+ return meminfo->total > 0;
+}
+
+bool ParseProcVmstat(StringPiece vmstat_data, VmStatInfo* vmstat) {
+ // The format of /proc/vmstat is:
+ //
+ // nr_free_pages 299878
+ // nr_inactive_anon 239863
+ // nr_active_anon 1318966
+ // nr_inactive_file 2015629
+ // ...
+ //
+ // Iterate through the whole file because the position of the
+ // fields are dependent on the kernel version and configuration.
+ bool has_pswpin = false;
+ bool has_pswpout = false;
+ bool has_pgmajfault = false;
+ for (const StringPiece& line : SplitStringPiece(
+ vmstat_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY)) {
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ line, " ", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() != 2)
+ continue;
+
+ uint64_t val;
+ if (!StringToUint64(tokens[1], &val))
+ continue;
+
+ if (tokens[0] == "pswpin") {
+ vmstat->pswpin = val;
+ DCHECK(!has_pswpin);
+ has_pswpin = true;
+ } else if (tokens[0] == "pswpout") {
+ vmstat->pswpout = val;
+ DCHECK(!has_pswpout);
+ has_pswpout = true;
+ } else if (tokens[0] == "pgmajfault") {
+ vmstat->pgmajfault = val;
+ DCHECK(!has_pgmajfault);
+ has_pgmajfault = true;
+ }
+ if (has_pswpin && has_pswpout && has_pgmajfault)
+ return true;
+ }
+
+ return false;
+}
+
+bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
+ // Synchronously reading files in /proc and /sys are safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Used memory is: total - free - buffers - caches
+ FilePath meminfo_file("/proc/meminfo");
+ std::string meminfo_data;
+ if (!ReadFileToString(meminfo_file, &meminfo_data)) {
+ DLOG(WARNING) << "Failed to open " << meminfo_file.value();
+ return false;
+ }
+
+ if (!ParseProcMeminfo(meminfo_data, meminfo)) {
+ DLOG(WARNING) << "Failed to parse " << meminfo_file.value();
+ return false;
+ }
+
+#if defined(OS_CHROMEOS)
+ ReadChromeOSGraphicsMemory(meminfo);
+#endif
+
+ return true;
+}
+
+std::unique_ptr<DictionaryValue> VmStatInfo::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
+ res->SetInteger("pswpin", pswpin);
+ res->SetInteger("pswpout", pswpout);
+ res->SetInteger("pgmajfault", pgmajfault);
+ return res;
+}
+
+bool GetVmStatInfo(VmStatInfo* vmstat) {
+ // Synchronously reading files in /proc and /sys are safe.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ FilePath vmstat_file("/proc/vmstat");
+ std::string vmstat_data;
+ if (!ReadFileToString(vmstat_file, &vmstat_data)) {
+ DLOG(WARNING) << "Failed to open " << vmstat_file.value();
+ return false;
+ }
+ if (!ParseProcVmstat(vmstat_data, vmstat)) {
+ DLOG(WARNING) << "Failed to parse " << vmstat_file.value();
+ return false;
+ }
+ return true;
+}
+
+SystemDiskInfo::SystemDiskInfo() {
+ reads = 0;
+ reads_merged = 0;
+ sectors_read = 0;
+ read_time = 0;
+ writes = 0;
+ writes_merged = 0;
+ sectors_written = 0;
+ write_time = 0;
+ io = 0;
+ io_time = 0;
+ weighted_io_time = 0;
+}
+
+SystemDiskInfo::SystemDiskInfo(const SystemDiskInfo& other) = default;
+
+std::unique_ptr<Value> SystemDiskInfo::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
+
+ // Write out uint64_t variables as doubles.
+ // Note: this may discard some precision, but for JS there's no other option.
+ res->SetDouble("reads", static_cast<double>(reads));
+ res->SetDouble("reads_merged", static_cast<double>(reads_merged));
+ res->SetDouble("sectors_read", static_cast<double>(sectors_read));
+ res->SetDouble("read_time", static_cast<double>(read_time));
+ res->SetDouble("writes", static_cast<double>(writes));
+ res->SetDouble("writes_merged", static_cast<double>(writes_merged));
+ res->SetDouble("sectors_written", static_cast<double>(sectors_written));
+ res->SetDouble("write_time", static_cast<double>(write_time));
+ res->SetDouble("io", static_cast<double>(io));
+ res->SetDouble("io_time", static_cast<double>(io_time));
+ res->SetDouble("weighted_io_time", static_cast<double>(weighted_io_time));
+
+ return std::move(res);
+}
+
+bool IsValidDiskName(StringPiece candidate) {
+ if (candidate.length() < 3)
+ return false;
+
+ if (candidate[1] == 'd' &&
+ (candidate[0] == 'h' || candidate[0] == 's' || candidate[0] == 'v')) {
+ // [hsv]d[a-z]+ case
+ for (size_t i = 2; i < candidate.length(); ++i) {
+ if (!islower(candidate[i]))
+ return false;
+ }
+ return true;
+ }
+
+ const char kMMCName[] = "mmcblk";
+ if (!candidate.starts_with(kMMCName))
+ return false;
+
+ // mmcblk[0-9]+ case
+ for (size_t i = SbStringGetLength(kMMCName); i < candidate.length(); ++i) {
+ if (!SbCharacterIsDigit(candidate[i]))
+ return false;
+ }
+ return true;
+}
+
+bool GetSystemDiskInfo(SystemDiskInfo* diskinfo) {
+ // Synchronously reading files in /proc does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ FilePath diskinfo_file("/proc/diskstats");
+ std::string diskinfo_data;
+ if (!ReadFileToString(diskinfo_file, &diskinfo_data)) {
+ DLOG(WARNING) << "Failed to open " << diskinfo_file.value();
+ return false;
+ }
+
+ std::vector<StringPiece> diskinfo_lines = SplitStringPiece(
+ diskinfo_data, "\n", KEEP_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (diskinfo_lines.empty()) {
+ DLOG(WARNING) << "No lines found";
+ return false;
+ }
+
+ diskinfo->reads = 0;
+ diskinfo->reads_merged = 0;
+ diskinfo->sectors_read = 0;
+ diskinfo->read_time = 0;
+ diskinfo->writes = 0;
+ diskinfo->writes_merged = 0;
+ diskinfo->sectors_written = 0;
+ diskinfo->write_time = 0;
+ diskinfo->io = 0;
+ diskinfo->io_time = 0;
+ diskinfo->weighted_io_time = 0;
+
+ uint64_t reads = 0;
+ uint64_t reads_merged = 0;
+ uint64_t sectors_read = 0;
+ uint64_t read_time = 0;
+ uint64_t writes = 0;
+ uint64_t writes_merged = 0;
+ uint64_t sectors_written = 0;
+ uint64_t write_time = 0;
+ uint64_t io = 0;
+ uint64_t io_time = 0;
+ uint64_t weighted_io_time = 0;
+
+ for (const StringPiece& line : diskinfo_lines) {
+ std::vector<StringPiece> disk_fields = SplitStringPiece(
+ line, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+
+ // Fields may have overflowed and reset to zero.
+ if (!IsValidDiskName(disk_fields[kDiskDriveName].as_string()))
+ continue;
+
+ StringToUint64(disk_fields[kDiskReads], &reads);
+ StringToUint64(disk_fields[kDiskReadsMerged], &reads_merged);
+ StringToUint64(disk_fields[kDiskSectorsRead], §ors_read);
+ StringToUint64(disk_fields[kDiskReadTime], &read_time);
+ StringToUint64(disk_fields[kDiskWrites], &writes);
+ StringToUint64(disk_fields[kDiskWritesMerged], &writes_merged);
+ StringToUint64(disk_fields[kDiskSectorsWritten], §ors_written);
+ StringToUint64(disk_fields[kDiskWriteTime], &write_time);
+ StringToUint64(disk_fields[kDiskIO], &io);
+ StringToUint64(disk_fields[kDiskIOTime], &io_time);
+ StringToUint64(disk_fields[kDiskWeightedIOTime], &weighted_io_time);
+
+ diskinfo->reads += reads;
+ diskinfo->reads_merged += reads_merged;
+ diskinfo->sectors_read += sectors_read;
+ diskinfo->read_time += read_time;
+ diskinfo->writes += writes;
+ diskinfo->writes_merged += writes_merged;
+ diskinfo->sectors_written += sectors_written;
+ diskinfo->write_time += write_time;
+ diskinfo->io += io;
+ diskinfo->io_time += io_time;
+ diskinfo->weighted_io_time += weighted_io_time;
+ }
+
+ return true;
+}
+
+TimeDelta GetUserCpuTimeSinceBoot() {
+ return internal::GetUserCpuTimeSinceBoot();
+}
+
+#if defined(OS_CHROMEOS)
+std::unique_ptr<Value> SwapInfo::ToValue() const {
+ auto res = std::make_unique<DictionaryValue>();
+
+ // Write out uint64_t variables as doubles.
+ // Note: this may discard some precision, but for JS there's no other option.
+ res->SetDouble("num_reads", static_cast<double>(num_reads));
+ res->SetDouble("num_writes", static_cast<double>(num_writes));
+ res->SetDouble("orig_data_size", static_cast<double>(orig_data_size));
+ res->SetDouble("compr_data_size", static_cast<double>(compr_data_size));
+ res->SetDouble("mem_used_total", static_cast<double>(mem_used_total));
+ double ratio = compr_data_size ? static_cast<double>(orig_data_size) /
+ static_cast<double>(compr_data_size)
+ : 0;
+ res->SetDouble("compression_ratio", ratio);
+
+ return std::move(res);
+}
+
+bool ParseZramMmStat(StringPiece mm_stat_data, SwapInfo* swap_info) {
+ // There are 7 columns in /sys/block/zram0/mm_stat,
+ // split by several spaces. The first three columns
+ // are orig_data_size, compr_data_size and mem_used_total.
+ // Example:
+ // 17715200 5008166 566062 0 1225715712 127 183842
+ //
+ // For more details:
+ // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ mm_stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() < 7) {
+ DLOG(WARNING) << "zram mm_stat: tokens: " << tokens.size()
+ << " malformed line: " << mm_stat_data.as_string();
+ return false;
+ }
+
+ if (!StringToUint64(tokens[0], &swap_info->orig_data_size))
+ return false;
+ if (!StringToUint64(tokens[1], &swap_info->compr_data_size))
+ return false;
+ if (!StringToUint64(tokens[2], &swap_info->mem_used_total))
+ return false;
+
+ return true;
+}
+
+bool ParseZramStat(StringPiece stat_data, SwapInfo* swap_info) {
+ // There are 11 columns in /sys/block/zram0/stat,
+ // split by several spaces. The first column is read I/Os
+ // and fifth column is write I/Os.
+ // Example:
+ // 299 0 2392 0 1 0 8 0 0 0 0
+ //
+ // For more details:
+ // https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+
+ std::vector<StringPiece> tokens = SplitStringPiece(
+ stat_data, kWhitespaceASCII, TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
+ if (tokens.size() < 11) {
+ DLOG(WARNING) << "zram stat: tokens: " << tokens.size()
+ << " malformed line: " << stat_data.as_string();
+ return false;
+ }
+
+ if (!StringToUint64(tokens[0], &swap_info->num_reads))
+ return false;
+ if (!StringToUint64(tokens[4], &swap_info->num_writes))
+ return false;
+
+ return true;
+}
+
+namespace {
+
+bool IgnoreZramFirstPage(uint64_t orig_data_size, SwapInfo* swap_info) {
+ if (orig_data_size <= 4096) {
+ // A single page is compressed at startup, and has a high compression
+ // ratio. Ignore this as it doesn't indicate any real swapping.
+ swap_info->orig_data_size = 0;
+ swap_info->num_reads = 0;
+ swap_info->num_writes = 0;
+ swap_info->compr_data_size = 0;
+ swap_info->mem_used_total = 0;
+ return true;
+ }
+ return false;
+}
+
+void ParseZramPath(SwapInfo* swap_info) {
+ FilePath zram_path("/sys/block/zram0");
+ uint64_t orig_data_size =
+ ReadFileToUint64(zram_path.Append("orig_data_size"));
+ if (IgnoreZramFirstPage(orig_data_size, swap_info))
+ return;
+
+ swap_info->orig_data_size = orig_data_size;
+ swap_info->num_reads = ReadFileToUint64(zram_path.Append("num_reads"));
+ swap_info->num_writes = ReadFileToUint64(zram_path.Append("num_writes"));
+ swap_info->compr_data_size =
+ ReadFileToUint64(zram_path.Append("compr_data_size"));
+ swap_info->mem_used_total =
+ ReadFileToUint64(zram_path.Append("mem_used_total"));
+}
+
+bool GetSwapInfoImpl(SwapInfo* swap_info) {
+ // Synchronously reading files in /sys/block/zram0 does not hit the disk.
+ ThreadRestrictions::ScopedAllowIO allow_io;
+
+ // Since ZRAM update, it shows the usage data in different places.
+ // If file "/sys/block/zram0/mm_stat" exists, use the new way, otherwise,
+ // use the old way.
+ static Optional<bool> use_new_zram_interface;
+ FilePath zram_mm_stat_file("/sys/block/zram0/mm_stat");
+ if (!use_new_zram_interface.has_value()) {
+ use_new_zram_interface = PathExists(zram_mm_stat_file);
+ }
+
+ if (!use_new_zram_interface.value()) {
+ ParseZramPath(swap_info);
+ return true;
+ }
+
+ std::string mm_stat_data;
+ if (!ReadFileToString(zram_mm_stat_file, &mm_stat_data)) {
+ DLOG(WARNING) << "Failed to open " << zram_mm_stat_file.value();
+ return false;
+ }
+ if (!ParseZramMmStat(mm_stat_data, swap_info)) {
+ DLOG(WARNING) << "Failed to parse " << zram_mm_stat_file.value();
+ return false;
+ }
+ if (IgnoreZramFirstPage(swap_info->orig_data_size, swap_info))
+ return true;
+
+ FilePath zram_stat_file("/sys/block/zram0/stat");
+ std::string stat_data;
+ if (!ReadFileToString(zram_stat_file, &stat_data)) {
+ DLOG(WARNING) << "Failed to open " << zram_stat_file.value();
+ return false;
+ }
+ if (!ParseZramStat(stat_data, swap_info)) {
+ DLOG(WARNING) << "Failed to parse " << zram_stat_file.value();
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool GetSwapInfo(SwapInfo* swap_info) {
+ if (!GetSwapInfoImpl(swap_info)) {
+ *swap_info = SwapInfo();
+ return false;
+ }
+ return true;
+}
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_LINUX) || defined(OS_AIX)
+int ProcessMetrics::GetIdleWakeupsPerSecond() {
+ uint64_t num_switches;
+ static const char kSwitchStat[] = "voluntary_ctxt_switches";
+ return ReadProcStatusAndGetFieldAsUint64(process_, kSwitchStat, &num_switches)
+ ? CalculateIdleWakeupsPerSecond(num_switches)
+ : 0;
+}
+#endif // defined(OS_LINUX) || defined(OS_AIX)
+
+} // namespace base
diff --git a/src/base/process/process_metrics_mac.cc b/src/base/process/process_metrics_mac.cc
new file mode 100644
index 0000000..3e96c07
--- /dev/null
+++ b/src/base/process/process_metrics_mac.cc
@@ -0,0 +1,301 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#include <mach/shared_region.h>
+#include <sys/sysctl.h>
+
+#include "base/containers/hash_tables.h"
+#include "base/logging.h"
+#include "base/mac/mac_util.h"
+#include "base/mac/mach_logging.h"
+#include "base/mac/scoped_mach_port.h"
+#include "base/memory/ptr_util.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/numerics/safe_math.h"
+#include "base/process/process_metrics_iocounters.h"
+#include "starboard/types.h"
+
+namespace base {
+
+namespace {
+
+#if !defined(MAC_OS_X_VERSION_10_11) || \
+ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11
+// The |phys_footprint| field was introduced in 10.11.
+struct ChromeTaskVMInfo {
+ mach_vm_size_t virtual_size;
+ integer_t region_count;
+ integer_t page_size;
+ mach_vm_size_t resident_size;
+ mach_vm_size_t resident_size_peak;
+ mach_vm_size_t device;
+ mach_vm_size_t device_peak;
+ mach_vm_size_t internal;
+ mach_vm_size_t internal_peak;
+ mach_vm_size_t external;
+ mach_vm_size_t external_peak;
+ mach_vm_size_t reusable;
+ mach_vm_size_t reusable_peak;
+ mach_vm_size_t purgeable_volatile_pmap;
+ mach_vm_size_t purgeable_volatile_resident;
+ mach_vm_size_t purgeable_volatile_virtual;
+ mach_vm_size_t compressed;
+ mach_vm_size_t compressed_peak;
+ mach_vm_size_t compressed_lifetime;
+ mach_vm_size_t phys_footprint;
+};
+#else
+using ChromeTaskVMInfo = task_vm_info;
+#endif // MAC_OS_X_VERSION_10_11
+mach_msg_type_number_t ChromeTaskVMInfoCount =
+ sizeof(ChromeTaskVMInfo) / sizeof(natural_t);
+
+bool GetTaskInfo(mach_port_t task, task_basic_info_64* task_info_data) {
+ if (task == MACH_PORT_NULL)
+ return false;
+ mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
+ kern_return_t kr = task_info(task,
+ TASK_BASIC_INFO_64,
+ reinterpret_cast<task_info_t>(task_info_data),
+ &count);
+ // Most likely cause for failure: |task| is a zombie.
+ return kr == KERN_SUCCESS;
+}
+
+MachVMRegionResult ParseOutputFromMachVMRegion(kern_return_t kr) {
+ if (kr == KERN_INVALID_ADDRESS) {
+ // We're at the end of the address space.
+ return MachVMRegionResult::Finished;
+ } else if (kr != KERN_SUCCESS) {
+ return MachVMRegionResult::Error;
+ }
+ return MachVMRegionResult::Success;
+}
+
+bool GetPowerInfo(mach_port_t task, task_power_info* power_info_data) {
+ if (task == MACH_PORT_NULL)
+ return false;
+
+ mach_msg_type_number_t power_info_count = TASK_POWER_INFO_COUNT;
+ kern_return_t kr = task_info(task, TASK_POWER_INFO,
+ reinterpret_cast<task_info_t>(power_info_data),
+ &power_info_count);
+ // Most likely cause for failure: |task| is a zombie.
+ return kr == KERN_SUCCESS;
+}
+
+} // namespace
+
+// Getting a mach task from a pid for another process requires permissions in
+// general, so there doesn't really seem to be a way to do these (and spinning
+// up ps to fetch each stats seems dangerous to put in a base api for anyone to
+// call). Child processes ipc their port, so return something if available,
+// otherwise return 0.
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process,
+ PortProvider* port_provider) {
+ return WrapUnique(new ProcessMetrics(process, port_provider));
+}
+
+ProcessMetrics::TaskVMInfo ProcessMetrics::GetTaskVMInfo() const {
+ TaskVMInfo info;
+ ChromeTaskVMInfo task_vm_info;
+ mach_msg_type_number_t count = ChromeTaskVMInfoCount;
+ kern_return_t result =
+ task_info(TaskForPid(process_), TASK_VM_INFO,
+ reinterpret_cast<task_info_t>(&task_vm_info), &count);
+ if (result != KERN_SUCCESS)
+ return info;
+
+ info.internal = task_vm_info.internal;
+ info.compressed = task_vm_info.compressed;
+ if (count == ChromeTaskVMInfoCount)
+ info.phys_footprint = task_vm_info.phys_footprint;
+ return info;
+}
+
+#define TIME_VALUE_TO_TIMEVAL(a, r) do { \
+ (r)->tv_sec = (a)->seconds; \
+ (r)->tv_usec = (a)->microseconds; \
+} while (0)
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ mach_port_t task = TaskForPid(process_);
+ if (task == MACH_PORT_NULL)
+ return TimeDelta();
+
+ // Libtop explicitly loops over the threads (libtop_pinfo_update_cpu_usage()
+ // in libtop.c), but this is more concise and gives the same results:
+ task_thread_times_info thread_info_data;
+ mach_msg_type_number_t thread_info_count = TASK_THREAD_TIMES_INFO_COUNT;
+ kern_return_t kr = task_info(task,
+ TASK_THREAD_TIMES_INFO,
+ reinterpret_cast<task_info_t>(&thread_info_data),
+ &thread_info_count);
+ if (kr != KERN_SUCCESS) {
+ // Most likely cause: |task| is a zombie.
+ return TimeDelta();
+ }
+
+ task_basic_info_64 task_info_data;
+ if (!GetTaskInfo(task, &task_info_data))
+ return TimeDelta();
+
+ /* Set total_time. */
+ // thread info contains live time...
+ struct timeval user_timeval, system_timeval, task_timeval;
+ TIME_VALUE_TO_TIMEVAL(&thread_info_data.user_time, &user_timeval);
+ TIME_VALUE_TO_TIMEVAL(&thread_info_data.system_time, &system_timeval);
+ timeradd(&user_timeval, &system_timeval, &task_timeval);
+
+ // ... task info contains terminated time.
+ TIME_VALUE_TO_TIMEVAL(&task_info_data.user_time, &user_timeval);
+ TIME_VALUE_TO_TIMEVAL(&task_info_data.system_time, &system_timeval);
+ timeradd(&user_timeval, &task_timeval, &task_timeval);
+ timeradd(&system_timeval, &task_timeval, &task_timeval);
+
+ return TimeDelta::FromMicroseconds(TimeValToMicroseconds(task_timeval));
+}
+
+int ProcessMetrics::GetPackageIdleWakeupsPerSecond() {
+ mach_port_t task = TaskForPid(process_);
+ task_power_info power_info_data;
+
+ GetPowerInfo(task, &power_info_data);
+
+ // The task_power_info struct contains two wakeup counters:
+ // task_interrupt_wakeups and task_platform_idle_wakeups.
+ // task_interrupt_wakeups is the total number of wakeups generated by the
+ // process, and is the number that Activity Monitor reports.
+ // task_platform_idle_wakeups is a subset of task_interrupt_wakeups that
+ // tallies the number of times the processor was taken out of its low-power
+ // idle state to handle a wakeup. task_platform_idle_wakeups therefore result
+ // in a greater power increase than the other interrupts which occur while the
+ // CPU is already working, and reducing them has a greater overall impact on
+ // power usage. See the powermetrics man page for more info.
+ return CalculatePackageIdleWakeupsPerSecond(
+ power_info_data.task_platform_idle_wakeups);
+}
+
+int ProcessMetrics::GetIdleWakeupsPerSecond() {
+ mach_port_t task = TaskForPid(process_);
+ task_power_info power_info_data;
+
+ GetPowerInfo(task, &power_info_data);
+
+ return CalculateIdleWakeupsPerSecond(power_info_data.task_interrupt_wakeups);
+}
+
+bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
+ return false;
+}
+
+ProcessMetrics::ProcessMetrics(ProcessHandle process,
+ PortProvider* port_provider)
+ : process_(process),
+ last_absolute_idle_wakeups_(0),
+ last_absolute_package_idle_wakeups_(0),
+ port_provider_(port_provider) {}
+
+mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const {
+ mach_port_t task = MACH_PORT_NULL;
+ if (port_provider_)
+ task = port_provider_->TaskForPid(process_);
+ if (task == MACH_PORT_NULL && process_ == getpid())
+ task = mach_task_self();
+ return task;
+}
+
+// Bytes committed by the system.
+size_t GetSystemCommitCharge() {
+ base::mac::ScopedMachSendRight host(mach_host_self());
+ mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
+ vm_statistics_data_t data;
+ kern_return_t kr = host_statistics(host.get(), HOST_VM_INFO,
+ reinterpret_cast<host_info_t>(&data),
+ &count);
+ if (kr != KERN_SUCCESS) {
+ MACH_DLOG(WARNING, kr) << "host_statistics";
+ return 0;
+ }
+
+ return (data.active_count * PAGE_SIZE) / 1024;
+}
+
+bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
+ struct host_basic_info hostinfo;
+ mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
+ base::mac::ScopedMachSendRight host(mach_host_self());
+ int result = host_info(host.get(), HOST_BASIC_INFO,
+ reinterpret_cast<host_info_t>(&hostinfo), &count);
+ if (result != KERN_SUCCESS)
+ return false;
+
+ DCHECK_EQ(HOST_BASIC_INFO_COUNT, count);
+ meminfo->total = static_cast<int>(hostinfo.max_mem / 1024);
+
+ vm_statistics64_data_t vm_info;
+ count = HOST_VM_INFO64_COUNT;
+
+ if (host_statistics64(host.get(), HOST_VM_INFO64,
+ reinterpret_cast<host_info64_t>(&vm_info),
+ &count) != KERN_SUCCESS) {
+ return false;
+ }
+ DCHECK_EQ(HOST_VM_INFO64_COUNT, count);
+
+ static_assert(PAGE_SIZE % 1024 == 0, "Invalid page size");
+ meminfo->free = saturated_cast<int>(
+ PAGE_SIZE / 1024 * (vm_info.free_count - vm_info.speculative_count));
+ meminfo->speculative =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.speculative_count);
+ meminfo->file_backed =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.external_page_count);
+ meminfo->purgeable =
+ saturated_cast<int>(PAGE_SIZE / 1024 * vm_info.purgeable_count);
+
+ return true;
+}
+
+// Both |size| and |address| are in-out parameters.
+// |info| is an output parameter, only valid on Success.
+MachVMRegionResult GetTopInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_top_info_data_t* info) {
+ mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT;
+ mach_port_t object_name;
+ kern_return_t kr = mach_vm_region(task, address, size, VM_REGION_TOP_INFO,
+ reinterpret_cast<vm_region_info_t>(info),
+ &info_count, &object_name);
+ // The kernel always returns a null object for VM_REGION_TOP_INFO, but
+ // balance it with a deallocate in case this ever changes. See 10.9.2
+ // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region.
+ mach_port_deallocate(task, object_name);
+ return ParseOutputFromMachVMRegion(kr);
+}
+
+MachVMRegionResult GetBasicInfo(mach_port_t task,
+ mach_vm_size_t* size,
+ mach_vm_address_t* address,
+ vm_region_basic_info_64* info) {
+ mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
+ mach_port_t object_name;
+ kern_return_t kr = mach_vm_region(
+ task, address, size, VM_REGION_BASIC_INFO_64,
+ reinterpret_cast<vm_region_info_t>(info), &info_count, &object_name);
+ // The kernel always returns a null object for VM_REGION_BASIC_INFO_64, but
+ // balance it with a deallocate in case this ever changes. See 10.9.2
+ // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region.
+ mach_port_deallocate(task, object_name);
+ return ParseOutputFromMachVMRegion(kr);
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_nacl.cc b/src/base/process/process_metrics_nacl.cc
new file mode 100644
index 0000000..8912198
--- /dev/null
+++ b/src/base/process/process_metrics_nacl.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <unistd.h>
+
+#include "starboard/types.h"
+
+namespace base {
+
+size_t GetPageSize() {
+ return getpagesize();
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_openbsd.cc b/src/base/process/process_metrics_openbsd.cc
new file mode 100644
index 0000000..fce715b
--- /dev/null
+++ b/src/base/process/process_metrics_openbsd.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <sys/param.h>
+#include <sys/sysctl.h>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/process_metrics_iocounters.h"
+#include "starboard/types.h"
+
+namespace base {
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ return WrapUnique(new ProcessMetrics(process));
+}
+
+bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
+ return false;
+}
+
+static int GetProcessCPU(pid_t pid) {
+ struct kinfo_proc info;
+ size_t length;
+ int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid,
+ sizeof(struct kinfo_proc), 0 };
+
+ if (sysctl(mib, arraysize(mib), NULL, &length, NULL, 0) < 0)
+ return -1;
+
+ mib[5] = (length / sizeof(struct kinfo_proc));
+
+ if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0)
+ return 0;
+
+ return info.p_pctcpu;
+}
+
+double ProcessMetrics::GetPlatformIndependentCPUUsage() {
+ TimeTicks time = TimeTicks::Now();
+
+ if (last_cpu_time_.is_zero()) {
+ // First call, just set the last values.
+ last_cpu_time_ = time;
+ return 0;
+ }
+
+ int cpu = GetProcessCPU(process_);
+
+ last_cpu_time_ = time;
+ double percentage = static_cast<double>((cpu * 100.0) / FSCALE);
+
+ return percentage;
+}
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ NOTREACHED();
+ return TimeDelta();
+}
+
+ProcessMetrics::ProcessMetrics(ProcessHandle process)
+ : process_(process),
+ last_cpu_(0) {}
+
+size_t GetSystemCommitCharge() {
+ int mib[] = { CTL_VM, VM_METER };
+ int pagesize;
+ struct vmtotal vmtotal;
+ unsigned long mem_total, mem_free, mem_inactive;
+ size_t len = sizeof(vmtotal);
+
+ if (sysctl(mib, arraysize(mib), &vmtotal, &len, NULL, 0) < 0)
+ return 0;
+
+ mem_total = vmtotal.t_vm;
+ mem_free = vmtotal.t_free;
+ mem_inactive = vmtotal.t_vm - vmtotal.t_avm;
+
+ pagesize = getpagesize();
+
+ return mem_total - (mem_free*pagesize) - (mem_inactive*pagesize);
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_posix.cc b/src/base/process/process_metrics_posix.cc
new file mode 100644
index 0000000..c70c0c5
--- /dev/null
+++ b/src/base/process/process_metrics_posix.cc
@@ -0,0 +1,116 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <limits.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "base/logging.h"
+#include "build/build_config.h"
+
+#if !defined(OS_FUCHSIA)
+#include <sys/resource.h>
+#endif
+
+#if defined(OS_MACOSX)
+#include <malloc/malloc.h>
+#else
+#include <malloc.h>
+
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+int64_t TimeValToMicroseconds(const struct timeval& tv) {
+ int64_t ret = tv.tv_sec; // Avoid (int * int) integer overflow.
+ ret *= Time::kMicrosecondsPerSecond;
+ ret += tv.tv_usec;
+ return ret;
+}
+
+ProcessMetrics::~ProcessMetrics() = default;
+
+#if !defined(OS_FUCHSIA)
+
+#if defined(OS_LINUX)
+static const rlim_t kSystemDefaultMaxFds = 8192;
+#elif defined(OS_MACOSX)
+static const rlim_t kSystemDefaultMaxFds = 256;
+#elif defined(OS_SOLARIS)
+static const rlim_t kSystemDefaultMaxFds = 8192;
+#elif defined(OS_FREEBSD)
+static const rlim_t kSystemDefaultMaxFds = 8192;
+#elif defined(OS_NETBSD)
+static const rlim_t kSystemDefaultMaxFds = 1024;
+#elif defined(OS_OPENBSD)
+static const rlim_t kSystemDefaultMaxFds = 256;
+#elif defined(OS_ANDROID)
+static const rlim_t kSystemDefaultMaxFds = 1024;
+#elif defined(OS_AIX)
+static const rlim_t kSystemDefaultMaxFds = 8192;
+#endif
+
+size_t GetMaxFds() {
+ rlim_t max_fds;
+ struct rlimit nofile;
+ if (getrlimit(RLIMIT_NOFILE, &nofile)) {
+ // getrlimit failed. Take a best guess.
+ max_fds = kSystemDefaultMaxFds;
+ RAW_LOG(ERROR, "getrlimit(RLIMIT_NOFILE) failed");
+ } else {
+ max_fds = nofile.rlim_cur;
+ }
+
+ if (max_fds > INT_MAX)
+ max_fds = INT_MAX;
+
+ return static_cast<size_t>(max_fds);
+}
+
+void IncreaseFdLimitTo(unsigned int max_descriptors) {
+ struct rlimit limits;
+ if (getrlimit(RLIMIT_NOFILE, &limits) == 0) {
+ unsigned int new_limit = max_descriptors;
+ if (max_descriptors <= limits.rlim_cur)
+ return;
+ if (limits.rlim_max > 0 && limits.rlim_max < max_descriptors) {
+ new_limit = limits.rlim_max;
+ }
+ limits.rlim_cur = new_limit;
+ if (setrlimit(RLIMIT_NOFILE, &limits) != 0) {
+ PLOG(INFO) << "Failed to set file descriptor limit";
+ }
+ } else {
+ PLOG(INFO) << "Failed to get file descriptor limit";
+ }
+}
+
+#endif // !defined(OS_FUCHSIA)
+
+size_t GetPageSize() {
+ return getpagesize();
+}
+
+size_t ProcessMetrics::GetMallocUsage() {
+#if defined(OS_MACOSX) || defined(OS_IOS)
+ malloc_statistics_t stats = {0};
+ malloc_zone_statistics(nullptr, &stats);
+ return stats.size_in_use;
+#elif defined(OS_LINUX) || defined(OS_ANDROID)
+ struct mallinfo minfo = mallinfo();
+#if defined(USE_TCMALLOC)
+ return minfo.uordblks;
+#else
+ return minfo.hblkhd + minfo.arena;
+#endif
+#elif defined(OS_FUCHSIA)
+ // TODO(fuchsia): Not currently exposed. https://crbug.com/735087.
+ return 0;
+#endif
+}
+
+} // namespace base
diff --git a/src/base/process/process_metrics_unittest.cc b/src/base/process/process_metrics_unittest.cc
new file mode 100644
index 0000000..3848d2b
--- /dev/null
+++ b/src/base/process/process_metrics_unittest.cc
@@ -0,0 +1,657 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "starboard/types.h"
+
+#include "starboard/memory.h"
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/shared_memory.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/sys_info.h"
+#include "base/test/multiprocess_test.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_MACOSX)
+#include <sys/mman.h>
+#endif
+
+namespace base {
+namespace debug {
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
+namespace {
+
+void BusyWork(std::vector<std::string>* vec) {
+ int64_t test_value = 0;
+ for (int i = 0; i < 100000; ++i) {
+ ++test_value;
+ vec->push_back(Int64ToString(test_value));
+ }
+}
+
+} // namespace
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+// Tests for SystemMetrics.
+// Exists as a class so it can be a friend of SystemMetrics.
+class SystemMetricsTest : public testing::Test {
+ public:
+ SystemMetricsTest() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemMetricsTest);
+};
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+TEST_F(SystemMetricsTest, IsValidDiskName) {
+ const char invalid_input1[] = "";
+ const char invalid_input2[] = "s";
+ const char invalid_input3[] = "sdz+";
+ const char invalid_input4[] = "hda0";
+ const char invalid_input5[] = "mmcbl";
+ const char invalid_input6[] = "mmcblka";
+ const char invalid_input7[] = "mmcblkb";
+ const char invalid_input8[] = "mmmblk0";
+
+ EXPECT_FALSE(IsValidDiskName(invalid_input1));
+ EXPECT_FALSE(IsValidDiskName(invalid_input2));
+ EXPECT_FALSE(IsValidDiskName(invalid_input3));
+ EXPECT_FALSE(IsValidDiskName(invalid_input4));
+ EXPECT_FALSE(IsValidDiskName(invalid_input5));
+ EXPECT_FALSE(IsValidDiskName(invalid_input6));
+ EXPECT_FALSE(IsValidDiskName(invalid_input7));
+ EXPECT_FALSE(IsValidDiskName(invalid_input8));
+
+ const char valid_input1[] = "sda";
+ const char valid_input2[] = "sdaaaa";
+ const char valid_input3[] = "hdz";
+ const char valid_input4[] = "mmcblk0";
+ const char valid_input5[] = "mmcblk999";
+
+ EXPECT_TRUE(IsValidDiskName(valid_input1));
+ EXPECT_TRUE(IsValidDiskName(valid_input2));
+ EXPECT_TRUE(IsValidDiskName(valid_input3));
+ EXPECT_TRUE(IsValidDiskName(valid_input4));
+ EXPECT_TRUE(IsValidDiskName(valid_input5));
+}
+
+TEST_F(SystemMetricsTest, ParseMeminfo) {
+ SystemMemoryInfoKB meminfo;
+ const char invalid_input1[] = "abc";
+ const char invalid_input2[] = "MemTotal:";
+ // Partial file with no MemTotal
+ const char invalid_input3[] =
+ "MemFree: 3913968 kB\n"
+ "Buffers: 2348340 kB\n"
+ "Cached: 49071596 kB\n"
+ "SwapCached: 12 kB\n"
+ "Active: 36393900 kB\n"
+ "Inactive: 21221496 kB\n"
+ "Active(anon): 5674352 kB\n"
+ "Inactive(anon): 633992 kB\n";
+ EXPECT_FALSE(ParseProcMeminfo(invalid_input1, &meminfo));
+ EXPECT_FALSE(ParseProcMeminfo(invalid_input2, &meminfo));
+ EXPECT_FALSE(ParseProcMeminfo(invalid_input3, &meminfo));
+
+ const char valid_input1[] =
+ "MemTotal: 3981504 kB\n"
+ "MemFree: 140764 kB\n"
+ "MemAvailable: 535413 kB\n"
+ "Buffers: 116480 kB\n"
+ "Cached: 406160 kB\n"
+ "SwapCached: 21304 kB\n"
+ "Active: 3152040 kB\n"
+ "Inactive: 472856 kB\n"
+ "Active(anon): 2972352 kB\n"
+ "Inactive(anon): 270108 kB\n"
+ "Active(file): 179688 kB\n"
+ "Inactive(file): 202748 kB\n"
+ "Unevictable: 0 kB\n"
+ "Mlocked: 0 kB\n"
+ "SwapTotal: 5832280 kB\n"
+ "SwapFree: 3672368 kB\n"
+ "Dirty: 184 kB\n"
+ "Writeback: 0 kB\n"
+ "AnonPages: 3101224 kB\n"
+ "Mapped: 142296 kB\n"
+ "Shmem: 140204 kB\n"
+ "Slab: 54212 kB\n"
+ "SReclaimable: 30936 kB\n"
+ "SUnreclaim: 23276 kB\n"
+ "KernelStack: 2464 kB\n"
+ "PageTables: 24812 kB\n"
+ "NFS_Unstable: 0 kB\n"
+ "Bounce: 0 kB\n"
+ "WritebackTmp: 0 kB\n"
+ "CommitLimit: 7823032 kB\n"
+ "Committed_AS: 7973536 kB\n"
+ "VmallocTotal: 34359738367 kB\n"
+ "VmallocUsed: 375940 kB\n"
+ "VmallocChunk: 34359361127 kB\n"
+ "DirectMap4k: 72448 kB\n"
+ "DirectMap2M: 4061184 kB\n";
+ // output from a much older kernel where the Active and Inactive aren't
+ // broken down into anon and file and Huge Pages are enabled
+ const char valid_input2[] =
+ "MemTotal: 255908 kB\n"
+ "MemFree: 69936 kB\n"
+ "Buffers: 15812 kB\n"
+ "Cached: 115124 kB\n"
+ "SwapCached: 0 kB\n"
+ "Active: 92700 kB\n"
+ "Inactive: 63792 kB\n"
+ "HighTotal: 0 kB\n"
+ "HighFree: 0 kB\n"
+ "LowTotal: 255908 kB\n"
+ "LowFree: 69936 kB\n"
+ "SwapTotal: 524280 kB\n"
+ "SwapFree: 524200 kB\n"
+ "Dirty: 4 kB\n"
+ "Writeback: 0 kB\n"
+ "Mapped: 42236 kB\n"
+ "Slab: 25912 kB\n"
+ "Committed_AS: 118680 kB\n"
+ "PageTables: 1236 kB\n"
+ "VmallocTotal: 3874808 kB\n"
+ "VmallocUsed: 1416 kB\n"
+ "VmallocChunk: 3872908 kB\n"
+ "HugePages_Total: 0\n"
+ "HugePages_Free: 0\n"
+ "Hugepagesize: 4096 kB\n";
+
+ EXPECT_TRUE(ParseProcMeminfo(valid_input1, &meminfo));
+ EXPECT_EQ(meminfo.total, 3981504);
+ EXPECT_EQ(meminfo.free, 140764);
+ EXPECT_EQ(meminfo.available, 535413);
+ EXPECT_EQ(meminfo.buffers, 116480);
+ EXPECT_EQ(meminfo.cached, 406160);
+ EXPECT_EQ(meminfo.active_anon, 2972352);
+ EXPECT_EQ(meminfo.active_file, 179688);
+ EXPECT_EQ(meminfo.inactive_anon, 270108);
+ EXPECT_EQ(meminfo.inactive_file, 202748);
+ EXPECT_EQ(meminfo.swap_total, 5832280);
+ EXPECT_EQ(meminfo.swap_free, 3672368);
+ EXPECT_EQ(meminfo.dirty, 184);
+ EXPECT_EQ(meminfo.reclaimable, 30936);
+#if defined(OS_CHROMEOS)
+ EXPECT_EQ(meminfo.shmem, 140204);
+ EXPECT_EQ(meminfo.slab, 54212);
+#endif
+ EXPECT_EQ(355725,
+ base::SysInfo::AmountOfAvailablePhysicalMemory(meminfo) / 1024);
+ // Simulate as if there is no MemAvailable.
+ meminfo.available = 0;
+ EXPECT_EQ(374448,
+ base::SysInfo::AmountOfAvailablePhysicalMemory(meminfo) / 1024);
+ meminfo = {};
+ EXPECT_TRUE(ParseProcMeminfo(valid_input2, &meminfo));
+ EXPECT_EQ(meminfo.total, 255908);
+ EXPECT_EQ(meminfo.free, 69936);
+ EXPECT_EQ(meminfo.available, 0);
+ EXPECT_EQ(meminfo.buffers, 15812);
+ EXPECT_EQ(meminfo.cached, 115124);
+ EXPECT_EQ(meminfo.swap_total, 524280);
+ EXPECT_EQ(meminfo.swap_free, 524200);
+ EXPECT_EQ(meminfo.dirty, 4);
+ EXPECT_EQ(69936,
+ base::SysInfo::AmountOfAvailablePhysicalMemory(meminfo) / 1024);
+}
+
+TEST_F(SystemMetricsTest, ParseVmstat) {
+ VmStatInfo vmstat;
+ // part of vmstat from a 3.2 kernel with numa enabled
+ const char valid_input1[] =
+ "nr_free_pages 905104\n"
+ "nr_inactive_anon 142478"
+ "nr_active_anon 1520046\n"
+ "nr_inactive_file 4481001\n"
+ "nr_active_file 8313439\n"
+ "nr_unevictable 5044\n"
+ "nr_mlock 5044\n"
+ "nr_anon_pages 1633780\n"
+ "nr_mapped 104742\n"
+ "nr_file_pages 12828218\n"
+ "nr_dirty 245\n"
+ "nr_writeback 0\n"
+ "nr_slab_reclaimable 831609\n"
+ "nr_slab_unreclaimable 41164\n"
+ "nr_page_table_pages 31470\n"
+ "nr_kernel_stack 1735\n"
+ "nr_unstable 0\n"
+ "nr_bounce 0\n"
+ "nr_vmscan_write 406\n"
+ "nr_vmscan_immediate_reclaim 281\n"
+ "nr_writeback_temp 0\n"
+ "nr_isolated_anon 0\n"
+ "nr_isolated_file 0\n"
+ "nr_shmem 28820\n"
+ "nr_dirtied 84674644\n"
+ "nr_written 75307109\n"
+ "nr_anon_transparent_hugepages 0\n"
+ "nr_dirty_threshold 1536206\n"
+ "nr_dirty_background_threshold 768103\n"
+ "pgpgin 30777108\n"
+ "pgpgout 319023278\n"
+ "pswpin 179\n"
+ "pswpout 406\n"
+ "pgalloc_dma 0\n"
+ "pgalloc_dma32 20833399\n"
+ "pgalloc_normal 1622609290\n"
+ "pgalloc_movable 0\n"
+ "pgfree 1644355583\n"
+ "pgactivate 75391882\n"
+ "pgdeactivate 4121019\n"
+ "pgfault 2542879679\n"
+ "pgmajfault 487192\n";
+ const char valid_input2[] =
+ "nr_free_pages 180125\n"
+ "nr_inactive_anon 51\n"
+ "nr_active_anon 38832\n"
+ "nr_inactive_file 50171\n"
+ "nr_active_file 47510\n"
+ "nr_unevictable 0\n"
+ "nr_mlock 0\n"
+ "nr_anon_pages 38825\n"
+ "nr_mapped 24043\n"
+ "nr_file_pages 97733\n"
+ "nr_dirty 0\n"
+ "nr_writeback 0\n"
+ "nr_slab_reclaimable 4032\n"
+ "nr_slab_unreclaimable 2848\n"
+ "nr_page_table_pages 1505\n"
+ "nr_kernel_stack 626\n"
+ "nr_unstable 0\n"
+ "nr_bounce 0\n"
+ "nr_vmscan_write 0\n"
+ "nr_vmscan_immediate_reclaim 0\n"
+ "nr_writeback_temp 0\n"
+ "nr_isolated_anon 0\n"
+ "nr_isolated_file 0\n"
+ "nr_shmem 58\n"
+ "nr_dirtied 435358\n"
+ "nr_written 401258\n"
+ "nr_anon_transparent_hugepages 0\n"
+ "nr_dirty_threshold 18566\n"
+ "nr_dirty_background_threshold 4641\n"
+ "pgpgin 299464\n"
+ "pgpgout 2437788\n"
+ "pswpin 12\n"
+ "pswpout 901\n"
+ "pgalloc_normal 144213030\n"
+ "pgalloc_high 164501274\n"
+ "pgalloc_movable 0\n"
+ "pgfree 308894908\n"
+ "pgactivate 239320\n"
+ "pgdeactivate 1\n"
+ "pgfault 716044601\n"
+ "pgmajfault 2023\n"
+ "pgrefill_normal 0\n"
+ "pgrefill_high 0\n"
+ "pgrefill_movable 0\n";
+ EXPECT_TRUE(ParseProcVmstat(valid_input1, &vmstat));
+ EXPECT_EQ(179LU, vmstat.pswpin);
+ EXPECT_EQ(406LU, vmstat.pswpout);
+ EXPECT_EQ(487192LU, vmstat.pgmajfault);
+ EXPECT_TRUE(ParseProcVmstat(valid_input2, &vmstat));
+ EXPECT_EQ(12LU, vmstat.pswpin);
+ EXPECT_EQ(901LU, vmstat.pswpout);
+ EXPECT_EQ(2023LU, vmstat.pgmajfault);
+
+ const char missing_pgmajfault_input[] =
+ "pswpin 12\n"
+ "pswpout 901\n";
+ EXPECT_FALSE(ParseProcVmstat(missing_pgmajfault_input, &vmstat));
+ const char empty_input[] = "";
+ EXPECT_FALSE(ParseProcVmstat(empty_input, &vmstat));
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_WIN)
+
+// Test that ProcessMetrics::GetPlatformIndependentCPUUsage() doesn't return
+// negative values when the number of threads running on the process decreases
+// between two successive calls to it.
+TEST_F(SystemMetricsTest, TestNoNegativeCpuUsage) {
+ ProcessHandle handle = GetCurrentProcessHandle();
+ std::unique_ptr<ProcessMetrics> metrics(
+ ProcessMetrics::CreateProcessMetrics(handle));
+
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
+ Thread thread1("thread1");
+ Thread thread2("thread2");
+ Thread thread3("thread3");
+
+ thread1.StartAndWaitForTesting();
+ thread2.StartAndWaitForTesting();
+ thread3.StartAndWaitForTesting();
+
+ ASSERT_TRUE(thread1.IsRunning());
+ ASSERT_TRUE(thread2.IsRunning());
+ ASSERT_TRUE(thread3.IsRunning());
+
+ std::vector<std::string> vec1;
+ std::vector<std::string> vec2;
+ std::vector<std::string> vec3;
+
+ thread1.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec1));
+ thread2.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec2));
+ thread3.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec3));
+
+ TimeDelta prev_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(prev_cpu_usage, TimeDelta());
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
+
+ thread1.Stop();
+ TimeDelta current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ prev_cpu_usage = current_cpu_usage;
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
+
+ thread2.Stop();
+ current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ prev_cpu_usage = current_cpu_usage;
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
+
+ thread3.Stop();
+ current_cpu_usage = metrics->GetCumulativeCPUUsage();
+ EXPECT_GE(current_cpu_usage, prev_cpu_usage);
+ EXPECT_GE(metrics->GetPlatformIndependentCPUUsage(), 0.0);
+}
+
+#endif // defined(OS_LINUX) || defined(OS_CHROMEOS)
+
+#if defined(OS_CHROMEOS)
+TEST_F(SystemMetricsTest, ParseZramMmStat) {
+ SwapInfo swapinfo;
+
+ const char invalid_input1[] = "aaa";
+ const char invalid_input2[] = "1 2 3 4 5 6";
+ const char invalid_input3[] = "a 2 3 4 5 6 7";
+ EXPECT_FALSE(ParseZramMmStat(invalid_input1, &swapinfo));
+ EXPECT_FALSE(ParseZramMmStat(invalid_input2, &swapinfo));
+ EXPECT_FALSE(ParseZramMmStat(invalid_input3, &swapinfo));
+
+ const char valid_input1[] =
+ "17715200 5008166 566062 0 1225715712 127 183842";
+ EXPECT_TRUE(ParseZramMmStat(valid_input1, &swapinfo));
+ EXPECT_EQ(17715200ULL, swapinfo.orig_data_size);
+ EXPECT_EQ(5008166ULL, swapinfo.compr_data_size);
+ EXPECT_EQ(566062ULL, swapinfo.mem_used_total);
+}
+
+TEST_F(SystemMetricsTest, ParseZramStat) {
+ SwapInfo swapinfo;
+
+ const char invalid_input1[] = "aaa";
+ const char invalid_input2[] = "1 2 3 4 5 6 7 8 9 10";
+ const char invalid_input3[] = "a 2 3 4 5 6 7 8 9 10 11";
+ EXPECT_FALSE(ParseZramStat(invalid_input1, &swapinfo));
+ EXPECT_FALSE(ParseZramStat(invalid_input2, &swapinfo));
+ EXPECT_FALSE(ParseZramStat(invalid_input3, &swapinfo));
+
+ const char valid_input1[] =
+ "299 0 2392 0 1 0 8 0 0 0 0";
+ EXPECT_TRUE(ParseZramStat(valid_input1, &swapinfo));
+ EXPECT_EQ(299ULL, swapinfo.num_reads);
+ EXPECT_EQ(1ULL, swapinfo.num_writes);
+}
+#endif // defined(OS_CHROMEOS)
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
+ defined(OS_ANDROID)
+TEST(SystemMetrics2Test, GetSystemMemoryInfo) {
+ SystemMemoryInfoKB info;
+ EXPECT_TRUE(GetSystemMemoryInfo(&info));
+
+ // Ensure each field received a value.
+ EXPECT_GT(info.total, 0);
+#if defined(OS_WIN)
+ EXPECT_GT(info.avail_phys, 0);
+#else
+ EXPECT_GT(info.free, 0);
+#endif
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ EXPECT_GT(info.buffers, 0);
+ EXPECT_GT(info.cached, 0);
+ EXPECT_GT(info.active_anon, 0);
+ EXPECT_GT(info.inactive_anon, 0);
+ EXPECT_GT(info.active_file, 0);
+ EXPECT_GT(info.inactive_file, 0);
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // All the values should be less than the total amount of memory.
+#if !defined(OS_WIN) && !defined(OS_IOS)
+ // TODO(crbug.com/711450): re-enable the following assertion on iOS.
+ EXPECT_LT(info.free, info.total);
+#endif
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ EXPECT_LT(info.buffers, info.total);
+ EXPECT_LT(info.cached, info.total);
+ EXPECT_LT(info.active_anon, info.total);
+ EXPECT_LT(info.inactive_anon, info.total);
+ EXPECT_LT(info.active_file, info.total);
+ EXPECT_LT(info.inactive_file, info.total);
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+#if defined(OS_MACOSX) || defined(OS_IOS)
+ EXPECT_GT(info.file_backed, 0);
+#endif
+
+#if defined(OS_CHROMEOS)
+ // Chrome OS exposes shmem.
+ EXPECT_GT(info.shmem, 0);
+ EXPECT_LT(info.shmem, info.total);
+ // Chrome unit tests are not run on actual Chrome OS hardware, so gem_objects
+ // and gem_size cannot be tested here.
+#endif
+}
+#endif // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) ||
+ // defined(OS_ANDROID)
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+TEST(ProcessMetricsTest, ParseProcStatCPU) {
+ // /proc/self/stat for a process running "top".
+ const char kTopStat[] = "960 (top) S 16230 960 16230 34818 960 "
+ "4202496 471 0 0 0 "
+ "12 16 0 0 " // <- These are the goods.
+ "20 0 1 0 121946157 15077376 314 18446744073709551615 4194304 "
+ "4246868 140733983044336 18446744073709551615 140244213071219 "
+ "0 0 0 138047495 0 0 0 17 1 0 0 0 0 0";
+ EXPECT_EQ(12 + 16, ParseProcStatCPU(kTopStat));
+
+ // cat /proc/self/stat on a random other machine I have.
+ const char kSelfStat[] = "5364 (cat) R 5354 5364 5354 34819 5364 "
+ "0 142 0 0 0 "
+ "0 0 0 0 " // <- No CPU, apparently.
+ "16 0 1 0 1676099790 2957312 114 4294967295 134512640 134528148 "
+ "3221224832 3221224344 3086339742 0 0 0 0 0 0 0 17 0 0 0";
+
+ EXPECT_EQ(0, ParseProcStatCPU(kSelfStat));
+
+ // Some weird long-running process with a weird name that I created for the
+ // purposes of this test.
+ const char kWeirdNameStat[] = "26115 (Hello) You ())) ) R 24614 26115 24614"
+ " 34839 26115 4218880 227 0 0 0 "
+ "5186 11 0 0 "
+ "20 0 1 0 36933953 4296704 90 18446744073709551615 4194304 4196116 "
+ "140735857761568 140735857761160 4195644 0 0 0 0 0 0 0 17 14 0 0 0 0 0 "
+ "6295056 6295616 16519168 140735857770710 140735857770737 "
+ "140735857770737 140735857774557 0";
+ EXPECT_EQ(5186 + 11, ParseProcStatCPU(kWeirdNameStat));
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+// Disable on Android because base_unittests runs inside a Dalvik VM that
+// starts and stop threads (crbug.com/175563).
+#if defined(OS_LINUX)
+// http://crbug.com/396455
+TEST(ProcessMetricsTest, DISABLED_GetNumberOfThreads) {
+ const ProcessHandle current = GetCurrentProcessHandle();
+ const int initial_threads = GetNumberOfThreads(current);
+ ASSERT_GT(initial_threads, 0);
+ const int kNumAdditionalThreads = 10;
+ {
+ std::unique_ptr<Thread> my_threads[kNumAdditionalThreads];
+ for (int i = 0; i < kNumAdditionalThreads; ++i) {
+ my_threads[i].reset(new Thread("GetNumberOfThreadsTest"));
+ my_threads[i]->Start();
+ ASSERT_EQ(GetNumberOfThreads(current), initial_threads + 1 + i);
+ }
+ }
+ // The Thread destructor will stop them.
+ ASSERT_EQ(initial_threads, GetNumberOfThreads(current));
+}
+#endif // defined(OS_LINUX)
+
+#if defined(OS_LINUX)
+namespace {
+
+// Keep these in sync so the GetChildOpenFdCount test can refer to correct test
+// main.
+#define ChildMain ChildFdCount
+#define ChildMainString "ChildFdCount"
+
+// Command line flag name and file name used for synchronization.
+const char kTempDirFlag[] = "temp-dir";
+const char kSignalClosed[] = "closed";
+
+bool SignalEvent(const FilePath& signal_dir, const char* signal_file) {
+ File file(signal_dir.AppendASCII(signal_file),
+ File::FLAG_CREATE | File::FLAG_WRITE);
+ return file.IsValid();
+}
+
+// Check whether an event was signaled.
+bool CheckEvent(const FilePath& signal_dir, const char* signal_file) {
+ File file(signal_dir.AppendASCII(signal_file),
+ File::FLAG_OPEN | File::FLAG_READ);
+ return file.IsValid();
+}
+
+// Busy-wait for an event to be signaled.
+void WaitForEvent(const FilePath& signal_dir, const char* signal_file) {
+ while (!CheckEvent(signal_dir, signal_file))
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+}
+
+// Subprocess to test the number of open file descriptors.
+MULTIPROCESS_TEST_MAIN(ChildMain) {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag);
+ CHECK(DirectoryExists(temp_path));
+
+ // Try to close all the file descriptors, so the open count goes to 0.
+ for (size_t i = 0; i < 1000; ++i)
+ close(i);
+ CHECK(SignalEvent(temp_path, kSignalClosed));
+
+ // Wait to be terminated.
+ while (true)
+ PlatformThread::Sleep(TimeDelta::FromSeconds(1));
+ return 0;
+}
+
+} // namespace
+
+TEST(ProcessMetricsTest, GetChildOpenFdCount) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ const FilePath temp_path = temp_dir.GetPath();
+ CommandLine child_command_line(GetMultiProcessTestChildBaseCommandLine());
+ child_command_line.AppendSwitchPath(kTempDirFlag, temp_path);
+ Process child = SpawnMultiProcessTestChild(
+ ChildMainString, child_command_line, LaunchOptions());
+ ASSERT_TRUE(child.IsValid());
+ WaitForEvent(temp_path, kSignalClosed);
+
+ std::unique_ptr<ProcessMetrics> metrics(
+ ProcessMetrics::CreateProcessMetrics(child.Handle()));
+ EXPECT_EQ(0, metrics->GetOpenFdCount());
+ ASSERT_TRUE(child.Terminate(0, true));
+}
+#endif // defined(OS_LINUX)
+
+#if defined(OS_ANDROID) || defined(OS_LINUX)
+
+TEST(ProcessMetricsTest, GetOpenFdCount) {
+ std::unique_ptr<base::ProcessMetrics> metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ base::GetCurrentProcessHandle()));
+ int fd_count = metrics->GetOpenFdCount();
+ EXPECT_GT(fd_count, 0);
+ ScopedFILE file(fopen("/proc/self/statm", "r"));
+ EXPECT_TRUE(file);
+ int new_fd_count = metrics->GetOpenFdCount();
+ EXPECT_GT(new_fd_count, 0);
+ EXPECT_EQ(new_fd_count, fd_count + 1);
+}
+
+TEST(ProcessMetricsTestLinux, GetPageFaultCounts) {
+ std::unique_ptr<base::ProcessMetrics> process_metrics(
+ base::ProcessMetrics::CreateProcessMetrics(
+ base::GetCurrentProcessHandle()));
+
+ PageFaultCounts counts;
+ ASSERT_TRUE(process_metrics->GetPageFaultCounts(&counts));
+ ASSERT_GT(counts.minor, 0);
+ ASSERT_GE(counts.major, 0);
+
+ {
+ // Allocate and touch memory. Touching it is required to make sure that the
+ // page fault count goes up, as memory is typically mapped lazily.
+ const size_t kMappedSize = 4 * (1 << 20);
+ SharedMemory memory;
+ ASSERT_TRUE(memory.CreateAndMapAnonymous(kMappedSize));
+ SbMemorySet(memory.memory(), 42, kMappedSize);
+ memory.Unmap();
+ }
+
+ PageFaultCounts counts_after;
+ ASSERT_TRUE(process_metrics->GetPageFaultCounts(&counts_after));
+ ASSERT_GT(counts_after.minor, counts.minor);
+ ASSERT_GE(counts_after.major, counts.major);
+}
+#endif // defined(OS_ANDROID) || defined(OS_LINUX)
+
+#if defined(OS_WIN)
+TEST(ProcessMetricsTest, GetDiskUsageBytesPerSecond) {
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ const FilePath temp_path = temp_dir.GetPath().AppendASCII("dummy");
+
+ ProcessHandle handle = GetCurrentProcessHandle();
+ std::unique_ptr<ProcessMetrics> metrics(
+ ProcessMetrics::CreateProcessMetrics(handle));
+
+ // First access is returning zero bytes.
+ EXPECT_EQ(metrics->GetDiskUsageBytesPerSecond(), 0U);
+
+ // Write a megabyte on disk.
+ const int kMegabyte = 1024 * 1014;
+ std::string data(kMegabyte, 'x');
+ ASSERT_EQ(kMegabyte, base::WriteFile(temp_path, data.c_str(), data.size()));
+
+ // Validate that the counters move up.
+ EXPECT_GT(metrics->GetDiskUsageBytesPerSecond(), 0U);
+}
+#endif // defined(OS_WIN)
+
+} // namespace debug
+} // namespace base
diff --git a/src/base/process/process_metrics_win.cc b/src/base/process/process_metrics_win.cc
new file mode 100644
index 0000000..acb8135
--- /dev/null
+++ b/src/base/process/process_metrics_win.cc
@@ -0,0 +1,371 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/process/process_metrics.h"
+
+#include <windows.h> // Must be in front of other Windows header files.
+
+#include <psapi.h>
+#include <winternl.h>
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/process/memory.h"
+#include "base/process/process_metrics_iocounters.h"
+#include "base/sys_info.h"
+#include "starboard/memory.h"
+#include "starboard/types.h"
+
+namespace base {
+namespace {
+
+// System pagesize. This value remains constant on x86/64 architectures.
+const int PAGESIZE_KB = 4;
+
+// ntstatus.h conflicts with windows.h so define this locally.
+#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
+
+// Definition of this struct is taken from the book:
+// Windows NT/200, Native API reference, Gary Nebbett
+struct SYSTEM_PERFORMANCE_INFORMATION {
+ // Total idle time of all processes in the system (units of 100 ns).
+ LARGE_INTEGER IdleTime;
+ // Number of bytes read (by all call to ZwReadFile).
+ LARGE_INTEGER ReadTransferCount;
+ // Number of bytes written (by all call to ZwWriteFile).
+ LARGE_INTEGER WriteTransferCount;
+ // Number of bytes transferred (e.g. DeviceIoControlFile)
+ LARGE_INTEGER OtherTransferCount;
+ // The amount of read operations.
+ ULONG ReadOperationCount;
+ // The amount of write operations.
+ ULONG WriteOperationCount;
+ // The amount of other operations.
+ ULONG OtherOperationCount;
+ // The number of pages of physical memory available to processes running on
+ // the system.
+ ULONG AvailablePages;
+ ULONG TotalCommittedPages;
+ ULONG TotalCommitLimit;
+ ULONG PeakCommitment;
+ ULONG PageFaults;
+ ULONG WriteCopyFaults;
+ ULONG TransitionFaults;
+ ULONG CacheTransitionFaults;
+ ULONG DemandZeroFaults;
+ // The number of pages read from disk to resolve page faults.
+ ULONG PagesRead;
+ // The number of read operations initiated to resolve page faults.
+ ULONG PageReadIos;
+ ULONG CacheReads;
+ ULONG CacheIos;
+ // The number of pages written to the system's pagefiles.
+ ULONG PagefilePagesWritten;
+ // The number of write operations performed on the system's pagefiles.
+ ULONG PagefilePageWriteIos;
+ ULONG MappedFilePagesWritten;
+ ULONG MappedFilePageWriteIos;
+ ULONG PagedPoolUsage;
+ ULONG NonPagedPoolUsage;
+ ULONG PagedPoolAllocs;
+ ULONG PagedPoolFrees;
+ ULONG NonPagedPoolAllocs;
+ ULONG NonPagedPoolFrees;
+ ULONG TotalFreeSystemPtes;
+ ULONG SystemCodePage;
+ ULONG TotalSystemDriverPages;
+ ULONG TotalSystemCodePages;
+ ULONG SmallNonPagedLookasideListAllocateHits;
+ ULONG SmallPagedLookasideListAllocateHits;
+ ULONG Reserved3;
+ ULONG MmSystemCachePage;
+ ULONG PagedPoolPage;
+ ULONG SystemDriverPage;
+ ULONG FastReadNoWait;
+ ULONG FastReadWait;
+ ULONG FastReadResourceMiss;
+ ULONG FastReadNotPossible;
+ ULONG FastMdlReadNoWait;
+ ULONG FastMdlReadWait;
+ ULONG FastMdlReadResourceMiss;
+ ULONG FastMdlReadNotPossible;
+ ULONG MapDataNoWait;
+ ULONG MapDataWait;
+ ULONG MapDataNoWaitMiss;
+ ULONG MapDataWaitMiss;
+ ULONG PinMappedDataCount;
+ ULONG PinReadNoWait;
+ ULONG PinReadWait;
+ ULONG PinReadNoWaitMiss;
+ ULONG PinReadWaitMiss;
+ ULONG CopyReadNoWait;
+ ULONG CopyReadWait;
+ ULONG CopyReadNoWaitMiss;
+ ULONG CopyReadWaitMiss;
+ ULONG MdlReadNoWait;
+ ULONG MdlReadWait;
+ ULONG MdlReadNoWaitMiss;
+ ULONG MdlReadWaitMiss;
+ ULONG ReadAheadIos;
+ ULONG LazyWriteIos;
+ ULONG LazyWritePages;
+ ULONG DataFlushes;
+ ULONG DataPages;
+ ULONG ContextSwitches;
+ ULONG FirstLevelTbFills;
+ ULONG SecondLevelTbFills;
+ ULONG SystemCalls;
+};
+
+} // namespace
+
+ProcessMetrics::~ProcessMetrics() { }
+
+size_t GetMaxFds() {
+ // Windows is only limited by the amount of physical memory.
+ return std::numeric_limits<size_t>::max();
+}
+
+// static
+std::unique_ptr<ProcessMetrics> ProcessMetrics::CreateProcessMetrics(
+ ProcessHandle process) {
+ return WrapUnique(new ProcessMetrics(process));
+}
+
+namespace {
+
+class WorkingSetInformationBuffer {
+ public:
+ WorkingSetInformationBuffer() {}
+ ~WorkingSetInformationBuffer() { Clear(); }
+
+ bool Reserve(size_t size) {
+ Clear();
+ // Use UncheckedMalloc here because this can be called from the code
+ // that handles low memory condition.
+ return UncheckedMalloc(size, reinterpret_cast<void**>(&buffer_));
+ }
+
+ const PSAPI_WORKING_SET_INFORMATION* operator ->() const { return buffer_; }
+
+ size_t GetPageEntryCount() const { return number_of_entries; }
+
+ // This function is used to get page entries for a process.
+ bool QueryPageEntries(const ProcessHandle& process) {
+ int retries = 5;
+ number_of_entries = 4096; // Just a guess.
+
+ for (;;) {
+ size_t buffer_size =
+ sizeof(PSAPI_WORKING_SET_INFORMATION) +
+ (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK));
+
+ if (!Reserve(buffer_size))
+ return false;
+
+ // On success, |buffer_| is populated with info about the working set of
+ // |process|. On ERROR_BAD_LENGTH failure, increase the size of the
+ // buffer and try again.
+ if (QueryWorkingSet(process, buffer_, buffer_size))
+ break; // Success
+
+ if (GetLastError() != ERROR_BAD_LENGTH)
+ return false;
+
+ number_of_entries = buffer_->NumberOfEntries;
+
+ // Maybe some entries are being added right now. Increase the buffer to
+ // take that into account. Increasing by 10% should generally be enough,
+ // especially considering the potentially low memory condition during the
+ // call (when called from OomMemoryDetails) and the potentially high
+ // number of entries (300K was observed in crash dumps).
+ number_of_entries *= 1.1;
+
+ if (--retries == 0) {
+ // If we're looping, eventually fail.
+ return false;
+ }
+ }
+
+ // TODO(chengx): Remove the comment and the logic below. It is no longer
+ // needed since we don't have Win2000 support.
+ // On windows 2000 the function returns 1 even when the buffer is too small.
+ // The number of entries that we are going to parse is the minimum between
+ // the size we allocated and the real number of entries.
+ number_of_entries = std::min(number_of_entries,
+ static_cast<size_t>(buffer_->NumberOfEntries));
+
+ return true;
+ }
+
+ private:
+ void Clear() {
+ SbMemoryDeallocate(buffer_);
+ buffer_ = nullptr;
+ }
+
+ PSAPI_WORKING_SET_INFORMATION* buffer_ = nullptr;
+
+ // Number of page entries.
+ size_t number_of_entries = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(WorkingSetInformationBuffer);
+};
+
+} // namespace
+
+TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
+ FILETIME creation_time;
+ FILETIME exit_time;
+ FILETIME kernel_time;
+ FILETIME user_time;
+
+ if (!GetProcessTimes(process_.Get(), &creation_time, &exit_time, &kernel_time,
+ &user_time)) {
+ // We don't assert here because in some cases (such as in the Task Manager)
+ // we may call this function on a process that has just exited but we have
+ // not yet received the notification.
+ return TimeDelta();
+ }
+
+ return TimeDelta::FromFileTime(kernel_time) +
+ TimeDelta::FromFileTime(user_time);
+}
+
+bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const {
+ return GetProcessIoCounters(process_.Get(), io_counters) != FALSE;
+}
+
+uint64_t ProcessMetrics::GetCumulativeDiskUsageInBytes() {
+ IoCounters counters;
+ if (!GetIOCounters(&counters))
+ return 0;
+
+ return counters.ReadTransferCount + counters.WriteTransferCount +
+ counters.OtherTransferCount;
+}
+
+ProcessMetrics::ProcessMetrics(ProcessHandle process) {
+ if (process) {
+ HANDLE duplicate_handle = INVALID_HANDLE_VALUE;
+ BOOL result = ::DuplicateHandle(::GetCurrentProcess(), process,
+ ::GetCurrentProcess(), &duplicate_handle,
+ PROCESS_QUERY_INFORMATION, FALSE, 0);
+ DPCHECK(result);
+ process_.Set(duplicate_handle);
+ }
+}
+
+size_t GetSystemCommitCharge() {
+ // Get the System Page Size.
+ SYSTEM_INFO system_info;
+ GetSystemInfo(&system_info);
+
+ PERFORMANCE_INFORMATION info;
+ if (!GetPerformanceInfo(&info, sizeof(info))) {
+ DLOG(ERROR) << "Failed to fetch internal performance info.";
+ return 0;
+ }
+ return (info.CommitTotal * system_info.dwPageSize) / 1024;
+}
+
+size_t GetPageSize() {
+ return PAGESIZE_KB * 1024;
+}
+
+// This function uses the following mapping between MEMORYSTATUSEX and
+// SystemMemoryInfoKB:
+// ullTotalPhys ==> total
+// ullAvailPhys ==> avail_phys
+// ullTotalPageFile ==> swap_total
+// ullAvailPageFile ==> swap_free
+bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) {
+ MEMORYSTATUSEX mem_status;
+ mem_status.dwLength = sizeof(mem_status);
+ if (!::GlobalMemoryStatusEx(&mem_status))
+ return false;
+
+ meminfo->total = mem_status.ullTotalPhys / 1024;
+ meminfo->avail_phys = mem_status.ullAvailPhys / 1024;
+ meminfo->swap_total = mem_status.ullTotalPageFile / 1024;
+ meminfo->swap_free = mem_status.ullAvailPageFile / 1024;
+
+ return true;
+}
+
+size_t ProcessMetrics::GetMallocUsage() {
+ // Unsupported as getting malloc usage on Windows requires iterating through
+ // the heap which is slow and crashes.
+ return 0;
+}
+
+SystemPerformanceInfo::SystemPerformanceInfo() = default;
+SystemPerformanceInfo::SystemPerformanceInfo(
+ const SystemPerformanceInfo& other) = default;
+
+std::unique_ptr<Value> SystemPerformanceInfo::ToValue() const {
+ std::unique_ptr<DictionaryValue> result(new DictionaryValue());
+
+ // Write out uint64_t variables as doubles.
+ // Note: this may discard some precision, but for JS there's no other option.
+ result->SetDouble("idle_time", strict_cast<double>(idle_time));
+ result->SetDouble("read_transfer_count",
+ strict_cast<double>(read_transfer_count));
+ result->SetDouble("write_transfer_count",
+ strict_cast<double>(write_transfer_count));
+ result->SetDouble("other_transfer_count",
+ strict_cast<double>(other_transfer_count));
+ result->SetDouble("read_operation_count",
+ strict_cast<double>(read_operation_count));
+ result->SetDouble("write_operation_count",
+ strict_cast<double>(write_operation_count));
+ result->SetDouble("other_operation_count",
+ strict_cast<double>(other_operation_count));
+ result->SetDouble("pagefile_pages_written",
+ strict_cast<double>(pagefile_pages_written));
+ result->SetDouble("pagefile_pages_write_ios",
+ strict_cast<double>(pagefile_pages_write_ios));
+ result->SetDouble("available_pages", strict_cast<double>(available_pages));
+ result->SetDouble("pages_read", strict_cast<double>(pages_read));
+ result->SetDouble("page_read_ios", strict_cast<double>(page_read_ios));
+
+ return result;
+}
+
+// Retrieves performance counters from the operating system.
+// Fills in the provided |info| structure. Returns true on success.
+BASE_EXPORT bool GetSystemPerformanceInfo(SystemPerformanceInfo* info) {
+ static const auto query_system_information_ptr =
+ reinterpret_cast<decltype(&::NtQuerySystemInformation)>(GetProcAddress(
+ GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation"));
+ if (!query_system_information_ptr)
+ return false;
+
+ SYSTEM_PERFORMANCE_INFORMATION counters = {};
+ const NTSTATUS status = query_system_information_ptr(
+ ::SystemPerformanceInformation, &counters,
+ sizeof(SYSTEM_PERFORMANCE_INFORMATION), nullptr);
+
+ if (status != STATUS_SUCCESS)
+ return false;
+
+ info->idle_time = counters.IdleTime.QuadPart;
+ info->read_transfer_count = counters.ReadTransferCount.QuadPart;
+ info->write_transfer_count = counters.WriteTransferCount.QuadPart;
+ info->other_transfer_count = counters.OtherTransferCount.QuadPart;
+ info->read_operation_count = counters.ReadOperationCount;
+ info->write_operation_count = counters.WriteOperationCount;
+ info->other_operation_count = counters.OtherOperationCount;
+ info->pagefile_pages_written = counters.PagefilePagesWritten;
+ info->pagefile_pages_write_ios = counters.PagefilePageWriteIos;
+ info->available_pages = counters.AvailablePages;
+ info->pages_read = counters.PagesRead;
+ info->page_read_ios = counters.PageReadIos;
+
+ return true;
+}
+
+} // namespace base
diff --git a/src/base/process/process_posix.cc b/src/base/process/process_posix.cc
new file mode 100644
index 0000000..45e260f
--- /dev/null
+++ b/src/base/process/process_posix.cc
@@ -0,0 +1,380 @@
+// Copyright 2011 The Chromium Authors. All rights reserved.
+// 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 <errno.h>
+#include <signal.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+
+#include "base/debug/activity_tracker.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/kill.h"
+#include "base/test/clang_coverage.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+
+#if defined(OS_MACOSX)
+#include <sys/event.h>
+
+#include "starboard/types.h"
+#endif
+
+namespace {
+
+#if !defined(OS_NACL_NONSFI)
+
+bool WaitpidWithTimeout(base::ProcessHandle handle,
+ int* status,
+ base::TimeDelta wait) {
+ // This POSIX version of this function only guarantees that we wait no less
+ // than |wait| for the process to exit. The child process may
+ // exit sometime before the timeout has ended but we may still block for up
+ // to 256 milliseconds after the fact.
+ //
+ // waitpid() has no direct support on POSIX for specifying a timeout, you can
+ // either ask it to block indefinitely or return immediately (WNOHANG).
+ // When a child process terminates a SIGCHLD signal is sent to the parent.
+ // Catching this signal would involve installing a signal handler which may
+ // affect other parts of the application and would be difficult to debug.
+ //
+ // Our strategy is to call waitpid() once up front to check if the process
+ // has already exited, otherwise to loop for |wait|, sleeping for
+ // at most 256 milliseconds each time using usleep() and then calling
+ // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and
+ // we double it every 4 sleep cycles.
+ //
+ // usleep() is speced to exit if a signal is received for which a handler
+ // has been installed. This means that when a SIGCHLD is sent, it will exit
+ // depending on behavior external to this function.
+ //
+ // This function is used primarily for unit tests, if we want to use it in
+ // the application itself it would probably be best to examine other routes.
+
+ if (wait == base::TimeDelta::Max()) {
+ return HANDLE_EINTR(waitpid(handle, status, 0)) > 0;
+ }
+
+ pid_t ret_pid = HANDLE_EINTR(waitpid(handle, status, WNOHANG));
+ static const int64_t kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds.
+ int64_t max_sleep_time_usecs = 1 << 10; // ~1 milliseconds.
+ int64_t double_sleep_time = 0;
+
+ // If the process hasn't exited yet, then sleep and try again.
+ base::TimeTicks wakeup_time = base::TimeTicks::Now() + wait;
+ while (ret_pid == 0) {
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (now > wakeup_time)
+ break;
+ // Guaranteed to be non-negative!
+ int64_t sleep_time_usecs = (wakeup_time - now).InMicroseconds();
+ // Sleep for a bit while we wait for the process to finish.
+ if (sleep_time_usecs > max_sleep_time_usecs)
+ sleep_time_usecs = max_sleep_time_usecs;
+
+ // usleep() will return 0 and set errno to EINTR on receipt of a signal
+ // such as SIGCHLD.
+ usleep(sleep_time_usecs);
+ ret_pid = HANDLE_EINTR(waitpid(handle, status, WNOHANG));
+
+ if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) &&
+ (double_sleep_time++ % 4 == 0)) {
+ max_sleep_time_usecs *= 2;
+ }
+ }
+
+ return ret_pid > 0;
+}
+
+#if defined(OS_MACOSX)
+// Using kqueue on Mac so that we can wait on non-child processes.
+// We can't use kqueues on child processes because we need to reap
+// our own children using wait.
+bool WaitForSingleNonChildProcess(base::ProcessHandle handle,
+ base::TimeDelta wait) {
+ DCHECK_GT(handle, 0);
+
+ base::ScopedFD kq(kqueue());
+ if (!kq.is_valid()) {
+ DPLOG(ERROR) << "kqueue";
+ return false;
+ }
+
+ struct kevent change = {0};
+ EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
+ int result = HANDLE_EINTR(kevent(kq.get(), &change, 1, NULL, 0, NULL));
+ if (result == -1) {
+ if (errno == ESRCH) {
+ // If the process wasn't found, it must be dead.
+ return true;
+ }
+
+ DPLOG(ERROR) << "kevent (setup " << handle << ")";
+ return false;
+ }
+
+ // Keep track of the elapsed time to be able to restart kevent if it's
+ // interrupted.
+ bool wait_forever = (wait == base::TimeDelta::Max());
+ base::TimeDelta remaining_delta;
+ base::TimeTicks deadline;
+ if (!wait_forever) {
+ remaining_delta = wait;
+ deadline = base::TimeTicks::Now() + remaining_delta;
+ }
+
+ result = -1;
+ struct kevent event = {0};
+
+ do {
+ struct timespec remaining_timespec;
+ struct timespec* remaining_timespec_ptr;
+ if (wait_forever) {
+ remaining_timespec_ptr = NULL;
+ } else {
+ remaining_timespec = remaining_delta.ToTimeSpec();
+ remaining_timespec_ptr = &remaining_timespec;
+ }
+
+ result = kevent(kq.get(), NULL, 0, &event, 1, remaining_timespec_ptr);
+
+ if (result == -1 && errno == EINTR) {
+ if (!wait_forever) {
+ remaining_delta = deadline - base::TimeTicks::Now();
+ }
+ result = 0;
+ } else {
+ break;
+ }
+ } while (wait_forever || remaining_delta > base::TimeDelta());
+
+ if (result < 0) {
+ DPLOG(ERROR) << "kevent (wait " << handle << ")";
+ return false;
+ } else if (result > 1) {
+ DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result "
+ << result;
+ return false;
+ } else if (result == 0) {
+ // Timed out.
+ return false;
+ }
+
+ DCHECK_EQ(result, 1);
+
+ if (event.filter != EVFILT_PROC ||
+ (event.fflags & NOTE_EXIT) == 0 ||
+ event.ident != static_cast<uintptr_t>(handle)) {
+ DLOG(ERROR) << "kevent (wait " << handle
+ << "): unexpected event: filter=" << event.filter
+ << ", fflags=" << event.fflags
+ << ", ident=" << event.ident;
+ return false;
+ }
+
+ return true;
+}
+#endif // OS_MACOSX
+
+bool WaitForExitWithTimeoutImpl(base::ProcessHandle handle,
+ int* exit_code,
+ base::TimeDelta timeout) {
+ const base::ProcessHandle our_pid = base::GetCurrentProcessHandle();
+ if (handle == our_pid) {
+ // We won't be able to wait for ourselves to exit.
+ return false;
+ }
+
+ const base::ProcessHandle parent_pid = base::GetParentProcessId(handle);
+ const bool exited = (parent_pid < 0);
+
+ if (!exited && parent_pid != our_pid) {
+#if defined(OS_MACOSX)
+ // On Mac we can wait on non child processes.
+ return WaitForSingleNonChildProcess(handle, timeout);
+#else
+ // Currently on Linux we can't handle non child processes.
+ NOTIMPLEMENTED();
+#endif // OS_MACOSX
+ }
+
+ int status;
+ if (!WaitpidWithTimeout(handle, &status, timeout))
+ return exited;
+ if (WIFSIGNALED(status)) {
+ if (exit_code)
+ *exit_code = -1;
+ return true;
+ }
+ if (WIFEXITED(status)) {
+ if (exit_code)
+ *exit_code = WEXITSTATUS(status);
+ return true;
+ }
+ return exited;
+}
+#endif // !defined(OS_NACL_NONSFI)
+
+} // namespace
+
+namespace base {
+
+Process::Process(ProcessHandle handle) : process_(handle) {
+}
+
+Process::~Process() = default;
+
+Process::Process(Process&& other) : process_(other.process_) {
+ other.Close();
+}
+
+Process& Process::operator=(Process&& other) {
+ process_ = other.process_;
+ other.Close();
+ return *this;
+}
+
+// static
+Process Process::Current() {
+ return Process(GetCurrentProcessHandle());
+}
+
+// static
+Process Process::Open(ProcessId pid) {
+ if (pid == GetCurrentProcId())
+ return Current();
+
+ // On POSIX process handles are the same as PIDs.
+ return Process(pid);
+}
+
+// static
+Process Process::OpenWithExtraPrivileges(ProcessId pid) {
+ // On POSIX there are no privileges to set.
+ return Open(pid);
+}
+
+// static
+Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
+ DCHECK_NE(handle, GetCurrentProcessHandle());
+ return Process(handle);
+}
+
+#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
+// static
+bool Process::CanBackgroundProcesses() {
+ return false;
+}
+#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
+
+// static
+void Process::TerminateCurrentProcessImmediately(int exit_code) {
+#if defined(CLANG_COVERAGE)
+ WriteClangCoverageProfile();
+#endif
+ _exit(exit_code);
+}
+
+bool Process::IsValid() const {
+ return process_ != kNullProcessHandle;
+}
+
+ProcessHandle Process::Handle() const {
+ return process_;
+}
+
+Process Process::Duplicate() const {
+ if (is_current())
+ return Current();
+
+ return Process(process_);
+}
+
+ProcessId Process::Pid() const {
+ DCHECK(IsValid());
+ return GetProcId(process_);
+}
+
+bool Process::is_current() const {
+ return process_ == GetCurrentProcessHandle();
+}
+
+void Process::Close() {
+ process_ = kNullProcessHandle;
+ // if the process wasn't terminated (so we waited) or the state
+ // wasn't already collected w/ a wait from process_utils, we're gonna
+ // end up w/ a zombie when it does finally exit.
+}
+
+#if !defined(OS_NACL_NONSFI)
+bool Process::Terminate(int exit_code, bool wait) const {
+ // exit_code isn't supportable.
+ DCHECK(IsValid());
+ CHECK_GT(process_, 0);
+
+ bool did_terminate = kill(process_, SIGTERM) == 0;
+
+ if (wait && did_terminate) {
+ if (WaitForExitWithTimeout(TimeDelta::FromSeconds(60), nullptr))
+ return true;
+ did_terminate = kill(process_, SIGKILL) == 0;
+ if (did_terminate)
+ return WaitForExit(nullptr);
+ }
+
+ if (!did_terminate)
+ DPLOG(ERROR) << "Unable to terminate process " << process_;
+
+ return did_terminate;
+}
+#endif // !defined(OS_NACL_NONSFI)
+
+bool Process::WaitForExit(int* exit_code) const {
+ return WaitForExitWithTimeout(TimeDelta::Max(), exit_code);
+}
+
+bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
+ if (!timeout.is_zero())
+ internal::AssertBaseSyncPrimitivesAllowed();
+
+ // Record the event that this thread is blocking upon (for hang diagnosis).
+ base::debug::ScopedProcessWaitActivity process_activity(this);
+
+ int local_exit_code = 0;
+ bool exited = WaitForExitWithTimeoutImpl(Handle(), &local_exit_code, timeout);
+ if (exited) {
+ Exited(local_exit_code);
+ if (exit_code)
+ *exit_code = local_exit_code;
+ }
+ return exited;
+}
+
+void Process::Exited(int exit_code) const {}
+
+#if !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
+bool Process::IsProcessBackgrounded() const {
+ // See SetProcessBackgrounded().
+ DCHECK(IsValid());
+ return false;
+}
+
+bool Process::SetProcessBackgrounded(bool value) {
+ // Not implemented for POSIX systems other than Linux and Mac. With POSIX, if
+ // we were to lower the process priority we wouldn't be able to raise it back
+ // to its initial priority.
+ NOTIMPLEMENTED();
+ return false;
+}
+#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) && !defined(OS_AIX)
+
+int Process::GetPriority() const {
+ DCHECK(IsValid());
+ return getpriority(PRIO_PROCESS, process_);
+}
+
+} // namespace base
diff --git a/src/base/process/process_starboard.cc b/src/base/process/process_starboard.cc
new file mode 100644
index 0000000..66eaf6f
--- /dev/null
+++ b/src/base/process/process_starboard.cc
@@ -0,0 +1,37 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// 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.
+
+#include <cstdlib>
+
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
+
+namespace {
+base::ProcessId kStarboardFakeProcessId = 1;
+}
+
+namespace base {
+
+ProcessId GetCurrentProcId() {
+ return kStarboardFakeProcessId;
+}
+
+#ifndef STARBOARD
+// static
+void Process::TerminateCurrentProcessImmediately(int exit_code) {
+ std::_Exit(exit_code);
+}
+#endif // !STARBOARD
+
+} // namespace base
\ No newline at end of file
diff --git a/src/base/process/process_unittest.cc b/src/base/process/process_unittest.cc
new file mode 100644
index 0000000..f5815cd
--- /dev/null
+++ b/src/base/process/process_unittest.cc
@@ -0,0 +1,338 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// 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 <utility>
+
+#include "base/at_exit.h"
+#include "base/debug/invalid_access_win.h"
+#include "base/process/kill.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_local.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+namespace {
+
+#if defined(OS_WIN)
+const int kExpectedStillRunningExitCode = 0x102;
+#else
+const int kExpectedStillRunningExitCode = 0;
+#endif
+
+#if defined(OS_MACOSX)
+// Fake port provider that returns the calling process's
+// task port, ignoring its argument.
+class FakePortProvider : public base::PortProvider {
+ mach_port_t TaskForPid(base::ProcessHandle process) const override {
+ return mach_task_self();
+ }
+};
+#endif
+
+} // namespace
+
+namespace base {
+
+class ProcessTest : public MultiProcessTest {
+};
+
+TEST_F(ProcessTest, Create) {
+ Process process(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+ ASSERT_FALSE(process.is_current());
+ EXPECT_NE(process.Pid(), kNullProcessId);
+ process.Close();
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessTest, CreateCurrent) {
+ Process process = Process::Current();
+ ASSERT_TRUE(process.IsValid());
+ ASSERT_TRUE(process.is_current());
+ EXPECT_NE(process.Pid(), kNullProcessId);
+ process.Close();
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessTest, Move) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ EXPECT_TRUE(process1.IsValid());
+
+ Process process2;
+ EXPECT_FALSE(process2.IsValid());
+
+ process2 = std::move(process1);
+ EXPECT_TRUE(process2.IsValid());
+ EXPECT_FALSE(process1.IsValid());
+ EXPECT_FALSE(process2.is_current());
+
+ Process process3 = Process::Current();
+ process2 = std::move(process3);
+ EXPECT_TRUE(process2.is_current());
+ EXPECT_TRUE(process2.IsValid());
+ EXPECT_FALSE(process3.IsValid());
+}
+
+TEST_F(ProcessTest, Duplicate) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = process1.Duplicate();
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_FALSE(process1.is_current());
+ EXPECT_FALSE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+TEST_F(ProcessTest, DuplicateCurrent) {
+ Process process1 = Process::Current();
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = process1.Duplicate();
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_TRUE(process1.is_current());
+ EXPECT_TRUE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+TEST_F(ProcessTest, DeprecatedGetProcessFromHandle) {
+ Process process1(SpawnChild("SimpleChildProcess"));
+ ASSERT_TRUE(process1.IsValid());
+
+ Process process2 = Process::DeprecatedGetProcessFromHandle(process1.Handle());
+ ASSERT_TRUE(process1.IsValid());
+ ASSERT_TRUE(process2.IsValid());
+ EXPECT_EQ(process1.Pid(), process2.Pid());
+ EXPECT_FALSE(process1.is_current());
+ EXPECT_FALSE(process2.is_current());
+
+ process1.Close();
+ ASSERT_TRUE(process2.IsValid());
+}
+
+MULTIPROCESS_TEST_MAIN(SleepyChildProcess) {
+ PlatformThread::Sleep(TestTimeouts::action_max_timeout());
+ return 0;
+}
+
+TEST_F(ProcessTest, Terminate) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ exit_code = kDummyExitCode;
+ int kExpectedExitCode = 250;
+ process.Terminate(kExpectedExitCode, false);
+ process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code);
+
+ EXPECT_NE(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+#if !defined(OS_POSIX) && !defined(OS_FUCHSIA)
+ // The POSIX & Fuchsia implementations actually ignore the exit_code.
+ EXPECT_EQ(kExpectedExitCode, exit_code);
+#endif
+}
+
+void AtExitHandler(void*) {
+ // At-exit handler should not be called at
+ // Process::TerminateCurrentProcessImmediately.
+ DCHECK(false);
+}
+
+class ThreadLocalObject {
+ ~ThreadLocalObject() {
+ // Thread-local storage should not be destructed at
+ // Process::TerminateCurrentProcessImmediately.
+ DCHECK(false);
+ }
+};
+
+#ifndef STARBOARD
+// TerminateCurrentProcessimmediately not supported
+MULTIPROCESS_TEST_MAIN(TerminateCurrentProcessImmediatelyWithCode0) {
+ base::ThreadLocalPointer<ThreadLocalObject> object;
+ base::AtExitManager::RegisterCallback(&AtExitHandler, nullptr);
+ Process::TerminateCurrentProcessImmediately(0);
+}
+
+TEST_F(ProcessTest, TerminateCurrentProcessImmediatelyWithZeroExitCode) {
+ Process process(SpawnChild("TerminateCurrentProcessImmediatelyWithCode0"));
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 42;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(TerminateCurrentProcessImmediatelyWithCode250) {
+ Process::TerminateCurrentProcessImmediately(250);
+}
+#endif
+
+TEST_F(ProcessTest, TerminateCurrentProcessImmediatelyWithNonZeroExitCode) {
+ Process process(SpawnChild("TerminateCurrentProcessImmediatelyWithCode250"));
+ ASSERT_TRUE(process.IsValid());
+ int exit_code = 42;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ EXPECT_EQ(250, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(FastSleepyChildProcess) {
+ PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 10);
+ return 0;
+}
+
+TEST_F(ProcessTest, WaitForExit) {
+ Process process(SpawnChild("FastSleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+TEST_F(ProcessTest, WaitForExitWithTimeout) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ ASSERT_TRUE(process.IsValid());
+
+ const int kDummyExitCode = 42;
+ int exit_code = kDummyExitCode;
+ TimeDelta timeout = TestTimeouts::tiny_timeout();
+ EXPECT_FALSE(process.WaitForExitWithTimeout(timeout, &exit_code));
+ EXPECT_EQ(kDummyExitCode, exit_code);
+
+ process.Terminate(kDummyExitCode, false);
+}
+
+// Ensure that the priority of a process is restored correctly after
+// backgrounding and restoring.
+// Note: a platform may not be willing or able to lower the priority of
+// a process. The calls to SetProcessBackground should be noops then.
+TEST_F(ProcessTest, SetProcessBackgrounded) {
+ if (!Process::CanBackgroundProcesses())
+ return;
+ Process process(SpawnChild("SimpleChildProcess"));
+ int old_priority = process.GetPriority();
+#if defined(OS_MACOSX)
+ // On the Mac, backgrounding a process requires a port to that process.
+ // In the browser it's available through the MachBroker class, which is not
+ // part of base. Additionally, there is an indefinite amount of time between
+ // spawning a process and receiving its port. Because this test just checks
+ // the ability to background/foreground a process, we can use the current
+ // process's port instead.
+ FakePortProvider provider;
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, true));
+ EXPECT_TRUE(process.IsProcessBackgrounded(&provider));
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, false));
+ EXPECT_FALSE(process.IsProcessBackgrounded(&provider));
+
+#else
+ EXPECT_TRUE(process.SetProcessBackgrounded(true));
+ EXPECT_TRUE(process.IsProcessBackgrounded());
+ EXPECT_TRUE(process.SetProcessBackgrounded(false));
+ EXPECT_FALSE(process.IsProcessBackgrounded());
+#endif
+ int new_priority = process.GetPriority();
+ EXPECT_EQ(old_priority, new_priority);
+}
+
+// Same as SetProcessBackgrounded but to this very process. It uses
+// a different code path at least for Windows.
+TEST_F(ProcessTest, SetProcessBackgroundedSelf) {
+ if (!Process::CanBackgroundProcesses())
+ return;
+ Process process = Process::Current();
+ int old_priority = process.GetPriority();
+#if defined(OS_WIN)
+ EXPECT_TRUE(process.SetProcessBackgrounded(true));
+ EXPECT_TRUE(process.IsProcessBackgrounded());
+ EXPECT_TRUE(process.SetProcessBackgrounded(false));
+ EXPECT_FALSE(process.IsProcessBackgrounded());
+#elif defined(OS_MACOSX)
+ FakePortProvider provider;
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, true));
+ EXPECT_TRUE(process.IsProcessBackgrounded(&provider));
+ EXPECT_TRUE(process.SetProcessBackgrounded(&provider, false));
+ EXPECT_FALSE(process.IsProcessBackgrounded(&provider));
+#else
+ process.SetProcessBackgrounded(true);
+ process.SetProcessBackgrounded(false);
+#endif
+ int new_priority = process.GetPriority();
+ EXPECT_EQ(old_priority, new_priority);
+}
+
+// Consumers can use WaitForExitWithTimeout(base::TimeDelta(), nullptr) to check
+// whether the process is still running. This may not be safe because of the
+// potential reusing of the process id. So we won't export Process::IsRunning()
+// on all platforms. But for the controllable scenario in the test cases, the
+// behavior should be guaranteed.
+TEST_F(ProcessTest, CurrentProcessIsRunning) {
+ EXPECT_FALSE(Process::Current().WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+
+#if defined(OS_MACOSX)
+// On Mac OSX, we can detect whether a non-child process is running.
+TEST_F(ProcessTest, PredefinedProcessIsRunning) {
+ // Process 1 is the /sbin/launchd, it should be always running.
+ EXPECT_FALSE(Process::Open(1).WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+#endif
+
+#if defined(OS_WIN)
+TEST_F(ProcessTest, HeapCorruption) {
+ EXPECT_EXIT(base::debug::win::TerminateWithHeapCorruption(),
+ ::testing::ExitedWithCode(STATUS_HEAP_CORRUPTION), "");
+}
+#endif
+
+TEST_F(ProcessTest, ChildProcessIsRunning) {
+ Process process(SpawnChild("SleepyChildProcess"));
+ EXPECT_FALSE(process.WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+ process.Terminate(0, true);
+ EXPECT_TRUE(process.WaitForExitWithTimeout(
+ base::TimeDelta(), nullptr));
+}
+
+#if defined(OS_CHROMEOS)
+
+// Tests that the function IsProcessBackgroundedCGroup() can parse the contents
+// of the /proc/<pid>/cgroup file successfully.
+TEST_F(ProcessTest, TestIsProcessBackgroundedCGroup) {
+ const char kNotBackgrounded[] = "5:cpuacct,cpu,cpuset:/daemons\n";
+ const char kBackgrounded[] =
+ "2:freezer:/chrome_renderers/to_be_frozen\n"
+ "1:cpu:/chrome_renderers/background\n";
+
+ EXPECT_FALSE(IsProcessBackgroundedCGroup(kNotBackgrounded));
+ EXPECT_TRUE(IsProcessBackgroundedCGroup(kBackgrounded));
+}
+
+#endif // defined(OS_CHROMEOS)
+
+} // namespace base
diff --git a/src/base/process/process_util_unittest.cc b/src/base/process/process_util_unittest.cc
new file mode 100644
index 0000000..35811b0
--- /dev/null
+++ b/src/base/process/process_util_unittest.cc
@@ -0,0 +1,1355 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include <limits>
+
+#include "base/command_line.h"
+#include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/process/kill.h"
+#include "base/process/launch.h"
+#include "base/process/memory.h"
+#include "base/process/process.h"
+#include "base/process/process_metrics.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/multiprocess_test.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/multiprocess_func_list.h"
+
+#if defined(OS_LINUX)
+#include <malloc.h>
+#include <sched.h>
+#include <sys/syscall.h>
+#endif
+#if defined(OS_POSIX)
+#include <sys/resource.h>
+#endif
+#if defined(OS_POSIX)
+#include <dlfcn.h>
+#include <errno.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#endif
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+#if defined(OS_MACOSX)
+#include <mach/vm_param.h>
+#include <malloc/malloc.h>
+#endif
+#if defined(OS_ANDROID)
+#include "third_party/lss/linux_syscall_support.h"
+#endif
+#if defined(OS_FUCHSIA)
+#include <lib/fdio/limits.h>
+#include <zircon/process.h>
+#include <zircon/processargs.h>
+#include <zircon/syscalls.h>
+#include "base/base_paths_fuchsia.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/fuchsia/file_utils.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "starboard/types.h"
+#endif
+
+namespace base {
+
+namespace {
+
+const char kSignalFileSlow[] = "SlowChildProcess.die";
+const char kSignalFileKill[] = "KilledChildProcess.die";
+const char kTestHelper[] = "test_child_process";
+
+#if defined(OS_POSIX)
+const char kSignalFileTerm[] = "TerminatedChildProcess.die";
+#endif
+
+#if defined(OS_FUCHSIA)
+const char kSignalFileClone[] = "ClonedTmpDir.die";
+const char kDataDirHasStaged[] = "DataDirHasStaged.die";
+const char kFooDirHasStaged[] = "FooDirHasStaged.die";
+const char kFooDirDoesNotHaveStaged[] = "FooDirDoesNotHaveStaged.die";
+#endif
+
+#if defined(OS_WIN)
+const int kExpectedStillRunningExitCode = 0x102;
+const int kExpectedKilledExitCode = 1;
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+const int kExpectedStillRunningExitCode = 0;
+#endif
+
+// Sleeps until file filename is created.
+void WaitToDie(const char* filename) {
+ FILE* fp;
+ do {
+ PlatformThread::Sleep(TimeDelta::FromMilliseconds(10));
+ fp = fopen(filename, "r");
+ } while (!fp);
+ fclose(fp);
+}
+
+// Signals children they should die now.
+void SignalChildren(const char* filename) {
+ FILE* fp = fopen(filename, "w");
+ fclose(fp);
+}
+
+// Using a pipe to the child to wait for an event was considered, but
+// there were cases in the past where pipes caused problems (other
+// libraries closing the fds, child deadlocking). This is a simple
+// case, so it's not worth the risk. Using wait loops is discouraged
+// in most instances.
+TerminationStatus WaitForChildTermination(ProcessHandle handle,
+ int* exit_code) {
+ // Now we wait until the result is something other than STILL_RUNNING.
+ TerminationStatus status = TERMINATION_STATUS_STILL_RUNNING;
+ const TimeDelta kInterval = TimeDelta::FromMilliseconds(20);
+ TimeDelta waited;
+ do {
+ status = GetTerminationStatus(handle, exit_code);
+ PlatformThread::Sleep(kInterval);
+ waited += kInterval;
+ } while (status == TERMINATION_STATUS_STILL_RUNNING &&
+ waited < TestTimeouts::action_max_timeout());
+
+ return status;
+}
+
+} // namespace
+
+const int kSuccess = 0;
+
+class ProcessUtilTest : public MultiProcessTest {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(PathService::Get(DIR_ASSETS, &test_helper_path_));
+ test_helper_path_ = test_helper_path_.AppendASCII(kTestHelper);
+ }
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+ // Spawn a child process that counts how many file descriptors are open.
+ int CountOpenFDsInChild();
+#endif
+ // Converts the filename to a platform specific filepath.
+ // On Android files can not be created in arbitrary directories.
+ static std::string GetSignalFilePath(const char* filename);
+
+ protected:
+ base::FilePath test_helper_path_;
+};
+
+std::string ProcessUtilTest::GetSignalFilePath(const char* filename) {
+#if defined(OS_ANDROID) || defined(OS_FUCHSIA)
+ FilePath tmp_dir;
+ PathService::Get(DIR_TEMP, &tmp_dir);
+ tmp_dir = tmp_dir.Append(filename);
+ return tmp_dir.value();
+#else
+ return filename;
+#endif
+}
+
+MULTIPROCESS_TEST_MAIN(SimpleChildProcess) {
+ return kSuccess;
+}
+
+// TODO(viettrungluu): This should be in a "MultiProcessTestTest".
+TEST_F(ProcessUtilTest, SpawnChild) {
+ Process process = SpawnChild("SimpleChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ int exit_code;
+ EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+}
+
+MULTIPROCESS_TEST_MAIN(SlowChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileSlow).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, KillSlowChild) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileSlow);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("SlowChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+ int exit_code;
+ EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
+ &exit_code));
+ remove(signal_file.c_str());
+}
+
+// Times out on Linux and Win, flakes on other platforms, http://crbug.com/95058
+TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileSlow);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("SlowChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_NORMAL_TERMINATION, status);
+ EXPECT_EQ(kSuccess, exit_code);
+ remove(signal_file.c_str());
+}
+
+#if defined(OS_FUCHSIA)
+
+MULTIPROCESS_TEST_MAIN(CheckDataDirHasStaged) {
+ if (!PathExists(base::FilePath("/data/staged"))) {
+ return 1;
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged).c_str());
+ return kSuccess;
+}
+
+// Test transferred paths override cloned paths.
+TEST_F(ProcessUtilTest, HandleTransfersOverrideClones) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kDataDirHasStaged);
+ remove(signal_file.c_str());
+
+ // Create a tempdir with "staged" as its contents.
+ ScopedTempDir tmpdir_with_staged;
+ ASSERT_TRUE(tmpdir_with_staged.CreateUniqueTempDir());
+ {
+ base::FilePath staged_file_path =
+ tmpdir_with_staged.GetPath().Append("staged");
+ base::File staged_file(staged_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(staged_file.created());
+ staged_file.Close();
+ }
+
+ base::LaunchOptions options;
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Attach the tempdir to "data", but also try to duplicate the existing "data"
+ // directory.
+ options.paths_to_clone.push_back(base::FilePath("/data"));
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {FilePath("/data"),
+ fuchsia::GetHandleFromFile(
+ base::File(base::FilePath(tmpdir_with_staged.GetPath()),
+ base::File::FLAG_OPEN | base::File::FLAG_READ))
+ .release()});
+
+ // Verify from that "/data/staged" exists from the child process' perspective.
+ Process process(SpawnChildWithOptions("CheckDataDirHasStaged", options));
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckMountedDir) {
+ if (!PathExists(base::FilePath("/foo/staged"))) {
+ return 1;
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged).c_str());
+ return kSuccess;
+}
+
+// Test that we can install an opaque handle in the child process' namespace.
+TEST_F(ProcessUtilTest, TransferHandleToPath) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kFooDirHasStaged);
+ remove(signal_file.c_str());
+
+ // Create a tempdir with "staged" as its contents.
+ ScopedTempDir new_tmpdir;
+ ASSERT_TRUE(new_tmpdir.CreateUniqueTempDir());
+ base::FilePath staged_file_path = new_tmpdir.GetPath().Append("staged");
+ base::File staged_file(staged_file_path,
+ base::File::FLAG_CREATE | base::File::FLAG_WRITE);
+ ASSERT_TRUE(staged_file.created());
+ staged_file.Close();
+
+ // Mount the tempdir to "/foo".
+ zx::handle tmp_handle = fuchsia::GetHandleFromFile(
+ base::File(base::FilePath(new_tmpdir.GetPath()),
+ base::File::FLAG_OPEN | base::File::FLAG_READ));
+ ASSERT_TRUE(tmp_handle.is_valid());
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {base::FilePath("/foo"), tmp_handle.release()});
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Verify from that "/foo/staged" exists from the child process' perspective.
+ Process process(SpawnChildWithOptions("CheckMountedDir", options));
+ ASSERT_TRUE(process.IsValid());
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckTmpFileExists) {
+ // Look through the filesystem to ensure that no other directories
+ // besides "tmp" are in the namespace.
+ base::FileEnumerator enumerator(
+ base::FilePath("/"), false,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ base::FilePath next_path;
+ while (!(next_path = enumerator.Next()).empty()) {
+ if (next_path != base::FilePath("/tmp")) {
+ LOG(ERROR) << "Clone policy violation: found non-tmp directory "
+ << next_path.MaybeAsASCII();
+ return 1;
+ }
+ }
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileClone).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, CloneTmp) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_TRUE(process.IsValid());
+
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+
+MULTIPROCESS_TEST_MAIN(CheckMountedDirDoesNotExist) {
+ if (PathExists(base::FilePath("/foo"))) {
+ return 1;
+ }
+ WaitToDie(
+ ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged).c_str());
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, TransferInvalidHandleFails) {
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_transfer.push_back(
+ {base::FilePath("/foo"), ZX_HANDLE_INVALID});
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ // Verify that the process is never constructed.
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kFooDirDoesNotHaveStaged);
+ remove(signal_file.c_str());
+ Process process(
+ SpawnChildWithOptions("CheckMountedDirDoesNotExist", options));
+ ASSERT_FALSE(process.IsValid());
+}
+
+TEST_F(ProcessUtilTest, CloneInvalidDirFails) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_clone.push_back(base::FilePath("/definitely_not_a_dir"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_FALSE(process.IsValid());
+}
+
+// Test that we can clone other directories. CheckTmpFileExists will return an
+// error code if it detects a directory other than "/tmp", so we can use that as
+// a signal that it successfully detected another entry in the root namespace.
+TEST_F(ProcessUtilTest, CloneAlternateDir) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileClone);
+ remove(signal_file.c_str());
+
+ LaunchOptions options;
+ options.paths_to_clone.push_back(base::FilePath("/tmp"));
+ options.paths_to_clone.push_back(base::FilePath("/data"));
+ options.spawn_flags = FDIO_SPAWN_CLONE_STDIO;
+
+ Process process(SpawnChildWithOptions("CheckTmpFileExists", options));
+ ASSERT_TRUE(process.IsValid());
+
+ SignalChildren(signal_file.c_str());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(1, exit_code);
+}
+
+TEST_F(ProcessUtilTest, HandlesToTransferClosedOnSpawnFailure) {
+ zx::handle handles[2];
+ zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
+ handles[1].reset_and_get_address());
+ ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
+
+ LaunchOptions options;
+ options.handles_to_transfer.push_back({0, handles[0].get()});
+
+ // Launch a non-existent binary, causing fdio_spawn() to fail.
+ CommandLine command_line(FilePath(
+ FILE_PATH_LITERAL("💩magical_filename_that_will_never_exist_ever")));
+ Process process(LaunchProcess(command_line, options));
+ ASSERT_FALSE(process.IsValid());
+
+ // If LaunchProcess did its job then handles[0] is no longer valid, and
+ // handles[1] should observe a channel-closed signal.
+ EXPECT_EQ(
+ zx_object_wait_one(handles[1].get(), ZX_CHANNEL_PEER_CLOSED, 0, nullptr),
+ ZX_OK);
+ EXPECT_EQ(ZX_ERR_BAD_HANDLE, zx_handle_close(handles[0].get()));
+ ignore_result(handles[0].release());
+}
+
+TEST_F(ProcessUtilTest, HandlesToTransferClosedOnBadPathToMapFailure) {
+ zx::handle handles[2];
+ zx_status_t result = zx_channel_create(0, handles[0].reset_and_get_address(),
+ handles[1].reset_and_get_address());
+ ZX_CHECK(ZX_OK == result, result) << "zx_channel_create";
+
+ LaunchOptions options;
+ options.handles_to_transfer.push_back({0, handles[0].get()});
+ options.spawn_flags = options.spawn_flags & ~FDIO_SPAWN_CLONE_NAMESPACE;
+ options.paths_to_clone.emplace_back(
+ "💩magical_path_that_will_never_exist_ever");
+
+ // LaunchProces should fail to open() the path_to_map, and fail before
+ // fdio_spawn().
+ Process process(LaunchProcess(CommandLine(FilePath()), options));
+ ASSERT_FALSE(process.IsValid());
+
+ // If LaunchProcess did its job then handles[0] is no longer valid, and
+ // handles[1] should observe a channel-closed signal.
+ EXPECT_EQ(
+ zx_object_wait_one(handles[1].get(), ZX_CHANNEL_PEER_CLOSED, 0, nullptr),
+ ZX_OK);
+ EXPECT_EQ(ZX_ERR_BAD_HANDLE, zx_handle_close(handles[0].get()));
+ ignore_result(handles[0].release());
+}
+#endif // defined(OS_FUCHSIA)
+
+// On Android SpawnProcess() doesn't use LaunchProcess() and doesn't support
+// LaunchOptions::current_directory.
+#if !defined(OS_ANDROID)
+MULTIPROCESS_TEST_MAIN(CheckCwdProcess) {
+ FilePath expected;
+ CHECK(GetTempDir(&expected));
+ expected = MakeAbsoluteFilePath(expected);
+ CHECK(!expected.empty());
+
+ FilePath actual;
+ CHECK(GetCurrentDirectory(&actual));
+ actual = MakeAbsoluteFilePath(actual);
+ CHECK(!actual.empty());
+
+ CHECK(expected == actual) << "Expected: " << expected.value()
+ << " Actual: " << actual.value();
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, CurrentDirectory) {
+ // TODO(rickyz): Add support for passing arguments to multiprocess children,
+ // then create a special directory for this test.
+ FilePath tmp_dir;
+ ASSERT_TRUE(GetTempDir(&tmp_dir));
+
+ LaunchOptions options;
+ options.current_directory = tmp_dir;
+
+ Process process(SpawnChildWithOptions("CheckCwdProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+#endif // !defined(OS_ANDROID)
+
+#if defined(OS_WIN)
+// TODO(cpu): figure out how to test this in other platforms.
+TEST_F(ProcessUtilTest, GetProcId) {
+ ProcessId id1 = GetProcId(GetCurrentProcess());
+ EXPECT_NE(0ul, id1);
+ Process process = SpawnChild("SimpleChildProcess");
+ ASSERT_TRUE(process.IsValid());
+ ProcessId id2 = process.Pid();
+ EXPECT_NE(0ul, id2);
+ EXPECT_NE(id1, id2);
+}
+#endif // defined(OS_WIN)
+
+#if !defined(OS_MACOSX) && !defined(OS_ANDROID)
+// This test is disabled on Mac, since it's flaky due to ReportCrash
+// taking a variable amount of time to parse and load the debug and
+// symbol data for this unit test's executable before firing the
+// signal handler.
+//
+// TODO(gspencer): turn this test process into a very small program
+// with no symbols (instead of using the multiprocess testing
+// framework) to reduce the ReportCrash overhead.
+//
+// It is disabled on Android as MultiprocessTests are started as services that
+// the framework restarts on crashes.
+const char kSignalFileCrash[] = "CrashingChildProcess.die";
+
+MULTIPROCESS_TEST_MAIN(CrashingChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileCrash).c_str());
+#if defined(OS_POSIX)
+ // Have to disable to signal handler for segv so we can get a crash
+ // instead of an abnormal termination through the crash dump handler.
+ ::signal(SIGSEGV, SIG_DFL);
+#endif
+ // Make this process have a segmentation fault.
+ volatile int* oops = nullptr;
+ *oops = 0xDEAD;
+ return 1;
+}
+
+// This test intentionally crashes, so we don't need to run it under
+// AddressSanitizer.
+#if defined(ADDRESS_SANITIZER) || defined(OS_FUCHSIA)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia.
+#define MAYBE_GetTerminationStatusCrash DISABLED_GetTerminationStatusCrash
+#else
+#define MAYBE_GetTerminationStatusCrash GetTerminationStatusCrash
+#endif
+TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileCrash);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("CrashingChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_CRASHED, status);
+
+#if defined(OS_WIN)
+ EXPECT_EQ(static_cast<int>(0xc0000005), exit_code);
+#elif defined(OS_POSIX)
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGSEGV, signal);
+#endif
+
+ // Reset signal handlers back to "normal".
+ debug::EnableInProcessStackDumping();
+ remove(signal_file.c_str());
+}
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+MULTIPROCESS_TEST_MAIN(KilledChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileKill).c_str());
+#if defined(OS_WIN)
+ // Kill ourselves.
+ HANDLE handle = ::OpenProcess(PROCESS_ALL_ACCESS, 0, ::GetCurrentProcessId());
+ ::TerminateProcess(handle, kExpectedKilledExitCode);
+#elif defined(OS_POSIX)
+ // Send a SIGKILL to this process, just like the OOM killer would.
+ ::kill(getpid(), SIGKILL);
+#elif defined(OS_FUCHSIA)
+ zx_task_kill(zx_process_self());
+#endif
+ return 1;
+}
+
+#if defined(OS_POSIX)
+MULTIPROCESS_TEST_MAIN(TerminatedChildProcess) {
+ WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileTerm).c_str());
+ // Send a SIGTERM to this process.
+ ::kill(getpid(), SIGTERM);
+ return 1;
+}
+#endif // defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+#if defined(OS_FUCHSIA)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia.
+#define MAYBE_GetTerminationStatusSigKill DISABLED_GetTerminationStatusSigKill
+#else
+#define MAYBE_GetTerminationStatusSigKill GetTerminationStatusSigKill
+#endif
+TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusSigKill) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileKill);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("KilledChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+#if defined(OS_CHROMEOS)
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM, status);
+#else
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED, status);
+#endif
+
+#if defined(OS_WIN)
+ EXPECT_EQ(kExpectedKilledExitCode, exit_code);
+#elif defined(OS_POSIX)
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGKILL, signal);
+#endif
+ remove(signal_file.c_str());
+}
+
+#if defined(OS_POSIX)
+// TODO(crbug.com/753490): Access to the process termination reason is not
+// implemented in Fuchsia. Unix signals are not implemented in Fuchsia so this
+// test might not be relevant anyway.
+TEST_F(ProcessUtilTest, GetTerminationStatusSigTerm) {
+ const std::string signal_file =
+ ProcessUtilTest::GetSignalFilePath(kSignalFileTerm);
+ remove(signal_file.c_str());
+ Process process = SpawnChild("TerminatedChildProcess");
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_EQ(TERMINATION_STATUS_STILL_RUNNING,
+ GetTerminationStatus(process.Handle(), &exit_code));
+ EXPECT_EQ(kExpectedStillRunningExitCode, exit_code);
+
+ SignalChildren(signal_file.c_str());
+ exit_code = 42;
+ TerminationStatus status =
+ WaitForChildTermination(process.Handle(), &exit_code);
+ EXPECT_EQ(TERMINATION_STATUS_PROCESS_WAS_KILLED, status);
+
+ int signaled = WIFSIGNALED(exit_code);
+ EXPECT_NE(0, signaled);
+ int signal = WTERMSIG(exit_code);
+ EXPECT_EQ(SIGTERM, signal);
+ remove(signal_file.c_str());
+}
+#endif // defined(OS_POSIX)
+
+TEST_F(ProcessUtilTest, EnsureTerminationUndying) {
+ test::ScopedTaskEnvironment task_environment;
+
+ Process child_process = SpawnChild("process_util_test_never_die");
+ ASSERT_TRUE(child_process.IsValid());
+
+ EnsureProcessTerminated(child_process.Duplicate());
+
+#if defined(OS_POSIX)
+ errno = 0;
+#endif // defined(OS_POSIX)
+
+ // Allow a generous timeout, to cope with slow/loaded test bots.
+ bool did_exit = child_process.WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr);
+
+#if defined(OS_POSIX)
+ // Both EnsureProcessTerminated() and WaitForExitWithTimeout() will call
+ // waitpid(). One will succeed, and the other will fail with ECHILD. If our
+ // wait failed then check for ECHILD, and assumed |did_exit| in that case.
+ did_exit = did_exit || (errno == ECHILD);
+#endif // defined(OS_POSIX)
+
+ EXPECT_TRUE(did_exit);
+}
+
+MULTIPROCESS_TEST_MAIN(process_util_test_never_die) {
+ while (1) {
+ PlatformThread::Sleep(TimeDelta::FromSeconds(500));
+ }
+ return kSuccess;
+}
+
+TEST_F(ProcessUtilTest, EnsureTerminationGracefulExit) {
+ test::ScopedTaskEnvironment task_environment;
+
+ Process child_process = SpawnChild("process_util_test_die_immediately");
+ ASSERT_TRUE(child_process.IsValid());
+
+ // Wait for the child process to actually exit.
+ child_process.Duplicate().WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr);
+
+ EnsureProcessTerminated(child_process.Duplicate());
+
+ // Verify that the process is really, truly gone.
+ EXPECT_TRUE(child_process.WaitForExitWithTimeout(
+ TestTimeouts::action_max_timeout(), nullptr));
+}
+
+MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) {
+ return kSuccess;
+}
+
+#if defined(OS_WIN)
+// TODO(estade): if possible, port this test.
+TEST_F(ProcessUtilTest, LaunchAsUser) {
+ UserTokenHandle token;
+ ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token));
+ LaunchOptions options;
+ options.as_user = token;
+ EXPECT_TRUE(
+ LaunchProcess(MakeCmdLine("SimpleChildProcess"), options).IsValid());
+}
+
+static const char kEventToTriggerHandleSwitch[] = "event-to-trigger-handle";
+
+MULTIPROCESS_TEST_MAIN(TriggerEventChildProcess) {
+ std::string handle_value_string =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kEventToTriggerHandleSwitch);
+ CHECK(!handle_value_string.empty());
+
+ uint64_t handle_value_uint64;
+ CHECK(StringToUint64(handle_value_string, &handle_value_uint64));
+ // Give ownership of the handle to |event|.
+ WaitableEvent event(
+ win::ScopedHandle(reinterpret_cast<HANDLE>(handle_value_uint64)));
+
+ event.Signal();
+
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, InheritSpecifiedHandles) {
+ // Manually create the event, so that it can be inheritable.
+ SECURITY_ATTRIBUTES security_attributes = {};
+ security_attributes.nLength = static_cast<DWORD>(sizeof(security_attributes));
+ security_attributes.lpSecurityDescriptor = NULL;
+ security_attributes.bInheritHandle = true;
+
+ // Takes ownership of the event handle.
+ WaitableEvent event(
+ win::ScopedHandle(CreateEvent(&security_attributes, true, false, NULL)));
+ LaunchOptions options;
+ options.handles_to_inherit.emplace_back(event.handle());
+
+ CommandLine cmd_line = MakeCmdLine("TriggerEventChildProcess");
+ cmd_line.AppendSwitchASCII(
+ kEventToTriggerHandleSwitch,
+ NumberToString(reinterpret_cast<uint64_t>(event.handle())));
+
+ // Launch the process and wait for it to trigger the event.
+ ASSERT_TRUE(LaunchProcess(cmd_line, options).IsValid());
+ EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout()));
+}
+#endif // defined(OS_WIN)
+
+TEST_F(ProcessUtilTest, GetAppOutput) {
+ base::CommandLine command(test_helper_path_);
+ command.AppendArg("hello");
+ command.AppendArg("there");
+ command.AppendArg("good");
+ command.AppendArg("people");
+ std::string output;
+ EXPECT_TRUE(GetAppOutput(command, &output));
+ EXPECT_EQ("hello there good people", output);
+ output.clear();
+
+ const char* kEchoMessage = "blah";
+ command = base::CommandLine(test_helper_path_);
+ command.AppendArg("-x");
+ command.AppendArg("28");
+ command.AppendArg(kEchoMessage);
+ EXPECT_FALSE(GetAppOutput(command, &output));
+ EXPECT_EQ(kEchoMessage, output);
+}
+
+TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) {
+ const char* kEchoMessage1 = "doge";
+ int exit_code = -1;
+ base::CommandLine command(test_helper_path_);
+ command.AppendArg(kEchoMessage1);
+ std::string output;
+ EXPECT_TRUE(GetAppOutputWithExitCode(command, &output, &exit_code));
+ EXPECT_EQ(kEchoMessage1, output);
+ EXPECT_EQ(0, exit_code);
+ output.clear();
+
+ const char* kEchoMessage2 = "pupper";
+ const int kExpectedExitCode = 42;
+ command = base::CommandLine(test_helper_path_);
+ command.AppendArg("-x");
+ command.AppendArg(base::IntToString(kExpectedExitCode));
+ command.AppendArg(kEchoMessage2);
+#if defined(OS_WIN)
+ // On Windows, anything that quits with a nonzero status code is handled as a
+ // "crash", so just ignore GetAppOutputWithExitCode's return value.
+ GetAppOutputWithExitCode(command, &output, &exit_code);
+#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+ EXPECT_TRUE(GetAppOutputWithExitCode(command, &output, &exit_code));
+#endif
+ EXPECT_EQ(kEchoMessage2, output);
+ EXPECT_EQ(kExpectedExitCode, exit_code);
+}
+
+#if defined(OS_POSIX) || defined(OS_FUCHSIA)
+
+namespace {
+
+// Returns the maximum number of files that a process can have open.
+// Returns 0 on error.
+int GetMaxFilesOpenInProcess() {
+#if defined(OS_FUCHSIA)
+ return FDIO_MAX_FD;
+#else
+ struct rlimit rlim;
+ if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) {
+ return 0;
+ }
+
+ // rlim_t is a uint64_t - clip to maxint. We do this since FD #s are ints
+ // which are all 32 bits on the supported platforms.
+ rlim_t max_int = static_cast<rlim_t>(std::numeric_limits<int32_t>::max());
+ if (rlim.rlim_cur > max_int) {
+ return max_int;
+ }
+
+ return rlim.rlim_cur;
+#endif // defined(OS_FUCHSIA)
+}
+
+const int kChildPipe = 20; // FD # for write end of pipe in child process.
+
+#if defined(OS_MACOSX)
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/sys/guarded.h>
+#if !defined(_GUARDID_T)
+#define _GUARDID_T
+typedef __uint64_t guardid_t;
+#endif // _GUARDID_T
+
+// From .../MacOSX10.9.sdk/usr/include/sys/syscall.h
+#if !defined(SYS_change_fdguard_np)
+#define SYS_change_fdguard_np 444
+#endif
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/sys/guarded.h>
+#if !defined(GUARD_DUP)
+#define GUARD_DUP (1u << 1)
+#endif
+
+// <http://opensource.apple.com/source/xnu/xnu-2422.1.72/bsd/kern/kern_guarded.c?txt>
+//
+// Atomically replaces |guard|/|guardflags| with |nguard|/|nguardflags| on |fd|.
+int change_fdguard_np(int fd,
+ const guardid_t *guard, u_int guardflags,
+ const guardid_t *nguard, u_int nguardflags,
+ int *fdflagsp) {
+ return syscall(SYS_change_fdguard_np, fd, guard, guardflags,
+ nguard, nguardflags, fdflagsp);
+}
+
+// Attempt to set a file-descriptor guard on |fd|. In case of success, remove
+// it and return |true| to indicate that it can be guarded. Returning |false|
+// means either that |fd| is guarded by some other code, or more likely EBADF.
+//
+// Starting with 10.9, libdispatch began setting GUARD_DUP on a file descriptor.
+// Unfortunately, it is spun up as part of +[NSApplication initialize], which is
+// not really something that Chromium can avoid using on OSX. See
+// <http://crbug.com/338157>. This function allows querying whether the file
+// descriptor is guarded before attempting to close it.
+bool CanGuardFd(int fd) {
+ // Saves the original flags to reset later.
+ int original_fdflags = 0;
+
+ // This can be any value at all, it just has to match up between the two
+ // calls.
+ const guardid_t kGuard = 15;
+
+ // Attempt to change the guard. This can fail with EBADF if the file
+ // descriptor is bad, or EINVAL if the fd already has a guard set.
+ int ret =
+ change_fdguard_np(fd, NULL, 0, &kGuard, GUARD_DUP, &original_fdflags);
+ if (ret == -1)
+ return false;
+
+ // Remove the guard. It should not be possible to fail in removing the guard
+ // just added.
+ ret = change_fdguard_np(fd, &kGuard, GUARD_DUP, NULL, 0, &original_fdflags);
+ DPCHECK(ret == 0);
+
+ return true;
+}
+#endif // defined(OS_MACOSX)
+
+} // namespace
+
+MULTIPROCESS_TEST_MAIN(ProcessUtilsLeakFDChildProcess) {
+ // This child process counts the number of open FDs, it then writes that
+ // number out to a pipe connected to the parent.
+ int num_open_files = 0;
+ int write_pipe = kChildPipe;
+ int max_files = GetMaxFilesOpenInProcess();
+ for (int i = STDERR_FILENO + 1; i < max_files; i++) {
+#if defined(OS_MACOSX)
+ // Ignore guarded or invalid file descriptors.
+ if (!CanGuardFd(i))
+ continue;
+#endif
+
+ if (i != kChildPipe) {
+ int fd;
+ if ((fd = HANDLE_EINTR(dup(i))) != -1) {
+ close(fd);
+ num_open_files += 1;
+ }
+ }
+ }
+
+ int written = HANDLE_EINTR(write(write_pipe, &num_open_files,
+ sizeof(num_open_files)));
+ DCHECK_EQ(static_cast<size_t>(written), sizeof(num_open_files));
+ int ret = IGNORE_EINTR(close(write_pipe));
+ DPCHECK(ret == 0);
+
+ return 0;
+}
+
+int ProcessUtilTest::CountOpenFDsInChild() {
+ int fds[2];
+ if (pipe(fds) < 0)
+ NOTREACHED();
+
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(fds[1], kChildPipe);
+ Process process =
+ SpawnChildWithOptions("ProcessUtilsLeakFDChildProcess", options);
+ CHECK(process.IsValid());
+ int ret = IGNORE_EINTR(close(fds[1]));
+ DPCHECK(ret == 0);
+
+ // Read number of open files in client process from pipe;
+ int num_open_files = -1;
+ ssize_t bytes_read =
+ HANDLE_EINTR(read(fds[0], &num_open_files, sizeof(num_open_files)));
+ CHECK_EQ(bytes_read, static_cast<ssize_t>(sizeof(num_open_files)));
+
+#if defined(THREAD_SANITIZER)
+ // Compiler-based ThreadSanitizer makes this test slow.
+ TimeDelta timeout = TimeDelta::FromSeconds(3);
+#else
+ TimeDelta timeout = TimeDelta::FromSeconds(1);
+#endif
+ int exit_code;
+ CHECK(process.WaitForExitWithTimeout(timeout, &exit_code));
+ ret = IGNORE_EINTR(close(fds[0]));
+ DPCHECK(ret == 0);
+
+ return num_open_files;
+}
+
+#if defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+// ProcessUtilTest.FDRemapping is flaky when ran under xvfb-run on Precise.
+// The problem is 100% reproducible with both ASan and TSan.
+// See http://crbug.com/136720.
+#define MAYBE_FDRemapping DISABLED_FDRemapping
+#else
+#define MAYBE_FDRemapping FDRemapping
+#endif // defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
+TEST_F(ProcessUtilTest, MAYBE_FDRemapping) {
+ int fds_before = CountOpenFDsInChild();
+
+ // open some dummy fds to make sure they don't propagate over to the
+ // child process.
+ int dev_null = open("/dev/null", O_RDONLY);
+ DPCHECK(dev_null != -1);
+ int sockets[2];
+ int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
+ DPCHECK(ret == 0);
+
+ int fds_after = CountOpenFDsInChild();
+
+ ASSERT_EQ(fds_after, fds_before);
+
+ ret = IGNORE_EINTR(close(sockets[0]));
+ DPCHECK(ret == 0);
+ ret = IGNORE_EINTR(close(sockets[1]));
+ DPCHECK(ret == 0);
+ ret = IGNORE_EINTR(close(dev_null));
+ DPCHECK(ret == 0);
+}
+
+const char kPipeValue = '\xcc';
+MULTIPROCESS_TEST_MAIN(ProcessUtilsVerifyStdio) {
+ // Write to stdio so the parent process can observe output.
+ CHECK_EQ(1, HANDLE_EINTR(write(STDOUT_FILENO, &kPipeValue, 1)));
+
+ // Close all of the handles, to verify they are valid.
+ CHECK_EQ(0, IGNORE_EINTR(close(STDIN_FILENO)));
+ CHECK_EQ(0, IGNORE_EINTR(close(STDOUT_FILENO)));
+ CHECK_EQ(0, IGNORE_EINTR(close(STDERR_FILENO)));
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, FDRemappingIncludesStdio) {
+ int dev_null = open("/dev/null", O_RDONLY);
+ ASSERT_LT(2, dev_null);
+
+ // Backup stdio and replace it with the write end of a pipe, for our
+ // child process to inherit.
+ int pipe_fds[2];
+ int result = pipe(pipe_fds);
+ ASSERT_EQ(0, result);
+ int backup_stdio = HANDLE_EINTR(dup(STDOUT_FILENO));
+ ASSERT_LE(0, backup_stdio);
+ result = dup2(pipe_fds[1], STDOUT_FILENO);
+ ASSERT_EQ(STDOUT_FILENO, result);
+
+ // Launch the test process, which should inherit our pipe stdio.
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(dev_null, dev_null);
+ Process process = SpawnChildWithOptions("ProcessUtilsVerifyStdio", options);
+ ASSERT_TRUE(process.IsValid());
+
+ // Restore stdio, so we can output stuff.
+ result = dup2(backup_stdio, STDOUT_FILENO);
+ ASSERT_EQ(STDOUT_FILENO, result);
+
+ // Close our copy of the write end of the pipe, so that the read()
+ // from the other end will see EOF if it wasn't copied to the child.
+ result = IGNORE_EINTR(close(pipe_fds[1]));
+ ASSERT_EQ(0, result);
+
+ result = IGNORE_EINTR(close(backup_stdio));
+ ASSERT_EQ(0, result);
+ result = IGNORE_EINTR(close(dev_null));
+ ASSERT_EQ(0, result);
+
+ // Read from the pipe to verify that it is connected to the child
+ // process' stdio.
+ char buf[16] = {};
+ EXPECT_EQ(1, HANDLE_EINTR(read(pipe_fds[0], buf, sizeof(buf))));
+ EXPECT_EQ(kPipeValue, buf[0]);
+
+ result = IGNORE_EINTR(close(pipe_fds[0]));
+ ASSERT_EQ(0, result);
+
+ int exit_code;
+ ASSERT_TRUE(
+ process.WaitForExitWithTimeout(TimeDelta::FromSeconds(5), &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+#if defined(OS_FUCHSIA)
+
+const uint16_t kStartupHandleId = 43;
+MULTIPROCESS_TEST_MAIN(ProcessUtilsVerifyHandle) {
+ zx_handle_t handle =
+ zx_take_startup_handle(PA_HND(PA_USER0, kStartupHandleId));
+ CHECK_NE(ZX_HANDLE_INVALID, handle);
+
+ // Write to the pipe so the parent process can observe output.
+ size_t bytes_written = 0;
+ zx_status_t result = zx_socket_write(handle, 0, &kPipeValue,
+ sizeof(kPipeValue), &bytes_written);
+ CHECK_EQ(ZX_OK, result);
+ CHECK_EQ(1u, bytes_written);
+
+ CHECK_EQ(ZX_OK, zx_handle_close(handle));
+ return 0;
+}
+
+TEST_F(ProcessUtilTest, LaunchWithHandleTransfer) {
+ // Create a pipe to pass to the child process.
+ zx_handle_t handles[2];
+ zx_status_t result =
+ zx_socket_create(ZX_SOCKET_STREAM, &handles[0], &handles[1]);
+ ASSERT_EQ(ZX_OK, result);
+
+ // Launch the test process, and pass it one end of the pipe.
+ LaunchOptions options;
+ options.handles_to_transfer.push_back(
+ {PA_HND(PA_USER0, kStartupHandleId), handles[0]});
+ Process process = SpawnChildWithOptions("ProcessUtilsVerifyHandle", options);
+ ASSERT_TRUE(process.IsValid());
+
+ // Read from the pipe to verify that the child received it.
+ zx_signals_t signals = 0;
+ result = zx_object_wait_one(
+ handles[1], ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED,
+ (base::TimeTicks::Now() + TestTimeouts::action_timeout()).ToZxTime(),
+ &signals);
+ ASSERT_EQ(ZX_OK, result);
+ ASSERT_TRUE(signals & ZX_SOCKET_READABLE);
+
+ size_t bytes_read = 0;
+ char buf[16] = {0};
+ result = zx_socket_read(handles[1], 0, buf, sizeof(buf), &bytes_read);
+ EXPECT_EQ(ZX_OK, result);
+ EXPECT_EQ(1u, bytes_read);
+ EXPECT_EQ(kPipeValue, buf[0]);
+
+ CHECK_EQ(ZX_OK, zx_handle_close(handles[1]));
+
+ int exit_code;
+ ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_timeout(),
+ &exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+
+#endif // defined(OS_FUCHSIA)
+
+namespace {
+
+std::string TestLaunchProcess(const std::vector<std::string>& args,
+ const EnvironmentMap& env_changes,
+ const bool clear_environ,
+ const int clone_flags) {
+ int fds[2];
+ PCHECK(pipe(fds) == 0);
+
+ LaunchOptions options;
+ options.wait = true;
+ options.environ = env_changes;
+ options.clear_environ = clear_environ;
+ options.fds_to_remap.emplace_back(fds[1], 1);
+#if defined(OS_LINUX)
+ options.clone_flags = clone_flags;
+#else
+ CHECK_EQ(0, clone_flags);
+#endif // defined(OS_LINUX)
+ EXPECT_TRUE(LaunchProcess(args, options).IsValid());
+ PCHECK(IGNORE_EINTR(close(fds[1])) == 0);
+
+ char buf[512];
+ const ssize_t n = HANDLE_EINTR(read(fds[0], buf, sizeof(buf)));
+
+ PCHECK(IGNORE_EINTR(close(fds[0])) == 0);
+
+ return std::string(buf, n);
+}
+
+const char kLargeString[] =
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789"
+ "0123456789012345678901234567890123456789012345678901234567890123456789";
+
+} // namespace
+
+TEST_F(ProcessUtilTest, LaunchProcess) {
+ const int no_clone_flags = 0;
+ const bool no_clear_environ = false;
+ const char kBaseTest[] = "BASE_TEST";
+ const std::vector<std::string> kPrintEnvCommand = {test_helper_path_.value(),
+ "-e", kBaseTest};
+
+ EnvironmentMap env_changes;
+ env_changes[kBaseTest] = "bar";
+ EXPECT_EQ("bar", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+ env_changes.clear();
+
+ EXPECT_EQ(0, setenv(kBaseTest, "testing", 1 /* override */));
+ EXPECT_EQ("testing", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes[kBaseTest] = std::string();
+ EXPECT_EQ("", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes[kBaseTest] = "foo";
+ EXPECT_EQ("foo", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+ env_changes.clear();
+ EXPECT_EQ(0, setenv(kBaseTest, kLargeString, 1 /* override */));
+ EXPECT_EQ(std::string(kLargeString),
+ TestLaunchProcess(kPrintEnvCommand, env_changes, no_clear_environ,
+ no_clone_flags));
+
+ env_changes[kBaseTest] = "wibble";
+ EXPECT_EQ("wibble", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, no_clone_flags));
+
+#if defined(OS_LINUX)
+ // Test a non-trival value for clone_flags.
+ EXPECT_EQ("wibble", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ no_clear_environ, CLONE_FS));
+
+ EXPECT_EQ("wibble",
+ TestLaunchProcess(kPrintEnvCommand, env_changes,
+ true /* clear_environ */, no_clone_flags));
+ env_changes.clear();
+ EXPECT_EQ("", TestLaunchProcess(kPrintEnvCommand, env_changes,
+ true /* clear_environ */, no_clone_flags));
+#endif // defined(OS_LINUX)
+}
+
+// There's no such thing as a parent process id on Fuchsia.
+#if !defined(OS_FUCHSIA)
+TEST_F(ProcessUtilTest, GetParentProcessId) {
+ ProcessId ppid = GetParentProcessId(GetCurrentProcessHandle());
+ EXPECT_EQ(ppid, static_cast<ProcessId>(getppid()));
+}
+#endif // !defined(OS_FUCHSIA)
+
+#if !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
+class WriteToPipeDelegate : public LaunchOptions::PreExecDelegate {
+ public:
+ explicit WriteToPipeDelegate(int fd) : fd_(fd) {}
+ ~WriteToPipeDelegate() override = default;
+ void RunAsyncSafe() override {
+ RAW_CHECK(HANDLE_EINTR(write(fd_, &kPipeValue, 1)) == 1);
+ RAW_CHECK(IGNORE_EINTR(close(fd_)) == 0);
+ }
+
+ private:
+ int fd_;
+ DISALLOW_COPY_AND_ASSIGN(WriteToPipeDelegate);
+};
+
+TEST_F(ProcessUtilTest, PreExecHook) {
+ int pipe_fds[2];
+ ASSERT_EQ(0, pipe(pipe_fds));
+
+ ScopedFD read_fd(pipe_fds[0]);
+ ScopedFD write_fd(pipe_fds[1]);
+
+ WriteToPipeDelegate write_to_pipe_delegate(write_fd.get());
+ LaunchOptions options;
+ options.fds_to_remap.emplace_back(write_fd.get(), write_fd.get());
+ options.pre_exec_delegate = &write_to_pipe_delegate;
+ Process process(SpawnChildWithOptions("SimpleChildProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ write_fd.reset();
+ char c;
+ ASSERT_EQ(1, HANDLE_EINTR(read(read_fd.get(), &c, 1)));
+ EXPECT_EQ(c, kPipeValue);
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(0, exit_code);
+}
+#endif // !defined(OS_ANDROID) && !defined(OS_FUCHSIA)
+
+#endif // !defined(OS_MACOSX) && !defined(OS_ANDROID)
+
+#if defined(OS_LINUX)
+MULTIPROCESS_TEST_MAIN(CheckPidProcess) {
+ const pid_t kInitPid = 1;
+ const pid_t pid = syscall(__NR_getpid);
+ CHECK(pid == kInitPid);
+ CHECK(getpid() == pid);
+ return kSuccess;
+}
+
+#if defined(CLONE_NEWUSER) && defined(CLONE_NEWPID)
+TEST_F(ProcessUtilTest, CloneFlags) {
+ if (!PathExists(FilePath("/proc/self/ns/user")) ||
+ !PathExists(FilePath("/proc/self/ns/pid"))) {
+ // User or PID namespaces are not supported.
+ return;
+ }
+
+ LaunchOptions options;
+ options.clone_flags = CLONE_NEWUSER | CLONE_NEWPID;
+
+ Process process(SpawnChildWithOptions("CheckPidProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = 42;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_EQ(kSuccess, exit_code);
+}
+#endif // defined(CLONE_NEWUSER) && defined(CLONE_NEWPID)
+
+TEST(ForkWithFlagsTest, UpdatesPidCache) {
+ // Warm up the libc pid cache, if there is one.
+ ASSERT_EQ(syscall(__NR_getpid), getpid());
+
+ pid_t ctid = 0;
+ const pid_t pid = ForkWithFlags(SIGCHLD | CLONE_CHILD_SETTID, nullptr, &ctid);
+ if (pid == 0) {
+ // In child. Check both the raw getpid syscall and the libc getpid wrapper
+ // (which may rely on a pid cache).
+ RAW_CHECK(syscall(__NR_getpid) == ctid);
+ RAW_CHECK(getpid() == ctid);
+ _exit(kSuccess);
+ }
+
+ ASSERT_NE(-1, pid);
+ int status = 42;
+ ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0)));
+ ASSERT_TRUE(WIFEXITED(status));
+ EXPECT_EQ(kSuccess, WEXITSTATUS(status));
+}
+
+TEST_F(ProcessUtilTest, InvalidCurrentDirectory) {
+ LaunchOptions options;
+ options.current_directory = FilePath("/dev/null");
+
+ Process process(SpawnChildWithOptions("SimpleChildProcess", options));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = kSuccess;
+ EXPECT_TRUE(process.WaitForExit(&exit_code));
+ EXPECT_NE(kSuccess, exit_code);
+}
+#endif // defined(OS_LINUX)
+
+} // namespace base
diff --git a/src/base/process/process_win.cc b/src/base/process/process_win.cc
new file mode 100644
index 0000000..4d1eccf
--- /dev/null
+++ b/src/base/process/process_win.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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 "base/debug/activity_tracker.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/process/kill.h"
+#include "base/test/clang_coverage.h"
+#include "base/threading/thread_restrictions.h"
+
+#include <windows.h>
+
+#include "starboard/types.h"
+
+namespace {
+
+DWORD kBasicProcessAccess =
+ PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE;
+
+} // namespace
+
+namespace base {
+
+Process::Process(ProcessHandle handle)
+ : process_(handle), is_current_process_(false) {
+ CHECK_NE(handle, ::GetCurrentProcess());
+}
+
+Process::Process(Process&& other)
+ : process_(other.process_.Take()),
+ is_current_process_(other.is_current_process_) {
+ other.Close();
+}
+
+Process::~Process() {
+}
+
+Process& Process::operator=(Process&& other) {
+ DCHECK_NE(this, &other);
+ process_.Set(other.process_.Take());
+ is_current_process_ = other.is_current_process_;
+ other.Close();
+ return *this;
+}
+
+// static
+Process Process::Current() {
+ Process process;
+ process.is_current_process_ = true;
+ return process;
+}
+
+// static
+Process Process::Open(ProcessId pid) {
+ return Process(::OpenProcess(kBasicProcessAccess, FALSE, pid));
+}
+
+// static
+Process Process::OpenWithExtraPrivileges(ProcessId pid) {
+ DWORD access = kBasicProcessAccess | PROCESS_DUP_HANDLE | PROCESS_VM_READ;
+ return Process(::OpenProcess(access, FALSE, pid));
+}
+
+// static
+Process Process::OpenWithAccess(ProcessId pid, DWORD desired_access) {
+ return Process(::OpenProcess(desired_access, FALSE, pid));
+}
+
+// static
+Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) {
+ DCHECK_NE(handle, ::GetCurrentProcess());
+ ProcessHandle out_handle;
+ if (!::DuplicateHandle(GetCurrentProcess(), handle,
+ GetCurrentProcess(), &out_handle,
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ return Process();
+ }
+ return Process(out_handle);
+}
+
+// static
+bool Process::CanBackgroundProcesses() {
+ return true;
+}
+
+// static
+void Process::TerminateCurrentProcessImmediately(int exit_code) {
+#if defined(CLANG_COVERAGE)
+ WriteClangCoverageProfile();
+#endif
+ ::TerminateProcess(GetCurrentProcess(), exit_code);
+ // There is some ambiguity over whether the call above can return. Rather than
+ // hitting confusing crashes later on we should crash right here.
+ IMMEDIATE_CRASH();
+}
+
+bool Process::IsValid() const {
+ return process_.IsValid() || is_current();
+}
+
+ProcessHandle Process::Handle() const {
+ return is_current_process_ ? GetCurrentProcess() : process_.Get();
+}
+
+Process Process::Duplicate() const {
+ if (is_current())
+ return Current();
+
+ ProcessHandle out_handle;
+ if (!IsValid() || !::DuplicateHandle(GetCurrentProcess(),
+ Handle(),
+ GetCurrentProcess(),
+ &out_handle,
+ 0,
+ FALSE,
+ DUPLICATE_SAME_ACCESS)) {
+ return Process();
+ }
+ return Process(out_handle);
+}
+
+ProcessId Process::Pid() const {
+ DCHECK(IsValid());
+ return GetProcId(Handle());
+}
+
+bool Process::is_current() const {
+ return is_current_process_;
+}
+
+void Process::Close() {
+ is_current_process_ = false;
+ if (!process_.IsValid())
+ return;
+
+ process_.Close();
+}
+
+bool Process::Terminate(int exit_code, bool wait) const {
+ constexpr DWORD kWaitMs = 60 * 1000;
+
+ // exit_code cannot be implemented.
+ DCHECK(IsValid());
+ bool result = (::TerminateProcess(Handle(), exit_code) != FALSE);
+ if (result) {
+ // The process may not end immediately due to pending I/O
+ if (wait && ::WaitForSingleObject(Handle(), kWaitMs) != WAIT_OBJECT_0)
+ DPLOG(ERROR) << "Error waiting for process exit";
+ Exited(exit_code);
+ } else {
+ // The process can't be terminated, perhaps because it has already
+ // exited or is in the process of exiting. A non-zero timeout is necessary
+ // here for the same reasons as above.
+ DPLOG(ERROR) << "Unable to terminate process";
+ if (::WaitForSingleObject(Handle(), kWaitMs) == WAIT_OBJECT_0) {
+ DWORD actual_exit;
+ Exited(::GetExitCodeProcess(Handle(), &actual_exit) ? actual_exit
+ : exit_code);
+ result = true;
+ }
+ }
+ return result;
+}
+
+bool Process::WaitForExit(int* exit_code) const {
+ return WaitForExitWithTimeout(TimeDelta::FromMilliseconds(INFINITE),
+ exit_code);
+}
+
+bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) const {
+ if (!timeout.is_zero())
+ internal::AssertBaseSyncPrimitivesAllowed();
+
+ // Record the event that this thread is blocking upon (for hang diagnosis).
+ base::debug::ScopedProcessWaitActivity process_activity(this);
+
+ // Limit timeout to INFINITE.
+ DWORD timeout_ms = saturated_cast<DWORD>(timeout.InMilliseconds());
+ if (::WaitForSingleObject(Handle(), timeout_ms) != WAIT_OBJECT_0)
+ return false;
+
+ DWORD temp_code; // Don't clobber out-parameters in case of failure.
+ if (!::GetExitCodeProcess(Handle(), &temp_code))
+ return false;
+
+ if (exit_code)
+ *exit_code = temp_code;
+
+ Exited(temp_code);
+ return true;
+}
+
+void Process::Exited(int exit_code) const {
+ base::debug::GlobalActivityTracker::RecordProcessExitIfEnabled(Pid(),
+ exit_code);
+}
+
+bool Process::IsProcessBackgrounded() const {
+ DCHECK(IsValid());
+ DWORD priority = GetPriority();
+ if (priority == 0)
+ return false; // Failure case.
+ return ((priority == BELOW_NORMAL_PRIORITY_CLASS) ||
+ (priority == IDLE_PRIORITY_CLASS));
+}
+
+bool Process::SetProcessBackgrounded(bool value) {
+ DCHECK(IsValid());
+ // Vista and above introduce a real background mode, which not only
+ // sets the priority class on the threads but also on the IO generated
+ // by it. Unfortunately it can only be set for the calling process.
+ DWORD priority;
+ if (is_current()) {
+ priority = value ? PROCESS_MODE_BACKGROUND_BEGIN :
+ PROCESS_MODE_BACKGROUND_END;
+ } else {
+ priority = value ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS;
+ }
+
+ return (::SetPriorityClass(Handle(), priority) != 0);
+}
+
+int Process::GetPriority() const {
+ DCHECK(IsValid());
+ return ::GetPriorityClass(Handle());
+}
+
+} // namespace base