diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-06-18 14:10:49 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com> | 2015-06-18 13:53:24 +0000 |
commit | 813fbf95af77a531c57a8c497345ad2c61d475b3 (patch) | |
tree | 821b2c8de8365f21b6c9ba17a236fb3006a1d506 /chromium/base/process | |
parent | af6588f8d723931a298c995fa97259bb7f7deb55 (diff) |
BASELINE: Update chromium to 44.0.2403.47
Change-Id: Ie056fedba95cf5e5c76b30c4b2c80fca4764aa2f
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Diffstat (limited to 'chromium/base/process')
38 files changed, 1563 insertions, 1190 deletions
diff --git a/chromium/base/process/BUILD.gn b/chromium/base/process/BUILD.gn new file mode 100644 index 00000000000..814459b13e0 --- /dev/null +++ b/chromium/base/process/BUILD.gn @@ -0,0 +1,108 @@ +# Copyright (c) 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("process") { + sources = [ + "internal_linux.cc", + "internal_linux.h", + "kill.cc", + "kill.h", + "kill_mac.cc", + "kill_posix.cc", + "kill_win.cc", + "launch.cc", + "launch.h", + "launch_ios.cc", + "launch_mac.cc", + "launch_posix.cc", + "launch_win.cc", + "memory.cc", + "memory.h", + "memory_linux.cc", + "memory_mac.mm", + "memory_win.cc", + "process.h", + "process_handle_freebsd.cc", + "process_handle_linux.cc", + "process_handle_mac.cc", + "process_handle_openbsd.cc", + "process_handle_posix.cc", + "process_handle_win.cc", + "process_info.h", + "process_info_linux.cc", + "process_info_mac.cc", + "process_info_win.cc", + "process_iterator.cc", + "process_iterator.h", + "process_iterator_freebsd.cc", + "process_iterator_linux.cc", + "process_iterator_mac.cc", + "process_iterator_openbsd.cc", + "process_iterator_win.cc", + "process_linux.cc", + "process_mac.cc", + "process_metrics.cc", + "process_metrics.h", + "process_metrics_freebsd.cc", + "process_metrics_ios.cc", + "process_metrics_linux.cc", + "process_metrics_mac.cc", + "process_metrics_openbsd.cc", + "process_metrics_posix.cc", + "process_metrics_win.cc", + "process_posix.cc", + "process_win.cc", + ] + + sources -= [ + "process_handle_freebsd.cc", + "process_handle_openbsd.cc", + "process_iterator_freebsd.cc", + "process_iterator_openbsd.cc", + "process_metrics_freebsd.cc", + "process_metrics_openbsd.cc", + ] + + if (is_android) { + # Android uses some Linux sources, put those back. + set_sources_assignment_filter([]) + sources += [ + "internal_linux.cc", + "memory_linux.cc", + "process_handle_linux.cc", + "process_iterator_linux.cc", + "process_metrics_linux.cc", + ] + set_sources_assignment_filter(sources_assignment_filter) + } + + if (is_nacl) { + sources -= [ + "kill.cc", + "kill.h", + "kill_posix.cc", + "launch.cc", + "launch.h", + "launch_posix.cc", + "memory.cc", + "memory.h", + "process_iterator.cc", + "process_iterator.h", + "process_metrics.cc", + "process_metrics_posix.cc", + "process_posix.cc", + ] + } + + configs += [ "//base:base_implementation" ] + + deps = [ + "//base/memory", + "//base/third_party/dynamic_annotations", + ] + + allow_circular_includes_from = [ "//base/memory" ] + + visibility = [ "//base/*" ] +} diff --git a/chromium/base/process/internal_linux.cc b/chromium/base/process/internal_linux.cc index ed7d7f4cf5e..d2e9ec52e53 100644 --- a/chromium/base/process/internal_linux.cc +++ b/chromium/base/process/internal_linux.cc @@ -106,12 +106,10 @@ bool ParseProcStats(const std::string& stats_data, 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; + base::StringPairs 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); + output->insert(key_value_pairs[i]); } } diff --git a/chromium/base/process/internal_linux.h b/chromium/base/process/internal_linux.h index 5fc33566607..1837f94ce52 100644 --- a/chromium/base/process/internal_linux.h +++ b/chromium/base/process/internal_linux.h @@ -5,8 +5,8 @@ // 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_ +#ifndef BASE_PROCESS_INTERNAL_LINUX_H_ +#define BASE_PROCESS_INTERNAL_LINUX_H_ #include <unistd.h> @@ -87,4 +87,4 @@ TimeDelta ClockTicksToTimeDelta(int clock_ticks); } // namespace internal } // namespace base -#endif // BASE_PROCESS_LINUX_INTERNAL_H_ +#endif // BASE_PROCESS_INTERNAL_LINUX_H_ diff --git a/chromium/base/process/kill.cc b/chromium/base/process/kill.cc index caca3484a11..5d8ba6a2d75 100644 --- a/chromium/base/process/kill.cc +++ b/chromium/base/process/kill.cc @@ -14,11 +14,8 @@ bool KillProcesses(const FilePath::StringType& executable_name, 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 + Process process = Process::Open(entry->pid()); + result &= process.Terminate(exit_code, true); } return result; } diff --git a/chromium/base/process/kill.h b/chromium/base/process/kill.h index de72d7a8dca..af00b03d984 100644 --- a/chromium/base/process/kill.h +++ b/chromium/base/process/kill.h @@ -9,6 +9,7 @@ #define BASE_PROCESS_KILL_H_ #include "base/files/file_path.h" +#include "base/process/process.h" #include "base/process/process_handle.h" #include "base/time/time.h" @@ -44,24 +45,12 @@ 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 @@ -93,22 +82,6 @@ BASE_EXPORT TerminationStatus GetKnownDeadTerminationStatus( 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. @@ -118,12 +91,6 @@ BASE_EXPORT bool WaitForProcessesToExit( 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 @@ -146,15 +113,15 @@ BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, // 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. +// NOTE: The process must have been opened with the PROCESS_TERMINATE and +// SYNCHRONIZE permissions. // -BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); +BASE_EXPORT void EnsureProcessTerminated(Process process); #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); +// wait for |pid| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessId pid); #endif } // namespace base diff --git a/chromium/base/process/kill_mac.cc b/chromium/base/process/kill_mac.cc index 9257ee6929a..a4e0a14cf8f 100644 --- a/chromium/base/process/kill_mac.cc +++ b/chromium/base/process/kill_mac.cc @@ -66,8 +66,8 @@ void BlockingReap(pid_t child) { // 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); + DCHECK_GT(child, 0); + DCHECK_GT(timeout, 0); // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that // |child| has been reaped. Specifically, even if a kqueue, kevent, or other @@ -165,8 +165,8 @@ void WaitForChildToDie(pid_t child, int timeout) { } // namespace -void EnsureProcessTerminated(ProcessHandle process) { - WaitForChildToDie(process, kWaitBeforeKillSeconds); +void EnsureProcessTerminated(Process process) { + WaitForChildToDie(process.Pid(), kWaitBeforeKillSeconds); } } // namespace base diff --git a/chromium/base/process/kill_posix.cc b/chromium/base/process/kill_posix.cc index d17e9907626..0e303c67d3c 100644 --- a/chromium/base/process/kill_posix.cc +++ b/chromium/base/process/kill_posix.cc @@ -15,77 +15,12 @@ #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 { -#if !defined(OS_NACL_NONSFI) -bool WaitpidWithTimeout(ProcessHandle handle, - int* status, - base::TimeDelta wait) { - // This POSIX version of this function only guarantees that we wait no less - // than |wait| for the process to exit. The child process may - // exit sometime before the timeout has ended but we may still block for up - // to 256 milliseconds after the fact. - // - // waitpid() has no direct support on POSIX for specifying a timeout, you can - // either ask it to block indefinitely or return immediately (WNOHANG). - // When a child process terminates a SIGCHLD signal is sent to the parent. - // Catching this signal would involve installing a signal handler which may - // affect other parts of the application and would be difficult to debug. - // - // Our strategy is to call waitpid() once up front to check if the process - // has already exited, otherwise to loop for |wait|, sleeping for - // at most 256 milliseconds each time using usleep() and then calling - // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and - // we double it every 4 sleep cycles. - // - // usleep() is speced to exit if a signal is received for which a handler - // has been installed. This means that when a SIGCHLD is sent, it will exit - // depending on behavior external to this function. - // - // This function is used primarily for unit tests, if we want to use it in - // the application itself it would probably be best to examine other routes. - - if (wait.InMilliseconds() == base::kNoTimeout) { - return HANDLE_EINTR(waitpid(handle, status, 0)) > 0; - } - - 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() + wait; - 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; - } - } - - return ret_pid > 0; -} -#endif // !defined(OS_NACL_NONSFI) - TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, bool can_block, int* exit_code) { @@ -133,61 +68,6 @@ TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, } // namespace #if !defined(OS_NACL_NONSFI) -// 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) @@ -211,173 +91,29 @@ TerminationStatus GetKnownDeadTerminationStatus(ProcessHandle handle, } #if !defined(OS_NACL_NONSFI) -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) { - int status; - if (!WaitpidWithTimeout(handle, &status, timeout)) - 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, + 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; + TimeTicks end_time = 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()); + PlatformThread::Sleep(TimeDelta::FromMilliseconds(100)); + } while ((end_time - TimeTicks::Now()) > 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()); - - ScopedFD kq(kqueue()); - if (!kq.is_valid()) { - DPLOG(ERROR) << "kqueue"; - return false; - } - - struct kevent change = {0}; - EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); - int result = HANDLE_EINTR(kevent(kq.get(), &change, 1, NULL, 0, NULL)); - if (result == -1) { - if (errno == ESRCH) { - // If the process wasn't found, it must be dead. - return true; - } - - DPLOG(ERROR) << "kevent (setup " << handle << ")"; - return false; - } - - // Keep track of the elapsed time to be able to restart kevent if it's - // interrupted. - bool wait_forever = wait.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.get(), NULL, 0, &event, 1, remaining_timespec_ptr); - - if (result == -1 && errno == EINTR) { - if (!wait_forever) { - remaining_delta = deadline - base::TimeTicks::Now(); - } - result = 0; - } else { - break; - } - } - - 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 = GetCurrentProcessHandle(); - 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 - } - - int status; - if (!WaitpidWithTimeout(handle, &status, wait)) - return false; - return WIFEXITED(status); -} - bool CleanupProcesses(const FilePath::StringType& executable_name, - base::TimeDelta wait, + TimeDelta wait, int exit_code, const ProcessFilter* filter) { bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); @@ -463,22 +199,22 @@ class BackgroundReaper : public PlatformThread::Delegate { } // namespace -void EnsureProcessTerminated(ProcessHandle process) { +void EnsureProcessTerminated(Process process) { // If the child is already dead, then there's nothing to do. - if (IsChildDead(process)) + if (IsChildDead(process.Pid())) return; const unsigned timeout = 2; // seconds - BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + BackgroundReaper* reaper = new BackgroundReaper(process.Pid(), timeout); PlatformThread::CreateNonJoinable(0, reaper); } -void EnsureProcessGetsReaped(ProcessHandle process) { +void EnsureProcessGetsReaped(ProcessId pid) { // If the child is already dead, then there's nothing to do. - if (IsChildDead(process)) + if (IsChildDead(pid)) return; - BackgroundReaper* reaper = new BackgroundReaper(process, 0); + BackgroundReaper* reaper = new BackgroundReaper(pid, 0); PlatformThread::CreateNonJoinable(0, reaper); } diff --git a/chromium/base/process/kill_win.cc b/chromium/base/process/kill_win.cc index b102a8781d0..0da3a26ae4f 100644 --- a/chromium/base/process/kill_win.cc +++ b/chromium/base/process/kill_win.cc @@ -12,7 +12,6 @@ #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/process/process_iterator.h" -#include "base/profiler/scoped_tracker.h" #include "base/win/object_watcher.h" namespace base { @@ -38,46 +37,40 @@ static const int kWaitInterval = 2000; class TimerExpiredTask : public win::ObjectWatcher::Delegate { public: - explicit TimerExpiredTask(ProcessHandle process); - ~TimerExpiredTask(); + explicit TimerExpiredTask(Process process); + ~TimerExpiredTask() override; void TimedOut(); // MessageLoop::Watcher ----------------------------------------------------- - virtual void OnObjectSignaled(HANDLE object); + void OnObjectSignaled(HANDLE object) override; private: void KillProcess(); // The process that we are watching. - ProcessHandle process_; + Process process_; win::ObjectWatcher watcher_; DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); }; -TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { - watcher_.StartWatching(process_, this); +TimerExpiredTask::TimerExpiredTask(Process process) : process_(process.Pass()) { + watcher_.StartWatching(process_.Handle(), this); } TimerExpiredTask::~TimerExpiredTask() { TimedOut(); - DCHECK(!process_) << "Make sure to close the handle."; } void TimerExpiredTask::TimedOut() { - if (process_) + if (process_.IsValid()) KillProcess(); } void TimerExpiredTask::OnObjectSignaled(HANDLE object) { - // TODO(vadimt): Remove ScopedTracker below once crbug.com/418183 is fixed. - tracked_objects::ScopedTracker tracking_profile( - FROM_HERE_WITH_EXPLICIT_FUNCTION("TimerExpiredTask_OnObjectSignaled")); - - CloseHandle(process_); - process_ = NULL; + process_.Close(); } void TimerExpiredTask::KillProcess() { @@ -88,42 +81,14 @@ void TimerExpiredTask::KillProcess() { // 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); + process_.Terminate(kProcessKilledExitCode, false); // Now, just cleanup as if the process exited normally. - OnObjectSignaled(process_); + OnObjectSignaled(process_.Handle()); } } // 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)) - DPLOG(ERROR) << "Error waiting for process exit"; - } else if (!result) { - DPLOG(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) { - DPLOG(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; @@ -182,29 +147,8 @@ TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { } } -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, static_cast<DWORD>(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, + TimeDelta wait, const ProcessFilter* filter) { bool result = true; DWORD start_time = GetTickCount(); @@ -226,13 +170,8 @@ bool WaitForProcessesToExit(const FilePath::StringType& executable_name, return result; } -bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { - int exit_code; - return WaitForExitCodeWithTimeout(handle, &exit_code, wait) && exit_code == 0; -} - bool CleanupProcesses(const FilePath::StringType& executable_name, - base::TimeDelta wait, + TimeDelta wait, int exit_code, const ProcessFilter* filter) { if (WaitForProcessesToExit(executable_name, wait, filter)) @@ -241,20 +180,19 @@ bool CleanupProcesses(const FilePath::StringType& executable_name, return false; } -void EnsureProcessTerminated(ProcessHandle process) { - DCHECK(process != GetCurrentProcess()); +void EnsureProcessTerminated(Process process) { + DCHECK(!process.is_current()); // If already signaled, then we are done! - if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { - CloseHandle(process); + if (WaitForSingleObject(process.Handle(), 0) == WAIT_OBJECT_0) { return; } MessageLoop::current()->PostDelayedTask( FROM_HERE, - base::Bind(&TimerExpiredTask::TimedOut, - base::Owned(new TimerExpiredTask(process))), - base::TimeDelta::FromMilliseconds(kWaitInterval)); + Bind(&TimerExpiredTask::TimedOut, + Owned(new TimerExpiredTask(process.Pass()))), + TimeDelta::FromMilliseconds(kWaitInterval)); } } // namespace base diff --git a/chromium/base/process/launch.cc b/chromium/base/process/launch.cc index a1c4d2159cb..c179b2f5f37 100644 --- a/chromium/base/process/launch.cc +++ b/chromium/base/process/launch.cc @@ -27,7 +27,11 @@ LaunchOptions::LaunchOptions() #if defined(OS_LINUX) , clone_flags(0) , allow_new_privs(false) + , kill_on_parent_death(false) #endif // OS_LINUX +#if defined(OS_POSIX) + , pre_exec_delegate(NULL) +#endif // OS_POSIX #if defined(OS_CHROMEOS) , ctrl_terminal_fd(-1) #endif // OS_CHROMEOS diff --git a/chromium/base/process/launch.h b/chromium/base/process/launch.h index 261019b138f..56f27a82109 100644 --- a/chromium/base/process/launch.h +++ b/chromium/base/process/launch.h @@ -14,6 +14,7 @@ #include "base/base_export.h" #include "base/basictypes.h" #include "base/environment.h" +#include "base/process/process.h" #include "base/process/process_handle.h" #include "base/strings/string_piece.h" @@ -21,7 +22,6 @@ #include "base/posix/file_descriptor_shuffle.h" #elif defined(OS_WIN) #include <windows.h> -#include "base/win/scoped_handle.h" #endif namespace base { @@ -37,6 +37,24 @@ 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 BASE_EXPORT LaunchOptions { +#if defined(OS_POSIX) + // Delegate to be run in between fork and exec in the subprocess (see + // pre_exec_delegate below) + class BASE_EXPORT PreExecDelegate { + public: + PreExecDelegate() {} + virtual ~PreExecDelegate() {} + + // Since this is to be run between fork and exec, and fork may have happened + // while multiple threads were running, this function needs to be async + // safe. + virtual void RunAsyncSafe() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(PreExecDelegate); + }; +#endif // defined(OS_POSIX) + LaunchOptions(); ~LaunchOptions(); @@ -115,13 +133,32 @@ struct BASE_EXPORT LaunchOptions { #if defined(OS_LINUX) // If non-zero, start the process using clone(), using flags as provided. + // Unlike in clone, clone_flags may not contain a custom termination signal + // that is sent to the parent when the child dies. The termination signal will + // always be set to SIGCHLD. int clone_flags; // By default, child processes will have the PR_SET_NO_NEW_PRIVS bit set. If // true, then this bit will not be set in the new child process. bool allow_new_privs; + + // Sets parent process death signal to SIGKILL. + bool kill_on_parent_death; #endif // defined(OS_LINUX) +#if defined(OS_POSIX) + // If not empty, change to this directory before execing the new process. + base::FilePath current_directory; + + // If non-null, a delegate to be run immediately prior to executing the new + // program in the child process. + // + // WARNING: If LaunchProcess is called in the presence of multiple threads, + // code running in this delegate essentially needs to be async-signal safe + // (see man 7 signal for a list of allowed functions). + PreExecDelegate* pre_exec_delegate; +#endif // defined(OS_POSIX) + #if defined(OS_CHROMEOS) // If non-negative, the specified file descriptor will be set as the launched // process' controlling terminal. @@ -143,12 +180,7 @@ struct BASE_EXPORT LaunchOptions { // 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. +// Returns a valid Process upon success. // // Unix-specific notes: // - All file descriptors open in the parent process will be closed in the @@ -158,9 +190,8 @@ struct BASE_EXPORT LaunchOptions { // 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); +BASE_EXPORT Process LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options); #if defined(OS_WIN) // Windows-specific LaunchProcess that takes the command line as a @@ -173,28 +204,24 @@ BASE_EXPORT bool LaunchProcess(const CommandLine& cmdline, // // Example (including literal quotes) // cmdline = "c:\windows\explorer.exe" -foo "c:\bar\" -BASE_EXPORT bool LaunchProcess(const string16& cmdline, - const LaunchOptions& options, - win::ScopedHandle* process_handle); +BASE_EXPORT Process LaunchProcess(const string16& cmdline, + const LaunchOptions& options); // Launches a process with elevated privileges. This does not behave exactly // like LaunchProcess as it uses ShellExecuteEx instead of CreateProcess to // create the process. This means the process will have elevated privileges -// and thus some common operations like OpenProcess will fail. The process will -// be available through the |process_handle| argument. Currently the only -// supported LaunchOptions are |start_hidden| and |wait|. -BASE_EXPORT bool LaunchElevatedProcess(const CommandLine& cmdline, - const LaunchOptions& options, - ProcessHandle* process_handle); +// and thus some common operations like OpenProcess will fail. Currently the +// only supported LaunchOptions are |start_hidden| and |wait|. +BASE_EXPORT Process LaunchElevatedProcess(const CommandLine& cmdline, + const LaunchOptions& options); #elif defined(OS_POSIX) // 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); +BASE_EXPORT Process LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options); // Close all file descriptors, except those which are a destination in the // given multimap. Only call this function in a child process where you know @@ -270,6 +297,25 @@ void ReplaceBootstrapPort(const std::string& replacement_bootstrap_name); // binary. This should not be called in production/released code. BASE_EXPORT LaunchOptions LaunchOptionsForTest(); +#if defined(OS_LINUX) +// A wrapper for clone with fork-like behavior, meaning that it returns the +// child's pid in the parent and 0 in the child. |flags|, |ptid|, and |ctid| are +// as in the clone system call (the CLONE_VM flag is not supported). +// +// This function uses the libc clone wrapper (which updates libc's pid cache) +// internally, so callers may expect things like getpid() to work correctly +// after in both the child and parent. An exception is when this code is run +// under Valgrind. Valgrind does not support the libc clone wrapper, so the libc +// pid cache may be incorrect after this function is called under Valgrind. +// +// As with fork(), callers should be extremely careful when calling this while +// multiple threads are running, since at the time the fork happened, the +// threads could have been in any state (potentially holding locks, etc.). +// Callers should most likely call execve() in the child soon after calling +// this. +BASE_EXPORT pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid); +#endif + } // namespace base #endif // BASE_PROCESS_LAUNCH_H_ diff --git a/chromium/base/process/launch_posix.cc b/chromium/base/process/launch_posix.cc index bc6294b2dad..77edc128319 100644 --- a/chromium/base/process/launch_posix.cc +++ b/chromium/base/process/launch_posix.cc @@ -7,9 +7,12 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <sched.h> +#include <setjmp.h> #include <signal.h> #include <stdlib.h> #include <sys/resource.h> +#include <sys/syscall.h> #include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> @@ -30,13 +33,15 @@ #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.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/third_party/valgrind/valgrind.h" #include "base/threading/platform_thread.h" #include "base/threading/thread_restrictions.h" +#include "build/build_config.h" #if defined(OS_LINUX) #include <sys/prctl.h> @@ -184,6 +189,54 @@ void ResetChildSignalHandlersToDefaults(void) { #endif // !defined(OS_LINUX) || // (!defined(__i386__) && !defined(__x86_64__) && !defined(__arm__)) +#if defined(OS_LINUX) +bool IsRunningOnValgrind() { + return RUNNING_ON_VALGRIND; +} + +// This function runs on the stack specified on the clone call. It uses longjmp +// to switch back to the original stack so the child can return from sys_clone. +int CloneHelper(void* arg) { + jmp_buf* env_ptr = reinterpret_cast<jmp_buf*>(arg); + longjmp(*env_ptr, 1); + + // Should not be reached. + RAW_CHECK(false); + return 1; +} + +// This function is noinline to ensure that stack_buf is below the stack pointer +// that is saved when setjmp is called below. This is needed because when +// compiled with FORTIFY_SOURCE, glibc's longjmp checks that the stack is moved +// upwards. See crbug.com/442912 for more details. +#if defined(ADDRESS_SANITIZER) +// Disable AddressSanitizer instrumentation for this function to make sure +// |stack_buf| is allocated on thread stack instead of ASan's fake stack. +// Under ASan longjmp() will attempt to clean up the area between the old and +// new stack pointers and print a warning that may confuse the user. +__attribute__((no_sanitize_address)) +#endif +NOINLINE pid_t CloneAndLongjmpInChild(unsigned long flags, + pid_t* ptid, + pid_t* ctid, + jmp_buf* env) { + // We use the libc clone wrapper instead of making the syscall + // directly because making the syscall may fail to update the libc's + // internal pid cache. The libc interface unfortunately requires + // specifying a new stack, so we use setjmp/longjmp to emulate + // fork-like behavior. + char stack_buf[PTHREAD_STACK_MIN]; +#if defined(ARCH_CPU_X86_FAMILY) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS64_FAMILY) || defined(ARCH_CPU_MIPS_FAMILY) + // The stack grows downward. + void* stack = stack_buf + sizeof(stack_buf); +#else +#error "Unsupported architecture" +#endif + return clone(&CloneHelper, stack, flags, env, ptid, nullptr, ctid); +} +#endif // defined(OS_LINUX) + } // anonymous namespace // Functor for |ScopedDIR| (below). @@ -277,9 +330,13 @@ void CloseSuperfluousFds(const base::InjectiveMultimap& saved_mapping) { } } -bool LaunchProcess(const std::vector<std::string>& argv, - const LaunchOptions& options, - ProcessHandle* process_handle) { +Process LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options) { + return LaunchProcess(cmdline.argv(), options); +} + +Process LaunchProcess(const std::vector<std::string>& argv, + const LaunchOptions& options) { size_t fd_shuffle_size = 0; if (options.fds_to_remap) { fd_shuffle_size = options.fds_to_remap->size(); @@ -290,7 +347,12 @@ bool LaunchProcess(const std::vector<std::string>& argv, 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* []> argv_cstr(new char* [argv.size() + 1]); + for (size_t i = 0; i < argv.size(); i++) { + argv_cstr[i] = const_cast<char*>(argv[i].c_str()); + } + argv_cstr[argv.size()] = NULL; + scoped_ptr<char*[]> new_environ; char* const empty_environ = NULL; char* const* old_environ = GetEnvironment(); @@ -303,6 +365,11 @@ bool LaunchProcess(const std::vector<std::string>& argv, sigfillset(&full_sigset); const sigset_t orig_sigmask = SetSignalMask(full_sigset); + const char* current_directory = nullptr; + if (!options.current_directory.empty()) { + current_directory = options.current_directory.value().c_str(); + } + pid_t pid; #if defined(OS_LINUX) if (options.clone_flags) { @@ -311,7 +378,17 @@ bool LaunchProcess(const std::vector<std::string>& argv, // 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); + + // We specify a null ptid and ctid. + RAW_CHECK( + !(options.clone_flags & + (CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | CLONE_PARENT_SETTID))); + + // Since we use waitpid, we do not support custom termination signals in the + // clone flags. + RAW_CHECK((options.clone_flags & 0xff) == 0); + + pid = ForkWithFlags(options.clone_flags | SIGCHLD, nullptr, nullptr); } else #endif { @@ -325,7 +402,7 @@ bool LaunchProcess(const std::vector<std::string>& argv, if (pid < 0) { DPLOG(ERROR) << "fork"; - return false; + return Process(); } else if (pid == 0) { // Child process @@ -446,11 +523,23 @@ bool LaunchProcess(const std::vector<std::string>& argv, RAW_LOG(FATAL, "prctl(PR_SET_NO_NEW_PRIVS) failed"); } } + + if (options.kill_on_parent_death) { + if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0) { + RAW_LOG(ERROR, "prctl(PR_SET_PDEATHSIG) failed"); + _exit(127); + } + } #endif - 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 (current_directory != nullptr) { + RAW_CHECK(chdir(current_directory) == 0); + } + + if (options.pre_exec_delegate != nullptr) { + options.pre_exec_delegate->RunAsyncSafe(); + } + execvp(argv_cstr[0], argv_cstr.get()); RAW_LOG(ERROR, "LaunchProcess: failed to execvp:"); @@ -465,19 +554,9 @@ bool LaunchProcess(const std::vector<std::string>& argv, 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); + return Process(pid); } void RaiseProcessToHighPriority() { @@ -612,7 +691,8 @@ static GetAppOutputInternalResult GetAppOutputInternal( // Always wait for exit code (even if we know we'll declare // GOT_MAX_OUTPUT). - bool success = WaitForExitCode(pid, exit_code); + Process process(pid); + bool success = process.WaitForExit(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|). @@ -661,4 +741,45 @@ bool GetAppOutputWithExitCode(const CommandLine& cl, return result == EXECUTE_SUCCESS; } +#if defined(OS_LINUX) +pid_t ForkWithFlags(unsigned long flags, pid_t* ptid, pid_t* ctid) { + const bool clone_tls_used = flags & CLONE_SETTLS; + const bool invalid_ctid = + (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) && !ctid; + const bool invalid_ptid = (flags & CLONE_PARENT_SETTID) && !ptid; + + // We do not support CLONE_VM. + const bool clone_vm_used = flags & CLONE_VM; + + if (clone_tls_used || invalid_ctid || invalid_ptid || clone_vm_used) { + RAW_LOG(FATAL, "Invalid usage of ForkWithFlags"); + } + + // Valgrind's clone implementation does not support specifiying a child_stack + // without CLONE_VM, so we cannot use libc's clone wrapper when running under + // Valgrind. As a result, the libc pid cache may be incorrect under Valgrind. + // See crbug.com/442817 for more details. + if (IsRunningOnValgrind()) { + // See kernel/fork.c in Linux. There is different ordering of sys_clone + // parameters depending on CONFIG_CLONE_BACKWARDS* configuration options. +#if defined(ARCH_CPU_X86_64) + return syscall(__NR_clone, flags, nullptr, ptid, ctid, nullptr); +#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM_FAMILY) || \ + defined(ARCH_CPU_MIPS_FAMILY) || defined(ARCH_CPU_MIPS64_FAMILY) + // CONFIG_CLONE_BACKWARDS defined. + return syscall(__NR_clone, flags, nullptr, ptid, nullptr, ctid); +#else +#error "Unsupported architecture" +#endif + } + + jmp_buf env; + if (setjmp(env) == 0) { + return CloneAndLongjmpInChild(flags, ptid, ctid, &env); + } + + return 0; +} +#endif // defined(OS_LINUX) + } // namespace base diff --git a/chromium/base/process/launch_win.cc b/chromium/base/process/launch_win.cc index a3303a5e085..ebc19b83130 100644 --- a/chromium/base/process/launch_win.cc +++ b/chromium/base/process/launch_win.cc @@ -105,9 +105,13 @@ void RouteStdioToConsole() { std::ios::sync_with_stdio(); } -bool LaunchProcess(const string16& cmdline, - const LaunchOptions& options, - win::ScopedHandle* process_handle) { +Process LaunchProcess(const CommandLine& cmdline, + const LaunchOptions& options) { + return LaunchProcess(cmdline.GetCommandLineString(), options); +} + +Process LaunchProcess(const string16& cmdline, + const LaunchOptions& options) { win::StartupInformation startup_info_wrapper; STARTUPINFO* startup_info = startup_info_wrapper.startup_info(); @@ -119,18 +123,18 @@ bool LaunchProcess(const string16& cmdline, } else { if (base::win::GetVersion() < base::win::VERSION_VISTA) { DLOG(ERROR) << "Specifying handles to inherit requires Vista or later."; - return false; + return Process(); } if (options.handles_to_inherit->size() > std::numeric_limits<DWORD>::max() / sizeof(HANDLE)) { DLOG(ERROR) << "Too many handles to inherit."; - return false; + return Process(); } if (!startup_info_wrapper.InitializeProcThreadAttributeList(1)) { DPLOG(ERROR); - return false; + return Process(); } if (!startup_info_wrapper.UpdateProcThreadAttribute( @@ -139,7 +143,7 @@ bool LaunchProcess(const string16& cmdline, static_cast<DWORD>(options.handles_to_inherit->size() * sizeof(HANDLE)))) { DPLOG(ERROR); - return false; + return Process(); } inherit_handles = true; @@ -184,7 +188,7 @@ bool LaunchProcess(const string16& cmdline, if (!CreateEnvironmentBlock(&enviroment_block, options.as_user, FALSE)) { DPLOG(ERROR); - return false; + return Process(); } BOOL launched = @@ -197,7 +201,7 @@ bool LaunchProcess(const string16& cmdline, if (!launched) { DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline) << std::endl;; - return false; + return Process(); } } else { if (!CreateProcess(NULL, @@ -206,7 +210,7 @@ bool LaunchProcess(const string16& cmdline, startup_info, &temp_process_info)) { DPLOG(ERROR) << "Command line:" << std::endl << UTF16ToUTF8(cmdline) << std::endl;; - return false; + return Process(); } } base::win::ScopedProcessInformation process_info(temp_process_info); @@ -215,8 +219,9 @@ bool LaunchProcess(const string16& cmdline, if (0 == AssignProcessToJobObject(options.job_handle, process_info.process_handle())) { DLOG(ERROR) << "Could not AssignProcessToObject."; - KillProcess(process_info.process_handle(), kProcessKilledExitCode, true); - return false; + Process scoped_process(process_info.TakeProcessHandle()); + scoped_process.Terminate(kProcessKilledExitCode, true); + return Process(); } ResumeThread(process_info.thread_handle()); @@ -225,28 +230,11 @@ bool LaunchProcess(const string16& cmdline, 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->Set(process_info.TakeProcessHandle()); - - return true; -} - -bool LaunchProcess(const CommandLine& cmdline, - const LaunchOptions& options, - ProcessHandle* process_handle) { - if (!process_handle) - return LaunchProcess(cmdline.GetCommandLineString(), options, NULL); - - win::ScopedHandle process; - bool rv = LaunchProcess(cmdline.GetCommandLineString(), options, &process); - *process_handle = process.Take(); - return rv; + return Process(process_info.TakeProcessHandle()); } -bool LaunchElevatedProcess(const CommandLine& cmdline, - const LaunchOptions& options, - ProcessHandle* process_handle) { +Process LaunchElevatedProcess(const CommandLine& cmdline, + const LaunchOptions& options) { const string16 file = cmdline.GetProgram().value(); const string16 arguments = cmdline.GetArgumentsString(); @@ -263,20 +251,13 @@ bool LaunchElevatedProcess(const CommandLine& cmdline, if (!ShellExecuteEx(&shex_info)) { DPLOG(ERROR); - return false; + return Process(); } if (options.wait) WaitForSingleObject(shex_info.hProcess, INFINITE); - // If the caller wants the process handle give it to them, otherwise just - // close it. Closing it does not terminate the process. - if (process_handle) - *process_handle = shex_info.hProcess; - else - CloseHandle(shex_info.hProcess); - - return true; + return Process(shex_info.hProcess); } bool SetJobObjectLimitFlags(HANDLE job_object, DWORD limit_flags) { diff --git a/chromium/base/process/memory.cc b/chromium/base/process/memory.cc index 1dbc3630703..133a72a0f75 100644 --- a/chromium/base/process/memory.cc +++ b/chromium/base/process/memory.cc @@ -2,10 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/debug/alias.h" +#include "base/logging.h" #include "base/process/memory.h" namespace base { +namespace { + +// Breakpad server classifies base::`anonymous namespace'::OnNoMemory as +// out-of-memory crash. +NOINLINE void OnNoMemory(size_t size) { + size_t tmp_size = size; + base::debug::Alias(&tmp_size); + LOG(FATAL) << "Out of memory. size=" << tmp_size; +} + +} // namespace + +void TerminateBecauseOutOfMemory(size_t size) { + OnNoMemory(size); +} + // Defined in memory_mac.mm for Mac. #if !defined(OS_MACOSX) @@ -27,4 +45,4 @@ bool UncheckedCalloc(size_t num_items, size_t size, void** result) { #endif -} +} // namespace base diff --git a/chromium/base/process/memory.h b/chromium/base/process/memory.h index 100d9c72f43..da27151d1b1 100644 --- a/chromium/base/process/memory.h +++ b/chromium/base/process/memory.h @@ -39,6 +39,10 @@ BASE_EXPORT void EnableTerminationOnHeapCorruption(); // Turns on process termination if memory runs out. BASE_EXPORT void EnableTerminationOnOutOfMemory(); +// Terminates process. Should be called only for out of memory errors. +// Crash reporting classifies such crashes as OOM. +BASE_EXPORT void TerminateBecauseOutOfMemory(size_t size); + #if defined(OS_WIN) // Returns the module handle to which an address belongs. The reference count // of the module is not incremented. diff --git a/chromium/base/process/memory_mac.mm b/chromium/base/process/memory_mac.mm index e59e63fa87f..4d719f8054e 100644 --- a/chromium/base/process/memory_mac.mm +++ b/chromium/base/process/memory_mac.mm @@ -4,12 +4,6 @@ #include "base/process/memory.h" -// AddressSanitizer handles heap corruption, and on 64 bit Macs, the malloc -// system automatically abort()s on heap corruption. -#if !defined(ADDRESS_SANITIZER) && ARCH_CPU_32_BITS -#define HANDLE_MEMORY_CORRUPTION_MANUALLY -#endif - #include <CoreFoundation/CoreFoundation.h> #include <errno.h> #include <mach/mach.h> @@ -27,170 +21,12 @@ #include "third_party/apple_apsl/CFBase.h" #include "third_party/apple_apsl/malloc.h" -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) -#include <dlfcn.h> -#include <mach-o/nlist.h> - -#include "base/threading/thread_local.h" -#include "third_party/mach_override/mach_override.h" -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - namespace base { -// These are helpers for EnableTerminationOnHeapCorruption, which is a no-op -// on 64 bit Macs. -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) -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_alloc = 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. - if (errno == ENOMEM) - return; - - // The malloc library attempts to log to ASL (syslog) before calling this - // code, which fails accessing a Unix-domain socket when sandboxed. The - // failed socket results in writing to a -1 fd, leaving EBADF in errno. If - // UncheckedMalloc() is on the stack, for large allocations (15k and up) only - // an OOM failure leads here. Smaller allocations could also arrive here due - // to freelist corruption, but there is no way to distinguish that from OOM at - // this point. - // - // NOTE(shess): I hypothesize that EPERM case in 10.9 is the same root cause - // as EBADF. Unfortunately, 10.9's opensource releases don't include malloc - // source code at this time. - // <http://crbug.com/312234> - if ((errno == EBADF || errno == EPERM) && g_unchecked_alloc.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 // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - void EnableTerminationOnHeapCorruption() { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - // 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(HANDLE_MEMORY_CORRUPTION_MANUALLY) +#if !ARCH_CPU_64_BITS + DLOG(WARNING) << "EnableTerminationOnHeapCorruption only works on 64-bit"; +#endif } // ------------------------------------------------------------------------ @@ -293,142 +129,106 @@ memalign_type g_old_memalign_purgeable; void* oom_killer_malloc(struct _malloc_zone_t* zone, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_malloc(zone, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void* oom_killer_calloc(struct _malloc_zone_t* zone, size_t num_items, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_calloc(zone, num_items, size); if (!result && num_items && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(num_items * size); return result; } void* oom_killer_valloc(struct _malloc_zone_t* zone, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_valloc(zone, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void oom_killer_free(struct _malloc_zone_t* zone, void* ptr) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) g_old_free(zone, ptr); } void* oom_killer_realloc(struct _malloc_zone_t* zone, void* ptr, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_realloc(zone, ptr, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void* oom_killer_memalign(struct _malloc_zone_t* zone, size_t alignment, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) 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(); + if (!result && size && alignment >= sizeof(void*) && + (alignment & (alignment - 1)) == 0) { + TerminateBecauseOutOfMemory(size); } return result; } void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_malloc_purgeable(zone, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone, size_t num_items, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_calloc_purgeable(zone, num_items, size); if (!result && num_items && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(num_items * size); return result; } void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_valloc_purgeable(zone, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void oom_killer_free_purgeable(struct _malloc_zone_t* zone, void* ptr) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) g_old_free_purgeable(zone, ptr); } void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone, void* ptr, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) void* result = g_old_realloc_purgeable(zone, ptr, size); if (!result && size) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(size); return result; } void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone, size_t alignment, size_t size) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) 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(); + TerminateBecauseOutOfMemory(size); } return result; } @@ -438,7 +238,7 @@ void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone, // === C++ operator new === void oom_killer_new() { - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(0); } #if !defined(ADDRESS_SANITIZER) @@ -477,7 +277,7 @@ void* oom_killer_cfallocator_system_default(CFIndex alloc_size, void* info) { void* result = g_old_cfallocator_system_default(alloc_size, hint, info); if (!result) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(alloc_size); return result; } @@ -486,7 +286,7 @@ void* oom_killer_cfallocator_malloc(CFIndex alloc_size, void* info) { void* result = g_old_cfallocator_malloc(alloc_size, hint, info); if (!result) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(alloc_size); return result; } @@ -495,7 +295,7 @@ void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size, void* info) { void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info); if (!result) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(alloc_size); return result; } @@ -510,7 +310,7 @@ id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) { id result = g_old_allocWithZone(self, _cmd, zone); if (!result) - debug::BreakDebugger(); + TerminateBecauseOutOfMemory(0); return result; } @@ -521,10 +321,6 @@ bool UncheckedMalloc(size_t size, void** result) { *result = malloc(size); #else if (g_old_malloc) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; - ThreadLocalBooleanAutoReset flag(g_unchecked_alloc.Pointer(), true); -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) *result = g_old_malloc(malloc_default_zone(), size); } else { *result = malloc(size); @@ -539,10 +335,6 @@ bool UncheckedCalloc(size_t num_items, size_t size, void** result) { *result = calloc(num_items, size); #else if (g_old_calloc) { -#if defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) - ScopedClearErrno clear_errno; - ThreadLocalBooleanAutoReset flag(g_unchecked_alloc.Pointer(), true); -#endif // defined(HANDLE_MEMORY_CORRUPTION_MANUALLY) *result = g_old_calloc(malloc_default_zone(), num_items, size); } else { *result = calloc(num_items, size); diff --git a/chromium/base/process/memory_unittest.cc b/chromium/base/process/memory_unittest.cc index afbf5c629c2..0276b495915 100644 --- a/chromium/base/process/memory_unittest.cc +++ b/chromium/base/process/memory_unittest.cc @@ -26,6 +26,7 @@ #endif #if defined(OS_LINUX) #include <malloc.h> +#include "base/test/malloc_wrapper.h" #endif #if defined(OS_WIN) @@ -105,37 +106,6 @@ TEST(ProcessMemoryTest, EnableLFH) { // 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) { -#if ARCH_CPU_32_BITS - // The Mavericks malloc library changed in a way which breaks the tricks used - // to implement EnableTerminationOnOutOfMemory() with UncheckedMalloc() under - // 32-bit. Under 64-bit the oom_killer code handles this. - if (base::mac::IsOSMavericksOrLater()) - return; -#endif - - // 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.*\\n?.*"); - - base::debug::Alias(buf); -} -#endif // !defined(ADDRESS_SANITIZER) - TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { // Assert that freeing an unallocated pointer will crash the process. char buf[9]; @@ -150,20 +120,19 @@ TEST(ProcessMemoryTest, MacTerminateOnHeapCorruption) { ASSERT_DEATH(free(buf), "attempting free on address which " "was not malloc\\(\\)-ed"); #else - ASSERT_DEATH(free(buf), "being freed.*\\n?\\.*" - "\\*\\*\\* set a breakpoint in malloc_error_break to debug.*\\n?.*" - "Terminating process due to a potential for future heap corruption"); -#endif // ARCH_CPU_64_BITS || defined(ADDRESS_SANITIZER) + ADD_FAILURE() << "This test is not supported in this build configuration."; +#endif } #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. +// OutOfMemoryTest cases. OpenBSD does not support these tests either. +// Don't test these on ASan/TSan/MSan configurations: only test the real +// allocator. // TODO(vandebo) make this work on Windows too. -#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && \ - !defined(OS_WIN) +#if !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) && \ + !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) #if defined(USE_TCMALLOC) extern "C" { @@ -171,6 +140,10 @@ int tc_set_new_mode(int mode); } #endif // defined(USE_TCMALLOC) +namespace { +const char *kOomRegex = "Out of memory"; +} // namespace + class OutOfMemoryTest : public testing::Test { public: OutOfMemoryTest() @@ -182,13 +155,9 @@ class OutOfMemoryTest : public testing::Test { } #if defined(USE_TCMALLOC) - virtual void SetUp() override { - tc_set_new_mode(1); - } + void SetUp() override { tc_set_new_mode(1); } - virtual void TearDown() override { - tc_set_new_mode(0); - } + void TearDown() override { tc_set_new_mode(0); } #endif // defined(USE_TCMALLOC) protected: @@ -213,42 +182,42 @@ TEST_F(OutOfMemoryDeathTest, New) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = operator new(test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, NewArray) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = new char[test_size_]; - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, Malloc) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc(test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, Realloc) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = realloc(NULL, test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, Calloc) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = calloc(1024, test_size_ / 1024L); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, Valloc) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = valloc(test_size_); - }, ""); + }, kOomRegex); } #if defined(OS_LINUX) @@ -258,7 +227,7 @@ TEST_F(OutOfMemoryDeathTest, Pvalloc) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = pvalloc(test_size_); - }, ""); + }, kOomRegex); } #endif // PVALLOC_AVAILABLE == 1 @@ -266,18 +235,16 @@ TEST_F(OutOfMemoryDeathTest, Memalign) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = memalign(4, test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, ViaSharedLibraries) { // This tests that the run-time symbol resolution is overriding malloc for - // shared libraries (including libc itself) as well as for our code. - std::string format = base::StringPrintf("%%%zud", test_size_); - char *value = NULL; + // shared libraries as well as for our code. ASSERT_DEATH({ - SetUpInDeathAssert(); - EXPECT_EQ(-1, asprintf(&value, format.c_str(), 0)); - }, ""); + SetUpInDeathAssert(); + value_ = MallocWrapper(test_size_); + }, kOomRegex); } #endif // OS_LINUX @@ -290,7 +257,7 @@ TEST_F(OutOfMemoryDeathTest, Posix_memalign) { ASSERT_DEATH({ SetUpInDeathAssert(); EXPECT_EQ(ENOMEM, posix_memalign(&value_, 8, test_size_)); - }, ""); + }, kOomRegex); } #endif // defined(OS_POSIX) && !defined(OS_ANDROID) @@ -303,7 +270,7 @@ TEST_F(OutOfMemoryDeathTest, MallocPurgeable) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc_zone_malloc(zone, test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { @@ -311,7 +278,7 @@ TEST_F(OutOfMemoryDeathTest, ReallocPurgeable) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc_zone_realloc(zone, NULL, test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { @@ -319,7 +286,7 @@ TEST_F(OutOfMemoryDeathTest, CallocPurgeable) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc_zone_calloc(zone, 1024, test_size_ / 1024L); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { @@ -327,7 +294,7 @@ TEST_F(OutOfMemoryDeathTest, VallocPurgeable) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc_zone_valloc(zone, test_size_); - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { @@ -335,7 +302,7 @@ TEST_F(OutOfMemoryDeathTest, PosixMemalignPurgeable) { ASSERT_DEATH({ SetUpInDeathAssert(); value_ = malloc_zone_memalign(zone, 8, test_size_); - }, ""); + }, kOomRegex); } // Since these allocation functions take a signed size, it's possible that @@ -350,7 +317,7 @@ TEST_F(OutOfMemoryDeathTest, CFAllocatorSystemDefault) { SetUpInDeathAssert(); while ((value_ = base::AllocateViaCFAllocatorSystemDefault(signed_test_size_))) {} - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { @@ -358,7 +325,7 @@ TEST_F(OutOfMemoryDeathTest, CFAllocatorMalloc) { SetUpInDeathAssert(); while ((value_ = base::AllocateViaCFAllocatorMalloc(signed_test_size_))) {} - }, ""); + }, kOomRegex); } TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { @@ -366,7 +333,7 @@ TEST_F(OutOfMemoryDeathTest, CFAllocatorMallocZone) { SetUpInDeathAssert(); while ((value_ = base::AllocateViaCFAllocatorMallocZone(signed_test_size_))) {} - }, ""); + }, kOomRegex); } #if !defined(ARCH_CPU_64_BITS) @@ -378,7 +345,7 @@ TEST_F(OutOfMemoryDeathTest, PsychoticallyBigObjCObject) { ASSERT_DEATH({ SetUpInDeathAssert(); while ((value_ = base::AllocatePsychoticallyBigObjCObject())) {} - }, ""); + }, kOomRegex); } #endif // !ARCH_CPU_64_BITS @@ -390,7 +357,7 @@ class OutOfMemoryHandledTest : public OutOfMemoryTest { static const size_t kSafeCallocSize = 128; static const size_t kSafeCallocItems = 4; - virtual void SetUp() { + void SetUp() override { OutOfMemoryTest::SetUp(); // We enable termination on OOM - just as Chrome does at early @@ -448,4 +415,5 @@ TEST_F(OutOfMemoryHandledTest, UncheckedCalloc) { EXPECT_TRUE(value_ == NULL); } #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR) -#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) +#endif // !defined(OS_ANDROID) && !defined(OS_OPENBSD) && !defined(OS_WIN) && + // !defined(ADDRESS_SANITIZER) diff --git a/chromium/base/process/memory_win.cc b/chromium/base/process/memory_win.cc index 668214ceaf0..fc57b48f1f7 100644 --- a/chromium/base/process/memory_win.cc +++ b/chromium/base/process/memory_win.cc @@ -4,6 +4,7 @@ #include "base/process/memory.h" +#include <new.h> #include <psapi.h> #include "base/logging.h" @@ -13,15 +14,19 @@ 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. +#pragma warning(push) +#pragma warning(disable: 4702) + +int OnNoMemory(size_t) { + // Kill the process. This is important for security since most of code + // does not check the result of memory allocation. __debugbreak(); _exit(1); + return 0; } +#pragma warning(pop) + // HeapSetInformation function pointer. typedef BOOL (WINAPI* HeapSetFn)(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); @@ -68,7 +73,8 @@ void EnableTerminationOnHeapCorruption() { } void EnableTerminationOnOutOfMemory() { - std::set_new_handler(&OnNoMemory); + _set_new_handler(&OnNoMemory); + _set_new_mode(1); } HMODULE GetModuleFromAddress(void* address) { diff --git a/chromium/base/process/process.h b/chromium/base/process/process.h index 701947491d7..7fb1b69954c 100644 --- a/chromium/base/process/process.h +++ b/chromium/base/process/process.h @@ -1,14 +1,15 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef BASE_PROCESS_PROCESS_PROCESS_H_ -#define BASE_PROCESS_PROCESS_PROCESS_H_ +#ifndef BASE_PROCESS_PROCESS_H_ +#define BASE_PROCESS_PROCESS_H_ #include "base/base_export.h" #include "base/basictypes.h" #include "base/move.h" #include "base/process/process_handle.h" +#include "base/time/time.h" #include "build/build_config.h" #if defined(OS_WIN) @@ -40,7 +41,7 @@ class BASE_EXPORT Process { Process(RValue other); // The destructor does not terminate the process. - ~Process() {} + ~Process(); // Move operator= for C++03 move emulation of this type. Process& operator=(RValue other); @@ -48,6 +49,26 @@ class BASE_EXPORT Process { // Returns an object for the current process. static Process Current(); + // Returns a Process for the given |pid|. + static Process Open(ProcessId pid); + + // Returns a Process for the given |pid|. On Windows the handle is opened + // with more access rights and must only be used by trusted code (can read the + // address space and duplicate handles). + static Process OpenWithExtraPrivileges(ProcessId pid); + +#if defined(OS_WIN) + // Returns a Process for the given |pid|, using some |desired_access|. + // See ::OpenProcess documentation for valid |desired_access|. + static Process OpenWithAccess(ProcessId pid, DWORD desired_access); +#endif + + // Creates an object from a |handle| owned by someone else. + // Don't use this for new code. It is only intended to ease the migration to + // a strict ownership model. + // TODO(rvargas) crbug.com/417532: Remove this code. + static Process DeprecatedGetProcessFromHandle(ProcessHandle handle); + // Returns true if processes can be backgrounded. static bool CanBackgroundProcesses(); @@ -62,7 +83,7 @@ class BASE_EXPORT Process { Process Duplicate() const; // Get the PID for this process. - ProcessId pid() const; + ProcessId Pid() const; // Returns true if this process is the current process. bool is_current() const; @@ -70,11 +91,40 @@ class BASE_EXPORT Process { // 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. - // NOTE: On POSIX |result_code| is ignored. - void Terminate(int result_code); - + // Terminates the process with extreme prejudice. The given |exit_code| will + // be the exit code of the process. If |wait| is true, this method will wait + // for up to one minute for the process to actually terminate. + // Returns true if the process terminates within the allowed time. + // NOTE: On POSIX |exit_code| is ignored. + bool Terminate(int exit_code, bool wait) const; + + // Waits for the process to exit. Returns true on success. + // On POSIX, if the process has been signaled then |exit_code| is set to -1. + // On Linux this must be a child process, however on Mac and Windows it can be + // any process. + bool WaitForExit(int* exit_code); + + // Same as WaitForExit() but only waits for up to |timeout|. + bool WaitForExitWithTimeout(TimeDelta timeout, int* exit_code); + +#if defined(OS_MACOSX) + // The Mac needs a Mach port in order to manipulate a process's priority, + // and there's no good way to get that from base given the pid. These Mac + // variants of the IsProcessBackgrounded and SetProcessBackgrounded API take + // the Mach port for this reason. See crbug.com/460102 + // + // A process is backgrounded when its priority is lower than normal. + // Return true if the process with mach port |task_port| is backgrounded, + // false otherwise. + bool IsProcessBackgrounded(mach_port_t task_port) const; + + // Set the process with the specified mach port 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(mach_port_t task_port, bool value); +#else // A process is backgrounded when it's priority is lower than normal. // Return true if this process is backgrounded, false otherwise. bool IsProcessBackgrounded() const; @@ -84,7 +134,7 @@ class BASE_EXPORT Process { // will be made "normal" - equivalent to default process priority. // Returns true if the priority was changed, false otherwise. bool SetProcessBackgrounded(bool value); - +#endif // defined(OS_MACOSX) // Returns an integer representing the priority of a process. The meaning // of this value is OS dependent. int GetPriority() const; @@ -100,4 +150,4 @@ class BASE_EXPORT Process { } // namespace base -#endif // BASE_PROCESS_PROCESS_PROCESS_H_ +#endif // BASE_PROCESS_PROCESS_H_ diff --git a/chromium/base/process/process_handle.h b/chromium/base/process/process_handle.h index 6f8094ee80b..77f2c585cfc 100644 --- a/chromium/base/process/process_handle.h +++ b/chromium/base/process/process_handle.h @@ -40,47 +40,12 @@ 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. +// DEPRECATED. New code should be using Process::Pid() instead. 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_POSIX) // Returns the path to the executable of the given process. BASE_EXPORT FilePath GetProcessExecutablePath(ProcessHandle process); diff --git a/chromium/base/process/process_handle_posix.cc b/chromium/base/process/process_handle_posix.cc index 4013254a5c2..4e332df1a0f 100644 --- a/chromium/base/process/process_handle_posix.cc +++ b/chromium/base/process/process_handle_posix.cc @@ -16,32 +16,6 @@ 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; } diff --git a/chromium/base/process/process_handle_win.cc b/chromium/base/process/process_handle_win.cc index 3bc3a125e0d..f2ffff8e882 100644 --- a/chromium/base/process/process_handle_win.cc +++ b/chromium/base/process/process_handle_win.cc @@ -20,107 +20,9 @@ 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 index e9e7b4e8151..85f204d04cf 100644 --- a/chromium/base/process/process_info.h +++ b/chromium/base/process/process_info.h @@ -2,11 +2,12 @@ // 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_ +#ifndef BASE_PROCESS_PROCESS_INFO_H_ +#define BASE_PROCESS_PROCESS_INFO_H_ #include "base/base_export.h" #include "base/basictypes.h" +#include "build/build_config.h" namespace base { @@ -20,6 +21,24 @@ class BASE_EXPORT CurrentProcessInfo { static const Time CreationTime(); }; +#if defined(OS_WIN) + +enum IntegrityLevel { + INTEGRITY_UNKNOWN, + LOW_INTEGRITY, + MEDIUM_INTEGRITY, + HIGH_INTEGRITY, +}; + +// Returns the integrity level of the process. Returns INTEGRITY_UNKNOWN if the +// system does not support integrity levels (pre-Vista) or in the case of an +// underlying system failure. +BASE_EXPORT IntegrityLevel GetCurrentProcessIntegrityLevel(); + +#endif // defined(OS_WIN) + + + } // namespace base -#endif // BASE_PROCESS_PROCESS_PROCESS_INFO_H_ +#endif // BASE_PROCESS_PROCESS_INFO_H_ diff --git a/chromium/base/process/process_info_win.cc b/chromium/base/process/process_info_win.cc index b930ae6dd88..2b9c40653fd 100644 --- a/chromium/base/process/process_info_win.cc +++ b/chromium/base/process/process_info_win.cc @@ -7,11 +7,14 @@ #include <windows.h> #include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" #include "base/time/time.h" +#include "base/win/scoped_handle.h" +#include "base/win/windows_version.h" namespace base { -//static +// static const Time CurrentProcessInfo::CreationTime() { FILETIME creation_time = {}; FILETIME ignore = {}; @@ -22,4 +25,55 @@ const Time CurrentProcessInfo::CreationTime() { return Time::FromFileTime(creation_time); } +IntegrityLevel GetCurrentProcessIntegrityLevel() { + if (win::GetVersion() < base::win::VERSION_VISTA) + return INTEGRITY_UNKNOWN; + + HANDLE process_token; + if (!::OpenProcessToken(::GetCurrentProcess(), + TOKEN_QUERY | TOKEN_QUERY_SOURCE, &process_token)) { + return INTEGRITY_UNKNOWN; + } + 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 INTEGRITY_UNKNOWN; + } + + scoped_ptr<char[]> token_label_bytes(new char[token_info_length]); + if (!token_label_bytes.get()) + return INTEGRITY_UNKNOWN; + + TOKEN_MANDATORY_LABEL* token_label = + reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_label_bytes.get()); + if (!token_label) + return INTEGRITY_UNKNOWN; + + if (!::GetTokenInformation(process_token, TokenIntegrityLevel, token_label, + token_info_length, &token_info_length)) { + return INTEGRITY_UNKNOWN; + } + + DWORD integrity_level = *::GetSidSubAuthority( + token_label->Label.Sid, + static_cast<DWORD>(*::GetSidSubAuthorityCount(token_label->Label.Sid)-1)); + + if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID) + return LOW_INTEGRITY; + + if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID && + integrity_level < SECURITY_MANDATORY_HIGH_RID) { + return MEDIUM_INTEGRITY; + } + + if (integrity_level >= SECURITY_MANDATORY_HIGH_RID) + return HIGH_INTEGRITY; + + NOTREACHED(); + return INTEGRITY_UNKNOWN; +} + } // namespace base diff --git a/chromium/base/process/process_iterator.h b/chromium/base/process/process_iterator.h index bdd07c698de..ec6500e653c 100644 --- a/chromium/base/process/process_iterator.h +++ b/chromium/base/process/process_iterator.h @@ -36,26 +36,6 @@ struct ProcessEntry : public PROCESSENTRY32 { 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(); @@ -75,23 +55,6 @@ struct BASE_EXPORT ProcessEntry { 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. diff --git a/chromium/base/process/process_linux.cc b/chromium/base/process/process_linux.cc index 59ee288f880..88a310eccdb 100644 --- a/chromium/base/process/process_linux.cc +++ b/chromium/base/process/process_linux.cc @@ -103,7 +103,7 @@ bool Process::IsProcessBackgrounded() const { &proc)) { std::vector<std::string> proc_parts; base::SplitString(proc, ':', &proc_parts); - DCHECK(proc_parts.size() == 3); + DCHECK_EQ(proc_parts.size(), 3u); bool ret = proc_parts[2] == std::string(kBackground); return ret; } else { diff --git a/chromium/base/process/process_mac.cc b/chromium/base/process/process_mac.cc new file mode 100644 index 00000000000..1913cc378e8 --- /dev/null +++ b/chromium/base/process/process_mac.cc @@ -0,0 +1,128 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/process.h" + +#include "base/mac/mac_util.h" +#include "base/mac/mach_logging.h" + +#include <mach/mach.h> + +// The following was added to <mach/task_policy.h> after 10.8. +// TODO(shrike): Remove the TASK_OVERRIDE_QOS_POLICY ifndef once builders +// reach 10.9 or higher. +#ifndef TASK_OVERRIDE_QOS_POLICY + +#define TASK_OVERRIDE_QOS_POLICY 9 + +typedef struct task_category_policy task_category_policy_data_t; +typedef struct task_category_policy* task_category_policy_t; + +enum task_latency_qos { + LATENCY_QOS_TIER_UNSPECIFIED = 0x0, + LATENCY_QOS_TIER_0 = ((0xFF << 16) | 1), + LATENCY_QOS_TIER_1 = ((0xFF << 16) | 2), + LATENCY_QOS_TIER_2 = ((0xFF << 16) | 3), + LATENCY_QOS_TIER_3 = ((0xFF << 16) | 4), + LATENCY_QOS_TIER_4 = ((0xFF << 16) | 5), + LATENCY_QOS_TIER_5 = ((0xFF << 16) | 6) +}; +typedef integer_t task_latency_qos_t; +enum task_throughput_qos { + THROUGHPUT_QOS_TIER_UNSPECIFIED = 0x0, + THROUGHPUT_QOS_TIER_0 = ((0xFE << 16) | 1), + THROUGHPUT_QOS_TIER_1 = ((0xFE << 16) | 2), + THROUGHPUT_QOS_TIER_2 = ((0xFE << 16) | 3), + THROUGHPUT_QOS_TIER_3 = ((0xFE << 16) | 4), + THROUGHPUT_QOS_TIER_4 = ((0xFE << 16) | 5), + THROUGHPUT_QOS_TIER_5 = ((0xFE << 16) | 6), +}; + +#define LATENCY_QOS_LAUNCH_DEFAULT_TIER LATENCY_QOS_TIER_3 +#define THROUGHPUT_QOS_LAUNCH_DEFAULT_TIER THROUGHPUT_QOS_TIER_3 + +typedef integer_t task_throughput_qos_t; + +struct task_qos_policy { + task_latency_qos_t task_latency_qos_tier; + task_throughput_qos_t task_throughput_qos_tier; +}; + +typedef struct task_qos_policy* task_qos_policy_t; +#define TASK_QOS_POLICY_COUNT \ + ((mach_msg_type_number_t)(sizeof(struct task_qos_policy) / sizeof(integer_t))) + +#endif // TASK_OVERRIDE_QOS_POLICY + +namespace base { + +bool Process::CanBackgroundProcesses() { + return true; +} + +bool Process::IsProcessBackgrounded(mach_port_t task_port) const { + // See SetProcessBackgrounded(). + DCHECK(IsValid()); + DCHECK_NE(task_port, TASK_NULL); + + task_category_policy_data_t category_policy; + mach_msg_type_number_t task_info_count = TASK_CATEGORY_POLICY_COUNT; + boolean_t get_default = FALSE; + + kern_return_t result = + task_policy_get(task_port, TASK_CATEGORY_POLICY, + reinterpret_cast<task_policy_t>(&category_policy), + &task_info_count, &get_default); + MACH_LOG_IF(ERROR, result != KERN_SUCCESS, result) << + "task_policy_get TASK_CATEGORY_POLICY"; + + if (result == KERN_SUCCESS && get_default == FALSE) { + return category_policy.role == TASK_BACKGROUND_APPLICATION; + } + return false; +} + +bool Process::SetProcessBackgrounded(mach_port_t task_port, bool background) { + DCHECK(IsValid()); + DCHECK_NE(task_port, TASK_NULL); + + if (!CanBackgroundProcesses()) { + return false; + } else if (IsProcessBackgrounded(task_port) == background) { + return true; + } + + task_category_policy category_policy; + category_policy.role = + background ? TASK_BACKGROUND_APPLICATION : TASK_FOREGROUND_APPLICATION; + kern_return_t result = + task_policy_set(task_port, TASK_CATEGORY_POLICY, + reinterpret_cast<task_policy_t>(&category_policy), + TASK_CATEGORY_POLICY_COUNT); + + if (result != KERN_SUCCESS) { + MACH_LOG(ERROR, result) << "task_policy_set TASK_CATEGORY_POLICY"; + return false; + } else if (!mac::IsOSMavericksOrLater()) { + return true; + } + + // Latency QoS regulates timer throttling/accuracy. Select default tier + // on foreground because precise timer firing isn't needed. + struct task_qos_policy qos_policy = { + background ? LATENCY_QOS_TIER_5 : LATENCY_QOS_TIER_UNSPECIFIED, + background ? THROUGHPUT_QOS_TIER_5 : THROUGHPUT_QOS_TIER_UNSPECIFIED + }; + result = task_policy_set(task_port, TASK_OVERRIDE_QOS_POLICY, + reinterpret_cast<task_policy_t>(&qos_policy), + TASK_QOS_POLICY_COUNT); + if (result != KERN_SUCCESS) { + MACH_LOG(ERROR, result) << "task_policy_set TASK_OVERRIDE_QOS_POLICY"; + return false; + } + + return true; +} + +} // namespace base diff --git a/chromium/base/process/process_metrics.cc b/chromium/base/process/process_metrics.cc index 2edd9c755de..e4863391076 100644 --- a/chromium/base/process/process_metrics.cc +++ b/chromium/base/process/process_metrics.cc @@ -33,11 +33,11 @@ scoped_ptr<Value> SystemMetrics::ToValue() const { res->SetInteger("committed_memory", static_cast<int>(committed_memory_)); #if defined(OS_LINUX) || defined(OS_ANDROID) - res->Set("meminfo", memory_info_.ToValue().release()); - res->Set("diskinfo", disk_info_.ToValue().release()); + res->Set("meminfo", memory_info_.ToValue()); + res->Set("diskinfo", disk_info_.ToValue()); #endif #if defined(OS_CHROMEOS) - res->Set("swapinfo", swap_info_.ToValue().release()); + res->Set("swapinfo", swap_info_.ToValue()); #endif return res.Pass(); diff --git a/chromium/base/process/process_metrics.h b/chromium/base/process/process_metrics.h index 3eb3604b7a3..5916b94148c 100644 --- a/chromium/base/process/process_metrics.h +++ b/chromium/base/process/process_metrics.h @@ -85,17 +85,6 @@ struct CommittedKBytes { 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); @@ -154,6 +143,14 @@ class BASE_EXPORT ProcessMetrics { // usage in bytes, as per definition of WorkingSetBytes. bool GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const; +#if defined(OS_MACOSX) + // Fills both CommitedKBytes and WorkingSetKBytes in a single operation. This + // is more efficient on Mac OS X, as the two can be retrieved with a single + // system call. + bool GetCommittedAndWorkingSetKBytes(CommittedKBytes* usage, + WorkingSetKBytes* ws_usage) const; +#endif + // Returns the CPU usage in percent since the last time this method or // GetPlatformIndependentCPUUsage() was called. The first time this method // is called it returns 0 and will return the actual CPU info on subsequent @@ -231,10 +228,13 @@ class BASE_EXPORT ProcessMetrics { // Returns 0 if it can't compute the commit charge. BASE_EXPORT size_t GetSystemCommitCharge(); +// Returns the number of bytes in a memory page. +BASE_EXPORT size_t GetPageSize(); + #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(); +BASE_EXPORT size_t GetMaxFds(); // Sets the file descriptor soft limit to |max_descriptors| or the OS hard // limit, whichever is lower. diff --git a/chromium/base/process/process_metrics_ios.cc b/chromium/base/process/process_metrics_ios.cc index 405c373c9fd..07f2c8de1d4 100644 --- a/chromium/base/process/process_metrics_ios.cc +++ b/chromium/base/process/process_metrics_ios.cc @@ -6,6 +6,8 @@ #include <mach/task.h> +#include "base/logging.h" + namespace base { namespace { @@ -30,6 +32,11 @@ ProcessMetrics* ProcessMetrics::CreateProcessMetrics(ProcessHandle process) { return new ProcessMetrics(process); } +double ProcessMetrics::GetCPUUsage() { + NOTIMPLEMENTED(); + return 0; +} + size_t ProcessMetrics::GetPagefileUsage() const { task_basic_info_64 task_info_data; if (!GetTaskInfo(&task_info_data)) @@ -65,4 +72,14 @@ void SetFdLimit(unsigned int max_descriptors) { // Unimplemented. } +size_t GetPageSize() { + return getpagesize(); +} + +// Bytes committed by the system. +size_t GetSystemCommitCharge() { + NOTIMPLEMENTED(); + return 0; +} + } // namespace base diff --git a/chromium/base/process/process_metrics_linux.cc b/chromium/base/process/process_metrics_linux.cc index e8db571a317..2d54c4c7265 100644 --- a/chromium/base/process/process_metrics_linux.cc +++ b/chromium/base/process/process_metrics_linux.cc @@ -399,17 +399,36 @@ size_t GetSystemCommitCharge() { 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)) + // |input| may be empty if the process disappeared somehow. + // e.g. http://crbug.com/145811. + if (input.empty()) return -1; - if (proc_stats.size() <= internal::VM_STIME) + size_t start = input.find_last_of(')'); + if (start == input.npos) return -1; - int utime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_UTIME); - int stime = GetProcStatsFieldAsInt64(proc_stats, internal::VM_STIME); - return utime + stime; + + // Number of spaces remaining until reaching utime's index starting after the + // last ')'. + int num_spaces_remaining = internal::VM_UTIME - 1; + + size_t i = start; + while ((i = input.find(' ', i + 1)) != input.npos) { + // Validate the assumption that there aren't any contiguous spaces + // in |input| before utime. + DCHECK_NE(input[i - 1], ' '); + if (--num_spaces_remaining == 0) { + int utime = 0; + int stime = 0; + if (sscanf(&input.data()[i], "%d %d", &utime, &stime) != 2) + return -1; + + return utime + stime; + } + } + + return -1; } const char kProcSelfExe[] = "/proc/self/exe"; @@ -650,13 +669,13 @@ bool GetSystemMemoryInfo(SystemMemoryInfoKB* meminfo) { } #if defined(OS_CHROMEOS) - // Report on Chrome OS GEM object graphics memory. /var/run/debugfs_gpu is a + // Report on Chrome OS GEM object graphics memory. /run/debugfs_gpu is a // bind mount into /sys/kernel/debug and synchronously reading the in-memory // files in /sys is fast. #if defined(ARCH_CPU_ARM_FAMILY) - FilePath geminfo_file("/var/run/debugfs_gpu/exynos_gem_objects"); + FilePath geminfo_file("/run/debugfs_gpu/exynos_gem_objects"); #else - FilePath geminfo_file("/var/run/debugfs_gpu/i915_gem_objects"); + FilePath geminfo_file("/run/debugfs_gpu/i915_gem_objects"); #endif std::string geminfo_data; meminfo->gem_objects = -1; diff --git a/chromium/base/process/process_metrics_mac.cc b/chromium/base/process/process_metrics_mac.cc index a07d3cd1049..f84b435a109 100644 --- a/chromium/base/process/process_metrics_mac.cc +++ b/chromium/base/process/process_metrics_mac.cc @@ -209,15 +209,32 @@ bool ProcessMetrics::GetMemoryBytes(size_t* private_bytes, } void ProcessMetrics::GetCommittedKBytes(CommittedKBytes* usage) const { + WorkingSetKBytes unused; + if (!GetCommittedAndWorkingSetKBytes(usage, &unused)) { + *usage = CommittedKBytes(); + } } bool ProcessMetrics::GetWorkingSetKBytes(WorkingSetKBytes* ws_usage) const { - size_t priv = GetWorkingSetSize(); - if (!priv) + CommittedKBytes unused; + return GetCommittedAndWorkingSetKBytes(&unused, ws_usage); +} + +bool ProcessMetrics::GetCommittedAndWorkingSetKBytes( + CommittedKBytes* usage, + WorkingSetKBytes* ws_usage) const { + task_basic_info_64 task_info_data; + if (!GetTaskInfo(TaskForPid(process_), &task_info_data)) return false; - ws_usage->priv = priv / 1024; + + usage->priv = task_info_data.virtual_size / 1024; + usage->mapped = 0; + usage->image = 0; + + ws_usage->priv = task_info_data.resident_size / 1024; ws_usage->shareable = 0; ws_usage->shared = 0; + return true; } diff --git a/chromium/base/process/process_metrics_posix.cc b/chromium/base/process/process_metrics_posix.cc index ea79d7348ff..42b3f2d6655 100644 --- a/chromium/base/process/process_metrics_posix.cc +++ b/chromium/base/process/process_metrics_posix.cc @@ -12,9 +12,8 @@ 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 *= Time::kMicrosecondsPerSecond; ret += tv.tv_usec; return ret; } @@ -69,4 +68,8 @@ void SetFdLimit(unsigned int max_descriptors) { } } +size_t GetPageSize() { + return getpagesize(); +} + } // namespace base diff --git a/chromium/base/process/process_metrics_unittest.cc b/chromium/base/process/process_metrics_unittest.cc index 69f5e837cb9..76767b09a95 100644 --- a/chromium/base/process/process_metrics_unittest.cc +++ b/chromium/base/process/process_metrics_unittest.cc @@ -143,29 +143,29 @@ TEST_F(SystemMetricsTest, ParseMeminfo) { "Hugepagesize: 4096 kB\n"; EXPECT_TRUE(ParseProcMeminfo(valid_input1, &meminfo)); - EXPECT_TRUE(meminfo.total == 3981504); - EXPECT_TRUE(meminfo.free == 140764); - EXPECT_TRUE(meminfo.buffers == 116480); - EXPECT_TRUE(meminfo.cached == 406160); - EXPECT_TRUE(meminfo.active_anon == 2972352); - EXPECT_TRUE(meminfo.active_file == 179688); - EXPECT_TRUE(meminfo.inactive_anon == 270108); - EXPECT_TRUE(meminfo.inactive_file == 202748); - EXPECT_TRUE(meminfo.swap_total == 5832280); - EXPECT_TRUE(meminfo.swap_free == 3672368); - EXPECT_TRUE(meminfo.dirty == 184); + EXPECT_EQ(meminfo.total, 3981504); + EXPECT_EQ(meminfo.free, 140764); + EXPECT_EQ(meminfo.buffers, 116480); + EXPECT_EQ(meminfo.cached, 406160); + EXPECT_EQ(meminfo.active_anon, 2972352); + EXPECT_EQ(meminfo.active_file, 179688); + EXPECT_EQ(meminfo.inactive_anon, 270108); + EXPECT_EQ(meminfo.inactive_file, 202748); + EXPECT_EQ(meminfo.swap_total, 5832280); + EXPECT_EQ(meminfo.swap_free, 3672368); + EXPECT_EQ(meminfo.dirty, 184); #if defined(OS_CHROMEOS) - EXPECT_TRUE(meminfo.shmem == 140204); - EXPECT_TRUE(meminfo.slab == 54212); + EXPECT_EQ(meminfo.shmem, 140204); + EXPECT_EQ(meminfo.slab, 54212); #endif EXPECT_TRUE(ParseProcMeminfo(valid_input2, &meminfo)); - EXPECT_TRUE(meminfo.total == 255908); - EXPECT_TRUE(meminfo.free == 69936); - EXPECT_TRUE(meminfo.buffers == 15812); - EXPECT_TRUE(meminfo.cached == 115124); - EXPECT_TRUE(meminfo.swap_total == 524280); - EXPECT_TRUE(meminfo.swap_free == 524200); - EXPECT_TRUE(meminfo.dirty == 4); + EXPECT_EQ(meminfo.total, 255908); + EXPECT_EQ(meminfo.free, 69936); + EXPECT_EQ(meminfo.buffers, 15812); + EXPECT_EQ(meminfo.cached, 115124); + EXPECT_EQ(meminfo.swap_total, 524280); + EXPECT_EQ(meminfo.swap_free, 524200); + EXPECT_EQ(meminfo.dirty, 4); } TEST_F(SystemMetricsTest, ParseVmstat) { @@ -260,13 +260,13 @@ TEST_F(SystemMetricsTest, ParseVmstat) { "pgrefill_high 0\n" "pgrefill_movable 0\n"; EXPECT_TRUE(ParseProcVmstat(valid_input1, &meminfo)); - EXPECT_TRUE(meminfo.pswpin == 179); - EXPECT_TRUE(meminfo.pswpout == 406); - EXPECT_TRUE(meminfo.pgmajfault == 487192); + EXPECT_EQ(meminfo.pswpin, 179); + EXPECT_EQ(meminfo.pswpout, 406); + EXPECT_EQ(meminfo.pgmajfault, 487192); EXPECT_TRUE(ParseProcVmstat(valid_input2, &meminfo)); - EXPECT_TRUE(meminfo.pswpin == 12); - EXPECT_TRUE(meminfo.pswpout == 901); - EXPECT_TRUE(meminfo.pgmajfault == 2023); + EXPECT_EQ(meminfo.pswpin, 12); + EXPECT_EQ(meminfo.pswpout, 901); + EXPECT_EQ(meminfo.pgmajfault, 2023); } #endif // defined(OS_LINUX) || defined(OS_ANDROID) @@ -323,6 +323,17 @@ TEST(ProcessMetricsTest, ParseProcStatCPU) { "3221224832 3221224344 3086339742 0 0 0 0 0 0 0 17 0 0 0"; EXPECT_EQ(0, base::ParseProcStatCPU(kSelfStat)); + + // Some weird long-running process with a weird name that I created for the + // purposes of this test. + const char kWeirdNameStat[] = "26115 (Hello) You ())) ) R 24614 26115 24614" + " 34839 26115 4218880 227 0 0 0 " + "5186 11 0 0 " + "20 0 1 0 36933953 4296704 90 18446744073709551615 4194304 4196116 " + "140735857761568 140735857761160 4195644 0 0 0 0 0 0 0 17 14 0 0 0 0 0 " + "6295056 6295616 16519168 140735857770710 140735857770737 " + "140735857770737 140735857774557 0"; + EXPECT_EQ(5186 + 11, base::ParseProcStatCPU(kWeirdNameStat)); } #endif // defined(OS_LINUX) || defined(OS_ANDROID) diff --git a/chromium/base/process/process_metrics_win.cc b/chromium/base/process/process_metrics_win.cc index 16db44f63f6..1dd97e629cf 100644 --- a/chromium/base/process/process_metrics_win.cc +++ b/chromium/base/process/process_metrics_win.cc @@ -284,4 +284,8 @@ size_t GetSystemCommitCharge() { return (info.CommitTotal * system_info.dwPageSize) / 1024; } +size_t GetPageSize() { + return PAGESIZE_KB * 1024; +} + } // namespace base diff --git a/chromium/base/process/process_posix.cc b/chromium/base/process/process_posix.cc index ea8fd8cea1e..8037da3ac6e 100644 --- a/chromium/base/process/process_posix.cc +++ b/chromium/base/process/process_posix.cc @@ -1,20 +1,217 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/process/process.h" #include <sys/resource.h> -#include <sys/time.h> -#include <sys/types.h> +#include <sys/wait.h> +#include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/posix/eintr_wrapper.h" #include "base/process/kill.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" + +#if defined(OS_MACOSX) +#include <sys/event.h> +#endif + +namespace { + +#if !defined(OS_NACL_NONSFI) + +bool WaitpidWithTimeout(base::ProcessHandle handle, + int* status, + base::TimeDelta wait) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait| for the process to exit. The child process may + // exit sometime before the timeout has ended but we may still block for up + // to 256 milliseconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for |wait|, sleeping for + // at most 256 milliseconds each time using usleep() and then calling + // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and + // we double it every 4 sleep cycles. + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + + if (wait == base::TimeDelta::Max()) { + return HANDLE_EINTR(waitpid(handle, status, 0)) > 0; + } + + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, status, WNOHANG)); + static const int64 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. + base::TimeTicks wakeup_time = base::TimeTicks::Now() + wait; + while (ret_pid == 0) { + base::TimeTicks now = base::TimeTicks::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Sleep for a bit while we wait for the process to finish. + if (sleep_time_usecs > max_sleep_time_usecs) + sleep_time_usecs = max_sleep_time_usecs; + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, status, WNOHANG)); + + if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && + (double_sleep_time++ % 4 == 0)) { + max_sleep_time_usecs *= 2; + } + } + + return ret_pid > 0; +} + +#if defined(OS_MACOSX) +// Using kqueue on Mac so that we can wait on non-child processes. +// We can't use kqueues on child processes because we need to reap +// our own children using wait. +static bool WaitForSingleNonChildProcess(base::ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK_GT(wait, base::TimeDelta()); + + base::ScopedFD kq(kqueue()); + if (!kq.is_valid()) { + DPLOG(ERROR) << "kqueue"; + return false; + } + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq.get(), &change, 1, NULL, 0, NULL)); + if (result == -1) { + if (errno == ESRCH) { + // If the process wasn't found, it must be dead. + return true; + } + + DPLOG(ERROR) << "kevent (setup " << handle << ")"; + return false; + } + + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + bool wait_forever = (wait == base::TimeDelta::Max()); + base::TimeDelta remaining_delta; + base::TimeTicks deadline; + if (!wait_forever) { + remaining_delta = wait; + deadline = base::TimeTicks::Now() + remaining_delta; + } + + result = -1; + struct kevent event = {0}; + + 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.get(), NULL, 0, &event, 1, remaining_timespec_ptr); + + if (result == -1 && errno == EINTR) { + if (!wait_forever) { + remaining_delta = deadline - base::TimeTicks::Now(); + } + result = 0; + } else { + break; + } + } + + if (result < 0) { + DPLOG(ERROR) << "kevent (wait " << handle << ")"; + return false; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " + << result; + return false; + } else if (result == 0) { + // Timed out. + return false; + } + + DCHECK_EQ(result, 1); + + if (event.filter != EVFILT_PROC || + (event.fflags & NOTE_EXIT) == 0 || + event.ident != static_cast<uintptr_t>(handle)) { + DLOG(ERROR) << "kevent (wait " << handle + << "): unexpected event: filter=" << event.filter + << ", fflags=" << event.fflags + << ", ident=" << event.ident; + return false; + } + + return true; +} +#endif // OS_MACOSX + +bool WaitForExitWithTimeoutImpl(base::ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + base::ProcessHandle parent_pid = base::GetParentProcessId(handle); + base::ProcessHandle our_pid = base::GetCurrentProcessHandle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, timeout); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + int status; + if (!WaitpidWithTimeout(handle, &status, timeout)) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} +#endif // !defined(OS_NACL_NONSFI) + +} // namespace namespace base { Process::Process(ProcessHandle handle) : process_(handle) { - CHECK_NE(handle, GetCurrentProcessHandle()); +} + +Process::~Process() { } Process::Process(RValue other) @@ -32,17 +229,36 @@ Process& Process::operator=(RValue other) { // static Process Process::Current() { - Process process; - process.process_ = GetCurrentProcessHandle(); - return process.Pass(); + return Process(GetCurrentProcessHandle()); +} + +// static +Process Process::Open(ProcessId pid) { + if (pid == GetCurrentProcId()) + return Current(); + + // On POSIX process handles are the same as PIDs. + return Process(pid); +} + +// static +Process Process::OpenWithExtraPrivileges(ProcessId pid) { + // On POSIX there are no privileges to set. + return Open(pid); } -#if !defined(OS_LINUX) +// static +Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) { + DCHECK_NE(handle, GetCurrentProcessHandle()); + return Process(handle); +} + +#if !defined(OS_LINUX) && !defined(OS_MACOSX) // static bool Process::CanBackgroundProcesses() { return false; } -#endif // !defined(OS_LINUX) +#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) bool Process::IsValid() const { return process_ != kNullProcessHandle; @@ -59,7 +275,7 @@ Process Process::Duplicate() const { return Process(process_); } -ProcessId Process::pid() const { +ProcessId Process::Pid() const { DCHECK(IsValid()); return GetProcId(process_); } @@ -75,15 +291,69 @@ void Process::Close() { // end up w/ a zombie when it does finally exit. } -void Process::Terminate(int result_code) { +#if !defined(OS_NACL_NONSFI) +bool Process::Terminate(int exit_code, bool wait) const { // result_code isn't supportable. DCHECK(IsValid()); - // We don't wait here. It's the responsibility of other code to reap the - // child. - KillProcess(process_, result_code, false); + DCHECK_GT(process_, 1); + bool result = kill(process_, 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_, NULL, WNOHANG)); + if (pid == process_) { + 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_; + } + + 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_, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_; + + return result; +} +#endif // !defined(OS_NACL_NONSFI) + +bool Process::WaitForExit(int* exit_code) { + return WaitForExitWithTimeout(TimeDelta::Max(), exit_code); +} + +bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) { + return WaitForExitWithTimeoutImpl(Handle(), exit_code, timeout); } -#if !defined(OS_LINUX) +#if !defined(OS_LINUX) && !defined(OS_MACOSX) bool Process::IsProcessBackgrounded() const { // See SetProcessBackgrounded(). DCHECK(IsValid()); @@ -91,17 +361,17 @@ bool Process::IsProcessBackgrounded() const { } 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. - DCHECK(IsValid()); + // Not implemented for POSIX systems other than Mac and Linux. With POSIX, if + // we were to lower the process priority we wouldn't be able to raise it back + // to its initial priority. + NOTIMPLEMENTED(); return false; } -#endif // !defined(OS_LINUX) +#endif // !defined(OS_LINUX) && !defined(OS_MACOSX) int Process::GetPriority() const { DCHECK(IsValid()); return getpriority(PRIO_PROCESS, process_); } -} // namspace base +} // namespace base diff --git a/chromium/base/process/process_unittest.cc b/chromium/base/process/process_unittest.cc index 66d6e634661..e094c032f3b 100644 --- a/chromium/base/process/process_unittest.cc +++ b/chromium/base/process/process_unittest.cc @@ -11,6 +11,9 @@ #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" +#if defined(OS_MACOSX) +#include <mach/mach.h> +#endif // OS_MACOSX namespace { @@ -69,7 +72,7 @@ TEST_F(ProcessTest, Duplicate) { Process process2 = process1.Duplicate(); ASSERT_TRUE(process1.IsValid()); ASSERT_TRUE(process2.IsValid()); - EXPECT_EQ(process1.pid(), process2.pid()); + EXPECT_EQ(process1.Pid(), process2.Pid()); EXPECT_FALSE(process1.is_current()); EXPECT_FALSE(process2.is_current()); @@ -84,7 +87,7 @@ TEST_F(ProcessTest, DuplicateCurrent) { Process process2 = process1.Duplicate(); ASSERT_TRUE(process1.IsValid()); ASSERT_TRUE(process2.IsValid()); - EXPECT_EQ(process1.pid(), process2.pid()); + EXPECT_EQ(process1.Pid(), process2.Pid()); EXPECT_TRUE(process1.is_current()); EXPECT_TRUE(process2.is_current()); @@ -92,6 +95,21 @@ TEST_F(ProcessTest, DuplicateCurrent) { ASSERT_TRUE(process2.IsValid()); } +TEST_F(ProcessTest, DeprecatedGetProcessFromHandle) { + Process process1(SpawnChild("SimpleChildProcess")); + ASSERT_TRUE(process1.IsValid()); + + Process process2 = Process::DeprecatedGetProcessFromHandle(process1.Handle()); + ASSERT_TRUE(process1.IsValid()); + ASSERT_TRUE(process2.IsValid()); + EXPECT_EQ(process1.Pid(), process2.Pid()); + EXPECT_FALSE(process1.is_current()); + EXPECT_FALSE(process2.is_current()); + + process1.Close(); + ASSERT_TRUE(process2.IsValid()); +} + MULTIPROCESS_TEST_MAIN(SleepyChildProcess) { PlatformThread::Sleep(TestTimeouts::action_max_timeout()); return 0; @@ -109,8 +127,9 @@ TEST_F(ProcessTest, Terminate) { exit_code = kDummyExitCode; int kExpectedExitCode = 250; - process.Terminate(kExpectedExitCode); - WaitForSingleProcess(process.Handle(), TestTimeouts::action_max_timeout()); + process.Terminate(kExpectedExitCode, false); + process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(), + &exit_code); EXPECT_NE(TERMINATION_STATUS_STILL_RUNNING, GetTerminationStatus(process.Handle(), &exit_code)); @@ -120,6 +139,34 @@ TEST_F(ProcessTest, Terminate) { #endif } +MULTIPROCESS_TEST_MAIN(FastSleepyChildProcess) { + PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 10); + return 0; +} + +TEST_F(ProcessTest, WaitForExit) { + Process process(SpawnChild("FastSleepyChildProcess")); + ASSERT_TRUE(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(0, exit_code); +} + +TEST_F(ProcessTest, WaitForExitWithTimeout) { + Process process(SpawnChild("SleepyChildProcess")); + ASSERT_TRUE(process.IsValid()); + + const int kDummyExitCode = 42; + int exit_code = kDummyExitCode; + TimeDelta timeout = TestTimeouts::tiny_timeout(); + EXPECT_FALSE(process.WaitForExitWithTimeout(timeout, &exit_code)); + EXPECT_EQ(kDummyExitCode, exit_code); + + process.Terminate(kDummyExitCode, false); +} + // Ensure that the priority of a process is restored correctly after // backgrounding and restoring. // Note: a platform may not be willing or able to lower the priority of @@ -127,7 +174,19 @@ TEST_F(ProcessTest, Terminate) { TEST_F(ProcessTest, SetProcessBackgrounded) { Process process(SpawnChild("SimpleChildProcess")); int old_priority = process.GetPriority(); -#if defined(OS_WIN) +#if defined(OS_MACOSX) + // On the Mac, backgrounding a process requires a port to that process. + // In the browser it's available through the MachBroker class, which is not + // part of base. Additionally, there is an indefinite amount of time between + // spawning a process and receiving its port. Because this test just checks + // the ability to background/foreground a process, we can use the current + // process's port instead. + mach_port_t process_port = mach_task_self(); + EXPECT_TRUE(process.SetProcessBackgrounded(process_port, true)); + EXPECT_TRUE(process.IsProcessBackgrounded(process_port)); + EXPECT_TRUE(process.SetProcessBackgrounded(process_port, false)); + EXPECT_FALSE(process.IsProcessBackgrounded(process_port)); +#elif defined(OS_WIN) EXPECT_TRUE(process.SetProcessBackgrounded(true)); EXPECT_TRUE(process.IsProcessBackgrounded()); EXPECT_TRUE(process.SetProcessBackgrounded(false)); @@ -145,7 +204,13 @@ TEST_F(ProcessTest, SetProcessBackgrounded) { TEST_F(ProcessTest, SetProcessBackgroundedSelf) { Process process = Process::Current(); int old_priority = process.GetPriority(); -#if defined(OS_WIN) +#if defined(OS_MACOSX) + mach_port_t process_port = mach_task_self(); + EXPECT_TRUE(process.SetProcessBackgrounded(process_port, true)); + EXPECT_TRUE(process.IsProcessBackgrounded(process_port)); + EXPECT_TRUE(process.SetProcessBackgrounded(process_port, false)); + EXPECT_FALSE(process.IsProcessBackgrounded(process_port)); +#elif defined(OS_WIN) EXPECT_TRUE(process.SetProcessBackgrounded(true)); EXPECT_TRUE(process.IsProcessBackgrounded()); EXPECT_TRUE(process.SetProcessBackgrounded(false)); diff --git a/chromium/base/process/process_util_unittest.cc b/chromium/base/process/process_util_unittest.cc index d846d1acfae..11d8874a52a 100644 --- a/chromium/base/process/process_util_unittest.cc +++ b/chromium/base/process/process_util_unittest.cc @@ -10,6 +10,8 @@ #include "base/debug/alias.h" #include "base/debug/stack_trace.h" #include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" @@ -27,21 +29,26 @@ #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" #if defined(OS_LINUX) #include <malloc.h> #include <sched.h> +#include <sys/syscall.h> #endif #if defined(OS_POSIX) #include <dlfcn.h> #include <errno.h> #include <fcntl.h> +#include <sched.h> #include <signal.h> #include <sys/resource.h> #include <sys/socket.h> +#include <sys/types.h> #include <sys/wait.h> +#include <unistd.h> #endif #if defined(OS_WIN) #include <windows.h> @@ -142,11 +149,11 @@ MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { // TODO(viettrungluu): This should be in a "MultiProcessTestTest". TEST_F(ProcessUtilTest, SpawnChild) { - base::ProcessHandle handle = SpawnChild("SimpleChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); - EXPECT_TRUE(base::WaitForSingleProcess( - handle, TestTimeouts::action_max_timeout())); - base::CloseProcessHandle(handle); + base::Process process = SpawnChild("SimpleChildProcess"); + ASSERT_TRUE(process.IsValid()); + int exit_code; + EXPECT_TRUE(process.WaitForExitWithTimeout( + TestTimeouts::action_max_timeout(), &exit_code)); } MULTIPROCESS_TEST_MAIN(SlowChildProcess) { @@ -158,12 +165,12 @@ TEST_F(ProcessUtilTest, KillSlowChild) { const std::string signal_file = ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); remove(signal_file.c_str()); - base::ProcessHandle handle = SpawnChild("SlowChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); + base::Process process = SpawnChild("SlowChildProcess"); + ASSERT_TRUE(process.IsValid()); SignalChildren(signal_file.c_str()); - EXPECT_TRUE(base::WaitForSingleProcess( - handle, TestTimeouts::action_max_timeout())); - base::CloseProcessHandle(handle); + int exit_code; + EXPECT_TRUE(process.WaitForExitWithTimeout( + TestTimeouts::action_max_timeout(), &exit_code)); remove(signal_file.c_str()); } @@ -172,21 +179,20 @@ TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) { const std::string signal_file = ProcessUtilTest::GetSignalFilePath(kSignalFileSlow); remove(signal_file.c_str()); - base::ProcessHandle handle = SpawnChild("SlowChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); + base::Process process = SpawnChild("SlowChildProcess"); + ASSERT_TRUE(process.IsValid()); int exit_code = 42; EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, - base::GetTerminationStatus(handle, &exit_code)); + base::GetTerminationStatus(process.Handle(), &exit_code)); EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); SignalChildren(signal_file.c_str()); exit_code = 42; base::TerminationStatus status = - WaitForChildTermination(handle, &exit_code); + WaitForChildTermination(process.Handle(), &exit_code); EXPECT_EQ(base::TERMINATION_STATUS_NORMAL_TERMINATION, status); EXPECT_EQ(0, exit_code); - base::CloseProcessHandle(handle); remove(signal_file.c_str()); } @@ -195,12 +201,11 @@ TEST_F(ProcessUtilTest, DISABLED_GetTerminationStatusExit) { TEST_F(ProcessUtilTest, GetProcId) { base::ProcessId id1 = base::GetProcId(GetCurrentProcess()); EXPECT_NE(0ul, id1); - base::ProcessHandle handle = SpawnChild("SimpleChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); - base::ProcessId id2 = base::GetProcId(handle); + base::Process process = SpawnChild("SimpleChildProcess"); + ASSERT_TRUE(process.IsValid()); + base::ProcessId id2 = process.Pid(); EXPECT_NE(0ul, id2); EXPECT_NE(id1, id2); - base::CloseProcessHandle(handle); } #endif @@ -239,18 +244,18 @@ TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) { const std::string signal_file = ProcessUtilTest::GetSignalFilePath(kSignalFileCrash); remove(signal_file.c_str()); - base::ProcessHandle handle = SpawnChild("CrashingChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); + base::Process process = SpawnChild("CrashingChildProcess"); + ASSERT_TRUE(process.IsValid()); int exit_code = 42; EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, - base::GetTerminationStatus(handle, &exit_code)); + base::GetTerminationStatus(process.Handle(), &exit_code)); EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); SignalChildren(signal_file.c_str()); exit_code = 42; base::TerminationStatus status = - WaitForChildTermination(handle, &exit_code); + WaitForChildTermination(process.Handle(), &exit_code); EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_CRASHED, status); #if defined(OS_WIN) @@ -261,7 +266,6 @@ TEST_F(ProcessUtilTest, MAYBE_GetTerminationStatusCrash) { int signal = WTERMSIG(exit_code); EXPECT_EQ(SIGSEGV, signal); #endif - base::CloseProcessHandle(handle); // Reset signal handlers back to "normal". base::debug::EnableInProcessStackDumping(); @@ -286,18 +290,18 @@ TEST_F(ProcessUtilTest, GetTerminationStatusKill) { const std::string signal_file = ProcessUtilTest::GetSignalFilePath(kSignalFileKill); remove(signal_file.c_str()); - base::ProcessHandle handle = SpawnChild("KilledChildProcess"); - ASSERT_NE(base::kNullProcessHandle, handle); + base::Process process = SpawnChild("KilledChildProcess"); + ASSERT_TRUE(process.IsValid()); int exit_code = 42; EXPECT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, - base::GetTerminationStatus(handle, &exit_code)); + base::GetTerminationStatus(process.Handle(), &exit_code)); EXPECT_EQ(kExpectedStillRunningExitCode, exit_code); SignalChildren(signal_file.c_str()); exit_code = 42; base::TerminationStatus status = - WaitForChildTermination(handle, &exit_code); + WaitForChildTermination(process.Handle(), &exit_code); EXPECT_EQ(base::TERMINATION_STATUS_PROCESS_WAS_KILLED, status); #if defined(OS_WIN) EXPECT_EQ(kExpectedKilledExitCode, exit_code); @@ -307,7 +311,6 @@ TEST_F(ProcessUtilTest, GetTerminationStatusKill) { int signal = WTERMSIG(exit_code); EXPECT_EQ(SIGKILL, signal); #endif - base::CloseProcessHandle(handle); remove(signal_file.c_str()); } @@ -325,7 +328,7 @@ TEST_F(ProcessUtilTest, GetAppOutput) { expected += "\r\n"; FilePath cmd(L"cmd.exe"); - CommandLine cmd_line(cmd); + base::CommandLine cmd_line(cmd); cmd_line.AppendArg("/c"); cmd_line.AppendArg("echo " + message + ""); std::string output; @@ -333,7 +336,7 @@ TEST_F(ProcessUtilTest, GetAppOutput) { EXPECT_EQ(expected, output); // Let's make sure stderr is ignored. - CommandLine other_cmd_line(cmd); + base::CommandLine other_cmd_line(cmd); other_cmd_line.AppendArg("/c"); // http://msdn.microsoft.com/library/cc772622.aspx cmd_line.AppendArg("echo " + message + " >&2"); @@ -348,22 +351,23 @@ TEST_F(ProcessUtilTest, LaunchAsUser) { ASSERT_TRUE(OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)); base::LaunchOptions options; options.as_user = token; - EXPECT_TRUE(base::LaunchProcess(MakeCmdLine("SimpleChildProcess"), options, - NULL)); + EXPECT_TRUE(base::LaunchProcess(MakeCmdLine("SimpleChildProcess"), + options).IsValid()); } static const char kEventToTriggerHandleSwitch[] = "event-to-trigger-handle"; MULTIPROCESS_TEST_MAIN(TriggerEventChildProcess) { std::string handle_value_string = - CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( kEventToTriggerHandleSwitch); CHECK(!handle_value_string.empty()); uint64 handle_value_uint64; CHECK(base::StringToUint64(handle_value_string, &handle_value_uint64)); // Give ownership of the handle to |event|. - base::WaitableEvent event(reinterpret_cast<HANDLE>(handle_value_uint64)); + base::WaitableEvent event(base::win::ScopedHandle( + reinterpret_cast<HANDLE>(handle_value_uint64))); event.Signal(); @@ -378,26 +382,26 @@ TEST_F(ProcessUtilTest, InheritSpecifiedHandles) { security_attributes.bInheritHandle = true; // Takes ownership of the event handle. - base::WaitableEvent event( - CreateEvent(&security_attributes, true, false, NULL)); + base::WaitableEvent event(base::win::ScopedHandle( + CreateEvent(&security_attributes, true, false, NULL))); base::HandlesToInheritVector handles_to_inherit; handles_to_inherit.push_back(event.handle()); base::LaunchOptions options; options.handles_to_inherit = &handles_to_inherit; - CommandLine cmd_line = MakeCmdLine("TriggerEventChildProcess"); + base::CommandLine cmd_line = MakeCmdLine("TriggerEventChildProcess"); cmd_line.AppendSwitchASCII(kEventToTriggerHandleSwitch, base::Uint64ToString(reinterpret_cast<uint64>(event.handle()))); // This functionality actually requires Vista or later. Make sure that it // fails properly on XP. if (base::win::GetVersion() < base::win::VERSION_VISTA) { - EXPECT_FALSE(base::LaunchProcess(cmd_line, options, NULL)); + EXPECT_FALSE(base::LaunchProcess(cmd_line, options).IsValid()); return; } // Launch the process and wait for it to trigger the event. - ASSERT_TRUE(base::LaunchProcess(cmd_line, options, NULL)); + ASSERT_TRUE(base::LaunchProcess(cmd_line, options).IsValid()); EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout())); } #endif // defined(OS_WIN) @@ -534,9 +538,9 @@ int ProcessUtilTest::CountOpenFDsInChild() { fd_mapping_vec.push_back(std::pair<int, int>(fds[1], kChildPipe)); base::LaunchOptions options; options.fds_to_remap = &fd_mapping_vec; - base::ProcessHandle handle = + base::Process process = SpawnChildWithOptions("ProcessUtilsLeakFDChildProcess", options); - CHECK(handle); + CHECK(process.IsValid()); int ret = IGNORE_EINTR(close(fds[1])); DPCHECK(ret == 0); @@ -548,11 +552,12 @@ int ProcessUtilTest::CountOpenFDsInChild() { #if defined(THREAD_SANITIZER) // Compiler-based ThreadSanitizer makes this test slow. - CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(3))); + base::TimeDelta timeout = base::TimeDelta::FromSeconds(3); #else - CHECK(base::WaitForSingleProcess(handle, base::TimeDelta::FromSeconds(1))); + base::TimeDelta timeout = base::TimeDelta::FromSeconds(1); #endif - base::CloseProcessHandle(handle); + int exit_code; + CHECK(process.WaitForExitWithTimeout(timeout, &exit_code)); ret = IGNORE_EINTR(close(fds[0])); DPCHECK(ret == 0); @@ -611,7 +616,7 @@ std::string TestLaunchProcess(const std::vector<std::string>& args, #else CHECK_EQ(0, clone_flags); #endif // OS_LINUX - EXPECT_TRUE(base::LaunchProcess(args, options, NULL)); + EXPECT_TRUE(base::LaunchProcess(args, options).IsValid()); PCHECK(IGNORE_EINTR(close(fds[1])) == 0); char buf[512]; @@ -683,10 +688,8 @@ TEST_F(ProcessUtilTest, LaunchProcess) { // 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( - echo_base_test, env_changes, no_clear_environ, CLONE_FS | SIGCHLD)); + EXPECT_EQ("wibble\n", TestLaunchProcess(echo_base_test, env_changes, + no_clear_environ, CLONE_FS)); } EXPECT_EQ( @@ -710,27 +713,29 @@ TEST_F(ProcessUtilTest, GetAppOutput) { argv.push_back("-c"); argv.push_back("exit 0"); - EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_TRUE(base::GetAppOutput(base::CommandLine(argv), &output)); EXPECT_STREQ("", output.c_str()); argv[2] = "exit 1"; - EXPECT_FALSE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_FALSE(base::GetAppOutput(base::CommandLine(argv), &output)); EXPECT_STREQ("", output.c_str()); argv[2] = "echo foobar42"; - EXPECT_TRUE(base::GetAppOutput(CommandLine(argv), &output)); + EXPECT_TRUE(base::GetAppOutput(base::CommandLine(argv), &output)); EXPECT_STREQ("foobar42\n", output.c_str()); #else - EXPECT_TRUE(base::GetAppOutput(CommandLine(FilePath("true")), &output)); + EXPECT_TRUE(base::GetAppOutput(base::CommandLine(FilePath("true")), + &output)); EXPECT_STREQ("", output.c_str()); - EXPECT_FALSE(base::GetAppOutput(CommandLine(FilePath("false")), &output)); + EXPECT_FALSE(base::GetAppOutput(base::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_TRUE(base::GetAppOutput(base::CommandLine(argv), &output)); EXPECT_STREQ("foobar42", output.c_str()); #endif // defined(OS_ANDROID) } @@ -754,34 +759,39 @@ TEST_F(ProcessUtilTest, MAYBE_GetAppOutputRestricted) { // 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_TRUE(base::GetAppOutputRestricted(base::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_FALSE(base::GetAppOutputRestricted(base::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_TRUE(base::GetAppOutputRestricted(base::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_TRUE(base::GetAppOutputRestricted(base::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_TRUE(base::GetAppOutputRestricted(base::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_TRUE(base::GetAppOutputRestricted(base::CommandLine(argv), &output, + 0)); EXPECT_STREQ("", output.c_str()); } @@ -796,11 +806,13 @@ TEST_F(ProcessUtilTest, GetAppOutputRestrictedSIGPIPE) { 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_TRUE(base::GetAppOutputRestricted(base::CommandLine(argv), &output, + 10)); EXPECT_STREQ("1234567890", output.c_str()); #else argv.push_back("yes"); - EXPECT_TRUE(base::GetAppOutputRestricted(CommandLine(argv), &output, 10)); + EXPECT_TRUE(base::GetAppOutputRestricted(base::CommandLine(argv), &output, + 10)); EXPECT_STREQ("y\ny\ny\ny\ny\n", output.c_str()); #endif } @@ -826,14 +838,16 @@ TEST_F(ProcessUtilTest, MAYBE_GetAppOutputRestrictedNoZombies) { // 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_TRUE(base::GetAppOutputRestricted(base::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_TRUE(base::GetAppOutputRestricted(base::CommandLine(argv), &output, + 10)); EXPECT_STREQ("1234567890", output.c_str()); } } @@ -846,7 +860,7 @@ TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) { 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, + EXPECT_TRUE(base::GetAppOutputWithExitCode(base::CommandLine(argv), &output, &exit_code)); EXPECT_STREQ("foo\n", output.c_str()); EXPECT_EQ(exit_code, 0); @@ -855,7 +869,7 @@ TEST_F(ProcessUtilTest, GetAppOutputWithExitCode) { // code. output.clear(); argv[2] = "echo foo; exit 2"; - EXPECT_TRUE(base::GetAppOutputWithExitCode(CommandLine(argv), &output, + EXPECT_TRUE(base::GetAppOutputWithExitCode(base::CommandLine(argv), &output, &exit_code)); EXPECT_STREQ("foo\n", output.c_str()); EXPECT_EQ(exit_code, 2); @@ -876,14 +890,15 @@ bool IsProcessDead(base::ProcessHandle child) { } TEST_F(ProcessUtilTest, DelayedTermination) { - base::ProcessHandle child_process = SpawnChild("process_util_test_never_die"); - ASSERT_TRUE(child_process); - base::EnsureProcessTerminated(child_process); - base::WaitForSingleProcess(child_process, base::TimeDelta::FromSeconds(5)); + base::Process child_process = SpawnChild("process_util_test_never_die"); + ASSERT_TRUE(child_process.IsValid()); + base::EnsureProcessTerminated(child_process.Duplicate()); + int exit_code; + child_process.WaitForExitWithTimeout(base::TimeDelta::FromSeconds(5), + &exit_code); // Check that process was really killed. - EXPECT_TRUE(IsProcessDead(child_process)); - base::CloseProcessHandle(child_process); + EXPECT_TRUE(IsProcessDead(child_process.Handle())); } MULTIPROCESS_TEST_MAIN(process_util_test_never_die) { @@ -894,20 +909,159 @@ MULTIPROCESS_TEST_MAIN(process_util_test_never_die) { } TEST_F(ProcessUtilTest, ImmediateTermination) { - base::ProcessHandle child_process = - SpawnChild("process_util_test_die_immediately"); - ASSERT_TRUE(child_process); + base::Process child_process = SpawnChild("process_util_test_die_immediately"); + ASSERT_TRUE(child_process.IsValid()); // Give it time to die. sleep(2); - base::EnsureProcessTerminated(child_process); + base::EnsureProcessTerminated(child_process.Duplicate()); // Check that process was really killed. - EXPECT_TRUE(IsProcessDead(child_process)); - base::CloseProcessHandle(child_process); + EXPECT_TRUE(IsProcessDead(child_process.Handle())); } MULTIPROCESS_TEST_MAIN(process_util_test_die_immediately) { return 0; } +#if !defined(OS_ANDROID) +const char kPipeValue = '\xcc'; + +class ReadFromPipeDelegate : public base::LaunchOptions::PreExecDelegate { + public: + explicit ReadFromPipeDelegate(int fd) : fd_(fd) {} + ~ReadFromPipeDelegate() override {} + void RunAsyncSafe() override { + char c; + RAW_CHECK(HANDLE_EINTR(read(fd_, &c, 1)) == 1); + RAW_CHECK(IGNORE_EINTR(close(fd_)) == 0); + RAW_CHECK(c == kPipeValue); + } + + private: + int fd_; + DISALLOW_COPY_AND_ASSIGN(ReadFromPipeDelegate); +}; + +TEST_F(ProcessUtilTest, PreExecHook) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + + base::ScopedFD read_fd(pipe_fds[0]); + base::ScopedFD write_fd(pipe_fds[1]); + base::FileHandleMappingVector fds_to_remap; + fds_to_remap.push_back(std::make_pair(read_fd.get(), read_fd.get())); + + ReadFromPipeDelegate read_from_pipe_delegate(read_fd.get()); + base::LaunchOptions options; + options.fds_to_remap = &fds_to_remap; + options.pre_exec_delegate = &read_from_pipe_delegate; + base::Process process(SpawnChildWithOptions("SimpleChildProcess", options)); + ASSERT_TRUE(process.IsValid()); + + read_fd.reset(); + ASSERT_EQ(1, HANDLE_EINTR(write(write_fd.get(), &kPipeValue, 1))); + + int exit_code = 42; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(0, exit_code); +} +#endif // !defined(OS_ANDROID) + #endif // defined(OS_POSIX) + +#if defined(OS_LINUX) +const int kSuccess = 0; + +MULTIPROCESS_TEST_MAIN(CheckPidProcess) { + const pid_t kInitPid = 1; + const pid_t pid = syscall(__NR_getpid); + CHECK(pid == kInitPid); + CHECK(getpid() == pid); + return kSuccess; +} + +TEST_F(ProcessUtilTest, CloneFlags) { + if (RunningOnValgrind() || + !base::PathExists(FilePath("/proc/self/ns/user")) || + !base::PathExists(FilePath("/proc/self/ns/pid"))) { + // User or PID namespaces are not supported. + return; + } + + base::LaunchOptions options; + options.clone_flags = CLONE_NEWUSER | CLONE_NEWPID; + + base::Process process(SpawnChildWithOptions("CheckPidProcess", options)); + ASSERT_TRUE(process.IsValid()); + + int exit_code = 42; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(kSuccess, exit_code); +} + +TEST(ForkWithFlagsTest, UpdatesPidCache) { + // The libc clone function, which allows ForkWithFlags to keep the pid cache + // up to date, does not work on Valgrind. + if (RunningOnValgrind()) { + return; + } + + // Warm up the libc pid cache, if there is one. + ASSERT_EQ(syscall(__NR_getpid), getpid()); + + pid_t ctid = 0; + const pid_t pid = + base::ForkWithFlags(SIGCHLD | CLONE_CHILD_SETTID, nullptr, &ctid); + if (pid == 0) { + // In child. Check both the raw getpid syscall and the libc getpid wrapper + // (which may rely on a pid cache). + RAW_CHECK(syscall(__NR_getpid) == ctid); + RAW_CHECK(getpid() == ctid); + _exit(kSuccess); + } + + ASSERT_NE(-1, pid); + int status = 42; + ASSERT_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(kSuccess, WEXITSTATUS(status)); +} + +MULTIPROCESS_TEST_MAIN(CheckCwdProcess) { + base::FilePath expected; + CHECK(base::GetTempDir(&expected)); + base::FilePath actual; + CHECK(base::GetCurrentDirectory(&actual)); + CHECK(actual == expected); + return kSuccess; +} + +TEST_F(ProcessUtilTest, CurrentDirectory) { + // TODO(rickyz): Add support for passing arguments to multiprocess children, + // then create a special directory for this test. + base::FilePath tmp_dir; + ASSERT_TRUE(base::GetTempDir(&tmp_dir)); + + base::LaunchOptions options; + options.current_directory = tmp_dir; + + base::Process process(SpawnChildWithOptions("CheckCwdProcess", options)); + ASSERT_TRUE(process.IsValid()); + + int exit_code = 42; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_EQ(kSuccess, exit_code); +} + +TEST_F(ProcessUtilTest, InvalidCurrentDirectory) { + base::LaunchOptions options; + options.current_directory = base::FilePath("/dev/null"); + + base::Process process(SpawnChildWithOptions("SimpleChildProcess", options)); + ASSERT_TRUE(process.IsValid()); + + int exit_code = kSuccess; + EXPECT_TRUE(process.WaitForExit(&exit_code)); + EXPECT_NE(kSuccess, exit_code); +} +#endif diff --git a/chromium/base/process/process_win.cc b/chromium/base/process/process_win.cc index 05041b20dda..0d312a35990 100644 --- a/chromium/base/process/process_win.cc +++ b/chromium/base/process/process_win.cc @@ -6,8 +6,18 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" +#include "base/numerics/safe_conversions.h" +#include "base/process/kill.h" #include "base/win/windows_version.h" +namespace { + +DWORD kBasicProcessAccess = + PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | SYNCHRONIZE; + +} // namespace + namespace base { Process::Process(ProcessHandle handle) @@ -22,6 +32,9 @@ Process::Process(RValue other) other.object->Close(); } +Process::~Process() { +} + Process& Process::operator=(RValue other) { if (this != other.object) { process_.Set(other.object->process_.Take()); @@ -39,6 +52,34 @@ Process Process::Current() { } // static +Process Process::Open(ProcessId pid) { + return Process(::OpenProcess(kBasicProcessAccess, FALSE, pid)); +} + +// static +Process Process::OpenWithExtraPrivileges(ProcessId pid) { + DWORD access = kBasicProcessAccess | PROCESS_DUP_HANDLE | PROCESS_VM_READ; + return Process(::OpenProcess(access, FALSE, pid)); +} + +// static +Process Process::OpenWithAccess(ProcessId pid, DWORD desired_access) { + return Process(::OpenProcess(desired_access, FALSE, pid)); +} + +// static +Process Process::DeprecatedGetProcessFromHandle(ProcessHandle handle) { + DCHECK_NE(handle, ::GetCurrentProcess()); + ProcessHandle out_handle; + if (!::DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), &out_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return Process(); + } + return Process(out_handle); +} + +// static bool Process::CanBackgroundProcesses() { return true; } @@ -68,7 +109,7 @@ Process Process::Duplicate() const { return Process(out_handle); } -ProcessId Process::pid() const { +ProcessId Process::Pid() const { DCHECK(IsValid()); return GetProcId(Handle()); } @@ -85,17 +126,36 @@ void Process::Close() { process_.Close(); } -void Process::Terminate(int result_code) { +bool Process::Terminate(int exit_code, bool wait) const { DCHECK(IsValid()); + bool result = (::TerminateProcess(Handle(), exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (::WaitForSingleObject(Handle(), 60 * 1000) != WAIT_OBJECT_0) + DPLOG(ERROR) << "Error waiting for process exit"; + } else if (!result) { + DPLOG(ERROR) << "Unable to terminate process"; + } + return result; +} + +bool Process::WaitForExit(int* exit_code) { + return WaitForExitWithTimeout(TimeDelta::FromMilliseconds(INFINITE), + exit_code); +} - // 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(Handle(), result_code); +bool Process::WaitForExitWithTimeout(TimeDelta timeout, int* exit_code) { + // Limit timeout to INFINITE. + DWORD timeout_ms = saturated_cast<DWORD>(timeout.InMilliseconds()); + if (::WaitForSingleObject(Handle(), timeout_ms) != WAIT_OBJECT_0) + return false; + + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(Handle(), &temp_code)) + return false; + + *exit_code = temp_code; + return true; } bool Process::IsProcessBackgrounded() const { @@ -117,7 +177,21 @@ bool Process::SetProcessBackgrounded(bool value) { priority = value ? PROCESS_MODE_BACKGROUND_BEGIN : PROCESS_MODE_BACKGROUND_END; } else { - priority = value ? BELOW_NORMAL_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS; + // Experiment (http://crbug.com/458594) with using IDLE_PRIORITY_CLASS as a + // background priority for background renderers (this code path is + // technically for more than just the renderers but they're the only use + // case in practice and experimenting here direclty is thus easier -- plus + // it doesn't really hurt as above we already state our intent of using + // PROCESS_MODE_BACKGROUND_BEGIN if available which is essentially + // IDLE_PRIORITY_CLASS plus lowered IO priority). Enabled by default in the + // asbence of field trials to get coverage on the perf waterfall. + DWORD background_priority = IDLE_PRIORITY_CLASS; + base::FieldTrial* trial = + base::FieldTrialList::Find("BackgroundRendererProcesses"); + if (trial && trial->group_name() == "AllowBelowNormalFromBrowser") + background_priority = BELOW_NORMAL_PRIORITY_CLASS; + + priority = value ? background_priority : NORMAL_PRIORITY_CLASS; } return (::SetPriorityClass(Handle(), priority) != 0); |