diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/base/process |
Initial import.
Diffstat (limited to 'chromium/base/process')
52 files changed, 8760 insertions, 0 deletions
diff --git a/chromium/base/process/internal_linux.cc b/chromium/base/process/internal_linux.cc new file mode 100644 index 00000000000..ee1107c1792 --- /dev/null +++ b/chromium/base/process/internal_linux.cc @@ -0,0 +1,190 @@ +// 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 <unistd.h> + +#include <map> +#include <string> +#include <vector> + +#include "base/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" + +namespace base { +namespace internal { + +const char kProcDir[] = "/proc"; + +const char kStatFile[] = "stat"; + +base::FilePath GetProcPidDir(pid_t pid) { + return base::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 (!file_util::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), ' ', &other_stats); + 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) { + typedef std::pair<std::string, std::string> StringPair; + std::vector<StringPair> key_value_pairs; + SplitStringIntoKeyValuePairs(contents, ' ', '\n', &key_value_pairs); + for (size_t i = 0; i < key_value_pairs.size(); ++i) { + const StringPair& key_value_pair = key_value_pairs[i]; + output->insert(key_value_pair); + } +} + +int GetProcStatsFieldAsInt(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()); + + int value; + return StringToInt(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; +} + +int ReadProcStatsAndGetFieldAsInt(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 GetProcStatsFieldAsInt(proc_stats, 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 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/chromium/base/process/internal_linux.h b/chromium/base/process/internal_linux.h new file mode 100644 index 00000000000..a10cee36e56 --- /dev/null +++ b/chromium/base/process/internal_linux.h @@ -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. + +// This file contains internal routines that are called by other files in +// base/process/. + +#ifndef BASE_PROCESS_LINUX_INTERNAL_H_ +#define BASE_PROCESS_LINUX_INTERNAL_H_ + +#include "base/files/file_path.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_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. +int GetProcStatsFieldAsInt(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num); + +// Same as GetProcStatsFieldAsInt(), but for size_t values. +size_t GetProcStatsFieldAsSizeT(const std::vector<std::string>& proc_stats, + ProcStatsFields field_num); + +// Convenience wrapper around GetProcStatsFieldAsInt(), ParseProcStats() and +// ReadProcStats(). See GetProcStatsFieldAsInt() for details. +int ReadProcStatsAndGetFieldAsInt(pid_t pid, + ProcStatsFields field_num); + +// Same as ReadProcStatsAndGetFieldAsInt() 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(); + +// Converts Linux clock ticks to a wall time delta. +TimeDelta ClockTicksToTimeDelta(int clock_ticks); + +} // namespace internal +} // namespace base + +#endif // BASE_PROCESS_LINUX_INTERNAL_H_ diff --git a/chromium/base/process/kill.cc b/chromium/base/process/kill.cc new file mode 100644 index 00000000000..caca3484a11 --- /dev/null +++ b/chromium/base/process/kill.cc @@ -0,0 +1,26 @@ +// 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/process/process_iterator.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()) { +#if defined(OS_WIN) + result &= KillProcessById(entry->pid(), exit_code, true); +#else + result &= KillProcess(entry->pid(), exit_code, true); +#endif + } + return result; +} + +} // namespace base diff --git a/chromium/base/process/kill.h b/chromium/base/process/kill.h new file mode 100644 index 00000000000..f81ea9090da --- /dev/null +++ b/chromium/base/process/kill.h @@ -0,0 +1,144 @@ +// 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_handle.h" +#include "base/time/time.h" + +namespace base { + +class ProcessFilter; + +// 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 + 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); + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. If |wait| is true, wait +// for the process to be actually terminated before returning. +// Returns true if this is successful, false otherwise. +BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); + +#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) + +#if defined(OS_WIN) +BASE_EXPORT bool KillProcessById(ProcessId process_id, + int exit_code, + bool wait); +#endif // defined(OS_WIN) + +// 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. |exit_code| may be NULL if the +// caller is not interested in it. 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) +// Wait for the process to exit and get the termination status. See +// GetTerminationStatus for more information. On POSIX systems, we can't call +// WaitForExitCode and then GetTerminationStatus as the child will be reaped +// when WaitForExitCode return and this information will be lost. +BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // defined(OS_POSIX) + +// Waits for process to exit. On POSIX systems, if the process hasn't been +// signaled then puts the exit code in |exit_code|; otherwise it's considered +// a failure. On Windows |exit_code| is always filled. Returns true on success, +// and closes |handle| in any case. +BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); + +// Waits for process to exit. If it did exit within |timeout_milliseconds|, +// then puts the exit code in |exit_code|, and returns true. +// In POSIX systems, if the process has been signaled then |exit_code| is set +// to -1. Returns false on failure (the caller is then responsible for closing +// |handle|). +// The caller is always responsible for closing the |handle|. +BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout); + +// 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); + +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. On Linux |handle| must be a child process, however +// on Mac and Windows it can be any process. +BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, + base::TimeDelta wait); + +// 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); + +// This method ensures that the specified process eventually terminates, and +// then it closes the given process handle. +// +// It assumes that the process has already been signalled to exit, and it +// begins by waiting a small amount of time for it to exit. If the process +// does not appear to have exited, then this function starts to become +// aggressive about ensuring that the process terminates. +// +// On Linux this method does not block the calling thread. +// On OS X this method may block for up to 2 seconds. +// +// NOTE: The process handle must have been opened with the PROCESS_TERMINATE +// and SYNCHRONIZE permissions. +// +BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// The nicer version of EnsureProcessTerminated() that is patient and will +// wait for |process_handle| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); +#endif + +} // namespace base + +#endif // BASE_PROCESS_KILL_H_ diff --git a/chromium/base/process/kill_mac.cc b/chromium/base/process/kill_mac.cc new file mode 100644 index 00000000000..5ebca5d0076 --- /dev/null +++ b/chromium/base/process/kill_mac.cc @@ -0,0 +1,173 @@ +// 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 <signal.h> +#include <sys/event.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.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(child > 0); + DCHECK(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; + + int kq = HANDLE_EINTR(kqueue()); + if (kq == -1) { + DPLOG(ERROR) << "kqueue()"; + } else { + file_util::ScopedFD auto_close_kq(&kq); + + struct kevent change = {0}; + EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + result = HANDLE_EINTR(kevent(kq, &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, 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(ProcessHandle process) { + WaitForChildToDie(process, kWaitBeforeKillSeconds); +} + +} // namespace base diff --git a/chromium/base/process/kill_posix.cc b/chromium/base/process/kill_posix.cc new file mode 100644 index 00000000000..5938fa53233 --- /dev/null +++ b/chromium/base/process/kill_posix.cc @@ -0,0 +1,492 @@ +// 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 <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_iterator.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +int WaitpidWithTimeout(ProcessHandle handle, + int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| 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_milliseconds, 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. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. + int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. + int64 double_sleep_time = 0; + + // If the process hasn't exited yet, then sleep and try again. + TimeTicks wakeup_time = TimeTicks::Now() + + TimeDelta::FromMilliseconds(wait_milliseconds); + while (ret_pid == 0) { + TimeTicks now = TimeTicks::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 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; + } + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, + bool can_block, + int* 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 << ")"; + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (result == 0) { + // the child hasn't exited yet. + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (exit_code) + *exit_code = status; + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + return TERMINATION_STATUS_PROCESS_CRASHED; + case SIGINT: + case SIGKILL: + 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 + +// Attempts to kill the process identified by the given process +// entry structure. Ignores specified exit_code; posix can't force that. +// Returns true if this is successful, false otherwise. +bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { + DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; + if (process_id <= 1) + return false; + bool result = kill(process_id, SIGTERM) == 0; + if (result && wait) { + int tries = 60; + + if (RunningOnValgrind()) { + // Wait for some extra time when running under Valgrind since the child + // processes may take some time doing leak checking. + tries *= 2; + } + + unsigned sleep_ms = 4; + + // The process may not end immediately due to pending I/O + bool exited = false; + while (tries-- > 0) { + pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); + if (pid == process_id) { + exited = true; + break; + } + if (pid == -1) { + if (errno == ECHILD) { + // The wait may fail with ECHILD if another process also waited for + // the same pid, causing the process state to get cleaned up. + exited = true; + break; + } + DPLOG(ERROR) << "Error waiting for process " << process_id; + } + + usleep(sleep_ms * 1000); + const unsigned kMaxSleepMs = 1000; + if (sleep_ms < kMaxSleepMs) + sleep_ms *= 2; + } + + // If we're waiting and the child hasn't died by now, force it + // with a SIGKILL. + if (!exited) + result = kill(process_id, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_id; + + return result; +} + +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; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); +} + +TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code) { + return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + int status; + if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { + NOTREACHED(); + return false; + } + + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + + // If it didn't exit cleanly, it must have been signaled. + DCHECK(WIFSIGNALED(status)); + return false; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + bool waitpid_success = false; + int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), + &waitpid_success); + if (status == -1) + return false; + if (!waitpid_success) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::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 + + base::TimeTicks end_time = base::TimeTicks::Now() + wait; + do { + NamedProcessIterator iter(executable_name, filter); + if (!iter.NextProcessEntry()) { + result = true; + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } while ((end_time - base::TimeTicks::Now()) > base::TimeDelta()); + + return result; +} + +#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. +static bool WaitForSingleNonChildProcess(ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); + + int kq = kqueue(); + if (kq == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + file_util::ScopedFD kq_closer(&kq); + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq, &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.InMilliseconds() == base::kNoTimeout; + 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}; + + while (wait_forever || remaining_delta > base::TimeDelta()) { + 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, 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; + } + } + + 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 WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + ProcessHandle parent_pid = GetParentProcessId(handle); + ProcessHandle our_pid = Process::Current().handle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, wait); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + bool waitpid_success; + int status = -1; + if (wait.InMilliseconds() == base::kNoTimeout) { + waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); + } else { + status = WaitpidWithTimeout( + handle, wait.InMilliseconds(), &waitpid_success); + } + + if (status != -1) { + DCHECK(waitpid_success); + return WIFEXITED(status); + } else { + return false; + } +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::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 { + +// Return true if the given child is dead. This will also reap the process. +// Doesn't block. +static bool IsChildDead(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ")"; + NOTREACHED(); + } else if (result > 0) { + // The child has died. + return true; + } + + return false; +} + +// A thread class which waits for the given child to exit and reaps it. +// If the child doesn't exit within a couple of seconds, kill it. +class BackgroundReaper : public PlatformThread::Delegate { + public: + BackgroundReaper(pid_t child, unsigned timeout) + : child_(child), + timeout_(timeout) { + } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE { + WaitForChildToDie(); + delete this; + } + + void WaitForChildToDie() { + // Wait forever case. + if (timeout_ == 0) { + pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); + if (r != child_) { + DPLOG(ERROR) << "While waiting for " << child_ + << " to terminate, we got the following result: " << r; + } + return; + } + + // There's no good way to wait for a specific child to exit in a timed + // fashion. (No kqueue on Linux), so we just loop and sleep. + + // Wait for 2 * timeout_ 500 milliseconds intervals. + for (unsigned i = 0; i < 2 * timeout_; ++i) { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + if (IsChildDead(child_)) + return; + } + + if (kill(child_, SIGKILL) == 0) { + // SIGKILL is uncatchable. Since the signal was delivered, we can + // just wait for the process to die now in a blocking manner. + if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) + DPLOG(WARNING) << "waitpid"; + } else { + DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" + << " failed to deliver a SIGKILL signal (" << errno << ")."; + } + } + + private: + const pid_t child_; + // Number of seconds to wait, if 0 then wait forever and do not attempt to + // kill |child_|. + const unsigned timeout_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); +}; + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + const unsigned timeout = 2; // seconds + BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + PlatformThread::CreateNonJoinable(0, reaper); +} + +void EnsureProcessGetsReaped(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + BackgroundReaper* reaper = new BackgroundReaper(process, 0); + PlatformThread::CreateNonJoinable(0, reaper); +} + +#endif // !defined(OS_MACOSX) + +} // namespace base diff --git a/chromium/base/process/kill_win.cc b/chromium/base/process/kill_win.cc new file mode 100644 index 00000000000..99a7c661851 --- /dev/null +++ b/chromium/base/process/kill_win.cc @@ -0,0 +1,255 @@ +// 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 <io.h> +#include <windows.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process_iterator.h" +#include "base/win/object_watcher.h" + +namespace base { + +namespace { + +// 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; + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +class TimerExpiredTask : public win::ObjectWatcher::Delegate { + public: + explicit TimerExpiredTask(ProcessHandle process); + ~TimerExpiredTask(); + + void TimedOut(); + + // MessageLoop::Watcher ----------------------------------------------------- + virtual void OnObjectSignaled(HANDLE object); + + private: + void KillProcess(); + + // The process that we are watching. + ProcessHandle process_; + + win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); +}; + +TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { + watcher_.StartWatching(process_, this); +} + +TimerExpiredTask::~TimerExpiredTask() { + TimedOut(); + DCHECK(!process_) << "Make sure to close the handle."; +} + +void TimerExpiredTask::TimedOut() { + if (process_) + KillProcess(); +} + +void TimerExpiredTask::OnObjectSignaled(HANDLE object) { + CloseHandle(process_); + process_ = NULL; +} + +void TimerExpiredTask::KillProcess() { + // Stop watching the process handle since we're killing it. + watcher_.StopWatching(); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + base::KillProcess(process_, kProcessKilledExitCode, false); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); +} + +} // namespace + +bool KillProcess(ProcessHandle process, int exit_code, bool wait) { + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG_GETLASTERROR(ERROR) << "Error waiting for process exit"; + } else if (!result) { + DLOG_GETLASTERROR(ERROR) << "Unable to terminate process"; + } + return result; +} + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. +// Returns true if this is successful, false otherwise. +bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (!process) { + DLOG_GETLASTERROR(ERROR) << "Unable to open process " << process_id; + return false; + } + bool ret = KillProcess(process, exit_code, wait); + CloseHandle(process); + return ret; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + DWORD tmp_exit_code = 0; + + if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { + DLOG_GETLASTERROR(FATAL) << "GetExitCodeProcess() failed"; + if (exit_code) { + // 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 = 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) { + if (exit_code) + *exit_code = wait_result; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (wait_result == WAIT_FAILED) { + DLOG_GETLASTERROR(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; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + switch (tmp_exit_code) { + case kNormalTerminationExitCode: + return TERMINATION_STATUS_NORMAL_TERMINATION; + case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. + case kKeyboardInterruptExitCode: // Control-C/end session. + case kDebuggerTerminatedExitCode: // Debugger terminated process. + case kProcessKilledExitCode: // Task manager kill. + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + // All other exit codes indicate crashes. + return TERMINATION_STATUS_PROCESS_CRASHED; + } +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + bool success = WaitForExitCodeWithTimeout( + handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); + CloseProcessHandle(handle); + return success; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) + return false; + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(handle, &temp_code)) + return false; + + *exit_code = temp_code; + return true; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + const ProcessEntry* entry; + bool result = true; + DWORD start_time = GetTickCount(); + + NamedProcessIterator iter(executable_name, filter); + while ((entry = iter.NextProcessEntry())) { + DWORD remaining_wait = std::max<int64>( + 0, wait.InMilliseconds() - (GetTickCount() - start_time)); + HANDLE process = OpenProcess(SYNCHRONIZE, + FALSE, + entry->th32ProcessID); + DWORD wait_result = WaitForSingleObject(process, remaining_wait); + CloseHandle(process); + result = result && (wait_result == WAIT_OBJECT_0); + } + + return result; +} + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + int exit_code; + if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) + return false; + return exit_code == 0; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::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; +} + +void EnsureProcessTerminated(ProcessHandle process) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + CloseHandle(process); + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimerExpiredTask::TimedOut, + base::Owned(new TimerExpiredTask(process))), + base::TimeDelta::FromMilliseconds(kWaitInterval)); +} + +} // namespace base diff --git a/chromium/base/process/launch.h b/chromium/base/process/launch.h new file mode 100644 index 00000000000..45b10537b84 --- /dev/null +++ b/chromium/base/process/launch.h @@ -0,0 +1,258 @@ +// 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 functions for launching subprocesses. + +#ifndef BASE_PROCESS_LAUNCH_H_ +#define BASE_PROCESS_LAUNCH_H_ + +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" + +#if defined(OS_POSIX) +#include "base/posix/file_descriptor_shuffle.h" +#elif defined(OS_WIN) +#include <windows.h> +#endif + +class CommandLine; + +namespace base { + +typedef std::vector<std::pair<std::string, std::string> > EnvironmentVector; +typedef std::vector<std::pair<int, int> > FileHandleMappingVector; + +// Options for launching a subprocess that are passed to LaunchProcess(). +// The default constructor constructs the object with default options. +struct LaunchOptions { + LaunchOptions() + : wait(false), +#if defined(OS_WIN) + start_hidden(false), + inherit_handles(false), + as_user(NULL), + empty_desktop_name(false), + job_handle(NULL), + stdin_handle(NULL), + stdout_handle(NULL), + stderr_handle(NULL), + force_breakaway_from_job_(false) +#else + environ(NULL), + fds_to_remap(NULL), + maximize_rlimits(NULL), + new_process_group(false) +#if defined(OS_LINUX) + , clone_flags(0) +#endif // OS_LINUX +#if defined(OS_CHROMEOS) + , ctrl_terminal_fd(-1) +#endif // OS_CHROMEOS +#endif // !defined(OS_WIN) + {} + + // If true, wait for the process to complete. + bool wait; + +#if defined(OS_WIN) + bool start_hidden; + + // If true, the new process inherits handles from the parent. 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. + bool inherit_handles; + + // 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; + + // If true, use an empty string for the desktop name. + bool empty_desktop_name; + + // 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; + + // Handles for the redirection of stdin, stdout and stderr. The handles must + // be inheritable. Caller should either set all three of them or none (i.e. + // there is no way to redirect stderr without redirecting stdin). The + // |inherit_handles| flag must be set to true when redirecting stdio stream. + HANDLE stdin_handle; + HANDLE stdout_handle; + HANDLE stderr_handle; + + // 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_; +#else + // If non-NULL, set/unset environment variables. + // See documentation of AlterEnvironment(). + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const EnvironmentVector* environ; + + // If non-NULL, remap file descriptors according to the mapping of + // src fd->dest fd to propagate FDs into the child process. + // This pointer is owned by the caller and must live through the + // call to LaunchProcess(). + const FileHandleMappingVector* fds_to_remap; + + // 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::set<int>* maximize_rlimits; + + // 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; + +#if defined(OS_LINUX) + // If non-zero, start the process using clone(), using flags as provided. + int clone_flags; +#endif // defined(OS_LINUX) + +#if defined(OS_CHROMEOS) + // If non-negative, the specified file descriptor will be set as the launched + // process' controlling terminal. + int ctrl_terminal_fd; +#endif // defined(OS_CHROMEOS) + +#endif // !defined(OS_WIN) +}; + +// Launch a process via the command line |cmdline|. +// See the documentation of LaunchOptions for details on |options|. +// +// Returns true upon success. +// +// Upon success, if |process_handle| is non-NULL, it will be filled in with the +// handle of the launched process. NOTE: In this case, the caller is +// responsible for closing the handle so that it doesn't leak! +// Otherwise, the process handle will be implicitly closed. +// +// 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 bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#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 bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle); + +#elif defined(OS_POSIX) +// 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 bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle); + +// AlterEnvironment returns a modified environment vector, constructed from the +// given environment and the list of changes given in |changes|. Each key in +// the environment is matched against the first element of the pairs. In the +// event of a match, the value is replaced by the second of the pair, unless +// the second is empty, in which case the key-value is removed. +// +// The returned array is allocated using new[] and must be freed by the caller. +BASE_EXPORT char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env); + +// 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_POSIX) + +#if defined(OS_WIN) +// Set JOBOBJECT_EXTENDED_LIMIT_INFORMATION to JobObject |job_object|. +// As its limit_info.BasicLimitInformation.LimitFlags has +// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. +// When the provide JobObject |job_object| is closed, the binded process will +// be terminated. +BASE_EXPORT bool SetJobObjectAsKillOnJobClose(HANDLE job_object); + +// 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(); +#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); + +#if defined(OS_POSIX) +// 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); + +// A restricted version of |GetAppOutput()| which (a) clears the environment, +// and (b) stores at most |max_output| bytes; also, it doesn't search the path +// for the command. +BASE_EXPORT bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_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); +#endif // defined(OS_POSIX) + +// 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) +// 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) + +} // namespace base + +#endif // BASE_PROCESS_LAUNCH_H_ diff --git a/chromium/base/process/launch_ios.cc b/chromium/base/process/launch_ios.cc new file mode 100644 index 00000000000..3c700f8f0a3 --- /dev/null +++ b/chromium/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/chromium/base/process/launch_mac.cc b/chromium/base/process/launch_mac.cc new file mode 100644 index 00000000000..176edca72ea --- /dev/null +++ b/chromium/base/process/launch_mac.cc @@ -0,0 +1,28 @@ +// 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 <mach/mach.h> + +namespace base { + +void RestoreDefaultExceptionHandler() { + // This function is tailored to remove the Breakpad exception handler. + // exception_mask matches s_exception_mask in + // 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); +} + +} // namespace base diff --git a/chromium/base/process/launch_posix.cc b/chromium/base/process/launch_posix.cc new file mode 100644 index 00000000000..52e149cd8df --- /dev/null +++ b/chromium/base/process/launch_posix.cc @@ -0,0 +1,760 @@ +// 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 <signal.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <iterator> +#include <limits> +#include <set> + +#include "base/allocator/type_profiler_control.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/debug/debugger.h" +#include "base/debug/stack_trace.h" +#include "base/file_util.h" +#include "base/files/dir_reader_posix.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/kill.h" +#include "base/process/process_metrics.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread_restrictions.h" + +#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> +#else +extern char** environ; +#endif + +namespace base { + +namespace { + +// 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(__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 = {0}; + int sigaction_get_ret = sys_rt_sigaction(signum, NULL, &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 = NULL; + if (sys_rt_sigaction(signum, &act, NULL)) { + 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, NULL, &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 + +// A class to handle auto-closing of DIR*'s. +class ScopedDIRClose { + public: + inline void operator()(DIR* x) const { + if (x) { + closedir(x); + } + } +}; +typedef scoped_ptr_malloc<DIR, ScopedDIRClose> ScopedDIR; + +#if defined(OS_LINUX) +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 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; + InjectiveMultimap::const_iterator j; + for (j = saved_mapping.begin(); j != saved_mapping.end(); j++) { + if (fd == j->dest) + break; + } + if (j != saved_mapping.end()) + continue; + + // Since we're just trying to close anything we can find, + // ignore any error return values of close(). + ignore_result(HANDLE_EINTR(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; + InjectiveMultimap::const_iterator i; + for (i = saved_mapping.begin(); i != saved_mapping.end(); i++) { + if (fd == i->dest) + break; + } + if (i != saved_mapping.end()) + continue; + if (fd == dir_fd) + continue; + + // When running under Valgrind, Valgrind opens several FDs for its + // own use and will complain if we try to close them. All of + // these FDs are >= |max_fds|, so we can check against that here + // before closing. See https://bugs.kde.org/show_bug.cgi?id=191758 + if (fd < static_cast<int>(max_fds)) { + int ret = HANDLE_EINTR(close(fd)); + DPCHECK(ret == 0); + } + } +} + +char** AlterEnvironment(const EnvironmentVector& changes, + const char* const* const env) { + unsigned count = 0; + unsigned size = 0; + + // First assume that all of the current environment will be included. + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + count++; + size += strlen(pair) + 1 /* terminating NUL */; + } + + for (EnvironmentVector::const_iterator j = changes.begin(); + j != changes.end(); + ++j) { + bool found = false; + const char *pair; + + for (unsigned i = 0; env[i]; i++) { + pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + // if found, we'll either be deleting or replacing this element. + if (found) { + count--; + size -= strlen(pair) + 1; + if (j->second.size()) + found = false; + } + + // if !found, then we have a new element to add. + if (!found && !j->second.empty()) { + count++; + size += j->first.size() + 1 /* '=' */ + j->second.size() + 1 /* NUL */; + } + } + + count++; // for the final NULL + uint8_t *buffer = new uint8_t[sizeof(char*) * count + size]; + char **const ret = reinterpret_cast<char**>(buffer); + unsigned k = 0; + char *scratch = reinterpret_cast<char*>(buffer + sizeof(char*) * count); + + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + continue; + } + const unsigned keylen = equals - pair; + bool handled = false; + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->first.size() == keylen && + memcmp(j->first.data(), pair, keylen) == 0) { + if (!j->second.empty()) { + ret[k++] = scratch; + memcpy(scratch, pair, keylen + 1); + scratch += keylen + 1; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + handled = true; + break; + } + } + + if (!handled) { + const unsigned len = strlen(pair); + ret[k++] = scratch; + memcpy(scratch, pair, len + 1); + scratch += len + 1; + } + } + + // Now handle new elements + for (EnvironmentVector::const_iterator + j = changes.begin(); j != changes.end(); j++) { + if (j->second.empty()) + continue; + + bool found = false; + for (unsigned i = 0; env[i]; i++) { + const char *const pair = env[i]; + const char *const equals = strchr(pair, '='); + if (!equals) + continue; + const unsigned keylen = equals - pair; + if (keylen == j->first.size() && + memcmp(pair, j->first.data(), keylen) == 0) { + found = true; + break; + } + } + + if (!found) { + ret[k++] = scratch; + memcpy(scratch, j->first.data(), j->first.size()); + scratch += j->first.size(); + *scratch++ = '='; + memcpy(scratch, j->second.c_str(), j->second.size() + 1); + scratch += j->second.size() + 1; + } + } + + ret[k] = NULL; + return ret; +} + +bool LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options, + ProcessHandle* process_handle) { + size_t fd_shuffle_size = 0; + if (options.fds_to_remap) { + fd_shuffle_size = options.fds_to_remap->size(); + } + + InjectiveMultimap fd_shuffle1; + InjectiveMultimap fd_shuffle2; + fd_shuffle1.reserve(fd_shuffle_size); + fd_shuffle2.reserve(fd_shuffle_size); + + scoped_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]); + scoped_ptr<char*[]> new_environ; + if (options.environ) + new_environ.reset(AlterEnvironment(*options.environ, GetEnvironment())); + + sigset_t full_sigset; + sigfillset(&full_sigset); + const sigset_t orig_sigmask = SetSignalMask(full_sigset); + + pid_t pid; +#if defined(OS_LINUX) + 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))); + pid = syscall(__NR_clone, options.clone_flags, 0, 0, 0); + } else +#endif + { + pid = fork(); + } + + // Always restore the original signal mask in the parent. + if (pid != 0) { + SetSignalMask(orig_sigmask); + } + + if (pid < 0) { + DPLOG(ERROR) << "fork"; + return false; + } else if (pid == 0) { + // Child process + + // 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. + int null_fd = HANDLE_EINTR(open("/dev/null", O_RDONLY)); + if (null_fd < 0) { + RAW_LOG(ERROR, "Failed to open /dev/null"); + _exit(127); + } + + file_util::ScopedFD null_fd_closer(&null_fd); + int new_fd = HANDLE_EINTR(dup2(null_fd, 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); + } + } + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + if (options.maximize_rlimits) { + // Some resource limits need to be maximal in this child. + std::set<int>::const_iterator resource; + for (resource = options.maximize_rlimits->begin(); + resource != options.maximize_rlimits->end(); + ++resource) { + 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); + memset(reinterpret_cast<void*>(malloc), 0xff, 8); +#endif // 0 + + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + +#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, NULL)) == -1) { + RAW_LOG(WARNING, "ioctl(TIOCSCTTY), ctrl terminal not set"); + } + } else { + RAW_LOG(WARNING, "setsid failed, ctrl terminal not set"); + } + } +#endif // defined(OS_CHROMEOS) + + if (options.fds_to_remap) { + for (FileHandleMappingVector::const_iterator + it = options.fds_to_remap->begin(); + it != options.fds_to_remap->end(); ++it) { + fd_shuffle1.push_back(InjectionArc(it->first, it->second, false)); + fd_shuffle2.push_back(InjectionArc(it->first, it->second, false)); + } + } + + if (options.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); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + execvp(argv_cstr[0], argv_cstr.get()); + + 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. + base::ThreadRestrictions::AssertIOAllowed(); + pid_t ret = HANDLE_EINTR(waitpid(pid, 0, 0)); + DPCHECK(ret > 0); + } + + if (process_handle) + *process_handle = pid; + } + + return true; +} + + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.argv(), options, process_handle); +} + +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. +} + +// Return value used by GetAppOutputInternal to encapsulate the various exit +// scenarios from the function. +enum GetAppOutputInternalResult { + EXECUTE_FAILURE, + EXECUTE_SUCCESS, + GOT_MAX_OUTPUT, +}; + +// 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. Redirects stderr to /dev/null. +// If we successfully start the application and get all requested output, we +// return GOT_MAX_OUTPUT, or if there is a problem starting or exiting +// the application we return RUN_FAILURE. Otherwise we return EXECUTE_SUCCESS. +// The GOT_MAX_OUTPUT return value exists so a caller that asks for limited +// output can treat this as a success, despite having an exit code of SIG_PIPE +// due to us closing the output pipe. +// In the case of EXECUTE_SUCCESS, the application exit code will be returned +// in |*exit_code|, which should be checked to determine if the application +// ran successfully. +static GetAppOutputInternalResult GetAppOutputInternal( + const std::vector<std::string>& argv, + char* const envp[], + std::string* output, + size_t max_output, + bool do_search_path, + int* exit_code) { + // Doing a blocking wait for another command to finish counts as IO. + base::ThreadRestrictions::AssertIOAllowed(); + // exit_code must be supplied so calling function can determine success. + DCHECK(exit_code); + *exit_code = EXIT_FAILURE; + + int pipe_fd[2]; + pid_t pid; + InjectiveMultimap fd_shuffle1, fd_shuffle2; + scoped_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]); + + 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); + + if (pipe(pipe_fd) < 0) + return EXECUTE_FAILURE; + + switch (pid = fork()) { + case -1: // error + close(pipe_fd[0]); + close(pipe_fd[1]); + return EXECUTE_FAILURE; + case 0: // child + { +#if defined(OS_MACOSX) + RestoreDefaultExceptionHandler(); +#endif + // DANGER: no calls to malloc are allowed from now on: + // http://crbug.com/36678 + + // 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); + + // Stop type-profiler. + // The profiler should be stopped between fork and exec since it inserts + // locks at new/delete expressions. See http://crbug.com/36678. + base::type_profiler::Controller::Stop(); + + fd_shuffle1.push_back(InjectionArc(pipe_fd[1], STDOUT_FILENO, true)); + fd_shuffle1.push_back(InjectionArc(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. + + std::copy(fd_shuffle1.begin(), fd_shuffle1.end(), + std::back_inserter(fd_shuffle2)); + + if (!ShuffleFileDescriptors(&fd_shuffle1)) + _exit(127); + + CloseSuperfluousFds(fd_shuffle2); + + for (size_t i = 0; i < argv.size(); i++) + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + argv_cstr[argv.size()] = NULL; + if (do_search_path) + execvp(argv_cstr[0], argv_cstr.get()); + else + execve(argv_cstr[0], argv_cstr.get(), 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(); + char buffer[256]; + size_t output_buf_left = max_output; + ssize_t bytes_read = 1; // A lie to properly handle |max_output == 0| + // case in the logic below. + + while (output_buf_left > 0) { + bytes_read = HANDLE_EINTR(read(pipe_fd[0], buffer, + std::min(output_buf_left, sizeof(buffer)))); + if (bytes_read <= 0) + break; + output->append(buffer, bytes_read); + output_buf_left -= static_cast<size_t>(bytes_read); + } + close(pipe_fd[0]); + + // Always wait for exit code (even if we know we'll declare + // GOT_MAX_OUTPUT). + bool success = WaitForExitCode(pid, exit_code); + + // If we stopped because we read as much as we wanted, we return + // GOT_MAX_OUTPUT (because the child may exit due to |SIGPIPE|). + if (!output_buf_left && bytes_read > 0) + return GOT_MAX_OUTPUT; + else if (success) + return EXECUTE_SUCCESS; + return EXECUTE_FAILURE; + } + } +} + +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 and store "unlimited" data. + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + argv, NULL, output, std::numeric_limits<std::size_t>::max(), true, + &exit_code); + return result == EXECUTE_SUCCESS && exit_code == EXIT_SUCCESS; +} + +// TODO(viettrungluu): Conceivably, we should have a timeout as well, so we +// don't hang if what we're calling hangs. +bool GetAppOutputRestricted(const CommandLine& cl, + std::string* output, size_t max_output) { + // Run |execve()| with the empty environment. + char* const empty_environ = NULL; + int exit_code; + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), &empty_environ, output, max_output, false, &exit_code); + return result == GOT_MAX_OUTPUT || (result == EXECUTE_SUCCESS && + exit_code == EXIT_SUCCESS); +} + +bool GetAppOutputWithExitCode(const CommandLine& cl, + std::string* output, + int* exit_code) { + // Run |execve()| with the current environment and store "unlimited" data. + GetAppOutputInternalResult result = GetAppOutputInternal( + cl.argv(), NULL, output, std::numeric_limits<std::size_t>::max(), true, + exit_code); + return result == EXECUTE_SUCCESS; +} + +} // namespace base diff --git a/chromium/base/process/launch_win.cc b/chromium/base/process/launch_win.cc new file mode 100644 index 00000000000..c5126c56fb8 --- /dev/null +++ b/chromium/base/process/launch_win.cc @@ -0,0 +1,283 @@ +// 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 <windows.h> +#include <userenv.h> +#include <psapi.h> + +#include <ios> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/debug/stack_trace.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/process/kill.h" +#include "base/sys_info.h" +#include "base/win/object_watcher.h" +#include "base/win/scoped_handle.h" +#include "base/win/scoped_process_information.h" +#include "base/win/windows_version.h" + +// userenv.dll is required for CreateEnvironmentBlock(). +#pragma comment(lib, "userenv.lib") + +namespace base { + +namespace { + +// 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 + +void RouteStdioToConsole() { + // 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) + 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; + // 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(); + } + + // 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, NULL, _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, NULL, _IOLBF, kOutputBufferSize); + _dup2(_fileno(stderr), 2); + } + + // Fix all cout, wcout, cin, wcin, cerr, wcerr, clog and wclog. + std::ios::sync_with_stdio(); +} + +bool LaunchProcess(const string16& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + if (options.empty_desktop_name) + startup_info.lpDesktop = L""; + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = options.start_hidden ? SW_HIDE : SW_SHOW; + + if (options.stdin_handle || options.stdout_handle || options.stderr_handle) { + DCHECK(options.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; + } + + DWORD flags = 0; + + if (options.job_handle) { + flags |= CREATE_SUSPENDED; + + // 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. + flags |= CREATE_BREAKAWAY_FROM_JOB; + } + + if (options.force_breakaway_from_job_) + flags |= CREATE_BREAKAWAY_FROM_JOB; + + base::win::ScopedProcessInformation process_info; + + if (options.as_user) { + flags |= CREATE_UNICODE_ENVIRONMENT; + void* enviroment_block = NULL; + + if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) { + DPLOG(ERROR); + return false; + } + + BOOL launched = + CreateProcessAsUser(options.as_user, NULL, + const_cast<wchar_t*>(cmdline.c_str()), + NULL, NULL, options.inherit_handles, flags, + enviroment_block, NULL, &startup_info, + process_info.Receive()); + DestroyEnvironmentBlock(enviroment_block); + if (!launched) { + DPLOG(ERROR); + return false; + } + } else { + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + options.inherit_handles, flags, NULL, NULL, + &startup_info, process_info.Receive())) { + DPLOG(ERROR); + return false; + } + } + + if (options.job_handle) { + if (0 == AssignProcessToJobObject(options.job_handle, + process_info.process_handle())) { + DLOG(ERROR) << "Could not AssignProcessToObject."; + KillProcess(process_info.process_handle(), kProcessKilledExitCode, true); + return false; + } + + ResumeThread(process_info.thread_handle()); + } + + if (options.wait) + WaitForSingleObject(process_info.process_handle(), INFINITE); + + // If the caller wants the process handle, we won't close it. + if (process_handle) + *process_handle = process_info.TakeProcessHandle(); + + return true; +} + +bool LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options, + ProcessHandle* process_handle) { + return LaunchProcess(cmdline.GetCommandLineString(), options, process_handle); +} + +bool SetJobObjectAsKillOnJobClose(HANDLE job_object) { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + return 0 != SetInformationJobObject( + job_object, + JobObjectExtendedLimitInformation, + &limit_info, + sizeof(limit_info)); +} + +bool GetAppOutput(const CommandLine& cl, std::string* output) { + HANDLE out_read = NULL; + HANDLE out_write = NULL; + + 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 = NULL; + + // 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 handle to the pipe for STDOUT is not inherited. + if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { + NOTREACHED() << "Failed to disabled pipe inheritance"; + return false; + } + + FilePath::StringType writable_command_line_string(cl.GetCommandLineString()); + + base::win::ScopedProcessInformation proc_info; + STARTUPINFO start_info = { 0 }; + + start_info.cb = sizeof(STARTUPINFO); + start_info.hStdOutput = out_write; + // Keep the normal stdin and stderr. + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + start_info.dwFlags |= STARTF_USESTDHANDLES; + + // Create the child process. + if (!CreateProcess(NULL, + &writable_command_line_string[0], + NULL, NULL, + TRUE, // Handles are inherited. + 0, NULL, NULL, &start_info, proc_info.Receive())) { + NOTREACHED() << "Failed to start process"; + return false; + } + + // 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, NULL); + 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); + + return true; +} + +void RaiseProcessToHighPriority() { + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +} + +} // namespace base diff --git a/chromium/base/process/memory.h b/chromium/base/process/memory.h new file mode 100644 index 00000000000..de79477e95d --- /dev/null +++ b/chromium/base/process/memory.h @@ -0,0 +1,69 @@ +// 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/basictypes.h" +#include "base/process/process_handle.h" +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace base { + +// Enables low fragmentation heap (LFH) for every heaps of this process. This +// won't have any effect on heaps created after this function call. It will not +// modify data allocated in the heaps before calling this function. So it is +// better to call this function early in initialization and again before +// entering the main loop. +// Note: Returns true on Windows 2000 without doing anything. +BASE_EXPORT bool EnableLowFragmentationHeap(); + +// 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(); + +#if defined(OS_WIN) +// Returns the module handle to which an address belongs. The reference count +// of the module is not incremented. +BASE_EXPORT HMODULE GetModuleFromAddress(void* address); +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) +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_MACOSX) +// Very large images or svg canvases can cause huge mallocs. Skia +// does tricks on tcmalloc-based systems to allow malloc to fail with +// a NULL rather than hit the oom crasher. This replicates that for +// OSX. +// +// IF YOU USE THIS WITHOUT CONSULTING YOUR FRIENDLY OSX DEVELOPER, +// YOUR CODE IS LIKELY TO BE REVERTED. THANK YOU. +BASE_EXPORT void* UncheckedMalloc(size_t size); +#endif // defined(OS_MACOSX) + +} // namespace base + +#endif // BASE_PROCESS_MEMORY_H_ diff --git a/chromium/base/process/memory_linux.cc b/chromium/base/process/memory_linux.cc new file mode 100644 index 00000000000..f81429b2ac0 --- /dev/null +++ b/chromium/base/process/memory_linux.cc @@ -0,0 +1,183 @@ +// 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/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_number_conversions.h" + +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 + +#if !defined(ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ + !defined(THREAD_SANITIZER) && !defined(LEAK_SANITIZER) + +#if defined(LIBC_GLIBC) && !defined(USE_TCMALLOC) + +extern "C" { +void* __libc_malloc(size_t size); +void* __libc_realloc(void* ptr, size_t size); +void* __libc_calloc(size_t nmemb, size_t size); +void* __libc_valloc(size_t size); +void* __libc_pvalloc(size_t size); +void* __libc_memalign(size_t alignment, size_t size); + +// Overriding the system memory allocation functions: +// +// For security reasons, we want malloc failures to be fatal. Too much code +// doesn't check for a NULL return value from malloc and unconditionally uses +// the resulting pointer. If the first offset that they try to access is +// attacker controlled, then the attacker can direct the code to access any +// part of memory. +// +// Thus, we define all the standard malloc functions here and mark them as +// visibility 'default'. This means that they replace the malloc functions for +// all Chromium code and also for all code in shared libraries. There are tests +// for this in process_util_unittest.cc. +// +// If we are using tcmalloc, then the problem is moot since tcmalloc handles +// this for us. Thus this code is in a !defined(USE_TCMALLOC) block. +// +// If we are testing the binary with AddressSanitizer, we should not +// redefine malloc and let AddressSanitizer do it instead. +// +// We call the real libc functions in this code by using __libc_malloc etc. +// Previously we tried using dlsym(RTLD_NEXT, ...) but that failed depending on +// the link order. Since ld.so needs calloc during symbol resolution, it +// defines its own versions of several of these functions in dl-minimal.c. +// Depending on the runtime library order, dlsym ended up giving us those +// functions and bad things happened. See crbug.com/31809 +// +// This means that any code which calls __libc_* gets the raw libc versions of +// these functions. + +#define DIE_ON_OOM_1(function_name) \ + void* function_name(size_t) __attribute__ ((visibility("default"))); \ + \ + void* function_name(size_t size) { \ + void* ret = __libc_##function_name(size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +#define DIE_ON_OOM_2(function_name, arg1_type) \ + void* function_name(arg1_type, size_t) \ + __attribute__ ((visibility("default"))); \ + \ + void* function_name(arg1_type arg1, size_t size) { \ + void* ret = __libc_##function_name(arg1, size); \ + if (ret == NULL && size != 0) \ + OnNoMemorySize(size); \ + return ret; \ + } + +DIE_ON_OOM_1(malloc) +DIE_ON_OOM_1(valloc) +DIE_ON_OOM_1(pvalloc) + +DIE_ON_OOM_2(calloc, size_t) +DIE_ON_OOM_2(realloc, void*) +DIE_ON_OOM_2(memalign, size_t) + +// posix_memalign has a unique signature and doesn't have a __libc_ variant. +int posix_memalign(void** ptr, size_t alignment, size_t size) + __attribute__ ((visibility("default"))); + +int posix_memalign(void** ptr, size_t alignment, size_t size) { + // This will use the safe version of memalign, above. + *ptr = memalign(alignment, size); + return 0; +} + +} // extern C + +#else + +// TODO(mostynb@opera.com): dlsym dance + +#endif // LIBC_GLIBC && !USE_TCMALLOC + +#endif // !*_SANITIZER + +void EnableTerminationOnHeapCorruption() { + // On Linux, there nothing to do AFAIK. +} + +void EnableTerminationOnOutOfMemory() { +#if defined(OS_ANDROID) + // Android doesn't support setting a new handler. + DLOG(WARNING) << "Not feasible."; +#else + // 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. +#endif +} + +// 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) { + if (score < 0 || score > kMaxOomScore) + return false; + + FilePath oom_path(internal::GetProcPidDir(process)); + + // 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 == file_util::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 == file_util::WriteFile(oom_file, + score_str.c_str(), + score_len)); + } + + return false; +} + +} // namespace base diff --git a/chromium/base/process/memory_mac.mm b/chromium/base/process/memory_mac.mm new file mode 100644 index 00000000000..dd30e704c8f --- /dev/null +++ b/chromium/base/process/memory_mac.mm @@ -0,0 +1,704 @@ +// 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 <CoreFoundation/CoreFoundation.h> +#include <errno.h> +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <malloc/malloc.h> +#import <objc/runtime.h> + +#include <new> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/scoped_clear_errno.h" +#include "third_party/apple_apsl/CFBase.h" +#include "third_party/apple_apsl/malloc.h" + +#if ARCH_CPU_32_BITS +#include <dlfcn.h> +#include <mach-o/nlist.h> + +#include "base/threading/thread_local.h" +#include "third_party/mach_override/mach_override.h" +#endif // ARCH_CPU_32_BITS + +namespace base { + +// These are helpers for EnableTerminationOnHeapCorruption, which is a no-op +// on 64 bit Macs. +#if ARCH_CPU_32_BITS +namespace { + +// Finds the library path for malloc() and thus the libC part of libSystem, +// which in Lion is in a separate image. +const char* LookUpLibCPath() { + const void* addr = reinterpret_cast<void*>(&malloc); + + Dl_info info; + if (dladdr(addr, &info)) + return info.dli_fname; + + DLOG(WARNING) << "Could not find image path for malloc()"; + return NULL; +} + +typedef void(*malloc_error_break_t)(void); +malloc_error_break_t g_original_malloc_error_break = NULL; + +// Returns the function pointer for malloc_error_break. This symbol is declared +// as __private_extern__ and cannot be dlsym()ed. Instead, use nlist() to +// get it. +malloc_error_break_t LookUpMallocErrorBreak() { + const char* lib_c_path = LookUpLibCPath(); + if (!lib_c_path) + return NULL; + + // Only need to look up two symbols, but nlist() requires a NULL-terminated + // array and takes no count. + struct nlist nl[3]; + bzero(&nl, sizeof(nl)); + + // The symbol to find. + nl[0].n_un.n_name = const_cast<char*>("_malloc_error_break"); + + // A reference symbol by which the address of the desired symbol will be + // calculated. + nl[1].n_un.n_name = const_cast<char*>("_malloc"); + + int rv = nlist(lib_c_path, nl); + if (rv != 0 || nl[0].n_type == N_UNDF || nl[1].n_type == N_UNDF) { + return NULL; + } + + // nlist() returns addresses as offsets in the image, not the instruction + // pointer in memory. Use the known in-memory address of malloc() + // to compute the offset for malloc_error_break(). + uintptr_t reference_addr = reinterpret_cast<uintptr_t>(&malloc); + reference_addr -= nl[1].n_value; + reference_addr += nl[0].n_value; + + return reinterpret_cast<malloc_error_break_t>(reference_addr); +} + +// Combines ThreadLocalBoolean with AutoReset. It would be convenient +// to compose ThreadLocalPointer<bool> with base::AutoReset<bool>, but that +// would require allocating some storage for the bool. +class ThreadLocalBooleanAutoReset { + public: + ThreadLocalBooleanAutoReset(ThreadLocalBoolean* tlb, bool new_value) + : scoped_tlb_(tlb), + original_value_(tlb->Get()) { + scoped_tlb_->Set(new_value); + } + ~ThreadLocalBooleanAutoReset() { + scoped_tlb_->Set(original_value_); + } + + private: + ThreadLocalBoolean* scoped_tlb_; + bool original_value_; + + DISALLOW_COPY_AND_ASSIGN(ThreadLocalBooleanAutoReset); +}; + +base::LazyInstance<ThreadLocalBoolean>::Leaky + g_unchecked_malloc = LAZY_INSTANCE_INITIALIZER; + +// NOTE(shess): This is called when the malloc library noticed that the heap +// is fubar. Avoid calls which will re-enter the malloc library. +void CrMallocErrorBreak() { + g_original_malloc_error_break(); + + // Out of memory is certainly not heap corruption, and not necessarily + // something for which the process should be terminated. Leave that decision + // to the OOM killer. The EBADF case comes up because the malloc library + // attempts to log to ASL (syslog) before calling this code, which fails + // accessing a Unix-domain socket because of sandboxing. + if (errno == ENOMEM || (errno == EBADF && g_unchecked_malloc.Get().Get())) + return; + + // A unit test checks this error message, so it needs to be in release builds. + char buf[1024] = + "Terminating process due to a potential for future heap corruption: " + "errno="; + char errnobuf[] = { + '0' + ((errno / 100) % 10), + '0' + ((errno / 10) % 10), + '0' + (errno % 10), + '\000' + }; + COMPILE_ASSERT(ELAST <= 999, errno_too_large_to_encode); + strlcat(buf, errnobuf, sizeof(buf)); + RAW_LOG(ERROR, buf); + + // Crash by writing to NULL+errno to allow analyzing errno from + // crash dump info (setting a breakpad key would re-enter the malloc + // library). Max documented errno in intro(2) is actually 102, but + // it really just needs to be "small" to stay on the right vm page. + const int kMaxErrno = 256; + char* volatile death_ptr = NULL; + death_ptr += std::min(errno, kMaxErrno); + *death_ptr = '!'; +} + +} // namespace +#endif // ARCH_CPU_32_BITS + +void EnableTerminationOnHeapCorruption() { +#if defined(ADDRESS_SANITIZER) || ARCH_CPU_64_BITS + // AddressSanitizer handles heap corruption, and on 64 bit Macs, the malloc + // system automatically abort()s on heap corruption. + return; +#else + // Only override once, otherwise CrMallocErrorBreak() will recurse + // to itself. + if (g_original_malloc_error_break) + return; + + malloc_error_break_t malloc_error_break = LookUpMallocErrorBreak(); + if (!malloc_error_break) { + DLOG(WARNING) << "Could not find malloc_error_break"; + return; + } + + mach_error_t err = mach_override_ptr( + (void*)malloc_error_break, + (void*)&CrMallocErrorBreak, + (void**)&g_original_malloc_error_break); + + if (err != err_none) + DLOG(WARNING) << "Could not override malloc_error_break; error = " << err; +#endif // defined(ADDRESS_SANITIZER) || ARCH_CPU_64_BITS +} + +// ------------------------------------------------------------------------ + +namespace { + +bool g_oom_killer_enabled; + +// Starting with Mac OS X 10.7, the zone allocators set up by the system are +// read-only, to prevent them from being overwritten in an attack. However, +// blindly unprotecting and reprotecting the zone allocators fails with +// GuardMalloc because GuardMalloc sets up its zone allocator using a block of +// memory in its bss. Explicit saving/restoring of the protection is required. +// +// This function takes a pointer to a malloc zone, de-protects it if necessary, +// and returns (in the out parameters) a region of memory (if any) to be +// re-protected when modifications are complete. This approach assumes that +// there is no contention for the protection of this memory. +void DeprotectMallocZone(ChromeMallocZone* default_zone, + mach_vm_address_t* reprotection_start, + mach_vm_size_t* reprotection_length, + vm_prot_t* reprotection_value) { + mach_port_t unused; + *reprotection_start = reinterpret_cast<mach_vm_address_t>(default_zone); + struct vm_region_basic_info_64 info; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + kern_return_t result = + mach_vm_region(mach_task_self(), + reprotection_start, + reprotection_length, + VM_REGION_BASIC_INFO_64, + reinterpret_cast<vm_region_info_t>(&info), + &count, + &unused); + CHECK(result == KERN_SUCCESS); + + result = mach_port_deallocate(mach_task_self(), unused); + CHECK(result == KERN_SUCCESS); + + // Does the region fully enclose the zone pointers? Possibly unwarranted + // simplification used: using the size of a full version 8 malloc zone rather + // than the actual smaller size if the passed-in zone is not version 8. + CHECK(*reprotection_start <= + reinterpret_cast<mach_vm_address_t>(default_zone)); + mach_vm_size_t zone_offset = reinterpret_cast<mach_vm_size_t>(default_zone) - + reinterpret_cast<mach_vm_size_t>(*reprotection_start); + CHECK(zone_offset + sizeof(ChromeMallocZone) <= *reprotection_length); + + if (info.protection & VM_PROT_WRITE) { + // No change needed; the zone is already writable. + *reprotection_start = 0; + *reprotection_length = 0; + *reprotection_value = VM_PROT_NONE; + } else { + *reprotection_value = info.protection; + result = mach_vm_protect(mach_task_self(), + *reprotection_start, + *reprotection_length, + false, + info.protection | VM_PROT_WRITE); + CHECK(result == KERN_SUCCESS); + } +} + +// === C malloc/calloc/valloc/realloc/posix_memalign === + +typedef void* (*malloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void* (*calloc_type)(struct _malloc_zone_t* zone, + size_t num_items, + size_t size); +typedef void* (*valloc_type)(struct _malloc_zone_t* zone, + size_t size); +typedef void (*free_type)(struct _malloc_zone_t* zone, + void* ptr); +typedef void* (*realloc_type)(struct _malloc_zone_t* zone, + void* ptr, + size_t size); +typedef void* (*memalign_type)(struct _malloc_zone_t* zone, + size_t alignment, + size_t size); + +malloc_type g_old_malloc; +calloc_type g_old_calloc; +valloc_type g_old_valloc; +free_type g_old_free; +realloc_type g_old_realloc; +memalign_type g_old_memalign; + +malloc_type g_old_malloc_purgeable; +calloc_type g_old_calloc_purgeable; +valloc_type g_old_valloc_purgeable; +free_type g_old_free_purgeable; +realloc_type g_old_realloc_purgeable; +memalign_type g_old_memalign_purgeable; + +void* oom_killer_malloc(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_malloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_calloc(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_valloc(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free(struct _malloc_zone_t* zone, + void* ptr) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + g_old_free(zone, ptr); +} + +void* oom_killer_realloc(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_realloc(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_memalign(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_malloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone, + size_t num_items, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_calloc_purgeable(zone, num_items, size); + if (!result && num_items && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_valloc_purgeable(zone, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void oom_killer_free_purgeable(struct _malloc_zone_t* zone, + void* ptr) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + g_old_free_purgeable(zone, ptr); +} + +void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone, + void* ptr, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_realloc_purgeable(zone, ptr, size); + if (!result && size) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone, + size_t alignment, + size_t size) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; +#endif // ARCH_CPU_32_BITS + void* result = g_old_memalign_purgeable(zone, alignment, size); + // Only die if posix_memalign would have returned ENOMEM, since there are + // other reasons why NULL might be returned (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ). + if (!result && size && alignment >= sizeof(void*) + && (alignment & (alignment - 1)) == 0) { + debug::BreakDebugger(); + } + return result; +} + +// === C++ operator new === + +void oom_killer_new() { + debug::BreakDebugger(); +} + +// === Core Foundation CFAllocators === + +bool CanGetContextForCFAllocator() { + return !base::mac::IsOSLaterThanMountainLion_DontCallThis(); +} + +CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) { + if (base::mac::IsOSSnowLeopard()) { + ChromeCFAllocatorLeopards* our_allocator = + const_cast<ChromeCFAllocatorLeopards*>( + reinterpret_cast<const ChromeCFAllocatorLeopards*>(allocator)); + return &our_allocator->_context; + } else if (base::mac::IsOSLion() || base::mac::IsOSMountainLion()) { + ChromeCFAllocatorLions* our_allocator = + const_cast<ChromeCFAllocatorLions*>( + reinterpret_cast<const ChromeCFAllocatorLions*>(allocator)); + return &our_allocator->_context; + } else { + return NULL; + } +} + +CFAllocatorAllocateCallBack g_old_cfallocator_system_default; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc; +CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone; + +void* oom_killer_cfallocator_system_default(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_system_default(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size, + CFOptionFlags hint, + void* info) { + void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info); + if (!result) + debug::BreakDebugger(); + return result; +} + +// === Cocoa NSObject allocation === + +typedef id (*allocWithZone_t)(id, SEL, NSZone*); +allocWithZone_t g_old_allocWithZone; + +id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) +{ + id result = g_old_allocWithZone(self, _cmd, zone); + if (!result) + debug::BreakDebugger(); + return result; +} + +} // namespace + +void* UncheckedMalloc(size_t size) { + if (g_old_malloc) { +#if ARCH_CPU_32_BITS + ScopedClearErrno clear_errno; + ThreadLocalBooleanAutoReset flag(g_unchecked_malloc.Pointer(), true); +#endif // ARCH_CPU_32_BITS + return g_old_malloc(malloc_default_zone(), size); + } + return malloc(size); +} + +void EnableTerminationOnOutOfMemory() { + if (g_oom_killer_enabled) + return; + + g_oom_killer_enabled = true; + + // === C malloc/calloc/valloc/realloc/posix_memalign === + + // This approach is not perfect, as requests for amounts of memory larger than + // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will + // still fail with a NULL rather than dying (see + // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). + // Unfortunately, it's the best we can do. Also note that this does not affect + // allocations from non-default zones. + + CHECK(!g_old_malloc && !g_old_calloc && !g_old_valloc && !g_old_realloc && + !g_old_memalign) << "Old allocators unexpectedly non-null"; + + CHECK(!g_old_malloc_purgeable && !g_old_calloc_purgeable && + !g_old_valloc_purgeable && !g_old_realloc_purgeable && + !g_old_memalign_purgeable) << "Old allocators unexpectedly non-null"; + +#if !defined(ADDRESS_SANITIZER) + // Don't do anything special on OOM for the malloc zones replaced by + // AddressSanitizer, as modifying or protecting them may not work correctly. + + ChromeMallocZone* default_zone = + reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); + ChromeMallocZone* purgeable_zone = + reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); + + mach_vm_address_t default_reprotection_start = 0; + mach_vm_size_t default_reprotection_length = 0; + vm_prot_t default_reprotection_value = VM_PROT_NONE; + DeprotectMallocZone(default_zone, + &default_reprotection_start, + &default_reprotection_length, + &default_reprotection_value); + + mach_vm_address_t purgeable_reprotection_start = 0; + mach_vm_size_t purgeable_reprotection_length = 0; + vm_prot_t purgeable_reprotection_value = VM_PROT_NONE; + if (purgeable_zone) { + DeprotectMallocZone(purgeable_zone, + &purgeable_reprotection_start, + &purgeable_reprotection_length, + &purgeable_reprotection_value); + } + + // Default zone + + g_old_malloc = default_zone->malloc; + g_old_calloc = default_zone->calloc; + g_old_valloc = default_zone->valloc; + g_old_free = default_zone->free; + g_old_realloc = default_zone->realloc; + CHECK(g_old_malloc && g_old_calloc && g_old_valloc && g_old_free && + g_old_realloc) + << "Failed to get system allocation functions."; + + default_zone->malloc = oom_killer_malloc; + default_zone->calloc = oom_killer_calloc; + default_zone->valloc = oom_killer_valloc; + default_zone->free = oom_killer_free; + default_zone->realloc = oom_killer_realloc; + + if (default_zone->version >= 5) { + g_old_memalign = default_zone->memalign; + if (g_old_memalign) + default_zone->memalign = oom_killer_memalign; + } + + // Purgeable zone (if it exists) + + if (purgeable_zone) { + g_old_malloc_purgeable = purgeable_zone->malloc; + g_old_calloc_purgeable = purgeable_zone->calloc; + g_old_valloc_purgeable = purgeable_zone->valloc; + g_old_free_purgeable = purgeable_zone->free; + g_old_realloc_purgeable = purgeable_zone->realloc; + CHECK(g_old_malloc_purgeable && g_old_calloc_purgeable && + g_old_valloc_purgeable && g_old_free_purgeable && + g_old_realloc_purgeable) + << "Failed to get system allocation functions."; + + purgeable_zone->malloc = oom_killer_malloc_purgeable; + purgeable_zone->calloc = oom_killer_calloc_purgeable; + purgeable_zone->valloc = oom_killer_valloc_purgeable; + purgeable_zone->free = oom_killer_free_purgeable; + purgeable_zone->realloc = oom_killer_realloc_purgeable; + + if (purgeable_zone->version >= 5) { + g_old_memalign_purgeable = purgeable_zone->memalign; + if (g_old_memalign_purgeable) + purgeable_zone->memalign = oom_killer_memalign_purgeable; + } + } + + // Restore protection if it was active. + + if (default_reprotection_start) { + kern_return_t result = mach_vm_protect(mach_task_self(), + default_reprotection_start, + default_reprotection_length, + false, + default_reprotection_value); + CHECK(result == KERN_SUCCESS); + } + + if (purgeable_reprotection_start) { + kern_return_t result = mach_vm_protect(mach_task_self(), + purgeable_reprotection_start, + purgeable_reprotection_length, + false, + purgeable_reprotection_value); + CHECK(result == KERN_SUCCESS); + } +#endif + + // === C malloc_zone_batch_malloc === + + // batch_malloc is omitted because the default malloc zone's implementation + // only supports batch_malloc for "tiny" allocations from the free list. It + // will fail for allocations larger than "tiny", and will only allocate as + // many blocks as it's able to from the free list. These factors mean that it + // can return less than the requested memory even in a non-out-of-memory + // situation. There's no good way to detect whether a batch_malloc failure is + // due to these other factors, or due to genuine memory or address space + // exhaustion. The fact that it only allocates space from the "tiny" free list + // means that it's likely that a failure will not be due to memory exhaustion. + // Similarly, these constraints on batch_malloc mean that callers must always + // be expecting to receive less memory than was requested, even in situations + // where memory pressure is not a concern. Finally, the only public interface + // to batch_malloc is malloc_zone_batch_malloc, which is specific to the + // system's malloc implementation. It's unlikely that anyone's even heard of + // it. + + // === C++ operator new === + + // Yes, operator new does call through to malloc, but this will catch failures + // that our imperfect handling of malloc cannot. + + std::set_new_handler(oom_killer_new); + +#ifndef ADDRESS_SANITIZER + // === Core Foundation CFAllocators === + + // This will not catch allocation done by custom allocators, but will catch + // all allocation done by system-provided ones. + + CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc && + !g_old_cfallocator_malloc_zone) + << "Old allocators unexpectedly non-null"; + + bool cf_allocator_internals_known = CanGetContextForCFAllocator(); + + if (cf_allocator_internals_known) { + CFAllocatorContext* context = + ContextForCFAllocator(kCFAllocatorSystemDefault); + CHECK(context) << "Failed to get context for kCFAllocatorSystemDefault."; + g_old_cfallocator_system_default = context->allocate; + CHECK(g_old_cfallocator_system_default) + << "Failed to get kCFAllocatorSystemDefault allocation function."; + context->allocate = oom_killer_cfallocator_system_default; + + context = ContextForCFAllocator(kCFAllocatorMalloc); + CHECK(context) << "Failed to get context for kCFAllocatorMalloc."; + g_old_cfallocator_malloc = context->allocate; + CHECK(g_old_cfallocator_malloc) + << "Failed to get kCFAllocatorMalloc allocation function."; + context->allocate = oom_killer_cfallocator_malloc; + + context = ContextForCFAllocator(kCFAllocatorMallocZone); + CHECK(context) << "Failed to get context for kCFAllocatorMallocZone."; + g_old_cfallocator_malloc_zone = context->allocate; + CHECK(g_old_cfallocator_malloc_zone) + << "Failed to get kCFAllocatorMallocZone allocation function."; + context->allocate = oom_killer_cfallocator_malloc_zone; + } else { + NSLog(@"Internals of CFAllocator not known; out-of-memory failures via " + "CFAllocator will not result in termination. http://crbug.com/45650"); + } +#endif + + // === Cocoa NSObject allocation === + + // Note that both +[NSObject new] and +[NSObject alloc] call through to + // +[NSObject allocWithZone:]. + + CHECK(!g_old_allocWithZone) + << "Old allocator unexpectedly non-null"; + + Class nsobject_class = [NSObject class]; + Method orig_method = class_getClassMethod(nsobject_class, + @selector(allocWithZone:)); + g_old_allocWithZone = reinterpret_cast<allocWithZone_t>( + method_getImplementation(orig_method)); + CHECK(g_old_allocWithZone) + << "Failed to get allocWithZone allocation function."; + method_setImplementation(orig_method, + reinterpret_cast<IMP>(oom_killer_allocWithZone)); +} + +} // namespace base diff --git a/chromium/base/process/memory_stubs.cc b/chromium/base/process/memory_stubs.cc new file mode 100644 index 00000000000..b06c7d5f2b5 --- /dev/null +++ b/chromium/base/process/memory_stubs.cc @@ -0,0 +1,19 @@ +// 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" + +namespace base { + +void EnableTerminationOnOutOfMemory() { +} + +void EnableTerminationOnHeapCorruption() { +} + +bool AdjustOOMScore(ProcessId process, int score) { + return false; +} + +} // namespace base diff --git a/chromium/base/process/memory_unittest.cc b/chromium/base/process/memory_unittest.cc new file mode 100644 index 00000000000..a1f30526aa3 --- /dev/null +++ b/chromium/base/process/memory_unittest.cc @@ -0,0 +1,379 @@ +// 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/compiler_specific.h" +#include "base/debug/alias.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/process/memory_unittest_mac.h" +#endif +#if defined(OS_LINUX) +#include <glib.h> +#include <malloc.h> +#endif + +#if defined(OS_WIN) +// HeapQueryInformation function pointer. +typedef BOOL (WINAPI* HeapQueryFn) \ + (HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); + +const int kConstantInModule = 42; + +TEST(ProcessMemoryTest, GetModuleFromAddress) { + // Since the unit tests are their own EXE, this should be + // equivalent to the EXE's HINSTANCE. + // + // kConstantInModule is a constant in this file and + // therefore within the unit test EXE. + EXPECT_EQ(::GetModuleHandle(NULL), + base::GetModuleFromAddress( + const_cast<int*>(&kConstantInModule))); + + // Any address within the kernel32 module should return + // kernel32's HMODULE. Our only assumption here is that + // kernel32 is larger than 4 bytes. + HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll"); + HMODULE kernel32_from_address = + base::GetModuleFromAddress(reinterpret_cast<DWORD*>(kernel32) + 1); + EXPECT_EQ(kernel32, kernel32_from_address); +} + +TEST(ProcessMemoryTest, EnableLFH) { + ASSERT_TRUE(base::EnableLowFragmentationHeap()); + if (IsDebuggerPresent()) { + // Under these conditions, LFH can't be enabled. There's no point to test + // anything. + const char* no_debug_env = getenv("_NO_DEBUG_HEAP"); + if (!no_debug_env || strcmp(no_debug_env, "1")) + return; + } + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + ASSERT_TRUE(kernel32 != NULL); + HeapQueryFn heap_query = reinterpret_cast<HeapQueryFn>(GetProcAddress( + kernel32, + "HeapQueryInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail but we won't be able to retrieves information about the heap, so we + // should stop here. + if (heap_query == NULL) + return; + + HANDLE heaps[1024] = { 0 }; + unsigned number_heaps = GetProcessHeaps(1024, heaps); + EXPECT_GT(number_heaps, 0u); + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG flag = 0; + SIZE_T length; + ASSERT_NE(0, heap_query(heaps[i], + HeapCompatibilityInformation, + &flag, + sizeof(flag), + &length)); + // If flag is 0, the heap is a standard heap that does not support + // look-asides. If flag is 1, the heap supports look-asides. If flag is 2, + // the heap is a low-fragmentation heap (LFH). Note that look-asides are not + // supported on the LFH. + + // We don't have any documented way of querying the HEAP_NO_SERIALIZE flag. + EXPECT_LE(flag, 2u); + EXPECT_NE(flag, 1u); + } +} +#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. + +#if !defined(ADDRESS_SANITIZER) +// The following code tests the system implementation of malloc() thus no need +// to test it under AddressSanitizer. +TEST(ProcessMemoryTest, MacMallocFailureDoesNotTerminate) { + // Test that ENOMEM doesn't crash via CrMallocErrorBreak two ways: the exit + // code and lack of the error string. The number of bytes is one less than + // MALLOC_ABSOLUTE_MAX_SIZE, more than which the system early-returns NULL and + // does not call through malloc_error_break(). See the comment at + // EnableTerminationOnOutOfMemory() for more information. + void* buf = NULL; + ASSERT_EXIT( + { + base::EnableTerminationOnOutOfMemory(); + + buf = malloc(std::numeric_limits<size_t>::max() - (2 * PAGE_SIZE) - 1); + }, + testing::KilledBySignal(SIGTRAP), + "\\*\\*\\* error: can't allocate region.*" + "(Terminating process due to a potential for future heap " + "corruption){0}"); + + base::debug::Alias(buf); +} +#endif // !defined(ADDRESS_SANITIZER) + +TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { + // 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(free(buf), ""); +#elif defined(ADDRESS_SANITIZER) + // AddressSanitizer replaces malloc() and prints a different error message on + // heap corruption. + ASSERT_DEATH(free(buf), "attempting free on address which " + "was not malloc\\(\\)-ed"); +#else + ASSERT_DEATH(free(buf), "being freed.*" + "\\*\\*\\* set a breakpoint in malloc_error_break to debug.*" + "Terminating process due to a potential for future heap corruption"); +#endif // ARCH_CPU_64_BITS || defined(ADDRESS_SANITIZER) +} + +#endif // defined(OS_MACOSX) + +// Android doesn't implement set_new_handler, so we can't use the +// OutOfMemoryTest cases. +// OpenBSD does not support these tests either. +// AddressSanitizer and ThreadSanitizer define the malloc()/free()/etc. +// functions so that they don't crash if the program is out of memory, so the +// OOM tests aren't supposed to work. +// TODO(vandebo) make this work on Windows too. +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ + !defined(OS_WIN) && \ + !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER) + +#if defined(USE_TCMALLOC) +extern "C" { +int tc_set_new_mode(int mode); +} +#endif // defined(USE_TCMALLOC) + +class OutOfMemoryDeathTest : public testing::Test { + public: + OutOfMemoryDeathTest() + : value_(NULL), + // 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), + signed_test_size_(std::numeric_limits<ssize_t>::max()) { + } + +#if defined(USE_TCMALLOC) + virtual void SetUp() OVERRIDE { + tc_set_new_mode(1); + } + + virtual void TearDown() OVERRIDE { + tc_set_new_mode(0); + } +#endif // defined(USE_TCMALLOC) + + void SetUpInDeathAssert() { + // 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(); + } + + void* value_; + size_t test_size_; + ssize_t signed_test_size_; +}; + +TEST_F(OutOfMemoryDeathTest, New) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = operator new(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, NewArray) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = new char[test_size_]; + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Malloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Realloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = realloc(NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Calloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = calloc(1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Valloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = valloc(test_size_); + }, ""); +} + +#if defined(OS_LINUX) +TEST_F(OutOfMemoryDeathTest, Pvalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = pvalloc(test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, Memalign) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = memalign(4, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { + // g_try_malloc is documented to return NULL on failure. (g_malloc is the + // 'safe' default that crashes if allocation fails). However, since we have + // hopefully overridden malloc, even g_try_malloc should fail. This tests + // that the run-time symbol resolution is overriding malloc for shared + // libraries as well as for our code. + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = g_try_malloc(test_size_); + }, ""); +} +#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_)); + }, ""); +} +#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_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_realloc(zone, NULL, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_valloc(zone, test_size_); + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { + malloc_zone_t* zone = malloc_default_purgeable_zone(); + ASSERT_DEATH({ + SetUpInDeathAssert(); + value_ = malloc_zone_memalign(zone, 8, test_size_); + }, ""); +} + +// 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_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {} + }, ""); +} + +TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { + ASSERT_DEATH({ + SetUpInDeathAssert(); + while ((value_ = + base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {} + }, ""); +} + +#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())) {} + }, ""); +} + +#endif // !ARCH_CPU_64_BITS +#endif // OS_MACOSX + +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && + // !defined(OS_WIN) && !defined(ADDRESS_SANITIZER) diff --git a/chromium/base/process/memory_unittest_mac.h b/chromium/base/process/memory_unittest_mac.h new file mode 100644 index 00000000000..472d2c59627 --- /dev/null +++ b/chromium/base/process/memory_unittest_mac.h @@ -0,0 +1,32 @@ +// 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 "base/basictypes.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/chromium/base/process/memory_unittest_mac.mm b/chromium/base/process/memory_unittest_mac.mm new file mode 100644 index 00000000000..bc4bf65347d --- /dev/null +++ b/chromium/base/process/memory_unittest_mac.mm @@ -0,0 +1,59 @@ +// 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" + +#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/chromium/base/process/memory_win.cc b/chromium/base/process/memory_win.cc new file mode 100644 index 00000000000..c53a1be5395 --- /dev/null +++ b/chromium/base/process/memory_win.cc @@ -0,0 +1,85 @@ +// 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 <psapi.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" + +namespace base { + +namespace { + +void OnNoMemory() { + // Kill the process. This is important for security, since WebKit doesn't + // NULL-check many memory allocations. If a malloc fails, returns NULL, and + // the buffer is then used, it provides a handy mapping of memory starting at + // address 0 for an attacker to utilize. + __debugbreak(); + _exit(1); +} + +// HeapSetInformation function pointer. +typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + +} // namespace + +bool EnableLowFragmentationHeap() { + HMODULE kernel32 = GetModuleHandle(L"kernel32.dll"); + HeapSetFn heap_set = reinterpret_cast<HeapSetFn>(GetProcAddress( + kernel32, + "HeapSetInformation")); + + // On Windows 2000, the function is not exported. This is not a reason to + // fail. + if (!heap_set) + return true; + + unsigned number_heaps = GetProcessHeaps(0, NULL); + if (!number_heaps) + return false; + + // Gives us some extra space in the array in case a thread is creating heaps + // at the same time we're querying them. + static const int MARGIN = 8; + scoped_ptr<HANDLE[]> heaps(new HANDLE[number_heaps + MARGIN]); + number_heaps = GetProcessHeaps(number_heaps + MARGIN, heaps.get()); + if (!number_heaps) + return false; + + for (unsigned i = 0; i < number_heaps; ++i) { + ULONG lfh_flag = 2; + // Don't bother with the result code. It may fails on heaps that have the + // HEAP_NO_SERIALIZE flag. This is expected and not a problem at all. + heap_set(heaps[i], + HeapCompatibilityInformation, + &lfh_flag, + sizeof(lfh_flag)); + } + return true; +} + +void EnableTerminationOnHeapCorruption() { + // Ignore the result code. Supported on XP SP3 and Vista. + HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); +} + +void EnableTerminationOnOutOfMemory() { + std::set_new_handler(&OnNoMemory); +} + +HMODULE GetModuleFromAddress(void* address) { + HMODULE instance = NULL; + if (!::GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast<char*>(address), + &instance)) { + NOTREACHED(); + } + return instance; +} + +} // namespace base diff --git a/chromium/base/process/process.h b/chromium/base/process/process.h new file mode 100644 index 00000000000..20e8884b972 --- /dev/null +++ b/chromium/base/process/process.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef BASE_PROCESS_PROCESS_PROCESS_H_ +#define BASE_PROCESS_PROCESS_PROCESS_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "build/build_config.h" + +namespace base { + +class BASE_EXPORT Process { + public: + Process() : process_(kNullProcessHandle) { + } + + explicit Process(ProcessHandle handle) : process_(handle) { + } + + // A handle to the current process. + static Process Current(); + + static bool CanBackgroundProcesses(); + + // Get/Set the handle for this process. The handle will be 0 if the process + // is no longer running. + ProcessHandle handle() const { return process_; } + void set_handle(ProcessHandle handle) { + process_ = handle; + } + + // Get the PID for this process. + ProcessId pid() const; + + // Is the this process the current process. + bool is_current() const; + + // Close the process handle. This will not terminate the process. + void Close(); + + // Terminates the process with extreme prejudice. The given result code will + // be the exit code of the process. If the process has already exited, this + // will do nothing. + void Terminate(int result_code); + + // 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); + + // Returns an integer representing the priority of a process. The meaning + // of this value is OS dependent. + int GetPriority() const; + + private: + ProcessHandle process_; +}; + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_PROCESS_H_ diff --git a/chromium/base/process/process_handle.h b/chromium/base/process/process_handle.h new file mode 100644 index 00000000000..a37784275ea --- /dev/null +++ b/chromium/base/process/process_handle.h @@ -0,0 +1,96 @@ +// 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 "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "build/build_config.h" + +#include <sys/types.h> +#if defined(OS_WIN) +#include <windows.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(OS_WIN) +typedef HANDLE ProcessHandle; +typedef DWORD ProcessId; +typedef HANDLE UserTokenHandle; +const ProcessHandle kNullProcessHandle = NULL; +const ProcessId kNullProcessId = 0; +#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) + +// Returns the id of the current process. +BASE_EXPORT ProcessId GetCurrentProcId(); + +// Returns the ProcessHandle of the current process. +BASE_EXPORT ProcessHandle GetCurrentProcessHandle(); + +// Converts a PID to a process handle. This handle must be closed by +// CloseProcessHandle when you are done with it. Returns true on success. +BASE_EXPORT bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle); + +// Converts a PID to a process handle. On Windows the handle is opened +// with more access rights and must only be used by trusted code. +// You have to close returned handle using CloseProcessHandle. Returns true +// on success. +// TODO(sanjeevr): Replace all calls to OpenPrivilegedProcessHandle with the +// more specific OpenProcessHandleWithAccess method and delete this. +BASE_EXPORT bool OpenPrivilegedProcessHandle(ProcessId pid, + ProcessHandle* handle); + +// Converts a PID to a process handle using the desired access flags. Use a +// combination of the kProcessAccess* flags defined above for |access_flags|. +BASE_EXPORT bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle); + +// Closes the process handle opened by OpenProcessHandle. +BASE_EXPORT void CloseProcessHandle(ProcessHandle process); + +// Returns the unique 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. +BASE_EXPORT ProcessId GetProcId(ProcessHandle process); + +#if defined(OS_WIN) +enum IntegrityLevel { + INTEGRITY_UNKNOWN, + LOW_INTEGRITY, + MEDIUM_INTEGRITY, + HIGH_INTEGRITY, +}; +// Determine the integrity level of the specified process. Returns false +// if the system does not support integrity levels (pre-Vista) or in the case +// of an underlying system failure. +BASE_EXPORT bool GetProcessIntegrityLevel(ProcessHandle process, + IntegrityLevel* level); +#endif + +#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_BSD) +// Returns the path to the executable of the given process. +BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process); +#endif + +#if defined(OS_POSIX) +// Returns the ID for the parent of the given process. +BASE_EXPORT ProcessId GetParentProcessId(ProcessHandle process); +#endif + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_HANDLE_H_ diff --git a/chromium/base/process/process_handle_freebsd.cc b/chromium/base/process/process_handle_freebsd.cc new file mode 100644 index 00000000000..8a0e9de8f2d --- /dev/null +++ b/chromium/base/process/process_handle_freebsd.cc @@ -0,0 +1,39 @@ +// 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_handle.h" + +#include <sys/sysctl.h> +#include <sys/types.h> +#include <unistd.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/chromium/base/process/process_handle_linux.cc b/chromium/base/process/process_handle_linux.cc new file mode 100644 index 00000000000..91441f7b38c --- /dev/null +++ b/chromium/base/process/process_handle_linux.cc @@ -0,0 +1,30 @@ +// 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/file_util.h" +#include "base/process/internal_linux.h" + +namespace base { + +ProcessId GetParentProcessId(ProcessHandle process) { + ProcessId pid = + internal::ReadProcStatsAndGetFieldAsInt(process, internal::VM_PPID); + if (pid) + return pid; + return -1; +} + +FilePath GetProcessExecutablePath(ProcessHandle process) { + FilePath stat_file = internal::GetProcPidDir(process).Append("exe"); + FilePath exe_name; + if (!file_util::ReadSymbolicLink(stat_file, &exe_name)) { + // No such process. Happens frequently in e.g. TerminateAllChromeProcesses + return FilePath(); + } + return exe_name; +} + +} // namespace base diff --git a/chromium/base/process/process_handle_mac.cc b/chromium/base/process/process_handle_mac.cc new file mode 100644 index 00000000000..6cb8d686e4d --- /dev/null +++ b/chromium/base/process/process_handle_mac.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/process_handle.h" + +#include <sys/sysctl.h> +#include <sys/types.h> + +#include "base/logging.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; +} + +} // namespace base diff --git a/chromium/base/process/process_handle_openbsd.cc b/chromium/base/process/process_handle_openbsd.cc new file mode 100644 index 00000000000..3508ccb04cb --- /dev/null +++ b/chromium/base/process/process_handle_openbsd.cc @@ -0,0 +1,49 @@ +// 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_handle.h" + +#include <sys/sysctl.h> +#include <sys/types.h> +#include <unistd.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/chromium/base/process/process_handle_posix.cc b/chromium/base/process/process_handle_posix.cc new file mode 100644 index 00000000000..4013254a5c2 --- /dev/null +++ b/chromium/base/process/process_handle_posix.cc @@ -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/process_handle.h" + +#include <unistd.h> + +namespace base { + +ProcessId GetCurrentProcId() { + return getpid(); +} + +ProcessHandle GetCurrentProcessHandle() { + return GetCurrentProcId(); +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On Posix platforms, process handles are the same as PIDs, so we + // don't need to do anything. + *handle = pid; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + // On POSIX permissions are checked for each operation on process, + // not when opening a "handle". + return OpenProcessHandle(pid, handle); +} + +void CloseProcessHandle(ProcessHandle process) { + // See OpenProcessHandle, nothing to do. + return; +} + +ProcessId GetProcId(ProcessHandle process) { + return process; +} + +} // namespace base diff --git a/chromium/base/process/process_handle_win.cc b/chromium/base/process/process_handle_win.cc new file mode 100644 index 00000000000..3bc3a125e0d --- /dev/null +++ b/chromium/base/process/process_handle_win.cc @@ -0,0 +1,126 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" + +namespace base { + +ProcessId GetCurrentProcId() { + return ::GetCurrentProcessId(); +} + +ProcessHandle GetCurrentProcessHandle() { + return ::GetCurrentProcess(); +} + +bool OpenProcessHandle(ProcessId pid, ProcessHandle* handle) { + // We try to limit privileges granted to the handle. If you need this + // for test code, consider using OpenPrivilegedProcessHandle instead of + // adding more privileges here. + ProcessHandle result = OpenProcess(PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | + SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenPrivilegedProcessHandle(ProcessId pid, ProcessHandle* handle) { + ProcessHandle result = OpenProcess(PROCESS_DUP_HANDLE | + PROCESS_TERMINATE | + PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ | + SYNCHRONIZE, + FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +bool OpenProcessHandleWithAccess(ProcessId pid, + uint32 access_flags, + ProcessHandle* handle) { + ProcessHandle result = OpenProcess(access_flags, FALSE, pid); + + if (result == NULL) + return false; + + *handle = result; + return true; +} + +void CloseProcessHandle(ProcessHandle process) { + CloseHandle(process); +} + +ProcessId GetProcId(ProcessHandle process) { + // This returns 0 if we have insufficient rights to query the process handle. + return GetProcessId(process); +} + +bool GetProcessIntegrityLevel(ProcessHandle process, IntegrityLevel *level) { + if (!level) + return false; + + if (win::GetVersion() < base::win::VERSION_VISTA) + return false; + + HANDLE process_token; + if (!OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, + &process_token)) + return false; + + win::ScopedHandle scoped_process_token(process_token); + + DWORD token_info_length = 0; + if (GetTokenInformation(process_token, TokenIntegrityLevel, NULL, 0, + &token_info_length) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return false; + + scoped_ptr<char[]> token_label_bytes(new char[token_info_length]); + if (!token_label_bytes.get()) + return false; + + TOKEN_MANDATORY_LABEL* token_label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_label_bytes.get()); + if (!token_label) + return false; + + if (!GetTokenInformation(process_token, TokenIntegrityLevel, token_label, + token_info_length, &token_info_length)) + return false; + + DWORD integrity_level = *GetSidSubAuthority(token_label->Label.Sid, + (DWORD)(UCHAR)(*GetSidSubAuthorityCount(token_label->Label.Sid)-1)); + + if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID) { + *level = LOW_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID && + integrity_level < SECURITY_MANDATORY_HIGH_RID) { + *level = MEDIUM_INTEGRITY; + } else if (integrity_level >= SECURITY_MANDATORY_HIGH_RID) { + *level = HIGH_INTEGRITY; + } else { + NOTREACHED(); + return false; + } + + return true; +} + +} // namespace base diff --git a/chromium/base/process/process_info.h b/chromium/base/process/process_info.h new file mode 100644 index 00000000000..e9e7b4e8151 --- /dev/null +++ b/chromium/base/process/process_info.h @@ -0,0 +1,25 @@ +// 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_PROCESS_INFO_H_ +#define BASE_PROCESS_PROCESS_PROCESS_INFO_H_ + +#include "base/base_export.h" +#include "base/basictypes.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(); +}; + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_PROCESS_INFO_H_ diff --git a/chromium/base/process/process_info_linux.cc b/chromium/base/process/process_info_linux.cc new file mode 100644 index 00000000000..da34c180e8a --- /dev/null +++ b/chromium/base/process/process_info_linux.cc @@ -0,0 +1,27 @@ +// 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/basictypes.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +namespace base { + +//static +const Time CurrentProcessInfo::CreationTime() { + ProcessHandle pid = GetCurrentProcessHandle(); + int start_ticks = internal::ReadProcStatsAndGetFieldAsInt( + pid, internal::VM_STARTTIME); + DCHECK(start_ticks); + TimeDelta start_offset = internal::ClockTicksToTimeDelta(start_ticks); + Time boot_time = internal::GetBootTime(); + DCHECK(!boot_time.is_null()); + return Time(boot_time + start_offset); +} + +} // namespace base diff --git a/chromium/base/process/process_info_mac.cc b/chromium/base/process/process_info_mac.cc new file mode 100644 index 00000000000..ab8394b891d --- /dev/null +++ b/chromium/base/process/process_info_mac.cc @@ -0,0 +1,31 @@ +// 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 "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.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(); + + scoped_ptr_malloc<struct kinfo_proc> + proc(static_cast<struct kinfo_proc*>(malloc(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/chromium/base/process/process_info_win.cc b/chromium/base/process/process_info_win.cc new file mode 100644 index 00000000000..b930ae6dd88 --- /dev/null +++ b/chromium/base/process/process_info_win.cc @@ -0,0 +1,25 @@ +// 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 "base/basictypes.h" +#include "base/time/time.h" + +namespace base { + +//static +const Time CurrentProcessInfo::CreationTime() { + FILETIME creation_time = {}; + FILETIME ignore = {}; + if (::GetProcessTimes(::GetCurrentProcess(), &creation_time, &ignore, + &ignore, &ignore) == false) + return Time(); + + return Time::FromFileTime(creation_time); +} + +} // namespace base diff --git a/chromium/base/process/process_iterator.cc b/chromium/base/process/process_iterator.cc new file mode 100644 index 00000000000..b9ef047732f --- /dev/null +++ b/chromium/base/process/process_iterator.cc @@ -0,0 +1,65 @@ +// 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" + +namespace base { + +#if defined(OS_POSIX) +ProcessEntry::ProcessEntry() : pid_(0), ppid_(0), gid_(0) {} +ProcessEntry::~ProcessEntry() {} +#endif + +const ProcessEntry* ProcessIterator::NextProcessEntry() { + bool result = false; + do { + result = CheckForNextProcess(); + } while (result && !IncludeEntry()); + if (result) + return &entry_; + return NULL; +} + +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() { +} + +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/chromium/base/process/process_iterator.h b/chromium/base/process/process_iterator.h new file mode 100644 index 00000000000..aa8ba74aba0 --- /dev/null +++ b/chromium/base/process/process_iterator.h @@ -0,0 +1,183 @@ +// 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 "base/base_export.h" +#include "base/basictypes.h" +#include "base/files/file_path.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_BSD) +#include <sys/sysctl.h> +#elif defined(OS_POSIX) +#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; } +}; + +// Process access masks. These constants provide platform-independent +// definitions for the standard Windows access masks. +// See http://msdn.microsoft.com/en-us/library/ms684880(VS.85).aspx for +// the specific semantics of each mask value. +const uint32 kProcessAccessTerminate = PROCESS_TERMINATE; +const uint32 kProcessAccessCreateThread = PROCESS_CREATE_THREAD; +const uint32 kProcessAccessSetSessionId = PROCESS_SET_SESSIONID; +const uint32 kProcessAccessVMOperation = PROCESS_VM_OPERATION; +const uint32 kProcessAccessVMRead = PROCESS_VM_READ; +const uint32 kProcessAccessVMWrite = PROCESS_VM_WRITE; +const uint32 kProcessAccessDuplicateHandle = PROCESS_DUP_HANDLE; +const uint32 kProcessAccessCreateProcess = PROCESS_CREATE_PROCESS; +const uint32 kProcessAccessSetQuota = PROCESS_SET_QUOTA; +const uint32 kProcessAccessSetInformation = PROCESS_SET_INFORMATION; +const uint32 kProcessAccessQueryInformation = PROCESS_QUERY_INFORMATION; +const uint32 kProcessAccessSuspendResume = PROCESS_SUSPEND_RESUME; +const uint32 kProcessAccessQueryLimitedInfomation = + PROCESS_QUERY_LIMITED_INFORMATION; +const uint32 kProcessAccessWaitForTermination = SYNCHRONIZE; +#elif defined(OS_POSIX) +struct BASE_EXPORT ProcessEntry { + ProcessEntry(); + ~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_; +}; + +// Process access masks. They are not used on Posix because access checking +// does not happen during handle creation. +const uint32 kProcessAccessTerminate = 0; +const uint32 kProcessAccessCreateThread = 0; +const uint32 kProcessAccessSetSessionId = 0; +const uint32 kProcessAccessVMOperation = 0; +const uint32 kProcessAccessVMRead = 0; +const uint32 kProcessAccessVMWrite = 0; +const uint32 kProcessAccessDuplicateHandle = 0; +const uint32 kProcessAccessCreateProcess = 0; +const uint32 kProcessAccessSetQuota = 0; +const uint32 kProcessAccessSetInformation = 0; +const uint32 kProcessAccessQueryInformation = 0; +const uint32 kProcessAccessSuspendResume = 0; +const uint32 kProcessAccessQueryLimitedInfomation = 0; +const uint32 kProcessAccessWaitForTermination = 0; +#endif // defined(OS_POSIX) + +// 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() {} +}; + +// 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) + 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); + virtual ~NamedProcessIterator(); + + protected: + virtual 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/chromium/base/process/process_iterator_freebsd.cc b/chromium/base/process/process_iterator_freebsd.cc new file mode 100644 index 00000000000..e8225b0054e --- /dev/null +++ b/chromium/base/process/process_iterator_freebsd.cc @@ -0,0 +1,124 @@ +// 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 <sys/sysctl.h> + +#include "base/logging.h" +#include "base/strings/string_util.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'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + 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/chromium/base/process/process_iterator_linux.cc b/chromium/base/process/process_iterator_linux.cc new file mode 100644 index 00000000000..6da51ca3b55 --- /dev/null +++ b/chromium/base/process/process_iterator_linux.cc @@ -0,0 +1,137 @@ +// 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/file_util.h" +#include "base/logging.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.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 0; +} + +// 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 (!file_util::ReadFileToString(cmd_line_file, &cmd_line)) + return false; + std::string delimiters; + delimiters.push_back('\0'); + Tokenize(cmd_line, delimiters, proc_cmd_line_args); + return true; +} + +} // namespace + +ProcessIterator::ProcessIterator(const ProcessFilter* filter) + : filter_(filter) { + procfs_dir_ = opendir(internal::kProcDir); +} + +ProcessIterator::~ProcessIterator() { + if (procfs_dir_) { + closedir(procfs_dir_); + procfs_dir_ = NULL; + } +} + +bool ProcessIterator::CheckForNextProcess() { + // TODO(port): skip processes owned by different UID + + 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_ = GetProcStatsFieldAsInt(proc_stats, internal::VM_PPID); + entry_.gid_ = GetProcStatsFieldAsInt(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/chromium/base/process/process_iterator_mac.cc b/chromium/base/process/process_iterator_mac.cc new file mode 100644 index 00000000000..e35c2ae19ba --- /dev/null +++ b/chromium/base/process/process_iterator_mac.cc @@ -0,0 +1,135 @@ +// 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/strings/string_util.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, 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 + 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.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'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |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/chromium/base/process/process_iterator_openbsd.cc b/chromium/base/process/process_iterator_openbsd.cc new file mode 100644 index 00000000000..7c44eb118c0 --- /dev/null +++ b/chromium/base/process/process_iterator_openbsd.cc @@ -0,0 +1,128 @@ +// 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/strings/string_util.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'); + Tokenize(data, delimiters, &entry_.cmd_line_args_); + + // |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/chromium/base/process/process_iterator_win.cc b/chromium/base/process/process_iterator_win.cc new file mode 100644 index 00000000000..9d5a970ef48 --- /dev/null +++ b/chromium/base/process/process_iterator_win.cc @@ -0,0 +1,41 @@ +// 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" + +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) { + memset(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/chromium/base/process/process_linux.cc b/chromium/base/process/process_linux.cc new file mode 100644 index 00000000000..93006aafe59 --- /dev/null +++ b/chromium/base/process/process_linux.cc @@ -0,0 +1,137 @@ +// 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/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" + +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)); + file_util::FileSystemType foreground_type; + file_util::FileSystemType background_type; + enabled = + file_util::GetFileSystemType(foreground_file, &foreground_type) && + file_util::GetFileSystemType(background_file, &background_type) && + foreground_type == file_util::FILE_SYSTEM_CGROUP && + background_type == file_util::FILE_SYSTEM_CGROUP; + } +}; + +base::LazyInstance<CGroups> cgroups = LAZY_INSTANCE_INITIALIZER; +#else +const int kBackgroundPriority = 5; +#endif +} + +namespace base { + +bool Process::IsProcessBackgrounded() const { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string proc; + if (file_util::ReadFileToString( + base::FilePath(StringPrintf(kProcPath, process_)), + &proc)) { + std::vector<std::string> proc_parts; + base::SplitString(proc, ':', &proc_parts); + DCHECK(proc_parts.size() == 3); + bool ret = proc_parts[2] == std::string(kBackground); + return ret; + } else { + return false; + } + } +#endif + return GetPriority() == kBackgroundPriority; +} + +bool Process::SetProcessBackgrounded(bool background) { + DCHECK(process_); + +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) { + std::string pid = StringPrintf("%d", process_); + const base::FilePath file = + background ? + cgroups.Get().background_file : cgroups.Get().foreground_file; + return file_util::WriteFile(file, pid.c_str(), pid.size()) > 0; + } +#endif // OS_CHROMEOS + + if (!CanBackgroundProcesses()) + return false; + + int priority = background ? kBackgroundPriority : kForegroundPriority; + int result = setpriority(PRIO_PROCESS, process_, priority); + DPCHECK(result == 0); + return result == 0; +} + +struct CheckForNicePermission { + CheckForNicePermission() : can_reraise_priority(false) { + // We won't be able to raise the priority if we don't have the right rlimit. + // The limit may be adjusted in /etc/security/limits.conf for PAM systems. + struct rlimit rlim; + if ((getrlimit(RLIMIT_NICE, &rlim) == 0) && + (20 - kForegroundPriority) <= static_cast<int>(rlim.rlim_cur)) { + can_reraise_priority = true; + } + }; + + bool can_reraise_priority; +}; + +// static +bool Process::CanBackgroundProcesses() { +#if defined(OS_CHROMEOS) + if (cgroups.Get().enabled) + return true; +#endif + + static LazyInstance<CheckForNicePermission> check_for_nice_permission = + LAZY_INSTANCE_INITIALIZER; + return check_for_nice_permission.Get().can_reraise_priority; +} + +} // namespace base diff --git a/chromium/base/process/process_metrics.h b/chromium/base/process/process_metrics.h new file mode 100644 index 00000000000..e329b4ef73b --- /dev/null +++ b/chromium/base/process/process_metrics.h @@ -0,0 +1,269 @@ +// 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 <string> + +#include "base/base_export.h" +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "base/time/time.h" + +#if defined(OS_MACOSX) +#include <mach/mach.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 + +// Working Set (resident) memory usage broken down by +// +// On Windows: +// priv (private): These pages (kbytes) cannot be shared with any other process. +// shareable: These pages (kbytes) can be shared with other processes under +// the right circumstances. +// shared : These pages (kbytes) are currently shared with at least one +// other process. +// +// On Linux: +// priv: Pages mapped only by this process. +// shared: PSS or 0 if the kernel doesn't support this. +// shareable: 0 + +// On ChromeOS: +// priv: Pages mapped only by this process. +// shared: PSS or 0 if the kernel doesn't support this. +// shareable: 0 +// swapped Pages swapped out to zram. +// +// On OS X: TODO(thakis): Revise. +// priv: Memory. +// shared: 0 +// shareable: 0 +// +struct WorkingSetKBytes { + WorkingSetKBytes() : priv(0), shareable(0), shared(0) {} + size_t priv; + size_t shareable; + size_t shared; +#if defined(OS_CHROMEOS) + size_t swapped; +#endif +}; + +// Committed (resident + paged) memory usage broken down by +// private: These pages cannot be shared with any other process. +// mapped: These pages are mapped into the view of a section (backed by +// pagefile.sys) +// image: These pages are mapped into the view of an image section (backed by +// file system) +struct CommittedKBytes { + CommittedKBytes() : priv(0), mapped(0), image(0) {} + size_t priv; + size_t mapped; + size_t image; +}; + +// Free memory (Megabytes marked as free) in the 2G process address space. +// total : total amount in megabytes marked as free. Maximum value is 2048. +// largest : size of the largest contiguous amount of memory found. It is +// always smaller or equal to FreeMBytes::total. +// largest_ptr: starting address of the largest memory block. +struct FreeMBytes { + size_t total; + size_t largest; + void* largest_ptr; +}; + +// Convert a POSIX timeval to microseconds. +BASE_EXPORT int64 TimeValToMicroseconds(const struct timeval& tv); + +// Provides performance metrics for a specified process (CPU usage, memory and +// IO counters). To use it, invoke CreateProcessMetrics() to get an instance +// for a specific process, then access the information with the different get +// methods. +class BASE_EXPORT ProcessMetrics { + public: + ~ProcessMetrics(); + + // Creates a ProcessMetrics for the specified process. + // The caller owns the returned object. +#if !defined(OS_MACOSX) || defined(OS_IOS) + static ProcessMetrics* CreateProcessMetrics(ProcessHandle process); +#else + class PortProvider { + public: + virtual ~PortProvider() {} + + // Should return the mach task for |process| if possible, or else + // |MACH_PORT_NULL|. Only processes that this returns tasks for will have + // metrics on OS X (except for the current process, which always gets + // metrics). + virtual mach_port_t TaskForPid(ProcessHandle process) const = 0; + }; + + // 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 ProcessMetrics* CreateProcessMetrics(ProcessHandle process, + PortProvider* port_provider); +#endif // !defined(OS_MACOSX) || defined(OS_IOS) + + // Returns the current space allocated for the pagefile, in bytes (these pages + // may or may not be in memory). On Linux, this returns the total virtual + // memory size. + size_t GetPagefileUsage() const; + // Returns the peak space allocated for the pagefile, in bytes. + size_t GetPeakPagefileUsage() const; + // Returns the current working set size, in bytes. On Linux, this returns + // the resident set size. + size_t GetWorkingSetSize() const; + // Returns the peak working set size, in bytes. + size_t GetPeakWorkingSetSize() const; + // Returns private and sharedusage, in bytes. Private bytes is the amount of + // memory currently allocated to a process that cannot be shared. Returns + // false on platform specific error conditions. Note: |private_bytes| + // returns 0 on unsupported OSes: prior to XP SP2. + bool GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes); + // Fills a CommittedKBytes with both resident and paged + // memory usage as per definition of CommittedBytes. + void GetCommittedKBytes(CommittedKBytes* usage) const; + // Fills a WorkingSetKBytes containing resident private and shared memory + // usage in bytes, as per definition of WorkingSetBytes. + bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; + + // Computes the current process available memory for allocation. + // It does a linear scan of the address space querying each memory region + // for its free (unallocated) status. It is useful for estimating the memory + // load and fragmentation. + bool CalculateFreeMemory(FreeMBytes* free) const; + + // Returns the CPU usage in percent since the last time this method was + // called. The first time this method is called it returns 0 and will return + // the actual CPU info on subsequent calls. + // On Windows, the CPU usage value is for all CPUs. So if you have 2 CPUs and + // your process is using all the cycles of 1 CPU and not the other CPU, this + // method returns 50. + double GetCPUUsage(); + + // 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; + + 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_LINUX) || defined(OS_ANDROID) + bool GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) const; +#endif + +#if defined(OS_CHROMEOS) + bool GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) const; +#endif + + ProcessHandle process_; + + int processor_count_; + + // Used to store the previous times and CPU usage counts so we can + // compute the CPU usage between calls. + int64 last_time_; + int64 last_system_time_; + +#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_; +#elif defined(OS_POSIX) + // Jiffie count at the last_time_ we updated. + int last_cpu_; +#endif // defined(OS_POSIX) +#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(); + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// 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(const std::string& input); + +// Data from /proc/meminfo about system-wide memory consumption. +// Values are in KB. +struct BASE_EXPORT SystemMemoryInfoKB { + SystemMemoryInfoKB(); + + int total; + int free; + int buffers; + int cached; + int active_anon; + int inactive_anon; + int active_file; + int inactive_file; + int shmem; + + // Gem data will be -1 if not supported. + int gem_objects; + long long gem_size; +}; +// Retrieves data from /proc/meminfo about system-wide memory consumption. +// Fills in the provided |meminfo| structure. Returns true on success. +// Exposed for memory debugging widget. +BASE_EXPORT bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo); +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_LINUX) || defined(OS_ANDROID) +// 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[]; +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +#if defined(OS_POSIX) +// 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. +size_t GetMaxFds(); +#endif // defined(OS_POSIX) + +} // namespace base + +#endif // BASE_PROCESS_PROCESS_METRICS_H_ diff --git a/chromium/base/process/process_metrics_freebsd.cc b/chromium/base/process/process_metrics_freebsd.cc new file mode 100644 index 00000000000..019454cd81a --- /dev/null +++ b/chromium/base/process/process_metrics_freebsd.cc @@ -0,0 +1,122 @@ +// 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" + +namespace base { + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return info.ki_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +// TODO(bapt) be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +double ProcessMetrics::GetCPUUsage() { + struct kinfo_proc info; + int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, process_ }; + size_t length = sizeof(info); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + if (sysctl(mib, arraysize(mib), &info, &length, NULL, 0) < 0) + return 0; + + return (info.ki_pctcpu / FSCALE) * 100.0; +} + +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, arraysize(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/chromium/base/process/process_metrics_ios.cc b/chromium/base/process/process_metrics_ios.cc new file mode 100644 index 00000000000..94c671901b6 --- /dev/null +++ b/chromium/base/process/process_metrics_ios.cc @@ -0,0 +1,64 @@ +// 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/task.h> + +namespace base { + +namespace { + +bool GetTaskInfo(task_basic_info_64* task_info_data) { + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT; + kern_return_t kr = task_info(mach_task_self(), + TASK_BASIC_INFO_64, + reinterpret_cast<task_info_t>(task_info_data), + &count); + return kr == KERN_SUCCESS; +} + +} // namespace + +ProcessMetrics::ProcessMetrics(ProcessHandle process) {} + +ProcessMetrics::~ProcessMetrics() {} + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(&task_info_data)) + return 0; + return task_info_data.resident_size; +} + +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); +} + +} // namespace base diff --git a/chromium/base/process/process_metrics_linux.cc b/chromium/base/process/process_metrics_linux.cc new file mode 100644 index 00000000000..1c86ee467c9 --- /dev/null +++ b/chromium/base/process/process_metrics_linux.cc @@ -0,0 +1,516 @@ +// 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 "base/file_util.h" +#include "base/logging.h" +#include "base/process/internal_linux.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/sys_info.h" +#include "base/threading/thread_restrictions.h" + +namespace base { + +namespace { + +enum ParsingState { + KEY_NAME, + KEY_VALUE +}; + +// Read /proc/<pid>/status and returns 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, const std::string& field) { + FilePath stat_file = internal::GetProcPidDir(pid).Append("status"); + std::string status; + { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + if (!file_util::ReadFileToString(stat_file, &status)) + return 0; + } + + StringTokenizer tokenizer(status, ":\n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == field) { + std::string value_str; + tokenizer.token_piece().CopyToString(&value_str); + std::string value_str_trimmed; + TrimWhitespaceASCII(value_str, TRIM_ALL, &value_str_trimmed); + std::vector<std::string> split_value_str; + SplitString(value_str_trimmed, ' ', &split_value_str); + 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; + } + state = KEY_NAME; + break; + } + } + NOTREACHED(); + return 0; +} + +// Get the total CPU of a single process. Return value is number of jiffies +// on success or -1 on error. +int GetProcessCPU(pid_t pid) { + // Use /proc/<pid>/task to find all threads and parse their /stat file. + FilePath task_path = internal::GetProcPidDir(pid).Append("task"); + + DIR* dir = opendir(task_path.value().c_str()); + if (!dir) { + DPLOG(ERROR) << "opendir(" << task_path.value() << ")"; + return -1; + } + + int total_cpu = 0; + while (struct dirent* ent = readdir(dir)) { + pid_t tid = internal::ProcDirSlotToPid(ent->d_name); + if (!tid) + continue; + + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string stat; + FilePath stat_path = + task_path.Append(ent->d_name).Append(internal::kStatFile); + if (file_util::ReadFileToString(stat_path, &stat)) { + int cpu = ParseProcStatCPU(stat); + if (cpu > 0) + total_cpu += cpu; + } + } + closedir(dir); + + return total_cpu; +} + +} // namespace + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +// On linux, we return vsize. +size_t ProcessMetrics::GetPagefileUsage() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, + internal::VM_VSIZE); +} + +// On linux, we return the high water mark of vsize. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmPeak") * 1024; +} + +// On linux, we return RSS. +size_t ProcessMetrics::GetWorkingSetSize() const { + return internal::ReadProcStatsAndGetFieldAsSizeT(process_, internal::VM_RSS) * + getpagesize(); +} + +// On linux, we return the high water mark of RSS. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return ReadProcStatusAndGetFieldAsSizeT(process_, "VmHWM") * 1024; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv * 1024; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { +#if defined(OS_CHROMEOS) + if (GetWorkingSetKBytesTotmaps(ws_usage)) + return true; +#endif + return GetWorkingSetKBytesStatm(ws_usage); +} + +double ProcessMetrics::GetCPUUsage() { + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + // We have the number of jiffies in the time period. Convert to percentage. + // Note this means we will go *over* 100 in the case where multiple threads + // are together adding to more than one CPU's worth. + TimeDelta cpu_time = internal::ClockTicksToTimeDelta(cpu); + TimeDelta last_cpu_time = internal::ClockTicksToTimeDelta(last_cpu_); + int percentage = 100 * (cpu_time - last_cpu_time).InSecondsF() / + TimeDelta::FromMicroseconds(time_delta).InSecondsF(); + + last_time_ = time; + last_cpu_ = cpu; + + return percentage; +} + +// To have /proc/self/io file you must enable CONFIG_TASK_IO_ACCOUNTING +// in your kernel configuration. +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + std::string proc_io_contents; + FilePath io_file = internal::GetProcPidDir(process_).Append("io"); + if (!file_util::ReadFileToString(io_file, &proc_io_contents)) + return false; + + (*io_counters).OtherOperationCount = 0; + (*io_counters).OtherTransferCount = 0; + + StringTokenizer tokenizer(proc_io_contents, ": \n"); + ParsingState state = KEY_NAME; + StringPiece last_key_name; + while (tokenizer.GetNext()) { + switch (state) { + case KEY_NAME: + last_key_name = tokenizer.token_piece(); + state = KEY_VALUE; + break; + case KEY_VALUE: + DCHECK(!last_key_name.empty()); + if (last_key_name == "syscr") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadOperationCount)); + } else if (last_key_name == "syscw") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteOperationCount)); + } else if (last_key_name == "rchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).ReadTransferCount)); + } else if (last_key_name == "wchar") { + StringToInt64(tokenizer.token_piece(), + reinterpret_cast<int64*>(&(*io_counters).WriteTransferCount)); + } + state = KEY_NAME; + break; + } + } + return true; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +#if defined(OS_CHROMEOS) +// Private, Shared and Proportional working set sizes are obtained from +// /proc/<pid>/totmaps +bool ProcessMetrics::GetWorkingSetKBytesTotmaps(WorkingSetKBytes *ws_usage) + 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 + const size_t kPssIndex = (1 * 3) + 1; + 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 = file_util::ReadFileToString(totmaps_file, &totmaps_data); + if (!ret || totmaps_data.length() == 0) + return false; + } + + std::vector<std::string> totmaps_fields; + SplitStringAlongWhitespace(totmaps_data, &totmaps_fields); + + DCHECK_EQ("Pss:", totmaps_fields[kPssIndex-1]); + 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 pss = 0; + int private_clean = 0; + int private_dirty = 0; + int swap = 0; + bool ret = true; + ret &= StringToInt(totmaps_fields[kPssIndex], &pss); + ret &= StringToInt(totmaps_fields[kPrivate_CleanIndex], &private_clean); + ret &= StringToInt(totmaps_fields[kPrivate_DirtyIndex], &private_dirty); + ret &= StringToInt(totmaps_fields[kSwapIndex], &swap); + + // On ChromeOS swap is to zram. We count this as private / shared, as + // increased swap decreases available RAM to user processes, which would + // otherwise create surprising results. + ws_usage->priv = private_clean + private_dirty + swap; + ws_usage->shared = pss + swap; + ws_usage->shareable = 0; + ws_usage->swapped = swap; + return ret; +} +#endif + +// Private and Shared working set sizes are obtained from /proc/<pid>/statm. +bool ProcessMetrics::GetWorkingSetKBytesStatm(WorkingSetKBytes* ws_usage) + const { + // Use statm instead of smaps because smaps is: + // a) Large and slow to parse. + // b) Unavailable in the SUID sandbox. + + // First we need to get the page size, since everything is measured in pages. + // For details, see: man 5 proc. + const int page_size_kb = getpagesize() / 1024; + if (page_size_kb <= 0) + return false; + + std::string statm; + { + FilePath statm_file = internal::GetProcPidDir(process_).Append("statm"); + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + bool ret = file_util::ReadFileToString(statm_file, &statm); + if (!ret || statm.length() == 0) + return false; + } + + std::vector<std::string> statm_vec; + SplitString(statm, ' ', &statm_vec); + if (statm_vec.size() != 7) + return false; // Not the format we expect. + + int statm_rss, statm_shared; + bool ret = true; + ret &= StringToInt(statm_vec[1], &statm_rss); + ret &= StringToInt(statm_vec[2], &statm_shared); + + ws_usage->priv = (statm_rss - statm_shared) * page_size_kb; + ws_usage->shared = statm_shared * page_size_kb; + + // Sharable is not calculated, as it does not provide interesting data. + ws_usage->shareable = 0; + +#if defined(OS_CHROMEOS) + // Can't get swapped memory from statm. + ws_usage->swapped = 0; +#endif + + return ret; +} + +size_t GetSystemCommitCharge() { + SystemMemoryInfoKB meminfo; + if (!GetSystemMemoryInfo(&meminfo)) + return 0; + return meminfo.total - meminfo.free - meminfo.buffers - meminfo.cached; +} + +// Exposed for testing. +int ParseProcStatCPU(const std::string& input) { + std::vector<std::string> proc_stats; + if (!internal::ParseProcStats(input, &proc_stats)) + return -1; + + if (proc_stats.size() <= internal::VM_STIME) + return -1; + int utime = GetProcStatsFieldAsInt(proc_stats, internal::VM_UTIME); + int stime = GetProcStatsFieldAsInt(proc_stats, internal::VM_STIME); + return utime + stime; +} + +namespace { + +// The format of /proc/meminfo is: +// +// MemTotal: 8235324 kB +// MemFree: 1628304 kB +// Buffers: 429596 kB +// Cached: 4728232 kB +// ... +const size_t kMemTotalIndex = 1; +const size_t kMemFreeIndex = 4; +const size_t kMemBuffersIndex = 7; +const size_t kMemCachedIndex = 10; +const size_t kMemActiveAnonIndex = 22; +const size_t kMemInactiveAnonIndex = 25; +const size_t kMemActiveFileIndex = 28; +const size_t kMemInactiveFileIndex = 31; + +} // namespace + +SystemMemoryInfoKB::SystemMemoryInfoKB() + : total(0), + free(0), + buffers(0), + cached(0), + active_anon(0), + inactive_anon(0), + active_file(0), + inactive_file(0), + shmem(0), + gem_objects(-1), + gem_size(-1) { +} + +bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { + // Synchronously reading files in /proc is safe. + ThreadRestrictions::ScopedAllowIO allow_io; + + // Used memory is: total - free - buffers - caches + FilePath meminfo_file("/proc/meminfo"); + std::string meminfo_data; + if (!file_util::ReadFileToString(meminfo_file, &meminfo_data)) { + DLOG(WARNING) << "Failed to open " << meminfo_file.value(); + return false; + } + std::vector<std::string> meminfo_fields; + SplitStringAlongWhitespace(meminfo_data, &meminfo_fields); + + if (meminfo_fields.size() < kMemCachedIndex) { + DLOG(WARNING) << "Failed to parse " << meminfo_file.value() + << ". Only found " << meminfo_fields.size() << " fields."; + return false; + } + + DCHECK_EQ(meminfo_fields[kMemTotalIndex-1], "MemTotal:"); + DCHECK_EQ(meminfo_fields[kMemFreeIndex-1], "MemFree:"); + DCHECK_EQ(meminfo_fields[kMemBuffersIndex-1], "Buffers:"); + DCHECK_EQ(meminfo_fields[kMemCachedIndex-1], "Cached:"); + DCHECK_EQ(meminfo_fields[kMemActiveAnonIndex-1], "Active(anon):"); + DCHECK_EQ(meminfo_fields[kMemInactiveAnonIndex-1], "Inactive(anon):"); + DCHECK_EQ(meminfo_fields[kMemActiveFileIndex-1], "Active(file):"); + DCHECK_EQ(meminfo_fields[kMemInactiveFileIndex-1], "Inactive(file):"); + + StringToInt(meminfo_fields[kMemTotalIndex], &meminfo->total); + StringToInt(meminfo_fields[kMemFreeIndex], &meminfo->free); + StringToInt(meminfo_fields[kMemBuffersIndex], &meminfo->buffers); + StringToInt(meminfo_fields[kMemCachedIndex], &meminfo->cached); + StringToInt(meminfo_fields[kMemActiveAnonIndex], &meminfo->active_anon); + StringToInt(meminfo_fields[kMemInactiveAnonIndex], + &meminfo->inactive_anon); + StringToInt(meminfo_fields[kMemActiveFileIndex], &meminfo->active_file); + StringToInt(meminfo_fields[kMemInactiveFileIndex], + &meminfo->inactive_file); +#if defined(OS_CHROMEOS) + // Chrome OS has a tweaked kernel that allows us to query Shmem, which is + // usually video memory otherwise invisible to the OS. Unfortunately, the + // meminfo format varies on different hardware so we have to search for the + // string. It always appears after "Cached:". + for (size_t i = kMemCachedIndex+2; i < meminfo_fields.size(); i += 3) { + if (meminfo_fields[i] == "Shmem:") { + StringToInt(meminfo_fields[i+1], &meminfo->shmem); + break; + } + } + + // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a + // bind mount into /sys/kernel/debug and synchronously reading the in-memory + // files in /sys is fast. +#if defined(ARCH_CPU_ARM_FAMILY) + FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects"); +#else + FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects"); +#endif + std::string geminfo_data; + meminfo->gem_objects = -1; + meminfo->gem_size = -1; + if (file_util::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/devices/platform/mali.0/memory"); + std::string mali_memory_data; + if (file_util::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) + + return true; +} + +const char kProcSelfExe[] = "/proc/self/exe"; + +int GetNumberOfThreads(ProcessHandle process) { + return internal::ReadProcStatsAndGetFieldAsInt(process, + internal::VM_NUMTHREADS); +} + +} // namespace base diff --git a/chromium/base/process/process_metrics_mac.cc b/chromium/base/process/process_metrics_mac.cc new file mode 100644 index 00000000000..048735ed36b --- /dev/null +++ b/chromium/base/process/process_metrics_mac.cc @@ -0,0 +1,325 @@ +// 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/scoped_mach_port.h" +#include "base/sys_info.h" + +namespace base { + +namespace { + +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; +} + +bool GetCPUTypeForProcess(pid_t pid, cpu_type_t* cpu_type) { + size_t len = sizeof(*cpu_type); + int result = sysctlbyname("sysctl.proc_cputype", + cpu_type, + &len, + NULL, + 0); + if (result != 0) { + DPLOG(ERROR) << "sysctlbyname(""sysctl.proc_cputype"")"; + return false; + } + + return true; +} + +bool IsAddressInSharedRegion(mach_vm_address_t addr, cpu_type_t type) { + if (type == CPU_TYPE_I386) { + return addr >= SHARED_REGION_BASE_I386 && + addr < (SHARED_REGION_BASE_I386 + SHARED_REGION_SIZE_I386); + } else if (type == CPU_TYPE_X86_64) { + return addr >= SHARED_REGION_BASE_X86_64 && + addr < (SHARED_REGION_BASE_X86_64 + SHARED_REGION_SIZE_X86_64); + } else { + return false; + } +} + +} // 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 +ProcessMetrics* ProcessMetrics::CreateProcessMetrics( + ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) { + return new ProcessMetrics(process, port_provider); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.virtual_size; +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) + return 0; + return task_info_data.resident_size; +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +// This is a rough approximation of the algorithm that libtop uses. +// private_bytes is the size of private resident memory. +// shared_bytes is the size of shared resident memory. +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + kern_return_t kr; + size_t private_pages_count = 0; + size_t shared_pages_count = 0; + + if (!private_bytes && !shared_bytes) + return true; + + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) { + DLOG(ERROR) << "Invalid process"; + return false; + } + + cpu_type_t cpu_type; + if (!GetCPUTypeForProcess(process_, &cpu_type)) + return false; + + // The same region can be referenced multiple times. To avoid double counting + // we need to keep track of which regions we've already counted. + base::hash_set<int> seen_objects; + + // We iterate through each VM region in the task's address map. For shared + // memory we add up all the pages that are marked as shared. Like libtop we + // try to avoid counting pages that are also referenced by other tasks. Since + // we don't have access to the VM regions of other tasks the only hint we have + // is if the address is in the shared region area. + // + // Private memory is much simpler. We simply count the pages that are marked + // as private or copy on write (COW). + // + // See libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-67/libtop.c + mach_vm_size_t size = 0; + for (mach_vm_address_t address = MACH_VM_MIN_ADDRESS;; address += size) { + vm_region_top_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + mach_port_t object_name; + kr = mach_vm_region(task, + &address, + &size, + VM_REGION_TOP_INFO, + (vm_region_info_t)&info, + &info_count, + &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // We're at the end of the address space. + break; + } else if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Calling mach_vm_region failed with error: " + << mach_error_string(kr); + return false; + } + + if (IsAddressInSharedRegion(address, cpu_type) && + info.share_mode != SM_PRIVATE) + continue; + + if (info.share_mode == SM_COW && info.ref_count == 1) + info.share_mode = SM_PRIVATE; + + switch (info.share_mode) { + case SM_PRIVATE: + private_pages_count += info.private_pages_resident; + private_pages_count += info.shared_pages_resident; + break; + case SM_COW: + private_pages_count += info.private_pages_resident; + // Fall through + case SM_SHARED: + if (seen_objects.count(info.obj_id) == 0) { + // Only count the first reference to this region. + seen_objects.insert(info.obj_id); + shared_pages_count += info.shared_pages_resident; + } + break; + default: + break; + } + } + + vm_size_t page_size; + kr = host_page_size(task, &page_size); + if (kr != KERN_SUCCESS) { + DLOG(ERROR) << "Failed to fetch host page size, error: " + << mach_error_string(kr); + return false; + } + + if (private_bytes) + *private_bytes = private_pages_count * page_size; + if (shared_bytes) + *shared_bytes = shared_pages_count * page_size; + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + return true; +} + +#define TIME_VALUE_TO_TIMEVAL(a, r) do { \ + (r)->tv_sec = (a)->seconds; \ + (r)->tv_usec = (a)->microseconds; \ +} while (0) + +double ProcessMetrics::GetCPUUsage() { + mach_port_t task = TaskForPid(process_); + if (task == MACH_PORT_NULL) + return 0; + + kern_return_t kr; + + // 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; + 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 0; + } + + task_basic_info_64 task_info_data; + if (!GetTaskInfo(task, &task_info_data)) + return 0; + + /* 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); + + struct timeval now; + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + int64 task_time = TimeValToMicroseconds(task_timeval); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = task_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = task_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + last_system_time_ = task_time; + last_time_ = time; + + return static_cast<double>(system_time_delta * 100.0) / time_delta; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return false; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process, + ProcessMetrics::PortProvider* port_provider) + : process_(process), + last_time_(0), + last_system_time_(0), + port_provider_(port_provider) { + processor_count_ = SysInfo::NumberOfProcessors(); +} + +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::ScopedMachPort 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, HOST_VM_INFO, + reinterpret_cast<host_info_t>(&data), + &count); + if (kr) { + DLOG(WARNING) << "Failed to fetch host statistics."; + return 0; + } + + vm_size_t page_size; + kr = host_page_size(host, &page_size); + if (kr) { + DLOG(ERROR) << "Failed to fetch host page size."; + return 0; + } + + return (data.active_count * page_size) / 1024; +} + +} // namespace base diff --git a/chromium/base/process/process_metrics_openbsd.cc b/chromium/base/process/process_metrics_openbsd.cc new file mode 100644 index 00000000000..36f607c0a9c --- /dev/null +++ b/chromium/base/process/process_metrics_openbsd.cc @@ -0,0 +1,168 @@ +// 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> + +namespace base { + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + 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_vm_tsize + info.p_vm_dsize + info.p_vm_ssize); +} + +size_t ProcessMetrics::GetPeakPagefileUsage() const { + return 0; +} + +size_t ProcessMetrics::GetWorkingSetSize() const { + 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_vm_rssize * getpagesize(); +} + +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + WorkingSetKBytes ws_usage; + + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + if (private_bytes) + *private_bytes = ws_usage.priv << 10; + + if (shared_bytes) + *shared_bytes = ws_usage.shared * 1024; + + return true; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + // TODO(bapt): be sure we can't be precise + size_t priv = GetWorkingSetSize(); + if (!priv) + return false; + ws_usage->priv = priv / 1024; + ws_usage->shareable = 0; + ws_usage->shared = 0; + + return true; +} + +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::GetCPUUsage() { + struct timeval now; + + int retval = gettimeofday(&now, NULL); + if (retval) + return 0; + + int64 time = TimeValToMicroseconds(now); + + if (last_time_ == 0) { + // First call, just set the last values. + last_time_ = time; + last_cpu_ = GetProcessCPU(process_); + return 0; + } + + int64 time_delta = time - last_time_; + DCHECK_NE(time_delta, 0); + + if (time_delta == 0) + return 0; + + int cpu = GetProcessCPU(process_); + + last_time_ = time; + last_cpu_ = cpu; + + double percentage = static_cast<double>((cpu * 100.0) / FSCALE); + + return percentage; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + last_time_(0), + last_system_time_(0), + last_cpu_(0) { + + processor_count_ = base::SysInfo::NumberOfProcessors(); +} + +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/chromium/base/process/process_metrics_posix.cc b/chromium/base/process/process_metrics_posix.cc new file mode 100644 index 00000000000..531f6a40d70 --- /dev/null +++ b/chromium/base/process/process_metrics_posix.cc @@ -0,0 +1,55 @@ +// 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/resource.h> +#include <sys/time.h> + +#include "base/logging.h" + +namespace base { + +int64 TimeValToMicroseconds(const struct timeval& tv) { + static const int kMicrosecondsPerSecond = 1000000; + int64 ret = tv.tv_sec; // Avoid (int * int) integer overflow. + ret *= kMicrosecondsPerSecond; + ret += tv.tv_usec; + return ret; +} + +ProcessMetrics::~ProcessMetrics() { } + +#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_OPENBSD) +static const rlim_t kSystemDefaultMaxFds = 256; +#elif defined(OS_ANDROID) +static const rlim_t kSystemDefaultMaxFds = 1024; +#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); +} + +} // namespace base diff --git a/chromium/base/process/process_metrics_win.cc b/chromium/base/process/process_metrics_win.cc new file mode 100644 index 00000000000..f42ea86feb7 --- /dev/null +++ b/chromium/base/process/process_metrics_win.cc @@ -0,0 +1,315 @@ +// 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> +#include <psapi.h> + +#include "base/logging.h" +#include "base/sys_info.h" + +namespace base { + +// System pagesize. This value remains constant on x86/64 architectures. +const int PAGESIZE_KB = 4; + +ProcessMetrics::~ProcessMetrics() { } + +// static +ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { + return new ProcessMetrics(process); +} + +size_t ProcessMetrics::GetPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PagefileUsage; + } + return 0; +} + +// Returns the peak space allocated for the pagefile, in bytes. +size_t ProcessMetrics::GetPeakPagefileUsage() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakPagefileUsage; + } + return 0; +} + +// Returns the current working set size, in bytes. +size_t ProcessMetrics::GetWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.WorkingSetSize; + } + return 0; +} + +// Returns the peak working set size, in bytes. +size_t ProcessMetrics::GetPeakWorkingSetSize() const { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(process_, &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize; + } + return 0; +} + +bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, + size_t* shared_bytes) { + // PROCESS_MEMORY_COUNTERS_EX is not supported until XP SP2. + // GetProcessMemoryInfo() will simply fail on prior OS. So the requested + // information is simply not available. Hence, we will return 0 on unsupported + // OSes. Unlike most Win32 API, we don't need to initialize the "cb" member. + PROCESS_MEMORY_COUNTERS_EX pmcx; + if (private_bytes && + GetProcessMemoryInfo(process_, + reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmcx), + sizeof(pmcx))) { + *private_bytes = pmcx.PrivateUsage; + } + + if (shared_bytes) { + WorkingSetKBytes ws_usage; + if (!GetWorkingSetKBytes(&ws_usage)) + return false; + + *shared_bytes = ws_usage.shared * 1024; + } + + return true; +} + +void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { + MEMORY_BASIC_INFORMATION mbi = {0}; + size_t committed_private = 0; + size_t committed_mapped = 0; + size_t committed_image = 0; + void* base_address = NULL; + while (VirtualQueryEx(process_, base_address, &mbi, sizeof(mbi)) == + sizeof(mbi)) { + if (mbi.State == MEM_COMMIT) { + if (mbi.Type == MEM_PRIVATE) { + committed_private += mbi.RegionSize; + } else if (mbi.Type == MEM_MAPPED) { + committed_mapped += mbi.RegionSize; + } else if (mbi.Type == MEM_IMAGE) { + committed_image += mbi.RegionSize; + } else { + NOTREACHED(); + } + } + void* new_base = (static_cast<BYTE*>(mbi.BaseAddress)) + mbi.RegionSize; + // Avoid infinite loop by weird MEMORY_BASIC_INFORMATION. + // If we query 64bit processes in a 32bit process, VirtualQueryEx() + // returns such data. + if (new_base <= base_address) { + usage->image = 0; + usage->mapped = 0; + usage->priv = 0; + return; + } + base_address = new_base; + } + usage->image = committed_image / 1024; + usage->mapped = committed_mapped / 1024; + usage->priv = committed_private / 1024; +} + +bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { + size_t ws_private = 0; + size_t ws_shareable = 0; + size_t ws_shared = 0; + + DCHECK(ws_usage); + memset(ws_usage, 0, sizeof(*ws_usage)); + + DWORD number_of_entries = 4096; // Just a guess. + PSAPI_WORKING_SET_INFORMATION* buffer = NULL; + int retries = 5; + for (;;) { + DWORD buffer_size = sizeof(PSAPI_WORKING_SET_INFORMATION) + + (number_of_entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + + // if we can't expand the buffer, don't leak the previous + // contents or pass a NULL pointer to QueryWorkingSet + PSAPI_WORKING_SET_INFORMATION* new_buffer = + reinterpret_cast<PSAPI_WORKING_SET_INFORMATION*>( + realloc(buffer, buffer_size)); + if (!new_buffer) { + free(buffer); + return false; + } + buffer = new_buffer; + + // Call the function once to get number of items + if (QueryWorkingSet(process_, buffer, buffer_size)) + break; // Success + + if (GetLastError() != ERROR_BAD_LENGTH) { + free(buffer); + return false; + } + + number_of_entries = static_cast<DWORD>(buffer->NumberOfEntries); + + // Maybe some entries are being added right now. Increase the buffer to + // take that into account. + number_of_entries = static_cast<DWORD>(number_of_entries * 1.25); + + if (--retries == 0) { + free(buffer); // If we're looping, eventually fail. + return false; + } + } + + // 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<DWORD>(buffer->NumberOfEntries)); + for (unsigned int i = 0; i < number_of_entries; i++) { + if (buffer->WorkingSetInfo[i].Shared) { + ws_shareable++; + if (buffer->WorkingSetInfo[i].ShareCount > 1) + ws_shared++; + } else { + ws_private++; + } + } + + ws_usage->priv = ws_private * PAGESIZE_KB; + ws_usage->shareable = ws_shareable * PAGESIZE_KB; + ws_usage->shared = ws_shared * PAGESIZE_KB; + free(buffer); + return true; +} + +static uint64 FileTimeToUTC(const FILETIME& ftime) { + LARGE_INTEGER li; + li.LowPart = ftime.dwLowDateTime; + li.HighPart = ftime.dwHighDateTime; + return li.QuadPart; +} + +double ProcessMetrics::GetCPUUsage() { + FILETIME now; + FILETIME creation_time; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + + GetSystemTimeAsFileTime(&now); + + if (!GetProcessTimes(process_, &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 0; + } + int64 system_time = (FileTimeToUTC(kernel_time) + FileTimeToUTC(user_time)) / + processor_count_; + int64 time = FileTimeToUTC(now); + + if ((last_system_time_ == 0) || (last_time_ == 0)) { + // First call, just set the last values. + last_system_time_ = system_time; + last_time_ = time; + return 0; + } + + int64 system_time_delta = system_time - last_system_time_; + int64 time_delta = time - last_time_; + DCHECK_NE(0U, time_delta); + if (time_delta == 0) + return 0; + + // We add time_delta / 2 so the result is rounded. + int cpu = static_cast<int>((system_time_delta * 100 + time_delta / 2) / + time_delta); + + last_system_time_ = system_time; + last_time_ = time; + + return cpu; +} + +bool ProcessMetrics::CalculateFreeMemory(FreeMBytes* free) const { + const SIZE_T kTopAddress = 0x7F000000; + const SIZE_T kMegabyte = 1024 * 1024; + SIZE_T accumulated = 0; + + MEMORY_BASIC_INFORMATION largest = {0}; + UINT_PTR scan = 0; + while (scan < kTopAddress) { + MEMORY_BASIC_INFORMATION info; + if (!::VirtualQueryEx(process_, reinterpret_cast<void*>(scan), + &info, sizeof(info))) + return false; + if (info.State == MEM_FREE) { + accumulated += info.RegionSize; + if (info.RegionSize > largest.RegionSize) + largest = info; + } + scan += info.RegionSize; + } + free->largest = largest.RegionSize / kMegabyte; + free->largest_ptr = largest.BaseAddress; + free->total = accumulated / kMegabyte; + return true; +} + +bool ProcessMetrics::GetIOCounters(IoCounters* io_counters) const { + return GetProcessIoCounters(process_, io_counters) != FALSE; +} + +ProcessMetrics::ProcessMetrics(ProcessHandle process) + : process_(process), + processor_count_(base::SysInfo::NumberOfProcessors()), + last_time_(0), + last_system_time_(0) { +} + +// GetPerformanceInfo is not available on WIN2K. So we'll +// load it on-the-fly. +const wchar_t kPsapiDllName[] = L"psapi.dll"; +typedef BOOL (WINAPI *GetPerformanceInfoFunction) ( + PPERFORMANCE_INFORMATION pPerformanceInformation, + DWORD cb); + +// Beware of races if called concurrently from multiple threads. +static BOOL InternalGetPerformanceInfo( + PPERFORMANCE_INFORMATION pPerformanceInformation, DWORD cb) { + static GetPerformanceInfoFunction GetPerformanceInfo_func = NULL; + if (!GetPerformanceInfo_func) { + HMODULE psapi_dll = ::GetModuleHandle(kPsapiDllName); + if (psapi_dll) + GetPerformanceInfo_func = reinterpret_cast<GetPerformanceInfoFunction>( + GetProcAddress(psapi_dll, "GetPerformanceInfo")); + + if (!GetPerformanceInfo_func) { + // The function could be loaded! + memset(pPerformanceInformation, 0, cb); + return FALSE; + } + } + return GetPerformanceInfo_func(pPerformanceInformation, cb); +} + +size_t GetSystemCommitCharge() { + // Get the System Page Size. + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + PERFORMANCE_INFORMATION info; + if (!InternalGetPerformanceInfo(&info, sizeof(info))) { + DLOG(ERROR) << "Failed to fetch internal performance info."; + return 0; + } + return (info.CommitTotal * system_info.dwPageSize) / 1024; +} + +} // namespace base diff --git a/chromium/base/process/process_posix.cc b/chromium/base/process/process_posix.cc new file mode 100644 index 00000000000..3d8b31d0352 --- /dev/null +++ b/chromium/base/process/process_posix.cc @@ -0,0 +1,73 @@ +// 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 <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/process/kill.h" + +namespace base { + +// static +Process Process::Current() { + return Process(GetCurrentProcessHandle()); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcessHandle(); +} + +void Process::Close() { + process_ = 0; + // 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. +} + +void Process::Terminate(int result_code) { + // result_code isn't supportable. + if (!process_) + return; + // We don't wait here. It's the responsibility of other code to reap the + // child. + KillProcess(process_, result_code, false); +} + +#if !defined(OS_LINUX) +bool Process::IsProcessBackgrounded() const { + // See SetProcessBackgrounded(). + return false; +} + +bool Process::SetProcessBackgrounded(bool value) { + // POSIX only allows lowering the priority of a process, so if we + // were to lower it we wouldn't be able to raise it back to its initial + // priority. + return false; +} + +// static +bool Process::CanBackgroundProcesses() { + return false; +} + +#endif + +int Process::GetPriority() const { + DCHECK(process_); + return getpriority(PRIO_PROCESS, process_); +} + +} // namspace base diff --git a/chromium/base/process/process_util_unittest.cc b/chromium/base/process/process_util_unittest.cc new file mode 100644 index 00000000000..77f058c8f26 --- /dev/null +++ b/chromium/base/process/process_util_unittest.cc @@ -0,0 +1,961 @@ +// 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_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.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/utf_string_conversions.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/threading/thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +#if defined(OS_LINUX) +#include <glib.h> +#include <malloc.h> +#include <sched.h> +#endif +#if defined(OS_POSIX) +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/wait.h> +#endif +#if defined(OS_WIN) +#include <windows.h> +#endif +#if defined(OS_MACOSX) +#include <mach/vm_param.h> +#include <malloc/malloc.h> +#endif + +using base::FilePath; + +namespace { + +#if defined(OS_WIN) +const wchar_t kProcessName[] = L"base_unittests.exe"; +#else +const wchar_t kProcessName[] = L"base_unittests"; +#endif // defined(OS_WIN) + +#if defined(OS_ANDROID) +const char kShellPath[] = "/system/bin/sh"; +const char kPosixShell[] = "sh"; +#else +const char kShellPath[] = "/bin/sh"; +const char kPosixShell[] = "bash"; +#endif + +const char kSignalFileSlow[] = "SlowChildProcess.die"; +const char kSignalFileCrash[] = "CrashingChildProcess.die"; +const char kSignalFileKill[] = "KilledChildProcess.die"; + +#if defined(OS_WIN) +const int kExpectedStillRunningExitCode = 0x102; +const int kExpectedKilledExitCode = 1; +#else +const int kExpectedStillRunningExitCode = 0; +#endif + +// Sleeps until file filename is created. +void WaitToDie(const char* filename) { + FILE* fp; + do { + base::PlatformThread::Sleep(base::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. +base::TerminationStatus WaitForChildTermination(base::ProcessHandle handle, + int* exit_code) { + // Now we wait until the result is something other than STILL_RUNNING. + base::TerminationStatus status = base::TERMINATION_STATUS_STILL_RUNNING; + const base::TimeDelta kInterval = base::TimeDelta::FromMilliseconds(20); + base::TimeDelta waited; + do { + status = base::GetTerminationStatus(handle, exit_code); + base::PlatformThread::Sleep(kInterval); + waited += kInterval; + } while (status == base::TERMINATION_STATUS_STILL_RUNNING && +// Waiting for more time for process termination on android devices. +#if defined(OS_ANDROID) + waited < TestTimeouts::large_test_timeout()); +#else + waited < TestTimeouts::action_max_timeout()); +#endif + + return status; +} + +} // namespace + +class ProcessUtilTest : public base::MultiProcessTest { + public: +#if defined(OS_POSIX) + // 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); +}; + +std::string ProcessUtilTest::GetSignalFilePath(const char* filename) { +#if !defined(OS_ANDROID) + return filename; +#else + FilePath tmp_dir; + PathService::Get(base::DIR_CACHE, &tmp_dir); + tmp_dir = tmp_dir.Append(filename); + return tmp_dir.value(); +#endif +} + +MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { + return 0; +} + +TEST_F(ProcessUtilTest, SpawnChild) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); +} + +MULTIPROCESS_TEST_MAIN(SlowChildProcess) { + WaitToDie(ProcessUtilTest::GetSignalFilePath(kSignalFileSlow).c_str()); + return 0; +} + +TEST_F(ProcessUtilTest, KillSlowChild) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + SignalChildren(signal_file.c_str()); + EXPECT_TRUE(base::WaitForSingleProcess( + handle, TestTimeouts::action_max_timeout())); + base::CloseProcessHandle(handle); + 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()); + base::ProcessHandle handle = this->SpawnChild("SlowChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_NORMAL_TERMINATION, status); + EXPECT_EQ(0, exit_code); + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +#if defined(OS_WIN) +// TODO(cpu): figure out how to test this in other platforms. +TEST_F(ProcessUtilTest, GetProcId) { + base::ProcessId id1 = base::GetProcId(GetCurrentProcess()); + EXPECT_NE(0ul, id1); + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + ASSERT_NE(base::kNullProcessHandle, handle); + base::ProcessId id2 = base::GetProcId(handle); + EXPECT_NE(0ul, id2); + EXPECT_NE(id1, id2); + base::CloseProcessHandle(handle); +} +#endif + +#if !defined(OS_MACOSX) +// 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. + +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 = NULL; + *oops = 0xDEAD; + return 1; +} + +// This test intentionally crashes, so we don't need to run it under +// AddressSanitizer. +// TODO(jschuh): crbug.com/175753 Fix this in Win64 bots. +#if defined(ADDRESS_SANITIZER) || (defined(OS_WIN) && defined(ARCH_CPU_X86_64)) +#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()); + base::ProcessHandle handle = this->SpawnChild("CrashingChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_CRASHED, status); + +#if defined(OS_WIN) + EXPECT_EQ(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 + base::CloseProcessHandle(handle); + + // Reset signal handlers back to "normal". + base::debug::EnableInProcessStackDumping(); + remove(signal_file.c_str()); +} +#endif // !defined(OS_MACOSX) + +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); +#endif + return 1; +} + +TEST_F(ProcessUtilTest, GetTerminationStatusKill) { + const std::string signal_file = + ProcessUtilTest::GetSignalFilePath(kSignalFileKill); + remove(signal_file.c_str()); + base::ProcessHandle handle = this->SpawnChild("KilledChildProcess", + false); + ASSERT_NE(base::kNullProcessHandle, handle); + + int exit_code = 42; + EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, + base::GetTerminationStatus(handle, &exit_code)); + EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); + + SignalChildren(signal_file.c_str()); + exit_code = 42; + base::TerminationStatus status = + WaitForChildTermination(handle, &exit_code); + EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_WAS_KILLED, status); +#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 + base::CloseProcessHandle(handle); + remove(signal_file.c_str()); +} + +// 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(ProcessUtilTest, SetProcessBackgrounded) { + base::ProcessHandle handle = this->SpawnChild("SimpleChildProcess", false); + base::Process process(handle); + 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()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#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(ProcessUtilTest, SetProcessBackgroundedSelf) { + base::Process process(base::Process::Current().handle()); + 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()); +#else + process.SetProcessBackgrounded(true); + process.SetProcessBackgrounded(false); +#endif + int new_priority = process.GetPriority(); + EXPECT_EQ(old_priority, new_priority); +} + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST_F(ProcessUtilTest, GetSystemMemoryInfo) { + base::SystemMemoryInfoKB info; + EXPECT_TRUE(base::GetSystemMemoryInfo(&info)); + + // Ensure each field received a value. + EXPECT_GT(info.total, 0); + EXPECT_GT(info.free, 0); + 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); + + // All the values should be less than the total amount of memory. + EXPECT_LT(info.free, info.total); + 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); + +#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_LINUX) || defined(OS_ANDROID) + +// TODO(estade): if possible, port these 2 tests. +#if defined(OS_WIN) +TEST_F(ProcessUtilTest, CalcFreeMemory) { + scoped_ptr<base::ProcessMetrics> metrics( + base::ProcessMetrics::CreateProcessMetrics(::GetCurrentProcess())); + ASSERT_TRUE(NULL != metrics.get()); + + bool using_tcmalloc = false; + + // Detect if we are using tcmalloc +#if !defined(NO_TCMALLOC) + const char* chrome_allocator = getenv("CHROME_ALLOCATOR"); + if (!chrome_allocator || _stricmp(chrome_allocator, "tcmalloc") == 0) + using_tcmalloc = true; +#endif + + // Typical values here is ~1900 for total and ~1000 for largest. Obviously + // it depends in what other tests have done to this process. + base::FreeMBytes free_mem1 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem1)); + EXPECT_LT(10u, free_mem1.total); + EXPECT_LT(10u, free_mem1.largest); + EXPECT_GT(2048u, free_mem1.total); + EXPECT_GT(2048u, free_mem1.largest); + EXPECT_GE(free_mem1.total, free_mem1.largest); + EXPECT_TRUE(NULL != free_mem1.largest_ptr); + + // Allocate 20M and check again. It should have gone down. + const int kAllocMB = 20; + scoped_ptr<char[]> alloc(new char[kAllocMB * 1024 * 1024]); + size_t expected_total = free_mem1.total - kAllocMB; + size_t expected_largest = free_mem1.largest; + + base::FreeMBytes free_mem2 = {0}; + EXPECT_TRUE(metrics->CalculateFreeMemory(&free_mem2)); + EXPECT_GE(free_mem2.total, free_mem2.largest); + // This test is flaky when using tcmalloc, because tcmalloc + // allocation strategy sometimes results in less than the + // full drop of 20Mb of free memory. + if (!using_tcmalloc) + EXPECT_GE(expected_total, free_mem2.total); + EXPECT_GE(expected_largest, free_mem2.largest); + EXPECT_TRUE(NULL != free_mem2.largest_ptr); +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + // Let's create a decently long message. + std::string message; + for (int i = 0; i < 1025; i++) { // 1025 so it does not end on a kilo-byte + // boundary. + message += "Hello!"; + } + // cmd.exe's echo always adds a \r\n to its output. + std::string expected(message); + expected += "\r\n"; + + FilePath cmd(L"cmd.exe"); + CommandLine cmd_line(cmd); + cmd_line.AppendArg("/c"); + cmd_line.AppendArg("echo " + message + ""); + std::string output; + ASSERT_TRUE(base::GetAppOutput(cmd_line, &output)); + EXPECT_EQ(expected, output); + + // Let's make sure stderr is ignored. + CommandLine other_cmd_line(cmd); + other_cmd_line.AppendArg("/c"); + // http://msdn.microsoft.com/library/cc772622.aspx + cmd_line.AppendArg("echo " + message + " >&2"); + output.clear(); + ASSERT_TRUE(base::GetAppOutput(other_cmd_line, &output)); + EXPECT_EQ("", output); +} + +TEST_F(ProcessUtilTest, LaunchAsUser) { + base::UserTokenHandle token; + ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); + std::wstring cmdline = + this->MakeCmdLine("SimpleChildProcess", false).GetCommandLineString(); + base::LaunchOptions options; + options.as_user = token; + EXPECT_TRUE(base::LaunchProcess(cmdline, options, NULL)); +} + +#endif // defined(OS_WIN) + +#if defined(OS_POSIX) + +namespace { + +// Returns the maximum number of files that a process can have open. +// Returns 0 on error. +int GetMaxFilesOpenInProcess() { + struct rlimit rlim; + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { + return 0; + } + + // rlim_t is a uint64 - 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>::max()); + if (rlim.rlim_cur > max_int) { + return max_int; + } + + return rlim.rlim_cur; +} + +const int kChildPipe = 20; // FD # for write end of pipe in child process. + +} // 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 (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 = HANDLE_EINTR(close(write_pipe)); + DPCHECK(ret == 0); + + return 0; +} + +int ProcessUtilTest::CountOpenFDsInChild() { + int fds[2]; + if (pipe(fds) < 0) + NOTREACHED(); + + base::FileHandleMappingVector fd_mapping_vec; + fd_mapping_vec.push_back(std::pair<int, int>(fds[1], kChildPipe)); + base::ProcessHandle handle = this->SpawnChild( + "ProcessUtilsLeakFDChildProcess", fd_mapping_vec, false); + CHECK(handle); + int ret = HANDLE_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) || defined(USE_HEAPCHECKER) + // Compiler-based ThreadSanitizer makes this test slow. + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(3))); +#else + CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(1))); +#endif + base::CloseProcessHandle(handle); + ret = HANDLE_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 +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); + int sockets[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + + int fds_after = CountOpenFDsInChild(); + + ASSERT_EQ(fds_after, fds_before); + + int ret; + ret = HANDLE_EINTR(close(sockets[0])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(sockets[1])); + DPCHECK(ret == 0); + ret = HANDLE_EINTR(close(dev_null)); + DPCHECK(ret == 0); +} + +namespace { + +std::string TestLaunchProcess(const base::EnvironmentVector& env_changes, + const int clone_flags) { + std::vector<std::string> args; + base::FileHandleMappingVector fds_to_remap; + + args.push_back(kPosixShell); + args.push_back("-c"); + args.push_back("echo $BASE_TEST"); + + int fds[2]; + PCHECK(pipe(fds) == 0); + + fds_to_remap.push_back(std::make_pair(fds[1], 1)); + base::LaunchOptions options; + options.wait = true; + options.environ = &env_changes; + options.fds_to_remap = &fds_to_remap; +#if defined(OS_LINUX) + options.clone_flags = clone_flags; +#else + CHECK_EQ(0, clone_flags); +#endif // OS_LINUX + EXPECT_TRUE(base::LaunchProcess(args, options, NULL)); + PCHECK(HANDLE_EINTR(close(fds[1])) == 0); + + char buf[512]; + const ssize_t n = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); + PCHECK(n > 0); + + PCHECK(HANDLE_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) { + base::EnvironmentVector env_changes; + const int no_clone_flags = 0; + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("bar"))); + EXPECT_EQ("bar\n", TestLaunchProcess(env_changes, no_clone_flags)); + env_changes.clear(); + + EXPECT_EQ(0, setenv("BASE_TEST", "testing", 1 /* override */)); + EXPECT_EQ("testing\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back( + std::make_pair(std::string("BASE_TEST"), std::string())); + EXPECT_EQ("\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes[0].second = "foo"; + EXPECT_EQ("foo\n", TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.clear(); + EXPECT_EQ(0, setenv("BASE_TEST", kLargeString, 1 /* override */)); + EXPECT_EQ(std::string(kLargeString) + "\n", + TestLaunchProcess(env_changes, no_clone_flags)); + + env_changes.push_back(std::make_pair(std::string("BASE_TEST"), + std::string("wibble"))); + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, no_clone_flags)); + +#if defined(OS_LINUX) + // Test a non-trival value for clone_flags. + // Don't test on Valgrind as it has limited support for clone(). + if (!RunningOnValgrind()) { + EXPECT_EQ("wibble\n", TestLaunchProcess(env_changes, CLONE_FS | SIGCHLD)); + } +#endif +} + +TEST_F(ProcessUtilTest, AlterEnvironment) { + const char* const empty[] = { NULL }; + const char* const a2[] = { "A=2", NULL }; + base::EnvironmentVector changes; + char** e; + + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, empty); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string())); + e = base::AlterEnvironment(changes, empty); + EXPECT_TRUE(e[0] == NULL); + delete[] e; + + changes.clear(); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=2"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string("1"))); + e = base::AlterEnvironment(changes, a2); + EXPECT_EQ(std::string("A=1"), e[0]); + EXPECT_TRUE(e[1] == NULL); + delete[] e; + + changes.clear(); + changes.push_back(std::make_pair(std::string("A"), std::string())); + e = base::AlterEnvironment(changes, a2); + EXPECT_TRUE(e[0] == NULL); + delete[] e; +} + +TEST_F(ProcessUtilTest, GetAppOutput) { + std::string output; + +#if defined(OS_ANDROID) + std::vector<std::string> argv; + argv.push_back("sh"); // Instead of /bin/sh, force path search to find it. + argv.push_back("-c"); + + argv.push_back("exit 0"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; + EXPECT_FALSE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "echo foobar42"; + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42\n", output.c_str()); +#else + EXPECT_TRUE(base::GetAppOutput(CommandLine(FilePath("true")), &output)); + EXPECT_STREQ("", output.c_str()); + + EXPECT_FALSE(base::GetAppOutput(CommandLine(FilePath("false")), &output)); + + std::vector<std::string> argv; + argv.push_back("/bin/echo"); + argv.push_back("-n"); + argv.push_back("foobar42"); + EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_STREQ("foobar42", output.c_str()); +#endif // defined(OS_ANDROID) +} + +TEST_F(ProcessUtilTest, GetAppOutputRestricted) { + // Unfortunately, since we can't rely on the path, we need to know where + // everything is. So let's use /bin/sh, which is on every POSIX system, and + // its built-ins. + std::vector<std::string> argv; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + + // On success, should set |output|. We use |/bin/sh -c 'exit 0'| instead of + // |true| since the location of the latter may be |/bin| or |/usr/bin| (and we + // need absolute paths). + argv.push_back("exit 0"); // argv[2]; equivalent to "true" + std::string output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("", output.c_str()); + + argv[2] = "exit 1"; // equivalent to "false" + output = "before"; + EXPECT_FALSE(base::GetAppOutputRestricted(CommandLine(argv), + &output, 100)); + EXPECT_STREQ("", output.c_str()); + + // Amount of output exactly equal to space allowed. + argv[2] = "echo 123456789"; // (the sh built-in doesn't take "-n") + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Amount of output greater than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 5)); + EXPECT_STREQ("12345", output.c_str()); + + // Amount of output less than space allowed. + output.clear(); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 15)); + EXPECT_STREQ("123456789\n", output.c_str()); + + // Zero space allowed. + output = "abc"; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 0)); + EXPECT_STREQ("", output.c_str()); +} + +#if !defined(OS_MACOSX) && !defined(OS_OPENBSD) +// TODO(benwells): GetAppOutputRestricted should terminate applications +// with SIGPIPE when we have enough output. http://crbug.com/88502 +TEST_F(ProcessUtilTest, GetAppOutputRestrictedSIGPIPE) { + std::vector<std::string> argv; + std::string output; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); +#if defined(OS_ANDROID) + argv.push_back("while echo 12345678901234567890; do :; done"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); +#else + argv.push_back("yes"); + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("y\ny\ny\ny\ny\n", output.c_str()); +#endif +} +#endif + +TEST_F(ProcessUtilTest, GetAppOutputRestrictedNoZombies) { + std::vector<std::string> argv; + + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo 123456789012345678901234567890"); // argv[2] + + // Run |GetAppOutputRestricted()| 300 (> default per-user processes on Mac OS + // 10.5) times with an output buffer big enough to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 100)); + EXPECT_STREQ("123456789012345678901234567890\n", output.c_str()); + } + + // Ditto, but with an output buffer too small to capture all output. + for (int i = 0; i < 300; i++) { + std::string output; + EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_STREQ("1234567890", output.c_str()); + } +} + +TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) { + // Test getting output from a successful application. + std::vector<std::string> argv; + std::string output; + int exit_code; + argv.push_back(std::string(kShellPath)); // argv[0] + argv.push_back("-c"); // argv[1] + argv.push_back("echo foo"); // argv[2]; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 0); + + // Test getting output from an application which fails with a specific exit + // code. + output.clear(); + argv[2] = "echo foo; exit 2"; + EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + &exit_code)); + EXPECT_STREQ("foo\n", output.c_str()); + EXPECT_EQ(exit_code, 2); +} + +TEST_F(ProcessUtilTest, GetParentProcessId) { + base::ProcessId ppid = base::GetParentProcessId(base::GetCurrentProcId()); + EXPECT_EQ(ppid, getppid()); +} + +#if defined(OS_LINUX) || defined(OS_ANDROID) +TEST_F(ProcessUtilTest, 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, base::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, base::ParseProcStatCPU(kSelfStat)); +} + +// Disable on Android because base_unittests runs inside a Dalvik VM that +// starts and stop threads (crbug.com/175563). +#if !defined(OS_ANDROID) +TEST_F(ProcessUtilTest, GetNumberOfThreads) { + const base::ProcessHandle current = base::GetCurrentProcessHandle(); + const int initial_threads = base::GetNumberOfThreads(current); + ASSERT_GT(initial_threads, 0); + const int kNumAdditionalThreads = 10; + { + scoped_ptr<base::Thread> my_threads[kNumAdditionalThreads]; + for (int i = 0; i < kNumAdditionalThreads; ++i) { + my_threads[i].reset(new base::Thread("GetNumberOfThreadsTest")); + my_threads[i]->Start(); + ASSERT_EQ(base::GetNumberOfThreads(current), initial_threads + 1 + i); + } + } + // The Thread destructor will stop them. + ASSERT_EQ(initial_threads, base::GetNumberOfThreads(current)); +} +#endif // !defined(OS_ANDROID) + +#endif // defined(OS_LINUX) || defined(OS_ANDROID) + +// TODO(port): port those unit tests. +bool IsProcessDead(base::ProcessHandle child) { + // waitpid() will actually reap the process which is exactly NOT what we + // want to test for. The good thing is that if it can't find the process + // we'll get a nice value for errno which we can test for. + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + return result == -1 && errno == ECHILD; +} + +TEST_F(ProcessUtilTest, DelayedTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_never_die", false); + ASSERT_TRUE(child_process); + base::EnsureProcessTerminated(child_process); + base::WaitForSingleProcess(child_process, base::TimeDelta::FromSeconds(5)); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_never_die) { + while (1) { + sleep(500); + } + return 0; +} + +TEST_F(ProcessUtilTest, ImmediateTermination) { + base::ProcessHandle child_process = + SpawnChild("process_util_test_die_immediately", false); + ASSERT_TRUE(child_process); + // Give it time to die. + sleep(2); + base::EnsureProcessTerminated(child_process); + + // Check that process was really killed. + EXPECT_TRUE(IsProcessDead(child_process)); + base::CloseProcessHandle(child_process); +} + +MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) { + return 0; +} + +#endif // defined(OS_POSIX) diff --git a/chromium/base/process/process_util_unittest_ios.cc b/chromium/base/process/process_util_unittest_ios.cc new file mode 100644 index 00000000000..cad0f1b09f0 --- /dev/null +++ b/chromium/base/process/process_util_unittest_ios.cc @@ -0,0 +1,15 @@ +// 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/memory/scoped_ptr.h" +#include "base/process/process_metrics.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ProcessUtilTestIos, Memory) { + scoped_ptr<base::ProcessMetrics> process_metrics( + base::ProcessMetrics::CreateProcessMetrics( + base::GetCurrentProcessHandle())); + + ASSERT_NE(0u, process_metrics->GetWorkingSetSize()); +} diff --git a/chromium/base/process/process_win.cc b/chromium/base/process/process_win.cc new file mode 100644 index 00000000000..1217b509896 --- /dev/null +++ b/chromium/base/process/process_win.cc @@ -0,0 +1,92 @@ +// 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/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/windows_version.h" + +namespace base { + +void Process::Close() { + if (!process_) + return; + + // Don't call CloseHandle on a pseudo-handle. + if (process_ != ::GetCurrentProcess()) + ::CloseHandle(process_); + + process_ = NULL; +} + +void Process::Terminate(int result_code) { + if (!process_) + return; + + // Call NtTerminateProcess directly, without going through the import table, + // which might have been hooked with a buggy replacement by third party + // software. http://crbug.com/81449. + HMODULE module = GetModuleHandle(L"ntdll.dll"); + typedef UINT (WINAPI *TerminateProcessPtr)(HANDLE handle, UINT code); + TerminateProcessPtr terminate_process = reinterpret_cast<TerminateProcessPtr>( + GetProcAddress(module, "NtTerminateProcess")); + terminate_process(process_, result_code); +} + +bool Process::IsProcessBackgrounded() const { + if (!process_) + return false; // Failure case. + 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) { + if (!process_) + return false; + // 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 ((base::win::GetVersion() >= base::win::VERSION_VISTA) && + (process_ == ::GetCurrentProcess())) { + priority = value ? PROCESS_MODE_BACKGROUND_BEGIN : + PROCESS_MODE_BACKGROUND_END; + } else { + priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + } + + return (::SetPriorityClass(process_, priority) != 0); +} + +ProcessId Process::pid() const { + if (process_ == 0) + return 0; + + return GetProcId(process_); +} + +bool Process::is_current() const { + return process_ == GetCurrentProcess(); +} + +// static +Process Process::Current() { + return Process(::GetCurrentProcess()); +} + +// static +bool Process::CanBackgroundProcesses() { + return true; +} + +int Process::GetPriority() const { + DCHECK(process_); + return ::GetPriorityClass(process_); +} + +} // namespace base |